Von Gabriel Landau bei Elastic Security. Modifikation von EDRSandblast – siehe Original-README unten.
Integriert GodFault in EDR Sandblast und erzielt das gleiche Ergebnis ohne die Verwendung anfälliger Treiber.
C:UsersuserDesktopOffsets>EDRSandblast.exe --kernelmode cmd
______ _____ _____ _____ _ _ _ _
| ____| __ | __ / ____| | | | | | | |
| |__ | | | | |__) | (___ __ _ _ __ __| | |__ | | __ _ ___| |_
| __| | | | | _ / ___ / _` | '_ / _` | '_ | |/ _` / __| __|
| |____| |__| | | ____) | (_| | | | | (_| | |_) | | (_| __ | |_
|______|_____/|_| _|_____/ __,_|_| |_|__,_|_.__/|_|__,_|___/__|
D3FC0N 30 Edition | Thomas DIOT (@_Qazeer) & Maxime MEIGNAN (@th3m4ks)
[!] If kernel mode bypass is enabled, it is recommended to enable usermode bypass as well (e.g. to unhook the NtLoadDriver API call)
[===== KERNEL MODE =====]
[+] Setting up prerequisites for the kernel read/write primitives...
[+] Loading kernel related offsets from the CSV file
[*] System's ntoskrnl.exe file version is: ntoskrnl_22621-1702.exe
[+] Offsets are available for this version of ntoskrnl.exe (ntoskrnl_22621-1702.exe)!
[+] Checking if any EDR kernel notify rountines are set for image loading, process and thread creations...
[+] [NotifyRountines] Enumerating process creation callbacks
[+] Running command: GodFault.exe -t 2684
[?] Server does not appear to be running. Attempting to install it...
[+] CSRSS PID is 748
[+] Testing initial ability to acquire PROCESS_ALL_ACCESS to System: Failure
[+] Ready. Spawning WinTcb.
[+] SpawnPPL: Waiting for child process to finish.
[+] Thread 2684 (KTHREAD FFFF910961E4C080) has been blessed by GodFault
[+] [NotifyRountines] fffff8034df25500 [cng.sys + 0x5500]
[+] [NotifyRountines] fffff8034e9efdc0 [WdFilter.sys + 0x4fdc0]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff803487bc460 [ksecdd.sys + 0x1c460]
[+] [NotifyRountines] fffff8034eff3fd0 [tcpip.sys + 0x13fd0]
[+] [NotifyRountines] fffff8034f5ed980 [iorate.sys + 0xd980]
[+] [NotifyRountines] fffff8034dea8890 [CI.dll + 0x88890]
[+] [NotifyRountines] fffff803525079f0 [dxgkrnl.sys + 0x179f0]
[+] [NotifyRountines] fffff80352be0a70 [vm3dmp.sys + 0x10a70]
[+] [NotifyRountines] fffff8036ebccd00 [peauth.sys + 0x3cd00]
[+] [NotifyRountines] fffff8036eda1550 [wtd.sys + 0x1550]
[+] [NotifyRountines] Found a total of 1 EDR / security products driver(s)
[+] [NotifyRountines] Enumerating thread creation callbacks
[+] [NotifyRountines] fffff8034e9f15c0 [WdFilter.sys + 0x515c0]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff8034e9f1350 [WdFilter.sys + 0x51350]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff8036eb71010 [mmcss.sys + 0x1010]
[+] [NotifyRountines] Found a total of 2 EDR / security products driver(s)
[+] [NotifyRountines] Enumerating image loading callbacks
[+] [NotifyRountines] fffff8034e9f0820 [WdFilter.sys + 0x50820]
[+] [NotifyRountines] Found callback belonging to EDR driver WdFilter.sys
[+] [NotifyRountines] fffff80352ab5710 [ahcache.sys + 0x25710]
[+] [NotifyRountines] Found a total of 1 EDR / security products driver(s)
[+] Checking if EDR callbacks are registered on processes and threads handle creation/duplication...
[+] [ObjectCallblacks] Enumerating Process object callbacks :
[+] [ObjectCallblacks] Callback at FFFF800C5E2F2940 for handle creations & duplications:
[+] [ObjectCallblacks] Status: Enabled
[+] [ObjectCallblacks] Preoperation at 0xfffff8034e9eda30 [WdFilter.sys + 0x4da30]
[+] [ObjectCallblacks] Callback belongs to an EDR and is enabled!
[+] [ObjectCallblacks] Enumerating Thread object callbacks :
[+] [ObjectCallblacks] Object callbacks are present !
[+] [ETWTI] Checking the ETW Threat Intelligence Provider state...
[+] [ETWTI] ETW Threat Intelligence Provider is ENABLED!
[+] Process is NOT "safe" to launch our payload, removing monitoring and starting another process...
[+] [ETWTI] Disabling the ETW Threat Intel provider by patching ProviderEnableInfo at 0xffff91095ce8c430 with 0x00.
[+] [ETWTI] The ETW Threat Intel provider was successfully disabled!
[+] Removing kernel callbacks registered by EDR for process creation, thread creation and image loading...
[+] [NotifyRountines] Removing process creation callbacks
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c2a8 | callback struct: 0xffff91095dbf3a5f | callback function: 0xfffff8034e9efdc0]
[+] [NotifyRountines] Removing thread creation callbacks
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c4a0 | callback struct: 0xffff91095dbf3b1f | callback function: 0xfffff8034e9f15c0]
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c4a8 | callback struct: 0xffff91095dbf3b4f | callback function: 0xfffff8034e9f1350]
[+] [NotifyRountines] Removing image loading callbacks
[+] [NotifyRountines] Removing callback of EDR driver "WdFilter.sys" [callback addr: 0xfffff8034970c6a0 | callback struct: 0xffff91095dbf3e4f | callback function: 0xfffff8034e9f0820]
[+] Disabling kernel callbacks registered by EDR for process and thread opening or handle duplication...
[+] [ObjectCallblacks] Disabling WdFilter.sys callback...
[+] All EDR drivers were successfully removed from Kernel callbacks!
==================================================
Starting a new unmonitored process...
==================================================
[!] If kernel mode bypass is enabled, it is recommended to enable usermode bypass as well (e.g. to unhook the NtLoadDriver API call)
[===== KERNEL MODE =====]
[+] Setting up prerequisites for the kernel read/write primitives...
[+] Loading kernel related offsets from the CSV file
[*] System's ntoskrnl.exe file version is: ntoskrnl_22621-1702.exe
[+] Offsets are available for this version of ntoskrnl.exe (ntoskrnl_22621-1702.exe)!
[+] Checking if any EDR kernel notify rountines are set for image loading, process and thread creations...
[+] [NotifyRountines] Enumerating process creation callbacks
[+] Running command: GodFault.exe -t 8344
[+] Thread 8344 (KTHREAD FFFF91096169F080) has been blessed by GodFault
[+] Initial blessing successful
[+] [NotifyRountines] fffff8034df25500 [cng.sys + 0x5500]
[+] [NotifyRountines] fffff803487bc460 [ksecdd.sys + 0x1c460]
[+] [NotifyRountines] fffff8034eff3fd0 [tcpip.sys + 0x13fd0]
[+] [NotifyRountines] fffff8034f5ed980 [iorate.sys + 0xd980]
[+] [NotifyRountines] fffff8034dea8890 [CI.dll + 0x88890]
[+] [NotifyRountines] fffff803525079f0 [dxgkrnl.sys + 0x179f0]
[+] [NotifyRountines] fffff80352be0a70 [vm3dmp.sys + 0x10a70]
[+] [NotifyRountines] fffff8036ebccd00 [peauth.sys + 0x3cd00]
[+] [NotifyRountines] fffff8036eda1550 [wtd.sys + 0x1550]
[+] [NotifyRountines] No EDR driver(s) found!
[+] [NotifyRountines] Enumerating thread creation callbacks
[+] [NotifyRountines] fffff8036eb71010 [mmcss.sys + 0x1010]
[+] [NotifyRountines] No EDR driver(s) found!
[+] [NotifyRountines] Enumerating image loading callbacks
[+] [NotifyRountines] fffff80352ab5710 [ahcache.sys + 0x25710]
[+] [NotifyRountines] No EDR driver(s) found!
[+] Checking if EDR callbacks are registered on processes and threads handle creation/duplication...
[+] [ObjectCallblacks] Enumerating Process object callbacks :
[+] [ObjectCallblacks] Callback at FFFF800C5E2F2940 for handle creations & duplications:
[+] [ObjectCallblacks] Status: Disabled
[+] [ObjectCallblacks] Preoperation at 0xfffff8034e9eda30 [WdFilter.sys + 0x4da30]
[+] [ObjectCallblacks] Callback belongs to an EDR but is disabled.
[+] [ObjectCallblacks] Enumerating Thread object callbacks :
[+] [ObjectCallblacks] Object callbacks are not found !
[+] [ETWTI] Checking the ETW Threat Intelligence Provider state...
[+] [ETWTI] ETW Threat Intelligence Provider is DISABLED!
[+] Process is "safe" to launch our payload
[+] Kernel callbacks have normally been removed, starting cmd.exe
WARNING: EDR kernel callbacks will be restored after exiting the cmd prompt (by typing exit)
WARNING: While unlikely, the longer the callbacks are removed, the higher the chance of being detected / causing a BSoD upon restore is!
Microsoft Windows [Version 10.0.22621.1702]
(c) Microsoft Corporation. All rights reserved.
C:UsersuserDesktopOffsets>
? Original-README unten?
EDRSandBlast
ist ein in C
geschriebenes Tool, das einen anfälligen signierten Treiber bewaffnet, um EDR-Erkennungen (Notify Routine Callbacks, Object Callbacks und ETW TI
Provider) und LSASS
Schutz zu umgehen. Es werden auch mehrere Userland-Unhooking-Techniken implementiert, um die Userland-Überwachung zu umgehen.
Zum Zeitpunkt der Veröffentlichung wurde eine Kombination aus Userland- ( --usermode
) und Kernel-land- ( --kernelmode
) Techniken verwendet, um LSASS
-Speicher einer EDR-Prüfung zu unterziehen, ohne blockiert zu werden oder „OS Credential Dumping“-bezogene Ereignisse im Produkt (Cloud) zu generieren ) Konsole. Die Tests wurden an drei verschiedenen EDR-Produkten durchgeführt und waren jeweils erfolgreich.
EDR-Produkte verwenden Kernel-„Notify Routines“-Rückrufe unter Windows, um vom Kernel über Systemaktivitäten wie Prozess- und Thread-Erstellung und das Laden von Bildern ( exe
/ DLL
) benachrichtigt zu werden.
Diese Kernel-Rückrufe werden im Kernel-Land definiert, normalerweise vom Treiber, der die Rückrufe implementiert, unter Verwendung einer Reihe dokumentierter APIs ( nt!PsSetCreateProcessNotifyRoutine
, nt!PsSetCreateThreadNotifyRoutine
usw.). Diese APIs fügen vom Treiber bereitgestellte Rückrufroutinen zu undokumentierten Arrays von Routinen im Kernel-Space hinzu:
PspCreateProcessNotifyRoutine
für die ProzesserstellungPspCreateThreadNotifyRoutine
für die Thread-ErstellungPspLoadImageNotifyRoutine
zum Laden von Bildern EDRSandBlast
zählt die in diesen Arrays definierten Routinen auf und entfernt alle Rückrufroutinen, die mit einer vordefinierten Liste von EDR-Treibern verknüpft sind (mehr als 1000 Treiber von Sicherheitsprodukten werden unterstützt, siehe Abschnitt „Erkennung von EDR-Treibern“). Die Aufzählung und Entfernung wird durch die Nutzung eines ermöglicht Beliebiges Kernel-Speicher-Lese-/Schreibprimitiv, das durch die Ausnutzung eines anfälligen Treibers bereitgestellt wird (siehe Abschnitt „Anfällige Treiber“).
Die Offsets der oben genannten Arrays werden mithilfe mehrerer Techniken wiederhergestellt. Weitere Informationen finden Sie im Abschnitt „Offsets“.
EDR- (und sogar EPP-)Produkte registrieren häufig „Objektrückrufe“ mithilfe der Kernel-API nt!ObRegisterCallbacks
. Mit diesen Rückrufen kann das Sicherheitsprodukt bei jeder Handle-Generierung für bestimmte Objekttypen benachrichtigt werden (Objektrückrufe im Zusammenhang mit Prozessen, Threads und Desktops werden jetzt von Windows unterstützt). Eine Handle-Generierung kann beim Öffnen eines Objekts (Aufruf von OpenProcess
, OpenThread
usw.) sowie bei der Handle-Duplizierung (Aufruf von DuplicateHandle
usw.) erfolgen.
Durch die Benachrichtigung des Kernels über jeden dieser Vorgänge kann ein Sicherheitsprodukt die Legitimität der Handle-Erstellung analysieren ( z. B. versucht ein unbekannter Prozess, LSASS zu öffnen ) und es sogar blockieren, wenn eine Bedrohung erkannt wird.
Bei jeder Rückrufregistrierung mit ObRegisterCallbacks
wird der doppelt verknüpften CallbackList
Liste im _OBJECT_TYPE
Objekt ein neues Element hinzugefügt, das den vom Rückruf betroffenen Objekttyp beschreibt (entweder ein Prozess, ein Thread oder ein Desktop). Leider werden diese Elemente durch eine Struktur beschrieben, die von Microsoft weder dokumentiert noch in Symboldateien veröffentlicht wird. Die Untersuchung verschiedener ntoskrnl.exe
Versionen scheint jedoch darauf hinzudeuten, dass sich die Struktur zwischen (mindestens) den Windows 10-Builds 10240 und 22000 (von 2015 bis 2022) nicht geändert hat.
Die erwähnte Struktur, die eine Objekt-Callback-Registrierung darstellt, ist die folgende:
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 oben erwähnte OB_CALLBACK
-Struktur ist ebenfalls nicht dokumentiert und wird wie folgt definiert:
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 Objektrückrufe zu deaktivieren, sind in EDRSandblast
drei Techniken implementiert; Derzeit ist jedoch nur einer aktiviert.
Enabled
“ von OB_CALLBACK_ENTRY
Dies ist die in EDRSandblast
aktivierte Standardtechnik. Um EDR-bezogene Objektrückrufe zu erkennen und zu deaktivieren, wird die CallbackList
Liste durchsucht, die sich in den _OBJECT_TYPE
Objekten befindet, die mit den Prozess- und Thread -Typen verknüpft sind. Auf beide _OBJECT_TYPE
s wird durch öffentliche globale Symbole im Kernel verwiesen, PsProcessType
und PsThreadType
.
Es wird davon ausgegangen, dass jedes Element der Liste zur oben beschriebenen OB_CALLBACK_ENTRY
-Struktur passt (Annahme, die zum Zeitpunkt des Schreibens zumindest in allen Windows 10-Builds zu gelten scheint). In den Feldern PreOperation
und PostOperation
definierte Funktionen prüfen, ob sie zu einem EDR-Treiber gehören. Wenn dies der Fall ist, werden Rückrufe einfach deaktiviert, indem das Flag Enabled
umgeschaltet wird.
Obwohl es sich um eine ziemlich sichere Technik handelt, ist sie mit dem Nachteil verbunden, dass sie sich auf eine undokumentierte Struktur verlässt. Um das Risiko einer unsicheren Manipulation dieser Struktur zu verringern, werden grundlegende Prüfungen durchgeführt, um zu überprüfen, ob einige Felder die erwarteten Werte aufweisen:
Enabled
ist entweder TRUE
oder FALSE
( lachen Sie nicht, ein BOOL
ist ein int
, es könnte also 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 Prozessen aufheben Eine weitere Strategie, die nicht auf einer undokumentierten Struktur basiert (und daher theoretisch robuster gegenüber NT-Kernel-Änderungen ist), ist das Aufheben der Verknüpfung der gesamten CallbackList
sowohl für Prozesse als auch für Threads. Das _OBJECT_TYPE
Objekt ist das folgende:
struct _OBJECT_TYPE {
LIST_ENTRY TypeList ;
UNICODE_STRING Name ;
[...]
_OBJECT_TYPE_INITIALIZER TypeInfo ;
[...]
LIST_ENTRY CallbackList ;
}
Wenn die Flink
und Blink
-Zeiger der CallbackList
LIST_ENTRY
auf LIST_ENTRY
selbst zeigen, wird die Liste effektiv leer. Da die _OBJECT_TYPE
-Struktur in den Kernel-Symbolen veröffentlicht wird, ist die Technik nicht auf fest codierte Offsets/Strukturen angewiesen. Es hat jedoch einige Nachteile.
Die erste besteht darin, dass Rückrufe nicht nur von EDR deaktiviert werden können. Tatsächlich betrifft die Technik alle Objektrückrufe, die von „legitimer“ Software registriert worden sein könnten. Es sollte jedoch beachtet werden, dass Objektrückrufe (zum Zeitpunkt des Verfassens dieses Artikels) von keiner vorinstallierten Komponente unter Windows 10 verwendet werden. Daher sollte ihre Deaktivierung keinen Einfluss auf die Maschinenstabilität haben (umso mehr, wenn die Deaktivierung nur vorübergehend ist).
Der zweite Nachteil besteht darin, dass Prozess- oder Thread-Handle-Operationen im normalen Betrieb des Betriebssystems sehr häufig (nahezu kontinuierlich) erfolgen. Wenn das verwendete Kernel-Schreibprimitiv einen QWORD
Schreibvorgang nicht „atomar“ durchführen kann, besteht daher eine gute Chance, dass der Kernel während des Überschreibens auf den Zeiger _OBJECT_TYPE.CallbackList.Flink
zugreift. Beispielsweise kann der anfällige MSI-Treiber RTCore64.sys
jeweils nur einen DWORD
-Schreibvorgang ausführen, sodass zum Überschreiben des Zeigers zwei unterschiedliche IOCTLs erforderlich sind, zwischen denen der Kernel ihn mit hoher Wahrscheinlichkeit verwenden wird (was zu einem Absturz führt). Andererseits kann der anfällige DELL-Treiber DBUtil_2_3.sys
Schreibvorgänge beliebiger Größe in einem IOCTL durchführen, sodass die Verwendung dieser Methode nicht zu einem Absturz führt.
Eine letzte Technik, die wir gefunden haben, bestand darin, die Objektrückrufunterstützung für Threads und Prozesse vollständig zu deaktivieren. Innerhalb der _OBJECT_TYPE
Struktur, die den Prozess- und Threadtypen entspricht, befindet sich ein TypeInfo
Feld, das der dokumentierten _OBJECT_TYPE_INITIALIZER
-Struktur folgt. Letzteres enthält ein ObjectTypeFlags
Bitfeld, dessen SupportsObjectCallbacks
-Flag bestimmt, ob der beschriebene Objekttyp (Prozess, Thread, Desktop, Token, Datei usw.) die Registrierung von Objektrückrufen unterstützt oder nicht. Wie bereits erwähnt, unterstützen zum Zeitpunkt des Schreibens nur die Objekttypen „Prozess“, „Thread“ und „Desktop“ diese Rückrufe in einer Windows-Installation.
Da das SupportsObjectCallbacks
Bit von ObpCreateHandle
oder ObDuplicateObject
überprüft wird, bevor die CallbackList
überhaupt gelesen wird (und natürlich vor der Ausführung von Rückrufen), wird durch das Umdrehen des Bits zur Kernel-Laufzeit die Ausführung aller Objektrückrufe effektiv deaktiviert.
Der Hauptnachteil der Methode besteht einfach darin, dass KPP („ PatchGuard “) die Integrität einiger (aller?) _OBJECT_TYPE
-Strukturen überwacht und eine 0x109 Bug Check
auslöst, wobei Parameter 4 gleich 0x8
ist, was bedeutet, dass eine Objekttypstruktur geändert wurde.
Allerdings sollte die schnelle Durchführung der Deaktivierung/Wiederaktivierung (und der „böswilligen“ Aktion dazwischen) ausreichen, um PatchGuard „einen Wettlauf“ zu ermöglichen (es sei denn, Sie haben Pech und eine regelmäßige Überprüfung wird gerade im falschen Moment durchgeführt).
Der ETW Microsoft-Windows-Threat-Intelligence
protokolliert Daten über die Verwendung einiger Windows-APIs, die häufig in böswilliger Absicht verwendet werden. Dazu gehört die nt!MiReadWriteVirtualMemory
-API, die von nt!NtReadVirtualMemory
aufgerufen wird (die zum Ausgeben LSASS
Speicher verwendet wird) und von der Funktion nt!EtwTiLogReadWriteVm
überwacht wird.
EDR-Produkte können die vom ETW TI
Anbieter erstellten Protokolle über Dienste oder Prozesse nutzen, die als SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT
bzw. PS_PROTECTED_ANTIMALWARE_LIGHT
ausgeführt werden und mit einem Early Launch Anti Malware (ELAM)
verknüpft sind.
Wie von slaeryan
in einem Blogbeitrag CNO Development Labs
veröffentlicht, kann der ETW TI
-Anbieter vollständig deaktiviert werden, indem im Kernel-Speicher sein ProviderEnableInfo
Attribut auf 0x0
gepatcht wird. Weitere Informationen zu dieser Technik finden Sie im oben genannten Blogbeitrag.
Ähnlich wie beim Entfernen von Kernel-Rückrufen werden die erforderlichen ntoskrnl.exe
Offsets ( nt!EtwThreatIntProvRegHandleOffset
, GuidEntry
von _ETW_REG_ENTRY
und ProviderEnableInfo
von _ETW_GUID_ENTRY
) in der Datei NtoskrnlOffsets.csv
für eine Reihe von Windows-Kernelversionen berechnet.
Um von Prozessen ausgeführte Aktionen einfach zu überwachen, setzen EDR-Produkte häufig einen Mechanismus namens Userland-Hooking ein. Erstens registrieren EDR-Produkte einen Kernel-Callback (normalerweise Image-Lade- oder Prozesserstellungs- Callbacks, siehe oben), der es ihnen ermöglicht, bei jedem Prozessstart benachrichtigt zu werden.
Wenn ein Prozess von Windows geladen wird und bevor er tatsächlich startet, kann der EDR eine benutzerdefinierte DLL in den Adressraum des Prozesses einfügen, der seine Überwachungslogik enthält. Beim Laden fügt diese DLL „ Hooks “ am Anfang jeder Funktion ein, die vom EDR überwacht werden soll. Wenn zur Laufzeit die überwachten Funktionen vom überwachten Prozess aufgerufen werden, leiten diese Hooks den Kontrollfluss an einen in der DLL des EDR vorhandenen Überwachungscode um, der es ihm ermöglicht, Argumente zu überprüfen und Werte dieser Aufrufe zurückzugeben.
In den meisten Fällen handelt es sich bei überwachten Funktionen um Systemaufrufe (z. B. NtReadVirtualMemory
, NtOpenProcess
usw.), deren Implementierungen sich in ntdll.dll
befinden. Durch das Abfangen von Aufrufen von Nt*
-Funktionen können Produkte so nah wie möglich an der Userland-/Kernel-Land-Grenze sein (während sie im Userland bleiben), aber auch Funktionen von einigen DLLs höherer Ebene können überwacht werden.
Nachfolgend finden Sie Beispiele für die gleiche Funktion vor und nach der Einbindung in das EDR-Produkt:
NtProtectVirtualMemory proc near
mov r10 , rcx
mov eax , 50h
test byte ptr ds : 7FFE0308h , 1
jnz short loc_18009D1E5
syscall
retn
loc_18009D1E5:
int 2Eh
retn
NtProtectVirtualMemory endp
NtProtectVirtualMemory proc near
jmp sub_7FFC74490298 ; --> "hook", jump to EDR analysis function
int 3 ; overwritten instructions
int 3 ; overwritten instructions
int 3 ; overwritten instructions
test byte_7FFE0308 , 1 ; <-- execution resumes here after analysis
jnz short loc_7FFCB44AD1E5
syscall
retn
loc_7FFCB44AD1E5:
int 2Eh
retn
NtProtectVirtualMemory endp
Userland-Hooks haben die „Schwäche“, sich im Userland-Speicher zu befinden, was bedeutet, dass sie durch den untersuchten Prozess direkt beobachtbar und modifizierbar sind. Um Hooks im Prozessadressraum automatisch zu erkennen, besteht die Hauptidee darin, die Unterschiede zwischen der ursprünglichen DLL auf der Festplatte und der im Speicher befindlichen Bibliothek zu vergleichen, die möglicherweise durch einen EDR geändert wurde. Um diesen Vergleich durchzuführen, führt EDRSandblast die folgenden Schritte aus:
InLoadOrderModuleList
im PEB
aufgelistet (um den Aufruf einer API zu vermeiden, die überwacht und verdächtig sein könnte). Hinweis: Der Prozess kann verallgemeinert werden, um Unterschiede überall in nicht beschreibbaren Abschnitten und nicht nur am Anfang exportierter Funktionen zu finden, z. B. wenn EDR-Produkte beginnen, Hooks in der Mitte der Funktion anzuwenden :) Daher wird dies vom Tool nicht verwendet wurde in findDiffsInNonWritableSections
implementiert.
Um die von diesen Hooks durchgeführte Überwachung zu umgehen, sind mehrere Techniken möglich, und jede hat Vor- und Nachteile.
Die intuitivste Methode, die Hook-basierte Überwachung zu umgehen, besteht darin, die Hooks zu entfernen. Da die Hooks im Speicher vorhanden sind, der für den Prozess selbst erreichbar ist, kann der Prozess zum Entfernen eines Hooks einfach Folgendes tun:
Dieser Ansatz ist ziemlich einfach und kann verwendet werden, um jeden erkannten Haken auf einmal zu entfernen. Dies wird zu Beginn von einem anstößigen Tool ausgeführt und ermöglicht, dass der Rest des Codes den Hooking-Mechanismus überhaupt nicht wahrnimmt und ohne Überwachung normal funktioniert.
Es hat jedoch zwei Hauptnachteile. Der EDR überwacht wahrscheinlich die Verwendung von NtProtectVirtualMemory
, daher ist es (zumindest konzeptionell) eine schlechte Idee, ihn zum Ändern der Berechtigungen der Seite zu verwenden, auf der die Hooks installiert wurden. Auch wenn ein Thread vom EDR ausgeführt wird und regelmäßig die Integrität der Hooks überprüft, könnte dies ebenfalls eine Erkennung auslösen.
Einzelheiten zur Implementierung finden Sie im Codepfad der Funktion unhook()
, wenn unhook_method
UNHOOK_WITH_NTPROTECTVIRTUALMEMORY
hat.
Wichtiger Hinweis: Der Einfachheit halber ist diese Technik in EDRSandblast als Basistechnik implementiert, die zur Präsentation der anderen Bypass-Techniken verwendet wird; Jeder von ihnen demonstriert, wie man eine nicht überwachte Version von NtProtectVirtualMemory
erhält, führt danach jedoch denselben Vorgang aus (Aushängen eines bestimmten Hooks).
Um einen bestimmten Hook zu umgehen, ist es möglich, einfach „überzuspringen“ und den Rest der Funktion unverändert auszuführen. Zunächst müssen die ursprünglichen Bytes der überwachten Funktion, die vom EDR zur Installation des Hooks überschrieben wurden, aus der DLL-Datei wiederhergestellt werden. In unserem vorherigen Codebeispiel wären dies die Bytes, die den folgenden Anweisungen entsprechen:
mov r10 , rcx
mov eax , 50h
Die Identifizierung dieser Bytes ist eine einfache Aufgabe, da wir, wie zuvor beschrieben, einen sauberen Diff sowohl der Speicher- als auch der Festplattenversionen der Bibliothek durchführen können. Anschließend stellen wir eine Sprunganweisung zusammen, die den Kontrollfluss an den Code umleitet, der unmittelbar auf den Hook folgt, an der Adresse NtProtectVirtualMemory + sizeof(overwritten_instructions)
jmp NtProtectVirtualMemory + 8
Schließlich verketten wir diese Opcodes, speichern sie im (neu) ausführbaren Speicher und behalten einen Zeiger auf sie. Dieses Objekt wird als „ Trampolin “ bezeichnet und kann dann als Funktionszeiger verwendet werden, was genau der ursprünglichen NtProtectVirtualMemory
-Funktion entspricht.
Der Hauptvorteil dieser Technik besteht wie bei allen unten aufgeführten Techniken darin, dass der Hook niemals gelöscht wird, sodass jede vom EDR an den Hooks durchgeführte Integritätsprüfung erfolgreich sein sollte. Es erfordert jedoch die Zuweisung von beschreibbarem und dann ausführbarem Speicher, was typisch für eine Shellcode-Zuweisung ist und daher die Prüfung des EDR auf sich zieht.
Einzelheiten zur Implementierung finden Sie im Codepfad der Funktion unhook()
, wenn unhook_method
UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE
hat. Bitte bedenken Sie, dass die Technik nur in unserer Implementierung gezeigt wird und letztendlich dazu dient, Hooks aus dem Speicher zu entfernen , wie jede Technik unten.
Damit sein Hook funktioniert, muss das EDR-Produkt die Opcodes, die es entfernt hat, irgendwo im Speicher speichern. Am schlechtesten ( oder „besser“ aus der Sicht des Angreifers ) ist es, die ursprünglichen Anweisungen effektiv zu nutzen. Der EDR hat sich wahrscheinlich irgendwo ein Trampolin zugewiesen, um die ursprüngliche Funktion auszuführen, nachdem er den Aufruf abgefangen hat.
Dieses Trampolin kann gesucht und als Ersatz für die Hooked-Funktion verwendet werden, ohne dass ausführbarer Speicher zugewiesen oder eine API aufgerufen werden muss, mit Ausnahme von VirtualQuery
, das höchstwahrscheinlich nicht als harmlose Funktion überwacht wird.
Um das Trampolin im Speicher zu finden, durchsuchen wir den gesamten Adressraum mit VirtualQuery
nach festgeschriebenem und ausführbarem Speicher. Wir durchsuchen jeden dieser Speicherbereiche nach einer Sprunganweisung, die auf die Adresse abzielt, die den überschriebenen Anweisungen folgt ( NtProtectVirtualMemory+8
in unserem vorherigen Beispiel). Mit dem Trampolin kann dann die Hooked-Funktion aufgerufen werden, ohne den Hook auszulösen.
Diese Technik funktioniert überraschend gut, da sie fast alle Trampoline auf dem getesteten EDR wiederherstellt. Einzelheiten zur Implementierung finden Sie im Codepfad der Funktion unhook()
, wenn unhook_method
UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE
hat.
Eine weitere einfache Methode, um Zugriff auf eine nicht überwachte Version der NtProtectVirtualMemory
-Funktion zu erhalten, besteht darin, eine doppelte Version der ntdll.dll
-Bibliothek in den Prozessadressraum zu laden. Da zwei identische DLLs im selben Prozess geladen werden können, sofern sie unterschiedliche Namen haben, können wir einfach die legitime Datei ntdll.dll
an einen anderen Speicherort kopieren, sie mit LoadLibrary
laden (oder den Ladevorgang erneut implementieren) und mit GetProcAddress
auf die Funktion zugreifen Zum Beispiel.
Diese Technik ist sehr einfach zu verstehen und zu implementieren und hat gute Erfolgsaussichten, da die meisten EDR-Produkte keine erneuten Hooks auf neu geladenen DLLs installieren, sobald der Prozess ausgeführt wird. Der größte Nachteil besteht jedoch darin, dass das Kopieren von Microsoft-signierten Binärdateien unter einem anderen Namen von EDR-Produkten oft als ebenso verdächtig angesehen wird wie selbst.
Diese Technik ist jedoch in EDRSandblast
implementiert. Einzelheiten zur Implementierung finden Sie im Codepfad der Funktion unhook()
, wenn unhook_method
UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY
hat.
Um mit Systemaufrufen verbundene Funktionen zu verwenden, kann ein Programm Systemaufrufe (in Assembly) neu implementieren, um die entsprechenden Betriebssystemfunktionen aufzurufen, ohne tatsächlich den Code in ntdll.dll
zu berühren, der möglicherweise vom EDR überwacht wird. Dadurch wird jegliches Userland-Hooking für Systemaufruffunktionen in ntdll.dll
vollständig umgangen.
Dies hat jedoch einige Nachteile. Dies setzt zunächst voraus, dass Sie die Liste der Systemaufrufnummern der vom Programm benötigten Funktionen kennen, die sich für jede Windows-Version ändert. Dies wird jedoch durch die Implementierung mehrerer Heuristiken gemildert, von denen bekannt ist, dass sie in allen früheren Versionen von Windows NT funktionieren (Sortieren der Zw*
-Exporte von ntdll
, Suchen nach mov rax, #syscall_number
in der zugehörigen ntdll
-Funktion usw.) und Überprüfen, ob alle das gleiche Ergebnis liefern (weitere Informationen finden Sie unter Syscalls.c
).
Auch Funktionen, die technisch gesehen keine Systemaufrufe sind (z. B. LoadLibraryX
/ LdrLoadDLL
), könnten ebenfalls überwacht werden und können nicht einfach mithilfe eines Systemaufrufs neu implementiert werden.
Die Technik der direkten Systemaufrufe ist in EDRSandblast implementiert. Wie bereits erwähnt, dient es nur dazu, NtProtectVirtualMemory
sicher auszuführen und alle erkannten Hooks zu entfernen.
Einzelheiten zur Implementierung finden Sie im Codepfad der Funktion unhook()
, wenn unhook_method
UNHOOK_WITH_DIRECT_SYSCALL
hat.
Wie bereits erwähnt, ist jede Aktion, die einen Lese- oder Schreibvorgang im Kernelspeicher erfordert, auf einen anfälligen Treiber angewiesen, der dieses Grundelement bereitstellt. In EDRSanblast kann das Hinzufügen der Unterstützung für einen neuen Treiber, der das Lese-/Schreibprimitiv bereitstellt, „einfach“ erfolgen, es müssen nur drei Funktionen implementiert werden:
ReadMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)
-Funktion, die Size
Bytes von der Kernel-Adresse Address
in den Userland-Puffer Buffer
kopiert;WriteMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)
-Funktion, die Size
Bytes aus dem Userland-Puffer Buffer
in die Kernel-Adresse Address
kopiert;CloseDriverHandle_DRIVERNAME()
der sicherstellt, dass alle Handles für den Treiber geschlossen sind (erforderlich vor dem Deinstallationsvorgang, der im Moment treiberunabhängig ist). Beispielsweise werden derzeit zwei Treiber von EDRSandblast unterstützt: RTCore64.sys
(SHA256: 01AA278B07B58DC46C84BD0B1B5C8E9EE4E62EA0BF7A695862444AF32E87F1FD
) und DBUtils_2_3.sys
(SHA256: 0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5
). Der folgende Code in KernelMemoryPrimitives.h
ist zu aktualisieren, wenn der verwendete anfällige Treiber geändert oder ein neuer implementiert werden muss.
#define RTCore 0
#define DBUtil 1
// Select the driver to use with the following #define
#define VULN_DRIVER RTCore
#if VULN_DRIVER == RTCore
#define DEFAULT_DRIVER_FILE TEXT("RTCore64.sys")
#define CloseDriverHandle CloseDriverHandle_RTCore
#define ReadMemoryPrimitive ReadMemoryPrimitive_RTCore
#define WriteMemoryPrimitive WriteMemoryPrimitive_RTCore
#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
#endif
Derzeit werden mehrere Techniken verwendet, um festzustellen, ob ein bestimmter Treiber oder Prozess zu einem EDR-Produkt gehört oder nicht.
Erstens kann hierfür einfach der Name des Fahrers verwendet werden. Tatsächlich weist Microsoft allen Treibern, die Rückrufe in den Kernel einfügen müssen, bestimmte Nummern namens „Altitudes“ zu. Dies ermöglicht eine deterministische Reihenfolge bei der Ausführung von Rückrufen, unabhängig von der Registrierungsreihenfolge, jedoch nur basierend auf der Treibernutzung. Eine Liste der Treiber (Anbieter), die eine bestimmte Höhe reserviert haben, finden Sie auf MSDN. Daher bietet Microsoft eine nahezu umfassende Liste von Sicherheitstreibernamen für Sicherheitsprodukte an, hauptsächlich in den Listen „FSFilter Anti-Virus“ und „FSFilter Activity Monitor“. Diese Listen mit Treibernamen sind in EDRSandblast eingebettet, ebenso wie zusätzliche Beiträge.
Darüber hinaus werden ausführbare EDR-Dateien und DLLs häufig mit dem Signaturzertifikat des Anbieters digital signiert. Daher kann die Überprüfung des Unterzeichners einer mit einem Prozess verknüpften ausführbaren Datei oder DLL eine schnelle Identifizierung von EDR-Produkten ermöglichen.
Außerdem müssen Treiber direkt von Microsoft signiert werden, damit sie in den Kernelraum geladen werden dürfen. Obwohl der Verkäufer des Fahrers nicht direkt der Unterzeichner des Fahrers selbst ist, scheint es, dass der Name des Verkäufers immer noch in einem Attribut der Signatur enthalten ist; Diese Erkennungstechnik muss jedoch noch untersucht und implementiert werden.
Wenn es schließlich um einen EDR geht, der EDRSandblast nicht bekannt ist, besteht der beste Ansatz darin, das Tool im „Überwachungs“-Modus auszuführen und die Liste der Treiber zu überprüfen, die Kernel-Rückrufe registriert haben. Anschließend kann der Name des Treibers zur Liste hinzugefügt, das Tool neu kompiliert und erneut ausgeführt werden.
Der Local Security Authority (LSA) Protection
, der erstmals in Windows 8.1 und Windows Server 2012 R2 eingeführt wurde, nutzt die Protected Process Light (PPL)
um den Zugriff auf den LSASS
-Prozess einzuschränken. Der PPL
Schutz reguliert und schränkt Vorgänge wie Speicherinjektion oder Speicherauszug geschützter Prozesse ein, selbst von einem Prozess aus, der über die SeDebugPrivilege
Berechtigung verfügt. Im Rahmen des Prozessschutzmodells können nur Prozesse, die mit höheren Schutzstufen ausgeführt werden, Vorgänge an geschützten Prozessen ausführen.
Die _EPROCESS
Struktur, die vom Windows-Kernel zur Darstellung eines Prozesses im Kernelspeicher verwendet wird, enthält ein _PS_PROTECTION
Feld, das die Schutzstufe eines Prozesses über seine Attribute Type
( _PS_PROTECTED_TYPE
) und Signer
( _PS_PROTECTED_SIGNER
) definiert.
Durch das Schreiben in den Kernel-Speicher ist der EDRSandblast-Prozess in der Lage, seine eigene Schutzstufe auf PsProtectedSignerWinTcb-Light
zu aktualisieren. Diese Ebene reicht aus, um den LSASS
-Prozessspeicher zu sichern, da sie gegenüber PsProtectedSignerLsa-Light
„dominiert“, der Schutzebene des LSASS
Prozesses, der mit dem RunAsPPL
-Mechanismus ausgeführt wird.
EDRSandBlast
implementiert den Selbstschutz wie folgt:
NtQuerySystemInformation
um das geöffnete Handle im aktuellen Prozess und die Adresse der EPROCESS
Struktur des aktuellen Prozesses im Kernelspeicher zu finden.Micro-Star MSI Afterburner
Treibers, um das _PS_PROTECTION
Feld des aktuellen Prozesses im Kernel-Speicher zu überschreiben. Die Offsets des Felds _PS_PROTECTION
relativ zur EPROCESS
Struktur (definiert durch die verwendete ntoskrnl
Version) werden in der Datei NtoskrnlOffsets.csv
berechnet. Microsoft Credential Guard
ist eine auf Virtualisierung basierende Isolationstechnologie, die in Microsoft Windows 10 (Enterprise edition)
eingeführt wurde und den direkten Zugriff auf die im LSASS
Prozess gespeicherten Anmeldeinformationen verhindert.
Wenn Credentials Guard
aktiviert ist, wird im Virtual Secure Mode
ein LSAIso
-Prozess ( LSA Insulated ) erstellt, eine Funktion, die die Virtualisierungserweiterungen der CPU nutzt, um zusätzliche Sicherheit der Daten im Speicher zu bieten. Der Zugriff auf den LSAIso
-Prozess ist auch für einen Zugriff mit dem Sicherheitskontext NT AUTHORITYSYSTEM
eingeschränkt. Bei der Verarbeitung eines Hash führt der LSA
-Prozess einen RPC
Aufruf an den LSAIso
Prozess durch und wartet auf die Fortsetzung des LSAIso
Ergebnisses. Daher enthält der LSASS
Prozess keine Geheimnisse und speichert stattdessen LSA Isolated Data
.
Wie es in der ursprünglichen Untersuchung von N4kedTurtle
heißt: „ Wdigest
kann auf einem System mit Credential Guard aktiviert werden, indem die Werte von g_fParameter_useLogonCredential
und g_IsCredGuardEnabled
im Speicher gepatcht werden.“ Die Aktivierung von Wdigest
führt dazu, dass Klartext-Anmeldeinformationen für alle neuen interaktiven Anmeldungen im LSASS
Speicher gespeichert werden (ohne dass ein Neustart des Systems erforderlich ist). Weitere Einzelheiten zu dieser Technik finden Sie im ursprünglichen Forschungsblogbeitrag.
EDRSandBlast
macht den ursprünglichen PoC einfach etwas opsec-freundlicher und bietet Unterstützung für eine Reihe von wdigest.dll
Versionen (durch berechnete Offsets für g_fParameter_useLogonCredential
und g_IsCredGuardEnabled
).
Um Umgehungsoperationen zur Kernelüberwachung zuverlässig durchführen zu können, muss EDRSandblast genau wissen, wo der Kernelspeicher gelesen und geschrieben werden soll. Dies erfolgt mithilfe von Offsets globaler Variablen innerhalb des Zielbilds (ntoskrnl.exe, wdigest.dll) sowie Offsets spezifischer Felder in Strukturen, deren Definitionen von Microsoft in Symboldateien veröffentlicht werden. Diese Offsets sind für jeden Build der Zielbilder spezifisch und müssen mindestens einmal für eine bestimmte Plattformversion erfasst werden.
Die Entscheidung, „hartcodierte“ Offsets anstelle von Mustersuchen zu verwenden, um die von EDRSandblast verwendeten Strukturen und Variablen zu lokalisieren, wird durch die Tatsache gerechtfertigt, dass die undokumentierten APIs, die für das Hinzufügen/Entfernen von Kernel-Rückrufen verantwortlich sind, Änderungen unterliegen und dass jeder Versuch, Kernel zu lesen oder zu schreiben Speicher an der falschen Adresse kann (und wird oft) zu einer Bug Check
( Blue Screen of Death
) führen. Ein Maschinenabsturz ist sowohl in Red-Teaming- als auch in normalen Penetrationstestszenarien nicht akzeptabel, da eine abstürzende Maschine für die Verteidiger gut sichtbar ist und alle Anmeldeinformationen verliert, die zum Zeitpunkt des Angriffs noch im Speicher waren.
Um Offsets für jede bestimmte Windows-Version abzurufen, werden zwei Ansätze implementiert.
Die erforderlichen Offsets ntoskrnl.exe
und wdigest.dll
können mit dem bereitgestellten Python-Skript ExtractOffsets.py
extrahiert werden, das auf radare2
und r2pipe
basiert, um Symbole aus PDB-Dateien herunterzuladen und zu analysieren, und die erforderlichen Offsets daraus zu extrahieren. Die Offsets werden dann zur späteren Verwendung durch EDRSandblast in CSV-Dateien gespeichert.
Um eine breite Palette von Windows-Builds sofort zu unterstützen, werden viele Versionen der Binärdateien ntoskrnl.exe
und wdigest.dll
von Winbindex referenziert und können von ExtractOffsets.py
. Dies ermöglicht das Extrahieren von Offsets aus fast allen Dateien, die jemals in Windows-Update-Paketen veröffentlicht wurden (bislang sind mehr als 450 Versionen ntoskrnl.exe
und mehr als 30 wdigest.dll
verfügbar und vorberechnet).
In EDRSandBlast
wurde eine zusätzliche Option implementiert, die es dem Programm ermöglicht, die benötigten .pdb
Dateien selbst vom Microsoft Symbol Server herunterzuladen, die erforderlichen Offsets zu extrahieren und sogar die entsprechenden .csv
Dateien zu aktualisieren, falls vorhanden.
Die Verwendung der Option --internet
vereinfacht die Ausführung des Tools erheblich und birgt gleichzeitig ein zusätzliches OpSec-Risiko, da während des Vorgangs eine .pdb
Datei heruntergeladen und auf der Festplatte abgelegt wird. Dies ist für die dbghelp.dll
-Funktionen erforderlich, die zum Parsen der Symboldatenbank verwendet werden. In Zukunft könnte jedoch eine vollständige speicherinterne PDB-Analyse implementiert werden, um diese Anforderung zu erhöhen und den Platzbedarf des Tools zu verringern.
Der anfällige RTCore64.sys
-Treiber kann abgerufen werden unter:
http://download-eu2.guru3d.com/afterburner/%5BGuru3D.com%5D-MSIAfterburnerSetup462Beta2.zip