1
mirror of https://github.com/rapid7/metasploit-payloads synced 2025-03-12 12:14:29 +01:00

Land , Add ADSI support to ExtAPI

This commit is contained in:
Meatballs 2014-01-07 11:23:07 +00:00
commit 880c247554
7 changed files with 408 additions and 0 deletions
c/meterpreter

@ -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;
}

@ -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

@ -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;
}

@ -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" />