In-depth analysis of Formbook/Xloader v7.1
Xloader is a sophisticated multi-stage malware that combines RAT and InfoStealer functionality. Xloader is under active development for many years. I believe v7.1 is the latest version available ITW by the time of the analysis.
Usually Xloader is packed by one or multiple packers into a .NET/AutoIt/NSIS loader. For example, the analyzed sample is packed by Cassandra Packer. Analyzing .NET or AutoIt packers is out of scope for this analysis, but I recommend to read the following article by Ryan Weil about Cassandra Packer, if you are interested: https://ryan-weil.github.io/posts/AGENT-TESLA-1/.
Stage 1. Loader.
This stage is basically a loader for Stage 2. Unlike next stages, this stage contains a lot of meaningless code.
Data decryption
Xloader uses non-modified RC4 algorithm.
Nevertheless, Xloader additionally modifies the data before and after encryption/decryption. I named this process “data unfolding”. Basically, there are two data unfolding algorithms (positive and negative), which slightly modify data by “add” or “sub” operations applied to the bytes of data. I could not recognize any standard shuffling or padding algorithm in it. If you could, please let me know in the comments section.
Eventually, Xloader decrypts the next stage and executes it. No Anti-VM or Anti-Debug checks were made so far.
Stage 2. Anti-VM, Anti-Debug checks, Injector.
At this stage malware allocates 0xC1C (3100d) bytes for Config
structure. It contains resolved API addresses, as well as other information use during execution.
On this stage Xloader initilizes master RC4 key that will be used for code and data decryption later on.
To generate mutex name, Xloader firstly gets SHA-1 hash of following 40 bytes of data:
Where DF 0F EF 99 75 6B 12 A8 4D 50 00 00 00 00 00 00 00 00 00 00
are hardcoded bytes followed by current user’s name (in UTF-16 encoding).
SHA-1 hash is then used as a key to RC4 algorithm (with positive unfold) applied to the same 40 bytes of data. After this 16 resulting bytes are transformed in a custom way into a printable widechar string. In result, mutex name looks like it was randomly generated, when in fact it is not.
API calls
Probably to avoid user-mode monitoring software, Xloader loads its own copy of ntdll.dll
and then resolves API functions’ addresses using its CRC32 hashes. Malware uses a non-modified version of Bzip2 CRC32 algorithm, but CRC32 hashes are stored encrypted by RC4.
Resolved API addresses and modules’ addresses are stored in Config
structure in a runtime.
Xloader checks if it is running under Windows for ARM or Windows x64. It calculates BZip2CRC32 hashes of substrings of full ntdll.dll
path and compares it to 0x79dbe71d
and 0x5c4ee455
hashes (“sychpe32” and “wow64” strings respectively).
For all other libraries Xloader uses another approach. Firstly, it stores the list of RC4-encrypted modules’ names. Oddly enough, decryption key is “.dll” and 12 zero bytes (2E 64 6C 6C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
).
Here is the list of modules (DLLs) that are dynamically loaded by Xloader (index and module name):
0
nss3.dll1
nspr4.dll2
ole32.dll3
user32.dll4
sspicli.dll5
browser.dll6
crypt32.dll7
shell32.dll8
dragon_s.dll9
advapi32.dll10
kernel32.dll11
vaultcli.dll12
winsqlite3.dll13
sqlite3.dll14
gdi32.dll15
gdiplus.dll
Here is a corresponding C-type enum for you to import into IDA:
enum MODULES
{
NSS3_DLL = 0x0,
NSPR4_DLL = 0x1,
OLE32_DLL = 0x2,
USER32_DLL = 0x3,
SSPICLI_DLL = 0x4,
BROWSER_DLL = 0x5,
CRYPT32_DLL = 0x6,
SHELL32_DLL = 0x7,
DRAGON_S_DLL = 0x8,
ADVAPI32_DLL = 0x9,
KERNEL32_DLL = 0xA,
VAULTCLI_DLL = 0xB,
WINSQLITE3_DLL = 0xC,
SQLITE3_DLL = 0xD,
GDI32_DLL = 0xE,
GDIPLUS_DLL = 0xF,
};
Bzip2 CRC32 hashes of ntdll.dll
export and other libraries used by Xloader can be found in my GitHub repository:
Interestingly enough, if Xloader is unable to load sqlite3.dll
, it downloads the library from the following URL: http://www.sqlite.org/2014/sqlite-dll-win32-x86-3080300.zip
For this operation Xloader uses Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.155 Safari/537.36
string as a user-agent.
Encrypted routines calls
Decryption key is built in a runtime by applying XOR encryption to the hardcoded bytes. Key length is 20 bytes. In order to find the beginning and the end of each encrypted routine, malware uses 6 prolog and 6 epilog bytes (in other words, two 6-byte markers). They are supplied to the decryption routine and then decrypted with the same key that will be used to decrypt the routine code later. Malware looks for a match of these 6 prolog bytes and 6 epilog bytes. If the markers were found, malware rewrites them with NOP (0x90)
instructions and decrypts the code between them.
After the decrypted routine call, Xloader encrypts the code back, probably to avoid exposing decrypted code to memory dumpers:
Sometimes Xloader decrypts in a similar manner not just a function, but a chunk of bytes, containing multiple functions or even a whole stage:
To locate the start and the end of encrypted chunk, Xloader searches for two DWORD markers. Once found, it decrypts the data using crafted on stack RC4 key.
Another trick is how Xloader gets the address of some routines, for example an address of a routine to be supplied as a lpStartAddress
parameter of CreateThread()
API function. First of all, the malware uses stubs that return current EIP
address (GetRetAddr_2
):
By adding two to the returned address by GetRetAddr_2
function, the address of ThreadRoutine_0
function is resolved.
Thus, there is no Xref pointers to the ThreadRoutine_0
function, which may confuse some malware analysis during a static analysis.
To complicate things a little bit more, there are exclusions, when a code or data is decrypted twice in a row: firstly, using a key crafted on stack, and then using the master RC4 key stored in Config structure.
Anti-debug/Anti-VM techniques
Malware uses multiple approaches to detect if it’s being debugged or executed under virtual environment. If Xloader detects any signs of that, it won’t fail immediately. Instead, it will rewrite important values in Config
structure, which lead to ACCESS_VIOLATION
exception later.
SbieDll.dll
check:
2. Running processes checks:
3. User name checks:
Instead of calculating CRC32 hash of a whole string, malware extracts substring from a user name and calculates its CRC32 hash. For example, for the first check malware searches for “c” symbol in user name strings, extracts the next 6 characters and checks its CRC32 checksum.
4. Current process name’s check:
Xloader/Formbook fails, if its file name has more than 31 characters in it and does not have any spaces. Probably it is a check against automated analysis systems, which rename analyzed files to a corresponding SHA-256 hash.
Yet, it is unclear which process name is “blacklisted” with a hash-sum of 0x7c81c71d.
5. Processes’ path checks:
6. Ring3 debugger check:
7. Kernel debugger check:
Injection
On this stage Xloader uses Thread Context Switching technique to inject itself into a newly spawned legitimate process. Malware tries to launch following legitimate executables for this purpose: write.exe
(WordPad), schtasks.exe
(command-line tool used to schedule tasks), finger.exe
(command-line utility that uses the Finger protocol to retrieve information about users on a remote network), sfc.exe
, iexpress.exe
(tool used to create self-extracting packages or self-installing executables), rasautou.exe
(Remote Access AutoDial Manager), choice.exe
(command-line utility that prompts the user to select one item from a list of choices and returns the index of the selected choice), bitsadmin.exe
(command-line tool used to manage Background Intelligent Transfer Service jobs), relog.exe
(part of the Windows Performance Toolkit, used to extract, consolidate, and manage performance logs), tcpsvcs.exe
(hosts several TCP/IP services), msfeedssync.exe
(used by Internet Explorer to synchronize RSS feeds), notepad.exe
.
Alternatively, if Xloader is running natively under Windows x86, it will attempt to inject itself into explorer.exe
process.
There is a fuse inside the code used during injection process. Originally there is 40 41 49 48 B8 88 88 88 88
bytes sequence, which resolves into following instructions:
Then the value of EAX
register is compared to 0x88888888
value.
This check will always returns True
, unless the code is patched by Xloader.
0x88888888
value assigned to the EAX register is replaced with a random value, so the check mentioned before will always return False
instead, which unlocks “hidden” code:
Where 0x2D0CC18
is a randomly generated value. Malware searches for the SHA-1 hash of this value, which was saved before in another routine, right after patching 0x88888888
bytes:
It is the way to communicate between different malware stages, where SHA-1 hash is used as a marker.
At this stage Xloader basically allocates a new section object and maps it into the memory of spawned process. The sections contains code and data for the next stage.
To complicate things a bit, the whole injected code is encrypted, except the first routine, which waits until Stage 2 decrypts the rest of code. Thus, Stage 3 code is decrypted by Stage 2 in a runtime.
Stage 3 and Stage 4
To complicate things a bit, code of both 3rd and 4th stages are executed simultaneously. For example, Xloader processes incoming traffic on the 3rd stage, while sends data on the 4th stage.
Stage 3. Persistence, data collection, C&C communication initialization.
On this stage Xloader performs the same anti-debug and anti-vm checks, described above and allocates 0xC1C bytes for a Config
object.
This is the stage where Xloader collects data from browsers and other supported software.
Incoming traffic decryption is done on this stage as well (see the corresponding info below).
Data initialization for the next stage
Additionally, Xloader initializes huge FinalConfig
structure for Stage4 (size of 0x12A4DD4 bytes), which stores data, required for traffic encryption and CnC decryption, such as: traffic encryption RC4 key, traffic encryption RC4 key’s XOR modifier (DWORD), CnC decryption 2nd RC4 key’s XOR modifier (DWORD), final Xloader’s executable name on the infected system, input and output buffers for network communication and so on.
Incoming traffic decryption
Incoming traffic is decrypted twice using RC4 algorithm and two different keys: the first key is stored in received data by 0x8 offset, while the second one is derived from master traffic key.
Decrypted incoming data should be of the next format: “XLNG” marker followed by a command ID (one byte) and other data (its size and purpose depend on the command ID).
A list of possible Command ID values:
0x31
Dump received data to temp (under random name) and execute. Supported file types:.exe
,.dll
,.ps1
.
0x32
Update itself (load received payload into memory and inject intoexplorer.exe
process). Also update autorun entries.
0x33
Delete itself and exit.
0x34
Perform one of the following: a) download payload from received URL, dump to %temp% and execute; b) download payload from received URL, load to memory and execute; c) open specified local file usingShellExecuteA()
API.
0x35
Delete cookies (%userprofiles%\Cookies
,%appdata%\Microsoft\Cookies
,%localappdata%\Microsoft Windows\INetCookies
, Google Chrome cookies) and launch legitimateexplorer.exe
process. Probably to force the user to re-enter credentials.0x36
Execute data stealing routine.0x37
System reboot.
0x38
Power off.
0x39
No action (not implemented in my sample).
Persistence
To achieve persistence, Xloader copies itself either under %APPDATA%
or %PROGRAMFILES%
directory and creates randomly named (from 5 to 12 characters length) registry value under the one of the following registry locations (depending on the available access rights):
\Registry\User\<USER_SID>\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
\Registry\User\<USER_SID>\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run
\Registry\Machine\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
\Registry\Machine\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run
If Xloader is running with elevated rights, it copies itself into %PROGRAMFILES%
folder under randomly generated name:
<PROGRAM_FILES>\[A-Z]<RND_VALUE3>\<RND_VALUE><RND_VALUE2>.exe
Code decryption
On this stage Xloader uses the same approach for code decryption. Right after achieving persistence, malware decrypts another chunk of code, using 80 5F 26 2B
and A8 13 4A 30
bytes as a markers of a beginning and end of encrypted chunk respectively.
The code is decrypted twice: using master RC4 key and a key, crafted in a runtime. In the case of my sample, RC4 keys are: 80 5F 26 2B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
(crafted in a runtime from hardcoded 0x2B265F80
DWORD) and A8 9E C9 D2 0F 7E 31 CA D6 59 4C AE CC D5 01 57 B1 BF B4 2F
(master RC4 key).
Later on, another code chunk is decrypted in the same way and then executed.
Another 7 code chunks are decrypted using only master RC4 key:
Eventually, this stage will launch explorer.exe
process in suspended state and inject itself into it by creating new section inside spawned process:
Xloader replaces 10 bytes at its own entry point with the following 6 bytes, plus another DWORD with the address of Xloader’s routine that should be executed:
By adding a DWORD (address of Stage4 entry point) these bytes resolve into following assembly:
After the patch Xloader copies itself to explorer.exe
’s memory space and execute itself by creating new thread inside explorer.exe
process.
The first Entry Point of injected code seems to have no real functionality though, probably because of some functionality was disabled in the analyzed sample.
Then Xloader iterates through all available threads inside explorer.exe
process and tries to suspend the first available thread.
As soon as the first suitable thread found, Xloader uses APC Injection technique to inject another code chunk into explorer.exe
process:
After the injection Fb83M538
message is sent to the injected APC routine:
Registration packet
On this stage Xloader crafts its first (registration) network packet, which contains information about the infected machine.
Firstly, Xloader builds XLNG data, containing Bzip2 CRC32 hash of current user’s SID string, system architecture (either x64 or x86 is reported, no ARM), OS version and build number. XLNG:
constant is hardcoded, but originally XOR-encrypted with 0x27
key.
Format of XLNG data is as follows:
XLNG:<USER_SID_CRC32>7_1:<OS_VER> <ARCH>:
, where:
XLNG:
— hardcoded constant.<USER_SID_CRC32>
— hex-encoded current user’s SID, supplemented with “A” character to match the length of 8 characters (if needed).7.1:
— Xloader version, hardcoded constant. Notice there is no separator between<USER_SID_CRC32>
and7.1:
constant.<OS_VER>
— value, extracted fromHKLM\Software\Microsoft\Windows NT\CurrentVersion!ProductName
registry entry.<ARCH>
— eitherx64
orx86
, depending on the system architecture (ARM is not supported).
Then base64-encoded computer name and user name are appended to the XLNG data.
Finally, the data is RC4-encrypted using hardcoded key (not master key).
Despite the data is ready to be sent, Xloader does not do it right away. Instead, it launches separate thread for network communication and waits for the signal from the next stage.
Data stealing
Xloader instance can be built with some features turned on or off. For example, my sample has some features disabled (corresponding routines are replaced with Ret_1
stubs):
Xloader copies sqlite-like files with saved credentials from Chrome and Opera as %temp%\Fb83M53
file and opens it with sqlite3 utility.
Xloader collects autofill data as well as cookies data, namely: host_key
, path
, is_secure
, expires_utc
, name
, value
, encrypted_value
values.
Affected Chromium-based browsers list: Chrome, SalamWeb, AVAST Browser, URBrowser, Kinza Browser, AVG Browser, CCleaner Browser, Opera Browser, Yandex Browser, Slimjet Browser, 360 Chrome, Comodo Browser, CoolNovo Browser (by MapleStudio), Chromium Browser, Torch Browser, Brave Browser, Iridium Browser, Opera Neon*, 7 Star Browser, Amigo Browser, Blisk Browser, Chedot Browser, CentBrowser, Cốc Cốc Browser, Elements Browser, Epic Privacy Browser, Kometa Browser, Orbitum Browser, Sputnik Browser, uCoz Browser, Sleipni Browser (by rFenrir), Catalina Browser, Coowon Browser, Liebao Browser (by Kingsoft), QIP Surf*, Microsoft Edge, Vivaldi Browser.
* New browsers added since v4.3.
Xloader collects history information from Internet Explorer browser:
For Firefox browser, logins.json
file of every profile is processed:
Passwords from Windows Credentials Vault are also collected:
Xloader also steals saved credentials from following mail clients: Mozilla Thunderbird, Foxmail (by Tencent), Microsoft Outlook.
Clipboard data is also collected:
All collected (stolen) data is processed on the next stage.
Stage 4. C&C decryption, RAT functionality.
On this stage Xloader extends existing Config
structure to 0x2D580 bytes for storing C&C addresses, connection information and other data.
Xloader resolves APIs required for embedded infostealer:
C&C decryption
Notice that C&C decryption is performed only on the last 4th stage of Xloader, after injection into explorer.exe
process. This version of Xloader contains 64 C&C host addresses, but only up to 16 of them are real C&C (numbers 0x01, 0x05, 0x0d, 0x10, 0x11, 0x12, 0x13, 0x18, 0x1d, 0x1e, 0x20, 0x21, 0x30, 0x31, 0x3d in case of my sample).
In general C&C decryption pipeline looks like this:
CnC hosts are decrypted twice using two different keys. XOR key (modifier) for the 2nd key is resolved only on 4th stage, while XOR key (modifier) for the 1st key is resolved on the 3rd stage and can be found in separate FinalConfig
structure.
Outgoing traffic encryption
As can be seen from the diagram, traffic data is encrypted twice using two different keys: a key, derived from master traffic key, and SHA-1 hash of CNC host used as a RC4 key. Thus, both encryption keys are different for every CnC.
Just like in CnC decryption algorithm, some of the data for traffic encryption is initialized on the 3rd stage, while the other data is initialized on the 4th stage. Xloader makes sure all sensitive data are not resolved at the same time at one place.
Xloader uses “Windows Explorer” string as a user agent during POST/GET requests.
Stage 4 (x64 variant)
This is the alternative Stage 4, compiled for 64 bit systems, which is decrypted and injected only if explorer.exe
process, launched by Stage 3, has x64 architecture. For example, if Xloader is executed in Windows for ARM, it will be never executed.
Therefore, Stage 4 (x64) is an x86–64 assembly inside of x86 executable, but the code is identical to Stage 4.
On this stage Xloader allocates 0x2CE08 bytes for Config
structure. Though, Config
structure on this stage has different layout than on the Stage 4, but its purpose remains the same: mainly it stores resolved imports and other information used by Xloader when running.
Thus, there is no point in analyzing Stage 4 x64, as the purpose of this stage is the same to the Stage 4 described above.
File Operations:
0x01
Read file’s content to a heap memory.0x02
Read file’s content (it should be a PE) and map the image into a heap memory.
0x03
Map the file to a section object in memory withPAGE_EXECUTE_READWRITE
rights.
0x04
SetFILE_ATTRIBUTE_HIDDEN
to a directory.0x05
Get full file name.
0x06
Try to open existing file for full access. Return1
on success.0x07
Check if file hasFILE_ATTRIBUTE_HIDDEN
attribute.
0x08
SetFILE_ATTRIBUTE_HIDDEN
andFILE_ATTRIBUTE_SYSTEM
attributes to a file.0x09
Reset file attributes (set toFILE_ATTRIBUTE_NORMAL
).0x0A
SetFILE_ATTRIBUTE_HIDDEN
attribute.
0x0B
Open existing file and write data from Config struct to it.
0x0C
Read file content.0x0F
Open existing file0x10
Try to open existing file for overwrite operation. Return1
on success.0x11
Try to create or open existing file. Return1
on success.0x12
Create or open existing file and write data from Config struct to it.0x13
Delete file.
0x14
Create or open existing file and write data from Config struct to it.
0x15
Get file size.
0x16
Open existing file for reading operations with write shared access rights (callNtCreateFile()
withShareAccess
set toFILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE
).0x17
Open existing file with full access rights (read, write, change attributes, change extended attributes).0x18
Read file’s content into theConfig
structure (allowed files of size up to 1000 KB).
0x19
Open file for reading operations exclusively (callNtCreateFile()
withShareAccess
set to0x00000000
, which prevents other processes from opening the file).0x1A
Create or open existing file and write data from Config struct to it.0x1B
Create or open existing file and write data from Config struct to it.0x1C
Create or open existing file and write data from Config struct to it.0x21
Create or open existing file and writeData7
from Config struct to it.0x22
Create or open existing file and write data from Config struct to it.0x23
Try to getFileBasicInformation
andFileStandardInformation
.0x24
Try to open existing file for read and returnNT_STATUS
value (probably check if the specified file exists).