In-depth analysis of Formbook/Xloader v7.1

Vladyslav Bahlai
19 min readNov 5, 2024

--

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.

Obfuscated memset function wrapper

Data decryption

Xloader uses non-modified RC4 algorithm.

Xloader 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.

Data preparation algorithms (only differences showed)
Decrypting module name using RC4 algorithm

Eventually, Xloader decrypts the next stage and executes it. No Anti-VM or Anti-Debug checks were made so far.

Checking memory access rights before next stage’s code decryption
Executing stage 2

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.

Initializing master RC4 key
XORing master RC4 key with hardcoded value
Initialized master RC4 key in Config structure

To generate mutex name, Xloader firstly gets SHA-1 hash of following 40 bytes of data:

Raw bytes supplied to SHA-1 algorithm

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.

Main mutex name generation algorithm
Generated mutex name (depends on the user’s name)
Acquired mutex by Xloader

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.dll
  • 1 nspr4.dll
  • 2 ole32.dll
  • 3 user32.dll
  • 4 sspicli.dll
  • 5 browser.dll
  • 6 crypt32.dll
  • 7 shell32.dll
  • 8 dragon_s.dll
  • 9 advapi32.dll
  • 10 kernel32.dll
  • 11 vaultcli.dll
  • 12 winsqlite3.dll
  • 13 sqlite3.dll
  • 14 gdi32.dll
  • 15 gdiplus.dll
Example of ShellExecuteA() API call

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

Encrypted routines decryption

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:

Another layer of code encryption

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):

Ret addr stub along with thread routine

By adding two to the returned address by GetRetAddr_2 function, the address of ThreadRoutine_0 function is resolved.

Resolving thread routine address by using ret addr stub

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.

Example of double data encryption during injection process

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.

  1. SbieDll.dll check:

2. Running processes checks:

Check if any “blacklisted” processes are currently running

3. User name checks:

Checking user name

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:

Checking current process’ name

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:

Checking if any of processes has following substrings in its full name

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.

Selecting target process for injection
Creating process in suspended state
Spawned notepad.exe process by Xloader

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:

Original code chunk

Then the value of EAX register is compared to 0x88888888 value.

EAX register value comparison

This check will always returns True, unless the code is patched by Xloader.

Code used to patch 0x88888888 value

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:

Code before the patch
Code after the patch

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:

Saving SHA-1 hash of randomly generated bytes

It is the way to communicate between different malware stages, where SHA-1 hash is used as a marker.

Diagram of shared memory between stages

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.

A part of FinalConfig structure containing crucial data for Stage4
Initializing master traffic key
Master traffic key initialization
XOR modifiers initialization

Incoming traffic decryption

Incoming traffic decryption diagram

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.
Executing received payload
  • 0x32 Update itself (load received payload into memory and inject into explorer.exe process). Also update autorun entries.
Updating itself
  • 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 using ShellExecuteA() API.
Processing 0x34 command
  • 0x35 Delete cookies (%userprofiles%\Cookies, %appdata%\Microsoft\Cookies, %localappdata%\Microsoft Windows\INetCookies, Google Chrome cookies) and launch legitimate explorer.exe process. Probably to force the user to re-enter credentials.
  • 0x36 Execute data stealing routine.
  • 0x37 System reboot.
Rebooting infected system
  • 0x38 Power off.
Powering off infected system
  • 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

Generated Xloader name stored in FinalConfig structure

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.

Code decryption using tags and two RC4 keys

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 code decryption using tags and two RC4 keys

Another 7 code chunks are decrypted using only master RC4 key:

Encrypted routines’ markers calculation

Eventually, this stage will launch explorer.exe process in suspended state and inject itself into it by creating new section inside spawned process:

Performing inject into explorer.exe process (newly spawned)
Spawned explorer.exe by the 3rd stage or Xloader

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:

Hardcoded 6 bytes for patch

By adding a DWORD (address of Stage4 entry point) these bytes resolve into following assembly:

Resolved 10 bytes at patched entry point. Credit: online assembler (https://defuse.ca/online-x86-assembler.htm)
Patched Xloader entry point

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.

Xloader is seeking for the first available thread inside explorer.exe

As soon as the first suitable thread found, Xloader uses APC Injection technique to inject another code chunk into explorer.exe process:

Using APC injection technique

After the injection Fb83M538 message is sent to the injected APC routine:

Sending user message to the thread inside explorer.exe

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.

Collecting information about infected system

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> and 7.1: constant.
  • <OS_VER> — value, extracted from HKLM\Software\Microsoft\Windows NT\CurrentVersion!ProductName registry entry.
  • <ARCH> — either x64 or x86, depending on the system architecture (ARM is not supported).
Crafter XLNG data in Xloader’s memory

Then base64-encoded computer name and user name are appended to the XLNG data.

Encoding computer name and user name strings
Appended base64-encoded data to XLNG message

Finally, the data is RC4-encrypted using hardcoded key (not master key).

Crafting PKT2 data

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.

Network communication thread

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):

Some data collection routines replaced with stubs

Xloader copies sqlite-like files with saved credentials from Chrome and Opera as %temp%\Fb83M53 file and opens it with sqlite3 utility.

Copying sqlite-like files to %TEMP% folder before processing it

Xloader collects autofill data as well as cookies data, namely: host_key, path, is_secure, expires_utc, name, value, encrypted_value values.

SQL requests for autofill and cookies data collection

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.

Filling the list of browser names

Xloader collects history information from Internet Explorer browser:

Collecting URL history information using COM objects

For Firefox browser, logins.json file of every profile is processed:

Parsing Firefox profiles’ logins.json files

Passwords from Windows Credentials Vault are also collected:

Processing Windows Credentials Vault

Xloader also steals saved credentials from following mail clients: Mozilla Thunderbird, Foxmail (by Tencent), Microsoft Outlook.

Clipboard data is also collected:

Getting clipboard data
Saving clipboard data along with foreground window title

All collected (stolen) data is processed on the next stage.

Storing collected info into FinalConfig structure used on the 4th 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.

Extended config structure on the 4th stage

Xloader resolves APIs required for embedded infostealer:

Resolving APIs required for clipboard interaction

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).

Indexes of real C&C addresses

In general C&C decryption pipeline looks like this:

CnC decryption algorithm

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

Outgoing traffic encryption diagram

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.

User agent string

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.

Allocating ConfigS5 structure
Stage 4 code on the left and Stage 4 x64 code on the right

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 with PAGE_EXECUTE_READWRITE rights.
  • 0x04 Set FILE_ATTRIBUTE_HIDDEN to a directory.
  • 0x05 Get full file name.
  • 0x06 Try to open existing file for full access. Return 1 on success.
  • 0x07 Check if file has FILE_ATTRIBUTE_HIDDEN attribute.
  • 0x08 Set FILE_ATTRIBUTE_HIDDEN and FILE_ATTRIBUTE_SYSTEM attributes to a file.
  • 0x09 Reset file attributes (set to FILE_ATTRIBUTE_NORMAL).
  • 0x0A Set FILE_ATTRIBUTE_HIDDEN attribute.
  • 0x0B Open existing file and write data from Config struct to it.
  • 0x0C Read file content.
  • 0x0F Open existing file
  • 0x10 Try to open existing file for overwrite operation. Return 1 on success.
  • 0x11 Try to create or open existing file. Return 1 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 (call NtCreateFile() with ShareAccess set to FILE_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 the Config structure (allowed files of size up to 1000 KB).
  • 0x19 Open file for reading operations exclusively (call NtCreateFile() with ShareAccess set to 0x00000000, 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 write Data7 from Config struct to it.
  • 0x22 Create or open existing file and write data from Config struct to it.
  • 0x23 Try to get FileBasicInformation and FileStandardInformation.
  • 0x24 Try to open existing file for read and return NT_STATUS value (probably check if the specified file exists).

Feel free to leave a comment and consider subscribing to my blog! Be safe 🤖

--

--

Responses (1)