When a process is created, amsi.dll is loaded and mapped into the virtual address space of the function. This means we can modify certain functions and patch them so that they function differently.
In our case, AmsiScanBuffer is the function used to detect malicious content, meaning that we want to patch the AmsiScanBuffer function so it always returns AMSI_RESULT_CLEAN(not malicious).
To do this, we need to find the AMSI_RESULT_CLEAN instructions in x86, this is mov EAX,0x80070057:
The hex for this is 0xB8.0x57.0x00.0x07.0x80
The value 0x80070057 is an error code from Microsoft which stands for E_INVALIDARG. AmsiScanBuffer() uses this to return when the parameters passed by the caller code are not valid.
We can modify the AmsiScanBuffer() function in memory to always force it to return 0x80070057, which will return AMSI_RESULT_CLEAN as a result.
To patch this, we can use this powershell snippet:
Forcing it to return 0x80070057 is not the only way to do it too, we can also make it return zero with something like: sub eax, eax | ret.
Reflection
Reflection allows us to violate the rules of OOP and allow us to modify private variables which speaking, should not be accessed from outside their classes.
This bypass uses reflection to set "amsiInitFailed" to true so that our AMSI result will return AMSI_RESULT.AMSI_RESULT_NOT_DETECTED; Which bypasses AMSI checking. We can see how this works in the following logic in “System.Management.Automation.AmsiUtils” :
[Ref] abbreviates to [System.Management.Automation.PSReference].
To get direct access to the dll, we add the ".Assembly"
We then call the GetType function which retrieves a handle to the internal class Utils.
With this handle, we can begin to fetch the amsiInitFailed field by calling the GetField function, we specify nonpublic and static because GetField requires this(needs binding flags.)
We then set that value so it can return: AMSI_RESULT.AMSI_RESULT_NOT_DETECTED;