Tradecraft Improvement 3 - AMSI Evasions 1 - Reverse Engineering AMSI
Pre-requisites
- Basic understanding of Windows internals
- Some knowledge about PowerShell
- Basic knowledge about malwares
- Basic C/C++ knowledge
Introduction to AMSI
There are already a number of blogs and sources dedicated to the evasion of Microsoft’s Antimalware Scan Interface aka AMSI. But I wanted to write a blog which would encompass certain important tips and tricks that would help you improve your own bypasses and would give you some knowledge and understanding of AMSI to look for more bypasses as well. I will divide this blog into 2 or more parts so that it helps you later on during your pentesting / Red Team engagements. I will also post my code and scripts for your reference in my GitHub
Coming back to AMSI, it was introduced by Microsoft to protect against the malicious usage of scripting languages like PowerShell, VBScript or JScript. This can also be used by AV engines, Microsoft Defender or even other developers to examine various .NET scripts that are executed.
However, with recent developments to AMSI, it is not just limited to scripting languages, but it can also be used to examine languages that are dynamically executed. This also includes applications written in C# since they use dynamic code loading or may even interact with scripting languages.
Internal Workings of AMSI
Example of AMSI working
In order to understand how AMSI bypasses work, we need to understand the internal workings of AMSI first. This will allow us to later understand where we are going to look for more AMSI bypasses. Therefore, we can make short changes to our loader scripts whenever we get flagged and keep on loading our malwares.
So, AMSI works based on signature based detection. This means that when a script is executed, it will look for certain characters or words which are already considered malicious.
As an example we can consider the following example
- If we write the string
invoke-mimikatzin a PowerShell session, we will get a message from AMSI flagging our string.

This is because the string invoke-mimikatz is already stored as malicious. Now let’s we try to execute something similar which is already flagged, it will be immediately blocked like this by AMSI. Similar thing will happen if we use Write-Host invoke-mimikatz to print out invoke-mimikatz on the output.
- Now if we use something common like a simple base64 encoding as obfuscation, we can bypass this.
function Decode-Base64String {
param (
[string]$base64String
)
try {
$decodedBytes = [Convert]::FromBase64String($base64String)
$decodedText = [System.Text.Encoding]::UTF8.GetString($decodedBytes)
return $decodedText
} catch {
Write-Error "An error occurred: $_"
return $null
}
}
# Example usage
$base64Encoded = "aW52b2tlLW1pbWlrYXR6Cg=="
$decodedText = Decode-Base64String -base64String $base64Encoded
Write-Host "Decoded Text: $decodedText"
- Here we have base64 encoded the string
invoke-mimiatzbeforehand. We will use this base64 encoded string in our PowerShell script to print out the stringinvoke-mimikatz- But before execution, we need to set the
Executionpolicy toRemote-SignedorUnrestrictedfor the sake of simplicity. There are also way to bypass this but I will discuss them later in another blog.
- But before execution, we need to set the
Run the following command in an Administrator PowerShell session
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

As we can see that our script has successfully printed the invoke-mimikatz string without AMSI printing out any alert.
Taking a look at AMSI Internals

For reference we can take a look at the above picture taken from https://learn.microsoft.com/en-us/windows/win32/amsi/how-amsi-helps
- It shows that there is an
AMSIprovider which is actually a COM (Component Object Model) which opens up aCOMinterface calledIAntimalwareProvider - This provider can be used by any
AV/EDRvendor or any program or developer to look for malicious signatures in various scripts or programs.- They can make use of the various functions within the provider such as
Scan()function
- They can make use of the various functions within the provider such as
- In order to implement AMSI, the AV vendor first needs to registry the
COMobject by creating an associatedCLSIDentry under theHKLM\CLSIDregistry key. - Then they need to registry the same
CLSIDunderComputer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\AMSI\Providers\

We can find the provider is already registered.
More references for developers about AMSI can be found here -> https://learn.microsoft.com/en-us/windows/win32/amsi/dev-audience
This might be help to someone who is looking to learn more about the defensive capabilities of AMSI and how they are being used by a certain AV or defensive program.
The amsi.dll
As shown in the reference image for AMSI, we will find that there is a library called amsi.dll is loaded in a process. This DLL can be used to directly work with AMSI.
If we take a look at the loaded modules in a PowerShell process, we will find the amsi.dll

- This will usually be our main target when trying to create bypasses for AMSI.
- This is because the function within it can be patched, hooked and manipulated pretty much under all circumstances
- We can see these functions, if we open up
amsi.dllunder PE Bear. Theamsi.dllcan be found in the folderC:\Windows\System32\amsi.dll

Here we can see all the functions that are exported by amsi.dll. Now we need to figure out which functions are mainly use by PowerShell. Since this will enable us to write bypasses for our PowerShell based loaders
Checking function calls in amsi.dll
- We will refer to the Export Table that we viewed in PE Bear and then inspect the disassembly of those functions in x64 Dbg
- Then we will attach to a PowerShell session and examine the flow of those function calls
NOTE: While testing on Windows 11, Windows Defender was flagging my x64 dbg when I tried attaching to PowerShell process. Probably because the binary wasn’t signed. For testing purposes I kept Real-time protection turned off.
- First lets attach to a PowerShell process using x64 Dbg
- Then under the
amsi.dllmodule look for theAmsiScanStringAPI

Once you attach to the PowerShell process you can view the loaded modules using the Alt+E shortcut and then search for the required modules and functions in the search bar.
- Clicking on the AmsiScanString API we find that after initializing some variables, it finally calls the AmsiScanBuffer API.

Therefore, we can put a breakpoint in the AmsiScanBuffer API call and enter some string in the PowerShell session.

Here I have added 2 breakpoints. One at the AmsiScanBuffer call and another at the first line of the AmsiScanBuffer function.
Also, added a breakpoint at the starting of AmsiScanString. This will help us understand if it is actually AmsiScanString that calls AmsiScanBuffer when we enter a string in the PowerShell session or not.

- Now enter any command in the PowerShell prompt

Upon pressing enter we will notice that instead of hitting the start of AmsiScanString or even the call from AmsiScanString we directly enter AmsiScanBuffer start
Taking a quick look at the disassembly of AmsiScanBuffer we will find our string being passed to it and some checks are run on it. We will later on go into details using IDA while trying to find more info about exploitable patterns.

However, we still need to find out which function is actually calling AmsiScanBuffer. For this we will place a breakpoint on some more functions in amsi.dll

Here you can see that I have added breakpoints to some common AMSI APIs. We will again start execution and check which API is hit first

As soon as we start execution, we will see that the AmsiOpenSession is called first. According to MSDN it opens up a session within which multiple scan requests can be correlated.. Looking at its definition
HRESULT AmsiOpenSession(
[in] HAMSICONTEXT amsiContext,
[out] HAMSISESSION *amsiSession
);
We will find that it takes a structure HAMSICONTEXT as an input and gives out a structure of type HAMSISESSION as an output. These structures are undocumented.
This means before any other function is called AmsiOpenSession definitely needs to get called. Continuing with the execution of the code we will find that the next function executed is AmsiScanBuffer

As we can see in the above picture our string will also be passed to AmsiScanBuffer API. Continuing through the execution, we will find that at this mainly 2 functions are being AmsiOpenSession and AmsiScanBuffer which are important to us.
Another function that we can notice is when starting the PowerShell process. Till now we were just attaching to the powershell.exe process, now we will start a powershell.exe process from the debugger itself. In that it will be debugged from the start only.
This can be done by selecting the powershell.exe file from File option from x64Dbg

- Right now the process has just be loaded and is stuck at the default breakpoint at the entry

Therefore, there are no extra modules like amsi.dll loaded at this moment.
- After continuing through the program for sometime, we will notice that
clr.dllis also loaded. This is the DLL where all the core components of .NET Framework are loaded. This will be important later on

- Now as soon as
amsi.dllshows up in the loaded modules check the breakpoints

- We will find that it has hit the AmsiInitialize function. It has the following definition
HRESULT AmsiInitialize(
[in] LPCWSTR appName,
[out] HAMSICONTEXT *amsiContext
);
appName-> is the first argument that it takes and contains the identity of the application that is calling the AMSI APIamsiContext-> of typeHAMSICONTEXTis a variable is passed to all subsequent API calls and we see this being passed toAmsiInitializeAPI as well. Therefore, it is also an important data.
Continuing through the execution we will find that the next API being called just after is AmsiOpenSession

And subsequently it will call AmsiScanBuffer until the PowerShell session is initialized.
Testing the API call Flow with API monitor
Now we will again test our theory with API Monitor . For this we will first run the API monitor program as an Administrator and then load the symbols for the external DLL amsi.dll


Once we have added all these APIs, we will start a new PowerShell process

- As soon as we launch a new PowerShell process we will find the APIs that are used to initialize AMSI

- Just like we previously analyzed,
AmsiInitializeis being called first followed byAmsiOpenSession - If we enter a malicious string like
invoke-mimikatzwe will find the APIs that are being called for scanning that

- We will also find our string being passed to
AmsiScanBuffercall

Finally it will exit out with AmsiCloseSession
Final API flow for AMSI in a PowerShell session
So the final flow of AMSI functions that will take place when a PowerShell session is loaded will look something as follows:
AmsiInitialize-> First theAmsiInitializeAPI is called to create a commonamsiContextof typeHAMSICONTEXTfor the particular AMSI API caller. This context will be used by the subsequent API calls likeAmsiOpenSessionAmsiOpenSession-> As already mentioned next API that is called will beAmsiOpenSession. This will take theamsiContextand create a newamsiSesssionwhich will then be used for a particular set of scanAmsiScanBuffer-> This API takes 6 arguments and the definition looks as follows
HRESULT AmsiScanBuffer(
[in] HAMSICONTEXT amsiContext,
[in] PVOID buffer,
[in] ULONG length,
[in] LPCWSTR contentName,
[in, optional] HAMSISESSION amsiSession,
[out] AMSI_RESULT *result
);
- First the
AmsiScanBufferwill take theamsiContextwhich was created byAmsiInitialize - Then it takes the pointer to the buffer containing our required input
lengthparameter holds the length of the buffer that we want to entercontentNamewill hold the name of the any file / script / URL that you want AMSI to scan.amsiSession-> In this case its an optional parameters but is used in most of the cases. This was created by theAmsiOpenSessionAPI.result-> Finally we have the result which is of type AMSI_RESULT enum. This helps AMSI determine whether or not the content in the buffer should be blocked.
typedef enum AMSI_RESULT {
AMSI_RESULT_CLEAN,
AMSI_RESULT_NOT_DETECTED,
AMSI_RESULT_BLOCKED_BY_ADMIN_START,
AMSI_RESULT_BLOCKED_BY_ADMIN_END,
AMSI_RESULT_DETECTED
} ;
AmsiCloseSession-> Lastly we will have this API to close the current AMSI scanning session. It requires both theamsiContextand theamsiSession.
Conclusion
Now that we have a clear idea about the main AMSI APIs being used in a PowerShell session and the order in which they are called, we can dive into how they work and their internals. Once we have a good understanding of their internals we will be able to start devising bypasses according to our requirements.
I will be posting the analysis of AMSI APIs and writing basic evasions in the next part since this part has already become very long.