Blog de Zscaler
Reciba en su bandeja de entrada las últimas actualizaciones del blog de Zscaler
Latest Xloader Obfuscation Methods and Network Protocol
Introduction
Xloader is an information stealing malware family that evolved from Formbook and targets web browsers, email clients, and File Transfer Protocol (FTP) applications. Additionally, Xloader may execute arbitrary commands and download second-stage payloads on an infected system. The author of Xloader continues to update the codebase, with the most recent observed version being 8.7. Since version 8.1, the Xloader developer applied several changes to the code obfuscation. The purpose of this blog is to describe the latest obfuscation methods and provide an in-depth analysis of the network communication protocol. We highly recommend reading our previous blogs about Xloader in order to get a better understanding of the malware’s internals.
Key Takeaways
- Formbook is an information stealer that was introduced in 2016 and rebranded as Xloader in early 2020.
- The malware continually receives enhancements, with the latest version being 8.7.
- Xloader version 8.1 introduced additional code obfuscation to make automation and analysis more difficult.
- Xloader supports a variety of network commands that may be used to deploy second-stage malware payloads.
- Xloader adds multiple encryption layers to protect network communications and leverages decoys to mask the actual malicious C2 servers.
Technical Analysis
In the following sections, ThreatLabz describes the key code updates introduced in Xloader from version 8.1 onward and the current network communication protocol. It is important to note that Xloader is a rebranded version of FormBook. Therefore, many parts of Xloader contain tangled legacy code that is not used.
Code obfuscation
Throughout Xloader’s development, the authors have used obfuscation at different stages of execution, such as:
- Encrypted strings that are decrypted at runtime.
- Encrypted code blocks consisting of functions that are decrypted at runtime and re-encrypted after execution.
- Opaque predicates in combination with bitwise XOR operations to decrypt integer values.
Xloader still relies on the obfuscated methods listed above with some additional modifications, which are described below.
Functions decryption routine
As previously documented, Xloader detects and decrypts each necessary function at runtime. This process involves constructing and decrypting two “eggs”, which mark the start and end of the encrypted function data. The function responsible for decrypting the encrypted functions at runtime has its parameters constructed on the stack. Starting with version 8.1, Xloader builds each parameter without following a specific order and, in some cases, builds each parameter byte by byte.
The figure below shows an example of Xloader constructing the eggs prior to version 8.1 (top) with a consistent size and ordering, compared to the latest versions of Xloader (bottom) constructing the egg parameters out of order with varying chunk sizes before calling the decrypt function.

Figure 1: Comparison of Xloader egg construction for function decryption.
Even though these changes may seem minor, they have a significant impact on automation tooling. Since the order of the encrypted starting and ending arrays are no longer set, the function’s memory layout needs to be reconstructed properly to perform analysis and extract values, as typical pattern matching would not be able to assist. As a result, extracting these values at an assembly level becomes a tedious task. One tool that can be used when analyzing these changes is the Miasm framework, which can statically lift the obfuscated code and reconstruct the stack properly.
Code obfuscation and opaque predicates
Starting with version 8.1, Xloader introduced more sophisticated obfuscation for hardcoded values and specific functions. Constant value obfuscation was present in previous versions of Xloader, but it was employed in much simpler cases. An example of an early, simpler constant obfuscation routine is shown below.
var1 = 190;
// Sets var1 memory pointer to 0
erase_data_if(&var1, sizeof(var1));
if ( var1 == 0x91529F54 )
out = 0; // Never executed
else
out = (out + 0x6EAD60AC) ^ 0x6C69DE1C; // result: 0x02c4beb0In the latest versions, Xloader encrypts additional constant values. For instance, when adding the typical assembly function prologue bytes (followed by a series of NOP instructions) for a decrypted function, Xloader now decodes the prologue bytes using a bitwise XOR operation, as shown in the figure below.

Figure 2: An example of Xloader’s function prologue bytes obfuscation.
In addition to the enhancements described above, the custom decryption routine that Xloader uses to decrypt data is now obfuscated. The unobfuscated custom decryption function prior to version 8.1 is shown below.

Figure 3: Xloader’s custom decryption routine prior to version 8.1.
In the latest versions, Xloader passes a structure parameter that includes hardcoded values. The obfuscated function reads each required structure member and decrypts each value. In the figure below, Xloader decrypts the Substitution Box (S-box) size by reading the value 0x25 from the structure passed to the function and adds 0xDB (line 39 in the decompiled obfuscated function shown in the figure below).

Figure 4: Xloader’s obfuscated custom decryption routine since version 8.1.
Network communication
At a high level, Xloader has two main objectives. First, to exfiltrate user credentials and sensitive information from the compromised host. These include passwords and cookies from various software applications such as internet browsers (e.g. Google Chrome) and email clients (e.g. Microsoft Outlook). Second, to execute arbitrary commands including downloading and executing additional payloads. In this section, we examine how Xloader performs these network-based actions.
Network protocol and encryption
Xloader has two methods for sending an HTTP request to the C2 that produce the same network traffic output but with a different User-Agent HTTP header. Depending on a pre-configured boolean flag, Xloader uses:
- Raw TCP sockets where the
User-Agentmay vary from sample to sample and tries to mimic common browserUser-Agentvalues. - WinINet API functions (e.g.
HttpSendRequest) where theUser-Agentis set toWindows Explorerand is the same across all samples.
For raw TCP sockets, Xloader confirms that the Windows API function gethostbyname is not inline-hooked by comparing the first byte of the API function with the following values.
0xE9- Near JMP instruction.0xEA- Far JMP instruction.0xCC- INT3 instruction.
If there is a hook detected, Xloader does not send the HTTP request. There are two primary threads for network communication:
- In all cases, the first thread is used to prepare exfiltrated data and encrypt any outgoing network packets. If the boolean flag for raw TCP sockets is true, Xloader uses this thread to send the exfiltrated data and request commands.
- Otherwise, a second thread is used to send HTTP requests with the WinINet API functions.
Internally, Xloader’s code uses request IDs for C2 communication, which are described in the table below.
Internal Request ID | Description |
|---|---|
3 | HTTP POST requests sent to the C2 server containing exfiltrated credentials. |
6 | HTTP GET requests sent to the C2 server containing PKT2 messages. |
Table 1: Xloader internal request IDs.
ANALYST NOTE: Despite not being used, Xloader does support a set of additional internal request IDs. These are 7, 8, 9, 10, and 12. ThreatLabz believes that the additional request IDs are part of legacy code.
Despite using plaintext HTTP requests for network communication, Xloader uses a combination of multiple encryption layers with different keys for encrypting network traffic as shown in the table below.
RC4 Key Name | Internal Request ID(s) | Description |
|---|---|---|
First PKT2 RC4 key | 6 | Encrypts PKT2 data (described below). |
Second PKT2 RC4 key | 6 | Encrypts the full PKT2 data, which includes the magic header |
HTTP GET packets RC4 key | 6 | Encrypts all HTTP GET requests before sending them. Xloader only uses this key for the outgoing PKT2 data. |
C2 URL key | 3 and 6 | Encrypts the message with the SHA1 hash of the C2 URL. |
C2 URL RC4 seed | 3 and 6 | Xloader uses these seed values to derive new keys based on the C2 URL to encrypt/decrypt network data. Xloader deliberately decrypts the key at different execution phases in an attempt to complicate analysis. |
Table 2: Summary of Xloader network communication encryption layers.
Network encryption for Xloader versions 8.1 and onward is similar to recent versions. Xloader uses a set of decoy C2 servers to mask the real malicious C2 servers. Xloader includes a total of 65 C2 IP addresses that are individually decrypted only when they are used at runtime. Xloader randomly chooses 16 C2 IP addresses and starts sending HTTP requests (both internal request IDs 3 and 6 mentioned in Table 1). Xloader repeats this process until all C2 servers have been contacted. This makes it difficult for malware sandboxes to differentiate decoys from the real C2 servers. Thus, the only way to determine the real C2 servers is to first establish a network connection with each C2 address (e.g. by network emulation) and verify the response.
ANALYST NOTE: For the rest of the blog, encryption/decryption refers to the RC4 cipher algorithm and encoding/decoding refers to the Base64-encoding algorithm, unless otherwise specified.
As mentioned above, Xloader sends an HTTP GET request to the C2 server to retrieve a network command. The packet contains the following information.
- A magic header set to
XLNG. - A 8-byte hexadecimal string, which is the bot ID.
- Xloader version in a string format (e.g.
8.5). - Windows version (e.g.
Windows 10 Pro x64). - Hostname and username in Base64-encoded format.
Xloaders encrypts the packet using the first PKT2 RC4 key and then encodes the packet. Next, Xloader prepends the string PKT2: to the encoded packet and encrypts it using the second PKT2 RC4 key.
Xloader has a dedicated function to prepare network data before sending it to the C2. Depending on the request type (Table 1), Xloader uses a different encryption chain and set of HTTP headers.
For HTTP GET requests, Xloader encrypts the network data in the order outlined below.
- Xloader uses a hardcoded RC4 key for the first encryption layer.
- Xloader encrypts the data by using the SHA-1 hash of the C2 URL as a key.
- Xloader derives a new RC4 key by decrypting the C2 URL network seed with the SHA-1 hash of the C2 URL as a key. The decryption algorithm is custom and has already been documented. Xloader uses the derived key to encrypt the network data.
- As a final step, Xloader encodes the encrypted data and prepends the hardcoded string
&dat=, even though this string value is stripped (and therefore not sent).
Xloader uses HTTP GET requests solely for PKT2 requests. Notably, the RC4 key of the first encryption layer is the same as the key used when preparing the PKT2 packet. As a result, this layer of encryption does not make any meaningful changes in the final output of the network data. ThreatLabz has observed this behaviour across all samples since at least version 7.9.
ANALYST NOTE: When Xloader uses high level Windows API functions (e.g. HttpSendRequest) instead of raw sockets for network communication, the Base64-encoded data includes the parameter query &wn=1 at the end.
Lastly, Xloader generates two random alphanumeric query parameter names that are placed in the generated GET request URI. One of them is used for the encoded data value. The size ranges of the parameter names change per sample. The position of the data’s query parameter is randomly selected (based on a flag deduced from the victim’s’s system time) and can be placed at the start or end of the URI. For example: {random_parameter1}={encoded_data}&{random_parameter2}={random_parameter2_junk_data}.
Additionally, Xloader collects credentials and cookies from the victim’s system. Xloader sends the stolen data using HTTP POST requests. The encryption process and data structure remain mostly the same but with some minor differences as described below:
- Xloader does not use the hardcoded RC4 key for encryption and completely ignores this encryption layer. Instead, Xloader encrypts the data using the SHA-1 hash of the C2 URL as a key followed by a secondary encryption layer with a key derived from the C2 URL seed.
- Xloader proceeds to encode the data. However, the characters “+”, “/” and “=” are replaced with “-”, “_”, and “.”, respectively.
- Xloader repeats the same encryption process described in the previous steps.
- The data resulting from the previous operation is encoded (without modifying the output this time).
- Xloader uses a different format for the constructed POST request data. In this case, the format is
“dat=” + final_base64_encoded_data + "&un=" + base64_encoded_host_info + "&br=9”.
Network commands
Xloader receives and parses network command packets only after sending HTTP GET requests. After a response is received, Xloader internally constructs a data structure that includes the data received and its size, along with the corresponding RC4 decryption key, as shown below.
struct parsed_network_packet
{
uint32_t packet_flag_marker; // Set to 1 after reading all network data.
uint32_t sizeof_data; // Total size of network data received.
uint8_t packet_rc4_key[20]; // RC4 key for decrypting the network data.
uint32_t unknown;
uint8_t* data;
};Similar to the outgoing network packets, Xloader uses the SHA-1 hash of the C2 URL as an RC4 key in order to derive a second key from the C2 URL network seed. Next, Xloader decodes the network data and decrypts it twice with two different keys. In the first instance, Xloader uses the SHA-1 hash of the C2 URL as an RC4 key, while in the second case Xloader uses the derived RC4 key. The decrypted packet contains a network command ID to execute and parameters (if any). The data structure for Xloader’s commands is shown below.
#pragma pack(1)
struct command_packet
{
char magic[4]; // Set to XLNG
char cmd_id;
char* command_data;
};ANALYST NOTE: When Xloader uses the high level WinINet functions, it checks if the currently chosen C2 index matches a hardcoded value (e.g. 9). If there is a match, Xloader uses the SHA-1 hash of the C2 URL as an RC4 key. If there isn’t a match, Xloader leaves the field empty causing the decryption of any network packets to fail. However, when using Windows raw TCP sockets, Xloader uses that RC4 key without performing any further checks.
The table below shows Xloader’s network commands.
Command ID | Description |
|---|---|
1 | Executes one of the following file types.
|
2 | Updates Xloader. |
3 | Xloader removes itself from the compromised host. |
4 | Depending on the command parameter field, Xloader performs one of the following actions.
If no parameters are passed, then Xloader executes the file specified in the command parameter. For example: |
5 | Remove browser cookies. |
6 | Invokes Xloader’s credential stealing capabilities. |
7 | Reboots the compromised host. |
8 | Shuts down the compromised host. |
9 | Not implemented. Across all samples, the functionality of this command corresponds to a function with the assembly instructions |
Table 3: Xloader’s network commands.
Conclusion
Xloader continues to be a highly active information stealer that constantly receives updates. As a result of the malware’s multiple encryption layers, decoy C2 servers, and robust code obfuscation, Xloader has been able to remain largely under the radar. Therefore, ThreatLabz expects Xloader to continue to pose a significant threat for the foreseeable future.
Zscaler Coverage
Zscaler’s multilayered cloud security platform detects indicators related to Xloader at various levels. The figure below depicts the Zscaler Cloud Sandbox, showing detection details for Xloader.

Figure 5: Zscaler Cloud Sandbox report for Xloader.
In addition to sandbox detections, Zscaler’s multilayered cloud security platform detects indicators related to Xloader at various levels with the following threat names:
Indicators Of Compromise (IOCs)
SHA256 Hash | Description |
|---|---|
316fee57d6004b1838576bb178215c99b56a0bd37a012e8650cd2898041f6785 | Xloader version 8.7 |
59db173fbff74cdab24995a0d3669dabf6b09f7332a0128d4faa68ae2526d39a | Xloader version 8.5 |
6b15d702539c47fd54a63bda4d309e06d3c0b92d150f61c0b8b65eae787680be | Xloader version 8.5 |
¿Este post ha sido útil?
Descargo de responsabilidad: Esta entrada de blog ha sido creada por Zscaler con fines únicamente informativos y se proporciona "tal cual" sin ninguna garantía de exactitud, integridad o fiabilidad. Zscaler no asume ninguna responsabilidad por cualquier error u omisión o por cualquier acción tomada en base a la información proporcionada. Cualquier sitio web de terceros o recursos vinculados en esta entrada del blog se proporcionan solo por conveniencia, y Zscaler no es responsable de su contenido o prácticas. Todo el contenido está sujeto a cambios sin previo aviso. Al acceder a este blog, usted acepta estos términos y reconoce su exclusiva responsabilidad de verificar y utilizar la información según convenga a sus necesidades.
Reciba en su bandeja de entrada las últimas actualizaciones del blog de Zscaler
Al enviar el formulario, acepta nuestra política de privacidad.



