
Weaponizes vulnerable signed drivers to bypass EDR kernel callbacks, object callbacks, ETW TI provider, and userland hooks for LSASS memory dumping and credential extraction.
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:
typedef struct OB_CALLBACK_ENTRY_t {
LIST_ENTRY CallbackList; // linked element tied to _OBJECT_TYPE.CallbackList
OB_OPERATION Operations; // bitfield : 1 for Creations, 2 for Duplications
BOOL Enabled; // self-explanatory
OB_CALLBACK* Entry; // points to the structure in which it is included
POBJECT_TYPE ObjectType; // points to the object type affected by the callback
POB_PRE_OPERATION_CALLBACK PreOperation; // callback function called before each handle operation
POB_POST_OPERATION_CALLBACK PostOperation; // callback function called after each handle operation
KSPIN_LOCK Lock; // lock object used for synchronization
} OB_CALLBACK_ENTRY;
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).