Par Gabriel Landau chez Elastic Security. Modification d'EDRSandblast - voir le README original ci-dessous.
Intègre GodFault dans EDR Sandblast, obtenant le même résultat sans utiliser de pilotes vulnérables.
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>
? README original ci-dessous ?
EDRSandBlast
est un outil écrit en C
qui utilise un pilote signé vulnérable pour contourner les détections EDR (rappels de routine Notify, rappels d'objet et fournisseur ETW TI
) et les protections LSASS
. Plusieurs techniques de décrochage des zones utilisateur sont également mises en œuvre pour échapper à la surveillance des zones utilisateurs.
Depuis la sortie, une combinaison de techniques userland ( --usermode
) et Kernel-land ( --kernelmode
) a été utilisée pour vider la mémoire LSASS
sous contrôle EDR, sans être bloquée ni générer d'événements liés au "OS Credential Dumping" dans le produit (cloud ) console. Les tests ont été réalisés sur 3 produits EDR distincts et ont été réussis dans chaque cas.
Les produits EDR utilisent les rappels du noyau « Notify Routines » sous Windows pour être avertis par le noyau de l'activité du système, telle que la création de processus et de threads et le chargement d'images ( exe
/ DLL
).
Ces rappels du noyau sont définis à partir du noyau, généralement à partir du pilote implémentant les rappels, à l'aide d'un certain nombre d'API documentées ( nt!PsSetCreateProcessNotifyRoutine
, nt!PsSetCreateThreadNotifyRoutine
, etc.). Ces API ajoutent des routines de rappel fournies par le pilote à des tableaux de routines non documentés dans l'espace noyau :
PspCreateProcessNotifyRoutine
pour la création de processusPspCreateThreadNotifyRoutine
pour la création de threadsPspLoadImageNotifyRoutine
pour le chargement d'images EDRSandBlast
énumère les routines définies dans ces tableaux et supprime toute routine de rappel liée à une liste prédéfinie de pilotes EDR (plus de 1000 pilotes de produits de sécurité supportés, voir la section détection des pilotes EDR. L'énumération et la suppression sont rendues possibles grâce à l'exploitation d'un Primitive arbitraire de lecture/écriture de la mémoire du noyau fournie par l'exploitation d'un pilote vulnérable (voir la section Pilotes vulnérables).
Les décalages des tableaux susmentionnés sont récupérés à l'aide de plusieurs techniques, veuillez vous référer à la section Décalages.
Les produits EDR (et même EPP) enregistrent souvent des « rappels d'objet » via l'utilisation de l'API du noyau nt!ObRegisterCallbacks
. Ces rappels permettent au produit de sécurité d'être notifié à chaque génération de handle sur des types d'objets spécifiques (les rappels d'objets liés aux processus, aux threads et aux bureaux sont désormais pris en charge par Windows). Une génération de handles peut se produire à l'ouverture d'un objet (appel à OpenProcess
, OpenThread
, etc.) ainsi qu'à la duplication de handles (appel à DuplicateHandle
, etc.).
En étant notifié par le noyau sur chacune de ces opérations, un produit de sécurité peut analyser la légitimité de la création du handle ( par exemple un processus inconnu tente d'ouvrir LSASS ), et même le bloquer si une menace est détectée.
A chaque enregistrement de rappel utilisant ObRegisterCallbacks
, un nouvel élément est ajouté à la liste double liée CallbackList
présente dans l'objet _OBJECT_TYPE
décrivant le type d'objet affecté par le rappel (soit un Processus, un Thread ou un Bureau). Malheureusement, ces éléments sont décrits par une structure qui n'est ni documentée ni publiée dans les fichiers de symboles par Microsoft. Cependant, son étude à partir de différentes versions ntoskrnl.exe
semble indiquer que la structure n'a pas changé entre (au moins) les versions 10240 et 22000 de Windows 10 (de 2015 à 2022).
La structure mentionnée, représentant un enregistrement de rappel d'objet, est la suivante :
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 ;
La structure OB_CALLBACK
mentionnée ci-dessus est également non documentée et est définie par ce qui suit :
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 ;
Afin de désactiver les rappels d'objets enregistrés par EDR, trois techniques sont implémentées dans EDRSandblast
; cependant un seul est activé pour le moment.
Enabled
de OB_CALLBACK_ENTRY
Il s'agit de la technique par défaut activée dans EDRSandblast
. Afin de détecter et de désactiver les rappels d'objets liés à EDR, la liste CallbackList
située dans les objets _OBJECT_TYPE
liés aux types Process et Thread est parcourue. Les deux _OBJECT_TYPE
sont pointés par des symboles globaux publics dans le noyau, PsProcessType
et PsThreadType
.
Chaque élément de la liste est supposé correspondre à la structure OB_CALLBACK_ENTRY
décrite ci-dessus (hypothèse qui semble être valable au moins dans toutes les versions de Windows 10 au moment de la rédaction). Les fonctions définies dans les champs PreOperation
et PostOperation
sont localisées pour vérifier si elles appartiennent à un pilote EDR, et si tel est le cas, les rappels sont simplement désactivés en basculant l'indicateur Enabled
.
Bien qu’il s’agisse d’une technique assez sûre, elle présente l’inconvénient de s’appuyer sur une structure non documentée ; pour réduire le risque de manipulation dangereuse de cette structure, des vérifications de base sont effectuées pour valider que certains champs ont les valeurs attendues :
Enabled
est soit TRUE
, soit FALSE
( ne riez pas, un BOOL
est un int
, donc il peut être autre chose que 1
ou 0
) ;Operations
sont OB_OPERATION_HANDLE_CREATE
, OB_OPERATION_HANDLE_DUPLICATE
ou les deux ;ObjectType
pointe sur PsProcessType
ou PsThreadType
. CallbackList
des threads et du processus Une autre stratégie qui ne repose pas sur une structure non documentée (et qui est donc théoriquement plus robuste face aux modifications du noyau NT) est la dissociation de l'ensemble de CallbackList
pour les processus et les threads. L'objet _OBJECT_TYPE
est le suivant :
struct _OBJECT_TYPE {
LIST_ENTRY TypeList ;
UNICODE_STRING Name ;
[...]
_OBJECT_TYPE_INITIALIZER TypeInfo ;
[...]
LIST_ENTRY CallbackList ;
}
Faire en sorte que les pointeurs Flink
et Blink
de CallbackList
LIST_ENTRY
pointent vers LIST_ENTRY
lui-même rend effectivement la liste vide. Puisque la structure _OBJECT_TYPE
est publiée dans les symboles du noyau, la technique ne s'appuie pas sur des décalages/structures codés en dur. Cependant, cela présente certains inconvénients.
Le premier n’étant pas capable de désactiver uniquement les rappels depuis EDR ; en effet, la technique affecte tous les rappels d'objets qui auraient pu être enregistrés par un logiciel « légitime ». Il convient néanmoins de noter que les rappels d'objets ne sont utilisés par aucun composant préinstallé sur Windows 10 (au moment de la rédaction) donc leur désactivation ne devrait pas affecter la stabilité de la machine (d'autant plus si la désactivation n'est que temporaire).
Le deuxième inconvénient est que les opérations de processus ou de thread handle sont très fréquentes (presque continues) dans le fonctionnement normal du système d’exploitation. Ainsi, si la primitive d'écriture du noyau utilisée ne peut pas effectuer une écriture QWORD
"atomiquement", il y a de fortes chances que le pointeur _OBJECT_TYPE.CallbackList.Flink
soit accédé par le noyau au milieu de son écrasement. Par exemple, le pilote vulnérable MSI RTCore64.sys
ne peut effectuer qu'une écriture DWORD
à la fois, donc 2 IOCTL distinctes seront nécessaires pour écraser le pointeur, entre lesquelles le noyau a une forte probabilité de l'utiliser (entraînant un crash). D'un autre côté, le pilote DELL vulnérable DBUtil_2_3.sys
peut effectuer des écritures de tailles arbitraires dans un IOCTL, donc utiliser cette méthode avec lui ne risque pas de provoquer un crash.
Une dernière technique que nous avons trouvée consistait à désactiver entièrement la prise en charge des rappels d'objets pour les threads et les processus. À l'intérieur de la structure _OBJECT_TYPE
correspondant aux types de processus et de thread réside un champ TypeInfo
, suivant la structure _OBJECT_TYPE_INITIALIZER
documentée. Ce dernier contient un champ binaire ObjectTypeFlags
, dont l'indicateur SupportsObjectCallbacks
détermine si le type d'objet décrit (Process, Thread, Desktop, Token, File, etc.) prend en charge ou non l'enregistrement du rappel d'objet. Comme indiqué précédemment, seuls les types d'objet Process, Thread et Desktop prennent en charge ces rappels sur une installation Windows au moment de la rédaction.
Étant donné que le bit SupportsObjectCallbacks
est vérifié par ObpCreateHandle
ou ObDuplicateObject
avant même de lire la CallbackList
(et avant d'exécuter les rappels, bien sûr), l'inversion du bit au moment de l'exécution du noyau désactive efficacement l'exécution de tous les rappels d'objets.
Le principal inconvénient de la méthode est simplement que KPP (" PatchGuard ") surveille l'intégrité de certaines (toutes ?) structures _OBJECT_TYPE
, et déclenche un 0x109 Bug Check
avec le paramètre 4 étant égal à 0x8
, ce qui signifie qu'une structure de type d'objet a été modifiée.
Cependant, effectuer la désactivation/réactivation (et l'action « malveillante » entre les deux) assez rapidement devrait suffire à « faire la course » avec PatchGuard (sauf si vous n'êtes pas chanceux et qu'une vérification périodique est effectuée juste au mauvais moment).
Le fournisseur ETW Microsoft-Windows-Threat-Intelligence
enregistre des données sur l'utilisation de certaines API Windows couramment utilisées à des fins malveillantes. Cela inclut l'API nt!MiReadWriteVirtualMemory
, appelée par nt!NtReadVirtualMemory
(qui est utilisée pour vider la mémoire LSASS
) et surveillée par la fonction nt!EtwTiLogReadWriteVm
.
Les produits EDR peuvent consommer les journaux produits par le fournisseur ETW TI
via des services ou des processus exécutés respectivement sous le nom SERVICE_LAUNCH_PROTECTED_ANTIMALWARE_LIGHT
ou PS_PROTECTED_ANTIMALWARE_LIGHT
, et associés à un pilote Early Launch Anti Malware (ELAM)
.
Comme publié par slaeryan
dans un article de blog CNO Development Labs
, le fournisseur ETW TI
peut être complètement désactivé en corrigeant, dans la mémoire du noyau, son attribut ProviderEnableInfo
sur 0x0
. Reportez-vous à l'excellent article de blog susmentionné pour plus d'informations sur la technique.
De la même manière que pour la suppression des rappels du noyau, les décalages ntoskrnl.exe
nécessaires ( nt!EtwThreatIntProvRegHandleOffset
, GuidEntry
de _ETW_REG_ENTRY
et ProviderEnableInfo
de _ETW_GUID_ENTRY
) sont calculés dans le fichier NtoskrnlOffsets.csv
pour un certain nombre de versions du noyau Windows.
Afin de surveiller facilement les actions effectuées par les processus, les produits EDR déploient souvent un mécanisme appelé userland hooking . Premièrement, les produits EDR enregistrent un rappel du noyau (généralement des rappels de chargement d'image ou de création de processus , voir ci-dessus) qui leur permet d'être avertis à chaque démarrage de processus.
Lorsqu'un processus est chargé par Windows et avant qu'il ne démarre réellement, l'EDR est capable d'injecter une DLL personnalisée dans l'espace d'adressage du processus, qui contient sa logique de surveillance. Lors du chargement, cette DLL injecte des " hooks " au début de chaque fonction qui doit être surveillée par l'EDR. Au moment de l'exécution, lorsque les fonctions surveillées sont appelées par le processus sous surveillance, ces hooks redirigent le flux de contrôle vers du code de supervision présent dans la DLL de l'EDR, ce qui lui permet d'inspecter les arguments et de renvoyer les valeurs de ces appels.
La plupart du temps, les fonctions surveillées sont des appels système (tels que NtReadVirtualMemory
, NtOpenProcess
, etc.), dont les implémentations résident dans ntdll.dll
. L'interception des appels aux fonctions Nt*
permet aux produits d'être aussi proches que possible de la frontière espace utilisateur/noyau (tout en restant dans l'espace utilisateur), mais les fonctions de certaines DLL de niveau supérieur peuvent également être surveillées.
Vous trouverez ci-dessous des exemples de la même fonction, avant et après avoir été accroché par le produit EDR :
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
Les hooks de l'espace utilisateur ont la « faiblesse » d'être localisés dans la mémoire de l'espace utilisateur, ce qui signifie qu'ils sont directement observables et modifiables par le processus examiné. Pour détecter automatiquement les hooks dans l'espace d'adressage du processus, l'idée principale est de comparer les différences entre la DLL d'origine sur le disque et la bibliothèque résidant en mémoire, qui a été potentiellement altérée par un EDR. Pour effectuer cette comparaison, les étapes suivantes sont suivies par EDRSandblast :
InLoadOrderModuleList
situé dans le PEB
(pour éviter d'appeler une API qui pourrait être surveillée et suspecte) Remarque : Le processus peut être généralisé pour trouver des différences n'importe où dans les sections non inscriptibles et pas seulement au début des fonctions exportées, par exemple si les produits EDR commencent à appliquer des hooks en milieu de fonction :) Ainsi non utilisé par l'outil, ce a été implémenté dans findDiffsInNonWritableSections
.
Afin de contourner la surveillance effectuée par ces hooks, plusieurs techniques sont possibles, et chacune présente des avantages et des inconvénients.
La méthode la plus intuitive pour contourner la surveillance basée sur les hooks consiste à supprimer les hooks. Puisque les hooks sont présents dans une mémoire accessible par le processus lui-même, pour supprimer un hook, le processus peut simplement :
Cette approche est assez simple et peut être utilisée pour supprimer tous les hooks détectés d’un seul coup. Réalisé par un outil offensif à ses débuts, cela permet au reste du code d'ignorer complètement le mécanisme de hooking et de s'exécuter normalement sans être surveillé.
Cependant, il présente deux inconvénients principaux. L'EDR surveille probablement l'utilisation de NtProtectVirtualMemory
, donc l'utiliser pour modifier les autorisations de la page sur laquelle les hooks ont été installés est (au moins conceptuellement) une mauvaise idée. De plus, si un thread est exécuté par l'EDR et vérifie périodiquement l'intégrité des hooks, cela pourrait également déclencher une détection.
Pour les détails d'implémentation, vérifiez le chemin du code de la fonction unhook()
lorsque unhook_method
est UNHOOK_WITH_NTPROTECTVIRTUALMEMORY
.
Remarque importante : par souci de simplicité, cette technique est implémentée dans EDRSandblast comme technique de base utilisée pour présenter les autres techniques de contournement ; chacun d'eux montre comment obtenir une version non surveillée de NtProtectVirtualMemory
, mais effectue ensuite la même opération (décrochage d'un hook spécifique).
Pour contourner un hook spécifique, il est possible de simplement "sauter par-dessus" et d'exécuter le reste de la fonction tel quel. Tout d'abord, les octets d'origine de la fonction surveillée, qui ont été écrasés par l'EDR pour installer le hook, doivent être récupérés à partir du fichier DLL. Dans notre exemple de code précédent, il s'agirait des octets correspondant aux instructions suivantes :
mov r10 , rcx
mov eax , 50h
L'identification de ces octets est une tâche simple puisque nous sommes capables d'effectuer une comparaison propre des versions mémoire et disque de la bibliothèque, comme décrit précédemment. Ensuite, nous assemblons une instruction de saut construite pour rediriger le flux de contrôle vers le code suivant immédiatement le hook, à l'adresse NtProtectVirtualMemory + sizeof(overwritten_instructions)
jmp NtProtectVirtualMemory + 8
Enfin, nous concaténons ces opcodes, les stockons dans la mémoire (nouvellement) exécutable et gardons un pointeur vers eux. Cet objet est appelé « trampoline » et peut alors être utilisé comme pointeur de fonction, strictement équivalent à la fonction NtProtectVirtualMemory
originale.
Le principal avantage de cette technique, comme pour toutes les techniques ci-dessous, est que le crochet n'est jamais effacé, donc tout contrôle d'intégrité effectué sur les crochets par l'EDR devrait réussir. Cependant, cela nécessite d'allouer de la mémoire inscriptible puis exécutable, ce qui est typique d'une allocation de shellcode, attirant ainsi l'attention de l'EDR.
Pour les détails d'implémentation, vérifiez le chemin du code de la fonction unhook()
lorsque unhook_method
est UNHOOK_WITH_INHOUSE_NTPROTECTVIRTUALMEMORY_TRAMPOLINE
. N'oubliez pas que la technique n'est présentée que dans notre implémentation et qu'elle est, en fin de compte, utilisée pour supprimer les hooks de la mémoire, comme chaque technique ci-dessous.
Le produit EDR, pour que son hook fonctionne, doit enregistrer quelque part en mémoire les opcodes qu'il a supprimés. Le pire ( ou "meilleur", du point de vue de l'attaquant ), pour utiliser efficacement les instructions d'origine, l'EDR s'est probablement alloué un trampoline quelque part pour exécuter la fonction d'origine après avoir intercepté l'appel.
Ce trampoline peut être recherché et utilisé en remplacement de la fonction hookée, sans qu'il soit nécessaire d'allouer de la mémoire exécutable ou d'appeler une API à l'exception VirtualQuery
, qui n'est probablement pas surveillée étant donné qu'elle est une fonction inoffensive.
Pour trouver le trampoline en mémoire, nous parcourons tout l'espace d'adressage à l'aide VirtualQuery
à la recherche de mémoire validée et exécutable. Pour chacune de ces régions de mémoire, nous l'analysons pour rechercher une instruction de saut qui cible l'adresse suivant les instructions écrasées ( NtProtectVirtualMemory+8
dans notre exemple précédent). Le trampoline peut alors être utilisé pour appeler la fonction hookée sans déclencher le hook.
Cette technique fonctionne étonnamment bien puisqu'elle récupère presque tous les trampolines sur les EDR testés. Pour les détails d'implémentation, vérifiez le chemin du code de la fonction unhook()
lorsque unhook_method
est UNHOOK_WITH_EDR_NTPROTECTVIRTUALMEMORY_TRAMPOLINE
.
Une autre méthode simple pour accéder à une version non surveillée de la fonction NtProtectVirtualMemory
consiste à charger une version en double de la bibliothèque ntdll.dll
dans l'espace d'adressage du processus. Étant donné que deux DLL identiques peuvent être chargées dans le même processus, à condition qu'elles portent des noms différents, nous pouvons simplement copier le fichier ntdll.dll
légitime dans un autre emplacement, le charger à l'aide LoadLibrary
(ou réimplémenter le processus de chargement) et accéder à la fonction à l'aide de GetProcAddress
Par exemple.
Cette technique est très simple à comprendre et à mettre en œuvre, et a de bonnes chances de succès, car la plupart des produits EDR ne réinstallent pas les hooks sur les DLL nouvellement chargées une fois le processus en cours d'exécution. Cependant, l'inconvénient majeur est que la copie de fichiers binaires signés par Microsoft sous un nom différent est souvent considérée comme suspecte par les produits EDR comme elle-même.
Cette technique est néanmoins implémentée dans EDRSandblast
. Pour les détails d'implémentation, vérifiez le chemin du code de la fonction unhook()
lorsque unhook_method
est UNHOOK_WITH_DUPLICATE_NTPROTECTVIRTUALMEMORY
.
Afin d'utiliser les fonctions liées aux appels système, un programme peut réimplémenter les appels système (en assembly) afin d'appeler les fonctionnalités correspondantes du système d'exploitation sans réellement toucher au code dans ntdll.dll
, qui pourrait être surveillé par l'EDR. Cela contourne complètement tout accrochage utilisateur effectué sur les fonctions syscall dans ntdll.dll
.
Cela présente néanmoins quelques inconvénients. Premièrement, cela implique de pouvoir connaître la liste des numéros d'appel système des fonctions dont le programme a besoin, qui change pour chaque version de Windows. Ceci est néanmoins atténué par l'implémentation de plusieurs heuristiques connues pour fonctionner dans toutes les versions précédentes de Windows NT (tri des exportations Zw*
de ntdll
, recherche de mov rax, #syscall_number
dans la fonction ntdll
associée, etc.), et vérifier qu'ils renvoient tous le même résultat (voir Syscalls.c
pour plus de détails).
De plus, les fonctions qui ne sont pas techniquement des appels système (par exemple LoadLibraryX
/ LdrLoadDLL
) pourraient également être surveillées et ne peuvent pas simplement être réimplémentées à l'aide d'un appel système.
La technique des appels système directs est implémentée dans EDRSandblast. Comme indiqué précédemment, il est uniquement utilisé pour exécuter NtProtectVirtualMemory
en toute sécurité et supprimer tous les hooks détectés.
Pour les détails d'implémentation, vérifiez le chemin du code de la fonction unhook()
lorsque unhook_method
est UNHOOK_WITH_DIRECT_SYSCALL
.
Comme indiqué précédemment, chaque action nécessitant une lecture ou une écriture dans la mémoire du noyau s'appuie sur un pilote vulnérable pour fournir cette primitive. Dans EDRSanblast, l'ajout du support d'un nouveau pilote fournissant la primitive de lecture/écriture peut être effectué « facilement », seules trois fonctions doivent être implémentées :
ReadMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)
, qui copie les octets Size
de Address
du noyau Address vers le tampon de l'espace utilisateur Buffer
;WriteMemoryPrimitive_DRIVERNAME(SIZE_T Size, DWORD64 Address, PVOID Buffer)
, qui copie les octets Size
du tampon Buffer
de l'espace utilisateur vers Address
du noyau Address ;CloseDriverHandle_DRIVERNAME()
qui garantit que tous les handles du pilote sont fermés (nécessaire avant l'opération de désinstallation qui est indépendante du pilote, pour le moment). À titre d'exemple, deux pilotes sont actuellement pris en charge par EDRSandblast, RTCore64.sys
(SHA256 : 01AA278B07B58DC46C84BD0B1B5C8E9EE4E62EA0BF7A695862444AF32E87F1FD
) et DBUtils_2_3.sys
(SHA256 : 0296e2ce999e67c76352613a718e11516fe1b0efc3ffdb8918fc999dd76a73a5
). Le code suivant dans KernelMemoryPrimitives.h
doit être mis à jour si le pilote vulnérable utilisé doit être modifié ou si un nouveau pilote est implémenté.
#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
Plusieurs techniques sont actuellement utilisées pour déterminer si un pilote ou un processus spécifique appartient ou non à un produit EDR.
Premièrement, le nom du conducteur peut simplement être utilisé à cette fin. En effet, Microsoft attribue des numéros spécifiques appelés « Altitudes » à tous les pilotes devant insérer des rappels dans le noyau. Cela permet un ordre déterministe dans l'exécution des rappels, indépendant de l'ordre d'enregistrement, mais uniquement basé sur l'utilisation du pilote. Une liste des (fournisseurs de) pilotes ayant réservé une altitude spécifique peut être trouvée sur MSDN. En conséquence, une liste presque complète de noms de pilotes de sécurité liés aux produits de sécurité est proposée par Microsoft, principalement dans les listes « FSFilter Anti-Virus » et « FSFilter Activity Monitor ». Ces listes de noms de pilotes sont intégrées dans EDRSandblast, ainsi que des contributions supplémentaires.
De plus, les exécutables EDR et les DLL sont le plus souvent signés numériquement à l'aide du certificat de signature du fournisseur. Ainsi, vérifier le signataire d'un exécutable ou d'une DLL associée à un processus peut permettre d'identifier rapidement les produits EDR.
De plus, les pilotes doivent être directement signés par Microsoft pour pouvoir être chargés dans l'espace du noyau. Bien que le fournisseur du pilote ne soit pas directement le signataire du pilote lui-même, il semblerait que le nom du fournisseur soit toujours inclus dans un attribut de la signature ; cette technique de détection reste néanmoins à étudier et à mettre en œuvre.
Enfin, face à un EDR inconnu d'EDRSandblast, la meilleure approche consiste à exécuter l'outil en mode « audit » et à vérifier la liste des pilotes ayant des rappels de noyau enregistrés ; le nom du pilote peut alors être ajouté à la liste, l'outil recompilé et réexécuté.
Le mécanisme Local Security Authority (LSA) Protection
, introduit pour la première fois dans Windows 8.1 et Windows Server 2012 R2, exploite la technologie Protected Process Light (PPL)
pour restreindre l'accès au processus LSASS
. La protection PPL
régule et restreint les opérations, telles que l'injection de mémoire ou le vidage de mémoire de processus protégés, même à partir d'un processus détenant le privilège SeDebugPrivilege
. Dans le cadre du modèle de protection des processus, seuls les processus exécutés avec des niveaux de protection plus élevés peuvent effectuer des opérations sur les processus protégés.
La structure _EPROCESS
, utilisée par le noyau Windows pour représenter un processus dans la mémoire du noyau, comprend un champ _PS_PROTECTION
définissant le niveau de protection d'un processus via ses attributs Type
( _PS_PROTECTED_TYPE
) et Signer
( _PS_PROTECTED_SIGNER
).
En écrivant dans la mémoire du noyau, le processus EDRSandblast est capable de mettre à niveau son propre niveau de protection vers PsProtectedSignerWinTcb-Light
. Ce niveau est suffisant pour vider la mémoire du processus LSASS
, puisqu'il « domine » vers PsProtectedSignerLsa-Light
, le niveau de protection du processus LSASS
exécuté avec le mécanisme RunAsPPL
.
EDRSandBlast
implémente l'autoprotection comme suit :
NtQuerySystemInformation
pour trouver le handle ouvert sur le processus en cours et l’adresse de la structure EPROCESS
du processus actuel dans la mémoire du noyau.Micro-Star MSI Afterburner
pour écraser le champ _PS_PROTECTION
du processus actuel dans la mémoire du noyau. Les offsets du champ _PS_PROTECTION
par rapport à la structure EPROCESS
(définis par la version ntoskrnl
utilisée) sont calculés dans le fichier NtoskrnlOffsets.csv
. Microsoft Credential Guard
est une technologie d'isolation basée sur la virtualisation, introduite dans Windows 10 (Enterprise edition)
de Microsoft qui empêche l'accès direct aux informations d'identification stockées dans le processus LSASS
.
Lorsque Credentials Guard
est activé, un processus LSAIso
( LSA Isolé ) est créé en Virtual Secure Mode
, une fonctionnalité qui exploite les extensions de virtualisation du processeur pour fournir une sécurité supplémentaire des données en mémoire. Les accès au processus LSAIso
sont restreints même pour un accès avec le contexte de sécurité NT AUTHORITYSYSTEM
. Lors du traitement d'un hachage, le processus LSA
effectue un appel RPC
au processus LSAIso
et attend que le résultat LSAIso
continue. Ainsi, le processus LSASS
ne contiendra aucun secret et stockera en place LSA Isolated Data
.
Comme indiqué dans la recherche originale menée par N4kedTurtle
: " Wdigest
peut être activé sur un système avec Credential Guard en corrigeant les valeurs de g_fParameter_useLogonCredential
et g_IsCredGuardEnabled
en mémoire". L'activation de Wdigest
entraînera le stockage des informations d'identification en texte clair dans la mémoire LSASS
pour toute nouvelle connexion interactive (sans nécessiter un redémarrage du système). Reportez-vous au billet de blog de recherche original pour plus de détails sur cette technique.
EDRSandBlast
rend simplement le PoC d'origine un peu plus convivial pour l'opsec et prend en charge un certain nombre de versions wdigest.dll
(via des décalages calculés pour g_fParameter_useLogonCredential
et g_IsCredGuardEnabled
).
Afin d'effectuer de manière fiable les opérations de contournement de la surveillance du noyau, EDRSandblast doit savoir exactement où lire et écrire la mémoire du noyau. Cela se fait en utilisant les décalages de variables globales à l'intérieur de l'image ciblée (ntoskrnl.exe, wdigest.dll), ainsi que le décalage de champs spécifiques dans les structures dont les définitions sont publiées par Microsoft dans des fichiers de symboles. Ces offsets sont spécifiques à chaque build des images ciblées, et doivent être collectés au moins une fois pour une version spécifique de la plateforme.
Le choix d'utiliser des offsets "codés en dur" au lieu de recherches de modèles pour localiser les structures et variables utilisées par EDRSandblast est justifié par le fait que les API non documentées responsables de l'ajout/suppression des rappels du noyau sont susceptibles de changer et que toute tentative de lecture ou d'écriture du noyau une mémoire à la mauvaise adresse peut (et entraînera souvent) une Bug Check
( Blue Screen of Death
). Un crash de machine n'est pas acceptable dans les scénarios d'équipe rouge et de tests d'intrusion normaux, car une machine qui plante est très visible par les défenseurs et perdra toutes les informations d'identification qui étaient encore en mémoire au moment de l'attaque.
Pour récupérer les offsets pour chaque version spécifique de Windows, deux approches sont implémentées.
Les décalages ntoskrnl.exe
et wdigest.dll
requis peuvent être extraits à l'aide du script Python ExtractOffsets.py
fourni, qui s'appuie sur radare2
et r2pipe
pour télécharger et analyser les symboles des fichiers PDB, et en extraire les décalages nécessaires. Les décalages sont ensuite stockés dans des fichiers CSV pour une utilisation ultérieure par EDRSandblast.
Afin de prendre en charge une large gamme de versions Windows prêtes à l'emploi, de nombreuses versions des binaires ntoskrnl.exe
et wdigest.dll
sont référencées par Winbindex et peuvent être automatiquement téléchargées (et leurs décalages extraits) par ExtractOffsets.py
. Cela permet d'extraire les décalages de presque tous les fichiers jamais publiés dans les packages de mise à jour Windows (à ce jour, plus de 450 versions ntoskrnl.exe
et plus de 30 wdigest.dll
sont disponibles et pré-calculées).
Une option supplémentaire a été implémentée dans EDRSandBlast
pour permettre au programme de télécharger lui-même les fichiers .pdb
nécessaires à partir de Microsoft Symbol Server, d'extraire les décalages requis et même de mettre à jour les fichiers .csv
correspondants s'ils sont présents.
L'utilisation de l'option --internet
rend l'exécution de l'outil beaucoup plus simple, tout en introduisant un risque OpSec supplémentaire, puisqu'un fichier .pdb
est téléchargé et déposé sur le disque pendant le processus. Ceci est requis par les fonctions dbghelp.dll
utilisées pour analyser la base de données de symboles ; cependant, une analyse PDB entièrement en mémoire pourrait être mise en œuvre à l'avenir pour lever cette exigence et réduire l'empreinte de l'outil.
Le pilote RTCore64.sys
vulnérable peut être récupéré à l'adresse :
http://download-eu2.guru3d.com/afterburner/%5BGuru3D.com%5D-MSIAfterburnerSetup462Beta2.zip