├── LICENSE ├── README.md ├── badger_stub.c ├── beacon_wrapper.h ├── encode_args.py └── patch.py /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, NVISO Security 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS2BR BOF 2 | 3 | > [!WARNING] 4 | > **Disclaimer:** This repository, including all code, scripts, and documentation contained herein, is provided by NVISO exclusively for educational and informational purposes. The contents of this repository are intended to be used solely as a learning resource. The authors of this repository expressly disclaim any responsibility for any misuse or unintended application of the tools, code, or information provided within this repository. 5 | > Users are solely responsible for ensuring that their use of the repository complies with applicable laws and regulations. The authors of this repository do not provide any warranties or guarantees regarding the accuracy, completeness, or suitability of the contents for any particular purpose. 6 | > If you do not agree with these terms, you are advised not to use or access this repository. 7 | 8 | You would like to execute BOFs written for Cobalt Strike in Brute Ratel C4? Look no further, we got you covered! CS2BR implements a compatibility-layer that make CS BOFs use the BRC4 API. This allows you to use the vast landscape that is BOFs in BRC4. 9 | 10 | _Please read about its [caveats](#caveats) before using CS2BR._ 11 | 12 | ## The Problem 13 | 14 | As the BRC4 documentation on [coffexec](https://bruteratel.com/tabs/badger/commands/coffexec/) describes, porting CS BOFs to BR is a straight-forward task: all that needs to be done is replacing the name of CS's `go` entrypoint to BRC4's `coffee` and replacing CS's API calls to the BRC4 equivalents. For some simple API calls this is trivial (e.g. you can replace `BeaconPrintf` with `BadgetDispatch`). 15 | 16 | However there are several sub-APIs in [CS's BOF C API](https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/beacon-object-files_main.htm) that make this a more elaborate task: 17 | 18 | * The `Data Parser API` provides utilities to parse data passed to the BOF. BRC4 doesn't have an equivalent for this as arguments are passed to BOFs as simple strings (using the `char** argv, int argc` parameters in the entrypoint). 19 | * The `Format API` allows BOFs to format output in buffers for later transmission. BRC4 doesn't currently have an equivalent API. 20 | * The `Internal API` features several utilities related to user impersonation, privileges and process injection. BRC4 doesn't currently have an equivalent API. 21 | 22 | ## Caveats 23 | 24 | CS2BR is not a silver bullet that solves the problem of CS and BRC4 BOF incompatibility. There are a couple of caveats one should consider when utilizing CS2BR: 25 | 26 | * CS2BR (*currently*) works only on a source code level: if you want to patch a BOF that you don't have source code for, CS2BR won't be of use to you. 27 | * Patching the compatibility layer into source code results in more code getting generated, thus increasing the size of the compiled BOF. Also note that the compatibility layer code can get flagged in the future. 28 | * CS2BR does not (*yet*) support all of CS's BOF C API: namely the `Internal API` is populated with stubs only and won't do anything. This mainly concerns BOFs utilizing CS's user impersonation and process injection API calls. 29 | * While CS2BR allows you to pass parameters to BOFs, you'll still have to work out the number and type of parameters yourself by dissecting your BOF's CNA. 30 | 31 | # Usage 32 | 33 | There are three steps to using CS2BR: 34 | 35 | 1. [Patching](#1-patching-bof-source-code): Patch CS2BR compatibility-layer into BOF source code 36 | 2. Compile the BOF as instructed by the BOF authors 37 | 3. (Optionally)[Parameters](#3-generating-bof-parameters): Generate parameters to pass to a BOF 38 | 4. Execute BOF using `coffexec` in BRC4 39 | 40 | ## 1. Patching BOF source code 41 | 42 | There are two options to patch BOF source code: you can either do this yourself of have the Python patching script do the job. 43 | 44 | ### Manual patching 45 | 46 | 1. Find the `beacon.h` file that contains the CS BOF C API definitions (ref. [beacon.h](https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/beacon.h)) 47 | 2. Replace its contents with [beacon_wrapper.h](beacon_wrapper.h)'s contents. 48 | 3. Find the file containing the `go` entrypoint. 49 | 4. Rename the `go` entrypoint to `csentry` 50 | 5. Append the contents of [badger_stub.c](badger_stub.c) to the file. 51 | 52 | ### Patching script 53 | 54 | Run [patch.py](patch.py) (requires Python 3): 55 | 56 | ``` 57 | usage: patch [-h] [--src SRC] [--beaconh BEACONH] [--entrypoint ENTRYPOINT] [--forcepatch] [--dry] 58 | 59 | Patches Cobalt Strike BOF source code to be compatible with BruteRatel 60 | 61 | options: 62 | -h, --help show this help message and exit 63 | --src SRC Directory of source code to patch (default: current working dir ,currently ".") 64 | --beaconh BEACONH Name/pattern of or path to the headerfile(s) with Cobalt Strike beacon definitions to patch (default: "beacon.h") 65 | --entrypoint ENTRYPOINT 66 | Name or pattern of the source file that contains the Cobalt Strike "go" entrypoint (default: "*.c", so any C source file). 67 | --forcepatch Force patching already patched files 68 | --dry Dry-run: don't actually patch any files. 69 | ``` 70 | 71 | Example: `./patch.py --src /path/to/CS-Situational-Awareness-BOF` (to patch [trustedsec's Situational Awareness BOFs](https://github.com/trustedsec/CS-Situational-Awareness-BOF)) 72 | 73 | ## 3. Generating BOF parameters 74 | 75 | CS's `Data Parse API` allows passing arbitrary data to BOFs, including integers and binary blobs. BRC4 however can't pass arbitrary binary data to BOFs but only provides passing strings. 76 | 77 | To workaround this, CS2BR's compatibility-layer takes base64 encoded input and feeds this to the `Data Parse API`. However BRC4 doesn't feature aggressor scripts (CNA scripts) that query user inputs. CS2BR comes with [encode_args.py](encode_args.py) that allows you to enter parameters and generates the base64 string you can pass to your BOF in BRC4. 78 | 79 | For example, here a base64 string is built using `encode_args.py` that can be consumed by the `Data Parse API` through CS2BR: 80 | 81 | ``` 82 | ./encode_args.py 83 | 84 | Documented commands (type help ): 85 | ======================================== 86 | addString addWString addint addshort exit generate help reset 87 | 88 | BOF parameter encoder 89 | CMD> addString localhost 90 | CMD> generate 91 | CgAAAGxvY2FsaG9zdAA= 92 | CMD> exit 93 | ``` 94 | 95 | Alternatively, you can use `encode_args.py` non-interactively by passing pairs of `:` arguments to it, e.g.: 96 | ``` 97 | ./encode_args.py "z:my first string" "Z:here's a wide-string" i:420 s:69 98 | EAAAAG15IGZpcnN0IHN0cmluZwAqAAAAaABlAHIAZQAnAHMAIABhACAAdwBpAGQAZQAtAHMAdAByAGkAbgBnAAAApAEAAEUA 99 | ``` 100 | 101 | # Credits 102 | 103 | CS2BR didn't invent (most of) the concepts it uses. It utilizes code from the following sources: 104 | 105 | * [COFF Loader](https://github.com/trustedsec/COFFLoader) by trustedsec: Basis for the compatibility-layer and [encode_args.py](encode_args.py) script 106 | * [Base64 C implementation](https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/) by John Schember: Basis for the compatibility-layer's base64 decoding 107 | 108 | # See also 109 | 110 | * Brute Ratel's [coffexec documentation](https://bruteratel.com/tabs/badger/commands/coffexec/) 111 | * Cobalt Strike's [BOF documentation](https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/beacon-object-files_main.htm) 112 | -------------------------------------------------------------------------------- /badger_stub.c: -------------------------------------------------------------------------------- 1 | /* 2 | * CS2BR BOF Patcher - Compatibility Layer by NVISO 3 | * ------------------------- 4 | * This C file implements a custom badger entrypoint that 5 | * - (optionally) decodes provided base64 input 6 | * - calls the original BOF entrypoint (which is renamed to csentry) 7 | */ 8 | 9 | #define CSEP go 10 | 11 | /* Forward declarations of the base64 utilities */ 12 | size_t b64_decoded_size(char *in); 13 | int b64_isvalidchar(char c); 14 | int b64_decode(char *in, unsigned char *out, size_t outlen); 15 | 16 | /* Badger entrypoint */ 17 | void coffee(char **argv, int argc, WCHAR **dispatch) 18 | { 19 | size_t size = 0; 20 | char *buffer = NULL; 21 | 22 | // Set global dispatch variable to allow CS-wrappers to use the BR API's output methods 23 | _dispatch = dispatch; 24 | 25 | BadgerDispatch(dispatch, "[cs2br] Starting...\n"); 26 | 27 | // Validate input args 28 | if (argc > 1) 29 | { 30 | BadgerDispatch(dispatch, "[cs2br] Expected 0-1 arguments, got %i!\n", argc); 31 | return; 32 | } 33 | 34 | if (argc == 1) 35 | { 36 | // Decode base64 input 37 | BadgerDispatch(dispatch, "[cs2br] Determining required buffer size...\n", size); 38 | size = b64_decoded_size(argv[0]); 39 | if (size != 0) 40 | { 41 | BadgerDispatch(dispatch, "[cs2br] Allocating %lld bytes...\n", size); 42 | buffer = (char *)BadgerAlloc(size); 43 | if (!buffer) 44 | { 45 | BadgerDispatch(dispatch, "[cs2br] Failed to allocate %lldi bytes; bailing out!\n", size); 46 | return; 47 | } 48 | BadgerMemset(buffer, 0, size); 49 | BadgerDispatch(dispatch, "[cs2br] Decoding base64 input...\n", size); 50 | if (!b64_decode(argv[0], (unsigned char *)buffer, size)) 51 | { 52 | BadgerDispatch(dispatch, "[cs2br] Failed to decode base64 input; bailing out!\n"); 53 | return; 54 | } 55 | BadgerDispatch(dispatch, "[cs2br] Decoding done!\n"); 56 | } 57 | else 58 | { 59 | BadgerDispatch(dispatch, "[cs2br] Determined buffer size is zero; bailing out!\n"); 60 | return; 61 | } 62 | } 63 | 64 | BadgerDispatch(dispatch, "[cs2br] Invoking entrypoint...\n"); 65 | #ifdef CS2BRBINPATCH 66 | go(buffer, size); 67 | #else 68 | csentry(buffer, size); 69 | #endif 70 | BadgerDispatch(dispatch, "[cs2br] Done; exiting!\n"); 71 | 72 | if (buffer != NULL) 73 | BadgerFree((PVOID *)&buffer); 74 | } 75 | 76 | /* Basic implementation of base64 decoding 77 | based on John Schember's base64 implementation in C 78 | ref https://nachtimwald.com/2017/11/18/base64-encode-and-decode-in-c/ */ 79 | size_t b64_decoded_size(char *in) 80 | { 81 | size_t len = 0, ret = 0, i = 0; 82 | 83 | if (in == NULL) 84 | return 0; 85 | 86 | len = BadgerStrlen(in); 87 | if (len < 2) 88 | return ret; // Sanity check: base64 encoding a single char results in at least 2 chars (+2 padding) 89 | if (len > 0) 90 | { 91 | ret = len / 4 * 3; 92 | 93 | for (i = len; i-- > 0;) 94 | { 95 | if (in[i] == '=') 96 | ret--; 97 | else 98 | break; 99 | } 100 | } 101 | 102 | return ret > 0 ? ret : 1; 103 | } 104 | 105 | int b64_isvalidchar(char c) 106 | { 107 | if (c >= '0' && c <= '9' || c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '+' || c == '/' || c == '=') 108 | return 1; 109 | return 0; 110 | } 111 | 112 | int b64_decode(char *in, unsigned char *out, size_t outlen) 113 | { 114 | size_t len, i, j; 115 | int v; 116 | int b64invs[] = {62, -1, -1, -1, 63, 52, 53, 54, 55, 56, 57, 58, 117 | 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, 5, 118 | 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 119 | 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 120 | 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 121 | 43, 44, 45, 46, 47, 48, 49, 50, 51}; 122 | 123 | if (in == NULL || out == NULL) 124 | return 0; 125 | 126 | len = BadgerStrlen(in); 127 | if (outlen < b64_decoded_size(in) || len % 4 != 0) 128 | return 0; 129 | 130 | for (i = 0; i < len; i++) 131 | { 132 | if (!b64_isvalidchar(in[i])) 133 | return 0; 134 | } 135 | 136 | for (i = 0, j = 0; i < len; i += 4, j += 3) 137 | { 138 | v = b64invs[in[i] - 43]; 139 | v = (v << 6) | b64invs[in[i + 1] - 43]; 140 | v = in[i + 2] == '=' ? v << 6 : (v << 6) | b64invs[in[i + 2] - 43]; 141 | v = in[i + 3] == '=' ? v << 6 : (v << 6) | b64invs[in[i + 3] - 43]; 142 | 143 | out[j] = (v >> 16) & 0xFF; 144 | if (in[i + 2] != '=') 145 | out[j + 1] = (v >> 8) & 0xFF; 146 | if (in[i + 3] != '=') 147 | out[j + 2] = v & 0xFF; 148 | } 149 | 150 | return 1; 151 | } -------------------------------------------------------------------------------- /beacon_wrapper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * CS2BR BOF Patcher - Compatibility Layer by NVISO 3 | * ------------------------- 4 | * This headerfile implements a compatibility layer that 5 | * - maps Cobalt Strike's custom BOF API calls to Brute Ratel C4's custom BOF API 6 | * - includes Win32 API imports provided to Cobalt Strike BOFs at runtime 7 | */ 8 | 9 | #ifndef __BEACON_WRAPPER__ 10 | #define __BEACON_WRAPPER__ 11 | 12 | #include 13 | 14 | /* Import functions that are by default included in CS BOFs but not in BR 15 | ref https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics/beacon-object-files_main.htm 16 | The following code 17 | - declares the required Win32 API symbols for importing 18 | - overwrites existing related macros 19 | - defines macros for the function names so that original CS BOF source code is compatible with the fulyl qualified import name of functions */ 20 | WINBASEAPI FARPROC WINAPI KERNEL32$GetProcAddress (HMODULE hModule, LPCSTR lpProcName); 21 | WINBASEAPI HMODULE WINAPI KERNEL32$GetModuleHandleA (LPCSTR lpModuleName); 22 | WINBASEAPI HMODULE WINAPI KERNEL32$GetModuleHandleW (LPCWSTR lpModuleName); 23 | WINBASEAPI HMODULE WINAPI KERNEL32$LoadLibraryA (LPCSTR lpLibFileName); 24 | WINBASEAPI HMODULE WINAPI KERNEL32$LoadLibraryW (LPCWSTR lpLibFileName); 25 | WINBASEAPI BOOL WINAPI KERNEL32$FreeLibrary (HMODULE hLibModule); 26 | 27 | #ifdef GetProcAddress 28 | #undef GetProcAddress 29 | #endif 30 | #define GetProcAddress KERNEL32$GetProcAddress 31 | #ifdef GetModuleHandleA 32 | #undef GetModuleHandleA 33 | #endif 34 | #define GetModuleHandleA KERNEL32$GetModuleHandleA 35 | #ifdef GetModuleHandleW 36 | #undef GetModuleHandleW 37 | #endif 38 | #define GetModuleHandleW KERNEL32$GetModuleHandleW 39 | #ifdef LoadLibraryA 40 | #undef LoadLibraryA 41 | #endif 42 | #define LoadLibraryA KERNEL32$LoadLibraryA 43 | #ifdef LoadLibraryW 44 | #undef LoadLibraryW 45 | #endif 46 | #define LoadLibraryW KERNEL32$LoadLibraryW 47 | #ifdef FreeLibrary 48 | #undef FreeLibrary 49 | #endif 50 | #define FreeLibrary KERNEL32$FreeLibrary 51 | 52 | /* Brute Ratel BOF API 53 | ref https://bruteratel.com/assets/badger_exports.h */ 54 | DECLSPEC_IMPORT int BadgerDispatch(WCHAR** dispatch, const char* __format, ...); 55 | DECLSPEC_IMPORT int BadgerDispatchW(WCHAR** dispatch, const WCHAR* __format, ...); 56 | DECLSPEC_IMPORT size_t BadgerStrlen(CHAR* buf); 57 | DECLSPEC_IMPORT size_t BadgerWcslen(WCHAR* buf); 58 | DECLSPEC_IMPORT void* BadgerMemcpy(void* dest, const void* src, size_t len); 59 | DECLSPEC_IMPORT void* BadgerMemset(void* dest, int val, size_t len); 60 | DECLSPEC_IMPORT int BadgerStrcmp(const char* p1, const char* p2); 61 | DECLSPEC_IMPORT int BadgerWcscmp(const wchar_t* s1, const wchar_t* s2); 62 | DECLSPEC_IMPORT int BadgerAtoi(char* string); 63 | DECLSPEC_IMPORT PVOID BadgerAlloc(SIZE_T length); 64 | DECLSPEC_IMPORT VOID BadgerFree(PVOID* memptr); 65 | DECLSPEC_IMPORT BOOL BadgerSetdebug(); 66 | DECLSPEC_IMPORT ULONG BadgerGetBufferSize(PVOID buffer); 67 | 68 | /* Cobalt Strike BOF API 69 | ref https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/beacon.h 70 | Removed DECLSPEC_IMPORTs since we provide our own implementations as part of our compatibility layer */ 71 | 72 | /* data API */ 73 | typedef struct { 74 | char* original; /* the original buffer [so we can free it] */ 75 | char* buffer; /* current pointer into our buffer */ 76 | int length; /* remaining length of data */ 77 | int size; /* total size of this buffer */ 78 | } datap; 79 | 80 | void BeaconDataParse(datap* parser, char* buffer, int size); 81 | int BeaconDataInt(datap* parser); 82 | short BeaconDataShort(datap* parser); 83 | int BeaconDataLength(datap* parser); 84 | char* BeaconDataExtract(datap* parser, int* size); 85 | 86 | /* format API */ 87 | typedef struct { 88 | char* original; /* the original buffer [so we can free it] */ 89 | char* buffer; /* current pointer into our buffer */ 90 | int length; /* remaining length of data */ 91 | int size; /* total size of this buffer */ 92 | } formatp; 93 | 94 | void BeaconFormatAlloc(formatp* format, int maxsz); 95 | void BeaconFormatReset(formatp* format); 96 | void BeaconFormatFree(formatp* format); 97 | void BeaconFormatAppend(formatp* format, char* text, int len); 98 | void BeaconFormatPrintf(formatp* format, char* fmt, ...); 99 | char* BeaconFormatToString(formatp* format, int* size); 100 | void BeaconFormatInt(formatp* format, int value); 101 | 102 | /* Output Functions */ 103 | #define CALLBACK_OUTPUT 0x0 104 | #define CALLBACK_OUTPUT_OEM 0x1e 105 | #define CALLBACK_ERROR 0x0d 106 | #define CALLBACK_OUTPUT_UTF8 0x20 107 | 108 | void BeaconPrintf(int type, char* fmt, ...); 109 | void BeaconOutput(int type, char* data, int len); 110 | 111 | /* Token Functions */ 112 | BOOL BeaconUseToken(HANDLE token) { return FALSE; } /* TODO: Implement */ 113 | void BeaconRevertToken() { } /* TODO: Implement */ 114 | BOOL BeaconIsAdmin() { return FALSE; } /* TODO: Implement */ 115 | 116 | /* Spawn+Inject Functions */ 117 | void BeaconGetSpawnTo(BOOL x86, char* buffer, int length) { } /* TODO: Implement */ 118 | void BeaconInjectProcess(HANDLE hProc, int pid, char* payload, int p_len, int p_offset, char* arg, int a_len) { } /* TODO: Implement */ 119 | void BeaconInjectTemporaryProcess(PROCESS_INFORMATION* pInfo, char* payload, int p_len, int p_offset, char* arg, int a_len) { } /* TODO: Implement */ 120 | void BeaconCleanupProcess(PROCESS_INFORMATION* pInfo) { } /* TODO: Implement */ 121 | 122 | /* Utility Functions */ 123 | BOOL toWideChar(char* src, wchar_t* dst, int max) { } /* TODO: Implement */ 124 | 125 | #ifdef _MSC_VER 126 | #pragma data_seg(".data") 127 | __declspec(allocate(".data")) WCHAR** _dispatch = 0; 128 | #pragma data_seg() 129 | #else 130 | WCHAR** _dispatch __attribute__((section(".data"))) = 0; 131 | #endif 132 | 133 | /* Custom Cobalt Strike BOF API 134 | implementations based on trustedsec's COFFLoader 135 | ref https://github.com/trustedsec/COFFLoader/blob/main/beacon_compatibility.c */ 136 | 137 | /* Custom DataParser API */ 138 | void BeaconDataParse(datap* parser, char* buffer, int size) { 139 | if (parser == NULL) return; 140 | parser->original = buffer; 141 | parser->buffer = buffer; 142 | parser->size = size; 143 | parser->length = parser->size; 144 | } 145 | 146 | int BeaconDataInt(datap* parser) { 147 | if (parser == NULL || parser->length < sizeof(int)) return 0; 148 | int val = *(int*)parser->buffer; 149 | parser->buffer += sizeof(int); 150 | parser->length -= sizeof(int); 151 | return val; 152 | } 153 | 154 | short BeaconDataShort(datap* parser) { 155 | if (parser == NULL || parser->length < sizeof(short)) return 0; 156 | short val = *(short*)parser->buffer; 157 | parser->buffer += sizeof(short); 158 | parser->length -= sizeof(short); 159 | return val; 160 | } 161 | 162 | int BeaconDataLength(datap* parser) { 163 | return parser->length; 164 | } 165 | 166 | char* BeaconDataExtract(datap* parser, int* size) { 167 | if (parser == NULL || parser->length < sizeof(int)) return NULL; 168 | char* res = NULL; 169 | int length = 0; 170 | 171 | length = BeaconDataInt(parser); 172 | 173 | res = parser->buffer; 174 | if (res == NULL) return NULL; 175 | 176 | parser->length -= length; 177 | parser->buffer += length; 178 | 179 | if (size != NULL && res != NULL) 180 | *size = length; 181 | 182 | return res; 183 | } 184 | 185 | /* Custom Format API */ 186 | void BeaconFormatAlloc(formatp* format, int maxsz) { 187 | if (format == NULL) return; 188 | format->original = (char*)BadgerAlloc(maxsz); 189 | format->buffer = format->original; 190 | format->length = 0; 191 | format->size = maxsz; 192 | } 193 | 194 | void BeaconFormatReset(formatp* format) { 195 | if (format == NULL) return; 196 | BadgerMemset(format->original, 0, format->size); 197 | format->buffer = format->original; 198 | format->length = format->size; 199 | } 200 | 201 | void BeaconFormatFree(formatp* format) { 202 | if (format == NULL) return; 203 | if (format->original) BadgerFree((void**)(&format->original)); 204 | format->original = NULL; 205 | format->buffer = NULL; 206 | format->length = 0; 207 | format->size = 0; 208 | } 209 | 210 | void BeaconFormatAppend(formatp* format, char* text, int len) { 211 | if (format == NULL || format->size < format->length + len) return; 212 | BadgerMemcpy(format->buffer, text, len); 213 | format->buffer += len; 214 | format->length += len; 215 | } 216 | 217 | /* MSVCRT import */ 218 | // TODO: Make these optional (breaks outputs though) 219 | #include 220 | 221 | #ifdef _MSC_VER 222 | #define RESTRICT __restrict 223 | #else 224 | #define RESTRICT __restrict__ 225 | #endif 226 | 227 | WINBASEAPI int __cdecl MSVCRT$vsnprintf(char * RESTRICT d, size_t n, const char * RESTRICT format, va_list arg); 228 | 229 | 230 | void BeaconFormatPrintf(formatp* format, char* fmt, ...) { 231 | if (format == NULL) return; 232 | 233 | va_list args; 234 | int length = 0; 235 | 236 | va_start(args, fmt); 237 | length = MSVCRT$vsnprintf(NULL, 0, fmt, args); 238 | va_end(args); 239 | if (format->length + length > format->size) return; 240 | 241 | va_start(args, fmt); 242 | (void)MSVCRT$vsnprintf(format->buffer, length, fmt, args); 243 | va_end(args); 244 | format->length += length; 245 | format->buffer += length; 246 | } 247 | 248 | char* BeaconFormatToString(formatp* format, int* size) { 249 | *size = format->length; 250 | return format->original; 251 | } 252 | 253 | int swap_endianess(int indata) { 254 | int testint = 0xaabbccdd; 255 | int outint = indata; 256 | if (((unsigned char*)&testint)[0] == 0xdd) { 257 | ((unsigned char*)&outint)[0] = ((unsigned char*)&indata)[3]; 258 | ((unsigned char*)&outint)[1] = ((unsigned char*)&indata)[2]; 259 | ((unsigned char*)&outint)[2] = ((unsigned char*)&indata)[1]; 260 | ((unsigned char*)&outint)[3] = ((unsigned char*)&indata)[0]; 261 | } 262 | return outint; 263 | } 264 | 265 | void BeaconFormatInt(formatp* format, int value) { 266 | int indata = value; 267 | int outdata = 0; 268 | if (format->length + sizeof(int) > format->size) return; 269 | outdata = swap_endianess(indata); 270 | BadgerMemcpy(format->buffer, &outdata, sizeof(int)); 271 | format->length += sizeof(int); 272 | format->buffer += sizeof(int); 273 | return; 274 | } 275 | 276 | /* Custom Output API */ 277 | void BeaconPrintf(int type, char* fmt, ...) { 278 | //TODO: Handle encodings dependent on `type` 279 | va_list args; 280 | int length = 0; 281 | char* buffer = NULL; 282 | 283 | va_start(args, fmt); 284 | length = MSVCRT$vsnprintf(NULL, 0, fmt, args); 285 | va_end(args); 286 | 287 | if (length <= 0) return; 288 | buffer = (char*)BadgerAlloc(length + 1); //+1 for the null-termination which isn't considered by vsnprintf 289 | if (buffer == NULL) return; 290 | buffer[length] = '\0'; 291 | 292 | va_start(args, fmt); 293 | (void)MSVCRT$vsnprintf(buffer, length, fmt, args); 294 | va_end(args); 295 | 296 | BadgerDispatch(_dispatch, buffer); 297 | BadgerFree((void**)&buffer); 298 | return; 299 | } 300 | 301 | void BeaconOutput(int type, char* data, int len) { 302 | char* buffer = (char*)BadgerAlloc(len + 1); 303 | BadgerMemcpy(buffer, data, len); 304 | buffer[len] = '\0'; //Ensure that the data is null-terminated 305 | BadgerDispatch(_dispatch, buffer); 306 | BadgerFree((void**)&buffer); 307 | } 308 | 309 | #endif // __BEACON_WRAPPER__ 310 | -------------------------------------------------------------------------------- /encode_args.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | from struct import pack, calcsize 4 | import base64 5 | import cmd 6 | from typing import List, Tuple 7 | import zlib 8 | import sys 9 | import os 10 | 11 | 12 | class BeaconPack: 13 | def __init__(self): 14 | self._buffer = b'' 15 | self._size = 0 # TODO: Just use len(buffer) instead? 16 | 17 | @property 18 | def buffer(self): 19 | return pack("= 128: 129 | return "." 130 | _str = bytes([byte]).decode("ascii") 131 | if not _str.isprintable() or _str.isspace(): 132 | return "." 133 | 134 | return _str 135 | 136 | width = 32 137 | for line, block in enumerate(buff[i: i+width] for i in range(0, len(buff), width)): 138 | _hex = ' '.join([hex(b)[2:].rjust(2, '0') 139 | for b in block] + [" " for _ in range(width - len(block))]) 140 | _ascii = ''.join([toascii(b) for b in block] + 141 | [' ' for _ in range(width - len(block))]) 142 | print("@{} | {} | {}".format( 143 | hex(line*width)[2:].rjust(8, '0'), 144 | _hex, 145 | _ascii)) 146 | 147 | def do_reset(self, text): 148 | '''reset 149 | Reset the buffer here. 150 | ''' 151 | self.BeaconPack._buffer = b'' 152 | self.BeaconPack._size = 0 153 | 154 | def do_exit(self, text): 155 | '''exit 156 | Exit the console 157 | ''' 158 | return True 159 | 160 | 161 | def process_args(args: List[str]) -> Tuple[bool, str]: 162 | pack = BeaconPack() 163 | 164 | def tryaction(fn, errortext) -> bool: 165 | try: 166 | fn() 167 | return True 168 | except Exception as e: 169 | print(errortext + ": " + str(e.args)) 170 | return False 171 | 172 | def process_arg(index: int, arg: str) -> bool: 173 | prefix = arg[0] 174 | value = arg[2:] 175 | 176 | if not prefix in ["b", "i", "s", "z", "Z", "f"]: 177 | print(f'Argument #{index+1}: Invalid argument type "{prefix}"') 178 | return False 179 | 180 | if len(value) == 0 and not prefix in ["z", "Z"]: 181 | print(f"Argument #{ 182 | index+1}: Empty value not allowed for prefix {prefix}") 183 | return False 184 | 185 | if (prefix == "b" and not tryaction(lambda: pack.addstr(base64.b64decode(value).decode()), f"Argument #{index+1}: Failed to base64 decode binary data")) or \ 186 | (prefix == "i" and not tryaction(lambda: pack.addint(int(value)), f"Argument #{index+1}: Failed to convert arg to int")) or \ 187 | (prefix == "s" and not tryaction(lambda: pack.addshort(int(value)), f"Argument #{index+1}: Failed to convert arg to short")) or \ 188 | (prefix == "z" and not tryaction(lambda: pack.addstr(value), f"Argument #{index+1}: Failed to add string")) or \ 189 | (prefix == "Z" and not tryaction(lambda: pack.addWstr(value), f"Argument #{index+1}: Failed to add string")) or \ 190 | (prefix == "f" and not tryaction(lambda: pack.addFile(value), f"Argument #{index+1}: Failed to add file")): 191 | return False 192 | 193 | return True 194 | 195 | for i, arg in enumerate(args): 196 | if not process_arg(i, arg): 197 | return [False, ""] 198 | 199 | return [True, base64.b64encode(pack.buffer).decode("ascii")] 200 | 201 | 202 | if __name__ == "__main__": 203 | print(r""" 204 | ____________ ___ ___ ___ ____ ____ ___ ___ _________ __________ _________ ___ __________ ___ 205 | / ___/ __/_ |/ _ )/ _ \ / _ )/ __ \/ __/ / _ | / _ \/ ___/ __/ / ___/ __/ |/ / __/ _ \/ _ /_ __/ __ \/ _ \ 206 | / /___\ \/ __// _ / , _/ / _ / /_/ / _/ / __ |/ , _/ (_ /\ \ / (_ / _// / _// , _/ __ |/ / / /_/ / , _/ 207 | \___/___/____/____/_/|_| /____/\____/_/ /_/ |_/_/|_|\___/___/ \___/___/_/|_/___/_/|_/_/ |_/_/ \____/_/|_| 208 | by NVISO, based on https://github.com/trustedsec/COFFLoader/blob/main/beacon_generate.py 209 | """) 210 | _args = sys.argv[1:] 211 | if _args: 212 | res, txt = process_args(_args) 213 | if res: 214 | print(txt) 215 | else: 216 | cmdloop = MainLoop() 217 | cmdloop.do_help("") 218 | cmdloop.cmdloop() 219 | -------------------------------------------------------------------------------- /patch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import re 4 | import os 5 | import sys 6 | import glob 7 | import logging 8 | import argparse 9 | from typing import List 10 | 11 | logging.basicConfig(format='[%(levelname)s] %(message)s', level=logging.INFO) 12 | 13 | PATTERN = re.compile(r"\s+(go)\(([^,]+?),([^\)]+?)\)\s*\{", re.MULTILINE) 14 | PTCH_DIR = os.path.dirname(os.path.realpath(__file__)) 15 | STUB_PATH = os.path.join(PTCH_DIR, "badger_stub.c") 16 | WRAP_PATH = os.path.join(PTCH_DIR, "beacon_wrapper.h") 17 | PTCH_COMMENT = "// Patched by cs2br-bof-patch" 18 | 19 | # Steps: 20 | # 1. Identify beacon.h 21 | # 2. Replace beacon.h with beacon_wrapper.h 22 | # 3. Identify entry file w/ go function 23 | # 4. Append badger_stub.h to entry file 24 | # 6. Done? 25 | 26 | # Also write customized beacon_generate.py 27 | 28 | 29 | def check_patch(file: str, forcepatch: bool) -> bool: 30 | with open(file, "r") as f: 31 | state = f.readline().startswith(PTCH_COMMENT) 32 | 33 | if state: 34 | if not forcepatch: 35 | logging.info(f'Skipping "{file}"; already patched!') 36 | return False 37 | logging.info(f'Re-patching "{file}"...') 38 | else: 39 | logging.info(f'Patching "{file}"...') 40 | 41 | return True 42 | 43 | def is_entrypoint(entryc: str) -> bool: 44 | with open(entryc, "r") as f: 45 | res = next(re.finditer(PATTERN, f.read()), None) 46 | return res is not None 47 | 48 | 49 | def check_files(): 50 | if not os.path.exists(STUB_PATH): 51 | raise FileNotFoundError(f'Missing patch file: "{STUB_PATH}"') 52 | if not os.path.exists(WRAP_PATH): 53 | raise FileNotFoundError(f'Missing patch file: "{WRAP_PATH}"') 54 | 55 | 56 | def find_files(name: str, dir: str) -> List[str]: 57 | # Supplied absolute file path? 58 | if os.path.isabs(name): 59 | if not os.path.exists(name): 60 | raise FileNotFoundError(f'Specified file "{name}" not found.') 61 | return [name] 62 | 63 | # Supplied relative file path? 64 | if os.path.exists(name): 65 | return [os.path.abspath(name)] 66 | 67 | # Search for pattern/name 68 | files = glob.glob(os.path.join(dir, "**", name), recursive=True) 69 | if not files: 70 | raise FileNotFoundError( 71 | f'Could not find any file with the specified name/pattern "{name}" in "{dir}"!') 72 | 73 | return [os.path.abspath(file) for file in files] 74 | 75 | def patch_header(beaconh: str, forcepatch: bool = False, dry: bool = False) -> bool: 76 | if not check_patch(beaconh, forcepatch): return False 77 | if dry: return True 78 | 79 | with open(WRAP_PATH, "r") as infile: 80 | with open(beaconh, "w") as outfile: 81 | outfile.write(PTCH_COMMENT + "\n") 82 | outfile.write(infile.read()) 83 | 84 | return True 85 | 86 | 87 | def patch_entryc(entryc: str, forcepatch: bool = False, dry: bool = False) -> bool: 88 | if not check_patch(entryc, forcepatch): return False 89 | if dry: return True 90 | 91 | # TODO: Test if entrypoint rename is actually needed. 92 | # Find & rename "go" entrypoint 93 | with open(entryc, "r") as outfile: 94 | contents = outfile.read() 95 | 96 | contents = re.sub(PATTERN, r" csentry(\2, \3) {", contents) 97 | 98 | # Append custom stub 99 | contents += "\n\n" 100 | with open(STUB_PATH, "r") as outfile: 101 | contents += outfile.read() 102 | 103 | # Write back file 104 | with open(entryc, "w") as outfile: 105 | outfile.write(PTCH_COMMENT + "\n") 106 | outfile.write(contents) 107 | 108 | return True 109 | 110 | def main(): 111 | print(r""" 112 | ____________ ___ ___ ___ ____ ____ ___ ___ _____________ _________ 113 | / ___/ __/_ |/ _ )/ _ \ / _ )/ __ \/ __/ / _ \/ _ /_ __/ ___/ // / __/ _ \ 114 | / /___\ \/ __// _ / , _/ / _ / /_/ / _/ / ___/ __ |/ / / /__/ _ / _// , _/ 115 | \___/___/____/____/_/|_| /____/\____/_/ /_/ /_/ |_/_/ \___/_//_/___/_/|_| 116 | by NVISO 117 | """) 118 | try: 119 | # Ensure we can run in the first place 120 | check_files() 121 | 122 | # Parsing args 123 | parser = argparse.ArgumentParser( 124 | "patch", description="Patches Cobalt Strike BOF source code to be compatible with BruteRatel") 125 | parser.add_argument("--src", default=os.path.curdir, 126 | help=f'Directory of source code to patch (default: current working dir, currently "{os.path.abspath(os.path.curdir)}")') 127 | parser.add_argument("--beaconh", default="beacon.h", 128 | help='Name/pattern of or path to the headerfile(s) with Cobalt Strike beacon definitions to patch (default: "beacon.h")') 129 | parser.add_argument("--entrypoint", default="*.c", 130 | help='Name or pattern of the source file(s) that contain(s) the Cobalt Strike "go" entrypoint (default: "*.c", so any C source file).') 131 | parser.add_argument("--forcepatch", default=False, action="store_true", help="Force patching already patched files") 132 | parser.add_argument("--dry", action="store_true", default=False, help="Dry-run: don't actually patch any files but show which actions will be executed.") 133 | 134 | args = parser.parse_args(sys.argv[1:]) 135 | 136 | headers = find_files(args.beaconh, args.src) 137 | entries = find_files(args.entrypoint, args.src) 138 | entries = [e for e in entries if is_entrypoint(e)] 139 | 140 | if not entries: raise ValueError(f'None of the source files found with name/pattern "{args.entrypoint}" in directory "{args.src}" contain a CS entrypoint!') 141 | 142 | logging.info(f"Identified {len(headers)} header file(s), {len(entries)} source file(s) to patch.") 143 | 144 | logging.info("Patching header files:") 145 | num_patches = sum(1 if patch_header(header, args.forcepatch, args.dry) else 0 for header in headers) 146 | logging.info(f"Patched {num_patches} header file(s)!") 147 | 148 | logging.info("Patching entrypoint source files:") 149 | num_patches = sum(1 if patch_entryc(entryc, args.forcepatch, args.dry) else 0 for entryc in entries) 150 | logging.info(f"Patched {num_patches} entrypoint source file(s)!") 151 | 152 | 153 | except Exception as e: 154 | logging.error("cs2br-bof-patch broke, here's details:", exc_info=e) 155 | 156 | 157 | if __name__ == '__main__': 158 | main() --------------------------------------------------------------------------------