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 callbac…
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.
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 creationPspCreateThreadNotifyRoutine for thread creationPspLoadImageNotifyRoutine for image loadingEDRSandBlast 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 (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:
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.
Enabled field of OB_CALLBACK_ENTRYThis 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.CallbackList of threads and processAnother 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:
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.
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).
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:
_FLTP_FRAME) as its root 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);_FLT_FILTER),
describing various properties such as its supported operations;_FLT_INSTANCE)
structure is created to mark each of the
filter<->volume associations;_CALLBACK_NODE structures, and can be accessed by different ways:_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.)._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.
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
No open issues yet, or sync has not completed.