Hooking with Frida
We could use WinDbg breakpoints to trace the calls to the exported AMSI calls,
but the Frida dynamic instrumentation framework offers a more flexible approach.
Frida allows us to hook Win32 APIs through a Python backend while using
JavaScript to display and interpret arguments and return values.
Frida is pre-installed on the Windows 10 victim machine and to use it,
we’ll first open a 64-bit PowerShell console,
and locate its process ID.
This is the process we want to trace.
Next, we’ll open a command prompt to trace from,
and invoke Frida with frida-trace.
We’ll supply the process ID of the PowerShell process with -p,
the DLL we want to trace with -x,
and the names of the specific APIs we want to trace with -i.
In this case, we will use a wildcard to trace all functions beginning with “Amsi”.
At this point, Frida has hooked several APIs and we can trace the input and output
To test this, we’ll simply enter the letters “test” in the PowerShell prompt
which produces the following output from Frida.
Although we recognize calls to AMSI OpenSession, ScanBuffer and CloseSession,
we have no way of knowing if our input is responsible for all those calls.
When we start a Frida tracing session,
handler files are created for each hooked API
For AmsiScanBuffer, the handler file is located in the handlers subfolder
If we open AmsiScanBuffer.js,
we find auto-generated content
that we can modify to investigate any call to AmsiScanBuffer.
We can update the handler code to better understand Frida’s output
and help analyze what is being detected.
Since we have already inspected the signature of the API,
we can update the JavaScript code in the onEnter function.
Every hook in the handler file provides us with three arguments.
The args array contains the arguments passed to the AMSI API.
The log method can be used to print the information
we are trying to capture to the console.
To provide visibility into the arguments provided to AmsiScanBuffer,
we can add log statements for each entry in the args array.
The readUtf16String method is used with the second argument,
the buffer to be scanned, to print out its content as a Unicode string.
The final argument is the storage address of the antivirus scan result.
This address is stored in the resultPointer JavaScript variable
through the this keyword for later access.
Our goal is to store the scan result pointer until the AMSI API exits,
at which point we will read the result and print it to the console.
To do this, we can hook the AmsiScanBuffer function exit
through onLeave in the JavaScript handler code.
In this code, we’ve used the readUshort method to read the result value
from the stored memory location and have printed it to the console.
As soon as the JavaScript file is saved, Frida automatically refreshes
the hooks from the handler files,
so we can supply the same “test” string in the PowerShell prompt again.
Now that we can monitor input and output from the AmsiScanBuffer API,
we notice our “test” input and a return of “1”,
indicating that AMSI has flagged our code as non-malicious.
Next, let’s enter a simple command in the PowerShell console
that Windows Defender will detect as malicious:
Although the command was benign, it was flagged as malicious nonetheless.
There is no doubt that the warning we received in the PowerShell prompt came
from Windows Defender, but it is not clear why it was flagged.
If we try to modify the command by splitting the string and concatenating
them, it is no longer flagged as malicious:
The Frida trace provides more detail:
From this input and output, we can deduce that,
for reasons that will be revealed later,
Windows Defender flagged the “AmsiUtils” string as malicious.
However, we easily bypassed this simple protection
by splitting and concatenating the string.