<index> / <windows-internals> / etw-patching
[ en | fr ]
┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
ETW Patching
~ quentiin19
É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 ]──
flowchart LR
    P["Provider ETW\n(OS, drivers, apps)"]
    K["Noyau ETW\n(ntoskrnl / ntdll)"]
    S["Session de tracing\n(logman, WPR…)"]
    C["Consommateur ETW\n(EDR, WEF, Sysmon…)"]

    P -->|"EtwEventWrite / EtwWrite"| K
    K -->|"ring buffer"| S
    S -->|"ETL / temps réel"| C
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.
sequenceDiagram
    participant A as Attaquant (Ring 3)
    participant V as Driver vulnérable (Ring 0)
    participant K as ntoskrnl

    A->>V: IOCTL — scan du GUID EtwTi
    V->>K: lecture mémoire noyau
    K-->>V: adresse _ETW_REG_ENTRY
    V-->>A: adresse retournée

    A->>V: IOCTL — écrire 0xC3 sur EnableCallback
    V->>K: patch du callback
    Note over K: EtwTi désormais silencieux
É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