From 6fc750b6f4b780cb2b75b31dbe0003a98d8f2162 Mon Sep 17 00:00:00 2001 From: MaorSabag Date: Mon, 23 Mar 2026 16:34:24 +0200 Subject: [PATCH] Add SAL-BOF for remote registry operations --- SAL-BOF/Makefile | 10 + SAL-BOF/reg/anticrash.c | 21 ++ SAL-BOF/reg/base.c | 207 ++++++++++++++++ SAL-BOF/reg/delete/delete.c | 160 +++++++++++++ SAL-BOF/reg/find/find.c | 329 +++++++++++++++++++++++++ SAL-BOF/reg/query/query.c | 461 ++++++++++++++++++++++++++++++++++++ SAL-BOF/reg/stack.c | 75 ++++++ SAL-BOF/reg/write/write.c | 198 ++++++++++++++++ SAL-BOF/sal.axs | 241 ++++++++++++++++++- 9 files changed, 1701 insertions(+), 1 deletion(-) create mode 100644 SAL-BOF/reg/anticrash.c create mode 100644 SAL-BOF/reg/base.c create mode 100644 SAL-BOF/reg/delete/delete.c create mode 100644 SAL-BOF/reg/find/find.c create mode 100644 SAL-BOF/reg/query/query.c create mode 100644 SAL-BOF/reg/stack.c create mode 100644 SAL-BOF/reg/write/write.c diff --git a/SAL-BOF/Makefile b/SAL-BOF/Makefile index 562b0d4..70a2d11 100755 --- a/SAL-BOF/Makefile +++ b/SAL-BOF/Makefile @@ -35,6 +35,11 @@ bof: clean @($(CC64) $(CFLAGS) privcheck/pshistory.c -o _bin/pshistory.x64.o && $(STRIP64) _bin/pshistory.x64.o) && echo '[+] pshistory x64' || echo '[!] pshistory x64' @($(CC64) $(CFLAGS) privcheck/uacstatus.c -o _bin/uacstatus.x64.o && $(STRIP64) _bin/uacstatus.x64.o) && echo '[+] uacstatus x64' || echo '[!] uacstatus x64' @($(CC64) $(CFLAGS) privcheck/privcheck_all.c -o _bin/privcheck_all.x64.o && $(STRIP64) _bin/privcheck_all.x64.o) && echo '[+] privcheck_all x64' || echo '[!] privcheck_all x64' + @($(CC64) $(CFLAGS) reg/query/query.c -I reg -o _bin/reg_query.x64.o && $(STRIP64) _bin/reg_query.x64.o) && echo '[+] reg query x64' || echo '[!] reg query x64' + @($(CC64) $(CFLAGS) reg/write/write.c -I reg -o _bin/reg_write.x64.o && $(STRIP64) _bin/reg_write.x64.o) && echo '[+] reg write x64' || echo '[!] reg write x64' + @($(CC64) $(CFLAGS) reg/delete/delete.c -I reg -o _bin/reg_delete.x64.o && $(STRIP64) _bin/reg_delete.x64.o) && echo '[+] reg delete x64' || echo '[!] reg delete x64' + @($(CC64) $(CFLAGS) reg/find/find.c -I reg -o _bin/reg_find.x64.o && $(STRIP64) _bin/reg_find.x64.o) && echo '[+] reg find x64' || echo '[!] reg find x64' + # 32-bit builds @($(CC86) $(CFLAGS) arp/arp.c -o _bin/arp.x32.o && $(STRIP86) _bin/arp.x32.o) && echo '[+] arp x32' || echo '[!] arp x32' @@ -62,5 +67,10 @@ bof: clean @($(CC86) $(CFLAGS) privcheck/pshistory.c -o _bin/pshistory.x32.o && $(STRIP86) _bin/pshistory.x32.o) && echo '[+] pshistory x32' || echo '[!] pshistory x32' @($(CC86) $(CFLAGS) privcheck/uacstatus.c -o _bin/uacstatus.x32.o && $(STRIP86) _bin/uacstatus.x32.o) && echo '[+] uacstatus x32' || echo '[!] uacstatus x32' @($(CC86) $(CFLAGS) privcheck/privcheck_all.c -o _bin/privcheck_all.x32.o && $(STRIP86) _bin/privcheck_all.x32.o) && echo '[+] privcheck_all x32' || echo '[!] privcheck_all x32' + @($(CC86) $(CFLAGS) reg/query/query.c -I reg -o _bin/reg_query.x32.o && $(STRIP86) _bin/reg_query.x32.o) && echo '[+] reg query x32' || echo '[!] reg query x32' + @($(CC86) $(CFLAGS) reg/write/write.c -I reg -o _bin/reg_write.x32.o && $(STRIP86) _bin/reg_write.x32.o) && echo '[+] reg write x32' || echo '[!] reg write x32' + @($(CC86) $(CFLAGS) reg/delete/delete.c -I reg -o _bin/reg_delete.x32.o && $(STRIP86) _bin/reg_delete.x32.o) && echo '[+] reg delete x32' || echo '[!] reg delete x32' + @($(CC86) $(CFLAGS) reg/find/find.c -I reg -o _bin/reg_find.x32.o && $(STRIP86) _bin/reg_find.x32.o) && echo '[+] reg find x32' || echo '[!] reg find x32' + clean: @(rm -rf _bin) diff --git a/SAL-BOF/reg/anticrash.c b/SAL-BOF/reg/anticrash.c new file mode 100644 index 0000000..61dfa7d --- /dev/null +++ b/SAL-BOF/reg/anticrash.c @@ -0,0 +1,21 @@ +#include +#include "bofdefs.h" +//For some reason char *[] is invalid in BOF files +//So this function stands to work around that problem + +//makes a char *[] since we can't seem to otherwise +//count is the number of strings you're passing in will crash if this is wrong + +//Must call intFree on returned result +char ** antiStringResolve(unsigned int count, ...) +{ + va_list strings; + va_start(strings, count); + char ** result = intAlloc(sizeof(char *) * count); + for(int i = 0; i < count; i++) + { + result[i] = (char *)va_arg(strings, char *); + } + va_end(strings); + return result; +} \ No newline at end of file diff --git a/SAL-BOF/reg/base.c b/SAL-BOF/reg/base.c new file mode 100644 index 0000000..71998be --- /dev/null +++ b/SAL-BOF/reg/base.c @@ -0,0 +1,207 @@ +#include +#include "bofdefs.h" +#include "beacon.h" +#ifndef bufsize +#define bufsize 8192 +#endif + + +char * output __attribute__((section (".data"))) = 0; // this is just done so its we don't go into .bss which isn't handled properly +WORD currentoutsize __attribute__((section (".data"))) = 0; +HANDLE trash __attribute__((section (".data"))) = NULL; // Needed for x64 to not give relocation error + +#ifdef BOF +int bofstart(); +void internal_printf(const char* format, ...); +void printoutput(BOOL done); +#endif +char * Utf16ToUtf8(const wchar_t* input); + +int bofstart() +{ + output = (char*)MSVCRT$calloc(bufsize, 1); + currentoutsize = 0; + return 1; +} + +void internal_printf(const char* format, ...){ + int buffersize = 0; + int transfersize = 0; + char * curloc = NULL; + char* intBuffer = NULL; + va_list args; + va_start(args, format); + buffersize = MSVCRT$vsnprintf(NULL, 0, format, args); // +1 because vsprintf goes to buffersize-1 , and buffersize won't return with the null + va_end(args); + + // vsnprintf will return -1 on encoding failure (ex. non latin characters in Wide string) + if (buffersize == -1) + return; + + char* transferBuffer = (char*)intAlloc(bufsize); + intBuffer = (char*)intAlloc(buffersize); + /*Print string to memory buffer*/ + va_start(args, format); + MSVCRT$vsnprintf(intBuffer, buffersize, format, args); // tmpBuffer2 has a null terminated string + va_end(args); + if(buffersize + currentoutsize < bufsize) // If this print doesn't overflow our output buffer, just buffer it to the end + { + //BeaconFormatPrintf(&output, intBuffer); + memcpy(output+currentoutsize, intBuffer, buffersize); + currentoutsize += buffersize; + } + else // If this print does overflow our output buffer, lets print what we have and clear any thing else as it is likely this is a large print + { + curloc = intBuffer; + while(buffersize > 0) + { + transfersize = bufsize - currentoutsize; // what is the max we could transfer this request + if(buffersize < transfersize) //if I have less then that, lets just transfer what's left + { + transfersize = buffersize; + } + memcpy(output+currentoutsize, curloc, transfersize); // copy data into our transfer buffer + currentoutsize += transfersize; + if(currentoutsize == bufsize) + { + printoutput(FALSE); // sets currentoutsize to 0 and prints + } + memset(transferBuffer, 0, transfersize); // reset our transfer buffer + curloc += transfersize; // increment by how much data we just wrote + buffersize -= transfersize; // subtract how much we just wrote from how much we are writing overall + } + } + intFree(intBuffer); + intFree(transferBuffer); +} + +void printoutput(BOOL done) +{ + + char * msg = NULL; + BeaconOutput(CALLBACK_OUTPUT, output, currentoutsize); + currentoutsize = 0; + memset(output, 0, bufsize); + if(done) {MSVCRT$free(output); output=NULL;} +} + + +#ifdef DYNAMIC_LIB_COUNT + + +typedef struct loadedLibrary { + HMODULE hMod; // mod handle + const char * name; // name normalized to uppercase +}loadedLibrary, *ploadedLibrary; +loadedLibrary loadedLibraries[DYNAMIC_LIB_COUNT] __attribute__((section (".data"))) = {0}; +DWORD loadedLibrariesCount __attribute__((section (".data"))) = 0; + +BOOL intstrcmp(LPCSTR szLibrary, LPCSTR sztarget) +{ + BOOL bmatch = FALSE; + DWORD pos = 0; + while(szLibrary[pos] && sztarget[pos]) + { + if(szLibrary[pos] != sztarget[pos]) + { + goto end; + } + pos++; + } + if(szLibrary[pos] | sztarget[pos]) // if either of these down't equal null then they can't match + {goto end;} + bmatch = TRUE; + + end: + return bmatch; +} + +FARPROC DynamicLoad(const char * szLibrary, const char * szFunction) +{ + FARPROC fp = NULL; + HMODULE hMod = NULL; + DWORD i = 0; + DWORD liblen = 0; + for(i = 0; i < loadedLibrariesCount; i++) + { + if(intstrcmp(szLibrary, loadedLibraries[i].name)) + { + hMod = loadedLibraries[i].hMod; + } + } + if(!hMod) + { + hMod = LoadLibraryA(szLibrary); + if(!hMod){ + BeaconPrintf(CALLBACK_ERROR, "*** DynamicLoad(%s) FAILED!\nCould not find library to load.", szLibrary); + return NULL; + } + loadedLibraries[loadedLibrariesCount].hMod = hMod; + loadedLibraries[loadedLibrariesCount].name = szLibrary; //And this is why this HAS to be a constant or not freed before bofstop + loadedLibrariesCount++; + } + fp = GetProcAddress(hMod, szFunction); + + if (NULL == fp) + { + BeaconPrintf(CALLBACK_ERROR, "*** DynamicLoad(%s) FAILED!\n", szFunction); + } + return fp; +} +#endif + + +char* Utf16ToUtf8(const wchar_t* input) +{ + int ret = KERNEL32$WideCharToMultiByte( + CP_UTF8, + 0, + input, + -1, + NULL, + 0, + NULL, + NULL + ); + + char* newString = (char*)intAlloc(sizeof(char) * ret); + + ret = KERNEL32$WideCharToMultiByte( + CP_UTF8, + 0, + input, + -1, + newString, + sizeof(char) * ret, + NULL, + NULL + ); + + if (0 == ret) + { + goto fail; + } + +retloc: + return newString; +/*location to free everything centrally*/ +fail: + if (newString){ + intFree(newString); + newString = NULL; + }; + goto retloc; +} + +//release any global functions here +void bofstop() +{ +#ifdef DYNAMIC_LIB_COUNT + DWORD i; + for(i = 0; i < loadedLibrariesCount; i++) + { + FreeLibrary(loadedLibraries[i].hMod); + } +#endif + return; +} diff --git a/SAL-BOF/reg/delete/delete.c b/SAL-BOF/reg/delete/delete.c new file mode 100644 index 0000000..df92bf0 --- /dev/null +++ b/SAL-BOF/reg/delete/delete.c @@ -0,0 +1,160 @@ +#include +#include "bofdefs.h" +#include "base.c" +#include "anticrash.c" + +WINADVAPI LONG WINAPI ADVAPI32$RegOpenKeyExA( + HKEY hKey, + LPCSTR lpSubKey, + DWORD ulOptions, + REGSAM samDesired, + PHKEY phkResult); + +WINADVAPI LONG WINAPI ADVAPI32$RegDeleteValueA( + HKEY hKey, + LPCSTR lpValueName); + +WINADVAPI LONG WINAPI ADVAPI32$RegDeleteKeyA( + HKEY hKey, + LPCSTR lpSubKey); + +WINADVAPI LONG WINAPI ADVAPI32$RegConnectRegistryA( + LPCSTR lpMachineName, + HKEY hKey, + PHKEY phkResult); + +WINADVAPI LONG WINAPI ADVAPI32$RegCloseKey( + HKEY hKey); + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wint-conversion" +char * gHiveName = 1; +#pragma GCC diagnostic pop + +void set_hive_name(DWORD h) +{ + switch(h) { + case 0: gHiveName = "HKEY_CLASSES_ROOT"; break; + case 1: gHiveName = "HKEY_CURRENT_USER"; break; + case 2: gHiveName = "HKEY_LOCAL_MACHINE"; break; + case 3: gHiveName = "HKEY_USERS"; break; + case 5: gHiveName = "HKEY_CURRENT_CONFIG"; break; + default: gHiveName = "UNKNOWN"; break; + } +} + +// Delete a value under a key, or the key itself if valuename is NULL +DWORD Reg_Delete( + const char * hostname, + HKEY hivekey, + const char * keystring, + const char * valuename // NULL = delete the key itself +){ + HKEY key = NULL; + HKEY RemoteKey = NULL; + DWORD dwresult = 0; + + if (hostname == NULL) + { + if (valuename) + { + // Delete value: need to open parent key first + dwresult = ADVAPI32$RegOpenKeyExA(hivekey, keystring, 0, KEY_SET_VALUE, &key); + if (dwresult) { goto END; } + + dwresult = ADVAPI32$RegDeleteValueA(key, valuename); + if (!dwresult) + internal_printf("Deleted value: %s\\%s\\%s\n", gHiveName, keystring, valuename); + } + else + { + // Delete key (must be empty — no subkeys) + dwresult = ADVAPI32$RegDeleteKeyA(hivekey, keystring); + if (!dwresult) + internal_printf("Deleted key: %s\\%s\n", gHiveName, keystring); + } + } + else + { + dwresult = ADVAPI32$RegConnectRegistryA(hostname, hivekey, &RemoteKey); + if (dwresult) { internal_printf("failed to connect\n"); goto END; } + + if (valuename) + { + dwresult = ADVAPI32$RegOpenKeyExA(RemoteKey, keystring, 0, KEY_SET_VALUE, &key); + if (dwresult) { internal_printf("failed to open remote key\n"); goto END; } + + dwresult = ADVAPI32$RegDeleteValueA(key, valuename); + if (!dwresult) + internal_printf("Deleted value: %s\\%s\\%s\n", gHiveName, keystring, valuename); + } + else + { + dwresult = ADVAPI32$RegDeleteKeyA(RemoteKey, keystring); + if (!dwresult) + internal_printf("Deleted key: %s\\%s\n", gHiveName, keystring); + } + } + +END: + if (key) ADVAPI32$RegCloseKey(key); + if (RemoteKey) ADVAPI32$RegCloseKey(RemoteKey); + return dwresult; +} + +#ifdef BOF + +VOID go( + IN PCHAR Buffer, + IN ULONG Length +){ + datap parser = {0}; + const char *hostname = NULL; + const char *path = NULL; + const char *key = NULL; + HKEY hive = (HKEY)0x80000000; + int t = 0; + DWORD dwresult = 0; + + BeaconDataParse(&parser, Buffer, Length); + hostname = BeaconDataExtract(&parser, NULL); + t = BeaconDataInt(&parser); + path = BeaconDataExtract(&parser, NULL); + key = BeaconDataExtract(&parser, NULL); + + set_hive_name(t); + + #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" + hive = (HKEY)((DWORD)hive + (DWORD)t); + #pragma GCC diagnostic pop + + if (*hostname == 0) hostname = NULL; + if (*key == 0) key = NULL; // no -k = delete the key itself + + if (!bofstart()) { return; } + + BeaconPrintf(CALLBACK_OUTPUT, + "Hostname: %s, Hive: %s, Path: %s, Key: %s", + hostname ? hostname : "NULL", gHiveName, path, key ? key : "NULL"); + + dwresult = Reg_Delete(hostname, hive, path, key); + + if (dwresult) + BeaconPrintf(CALLBACK_ERROR, "Failed to delete, error: %lu", dwresult); + + printoutput(TRUE); +} + +#else + +int main() +{ + gHiveName = "HKEY_LOCAL_MACHINE"; + // Delete a value + Reg_Delete(NULL, HKEY_LOCAL_MACHINE, "SOFTWARE\\TestKey", "TestValue"); + // Delete a key + Reg_Delete(NULL, HKEY_LOCAL_MACHINE, "SOFTWARE\\TestKey", NULL); + return 0; +} + +#endif \ No newline at end of file diff --git a/SAL-BOF/reg/find/find.c b/SAL-BOF/reg/find/find.c new file mode 100644 index 0000000..4b37e99 --- /dev/null +++ b/SAL-BOF/reg/find/find.c @@ -0,0 +1,329 @@ +#include +#include "bofdefs.h" +#include "base.c" +#include "anticrash.c" +#include "stack.c" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wint-conversion" +char ** ERegTypes = 1; +char * gHiveName = 1; +#pragma GCC diagnostic pop + +typedef struct _regkeyval { + char * keypath; + DWORD dwkeypathsz; + HKEY hreg; +} regkeyval, *pregkeyval; + +void init_enums(){ + ERegTypes = antiStringResolve(12, "REG_NONE", "REG_SZ", "REG_EXPAND_SZ", "REG_BINARY", "REG_DWORD", "REGDWORD_BE", "REG_LINK", "REG_MULTI_SZ", "REG_RESOURCE_LIST", "REG_FULL_RESOURCE_DESC", "REG_RESOURCE_REQ_LIST", "REG_QWORD"); +} + +void free_enums(){ + intFree(ERegTypes); +} + +void set_hive_name(DWORD h) +{ + switch(h) { + case 0: gHiveName = "HKEY_CLASSES_ROOT"; break; + case 1: gHiveName = "HKEY_CURRENT_USER"; break; + case 2: gHiveName = "HKEY_LOCAL_MACHINE"; break; + case 3: gHiveName = "HKEY_USERS"; break; + case 5: gHiveName = "HKEY_CURRENT_CONFIG"; break; + default: gHiveName = "UNKNOWN"; break; + } +} + +pregkeyval init_regkey(const char * curpath, DWORD dwcurpathsz, const char * childkey, DWORD dwchildkeysz, HKEY hreg) +{ + pregkeyval item = (pregkeyval)intAlloc(sizeof(regkeyval)); + item->dwkeypathsz = dwcurpathsz + ((dwchildkeysz) ? dwchildkeysz + 1 : 0); //str\str does not include null or just str, if we don't have a child key + item->keypath = intAlloc(item->dwkeypathsz + 1); + memcpy(item->keypath, curpath, dwcurpathsz); + if(dwchildkeysz > 0) + { + item->keypath[dwcurpathsz] = '\\'; + memcpy(item->keypath + dwcurpathsz + 1, childkey, dwchildkeysz); + } + item->hreg = hreg; + //item->keypath[item->dwkeypathsz] = 0; + return item; +} + +void free_regkey(pregkeyval val) +{ + if(val->keypath) + { + intFree(val->keypath); + } + if(val->hreg) + { + ADVAPI32$RegCloseKey(val->hreg); + } +} + +void Reg_InternalPrintKey(char * data, const char * valuename, DWORD type, DWORD datalen, HKEY key){ + char default_name[] = {'[', 'N', 'U', 'L', 'L', ']', 0}; + int i = 0; + + if(valuename == NULL) + { + valuename = default_name; + } + internal_printf("\t%-20s %-15s ", valuename, (type >= 0 && type <= 11) ? ERegTypes[type] : "UNKNOWN"); + + if(type == REG_BINARY) + { + for(i = 0; i < datalen; i++) + { + if(i % 16 == 0) + internal_printf("\n"); + internal_printf(" %2.2x ", data[i] & 0xff); + } + internal_printf("\n"); + } + else if ((type == REG_DWORD || type == REG_DWORD_BIG_ENDIAN) && datalen == 4) + internal_printf("%lu\n", *(DWORD *)data); + else if (type == REG_QWORD && datalen == 8) + internal_printf("%llu\n", *(QWORD *)data); + else if (type == REG_SZ || type == REG_EXPAND_SZ) + internal_printf("%s\n", data); + else if (type == REG_MULTI_SZ) + { + while(data[0] != '\0') + { + DWORD len = MSVCRT$strlen(data)+1; + internal_printf("%s%s", data, (data[len]) ? "\\0" : ""); + data += MSVCRT$strlen(data)+1; + } + internal_printf("\n"); + } + else + { + internal_printf("None data type, or unhandled\n"); + } +} + +// Simple case-insensitive substring match, no CRT +static BOOL str_icontains(const char *haystack, const char *needle) { + if (!needle || !*needle) return TRUE; + if (!haystack) return FALSE; + size_t nlen = MSVCRT$strlen(needle); + size_t hlen = MSVCRT$strlen(haystack); + if (nlen > hlen) return FALSE; + for (size_t i = 0; i <= hlen - nlen; i++) { + size_t j = 0; + while (j < nlen) { + char hc = haystack[i+j]; + char nc = needle[j]; + // tolower inline + if (hc >= 'A' && hc <= 'Z') hc += 32; + if (nc >= 'A' && nc <= 'Z') nc += 32; + if (hc != nc) break; + j++; + } + if (j == nlen) return TRUE; + } + return FALSE; +} + +// search_type: 0 = value name, 1 = value data, 2 = both +DWORD Reg_Find( + const char * hostname, + HKEY hivekey, + const char * keystring, + const char * pattern, + int search_type +){ + HKEY rootkey = NULL; + HKEY RemoteKey = NULL; + HKEY curKey = NULL; + DWORD dwresult = 0; + DWORD cSubKeys = 0, cbMaxSubKey = 0; + DWORD cValues = 0, cchMaxValue = 0, cchMaxData = 0; + DWORD cchValue = 0, cchData = 0, regType = 0; + DWORD cbName = 0; + DWORD i = 0; + DWORD retCode = 0; + DWORD matches = 0; + Pstack keyStack = NULL; + pregkeyval curitem = NULL; + char * currentkeyname = NULL; + char * currentvaluename = NULL; + char * currentdata = NULL; + + if (hostname == NULL) + { + dwresult = ADVAPI32$RegOpenKeyExA(hivekey, keystring, 0, KEY_READ, &rootkey); + if (dwresult) { goto END; } + } + else + { + dwresult = ADVAPI32$RegConnectRegistryA(hostname, hivekey, &RemoteKey); + if (dwresult) { internal_printf("failed to connect\n"); goto END; } + + dwresult = ADVAPI32$RegOpenKeyExA(RemoteKey, keystring, 0, KEY_READ, &rootkey); + if (dwresult) { internal_printf("failed to open remote key\n"); goto END; } + } + + keyStack = stackInit(); + keyStack->push(keyStack, init_regkey(keystring, MSVCRT$strlen(keystring), NULL, 0, rootkey)); + + while ((curitem = keyStack->pop(keyStack)) != NULL) + { + dwresult = ADVAPI32$RegQueryInfoKeyA( + curitem->hreg, NULL, NULL, NULL, + &cSubKeys, &cbMaxSubKey, NULL, + &cValues, &cchMaxValue, &cchMaxData, + NULL, NULL); + + if (dwresult) { goto nextloop; } + + currentkeyname = intAlloc(cbMaxSubKey + 1); + currentvaluename = intAlloc(cchMaxValue + 2); + currentdata = intAlloc(cchMaxData + 2); + + // ── Search values ────────────────────────────────────────────────── + for (i = 0; i < cValues; i++) + { + cchValue = cchMaxValue + 2; + cchData = cchMaxData + 2; + + retCode = ADVAPI32$RegEnumValueA( + curitem->hreg, i, + currentvaluename, &cchValue, + NULL, ®Type, + (LPBYTE)currentdata, &cchData); + + if (retCode != ERROR_SUCCESS) continue; + + BOOL name_match = (search_type == 0 || search_type == 2) + && str_icontains(currentvaluename, pattern); + + // Data match only meaningful for string types + BOOL data_match = FALSE; + if (search_type == 1 || search_type == 2) + { + if (regType == REG_SZ || regType == REG_EXPAND_SZ) + data_match = str_icontains(currentdata, pattern); + } + + if (name_match || data_match) + { + if (matches == 0) + internal_printf("%-12s %-20s %-15s %s\n", + "Match", "Value Name", "Type", "Path"); + + internal_printf("%-12s %-20s %-15s %s\\%s\n", + name_match ? "name" : "data", + currentvaluename, + (regType <= 11) ? ERegTypes[regType] : "UNKNOWN", + gHiveName, curitem->keypath); + + // Print the matched value inline + Reg_InternalPrintKey(currentdata, currentvaluename, regType, cchData, curitem->hreg); + matches++; + } + } + + // ── Push subkeys onto stack for traversal ───────────────────────── + for (i = 0; i < cSubKeys; i++) + { + cbName = cbMaxSubKey + 1; + retCode = ADVAPI32$RegEnumKeyExA( + curitem->hreg, i, + currentkeyname, &cbName, + NULL, NULL, NULL, NULL); + + if (retCode == ERROR_SUCCESS) + { + DWORD openResult = ADVAPI32$RegOpenKeyExA( + curitem->hreg, currentkeyname, 0, KEY_READ, &curKey); + if (openResult == ERROR_SUCCESS) + keyStack->push(keyStack, init_regkey( + curitem->keypath, curitem->dwkeypathsz, + currentkeyname, cbName, curKey)); + // non-fatal, continue on access denied + } + } + + nextloop: + if (currentkeyname) { intFree(currentkeyname); currentkeyname = NULL; } + if (currentvaluename) { intFree(currentvaluename); currentvaluename = NULL; } + if (currentdata) { intFree(currentdata); currentdata = NULL; } + cSubKeys = cbMaxSubKey = cValues = cchMaxValue = cchMaxData = 0; + + if (curitem) { free_regkey(curitem); intFree(curitem); curitem = NULL; } + } + + if (matches == 0) + internal_printf("No matches found for pattern: %s\n", pattern); + else + internal_printf("\nTotal matches: %lu\n", matches); + + dwresult = ERROR_SUCCESS; // partial access denials are non-fatal + +END: + if (currentkeyname) intFree(currentkeyname); + if (currentvaluename) intFree(currentvaluename); + if (currentdata) intFree(currentdata); + if (RemoteKey) ADVAPI32$RegCloseKey(RemoteKey); + if (keyStack) keyStack->free(keyStack); + return dwresult; +} + +#ifdef BOF + +VOID go( + IN PCHAR Buffer, + IN ULONG Length +){ + datap parser = {0}; + const char *hostname = NULL; + const char *path = NULL; + const char *pattern = NULL; + HKEY hive = (HKEY)0x80000000; + int t = 0; + int search_type = 0; + DWORD dwresult = 0; + + init_enums(); + BeaconDataParse(&parser, Buffer, Length); + hostname = BeaconDataExtract(&parser, NULL); + t = BeaconDataInt(&parser); + path = BeaconDataExtract(&parser, NULL); + pattern = BeaconDataExtract(&parser, NULL); + search_type = BeaconDataInt(&parser); + + set_hive_name(t); + + #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" + hive = (HKEY)((DWORD)hive + (DWORD)t); + #pragma GCC diagnostic pop + + if (*hostname == 0) hostname = NULL; + + if (!*pattern) { + BeaconPrintf(CALLBACK_ERROR, "Pattern required. Use -s "); + return; + } + + if (!bofstart()) { return; } + + BeaconPrintf(CALLBACK_OUTPUT, + "Searching: %s\\%s for pattern \"%s\" (mode: %s)", + gHiveName, path, pattern, + (search_type == 0) ? "name" : (search_type == 1) ? "data" : "both"); + + dwresult = Reg_Find(hostname, hive, path, pattern, search_type); + + if (dwresult) + BeaconPrintf(CALLBACK_ERROR, "reg_find failed, error: %lu", dwresult); + + printoutput(TRUE); + free_enums(); +} + +#endif \ No newline at end of file diff --git a/SAL-BOF/reg/query/query.c b/SAL-BOF/reg/query/query.c new file mode 100644 index 0000000..114becf --- /dev/null +++ b/SAL-BOF/reg/query/query.c @@ -0,0 +1,461 @@ +#include +#include +#include "bofdefs.h" +#include "base.c" +#include "anticrash.c" +#include "stack.c" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wint-conversion" +char ** ERegTypes = 1; +char * gHiveName = 1; +#pragma GCC diagnostic pop +//const char * hostname, HKEY hivekey, DWORD Arch, const char* keystring, int depth, int maxdepth) + +typedef struct _regkeyval{ + char * keypath; + DWORD dwkeypathsz; + HKEY hreg; +} regkeyval, *pregkeyval; + +void init_enums(){ + ERegTypes = antiStringResolve(12, "REG_NONE", "REG_SZ", "REG_EXPAND_SZ", "REG_BINARY", "REG_DWORD", "REGDWORD_BE", "REG_LINK", "REG_MULTI_SZ", "REG_RESOURCE_LIST", "REG_FULL_RESOURCE_DESC", "REG_RESOURCE_REQ_LIST", "REG_QWORD"); +} + +void free_enums(){ + intFree(ERegTypes); +} + +//HKEY compare didn't work in BOF for some reason +void set_hive_name(DWORD h) +{ + switch(h) { + case 0: gHiveName = "HKEY_CLASSES_ROOT"; break; + case 1: gHiveName = "HKEY_CURRENT_USER"; break; + case 2: gHiveName = "HKEY_LOCAL_MACHINE"; break; + case 3: gHiveName = "HKEY_USERS"; break; + case 5: gHiveName = "HKEY_CURRENT_CONFIG"; break; + default: gHiveName = "UNKNOWN"; break; + } +} + +pregkeyval init_regkey(const char * curpath, DWORD dwcurpathsz, const char * childkey, DWORD dwchildkeysz, HKEY hreg) +{ + pregkeyval item = (pregkeyval)intAlloc(sizeof(regkeyval)); + item->dwkeypathsz = dwcurpathsz + ((dwchildkeysz) ? dwchildkeysz + 1 : 0); //str\str does not include null or just str, if we don't have a child key + item->keypath = intAlloc(item->dwkeypathsz + 1); + memcpy(item->keypath, curpath, dwcurpathsz); + if(dwchildkeysz > 0) + { + item->keypath[dwcurpathsz] = '\\'; + memcpy(item->keypath + dwcurpathsz + 1, childkey, dwchildkeysz); + } + item->hreg = hreg; + //item->keypath[item->dwkeypathsz] = 0; + return item; +} + +void free_regkey(pregkeyval val) +{ + if(val->keypath) + { + intFree(val->keypath); + } + if(val->hreg) + { + ADVAPI32$RegCloseKey(val->hreg); + } +} + +void Reg_KeyToTimestamp(HKEY key, char *stringDate) { + FILETIME mytime; + LSTATUS stat; + SYSTEMTIME sAsystemTime, stLocal; + + stat = ADVAPI32$RegQueryInfoKeyA(key, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, (PFILETIME) &mytime); + if(stat != ERROR_SUCCESS) { + BeaconPrintf(CALLBACK_ERROR, "Error calling RegQueryInfoKeyA with error code %d\n", stat); + return; + } + + KERNEL32$FileTimeToSystemTime(&mytime, &sAsystemTime); + KERNEL32$SystemTimeToTzSpecificLocalTime(NULL, &sAsystemTime, &stLocal); + + MSVCRT$sprintf(stringDate, "%02i/%02i/%04i %02i:%02i:%02i", stLocal.wMonth, stLocal.wDay, stLocal.wYear, stLocal.wHour, stLocal.wMinute, stLocal.wSecond); +} + +void Reg_InternalPrintKey(char * data, const char * valuename, DWORD type, DWORD datalen, HKEY key){ + char default_name[] = {'[', 'N', 'U', 'L', 'L', ']', 0}; + int i = 0; + + if(valuename == NULL) + { + valuename = default_name; + } + internal_printf("\t%-20s %-15s ", valuename, (type >= 0 && type <= 11) ? ERegTypes[type] : "UNKNOWN"); + + if(type == REG_BINARY) + { + for(i = 0; i < datalen; i++) + { + if(i % 16 == 0) + internal_printf("\n"); + internal_printf(" %2.2x ", data[i] & 0xff); + } + internal_printf("\n"); + } + else if ((type == REG_DWORD || type == REG_DWORD_BIG_ENDIAN) && datalen == 4) + internal_printf("%lu\n", *(DWORD *)data); + else if (type == REG_QWORD && datalen == 8) + internal_printf("%llu\n", *(QWORD *)data); + else if (type == REG_SZ || type == REG_EXPAND_SZ) + internal_printf("%s\n", data); + else if (type == REG_MULTI_SZ) + { + while(data[0] != '\0') + { + DWORD len = MSVCRT$strlen(data)+1; + internal_printf("%s%s", data, (data[len]) ? "\\0" : ""); + data += MSVCRT$strlen(data)+1; + } + internal_printf("\n"); + } + else + { + internal_printf("None data type, or unhandled\n"); + } +} + +DWORD Reg_GetValue(const char * hostname, HKEY hivekey, DWORD Arch, const char* keystring, const char* value){ + HKEY key = 0; + HKEY RemoteKey = NULL; + char* ValueData = NULL; + DWORD type = 0; + DWORD dwRet = 0; + ValueData = NULL; + DWORD flags = RRF_RT_ANY; + DWORD size = 0; + if(hostname == NULL) + { + dwRet = ADVAPI32$RegOpenKeyExA(hivekey, keystring, 0, KEY_READ, &key); + + if(dwRet){ goto END;} + } + else + { + dwRet = ADVAPI32$RegConnectRegistryA(hostname, hivekey, &RemoteKey); + + if(dwRet){ + internal_printf("failed to connect"); + goto END; + } + dwRet = ADVAPI32$RegOpenKeyExA(RemoteKey, keystring, 0, KEY_READ, &key); + + if(dwRet){ + internal_printf("failed to open remote key"); + goto END; + } + } + + dwRet = ADVAPI32$RegQueryValueExA( key, + value, + NULL, + &type, + NULL, + &size ); + if(dwRet != ERROR_SUCCESS){goto END;} + if(type == REG_SZ || type == REG_EXPAND_SZ || type == REG_MULTI_SZ) + size += 2; // This makes sure that even if the string was stored without the appropriate terminating characters we don't overrun, 2 because multi needs 2 + ValueData = intAlloc(size); + if (ValueData == NULL){ + BeaconPrintf(CALLBACK_ERROR, "Failed to allocate memory\n"); + dwRet = E_OUTOFMEMORY; + goto END; + } + dwRet = ADVAPI32$RegQueryValueExA( + key, + value, + NULL, + &type, + (LPBYTE)ValueData, + &size + ); + if(!dwRet) + { + char stringDate[19]; + Reg_KeyToTimestamp(key, stringDate); + internal_printf("%24s %s\\%s\n", stringDate, gHiveName, keystring); + Reg_InternalPrintKey(ValueData, value, type, size, key); + } + END: + if(ValueData) + intFree(ValueData); + if(key) + ADVAPI32$RegCloseKey(key); + if(RemoteKey) + ADVAPI32$RegCloseKey(RemoteKey); + return dwRet; + + +} + +DWORD Reg_EnumKey(const char * hostname, HKEY hivekey, DWORD Arch, const char* keystring, BOOL recursive){ + DWORD testval = 0; + DWORD cbName = 0; // size of name string + DWORD cSubKeys=0; // number of subkeys + DWORD cbMaxSubKey = 0; // longest subkey size + DWORD cchMaxClass = 0; // longest class string + DWORD cValues = 0; // number of values for key + DWORD cchMaxValue = 0; // longest value name + DWORD cchMaxData = 0; + DWORD cchData = 0; + DWORD cchValue = 0; + DWORD regType = 0; + DWORD i = 0, j = 0, retCode = 0; + DWORD dwresult = 0; + HKEY rootkey = 0; + HKEY curKey = 0; + HKEY RemoteKey = 0; + Pstack keyStack = NULL; + pregkeyval curitem = NULL; + char * errormsg = NULL; + char * currentkeyname = NULL; + char * currentvaluename = NULL; + char * currentdata = NULL; + //char * fullkeyname = NULL; + if(hostname == NULL) + { + dwresult = ADVAPI32$RegOpenKeyExA(hivekey, keystring, 0, KEY_READ, &rootkey); + + if(dwresult){ goto END;} + } + else + { + dwresult = ADVAPI32$RegConnectRegistryA(hostname, hivekey, &RemoteKey); + + if(dwresult){ + internal_printf("failed to connect"); + goto END; + } + dwresult = ADVAPI32$RegOpenKeyExA(RemoteKey, keystring, 0, KEY_READ, &rootkey); + + if(dwresult){ + internal_printf("failed to open remote key"); + goto END; + } + } + keyStack = stackInit(); + + keyStack->push(keyStack, init_regkey(keystring, MSVCRT$strlen(keystring), NULL, 0, rootkey)); + while((curitem = keyStack->pop(keyStack)) != NULL) + { + char stringDate[19]; + Reg_KeyToTimestamp(curitem->hreg, stringDate); + + internal_printf("%-24s %s\\%s\n", stringDate, gHiveName, curitem->keypath); + // Get the class name and the value count. + dwresult = ADVAPI32$RegQueryInfoKeyA( + curitem->hreg, // key handle + NULL, // buffer for class name + NULL, // size of class string + NULL, // reserved + &cSubKeys, // number of subkeys + &cbMaxSubKey, // longest subkey size + NULL, // longest class string + &cValues, // number of values for this key + &cchMaxValue, // longest value name + &cchMaxData, // longest value data + NULL, // security descriptor + NULL); // last write time + + if(dwresult){ + internal_printf("failed to query info about key"); + goto nextloop; + } + // Enumerate the subkeys, until RegEnumKeyEx fails. + currentkeyname = intAlloc(cbMaxSubKey +1); + currentvaluename = intAlloc(cchMaxValue+2); + currentdata = intAlloc(cchMaxData); + if (cValues) + { + for (i=0, retCode=ERROR_SUCCESS; ihreg, i, + currentvaluename, + &cchValue, + NULL, + ®Type, + (LPBYTE)currentdata, + &cchData); + if (retCode == ERROR_SUCCESS ) + { + Reg_InternalPrintKey(currentdata, currentvaluename, regType, cchData, (HKEY) curitem->hreg); + } + + } + internal_printf("\n"); + } + if (cSubKeys) + { + for (i=0; ihreg, i, + currentkeyname, + &cbName, + NULL, NULL, NULL, NULL); + if (retCode == ERROR_SUCCESS) + { + if(recursive) + { + dwresult = ADVAPI32$RegOpenKeyExA(curitem->hreg, currentkeyname, 0, KEY_READ, &curKey); + if(dwresult) + { + BeaconPrintf(CALLBACK_ERROR, "Could not open key %s\\%s\\%s: Error %lx", + gHiveName, curitem->keypath, currentkeyname, dwresult); + dwresult = ERROR_SUCCESS; // ← reset, don't poison final return + } + else { + keyStack->push(keyStack, init_regkey(curitem->keypath, curitem->dwkeypathsz, currentkeyname, cbName, curKey)); + } + } + else + { + curKey = NULL; + // Don't propagate this open error — it's only for timestamp display + DWORD tsResult = ADVAPI32$RegOpenKeyExA(curitem->hreg, currentkeyname, 0, KEY_READ, &curKey); + if (curKey) { + Reg_KeyToTimestamp(curKey, stringDate); + ADVAPI32$RegCloseKey(curKey); // ← also close it, was leaking + curKey = NULL; + } + internal_printf("%-24s %s\\%s\\%s\n", + (tsResult == ERROR_SUCCESS) ? stringDate : "Unable to get time", + gHiveName, curitem->keypath, currentkeyname); + } + } + } + } + nextloop: + if(currentkeyname) + {intFree(currentkeyname); currentkeyname = NULL;} + if(currentvaluename) + {intFree(currentvaluename); currentvaluename = NULL;} + if(currentdata) + {intFree(currentdata); currentdata = NULL;} + cSubKeys = 0; + cbMaxSubKey = 0; + cValues = 0; + cchMaxValue = 0; + cchMaxData = 0; + if (curitem) + { + free_regkey(curitem); + intFree(curitem); + curitem = NULL; + } + } // end While + + END: + if(currentkeyname != NULL) + intFree(currentkeyname); + if(currentvaluename != NULL) + intFree(currentvaluename); + if(currentdata != NULL) + intFree(currentdata); + if(RemoteKey) + ADVAPI32$RegCloseKey(RemoteKey); + if(keyStack) + { + keyStack->free(keyStack); + } + return dwresult; +} + +#ifdef BOF + +VOID go( + IN PCHAR Buffer, + IN ULONG Length +) +{ + datap parser = {0}; + const char * hostname = NULL; + HKEY hive = (HKEY)0x80000000; + + const char * path = NULL; + const char * key = NULL; + int t = 0; + BOOL recursive = FALSE; + DWORD dwresult = 0; + init_enums(); + BeaconDataParse(&parser, Buffer, Length); + hostname = BeaconDataExtract(&parser, NULL); + t = BeaconDataInt(&parser); + set_hive_name(t); + #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" + #pragma GCC diagnostic ignored "-Wpointer-to-int-cast" + hive = (HKEY)((DWORD) hive + (DWORD)t); + #pragma GCC diagnostic pop + path = BeaconDataExtract(&parser, NULL); + key = BeaconDataExtract(&parser, NULL); + recursive = BeaconDataInt(&parser); + //correct hostname param + if(*hostname == 0) + { + hostname = NULL; + } + if(*key == 0) + { + key = NULL; + } + if(!bofstart()) + { + return; + } + + // testing the args passed + BeaconPrintf(CALLBACK_OUTPUT, "Hostname: %s, Hive: %s, Path: %s, Key: %s, Recursive: %d", (hostname) ? hostname : "NULL", gHiveName, path, (key) ? key : "NULL", recursive); + + if(key) + { + dwresult = Reg_GetValue(hostname,hive,0,path,key); + } + else + { + dwresult = Reg_EnumKey(hostname,hive,0,path, recursive); + } + if(dwresult) + { + BeaconPrintf(CALLBACK_ERROR, "Failed to query Regkey, error value: %d", dwresult); + } + printoutput(TRUE); + free_enums(); +}; + +#else + +int main() +{ + init_enums(); + gHiveName = "Testname"; + Reg_EnumKey(NULL,HKEY_LOCAL_MACHINE,0,"system\\currentcontrolset\\services\\webclient", 1); + Reg_EnumKey(NULL,HKEY_LOCAL_MACHINE,0,"system\\currentcontrolset\\services\\webclient", FALSE); + Reg_EnumKey(NULL,HKEY_LOCAL_MACHINE,0,"system\\currentcontrolset\\asdf\\webclient", TRUE); + Reg_GetValue(NULL,HKEY_LOCAL_MACHINE,0,"system\\currentcontrolset\\services\\webclient","ImagePath"); + Reg_GetValue(NULL,HKEY_LOCAL_MACHINE,0,"system\\currentcontrolset\\services\\webclient","nope"); + Reg_GetValue(NULL,HKEY_LOCAL_MACHINE,0,"nope","ImagePath"); + Reg_EnumKey("172.31.0.1",HKEY_LOCAL_MACHINE,0,"system\\currentcontrolset\\services\\vds", TRUE); + Reg_EnumKey("172.31.0.1",HKEY_LOCAL_MACHINE,0,"system\\currentcontrolset\\services\\vds", FALSE); + Reg_EnumKey("172.31.0.1",HKEY_LOCAL_MACHINE,0,"system\\currentcontrolset\\asdf\\vds", TRUE); + Reg_GetValue("172.31.0.1",HKEY_LOCAL_MACHINE,0,"system\\currentcontrolset\\services\\vds","ImagePath"); + Reg_GetValue("172.31.0.1",HKEY_LOCAL_MACHINE,0,"system\\currentcontrolset\\services\\vds","nope"); + Reg_GetValue("172.31.0.1",HKEY_LOCAL_MACHINE,0,"nope","ImagePath"); + Reg_GetValue("nope",HKEY_LOCAL_MACHINE,0,"nope","ImagePath"); + free_enums(); + return 0; +} + +#endif \ No newline at end of file diff --git a/SAL-BOF/reg/stack.c b/SAL-BOF/reg/stack.c new file mode 100644 index 0000000..daaa8e6 --- /dev/null +++ b/SAL-BOF/reg/stack.c @@ -0,0 +1,75 @@ +#include "bofdefs.h" +//Note if anyone else adopts or looks at this +//Its not threadsafe +typedef struct _item{ + void * elem; + struct _item * next; + struct _item * prev; +}item, *Pitem; + +typedef struct _stack{\ + Pitem head; + Pitem tail; + void (*push)(struct _stack *, void *); + void * (*pop)(struct _stack *); + void (*free)(struct _stack *); +}stack, *Pstack; + +void _push(Pstack q, void * v) +{ + Pitem i = (Pitem)intAlloc(sizeof(item)); + i->elem = v; + if(q->head == NULL && q->tail == NULL) // empty + { + q->head = i; + q->tail = i; + i->next = NULL; + i->prev = NULL; + }else // not empty + { + q->tail->next = i; + i->prev = q->tail; + q->tail = i; + } +} +void * _pop(Pstack q) +{ + void * retval = NULL; + Pitem i = NULL; + if(q->head == NULL && q->tail == NULL) // empty + { + return NULL; + } + retval = q->tail->elem; + if(q->head == q->tail) //last elem + { + intFree(q->head); + q->head = NULL; + q->tail = NULL; + } + else // not the last item + { + i = q->tail; + q->tail = i->prev; + intFree(i); + } + return retval; + +} + +void _free(Pstack q) +{ + intFree(q); +} + + +Pstack stackInit() +{ + Pstack q = (Pstack)intAlloc(sizeof(stack)); + q->head = NULL; + q->tail = NULL; + q->push = _push; + q->pop = _pop; + q->free = _free; + return q; +} \ No newline at end of file diff --git a/SAL-BOF/reg/write/write.c b/SAL-BOF/reg/write/write.c new file mode 100644 index 0000000..b299b24 --- /dev/null +++ b/SAL-BOF/reg/write/write.c @@ -0,0 +1,198 @@ +#include +#include "bofdefs.h" +#include "base.c" +#include "anticrash.c" + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wint-conversion" +char * gHiveName = 1; +#pragma GCC diagnostic pop + +static QWORD parse_qword(const char *s) { + QWORD result = 0; + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + while (*s) { + char c = *s++; + QWORD digit; + if (c >= '0' && c <= '9') digit = c - '0'; + else if (c >= 'a' && c <= 'f') digit = c - 'a' + 10; + else if (c >= 'A' && c <= 'F') digit = c - 'A' + 10; + else break; + result = (result << 4) | digit; + } + } else { + while (*s >= '0' && *s <= '9') + result = result * 10 + (*s++ - '0'); + } + return result; +} + +static DWORD parse_dword(const char *s) { + return (DWORD)parse_qword(s); // reuse, truncate to 32-bit +} + + +void set_hive_name(DWORD h) +{ + switch(h) { + case 0: gHiveName = "HKEY_CLASSES_ROOT"; break; + case 1: gHiveName = "HKEY_CURRENT_USER"; break; + case 2: gHiveName = "HKEY_LOCAL_MACHINE"; break; + case 3: gHiveName = "HKEY_USERS"; break; + case 5: gHiveName = "HKEY_CURRENT_CONFIG"; break; + default: gHiveName = "UNKNOWN"; break; + } +} + +// Supported types: REG_SZ=1, REG_EXPAND_SZ=2, REG_BINARY=3, REG_DWORD=4, REG_QWORD=11 +DWORD Reg_WriteValue( + const char * hostname, + HKEY hivekey, + const char * keystring, + const char * valuename, + DWORD regtype, + const char * data, + DWORD datasz +){ + HKEY key = NULL; + HKEY RemoteKey = NULL; + DWORD dwresult = 0; + DWORD dwDisp = 0; // REG_CREATED_NEW_KEY or REG_OPENED_EXISTING_KEY + + if (hostname == NULL) + { + dwresult = ADVAPI32$RegCreateKeyExA( + hivekey, keystring, 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, + NULL, &key, &dwDisp); + if (dwresult) { goto END; } + } + else + { + dwresult = ADVAPI32$RegConnectRegistryA(hostname, hivekey, &RemoteKey); + if (dwresult) { internal_printf("failed to connect\n"); goto END; } + + dwresult = ADVAPI32$RegCreateKeyExA( + RemoteKey, keystring, 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, + NULL, &key, &dwDisp); + if (dwresult) { internal_printf("failed to open/create remote key\n"); goto END; } + } + + dwresult = ADVAPI32$RegSetValueExA(key, valuename, 0, regtype, (const BYTE *)data, datasz); + + if (!dwresult) + { + internal_printf("%s: %s\\%s\\%s = [type:%lu, sz:%lu]\n", + (dwDisp == REG_CREATED_NEW_KEY) ? "Created" : "Updated", + gHiveName, keystring, valuename, regtype, datasz); + } + +END: + if (key) ADVAPI32$RegCloseKey(key); + if (RemoteKey) ADVAPI32$RegCloseKey(RemoteKey); + return dwresult; +} + +#ifdef BOF + +VOID go( + IN PCHAR Buffer, + IN ULONG Length +){ + datap parser = {0}; + const char *hostname = NULL; + const char *path = NULL; + const char *key = NULL; + const char *dataStr = NULL; + HKEY hive = (HKEY)0x80000000; + int t = 0; + DWORD regtype = 0; + DWORD dwresult = 0; + + // Buffers for numeric coercion + DWORD dword_val = 0; + QWORD qword_val = 0; + const char *data = NULL; + DWORD datasz = 0; + + BeaconDataParse(&parser, Buffer, Length); + hostname = BeaconDataExtract(&parser, NULL); + t = BeaconDataInt(&parser); + path = BeaconDataExtract(&parser, NULL); + key = BeaconDataExtract(&parser, NULL); + regtype = (DWORD)BeaconDataInt(&parser); + dataStr = BeaconDataExtract(&parser, NULL); // always arrives as cstr + + set_hive_name(t); + + #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" + hive = (HKEY)((DWORD)hive + (DWORD)t); + #pragma GCC diagnostic pop + + if (*hostname == 0) hostname = NULL; + if (*key == 0) { BeaconPrintf(CALLBACK_ERROR, "Value name required\n"); return; } + + // Type coercion — BOF owns conversion from string + switch (regtype) { + case REG_DWORD: + dword_val = parse_dword(dataStr); + data = (const char *)&dword_val; + datasz = sizeof(DWORD); + break; + case REG_QWORD: + qword_val = parse_qword(dataStr); + data = (const char *)&qword_val; + datasz = sizeof(QWORD); + break; + case REG_SZ: + case REG_EXPAND_SZ: + data = dataStr; + datasz = (DWORD)MSVCRT$strlen(dataStr) + 1; + break; + case REG_BINARY: + // For binary, expect hex string: "deadbeef" → bytes + // Simple implementation: pass raw string bytes as-is + // For proper hex decode, add a hex2bin helper + data = dataStr; + datasz = (DWORD)MSVCRT$strlen(dataStr); + break; + default: + data = dataStr; + datasz = (DWORD)MSVCRT$strlen(dataStr) + 1; + break; + } + + if (!bofstart()) return; + + BeaconPrintf(CALLBACK_OUTPUT, + "Hostname: %s, Hive: %s, Path: %s, Key: %s, Type: %lu, DataSz: %lu", + hostname ? hostname : "NULL", gHiveName, path, key, regtype, datasz); + + dwresult = Reg_WriteValue(hostname, hive, path, key, regtype, data, datasz); + + if (dwresult) + BeaconPrintf(CALLBACK_ERROR, "Failed to write registry value, error: %lu", dwresult); + + printoutput(TRUE); +} + +#else + +int main() +{ + gHiveName = "HKEY_LOCAL_MACHINE"; + // Test REG_SZ write + Reg_WriteValue(NULL, HKEY_LOCAL_MACHINE, + "SOFTWARE\\TestKey", "TestValue", + REG_SZ, "HelloWorld", 11); + // Test REG_DWORD write + DWORD dval = 1; + Reg_WriteValue(NULL, HKEY_LOCAL_MACHINE, + "SOFTWARE\\TestKey", "DwordValue", + REG_DWORD, (char*)&dval, sizeof(DWORD)); + return 0; +} + +#endif \ No newline at end of file diff --git a/SAL-BOF/sal.axs b/SAL-BOF/sal.axs index 8d3185c..d037568 100644 --- a/SAL-BOF/sal.axs +++ b/SAL-BOF/sal.axs @@ -167,5 +167,244 @@ cmd_whoami.setPreHook(function (id, cmdline, parsed_json, ...parsed_lines) { ax.execute_alias(id, cmdline, `execute bof "${bof_path}"`, "BOF implementation: whoami /all"); }); -var group_test = ax.create_commands_group("SAL-BOF", [cmd_arp, cmd_cacls, cmd_dir, cmd_env, cmd_ipconfig, cmd_listdns, cmd_netstat, cmd_nslookup, cmd_privcheck, cmd_routeprint, cmd_uptime, cmd_useridletime, cmd_whoami]); +var REGHIVES = { + "HKLM": 2, + "HKCU": 1, + "HKU": 3, + "HKCR": 0, + "HKCC": 5, +}; + +var cmd_reg_query = ax.create_command( + "reg_query", + "Query a registry key or specific value.", + "reg_query [-h hostname] [-k value]\n" + + " reg_query HKLM -p SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion\n" + + " reg_query HKLM -p SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion -k ProductName\n" + + " reg_query HKLM -p SOFTWARE\\\\Microsoft\\\\Windows\\\\CurrentVersion -k ProductName -h 192.168.1.10" +); + +cmd_reg_query.addArgString("hive", true); +cmd_reg_query.addArgFlagString("-h", "hostname", "Remote hostname or IP", ""); +cmd_reg_query.addArgFlagString("-k", "key", "Value name to query", ""); +cmd_reg_query.addArgFlagString("-p", "path", "Registry path", ""); + +cmd_reg_query.setPreHook(function(id, cmdline, parsed_json, ...parsed_lines) { + + let rawHostname = parsed_json["hostname"] || ""; + let hostname = rawHostname ? ("\\\\" + rawHostname) : ""; + let key = parsed_json["key"] || ""; + let path = parsed_json["path"] || ""; + + let hiveStr = (parsed_json["hive"] || "").toUpperCase(); + if (!(hiveStr in REGHIVES)) { + ax.console_message(id, "Invalid hive: " + hiveStr + "\nExpected: HKLM, HKCU, HKU, HKCR, HKCC\n", "error"); + return; + } + let hive = REGHIVES[hiveStr]; + + if (!path) { + ax.console_message(id, "Missing registry path. Use -p ", "error"); + return; + } + + let bof_params = ax.bof_pack("cstr,int,cstr,cstr,int", [hostname, hive, path, key, 0]); + let bof_path = ax.script_dir() + "_bin/reg_query." + ax.arch(id) + ".o"; + + ax.execute_alias(id, cmdline, `execute bof ${bof_path} ${bof_params}`, "Task: reg_query"); +}); + +var cmd_reg_query_recursive = ax.create_command( + "reg_query_recursive", + "Recursively enumerate all subkeys and values under a registry path.", + "reg_query_recursive -p [-h hostname]\n" + + " reg_query_recursive HKLM -p SOFTWARE\\\\Microsoft\n" + + " reg_query_recursive HKCU -p SOFTWARE -h 192.168.1.10" +); + +cmd_reg_query_recursive.addArgString("hive", true); +cmd_reg_query_recursive.addArgFlagString("-h", "hostname", "Remote hostname or IP", ""); +cmd_reg_query_recursive.addArgFlagString("-p", "path", "Registry path", ""); + +cmd_reg_query_recursive.setPreHook(function(id, cmdline, parsed_json, ...parsed_lines) { + + let rawHostname = parsed_json["hostname"] || ""; + let hostname = rawHostname ? ("\\\\" + rawHostname) : ""; + let path = parsed_json["path"] || ""; + + let hiveStr = (parsed_json["hive"] || "").toUpperCase(); + if (!(hiveStr in REGHIVES)) { + ax.console_message(id, "Invalid hive: " + hiveStr + "\nExpected: HKLM, HKCU, HKU, HKCR, HKCC\n", "error"); + return; + } + if (!path) { + ax.console_message(id, "Missing -p ", "error"); + return; + } + + let hive = REGHIVES[hiveStr]; + + let bof_params = ax.bof_pack("cstr,int,cstr,cstr,int", [hostname, hive, path, "", 1]); + let bof_path = ax.script_dir() + "_bin/reg_query." + ax.arch(id) + ".o"; + + ax.execute_alias(id, cmdline, `execute bof ${bof_path} ${bof_params}`, "Task: reg_query_recursive"); +}); + +var REG_TYPES = { + "REG_SZ": 1, + "REG_EXPAND_SZ": 2, + "REG_BINARY": 3, + "REG_DWORD": 4, + "REG_QWORD": 11, +}; + +var cmd_reg_write = ax.create_command( + "reg_write", + "Write a registry value. Supported types: REG_SZ, REG_EXPAND_SZ, REG_DWORD, REG_QWORD, REG_BINARY", + "reg_write -p -k -t -d [-h hostname]\n" + + " reg_write HKLM -p SOFTWARE\\\\TestKey -k TestValue -t REG_SZ -d HelloWorld\n" + + " reg_write HKLM -p SOFTWARE\\\\TestKey -k DwordVal -t REG_DWORD -d 1\n" + + " reg_write HKLM -p SOFTWARE\\\\TestKey -k TestValue -t REG_SZ -d HelloWorld -h 192.168.1.10" +); + +cmd_reg_write.addArgString("hive", true); +cmd_reg_write.addArgFlagString("-h", "hostname", "Remote hostname or IP", ""); +cmd_reg_write.addArgFlagString("-p", "path", "Registry key path", ""); +cmd_reg_write.addArgFlagString("-k", "key", "Value name to write", ""); +cmd_reg_write.addArgFlagString("-t", "type", "Registry type (REG_SZ, REG_DWORD, REG_QWORD, REG_BINARY, REG_EXPAND_SZ)", "REG_SZ"); +cmd_reg_write.addArgFlagString("-d", "data", "Data to write", ""); + +cmd_reg_write.setPreHook(function(id, cmdline, parsed_json, ...parsed_lines) { + + let rawHostname = parsed_json["hostname"] || ""; + let hostname = rawHostname ? ("\\\\" + rawHostname) : ""; + let path = parsed_json["path"] || ""; + let key = parsed_json["key"] || ""; + let typeStr = (parsed_json["type"] || "REG_SZ").toUpperCase(); + let dataStr = parsed_json["data"] || ""; + + let hiveStr = (parsed_json["hive"] || "").toUpperCase(); + if (!(hiveStr in REGHIVES)) { + ax.console_message(id, "Invalid hive: " + hiveStr + "\nExpected: HKLM, HKCU, HKU, HKCR, HKCC\n", "error"); + return; + } + if (!path) { + ax.console_message(id, "Missing -p ", "error"); + return; + } + if (!key) { + ax.console_message(id, "Missing -k ", "error"); + return; + } + if (!dataStr) { + ax.console_message(id, "Missing -d ", "error"); + return; + } + if (!(typeStr in REG_TYPES)) { + ax.console_message(id, "Invalid type: " + typeStr, "error"); + return; + } + + let hive = REGHIVES[hiveStr]; + let regtype = REG_TYPES[typeStr]; + + // Data always packed as cstr — BOF owns type coercion for DWORD/QWORD + let bof_params = ax.bof_pack("cstr,int,cstr,cstr,int,cstr", [hostname, hive, path, key, regtype, dataStr]); + let bof_path = ax.script_dir() + "_bin/reg_write." + ax.arch(id) + ".o"; + + ax.execute_alias(id, cmdline, `execute bof ${bof_path} ${bof_params}`, "Task: reg_write"); +}); + +var cmd_reg_delete = ax.create_command( + "reg_delete", + "Delete a registry value (-k) or an entire key (omit -k). Key must have no subkeys to be deleted.", + "reg_delete -p [-k value] [-h hostname]\n" + + " reg_delete HKLM -p SOFTWARE\\\\TestKey -k TestValue\n" + + " reg_delete HKLM -p SOFTWARE\\\\TestKey\n" + + " reg_delete HKLM -p SOFTWARE\\\\TestKey -k TestValue -h 192.168.1.10" +); + +cmd_reg_delete.addArgString("hive", true); +cmd_reg_delete.addArgFlagString("-h", "hostname", "Remote hostname or IP", ""); +cmd_reg_delete.addArgFlagString("-p", "path", "Registry key path", ""); +cmd_reg_delete.addArgFlagString("-k", "key", "Value name to delete (omit to delete the key itself)", ""); + +cmd_reg_delete.setPreHook(function(id, cmdline, parsed_json, ...parsed_lines) { + + let rawHostname = parsed_json["hostname"] || ""; + let hostname = rawHostname ? ("\\\\" + rawHostname) : ""; + let path = parsed_json["path"] || ""; + let key = parsed_json["key"] || ""; + + let hiveStr = (parsed_json["hive"] || "").toUpperCase(); + if (!(hiveStr in REGHIVES)) { + ax.console_message(id, "Invalid hive: " + hiveStr + "\nExpected: HKLM, HKCU, HKU, HKCR, HKCC\n", "error"); + return; + } + if (!path) { + ax.console_message(id, "Missing -p ", "error"); + return; + } + + let hive = REGHIVES[hiveStr]; + + let bof_params = ax.bof_pack("cstr,int,cstr,cstr", [hostname, hive, path, key]); + let bof_path = ax.script_dir() + "_bin/reg_delete." + ax.arch(id) + ".o"; + + ax.execute_alias(id, cmdline, `execute bof ${bof_path} ${bof_params}`, "Task: reg_delete"); +}); + +var cmd_reg_find = ax.create_command( + "reg_find", + "Search registry values by name or data under a path (recursive). Use -m to control match mode.", + "reg_find -p -s [-m name|data|both] [-h hostname]\n" + + " reg_find HKLM -p SOFTWARE -s password\n" + + " reg_find HKLM -p SOFTWARE -s password -m both\n" + + " reg_find HKLM -p SYSTEM\\\\CurrentControlSet -s autologon -m name -h 192.168.1.10" +); + +cmd_reg_find.addArgString("hive", true); +cmd_reg_find.addArgFlagString("-h", "hostname", "Remote hostname or IP", ""); +cmd_reg_find.addArgFlagString("-p", "path", "Registry root path to search under", ""); +cmd_reg_find.addArgFlagString("-s", "pattern", "Search pattern (case-insensitive)", ""); +cmd_reg_find.addArgFlagString("-m", "mode", "Match mode: name, data, both (default: name)", "name"); + +cmd_reg_find.setPreHook(function(id, cmdline, parsed_json, ...parsed_lines) { + + let rawHostname = parsed_json["hostname"] || ""; + let hostname = rawHostname ? ("\\\\" + rawHostname) : ""; + let path = parsed_json["path"] || ""; + let pattern = parsed_json["pattern"] || ""; + let modeStr = (parsed_json["mode"] || "name").toLowerCase(); + + let hiveStr = (parsed_json["hive"] || "").toUpperCase(); + if (!(hiveStr in REGHIVES)) { + ax.console_message(id, "Invalid hive: " + hiveStr + "\nExpected: HKLM, HKCU, HKU, HKCR, HKCC\n", "error"); + return; + } + if (!path) { + ax.console_message(id, "Missing -p ", "error"); + return; + } + if (!pattern) { + ax.console_message(id, "Missing -s ", "error"); + return; + } + + let modeMap = { "name": 0, "data": 1, "both": 2 }; + if (!(modeStr in modeMap)) { + ax.console_message(id, "Invalid -m mode. Use: name, data, both", "error"); + return; + } + + let hive = REGHIVES[hiveStr]; + let search_type = modeMap[modeStr]; + + let bof_params = ax.bof_pack("cstr,int,cstr,cstr,int", [hostname, hive, path, pattern, search_type]); + let bof_path = ax.script_dir() + "_bin/reg_find." + ax.arch(id) + ".o"; + + ax.execute_alias(id, cmdline, `execute bof ${bof_path} ${bof_params}`, "Task: reg_find"); +}); + +var group_test = ax.create_commands_group("SAL-BOF", [cmd_arp, cmd_cacls, cmd_dir, cmd_env, cmd_ipconfig, cmd_listdns, cmd_netstat, cmd_nslookup, cmd_privcheck, cmd_routeprint, cmd_uptime, cmd_useridletime, cmd_whoami, cmd_reg_query, cmd_reg_query_recursive, cmd_reg_write, cmd_reg_delete, cmd_reg_find]); ax.register_commands_group(group_test, ["beacon", "gopher", "kharon"], ["windows"], []);