#include #include #include #include "control.h" #include "emitter.h" #include "../Shared/common.h" #include "logging.h" #include "etwtireader.h" #include "etwtihandler.h" #include "piping.h" #include "json.hpp" #include "process_resolver.h" #include "../RedEdrShared/utils.h" namespace fs = std::filesystem; // Link against Version.lib #pragma comment(lib, "Version.lib") DWORD start_child_process(wchar_t* childCMD); HANDLE control_thread = NULL; volatile BOOL keep_running = TRUE; // Made volatile for thread safety PipeServer pipeServer = PipeServer("RedEdrPPL Server", (wchar_t*)PPL_SERVICE_PIPE_NAME); // Helper function to extract file version (of a PE/dll) std::string GetDllVersion(const fs::path& dllPath) { DWORD dwHandle = 0; DWORD dwSize = GetFileVersionInfoSizeW(dllPath.c_str(), &dwHandle); if (dwSize == 0) return "Unknown"; std::vector buffer(dwSize); if (!GetFileVersionInfoW(dllPath.c_str(), 0, dwSize, buffer.data())) { return "Unknown"; } VS_FIXEDFILEINFO* lpFileInfo = nullptr; UINT uiLen = 0; if (!VerQueryValueW(buffer.data(), L"\\", (LPVOID*)&lpFileInfo, &uiLen) || uiLen == 0) { return "Unknown"; } // Extract major, minor, build, and private parts int major = HIWORD(lpFileInfo->dwFileVersionMS); int minor = LOWORD(lpFileInfo->dwFileVersionMS); int build = HIWORD(lpFileInfo->dwFileVersionLS); int revision = LOWORD(lpFileInfo->dwFileVersionLS); return std::to_string(major) + "." + std::to_string(minor) + "." + std::to_string(build) + "." + std::to_string(revision); } std::string GetMpengineVersion() { fs::path baseDir = L"C:\\ProgramData\\Microsoft\\Windows Defender\\Definition Updates"; fs::path targetDll; // 1. Search dynamically for mpengine.dll inside subdirectories // Its in something like: // C:\ProgramData\Microsoft\Windows Defender\Definition Updates\{26187561-F935-4D14-8C5C-78828BFBE0F6}\mpengine.dll if (fs::exists(baseDir) && fs::is_directory(baseDir)) { for (const auto& entry : fs::recursive_directory_iterator(baseDir)) { if (entry.is_regular_file() && entry.path().filename() == "mpengine.dll") { targetDll = entry.path(); break; // Found the active instance } } } // 2. Return the found version if (!targetDll.empty()) { LOG_W(LOG_INFO, L"Found mpengine.dll at: %s", targetDll.c_str()); return GetDllVersion(targetDll); } else { LOG_A(LOG_WARNING, "mpengine.dll could not be found under the Definition Updates tree."); return "Unknown"; } } nlohmann::json GetDefenderPlatformInfo() { nlohmann::json info; // Helper lambda to read registry string and convert to narrow string auto readRegString = [](const std::wstring& subKey, const std::wstring& valueName) -> std::string { HKEY hKey; wchar_t buffer[MAX_PATH]; DWORD bufferSize = sizeof(buffer); if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, subKey.c_str(), 0, KEY_READ, &hKey) == ERROR_SUCCESS) { if (RegQueryValueExW(hKey, valueName.c_str(), NULL, NULL, (LPBYTE)buffer, &bufferSize) == ERROR_SUCCESS) { RegCloseKey(hKey); return wchar2string(buffer); } RegCloseKey(hKey); } return ""; }; info["as_signature_version"] = readRegString(L"SOFTWARE\\Microsoft\\Windows Defender\\Signature Updates", L"ASSignatureVersion"); info["av_signature_version"] = readRegString(L"SOFTWARE\\Microsoft\\Windows Defender\\Signature Updates", L"AVSignatureVersion"); info["install_location"] = readRegString(L"SOFTWARE\\Microsoft\\Windows Defender", L"InstallLocation"); return info; } void SendDefenderInfos() { // List of processes to gather module info from const wchar_t* target_processes[] = {L"MsMpEng.exe", L"MsSense.exe"}; for (const wchar_t* process_name : target_processes) { DWORD pid = FindProcessIdByName(process_name); if (pid != 0) { Process* process = g_ProcessResolver.getObject(pid); if (process) { LOG_W(LOG_INFO, L"Control: Found %s (PID: %lu) in resolver", process_name, pid); // Augment process info if not already done if (!process->augmented) { if (process->AugmentInfo()) { process->augmented = TRUE; } else { LOG_W(LOG_ERROR, L"Control: Failed to augment %s process info", process_name); } } nlohmann::json modules_array = nlohmann::json::array(); for (const auto& mod : process->processLoadedDlls) { nlohmann::json mod_info; // Only dll filename not full path std::string mod_name = mod.name; size_t pos = mod_name.find_last_of("\\/"); if (pos != std::string::npos) { mod_name = mod_name.substr(pos + 1); } mod_info["name"] = mod_name; mod_info["base"] = mod.dll_base; mod_info["size"] = mod.size; modules_array.push_back(mod_info); } // Send event with process modules nlohmann::json modules_event; modules_event["event"] = "process_modules"; modules_event["type"] = "meta"; modules_event["pid"] = pid; modules_event["event_time"] = get_time(); modules_event["process_name"] = wchar2string(process_name); modules_event["modules"] = modules_array; SendEmitterPipe((char*)modules_event.dump().c_str()); } else { LOG_W(LOG_ERROR, L"Control: Failed to get %s process from resolver", process_name); } } else { LOG_W(LOG_WARNING, L"Control: %s process not found", process_name); } } // Send separate event with Defender platform info (only once, not per process) DWORD msmpeng_pid = FindProcessIdByName(L"MsMpEng.exe"); if (msmpeng_pid != 0) { nlohmann::json platform_event; platform_event["event"] = "defender_platform_info"; platform_event["type"] = "meta"; platform_event["pid"] = msmpeng_pid; platform_event["event_time"] = get_time(); nlohmann::json platform_info = GetDefenderPlatformInfo(); platform_event["as_signature_version"] = platform_info["as_signature_version"]; platform_event["av_signature_version"] = platform_info["av_signature_version"]; platform_event["install_location"] = platform_info["install_location"]; platform_event["mpengine_version"] = GetMpengineVersion(); SendEmitterPipe((char*)platform_event.dump().c_str()); } } DWORD WINAPI ServiceControlPipeThread(LPVOID param) { char buffer[PPL_CONFIG_LEN]; while (keep_running) { LOG_W(LOG_INFO, L"Control: Waiting for client (RedEdr.exe) to connect..."); if (!pipeServer.StartAndWaitForClient(false)) { LOG_A(LOG_ERROR, "Error waiting for RedEdr.exe"); continue; } LOG_A(LOG_INFO, "Control: Client connected"); LOG_A(LOG_INFO, "Control: Connect us back to RedEdr"); if (!ConnectEmitterPipe()) { // Connect to the RedEdr pipe LOG_A(LOG_ERROR, "Control: Failed to connect to RedEdr pipe"); //break; // Exit the loop if connection fails } while (keep_running) { LOG_A(LOG_INFO, "Control: Wait for command"); memset(buffer, 0, sizeof(buffer)); if (!pipeServer.Receive(buffer, PPL_CONFIG_LEN)) { LOG_A(LOG_ERROR, "Error waiting for RedEdr.exe command"); break; } LOG_A(LOG_INFO, "Control: Received command: %s", buffer); try { // Try to parse as JSON first nlohmann::json j = nlohmann::json::parse(buffer); if (j.contains("command")) { std::string command = j["command"]; if (command == "start") { if (j.contains("targets") && j["targets"].is_array()) { LOG_A(LOG_INFO, "Control: Processing start command with %zu targets", j["targets"].size()); std::vector targets = j["targets"]; g_ProcessResolver.SetTargetNames(targets); g_ProcessResolver.RefreshTargetMatching(); BOOL doDefenderTrace = j.value("do_defendertrace", false) ? TRUE : FALSE; SetDefenderTraceConfig(doDefenderTrace); if (doDefenderTrace) { // Send on each new start command SendDefenderInfos(); } } else { LOG_A(LOG_ERROR, "Control: Start command missing 'targets' array"); } } else if (command == "stop") { nlohmann::json stop_event; stop_event["event"] = "ppl_stop"; stop_event["type"] = "meta"; SendEmitterPipe((char*) stop_event.dump().c_str()); } else if (command == "shutdown") { LOG_A(LOG_INFO, "Control: Received JSON command: shutdown"); keep_running = FALSE; // Signal thread to stop g_ServiceStopping = TRUE; // Signal main service loop to stop ShutdownEtwtiReader(); // also makes main return break; } else { LOG_A(LOG_INFO, "Control: Unknown JSON command: %s", command.c_str()); } } else { LOG_A(LOG_ERROR, "Control: JSON missing 'command' field"); } } catch (const nlohmann::json::parse_error& e) { // Fallback to legacy string-based parsing for backward compatibility LOG_A(LOG_WARNING, "Control: JSON parse failed %s", e.what()); } } LOG_A(LOG_INFO, "Control: Client disconnected, shutting down pipe"); pipeServer.Shutdown(); } LOG_A(LOG_INFO, "Control: Finished"); return 0; } void StartControl() { LOG_W(LOG_INFO, L"Control: Start Thread"); // Reset the running flag keep_running = TRUE; control_thread = CreateThread(NULL, 0, ServiceControlPipeThread, NULL, 0, NULL); if (control_thread == NULL) { DWORD error = GetLastError(); LOG_W(LOG_ERROR, L"Control: Failed to create thread, error: %d", error); } else { LOG_W(LOG_INFO, L"Control: Thread created successfully"); } } void StopControl() { LOG_W(LOG_INFO, L"Control: Stop Thread"); // Signal the thread to stop keep_running = FALSE; // Send empty data to unblock any pending pipe operations pipeServer.Send((char*)""); // Wait for the thread to finish (with timeout) if (control_thread != NULL) { DWORD waitResult = WaitForSingleObject(control_thread, 5000); // 5 second timeout if (waitResult == WAIT_TIMEOUT) { LOG_W(LOG_ERROR, L"Control: Thread did not stop gracefully, terminating"); TerminateThread(control_thread, 1); } CloseHandle(control_thread); control_thread = NULL; } } ////// // broken atm void rededr_remove_service() { //wchar_t* cmd = L"C:\\RedEdr\\RedEdr.exe --pplstop"; WCHAR childCMD[PATH_LEN] = { 0 }; //wcscpy_s(childCMD, PATH_LEN, L"C:\\windows\\system32\\cmd.exe /c \"echo AAA > c:\\rededr\\aa\""); //wcscpy_s(childCMD, PATH_LEN, L"C:\\RedEdr\\RedEdrPplRemover.exe"); wcscpy_s(childCMD, PATH_LEN, L"C:\\RedEdr\\RedEdr.exe --pplstop"); start_child_process(childCMD); } DWORD start_child_process(wchar_t* childCMD) { DWORD retval = 0; LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList = NULL; PROCESS_INFORMATION ProcessInformation = { 0 }; DWORD ProtectionLevel = PROTECTION_LEVEL_SAME; if (childCMD == NULL) { LOG_W(LOG_ERROR, L"start_child_process: Invalid command parameter"); return ERROR_INVALID_PARAMETER; } LOG_W(LOG_INFO, L"start_child_process: Starting"); // Create Attribute List STARTUPINFOEXW StartupInfoEx = { 0 }; SIZE_T AttributeListSize = 0; StartupInfoEx.StartupInfo.cb = sizeof(StartupInfoEx); InitializeProcThreadAttributeList(NULL, 1, 0, &AttributeListSize); if (AttributeListSize == 0) { retval = GetLastError(); LOG_W(LOG_ERROR, L"start_child_process: InitializeProcThreadAttributeList1 Error: %d", retval); return retval; } lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AttributeListSize); if (lpAttributeList == NULL) { LOG_W(LOG_ERROR, L"start_child_process: HeapAlloc failed"); return ERROR_NOT_ENOUGH_MEMORY; } StartupInfoEx.lpAttributeList = lpAttributeList; if (InitializeProcThreadAttributeList(lpAttributeList, 1, 0, &AttributeListSize) == FALSE) { retval = GetLastError(); LOG_W(LOG_ERROR, L"start_child_process: InitializeProcThreadAttributeList2 Error: %d", retval); goto cleanup; } // Set ProtectionLevel to be the same, i.e. PPL if (UpdateProcThreadAttribute(lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PROTECTION_LEVEL, &ProtectionLevel, sizeof(ProtectionLevel), NULL, NULL) == FALSE) { retval = GetLastError(); LOG_W(LOG_ERROR, L"start_child_process: UpdateProcThreadAttribute Error: %d", retval); goto cleanup; } // Start Process (hopefully) LOG_W(LOG_INFO, L"start_child_process: Creating Process: '%s'", childCMD); if (CreateProcess(NULL, childCMD, NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT | CREATE_PROTECTED_PROCESS, NULL, NULL, (LPSTARTUPINFOW)&StartupInfoEx, &ProcessInformation) == FALSE) { retval = GetLastError(); if (retval == ERROR_INVALID_IMAGE_HASH) { LOG_W(LOG_ERROR, L"start_child_process: CreateProcess Error: Invalid Certificate"); } else { LOG_W(LOG_ERROR, L"start_child_process: CreateProcess Error: %d", retval); } goto cleanup; } // Close handles immediately as we don't need to wait for the process CloseHandle(ProcessInformation.hProcess); CloseHandle(ProcessInformation.hThread); LOG_W(LOG_INFO, L"start_child_process: Process created successfully"); cleanup: // Clean up attribute list if (lpAttributeList != NULL) { DeleteProcThreadAttributeList(lpAttributeList); HeapFree(GetProcessHeap(), 0, lpAttributeList); } LOG_W(LOG_INFO, L"start_child_process: finished with return code %d", retval); return retval; }