Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
E

EDRSandblast

> 编程语言
Open source

EDRSandBlast EDRSandBlast is a tool written in C that weaponize a vulnerable signed driver to bypass EDR detections (Notify Routine callbac…

1.8K stars0 likes1 views
WebsiteGitHub

About

EDRSandBlast EDRSandBlast is a tool written in C that weaponize a vulnerable signed driver to bypass EDR detections (Notify Routine callbac…

EDRSandBlast

EDRSandBlast is a tool written in C that weaponize a vulnerable signed driver to bypass EDR detections (Notify Routine callbacks, Object Callbacks and ETW TI provider) and LSASS protections. Multiple userland unhooking techniques are also implemented to evade userland monitoring.

As of release, combination of userland (--usermode) and Kernel-land (--kernelmode) techniques were used to dump LSASS memory under EDR scrutiny, without being blocked nor generating "OS Credential Dumping"-related events in the product (cloud) console. The tests were performed on 3 distinct EDR products and were successful in each case.

Description

EDR bypass through Kernel Notify Routines removal

EDR products use Kernel "Notify Routines" callbacks on Windows to be notified by the kernel of system activity, such as process and thread creation and loading of images (exe / DLL).

These Kernel callbacks are defined from kernel-land, usually from the driver implementing the callbacks, using a number of documented APIs (nt!PsSetCreateProcessNotifyRoutine, nt!PsSetCreateThreadNotifyRoutine, etc.). These APIs add driver-supplied callback routines to undocumented arrays of routines in Kernel-space:

  • PspCreateProcessNotifyRoutine for process creation
  • PspCreateThreadNotifyRoutine for thread creation
  • PspLoadImageNotifyRoutine for image loading

EDRSandBlast enumerates the routines defined in those arrays and remove any callback routine linked to a predefined list of EDR drivers (more than 1000 drivers of security products supported, see the EDR driver detection section. The enumeration and removal are made possible through the exploitation of an arbitrary Kernel memory read / write primitive provided by the exploitation of a vulnerable driver (see Vulnerable drivers section).

The offsets of the aforementioned arrays are recovered using multiple techniques, please refer to Offsets section.

EDR bypass through Object Callbacks removal

EDR (and even EPP) products often register "Object callbacks" through the use of the nt!ObRegisterCallbacks kernel API. These callbacks allow the security product to be notified at each handle generation on specific object types (Processes, Threads and Desktops related object callbacks are now supported by Windows). A handle generation may occur on object opening (call to OpenProcess, OpenThread, etc.) as well as handle duplication (call to DuplicateHandle, etc.).

By being notified by the kernel on each of these operations, a security product may analyze the legitimacy of the handle creation (e.g. an unknown process is trying to open LSASS), and even block it if a threat is detected.

At each callback registration using ObRegisterCallbacks, a new item is added to the CallbackList double-linked list present in the _OBJECT_TYPE object describing the type of object affected by the callback (either a Process, a Thread or a Desktop). Unfortunately, these items are described by a structure that is not documented nor published in symbol files by Microsoft. However, studying it from various ntoskrnl.exe versions seems to indicate that the structure did not change between (at least) Windows 10 builds 10240 and 22000 (from 2015 to 2022).

The mentionned structure, representing an object callback registration, is the following:

…

The OB_CALLBACK structure mentionned above is also undocumented, and is defined by the following:

c
typedef struct OB_CALLBACK_t {
    USHORT Version;                           // usually 0x100
    USHORT OperationRegistrationCount;        // number of registered callbacks
    PVOID RegistrationContext;                // arbitrary data passed at registration time
    UNICODE_STRING AltitudeString;            // used to determine callbacks order
    struct OB_CALLBACK_ENTRY_t EntryItems[1]; // array of OperationRegistrationCount items
    WCHAR AltitudeBuffer[1];                  // is AltitudeString.MaximumLength bytes long, and pointed by AltitudeString.Buffer
} OB_CALLBACK;

In order to disable EDR-registered object callbacks, three techniques are implemented in EDRSandblast; however only one is enabled for the moment.

Using the Enabled field of OB_CALLBACK_ENTRY

This is the default technique enabled in EDRSandblast. In order to detect and disable EDR-related object callbacks, the CallbackList list located in the _OBJECT_TYPE objects tied to the Process and Thread types is browsed. Both _OBJECT_TYPEs are pointed by public global symbols in the kernel, PsProcessType and PsThreadType.

Each item of the list is assumed to fit the OB_CALLBACK_ENTRY structure described above (assumption that seems to hold at least in all Windows 10 builds at the time of writing). Functions defined in PreOperation and PostOperation fields are located to checks if they belong to an EDR driver, and if so, callbacks are simply disabled toggling the Enabled flag.

While being a pretty safe technique, it has the inconvenient of relying on an undocumented structure; to reduce the risk of unsafe manipulation of this structure, basic checks are performed to validate that some fields have the expected values :

  • Enabled is either TRUE or FALSE (don't laugh, a BOOL is an int, so it could be anything other than 1 or 0);
  • Operations is OB_OPERATION_HANDLE_CREATE, OB_OPERATION_HANDLE_DUPLICATE or both;
  • ObjectType points on PsProcessType or PsThreadType.

Unlinking the CallbackList of threads and process

Another strategy that do not rely on an undocumented structure (and is thus theoretically more robust against NT kernel changes) is the unlinking of the whole CallbackList for both processes and threads. The _OBJECT_TYPE object is the following:

c
struct _OBJECT_TYPE {
	LIST_ENTRY TypeList;
	UNICODE_STRING Name;
	[...]
	_OBJECT_TYPE_INITIALIZER TypeInfo;
	[...]
	LIST_ENTRY CallbackList;
}

Making the Flink and Blink pointers of the CallbackList LIST_ENTRY point to the LIST_ENTRY itself effectively make the list empty. Since the _OBJECT_TYPE structure is published in the kernel' symbols, the technique does not rely on hardcoded offsets/structures. However, it has some drawbacks.

The first being not able to only disable callbacks from EDR; indeed, the technique affects all object callbacks that could have been registered by "legitimate" software. It should nevertheless be noted that object callbacks are not used by any pre-installed component on Windows 10 (at the time of writing) so disabling them should not affect the machine stability (even more so if the disabling is only temporary).

The second drawback is that process or thread handle operation are really frequent (nearly continuous) in the normal functioning of the OS. As such, if the kernel write primitive used cannot perform a QWORD write "atomically", there is a good chance that the _OBJECT_TYPE.CallbackList.Flink pointer will be accessed by the kernel in the middle of its overwriting. For instance, the MSI vulnerable driver RTCore64.sys can only perform a DWORD write at a time, so 2 distinct IOCTLs will be needed to overwrite the pointer, between which the kernel has a high probability of using it (resulting in a crash). On the other hand, the vulnerable DELL driver DBUtil_2_3.sys can perform writes of arbitrary sizes in one IOCTL, so using this method with it does not risk causing a crash.

Disabling object callbacks altogether

One last technique we found was to disable entirely the object callbacks support for thread and processes. Inside the _OBJECT_TYPE structure corresponding to the process and thread types resides a TypeInfo field, following the documented _OBJECT_TYPE_INITIALIZER structure. The latter contains a ObjectTypeFlags bit field, whose SupportsObjectCallbacks flag determines if the described object type (Process, Thread, Desktop, Token, File, etc.) supports object callback registering or not. As previously stated, only Process, Thread and Desktop object types supports these callbacks on a Windows installation at the time of writing.

Since the SupportsObjectCallbacks bit is checked by ObpCreateHandle or ObDuplicateObject before even reading the CallbackList (and before executing callbacks, of course), flipping the bit at kernel runtime effectively disable all object callbacks execution.

The main drawback of the method is simply that KPP ("PatchGuard") monitors the integrity of some (all ?) _OBJECT_TYPE structures, and triggers a 0x109 Bug Check with parameter 4 being equal to 0x8, meaning an object type structure has been altered.

However, performing the disabling / re-enabling (and "malicious" action in-between) quickly 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_NODEs 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_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.

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 usages of some Windows API commonly used maliciously. This include the nt!MiReadWriteVirtualMemory API, called by nt!NtReadVirtualMemory (which is used to dump LSASS memory) and monitored by the nt!EtwTiLogReadWriteVm function.

EDR products can consume the logs produced by the ETW TI provider through services or processes running as, respectively, `SERVICE_LAUNCH_PROTECTED_ANTIMALWARE

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

C

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言