mirror of
https://github.com/rapid7/metasploit-payloads
synced 2025-03-12 12:14:29 +01:00
Land #60, Add ADSI support to ExtAPI
This commit is contained in:
commit
880c247554
c/meterpreter
source/extensions/extapi
workspace/ext_server_extapi
171
c/meterpreter/source/extensions/extapi/adsi.c
Normal file
171
c/meterpreter/source/extensions/extapi/adsi.c
Normal file
@ -0,0 +1,171 @@
|
||||
/*!
|
||||
* @file adsi.c
|
||||
* @brief Definitions for ADSI functionality.
|
||||
*/
|
||||
#include "extapi.h"
|
||||
#include "adsi.h"
|
||||
#include "adsi_interface.h"
|
||||
|
||||
/*! @brief The default page size to use when no page size is specified */
|
||||
#define DEFAULT_PAGE_SIZE 1000
|
||||
|
||||
/*!
|
||||
* @brief Helper function that converts an ASCII string to a wide char string.
|
||||
* @param lpValue ASCII string to convert.
|
||||
* @param lpwValue Target memory for the converted string.
|
||||
* @remark \c lpwValue must be freed by the caller using `free`.
|
||||
* @returns Indication of success or failure.
|
||||
*/
|
||||
DWORD to_wide_string(LPSTR lpValue, LPWSTR* lpwValue)
|
||||
{
|
||||
size_t charsCopied = 0;
|
||||
DWORD valueLength;
|
||||
DWORD dwResult;
|
||||
|
||||
do
|
||||
{
|
||||
if (lpValue == NULL)
|
||||
{
|
||||
BREAK_WITH_ERROR("[EXTAPI ADSI] Value parameter missing", ERROR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
valueLength = lstrlenA(lpValue);
|
||||
*lpwValue = (LPWSTR)malloc(sizeof(WCHAR)* (lstrlenA(lpValue) + 1));
|
||||
if (*lpwValue == NULL)
|
||||
{
|
||||
BREAK_WITH_ERROR("[EXTAPI ADSI] Unable to allocate memory", ERROR_OUTOFMEMORY);
|
||||
}
|
||||
|
||||
mbstowcs_s(&charsCopied, *lpwValue, valueLength + 1, lpValue, valueLength);
|
||||
dwResult = ERROR_SUCCESS;
|
||||
} while (0);
|
||||
|
||||
return dwResult;
|
||||
}
|
||||
|
||||
/*!
|
||||
* @brief Enumerate all the users in AD.
|
||||
* @param remote Pointer to the \c Remote instance.
|
||||
* @param packet Pointer to the incoming \c Packet instance.
|
||||
* @returns Indication of success or failure.
|
||||
* @remark Real error codes are returned to the caller via a response packet.
|
||||
*/
|
||||
DWORD request_adsi_domain_query(Remote *remote, Packet *packet)
|
||||
{
|
||||
DWORD dwResult = ERROR_SUCCESS;
|
||||
LPSTR lpValue = NULL;
|
||||
LPWSTR lpwDomain = NULL;
|
||||
LPWSTR lpwFilter = NULL;
|
||||
LPWSTR* lpwFields = NULL;
|
||||
DWORD fieldCount = 0;
|
||||
DWORD fieldIndex = 0;
|
||||
Packet * response = packet_create_response(packet);
|
||||
Tlv fieldTlv;
|
||||
DWORD maxResults;
|
||||
DWORD pageSize;
|
||||
|
||||
do
|
||||
{
|
||||
if (!response)
|
||||
{
|
||||
BREAK_WITH_ERROR("[EXTAPI ADSI] Unable to create response packet", ERROR_OUTOFMEMORY);
|
||||
}
|
||||
|
||||
lpValue = packet_get_tlv_value_string(packet, TLV_TYPE_EXT_ADSI_DOMAIN);
|
||||
dprintf("[EXTAPI ADSI] Domain: %s", lpValue);
|
||||
dwResult = to_wide_string(lpValue, &lpwDomain);
|
||||
if (dwResult != ERROR_SUCCESS)
|
||||
{
|
||||
dprintf("[EXTAPI ADSI] Failed to get Domain");
|
||||
break;
|
||||
}
|
||||
|
||||
lpValue = packet_get_tlv_value_string(packet, TLV_TYPE_EXT_ADSI_FILTER);
|
||||
dprintf("[EXTAPI ADSI] Filter: %s", lpValue);
|
||||
dwResult = to_wide_string(lpValue, &lpwFilter);
|
||||
if (dwResult != ERROR_SUCCESS)
|
||||
{
|
||||
dprintf("[EXTAPI ADSI] Failed to get Filter");
|
||||
break;
|
||||
}
|
||||
|
||||
maxResults = packet_get_tlv_value_uint(packet, TLV_TYPE_EXT_ASDI_MAXRESULTS);
|
||||
dprintf("[EXTAPI ADSI] Max results will be %u", maxResults);
|
||||
|
||||
pageSize = packet_get_tlv_value_uint(packet, TLV_TYPE_EXT_ASDI_PAGESIZE);
|
||||
dprintf("[EXTAPI ADSI] Page size specified as %u", pageSize);
|
||||
|
||||
// Set the page size to something sensible if not given.
|
||||
if (pageSize == 0)
|
||||
{
|
||||
pageSize = DEFAULT_PAGE_SIZE;
|
||||
}
|
||||
|
||||
// If max results is given, there's no point in having a page size
|
||||
// that's bigger!
|
||||
if (maxResults != 0)
|
||||
{
|
||||
pageSize = min(pageSize, maxResults);
|
||||
}
|
||||
dprintf("[EXTAPI ADSI] Page size will be %u", pageSize);
|
||||
|
||||
while (packet_enum_tlv(packet, fieldCount, TLV_TYPE_EXT_ADSI_FIELD, &fieldTlv) == ERROR_SUCCESS)
|
||||
{
|
||||
lpValue = (char*)fieldTlv.buffer;
|
||||
dprintf("[EXTAPI ADSI] Field %u: %s", fieldCount, lpValue);
|
||||
lpwFields = (LPWSTR*)realloc(lpwFields, sizeof(LPWSTR) * (fieldCount + 1));
|
||||
|
||||
if (lpwFields == NULL)
|
||||
{
|
||||
BREAK_WITH_ERROR("[EXTAPI ADSI] Unable to allocate memory", ERROR_OUTOFMEMORY);
|
||||
}
|
||||
|
||||
dwResult = to_wide_string(lpValue, &lpwFields[fieldCount]);
|
||||
if (dwResult != ERROR_SUCCESS)
|
||||
{
|
||||
dprintf("[EXTAPI ADSI] Failed to get field as wide string");
|
||||
break;
|
||||
}
|
||||
++fieldCount;
|
||||
}
|
||||
|
||||
dprintf("[EXTAPI ADSI] Field count: %u", fieldCount, lpValue);
|
||||
|
||||
if (dwResult == ERROR_SUCCESS)
|
||||
{
|
||||
dprintf("[EXTAPI ADSI] Beginning user enumeration");
|
||||
dwResult = domain_query(lpwDomain, lpwFilter, lpwFields, fieldCount, maxResults, pageSize, response);
|
||||
dprintf("[EXTAPI ADSI] Result of processing: %u (0x%x)", dwResult, dwResult);
|
||||
}
|
||||
} while (0);
|
||||
|
||||
if (lpwFields)
|
||||
{
|
||||
for (fieldIndex = 0; fieldIndex < fieldCount; ++fieldIndex)
|
||||
{
|
||||
if (lpwFields[fieldIndex])
|
||||
{
|
||||
free(lpwFields[fieldIndex]);
|
||||
}
|
||||
}
|
||||
free(lpwFields);
|
||||
}
|
||||
|
||||
if (lpwFilter)
|
||||
{
|
||||
free(lpwFilter);
|
||||
}
|
||||
|
||||
if (lpwDomain)
|
||||
{
|
||||
free(lpwDomain);
|
||||
}
|
||||
|
||||
dprintf("[EXTAPI ADSI] Transmitting response back to caller.");
|
||||
if (response)
|
||||
{
|
||||
packet_transmit_response(dwResult, remote, response);
|
||||
}
|
||||
|
||||
return dwResult;
|
||||
}
|
12
c/meterpreter/source/extensions/extapi/adsi.h
Normal file
12
c/meterpreter/source/extensions/extapi/adsi.h
Normal file
@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* @file adsi.h
|
||||
* @brief Declarations for ADSI support.
|
||||
*/
|
||||
#ifndef _METERPRETER_SOURCE_EXTENSION_EXTAPI_ADSI_H
|
||||
#define _METERPRETER_SOURCE_EXTENSION_EXTAPI_ADSI_H
|
||||
|
||||
//DWORD request_adsi_user_enum(Remote *remote, Packet *packet);
|
||||
//DWORD request_adsi_computer_enum(Remote *remote, Packet *packet);
|
||||
DWORD request_adsi_domain_query(Remote *remote, Packet *packet);
|
||||
|
||||
#endif
|
199
c/meterpreter/source/extensions/extapi/adsi_interface.cpp
Normal file
199
c/meterpreter/source/extensions/extapi/adsi_interface.cpp
Normal file
@ -0,0 +1,199 @@
|
||||
/*!
|
||||
* @file adsi_interface.cpp
|
||||
* @brief Definitions for functions that directly interact with ADSI
|
||||
* through the (awful) COM interface.
|
||||
*/
|
||||
extern "C" {
|
||||
#include "extapi.h"
|
||||
#include <Iads.h>
|
||||
#include <Adshlp.h>
|
||||
#include <AdsErr.h>
|
||||
#include "adsi_interface.h"
|
||||
}
|
||||
|
||||
#pragma comment(lib, "Activeds.lib")
|
||||
|
||||
#define VALUE_SIZE 512
|
||||
#define PATH_SIZE 256
|
||||
|
||||
/*! @brief The GUID of the Directory Search COM object. */
|
||||
static const IID IID_IDirectorySearch = { 0x109BA8EC, 0x92F0, 0x11D0, { 0xA7, 0x90, 0x00, 0xC0, 0x4F, 0xD8, 0xD5, 0xA8 } };
|
||||
|
||||
/*!
|
||||
* @brief Perform a domain query via ADSI.
|
||||
* @param lpwDomain Name of the domain that is to be queried.
|
||||
* @param lpwFilter The filter to use when reading objects (LDAP style).
|
||||
* @param lpwQueryCols Array of column names representing fields to extract.
|
||||
* @param queryColCount Number of columns in \c lpwQueryCols.
|
||||
* @param maxResults The maximum number of results to return.
|
||||
* @param pageSize The size of the page of results to return.
|
||||
* @param response The response \c Packet to add the results to.
|
||||
*/
|
||||
DWORD domain_query(LPCWSTR lpwDomain, LPWSTR lpwFilter, LPWSTR* lpwQueryCols,
|
||||
UINT queryColCount, DWORD maxResults, DWORD pageSize, Packet* response)
|
||||
{
|
||||
HRESULT hResult;
|
||||
WCHAR cbPath[PATH_SIZE];
|
||||
|
||||
swprintf_s(cbPath, PATH_SIZE - 1, L"LDAP://%s", lpwDomain);
|
||||
|
||||
if ((hResult = CoInitialize(NULL)) == S_OK)
|
||||
{
|
||||
IDirectorySearch* pDirSearch = NULL;
|
||||
ADS_SEARCH_HANDLE hSearch = NULL;
|
||||
|
||||
do
|
||||
{
|
||||
// start by trying to create the search object which we can use to run searches
|
||||
hResult = ADsOpenObject(cbPath, NULL, NULL, ADS_SECURE_AUTHENTICATION | ADS_READONLY_SERVER, IID_IDirectorySearch, (void**)&pDirSearch);
|
||||
if (hResult != S_OK)
|
||||
{
|
||||
dprintf("[ADSI] Unable to open domain: %x", hResult);
|
||||
break;
|
||||
}
|
||||
|
||||
// set the limit of results so that we don't take forever on large domains
|
||||
ADS_SEARCHPREF_INFO prefInfo[4];
|
||||
prefInfo[0].dwSearchPref = ADS_SEARCHPREF_SIZE_LIMIT;
|
||||
prefInfo[0].vValue.dwType = ADSTYPE_INTEGER;
|
||||
prefInfo[0].vValue.Integer = (ADS_INTEGER)maxResults;
|
||||
prefInfo[1].dwSearchPref = ADS_SEARCHPREF_PAGESIZE;
|
||||
prefInfo[1].vValue.dwType = ADSTYPE_INTEGER;
|
||||
prefInfo[1].vValue.Integer = (ADS_INTEGER)pageSize;
|
||||
prefInfo[2].dwSearchPref = ADS_SEARCHPREF_SEARCH_SCOPE;
|
||||
prefInfo[2].vValue.dwType = ADSTYPE_INTEGER;
|
||||
prefInfo[2].vValue.Integer = ADS_SCOPE_SUBTREE;
|
||||
prefInfo[3].dwSearchPref = ADS_SEARCHPREF_CACHE_RESULTS;
|
||||
prefInfo[3].vValue.dwType = ADSTYPE_BOOLEAN;
|
||||
prefInfo[3].vValue.Boolean = false;
|
||||
|
||||
dprintf("[ADSI] Setting Max results to %u", (ADS_INTEGER)maxResults);
|
||||
dprintf("[ADSI] Setting Page size to %u", (ADS_INTEGER)pageSize);
|
||||
if (FAILED(hResult = pDirSearch->SetSearchPreference(prefInfo, 4)))
|
||||
{
|
||||
dprintf("[ADSI] Failed to set search settings %u %x", pageSize, hResult);
|
||||
}
|
||||
|
||||
dprintf("[ADSI] Search executing");
|
||||
hResult = pDirSearch->ExecuteSearch(lpwFilter, lpwQueryCols, queryColCount, &hSearch);
|
||||
if (hResult != S_OK)
|
||||
{
|
||||
dprintf("[ADSI] Unable to execute the search");
|
||||
break;
|
||||
}
|
||||
|
||||
// These buffers are used to store the values that we're reading out of AD
|
||||
Tlv* entries = (Tlv*)malloc(queryColCount * sizeof(Tlv));
|
||||
char* values = (char*)malloc(queryColCount * VALUE_SIZE);
|
||||
|
||||
DWORD rowsProcessed = 0;
|
||||
|
||||
// now we iterate through the search results
|
||||
while (SUCCEEDED((hResult = pDirSearch->GetNextRow(hSearch))) && (maxResults == 0 || rowsProcessed < maxResults))
|
||||
{
|
||||
if (hResult == S_ADS_NOMORE_ROWS)
|
||||
{
|
||||
hResult = S_OK;
|
||||
|
||||
// out of results, so bomb out of the loop
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD dwIndex = 0;
|
||||
size_t charsConverted;
|
||||
ADS_SEARCH_COLUMN col;
|
||||
|
||||
// iterate through the columns, adding Tlv entries as we go, but only
|
||||
// if we can get the values out.
|
||||
for (DWORD colIndex = 0; colIndex < queryColCount; ++colIndex)
|
||||
{
|
||||
char* valueTarget = values + dwIndex * VALUE_SIZE;
|
||||
|
||||
entries[dwIndex].buffer = (PUCHAR)valueTarget;
|
||||
entries[dwIndex].header.type = TLV_TYPE_EXT_ADSI_VALUE;
|
||||
|
||||
// try to do something sane based on the type that's being used to store
|
||||
// the value.
|
||||
HRESULT hr = pDirSearch->GetColumn(hSearch, lpwQueryCols[dwIndex], &col);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
switch (col.dwADsType)
|
||||
{
|
||||
case ADSTYPE_LARGE_INTEGER:
|
||||
_i64toa_s(col.pADsValues->LargeInteger.QuadPart, valueTarget, VALUE_SIZE, 10);
|
||||
entries[dwIndex].header.length = lstrlenA(valueTarget) + 1;
|
||||
dprintf("[ADSI] Adding large int value %ul", (UINT)col.pADsValues->Integer);
|
||||
break;
|
||||
case ADSTYPE_INTEGER:
|
||||
_itoa_s((UINT)col.pADsValues->Integer, valueTarget, VALUE_SIZE, 10);
|
||||
entries[dwIndex].header.length = lstrlenA(valueTarget) + 1;
|
||||
dprintf("[ADSI] Adding int value %u", (UINT)col.pADsValues->Integer);
|
||||
break;
|
||||
default:
|
||||
WCHAR* source = col.dwADsType == ADSTYPE_CASE_IGNORE_STRING
|
||||
? col.pADsValues->CaseIgnoreString
|
||||
: col.pADsValues->CaseExactString;
|
||||
|
||||
wcstombs_s(&charsConverted, valueTarget, VALUE_SIZE, source, VALUE_SIZE - 1);
|
||||
dprintf("[ADSI] Adding string %s", valueTarget);
|
||||
entries[dwIndex].header.length = lstrlenA(valueTarget) + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
pDirSearch->FreeColumn(&col);
|
||||
}
|
||||
else
|
||||
{
|
||||
dprintf("[ADSI] Col read failed: %x", hr);
|
||||
valueTarget[0] = 0;
|
||||
entries[dwIndex].header.length = 1;
|
||||
}
|
||||
|
||||
dwIndex++;
|
||||
}
|
||||
|
||||
if (dwIndex > 0)
|
||||
{
|
||||
dprintf("[ADSI] Adding group packet of %u values", dwIndex);
|
||||
// Throw the user details together in a group, ready to return.
|
||||
packet_add_tlv_group(response, TLV_TYPE_EXT_ADSI_RESULT, entries, dwIndex);
|
||||
dprintf("[ADSI] Added group packet of %u values", dwIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
dprintf("[ADSI] Item found, but no fields extracted.");
|
||||
}
|
||||
|
||||
++rowsProcessed;
|
||||
}
|
||||
|
||||
dprintf("[ADSI] Processed %u. Final result: %u (0x%x)", rowsProcessed, hResult, hResult);
|
||||
|
||||
if (SUCCEEDED(hResult))
|
||||
{
|
||||
hResult = S_OK;
|
||||
}
|
||||
|
||||
free(entries);
|
||||
free(values);
|
||||
} while (0);
|
||||
|
||||
if (hSearch != NULL)
|
||||
{
|
||||
pDirSearch->CloseSearchHandle(hSearch);
|
||||
}
|
||||
|
||||
if (pDirSearch != NULL)
|
||||
{
|
||||
pDirSearch->Release();
|
||||
}
|
||||
|
||||
CoUninitialize();
|
||||
}
|
||||
else
|
||||
{
|
||||
dprintf("[ADSI] Failed to initialize COM");
|
||||
}
|
||||
|
||||
return (DWORD)hResult;
|
||||
}
|
12
c/meterpreter/source/extensions/extapi/adsi_interface.h
Normal file
12
c/meterpreter/source/extensions/extapi/adsi_interface.h
Normal file
@ -0,0 +1,12 @@
|
||||
/*!
|
||||
* @file adsi_interface.h
|
||||
* @brief Declarations for functions that deal directly with ADSI
|
||||
* via the COM interfaces (hence the .cpp extension).
|
||||
*/
|
||||
#ifndef _METERPRETER_SOURCE_EXTENSION_EXTAPI_ADSI_INTERFACE_H
|
||||
#define _METERPRETER_SOURCE_EXTENSION_EXTAPI_ADSI_INTERFACE_H
|
||||
|
||||
DWORD domain_query(LPCWSTR lpwDomain, LPWSTR lpwFilter, LPWSTR* lpwQueryCols,
|
||||
UINT queryColCount, DWORD maxResults, DWORD pageSize, Packet* response);
|
||||
|
||||
#endif
|
@ -13,6 +13,7 @@
|
||||
#include "window.h"
|
||||
#include "service.h"
|
||||
#include "clipboard.h"
|
||||
#include "adsi.h"
|
||||
|
||||
// this sets the delay load hook function, see DelayLoadMetSrv.h
|
||||
EnableDelayLoadMetSrv();
|
||||
@ -25,6 +26,7 @@ Command customCommands[] =
|
||||
COMMAND_REQ("extapi_service_query", request_service_query),
|
||||
COMMAND_REQ("extapi_clipboard_get_data", request_clipboard_get_data),
|
||||
COMMAND_REQ("extapi_clipboard_set_data", request_clipboard_set_data),
|
||||
COMMAND_REQ("extapi_adsi_domain_query", request_adsi_domain_query),
|
||||
COMMAND_TERMINATOR
|
||||
};
|
||||
|
||||
|
@ -42,4 +42,12 @@
|
||||
#define TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY MAKE_CUSTOM_TLV(TLV_META_TYPE_UINT, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 47)
|
||||
#define TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA MAKE_CUSTOM_TLV(TLV_META_TYPE_RAW, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 48)
|
||||
|
||||
#define TLV_TYPE_EXT_ADSI_DOMAIN MAKE_CUSTOM_TLV(TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 55)
|
||||
#define TLV_TYPE_EXT_ADSI_FILTER MAKE_CUSTOM_TLV(TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 56)
|
||||
#define TLV_TYPE_EXT_ADSI_FIELD MAKE_CUSTOM_TLV(TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 57)
|
||||
#define TLV_TYPE_EXT_ADSI_VALUE MAKE_CUSTOM_TLV(TLV_META_TYPE_STRING, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 58)
|
||||
#define TLV_TYPE_EXT_ADSI_RESULT MAKE_CUSTOM_TLV(TLV_META_TYPE_GROUP, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 59)
|
||||
#define TLV_TYPE_EXT_ASDI_MAXRESULTS MAKE_CUSTOM_TLV(TLV_META_TYPE_UINT, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 60)
|
||||
#define TLV_TYPE_EXT_ASDI_PAGESIZE MAKE_CUSTOM_TLV(TLV_META_TYPE_UINT, TLV_TYPE_EXTENSION_EXTAPI, TLV_EXTENSIONS + 61)
|
||||
|
||||
#endif
|
||||
|
@ -423,6 +423,8 @@ copy /y "$(TargetDir)$(TargetFileName)" "$(ProjectDir)..\..\output\$(PlatformSho
|
||||
</PostBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\..\source\extensions\extapi\adsi.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\adsi_interface.cpp" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\clipboard.c" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\clipboard_image.cpp" />
|
||||
<ClCompile Include="..\..\source\extensions\extapi\extapi.c" />
|
||||
@ -430,6 +432,8 @@ copy /y "$(TargetDir)$(TargetFileName)" "$(ProjectDir)..\..\output\$(PlatformSho
|
||||
<ClCompile Include="..\..\source\extensions\extapi\window.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\..\source\extensions\extapi\adsi.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\adsi_interface.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\clipboard.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\clipboard_image.h" />
|
||||
<ClInclude Include="..\..\source\extensions\extapi\extapi.h" />
|
||||
|
Loading…
x
Reference in New Issue
Block a user