#include "Registry.hpp" #include "Common.hpp" #include #include #include #include #include #include #ifdef _WIN32 #include #include #pragma comment(lib, "advapi32.lib") #endif using namespace std; constexpr std::string_view moduleNameRegistry = "registry"; constexpr unsigned long long moduleHashRegistry = djb2(moduleNameRegistry); #ifdef _WIN32 extern "C" __declspec(dllexport) Registry* RegistryConstructor() { return new Registry(); } #else extern "C" __attribute__((visibility("default"))) Registry* RegistryConstructor() { return new Registry(); } #endif Registry::Registry() #ifdef BUILD_TEAMSERVER : ModuleCmd(std::string(moduleNameRegistry), moduleHashRegistry) #else : ModuleCmd("", moduleHashRegistry) #endif { } Registry::~Registry() = default; std::string Registry::getInfo() { std::ostringstream oss; #ifdef BUILD_TEAMSERVER oss << "Registry Module:\n"; oss << "Manipulate local or remote Windows registry keys." << '\n'; oss << "Usage:" << '\n'; oss << " registry [options]\n"; oss << "Operations:" << '\n'; oss << " set Create or update a value." << '\n'; oss << " deleteValue Delete an existing value." << '\n'; oss << " query Query the value data." << '\n'; oss << " createKey Create a key." << '\n'; oss << " deleteKey Delete a key recursively." << '\n'; oss << "Options:" << '\n'; oss << " -s Remote host (omit for localhost)." << '\n'; oss << " -h Root hive (HKLM, HKCU, HKU, HKCR, HKCC)." << '\n'; oss << " -k Sub key path." << '\n'; oss << " -n Value name (required for value operations)." << '\n'; oss << " -d Data for set (default empty)." << '\n'; oss << " -t Value type for set (REG_SZ, REG_DWORD, REG_QWORD, REG_EXPAND_SZ)." << '\n'; oss << "Example:" << '\n'; oss << " registry set -h HKLM -k Software\\Acme -n Path -d C:/Temp -t REG_SZ" << '\n'; #endif return oss.str(); } std::string Registry::packParameters(const Parameters& params) const { std::string packed; packed.push_back(static_cast(params.operation)); auto append = [&packed](const std::string& value) { packed.append(value); packed.push_back('\0'); }; append(params.server); append(params.rootKey); append(params.subKey); append(params.valueName); append(params.valueData); append(params.valueType); return packed; } Registry::Parameters Registry::unpackParameters(const std::string& data) const { Parameters params; if (data.empty()) { return params; } params.operation = static_cast(static_cast(data[0])); size_t start = 1; std::vector parts; while (start < data.size()) { size_t end = data.find('\0', start); if (end == std::string::npos) { break; } parts.emplace_back(data.substr(start, end - start)); start = end + 1; } if (parts.size() >= 6) { params.server = parts[0]; params.rootKey = parts[1]; params.subKey = parts[2]; params.valueName = parts[3]; params.valueData = parts[4]; params.valueType = parts[5]; } return params; } #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) namespace { Registry::Operation operationFromString(const std::string& op, bool& valid) { std::string lower = op; std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return static_cast(std::tolower(c)); }); if (lower == "set") { valid = true; return Registry::Operation::SetValue; } if (lower == "deletevalue" || lower == "delete" || lower == "delvalue") { valid = true; return Registry::Operation::DeleteValue; } if (lower == "query" || lower == "get") { valid = true; return Registry::Operation::QueryValue; } if (lower == "createkey" || lower == "create") { valid = true; return Registry::Operation::CreateKey; } if (lower == "deletekey" || lower == "delkey") { valid = true; return Registry::Operation::DeleteKey; } valid = false; return Registry::Operation::SetValue; } } #endif int Registry::init(std::vector& splitedCmd, C2Message& c2Message) { #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) std::vector args = regroupStrings(splitedCmd); if (args.size() < 2) { c2Message.set_returnvalue(getInfo()); return -1; } Parameters params; bool operationValid = false; params.operation = operationFromString(args[1], operationValid); if (!operationValid) { c2Message.set_returnvalue("Unknown operation '" + args[1] + "'.\n" + getInfo()); return -1; } params.valueType = "REG_SZ"; for (size_t i = 2; i < args.size(); ++i) { const std::string& token = args[i]; if (token == "-s" && i + 1 < args.size()) { params.server = args[++i]; } else if (token == "-h" && i + 1 < args.size()) { params.rootKey = args[++i]; } else if (token == "-k" && i + 1 < args.size()) { params.subKey = args[++i]; } else if (token == "-n" && i + 1 < args.size()) { params.valueName = args[++i]; } else if (token == "-d" && i + 1 < args.size()) { params.valueData = args[++i]; } else if (token == "-t" && i + 1 < args.size()) { params.valueType = args[++i]; } } if (params.rootKey.empty() || params.subKey.empty()) { c2Message.set_returnvalue("Missing required hive or subkey.\n" + getInfo()); return -1; } if ((params.operation == Operation::SetValue || params.operation == Operation::DeleteValue || params.operation == Operation::QueryValue) && params.valueName.empty()) { c2Message.set_returnvalue("Value name required for this operation.\n" + getInfo()); return -1; } if (params.operation == Operation::SetValue && params.valueData.empty()) { params.valueData = ""; } c2Message.set_instruction(splitedCmd[0]); c2Message.set_cmd(packParameters(params)); #endif return 0; } int Registry::process(C2Message& c2Message, C2Message& c2RetMessage) { c2RetMessage.set_instruction(c2Message.instruction()); c2RetMessage.set_cmd(c2Message.cmd()); Parameters params = unpackParameters(c2Message.cmd()); int error = 0; std::string result; #ifdef _WIN32 error = execute(params, result); #else result = "Only supported on Windows.\n"; #endif if (error) { c2RetMessage.set_errorCode(error); } c2RetMessage.set_returnvalue(result); return 0; } #define ERROR_INVALID_ROOT 1 #define ERROR_CONNECT 2 #define ERROR_OPEN_KEY 3 #define ERROR_SET_VALUE 4 #define ERROR_DELETE_VALUE 5 #define ERROR_QUERY_VALUE 6 #define ERROR_CREATE_KEY 7 #define ERROR_DELETE_KEY 8 int Registry::errorCodeToMsg(const C2Message& c2RetMessage, std::string& errorMsg) { #if defined(BUILD_TEAMSERVER) || defined(C2CORE_BUILD_TESTS) if (c2RetMessage.errorCode() > 0) { errorMsg = c2RetMessage.returnvalue(); } #endif return 0; } #ifdef _WIN32 namespace { std::wstring toWide(const std::string& input) { if (input.empty()) { return std::wstring(); } int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, input.c_str(), static_cast(input.size()), nullptr, 0); std::wstring wide(sizeNeeded, L'\0'); MultiByteToWideChar(CP_UTF8, 0, input.c_str(), static_cast(input.size()), wide.data(), sizeNeeded); return wide; } std::string formatWinError(LONG error) { LPVOID buffer = nullptr; DWORD size = FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), reinterpret_cast(&buffer), 0, nullptr); std::string message; if (size && buffer) { message.assign(static_cast(buffer), size); LocalFree(buffer); } else { message = "Error code: " + std::to_string(error); } return message; } HKEY resolveRoot(const std::string& root) { std::string upper = root; std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); if (upper == "HKLM" || upper == "HKEY_LOCAL_MACHINE") return HKEY_LOCAL_MACHINE; if (upper == "HKCU" || upper == "HKEY_CURRENT_USER") return HKEY_CURRENT_USER; if (upper == "HKCR" || upper == "HKEY_CLASSES_ROOT") return HKEY_CLASSES_ROOT; if (upper == "HKU" || upper == "HKEY_USERS") return HKEY_USERS; if (upper == "HKCC" || upper == "HKEY_CURRENT_CONFIG") return HKEY_CURRENT_CONFIG; return nullptr; } DWORD resolveValueType(const std::string& type) { std::string upper = type; std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); if (upper == "REG_DWORD") return REG_DWORD; if (upper == "REG_QWORD") return REG_QWORD; if (upper == "REG_EXPAND_SZ") return REG_EXPAND_SZ; return REG_SZ; } } int Registry::execute(const Parameters& params, std::string& result) const { HKEY root = resolveRoot(params.rootKey); if (!root) { result = "Unknown root hive."; return ERROR_INVALID_ROOT; } HKEY baseHandle = root; HKEY connectedHandle = nullptr; if (!params.server.empty()) { LONG status = RegConnectRegistryW(toWide(params.server).c_str(), root, &connectedHandle); if (status != ERROR_SUCCESS) { result = formatWinError(status); return ERROR_CONNECT; } baseHandle = connectedHandle; } auto closeHandles = [&]() { if (connectedHandle) { RegCloseKey(connectedHandle); } }; auto openOrCreateKey = [&](REGSAM access, HKEY& keyHandle, bool create) { LONG status; if (create) { status = RegCreateKeyExW(baseHandle, toWide(params.subKey).c_str(), 0, nullptr, REG_OPTION_NON_VOLATILE, access, nullptr, &keyHandle, nullptr); } else { status = RegOpenKeyExW(baseHandle, toWide(params.subKey).c_str(), 0, access, &keyHandle); } return status; }; LONG status = ERROR_SUCCESS; switch (params.operation) { case Operation::SetValue: { HKEY keyHandle = nullptr; status = openOrCreateKey(KEY_SET_VALUE, keyHandle, true); if (status != ERROR_SUCCESS) { result = formatWinError(status); closeHandles(); return ERROR_OPEN_KEY; } DWORD type = resolveValueType(params.valueType); std::vector buffer; try { if (type == REG_DWORD) { DWORD value = static_cast(std::stoul(params.valueData.empty() ? "0" : params.valueData)); buffer.resize(sizeof(DWORD)); std::memcpy(buffer.data(), &value, sizeof(DWORD)); } else if (type == REG_QWORD) { unsigned long long value = std::stoull(params.valueData.empty() ? "0" : params.valueData); buffer.resize(sizeof(unsigned long long)); std::memcpy(buffer.data(), &value, sizeof(unsigned long long)); } else { std::wstring wideData = toWide(params.valueData); buffer.resize((wideData.size() + 1) * sizeof(wchar_t)); std::memcpy(buffer.data(), wideData.c_str(), buffer.size()); } } catch (const std::exception&) { RegCloseKey(keyHandle); result = "Failed to parse numeric registry data."; closeHandles(); return ERROR_SET_VALUE; } status = RegSetValueExW(keyHandle, toWide(params.valueName).c_str(), 0, type, buffer.empty() ? nullptr : buffer.data(), static_cast(buffer.size())); RegCloseKey(keyHandle); if (status != ERROR_SUCCESS) { result = formatWinError(status); closeHandles(); return ERROR_SET_VALUE; } result = "Value set successfully.\n"; break; } case Operation::DeleteValue: { HKEY keyHandle = nullptr; status = openOrCreateKey(KEY_SET_VALUE, keyHandle, false); if (status != ERROR_SUCCESS) { result = formatWinError(status); closeHandles(); return ERROR_OPEN_KEY; } status = RegDeleteValueW(keyHandle, toWide(params.valueName).c_str()); RegCloseKey(keyHandle); if (status != ERROR_SUCCESS) { result = formatWinError(status); closeHandles(); return ERROR_DELETE_VALUE; } result = "Value deleted successfully.\n"; break; } case Operation::QueryValue: { HKEY keyHandle = nullptr; status = openOrCreateKey(KEY_QUERY_VALUE, keyHandle, false); if (status != ERROR_SUCCESS) { result = formatWinError(status); closeHandles(); return ERROR_OPEN_KEY; } DWORD type = 0; DWORD dataSize = 0; status = RegQueryValueExW(keyHandle, toWide(params.valueName).c_str(), nullptr, &type, nullptr, &dataSize); if (status != ERROR_SUCCESS) { RegCloseKey(keyHandle); result = formatWinError(status); closeHandles(); return ERROR_QUERY_VALUE; } std::vector buffer(dataSize ? dataSize : 1); status = RegQueryValueExW(keyHandle, toWide(params.valueName).c_str(), nullptr, &type, buffer.data(), &dataSize); RegCloseKey(keyHandle); if (status != ERROR_SUCCESS) { result = formatWinError(status); closeHandles(); return ERROR_QUERY_VALUE; } std::ostringstream oss; oss << "Type: " << type << "\n"; if (type == REG_DWORD && dataSize >= sizeof(DWORD)) { DWORD value = 0; std::memcpy(&value, buffer.data(), sizeof(DWORD)); oss << "Data: " << value << "\n"; } else if (type == REG_QWORD && dataSize >= sizeof(unsigned long long)) { unsigned long long value = 0; std::memcpy(&value, buffer.data(), sizeof(unsigned long long)); oss << "Data: " << value << "\n"; } else { std::wstring wide(reinterpret_cast(buffer.data()), dataSize / sizeof(wchar_t)); if (!wide.empty() && wide.back() == L'\0') { wide.pop_back(); } oss << "Data: "; int length = WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), static_cast(wide.size()), nullptr, 0, nullptr, nullptr); std::string utf8; if (length > 0) { utf8.resize(length); WideCharToMultiByte(CP_UTF8, 0, wide.c_str(), static_cast(wide.size()), utf8.data(), length, nullptr, nullptr); } oss << utf8 << "\n"; } result = oss.str(); break; } case Operation::CreateKey: { HKEY keyHandle = nullptr; status = openOrCreateKey(KEY_CREATE_SUB_KEY, keyHandle, true); if (status != ERROR_SUCCESS) { result = formatWinError(status); closeHandles(); return ERROR_CREATE_KEY; } RegCloseKey(keyHandle); result = "Key created successfully.\n"; break; } case Operation::DeleteKey: { status = RegDeleteTreeW(baseHandle, toWide(params.subKey).c_str()); if (status != ERROR_SUCCESS) { result = formatWinError(status); closeHandles(); return ERROR_DELETE_KEY; } result = "Key deleted successfully.\n"; break; } default: result = "Unsupported operation."; closeHandles(); return ERROR_INVALID_ROOT; } closeHandles(); return 0; } #endif