
Nutzt anfällige signierte Treiber aus, um EDR-Kernel-Callbacks, Objekt-Callbacks, ETW-TI-Provider und Userland-Hooks zu umgehen, für LSASS-Speicherauszug und Anmeldedatenextraktion.
EDRSandBlast ist ein in C geschriebenes Tool, das einen verwundbaren signierten Treiber ausnutzt, um EDR-Erkennungen (Notify Routine Callbacks, Object Callbacks und den ETW TI-Provider) sowie LSASS-Schutzmechanismen zu umgehen. Es werden mehrere Userland-Unhooking-Techniken implementiert, um die Überwachung im Benutzermodus zu umgehen.
Zum Zeitpunkt der Veröffentlichung wurde eine Kombination aus Userland- (--usermode) und Kernel-Land- (--kernelmode) Techniken verwendet, um den LSASS-Speicher unter EDR-Überwachung auszulesen, ohne blockiert zu werden oder Ereignisse im Zusammenhang mit "OS Credential Dumping" in der (Cloud-)Konsole des Produkts zu erzeugen. Die Tests wurden mit 3 verschiedenen EDR-Produkten durchgeführt und waren in jedem Fall erfolgreich.
EDR-Produkte nutzen Kernel-"Notify Routines"-Callbacks unter Windows, um vom Kernel über Systemaktivitäten wie Prozess- und Thread-Erstellung sowie das Laden von Images (exe / DLL) benachrichtigt zu werden.
Diese Kernel-Callbacks werden aus dem Kernel-Land heraus definiert, normalerweise vom Treiber, der die Callbacks implementiert, unter Verwendung einer Reihe dokumentierter APIs (nt!PsSetCreateProcessNotifyRoutine, nt!PsSetCreateThreadNotifyRoutine, usw.). Diese APIs fügen treiberbereitgestellte Callback-Routinen in undokumentierte Arrays von Routinen im Kernel-Space ein:
PspCreateProcessNotifyRoutine für die ProzesserstellungPspCreateThreadNotifyRoutine für die Thread-ErstellungPspLoadImageNotifyRoutine für das Laden von ImagesEDRSandBlast enumeriert die in diesen Arrays definierten Routinen und entfernt alle Callback-Routinen, die mit einer vordefinierten Liste von EDR-Treibern verknüpft sind (mehr als 1000 Treiber von Sicherheitsprodukten werden unterstützt, siehe den Abschnitt zur EDR-Treibererkennung). Die Enumeration und Entfernung wird durch die Ausnutzung einer primitiven Kernel-Speicher-Lese-/Schreibfähigkeit ermöglicht, die durch die Ausnutzung eines verwundbaren Treibers bereitgestellt wird (siehe Abschnitt zu verwundbaren Treibern).
Die Offsets der genannten Arrays werden mit verschiedenen Techniken wiederhergestellt, siehe Abschnitt zu Offsets.
EDR- (und sogar EPP-) Produkte registrieren oft "Object-Callbacks" über die Verwendung der Kernel-API nt!ObRegisterCallbacks. Diese Callbacks erlauben es dem Sicherheitsprodukt, bei jeder Handle-Erstellung für bestimmte Objekttypen (Prozesse, Threads und Desktop-bezogene Objekt-Callbacks werden von Windows unterstützt) benachrichtigt zu werden. Eine Handle-Erstellung kann beim Öffnen eines Objekts (Aufruf von OpenProcess, OpenThread usw.) sowie bei der Handle-Duplizierung (Aufruf von DuplicateHandle usw.) auftreten.
Indem das Sicherheitsprodukt vom Kernel bei jeder dieser Operationen benachrichtigt wird, kann es die Legitimität der Handle-Erstellung analysieren (z. B. versucht ein unbekannter Prozess, LSASS zu öffnen) und diese sogar blockieren, falls eine Bedrohung erkannt wird.
Bei jeder Callback-Registrierung mit ObRegisterCallbacks wird ein neues Element zur doppelt verketteten Liste CallbackList hinzugefügt, die im _OBJECT_TYPE-Objekt vorhanden ist, das den vom Callback betroffenen Objekttyp beschreibt (entweder ein Prozess, ein Thread oder ein Desktop). Leider werden diese Elemente durch eine Struktur beschrieben, die weder dokumentiert noch in Symbol-Dateien von Microsoft veröffentlicht ist. Die Untersuchung verschiedener ntoskrnl.exe-Versionen deutet jedoch darauf hin, dass sich die Struktur zwischen (mindestens) den Windows 10 Builds 10240 und 22000 (von 2015 bis 2022) nicht geändert hat.
Die genannte Struktur, die eine Objekt-Callback-Registrierung darstellt, ist die folgende:```C 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;
Die `OB_CALLBACK`-Struktur, die oben erwähnt wurde, ist ebenfalls undokumentiert und wird wie folgt definiert:```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;
Um EDR-registrierte Objekt-Callbacks zu deaktivieren, werden in
EDRSandblast drei Techniken implementiert;
allerdings ist derzeit nur eine aktiviert.
Enabled-Felds von OB_CALLBACK_ENTRYDies ist die Standardtechnik, die in EDRSandblast aktiviert ist. Um EDR-bezogene
Objekt-Callbacks zu erkennen und zu deaktivieren, wird die CallbackList-Liste in den
_OBJECT_TYPE-Objekten, die an die Process- und Thread-Typen gebunden sind, durchsucht.
Beide _OBJECT_TYPEs werden von öffentlichen globalen Symbolen im Kernel referenziert,
PsProcessType und PsThreadType.
Jedes Element der Liste wird als zur oben beschriebenen OB_CALLBACK_ENTRY-Struktur passend
angenommen (eine Annahme, die zum Zeitpunkt der Erstellung dieses Textes zumindest in allen
Windows 10-Builds zu gelten scheint). Funktionen, die in den PreOperation- und PostOperation-Feldern
definiert sind, werden lokalisiert, um zu prüfen, ob sie zu einem EDR-Treiber gehören,
und falls ja, werden Callbacks einfach durch Umschalten des Enabled-Flags deaktiviert.
Obwohl es eine ziemlich sichere Technik ist, hat sie den Nachteil, auf einer undokumentierten Struktur zu beruhen; um das Risiko einer unsicheren Manipulation dieser Struktur zu verringern, werden grundlegende Prüfungen durchgeführt, um zu validieren, dass einige Felder die erwarteten Werte haben:
Enabled ist entweder TRUE oder FALSE (nicht lachen, ein BOOL ist ein int, also könnte es alles andere als 1 oder 0 sein);Operations ist OB_OPERATION_HANDLE_CREATE, OB_OPERATION_HANDLE_DUPLICATE oder beides;ObjectType zeigt auf PsProcessType oder PsThreadType.CallbackList von Threads und ProzessenEine weitere Strategie, die nicht auf einer undokumentierten Struktur beruht (und daher theoretisch
robuster gegenüber NT-Kernel-Änderungen ist), ist das Entfernen der gesamten CallbackList
sowohl für Prozesse als auch für Threads. Das _OBJECT_TYPE-Objekt ist wie folgt:```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.