Écrit par quentiin19 — https://github.com/quentiin19
SOMMAIRE
0 qu'est-ce que etw
1 architecture etw
2 comment les edr utilisent etw — le provider ti
3 patch userland — etwEventWrite
4 désactivation du trace par processus
5 patch en mode noyau — provider etwti
6 détection
──[ 0. Qu'est-ce que ETW ]──
ETW (Event Tracing for Windows) est un framework de tracing haute performance,
en mode noyau, présent dans Windows depuis XP. À l'origine pour le diagnostic et
le monitoring de perf, mais les produits de sécurité s'appuient maintenant
largement dessus pour la détection comportementale.
Plus rapide et bien moins coûteux que du polling ou du logging fichier. Les
events sont écrits direct dans des buffers mémoire partagés et flushés vers les
consommateurs uniquement quand le buffer est plein ou qu'un timeout expire —
aucune I/O disque dans le chemin chaud.
──[ 1. Architecture ETW ]──
Trois acteurs :
Provider — tout code qui émet des events. Chaque provider a un GUID.
Ex : Microsoft-Windows-Kernel-Process,
Microsoft-Windows-Threat-Intelligence.
Session — objet noyau qui reçoit les events d'un ou plusieurs
providers et les route vers les consommateurs. Créée
avec StartTrace() / EnableTraceEx2().
Consommateur — lit les events en temps réel ou depuis un .etl. Les
EDR ouvrent typiquement une session temps réel avec
OpenTrace() + ProcessTrace().
──[ 2. Comment les EDR utilisent ETW — Le provider TI ]──
Le provider ETW le plus intéressant côté sécurité :
Microsoft-Windows-Threat-Intelligence
GUID: {F4E1897C-BB5D-5668-F1D8-040F4D8DD344}
Aussi appelé provider EtwTi. Tourne entièrement dans le kernel et émet des
events pour :
Allocation mémoire exécutable VirtualAlloc(PAGE_EXECUTE_*)
Écritures mémoire distantes WriteProcessMemory
Création de thread distant CreateRemoteThread
Duplication de handle DuplicateHandle sur des processus
Accès à LSASS OpenProcess sur lsass.exe
Opérations sur objets noyau chargement de drivers, écritures registre
EtwTi est en mode kernel, donc impossible de le faire taire depuis userland en
patchant ntdll.dll. Il faut du Ring 0 pour le toucher.
Defender ATP (MDE), CrowdStrike Falcon, et la plupart des EDR entreprise
consomment EtwTi comme signal de détection principal.
──[ 3. Patch userland — EtwEventWrite ]──
Chaque event ETW user-mode finit par appeler EtwEventWrite (ou son alias
NtTraceEvent) dans ntdll.dll. Tu patches la fonction pour qu'elle retourne
immédiatement, et le processus courant arrête d'émettre des events ETW
user-mode.
// Charge ntdll, localise EtwEventWrite
HMODULE ntdll = GetModuleHandleA("ntdll.dll");
FARPROC target = GetProcAddress(ntdll, "EtwEventWrite");
// Patch : écrase le premier octet avec 0xC3 (RET)
DWORD old;
VirtualProtect(target, 1, PAGE_EXECUTE_READWRITE, &old);
*(BYTE*)target = 0xC3;
VirtualProtect(target, 1, old, &old);
Effet : chaque appel à EtwEventWrite dans ce processus retourne silencieusement.
Les providers qui appellent EtwEventWrite direct (PowerShell, runtime .NET, hôte
WMI, etc.) se taisent.
Limite : ça n'affecte que le processus courant, et uniquement les providers
user-mode. Les providers kernel-mode (EtwTi) continuent intacts.
Alternative : patcher EtwEventWriteFull ou NtTraceEvent pour couvrir plus de
chemins d'appel internes à ntdll.
──[ 4. Désactivation du trace par processus ]──
Plus ciblé : on triture la structure d'enregistrement ETW dans le processus
courant pour désactiver le tracing d'un GUID de provider précis, sans patcher de
code exécutable.
Chaque provider enregistré dans un processus est suivi par un REGHANDLE
(retourné par EventRegister). Le handle pointe vers une structure interne
_ETW_REG_ENTRY. Tu mets le flag de tracing à désactivé et plus rien n'est écrit.
Via l'API publique (besoin du handle de session — généralement inaccessible) :
// Conceptuellement : désactive le provider Threat-Intelligence
// dans la session courante
EnableTraceEx2(sessionHandle, &TIproviderGUID,
EVENT_CONTROL_CODE_DISABLE_PROVIDER, 0, 0, 0, 0, NULL);
En pratique le handle de session est rarement entre tes mains. La méthode
habituelle : trouver le REGHANDLE du provider dans ntdll!EtwpRegistrationTable
et remettre direct le flag IsEnabled à zéro.
──[ 5. Patch en mode noyau — Provider EtwTi ]──
Les events EtwTi viennent de fonctions callback enregistrées dans ntoskrnl. Les
patcher demande du write Ring 0 — en pratique, via BYOVD.
La technique (utilisée dans des outils comme FireWalker et PoC similaires) :
Étape 1 — Localiser l'enregistrement EtwTi
Le provider EtwTi est enregistré dans le kernel via EtwRegister(). Le
_ETW_REG_ENTRY résultant est chaîné dans une liste globale de
providers dans ntoskrnl. Pattern-scan du GUID du provider pour
trouver l'entrée.
Étape 2 — Trouver le callback
_ETW_REG_ENTRY contient un pointeur vers la fonction EnableCallback —
celle invoquée quand un consommateur active le provider.
struct _ETW_REG_ENTRY {
...
PLIST_ENTRY Links;
LPGUID ProviderId;
...
PETWENABLECALLBACK EnableCallback;
...
};
Étape 3 — Patcher le callback
Écrase le pointeur EnableCallback avec un stub qui retourne direct,
ou patche les premiers octets de la fonction callback elle-même
avec un RET :
// Via une primitive d'écriture noyau BYOVD :
KernelWrite(enableCallbackAddr, "\xC3", 1); // RET
Maintenant les consommateurs peuvent toujours « activer » EtwTi mais
le callback ne se déclenche jamais. Aucun event ne sort. Le provider
est aveugle pour toute la session de boot.
Étape 4 — Faire taire les callbacks d'event individuels
Certaines implémentations vont plus loin et patchent les fonctions
individuelles d'écriture d'event (EtwWriteEx, EtwpWriteUserEvent)
pour couvrir les cas où l'approche callback global rate certains
types d'event.
──[ 6. Détection ]──
Le patching ETW est dur à voir depuis un processus patché (c'est le but), mais
côté défense il y a des options :
Scan d'intégrité depuis un processus séparé
Un processus de confiance peut lire les premiers octets de
ntdll!EtwEventWrite dans la mémoire du processus cible via
ReadProcessMemory et comparer avec la copie disque. Un 0xC3 en tête,
c'est un signal fort.
// Similaire à la détection de hook inline (voir article DLL Hooking)
ReadProcessMemory(hTarget, etwEventWriteAddr, buf, 5, NULL);
if (buf[0] == 0xC3) { /* patché */ }
Vérifs de santé des providers ETW
Microsoft-Windows-Kernel-Audit-API-Calls (entre autres) peut être
monitoré par un consommateur out-of-process. Si le provider devient
silencieux alors que le processus tourne encore, quelque chose a
été patché.
Intégrité noyau (callback EtwTi)
HVCI (Hypervisor-Protected Code Integrity) bloque l'écriture sur les
pages de code noyau, ce qui tue l'étape 3 sur les systèmes modernes
avec HVCI activé. Bloque aussi le chargement des modules noyau non
signés — tue la plupart des BYOVD.
PatchGuard (KPP)
KernelPatchGuard vérifie périodiquement le code et les structures
noyau. Patcher le pointeur de callback EtwTi peut déclencher un BSOD
(CRITICAL_STRUCTURE_CORRUPTION, bugcheck 0x109) si PatchGuard scanne
la région avant que tu aies fini.
Portée du patch vs. complexité :
Patch EtwEventWrite Ring 3 facile providers user-mode seulement
Désactivation par processus Ring 3 moyen par provider, sans patch code
Patch callback EtwTi Ring 0 difficile télémétrie noyau aveugle