1
mirror of https://github.com/rapid7/metasploit-payloads synced 2025-03-18 15:14:10 +01:00
2020-06-25 10:31:53 +10:00

513 lines
14 KiB
C++

/*!
* @file wmi_interface.h
* @brief Declarations for functions that deal directly with WMI
* via the COM interfaces (hence the .cpp extension).
*/
extern "C" {
#include "extapi.h"
#include "common_metapi.h"
#include <inttypes.h>
#include "wmi_interface.h"
}
#include <wbemcli.h>
#include <comutil.h>
#include <comdef.h>
#define FIELD_SIZE 1024
#define ENUM_TIMEOUT 5000
/*! The number of fields to ignore at the start of the query, which we aren't interested in.
* For some reason there's one more system field in x64 than there is in x86. */
#ifdef _WIN64
#define SYSTEM_FIELD_COUNT 9
#else
#define SYSTEM_FIELD_COUNT 8
#endif
#ifdef __MINGW32__
// Provide custom implmentations of the BSTR conversion
// functions because comsuppw.lib is a proprietary lib
// that comes with Vis Studio
namespace _com_util
{
inline BSTR ConvertStringToBSTR(const char* pSrc)
{
if(!pSrc)
{
return NULL;
}
DWORD cwch;
BSTR wsOut(NULL);
if(cwch = ::MultiByteToWideChar(CP_ACP, 0, pSrc, -1, NULL, 0))
{
cwch--;
wsOut = ::SysAllocStringLen(NULL, cwch);
if(wsOut)
{
if(!::MultiByteToWideChar(CP_ACP, 0, pSrc, -1, wsOut, cwch))
{
if(ERROR_INSUFFICIENT_BUFFER == ::GetLastError())
{
return wsOut;
}
::SysFreeString(wsOut);//must clean up
wsOut = NULL;
}
}
}
return wsOut;
}
inline char* ConvertBSTRToString(BSTR pSrc)
{
if(!pSrc)
{
return NULL;
}
//convert even embeded NULL
DWORD cb,cwch = ::SysStringLen(pSrc);
char *szOut = NULL;
if(cb = ::WideCharToMultiByte(CP_ACP, 0, pSrc, cwch + 1, NULL, 0, 0, 0))
{
szOut = new char[cb];
if(szOut)
{
szOut[cb - 1] = '\0';
if(!::WideCharToMultiByte(CP_ACP, 0, pSrc, cwch + 1, szOut, cb, 0, 0))
{
delete []szOut;//clean up if failed;
szOut = NULL;
}
}
}
return szOut;
}
}
#endif
/*!
* @brief Convert a variant type to a string and write it to the given buffer.
* @param v The variant to convert.
* @param buffer Pointer to the buffer to write the value to.
* @param bufferSize size of the buffer.
* @returns Pointer to the next location in the buffer.
* @remarks This attempts to "flatten" a variant, including array types. The implementation is
* not 100% complete, but is good enough for the sake of this requirement. Only arrays
* of BSTR are currenty supported, more types can be added later if needed. Arbitrary
* array depth has been attempted, but no tests have yet found a nested array in the
* result set. There's probably bugs in that bit.
*/
char* variant_to_string(const _variant_t& v, char* buffer, DWORD bufferSize)
{
dprintf("[WMI] preparing to parse variant of type %u (%x), buffer size %u", v.vt, v.vt, bufferSize);
switch (v.vt)
{
case VT_EMPTY:
strncpy_s(buffer, bufferSize, "(EMPTY)", bufferSize - 1);
break;
case VT_NULL:
strncpy_s(buffer, bufferSize, "(NULL)", bufferSize - 1);
break;
case VT_BOOL:
strncpy_s(buffer, bufferSize, v.boolVal == VARIANT_TRUE ? "true" : "false", bufferSize - 1);
break;
case VT_I1:
_snprintf_s(buffer, bufferSize, bufferSize - 1, "%" PRId8, (CHAR)v);
break;
case VT_I2:
_snprintf_s(buffer, bufferSize, bufferSize - 1, "%" PRId16, (SHORT)v);
break;
case VT_INT:
case VT_I4:
_snprintf_s(buffer, bufferSize, bufferSize - 1, "%" PRId32, (INT)v);
break;
case VT_INT_PTR:
_snprintf_s(buffer, bufferSize, bufferSize - 1, "%" PRIdPTR, (INT_PTR)v);
break;
case VT_I8:
_snprintf_s(buffer, bufferSize, bufferSize - 1, "%" PRId64, (__int64)v);
break;
case VT_UI1:
_snprintf_s(buffer, bufferSize, bufferSize - 1, "%" PRIu8, (BYTE)v);
break;
case VT_UI2:
_snprintf_s(buffer, bufferSize, bufferSize - 1, "%" PRIu16, (SHORT)v);
break;
case VT_UINT:
case VT_UI4:
_snprintf_s(buffer, bufferSize, bufferSize - 1, "%" PRIu32, (UINT)v);
break;
case VT_UINT_PTR:
_snprintf_s(buffer, bufferSize, bufferSize - 1, "%" PRIuPTR, (UINT_PTR)v);
break;
case VT_UI8:
_snprintf_s(buffer, bufferSize, bufferSize - 1, "%" PRIu64, (unsigned __int64)v);
break;
case VT_BSTR:
case VT_LPSTR:
case VT_LPWSTR:
// not sure if this is correct
strncpy_s(buffer, bufferSize, (char*)(_bstr_t)v.bstrVal, bufferSize - 1);
break;
// TODO more types, such as floats, dates, etc.
default:
if ((v.vt & VT_ARRAY) == VT_ARRAY)
{
// nested array type, great.
dprintf("[WMI] array type found!");
LPSAFEARRAY array = v.parray;
HRESULT hResult;
if (FAILED(hResult = SafeArrayLock(array)))
{
dprintf("[WMI] Failed to get array dimension: %x", hResult);
break;
}
dprintf("[WMI] Field name array locked.");
LONG* indices = NULL;
LONG* bounds = NULL;
do
{
VARTYPE varType;
SafeArrayGetVartype(array, &varType);
dprintf("[WMI] Array type %u (%x)", (ULONG)varType, (ULONG)varType);
dprintf("[WMI] Array dimensions: %u", SafeArrayGetDim(array));
LONG iterations = 1;
LONG dim = SafeArrayGetDim(array);
indices = (LONG*)malloc(dim * sizeof(LONG));
bounds = (LONG*)malloc(dim * sizeof(LONG) * 2);
memset(indices, 0, dim * sizeof(LONG));
memset(bounds, 0, dim * sizeof(LONG) * 2);
for (LONG i = 0; i < dim; ++i)
{
LONG* lBound = bounds + i * 2;
LONG* uBound = lBound + 1;
if (FAILED(hResult = SafeArrayGetLBound(array, i + 1, lBound))
|| FAILED(hResult = SafeArrayGetUBound(array, i + 1, uBound)))
{
dprintf("[WMI] Failed to get array dimensions: %x", hResult);
break;
}
dprintf("[WMI] Array bounds: %u to %u", *lBound, *uBound);
iterations *= *uBound - *lBound;
indices[i] = *lBound;
}
dprintf("[WMI] Array requires %u iterations", iterations);
// we're going to wrap our array elements in brackets, and separate with pipes
// because we need some kind of array visualisation and this is the best I could
// come up with at this time of night. Each dimension nests in a new set of brackets
while (iterations-- > 0)
{
for (LONG i = 0; i < dim; ++i)
{
if (indices[i] == 0)
{
// save space for the closing bracket as well
bufferSize -= 2;
*buffer++ = '{';
dprintf("[WMI] opening bracket for dimension %u", i);
}
else if(*(buffer - 1) != '|')
{
--bufferSize;
*buffer++ = '|';
}
}
dprintf("[WMI] extracting value for iteration %u", iterations);
switch (varType)
{
case VT_BSTR:
BSTR val;
if (SUCCEEDED(SafeArrayGetElement(array, indices, (void*)&val)))
{
dprintf("[WMI] Value extracted for iteration %u", iterations);
char* newBuf = variant_to_string(_variant_t(val), buffer, bufferSize);
bufferSize -= (LONG)(newBuf - buffer + 1);
buffer = newBuf;
dprintf("[WMI] Value added", iterations);
}
break;
default:
dprintf("[WMI] Unsupported nested array type %u", (LONG)varType);
break;
}
++indices[dim - 1];
for (LONG i = dim - 1; i >= 0; --i)
{
if (indices[i] == bounds[i * 2 + 1])
{
dprintf("[WMI] closing bracket for dimension %u", i);
*buffer++ = '}';
indices[i] = bounds[i * 2];
if (i > 0)
{
++indices[i - 1];
}
}
}
}
} while (0);
if (indices)
{
free(indices);
}
if (bounds)
{
free(bounds);
}
SafeArrayUnlock(array);
}
else
{
dprintf("[WMI] Unhandled type: %u (%x)", v.vt, v.vt);
}
// ignore the buffer for other types
break;
}
// return wherever we go to.
return buffer + strlen(buffer);
}
/*!
* @brief Perform a WMI query.
* @param lpwRoot Name of the root object that is to be queried against.
* @param lpwQuery The filter to use when reading objects (LDAP style).
* @param response The response \c Packet to add the results to.
*/
DWORD wmi_query(LPCWSTR lpwRoot, LPWSTR lpwQuery, Packet* response)
{
HRESULT hResult;
dprintf("[WMI] Initialising COM");
if ((hResult = CoInitializeEx(NULL, COINIT_MULTITHREADED)) == S_OK)
{
dprintf("[WMI] COM initialised");
IWbemLocator* pLocator = NULL;
IWbemServices* pServices = NULL;
IEnumWbemClassObject* pEnumerator = NULL;
IWbemClassObject* pSuperClass = NULL;
IWbemClassObject* pObj = NULL;
VARIANT** fields = NULL;
do
{
if (FAILED(hResult = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0)))
{
dprintf("[WMI] Failed to initialize security: %x", hResult);
break;
}
dprintf("[WMI] Security initialised");
if (FAILED(hResult = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator))))
{
dprintf("[WMI] Failed to create WbemLocator: %x", hResult);
break;
}
dprintf("[WMI] WbemLocator created.");
if (FAILED(hResult = pLocator->ConnectServer(_bstr_t(lpwRoot), NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pServices)))
{
dprintf("[WMI] Failed to create WbemServices at %S: %x", lpwRoot, hResult);
break;
}
dprintf("[WMI] WbemServices created.");
if (FAILED(hResult = pServices->ExecQuery(L"WQL", lpwQuery, WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator)))
{
dprintf("[WMI] Failed to create Enumerator for query %S: %x", lpwQuery, hResult);
break;
}
dprintf("[WMI] Enumerated created.");
ULONG numFound;
if (FAILED(hResult = pEnumerator->Next(ENUM_TIMEOUT, 1, &pObj, &numFound)))
{
dprintf("[WMI] Failed to get the first query element: %x", lpwQuery, hResult);
break;
}
dprintf("[WMI] First result read. hr=%x p=%p", hResult, pObj);
if (hResult == WBEM_S_FALSE)
{
// this is not an error
dprintf("[WMI] No results found!");
break;
}
// get the names of the fields out of the first object before doing anything else.
LPSAFEARRAY pFieldArray = NULL;
if (FAILED(hResult = pObj->GetNames(NULL, WBEM_FLAG_ALWAYS, NULL, &pFieldArray)))
{
dprintf("[WMI] Failed to get field names: %x", hResult);
break;
}
dprintf("[WMI] Field Names extracted. hr=%x p=%p", hResult, pFieldArray);
// lock the array
if (FAILED(hResult = SafeArrayLock(pFieldArray)))
{
dprintf("[WMI] Failed to get array dimension: %x", hResult);
break;
}
dprintf("[WMI] Field name array locked.");
do
{
dprintf("[WMI] Array dimensions: %u", SafeArrayGetDim(pFieldArray));
// this array is just one dimension, let's get the bounds of the first dimension
LONG lBound, uBound;
if (FAILED(hResult = SafeArrayGetLBound(pFieldArray, 1, &lBound))
|| FAILED(hResult = SafeArrayGetUBound(pFieldArray, 1, &uBound)))
{
dprintf("[WMI] Failed to get array dimensions: %x", hResult);
break;
}
dprintf("[WMI] Bounds: %u to %u", lBound, uBound);
LONG fieldCount = uBound - lBound - SYSTEM_FIELD_COUNT;
#ifndef _WIN64
// on x86 the array bounds behave differently as the uBound is actually EXCLUSIVE
// of the last value and not INCLUSIVE
fieldCount -= 1;
#endif
dprintf("[WMI] Query results in %u fields", fieldCount);
VARIANT** fields = (VARIANT**)malloc(sizeof(VARIANT*) * fieldCount);
char value[FIELD_SIZE];
Packet* fieldGroup = met_api->packet.create_group();
memset(fields, 0, sizeof(VARIANT*) * fieldCount);
for (LONG i = 0; i < fieldCount; ++i)
{
LONG indices[1] = { i + SYSTEM_FIELD_COUNT };
SafeArrayPtrOfIndex(pFieldArray, indices, (void**)&fields[i]);
_bstr_t bstr(fields[i]->bstrVal);
met_api->packet.add_tlv_string(fieldGroup, TLV_TYPE_EXT_WMI_FIELD, (const char*)bstr);
dprintf("[WMI] Added header field: %s", (const char*)bstr);
}
dprintf("[WMI] added all field headers");
// add the field names to the packet
met_api->packet.add_group(response, TLV_TYPE_EXT_WMI_FIELDS, fieldGroup);
dprintf("[WMI] processing values...");
// with that horrible pain out of the way, let's actually grab the data
do
{
if (FAILED(hResult))
{
dprintf("[WMI] Loop exited via %x", hResult);
break;
}
Packet* valueGroup = met_api->packet.create_group();
for (LONG i = 0; i < fieldCount; ++i)
{
memset(value, 0, FIELD_SIZE);
VARIANT varValue;
VariantInit(&varValue);
_bstr_t field(fields[i]->bstrVal);
dprintf("[WMI] Extracting value for %s", (char*)field);
if (SUCCEEDED(pObj->Get(field, 0, &varValue, NULL, NULL)))
{
variant_to_string(_variant_t(varValue), value, FIELD_SIZE);
}
met_api->packet.add_tlv_string(valueGroup, TLV_TYPE_EXT_WMI_VALUE, value);
dprintf("[WMI] Added value for %s: %s", (char*)_bstr_t(fields[i]->bstrVal), value);
}
// add the field values to the packet
met_api->packet.add_group(response, TLV_TYPE_EXT_WMI_VALUES, valueGroup);
pObj->Release();
pObj = NULL;
} while ((hResult = pEnumerator->Next(ENUM_TIMEOUT, 1, &pObj, &numFound)) != WBEM_S_FALSE);
} while (0);
SafeArrayUnlock(pFieldArray);
} while (0);
if (fields)
{
free(fields);
}
if (pObj)
{
pObj->Release();
}
if (pEnumerator)
{
pEnumerator->Release();
}
if (pServices)
{
pServices->Release();
}
if (pLocator)
{
pLocator->Release();
}
CoUninitialize();
if (SUCCEEDED(hResult))
{
hResult = S_OK;
dprintf("[WMI] Things appeared to go well!");
}
}
else
{
dprintf("[WMI] Failed to initialize COM");
}
if (FAILED(hResult))
{
// if we failed, we're going to convert the error to a string, add it and still return success, but we'll
// also include the hresult.
char errorMessage[1024];
memset(errorMessage, 0, 1024);
_com_error comError(hResult);
_snprintf_s(errorMessage, 1024, 1023, "%s (0x%x)", comError.ErrorMessage(), hResult);
dprintf("[WMI] returning error -> %s", errorMessage);
met_api->packet.add_tlv_string(response, TLV_TYPE_EXT_WMI_ERROR, errorMessage);
hResult = S_OK;
}
return (DWORD)hResult;
}