Between Windows 11 and Windows 10 AMSI bypass - Corrupting structures and variables
Published:
Disabling the Antimalware Scan Interface (AMSI) by locating and corrupting vital structures and variables for both Windows 11 and Windows 10.
Whaaaat!
In recent years, many (not all) antivirus products have begun to rely on Antimalware Scan Interface (AMSI) to detect more advanced malicious activity. This means, the content passed by AMSI is only analyzed if one of those antivirus products is installed.
To protect against the increased malicious PowerShell scripts, Microsoft introduced the Antimalware Scan Interface (AMSI) in 2015 with the release of Windows 10. The Antimalware Scan Interface captures every (all) PowerShell, Jscript, VBScript, VBA (added in MS Office 2019), or .NET (added in .NET framework 4.8) command or script at run-time and passes it to the Microsoft’s own Windows Defender antivirus or any other local antivirus products for inspection.
Let’s see how AMSI interacts with the local antivirus product, which in this case is Windows Defender.
The amsi.dll which is an unmanaged dynamic link library is loaded into every PowerShell (32-bit/64-bit) and PowerShell_ISE (32-bit/64-bit) process. The amsi.dll provides several APIs that PowerShell/PowerShell_ISE takes advantage of. The information (PowerShell commands or scripts) captured by these APIs is forwarded to Windows Defender through the Remote Procedure Call (RPC). After the local antivirus product (Windows Defender in this case) analyzes the captured data, the result is sent back to amsi.dll inside the PowerShell process.
Antimalware Scan Interface is essentially a set of APIs that allow antivirus products to scan PowerShell, Jscript, VBScript, VBA or .NET commands or scripts when they are executed, of course, even if they are never written to disk.
AMSI APIs
The AMSI exported APIs include AmsiInitialize
, AmsiOpenSession
, AmsiScanString
, AmsiScanBuffer
, and AmsiCloseSession
.
When PowerShell is launched, it loads amsi.dll and calls AmsiInitialize
before we can invoke any commands, which means we cannot influence AmsiInitialize
in any way. The AmsiInitialize
is the API responsible for populating the Context Structure, which is used in every subsequent AMSI-related APIs.
Once the AmsiInitialize
is done and the Context Structure is created, AMSI can now parse the typed commands. The moment we execute a PowerShell command, the AmsiOpenSession
is called. The AmsiOpenSession
is the API responsible for accepting the Context Structure created by calling AmsiInitialize
and creating a Session Structure to be used in all calls within that session.
Then, we have the AmsiScanString
and AmsiScanBuffer
, which are the ones responsible for the actual capture of command or script content either as a string or as a binary buffer respectively. The Windows Defender scans the string or buffer passed to AmsiScanString
and AmsiScanBuffer
respectively and returns the result value. A return value of “1” indicates a clean scan, and “32768” indicates the presence of malware.
Finally, a calling to AmsiCloseSession
, once the scan is complete. The AmsiCloseSession
is the API responsible for closing the current AMSI scanning session.
Let’s hook the AMSI APIs using Frida and see the AMSI process in practice:
The issued command is “cd” as you can you see in the buffer parameter, which is the one that containing the content to be scanned. Windows Defender scans the buffer passed to AmsiScanBuffer
and returns the result value. The result value is “1” which indicates that the “cd” command is clean. However, the scanned “AmsiUtils” returns a value of “32768”, indicating that AMSI has flagged “AmsiUtils” as malicious, and there is no doubt that the warning we received in PowerShell prompt came from Windows Defender (in our case):
Splitting the “AmsiUtils” in two strings and concatenating them, will return the result of “1” indicating that AMSI has flagged it as non-malicious:
We don’t know why AMSI has flagged “AmsiUtils” as malicious, yet. However, we easily bypassed it by splitting and concatenating the string.
Wait! Where is the AmsiScanString
? Microsoft has stopped using the AmsiScanString which was vulnerable to a trivial bypass technique and is now using AmsiScanBuffer instead.
Windows 10 AMSI Bypasses
amsiContext and Forcing an Error
We said that the Context Structure that is created by calling AmsiInitialize
, is found to be used in all the AMSI APIs. If we can force some sort of error in this undocumented structure, we may be able to discover a way to bypass AMSI:
From the above screenshot, we can see the memory address of the Context Structure. Let’s see its content with the help of WinDbg:
As the Context Structure is undocumented, we don’t know the size of it, but we found that the first four bytes equate to the ASCII representation of “AMSI”. The real question, is this string static between processes? To confirm this, we’ll launch another PowerShell prompt and use Frida to interact with it and hook the AMSI APIs. After that we’ll re-read the content at the memory address of amsiContext
:
From the above screenshots, we can confirm that the string “AMSI” is static between processes. Next thing is to see the Context Structure in action in the AMSI APIs, this way we can determine if the string “AMSI” is referenced in any way:
The fourth line of assembly code is comparing the content of RCX
register, which is the one contains the function’s first argument, to the four static bytes “49534D41”. The first argument of AmsiOpenSession
is exactly the Context Structure according to its function prototype, which means that a comparison is performed to check the header of the buffer.
The fifth line of assembly code is jne
(jump if not equal), which means if the header bytes are not equal to this static DWORD - “AMSI”, a conditional jump to an exit of the function is triggered:
The static value 0x80070057
corresponds to the message text E_INVALIDARG. The message text indicates that an argument – amsiContext
– is invalid. So, this error occurs if the Context Structure has been corrupted.
Let’s try to enforce this error and see if we can bypass the Antimalware Scan Interface. To do that, we’ll try to modify the first four bytes of the Context Structure:
We have effectively bypassed AMSI by corrupting the amsiContext
header.
Instead WinDbg, we’ll try to use PowerShell to interact with System.Management.Automation.AmsiUtils class as it the one that stores information about AMSI, corrupting the Context Structure and helpfully bypassing AMSI:
Very nice! AMSI bypassed.
Converting the very large number “1960613452080” to hexadecimal produces 0x1C87DAB1930. Which is the memory address of the amsiContext
buffer. By overwriting the amsiContext
header with zeros, we were able to bypass the Antimalware Scan Interface:
amsiInitFailed and Forcing an Error
We can use another technique to bypass the Antimalware Scan Interface, which is based on the manipulation of a result variable set by AmsiInitialize
through the amsiInitFailed
(true/false). This field is verified by AmsiOpenSession
in the same manner as the amsiContext
header, which leads to an error:
As we can see, by setting the value to true we were able to bypass AMSI. However, the AMSI start working once again when we set the value to false.
Windows 11 AMSI Bypasses
With the release of Windows 11 in 2021, a lot of things changed but is AMSI protection changed as well, or our techniques are still valid? Let’s try to bypass AMSI by corrupting the Context Structure from PowerShell:
This is not working anymore, but why? To know why, we need to see the Context Structure in action in the AMSI APIs once again:
There is no check on the header of the buffer to be perform as we saw on Windows 10. That’s why our method to bypass the AMSI is not working anymore. However, enforcing an error using the amsiInitFailed
is still working:
The techniques that we discussed here are tested on fully updated Windows 10 and Windows 11 systems.
In summary, the discussed techniques are known by security professional for years, but still can be used with a small modifications.