Understanding AMSI
There are a few AMSI components we should discussed.
This figure shows a simplified overview of an AMSI implementation and how it
interacts with an antivirus product, which in our case is Windows Defender.
The AMSI unmanaged dynamic link library,
is loaded into every PowerShell and PowerShell_ISE process
& provides a number of exported functions that PowerShell takes advantage of.
Let’s cover each of these in detail.
Relevant information captured by these APIs is forwarded to Windows Defender
through an interprocess Remote Procedure Call or RPC.
After Windows Defender analyzes the data,
the result is sent back to AMSI.DLL inside the PowerShell process.
The AMSI exported APIs include,
AMSI Initialize,
OpenSession,
ScanString,
ScanBuffer,
and CloseSession
Since these functions have been documented by Microsoft,
we’re able to read that documentation to better understand
the intricacies of the capture process.
Let’s step through that process.
When PowerShell is launched, it loads AMSI.DLL and calls AmsiInitialize,
which takes two arguments.
The first parameter is the name of the application,
and the second is a pointer to a context structure
that is populated by the function.
This context structure is used in every subsequent AMSI-related function.
Note that the call to AmsiInitialize takes place before we’re able to invoke
any PowerShell commands, which means we can’t influence it in any way.
Once AmsiInitialize is complete and the context structure is created,
AMSI can parse the issued commands.
When we execute a PowerShell command, the AmsiOpenSession API is called.
This API accepts the amsiContext structure,
and creates a session structure to be used in all calls within that session.
This leads to the next two APIs that perform the actual captures.
AmsiScanString and AmsiScanBuffer can both be used to capture the console input
or script content either as a string or as a binary buffer respectively.
Note that AmsiScanBuffer supersedes AmsiScanString,
which was vulnerable to a trivial bypass technique
AmsiScanBuffer accepts a few more arguments
as shown in its function prototype.
The first argument is the AMSI context buffer,
followed by a pointer to the buffer containing the content to be scanned,
and the length of the buffer.
The next arguments are an input identifier,
the session structure,
and finally a pointer to a storage buffer for the result of the scan.
Windows Defender scans the buffer passed to AmsiScanBuffer
and returns the result value.
This value is defined according to the AMSI_RESULT enum
A return value of “32768” indicates the presence of malware,
and “1” indicates a clean scan.
Once the scan is complete,
calling AmsiCloseSession will close the current AMSI scanning session.
This function is not that important to us since it takes place after the result
of the scan and AMSI bypasses must happen before it is called.
Armed with this basic understanding of the AMSI mechanisms and APIs,
let’s trace calls to these APIs to learn what, exactly,
is passed in the buffer to AmsiScanBuffer.