Cómo evadir AMSI haciendo uso de DLL Hijacking

How to bypass AMSI using DLL Hijacking

Very good to everyone! Today we come with one last installment (for now) about the world of AMSI. On this occasion, we are going to discover how we could evade AMSI using a technique called DLL hijacking. 

When a binary is compiled with dynamic links, the operating system will look for the specific DLL to cover a runtime functionality. In many cases, the link to the DLL is not defined with the full path, but only with the name. 

This allows Windows, through its search mode, to interpret which DLL the binary intended to load in that link. The search order is as follows:

  • The directory where the application that is running is located
  • C:\Windows\System32
  • C:\Windows\System
  • C:\Windows
  • The directory where it is located
  • Anything in the %PATH% environment variable

Therefore, because of this way Windows works, anyone with write access somewhere on the disk could bypass AMSI. Because? Because if we can copy the PowerShell to have it in a place where we have write permissions, and we create a file called amsi.dll, when that PowerShell is opened, it will load that DLL by preference. 

In the following example we can see how, by having the PowerShell in a place like the desktop, we can make it load amsi.dll from that same place.

The only requirement to use this technique is to have a DLL (created in C/C++) that has the functions defined in the same way as the actual DLL. That is, it is not necessary that the functionality be the same, but for PowerShell, the call to the function must be given in the same way: with the same name and the same arguments.

64-bit PowerShell can be found in the following directory:

  • C:\Windows\System32\WindowsPowerShell\v1.0
While 32-bit PowerShell can be found in the following directory:
  • C:\Windows\SysWOW64\WindowsPowerShell\v1.0

An easy way to accomplish this task is by patching the original DLL. In other words, we could take amsi.dll and change the relevant instructions so that the original functionality is lost, and it never returns malware.

Previously in the post AMSI Functions we explained what each AMSI function was for.

We know that AmsiScanBuffer returns an HRESULT, which is a code that represents whether the function has been executed successfully or not. If we change AmsiScanBuffer to always return 0x80070057, AMSI will stop working and we will have circumvented it.

The following image is a part of the AmsiScanBuffer function. 

The first if does a series of basic validations and if it passes, it means that the arguments have been provided invalidly and the scan will not be possible, so it sets uVar2 to 0x80070057.

But what would happen if within the else uVar2 was also set to 0x80070057? The answer is that AMSI would stop working correctly, because even though AMSI communicates with Windows Defender to send the buffer, if the function returns INVALIDARG, the command will be executed normally.

In assembler, to return a value in a return, we must load the content into a return register, therefore, to return INVALIDARG, we could add the following:
  • MOV        EAX,0x80070057
If you use ghidra, selecting in the decompile part, the part that interests us, we can see where its corresponding assembler is located. To patch this function so that it always returns INVALIDARG, we can change the last call.

Therefore, the function would look like the following:

Finally, it would be necessary to export the DLL as PE and that would be it.

And with this we say goodbye to AMSI for the moment. If you have been left wanting more, write to us!

Justo MartínSecurity Analyst at Zerolynx.
return to blog

Leave a comment

Please note that comments must be approved before they are published.