Xloader | ThreatLabz By Javier Vicente Vallejo, Brett Stone-Gross, Nikolaos Pantazopoulos Published: 2023-03-30 · Archived: 2026-04-05 22:59:19 UTC Technical Analysis Basic Algorithms and Structures Formbook and Xloader have evolved along the years with new layers of obfuscation added in each new version. However, there is a set of basic algorithms that have been used since the first versions of Formbook. These algorithms are combined in different ways to decrypt other blocks of code and data. The primary algorithms that are shared between different versions of Xloader are the following: Custom RC4: an RC4-based algorithm with two additional layers based on subtraction operations. Custom buffer decryption algorithm: a custom algorithm used by Xloader, mainly used to decrypt the first encryption layer of the PUSHEBP data blocks (described in the following sections). Custom SHA1: a SHA1 hash is calculated and the result is reversed DWORD by DWORD. There is also a large global data structure that is used to store important information. When Xloader is executed, this structure is allocated and initialized with information from PUSHEBP data blocks, or from hardcoded values in the code. This structure contains data and encryption keys that are used by other parts of the code. Previous blog posts have referred to this structure as the ConfigObj, with fields that are used to store flags, encryption parameters, pointers, etc. The most important offsets in the ConfigObj structure are identified in Table 1. Offset Description Size 0x00 Value 0xffffffff 0x04 0x04 Pointer to a second PE header used for process injection (e.g., explorer.exe) 0x04 0x08 Result of RtlGetProcessHeaps() 0x04 0x48 Branch ID – XLNG (XORed with 0x3c) 0x04 0x90 Pointer to an extended config (located in memory following the ConfigObj structure) 0x04 https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 1 of 15 Offset Description Size 0x2DC Decrypted content of the PUSHEBP block 2, which is an array of API hashes 0x220 0x510 Array of library and process names hashes 0x254 0x828 Seed of a random number generator (RNG) used by the malware 0x4 0x83C Flag indicating that Xloader has generated the parameters necessary for the communications with the C2 0x4 0x970 The Xloader version number (XORed with 0x3c) 0x4 0x990 RC4 key used to decrypt other parameters 0x14 0x9A4 RC4 key used to decrypt other parameters (this key is the SHA1 of the decrypted content of the PUSHEBP block 5) 0x14 0xCE8 RC4 key used to decrypt the C2s list 0x14 Table 1. Important Xloader 4.3 ConfigObj fields. Encrypted PUSHEBP Data Blocks Throughout the Xloader code there is a set of encrypted data blocks with the structure shown in Figure 1. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 2 of 15 Figure 1. Xloader PUSHEBP encrypted block This structure is designed to resemble the beginning of a function, but are in fact blocks of encrypted data such as encryption keys and encrypted strings. These data blocks are decrypted using a custom buffer decryption algorithm. Table 2 shows the PUSHEBP encrypted blocks that were found in Xloader 4.3. PUSHEBP Block Number Description Size PUSHEBP Block 1 Encrypted strings 0xA82 PUSHEBP Block 2 API CRCs 0x222 PUSHEBP Block 3 Encryption key involved in C2 communications 0x15 PUSHEBP Block 4 Encryption key used to decrypt other data 0x14 PUSHEBP Block 5 Hardcoded C2 0x78 PUSHEBP Block 6 API CRCs 0x310 Table 2. PUSHEBP encrypted block contents Encrypted PUSHEBP Functions Formbook and Xloader also contain functions that decrypt code. An example function to decrypt code (prior to Xloader 2.9) is shown in Figure 2. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 3 of 15 Figure 2. Encrypted code in earlier versions of Xloader and Formbook This code starts with the well-known function preamble push ebp / mov ebp, esp, followed by a tag identifying the encrypted code (0x49909090 in Figure 2), the encrypted code, and an ending tag 0x90909090. In older versions, the code was decrypted using a custom RC4 algorithm with a key stored in the ConfigObj structure. In Xloader version 2.9, the code retained the custom RC4 algorithm and added a layer of encryption with a second key built on the stack, as shown in Figure 3. Figure 3. Decryptor of the PUSHEBP functions in Xloader version 2.9 In Xloader version 4.3, there are still PUSHEBP encrypted functions. However, the tags identifying the start and the end of the encrypted code have changed, and now they appear to be random bytes. Figure 4 shows an example of an encrypted function in Xloader 4.3. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 4 of 15 Figure 4. Encrypted PUSHEBP function (Xloader version 4.3) Figure 5 shows the code that decrypts a PUSHEBP function (e.g., the function DecryptCriticalCodeType1_Set_909090909090), which accepts two encrypted tags and an ID value. Inside the decryptor, another 0x14 byte key is constructed dynamically in the sub-function init_key_encrypted_funcs, XORed with a DWORD (XOR key 1) and XORed again with the ID value passed as an argument and another hardcoded DWORD (XOR key 2). The resulting 0x14 byte key will be used to decrypt the encrypted code using Xloader’s custom RC4 algorithm. The same RC4 key is also used to decrypt the encrypted TAG1 and TAG2, which are passed as arguments to the decryptor to derive the starting and ending tags that delimit the encrypted PUSHEBP function. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 5 of 15 Figure 5. PUSHEBP function decryption code (Xloader version 4.3) After the code is decrypted, the delimiter tags are replaced by 90 90 90 90 90 90 (NOP) opcodes. Figure 6 shows an encrypted function before and after being decrypted. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 6 of 15 Figure 6. Example PUSHEBP function decrypted (Xloader version 4.3) Encrypted NO-PUSHEBP Functions In Xloader version 4.3, a new type of encrypted function without the push ebp / mov ebp esp preamble has also been introduced. The limits of the encrypted code are located by searching for two tags that identify the start and the end of the block. Figure 7 shows the code responsible for determining the limits of a NO-PUSHEBP encrypted function. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 7 of 15 Figure 7. NO-PUSHEBP decryption code limit identification and layer 1 decryption (Xloader version 4.3) The custom Xloader RC4 algorithm is again used to decrypt the encrypted code with two layers and two different keys. The encryption key for the first layer is calculated in another function and stored in the global structure ConfigObj (the value is the result of Xloader’s custom SHA1 algorithm of the decrypted content of the PUSHEBP data block number 5). The encryption key for the second layer is built on the fly: an initial key is built on the stack and XORed with a DWORD (XOR key), producing the final key (Xloader never hardcodes exact values including for encryption keys and delimiter tags). Figure 8 shows the code involved in the decryption of the second layer for one of the encrypted NO-PUSHEBP functions. Figure 8. NO-PUSHEBP layer 2 decryption (Xloader version 4.3) After the code is decrypted, the tag before the encrypted code is replaced by the opcodes EC 8B 55 (push ebp / mov ebp esp function preamble). The tag after the encrypted code is replaced by 90 90 90 90 (NOP) opcodes. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 8 of 15 Encrypted Configuration The most important parameters of Xloader’s configuration are stored in the PUSHEBP encrypted data blocks or calculated from hardcoded constants (that are also obfuscated). Encrypted Strings The encrypted strings in Xloader are stored in the PUSHEBP data block 1. All the PUSHEBP data blocks have to be decrypted with the custom buffer decryption algorithm as explained before. Once the block is decrypted, the result is a sequential list of items that have the following format: struct encrypted_string { BYTE length; BYTE content[length]; } Each string is decrypted with the custom Xloader RC4 algorithm and an encryption key stored at offset 0x990 in the ConfigObj. This RC4 key is generated in the function shown in Figure 9. Figure 9. Generation of the RC4 key for encrypted strings (Xloader version 4.3) ThreatLabz has reproduced this algorithm to decrypt the encrypted strings in Xloader 4.3 in Python. The code is available in our GitHub repository here. Encrypted C2s The Xloader configuration contains a C2 that is stored separately from another list of C2 domains. The C2 that is stored separately was thought to be Xloader’s real C2 and the other C2s were used as decoys. However, in more recent versions of Xloader, real C2s are likely hidden among the list of decoy C2s. In fact, the author behind Xloader has made significant efforts to protect the list of C2s that were previously thought to be decoys. Hardcoded C2 The code shown in Figure 10 is responsible for decrypting the hardcoded Xloader C2. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 9 of 15 Figure 10. Hardcoded C2 decryption (Xloader version 4.3) The code in Figure 10 combines a set of operations based on Xloader’s various encryption algorithms and the data stored in the PUSHEBP data blocks to generate the encryption key necessary to decrypt the hardcoded C2 (which is stored in the PUSHEBP data block 5). C2 List As previously mentioned, there is another list of C2s that may contain decoy C2s and real C2s. In Formbook and in earlier versions of Xloader, these were stored as an encrypted string with no additional layers of encryption. In Xloader 2.9, the developers introduced an additional custom RC4 layer and Base64 encoding for the C2 list as shown in Figure 11. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 10 of 15 Figure 11. Additional encryption layer for the C2 list (Xloader version 2.9) In Figure 11, the function StringsDecryptor2 decrypts the first layer of the encrypted strings. In version 2.9, an additional Base64 layer is decoded followed by a layer of custom RC4 decryption. In Xloader version 4.3, they have added an additional encryption layer to this C2 list. Figure 12 shows the code responsible for decrypting these new layers. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 11 of 15 Figure 12. New encryption layer for Xloader’s C2 list (Xloader version 4.3) In the new version, the C2 list is first Base64 decoded and a custom RC4 layer is decrypted. A table of 4 byte keys is built on the stack. Each position of the table corresponds to a C2. Once decrypted, this custom RC4 layer is Base64 encoded again. After the new additional decryption layer is complete, Xloader decrypts the same layers as version 2.9: decoding the Base64 layer again and decrypting an additional custom RC4 layer with a key stored in a sub-structure of the ConfigObj. The way that this key (for the last RC4 layer) is generated has also changed in Xloader 4.3. Figure 13 shows the code generating the RC4 key for the last encryption layer of the C2 list. Figure 13. Key generation for the final encryption layer of the C2 list (Xloader version 4.3) As shown in Figure 13, the key is built on the stack and it is XORed with a value from the ConfigObj that was initialized previously in a different part of the code. Once this last layer is decrypted, the plaintext C2s are obtained. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 12 of 15 Branch ID and Version Number In previous versions, the Xloader branch ID and version number were sent in the registration packet to the C2. The format of the registration packet (before the last two RC4 layers) was the following: XLNG Bot ID Version Number Operating System Base64(Username) XLNG is the tag for the Xloader branch (FBNG was the branch ID for Formbook). In Xloader version 4.3, the registration packet sent to the C2 includes an additional encryption layer as shown in Figure 14. Figure 14. Xloader 4.3 Registration packet with additional PKT2 layer This new encryption layer is marked with the tag PKT2. Communications are performed in the context of explorer.exe (previously injected). However, this registration packet is built in the first injected process (a hollow process) and copied to the context of explorer together with the rest of the injected code. That first injected process exits after injecting into explorer, so the registration packet under the last encryption layer marked with the PKT2 tag is no longer in plaintext after the first injected process terminates. The PKT2 packet is built in one of the NO-PUSHEBP encrypted functions. That function is decrypted and executed in the context of the first injected process. The code first builds a string with the same format as the registration packet in previous Xloader versions as shown in Figure 15. Figure 15. Registration packet with the new PKT2 encryption layer However, as we can see in Figure 15, Xloader 4.3 introduces a NULL character separating the bot ID and the malware version number. This added NULL byte is likely a coding error. Figure 16 shows how the first registration packet is constructed (marked with XLNG tag) and encrypted with RC4 and encoded with Base64, and then concatenated to the PKT2 tag to generate the final registration packet. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 13 of 15 Figure 16. Xloader version 4.3 registration packet construction However, because of the coding error previously mentioned (an extra NULL character after the bot ID) the final registration data contains just two fields for the XLNG branch and bot ID as shown below: XLNG Bot ID The PKT2 tag is then prepended with the RC4 and Base64 encoded data as follows: PKT2 RC4_BASE64(registration_data) As a result, the version_number, operating_system and user_name is never sent to the C2. This bug will likely be fixed in future versions of the malware. https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 14 of 15 Figure 16 also shows that the branch ID and version number are no longer hardcoded unlike previous versions, with the encrypted version number and branch ID decrypted with an XOR key (0x3c). Source: https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43 Page 15 of 15