{
	"id": "3cb0f0ce-f74c-4198-a2a5-cc6c920743f9",
	"created_at": "2026-04-06T00:11:39.605776Z",
	"updated_at": "2026-04-10T13:13:07.749049Z",
	"deleted_at": null,
	"sha1_hash": "849317054564c7d84356f6b9679818b2ca2d6401",
	"title": "Xloader | ThreatLabz",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 4398601,
	"plain_text": "Xloader | ThreatLabz\r\nBy Javier Vicente Vallejo, Brett Stone-Gross, Nikolaos Pantazopoulos\r\nPublished: 2023-03-30 · Archived: 2026-04-05 22:59:19 UTC\r\nTechnical Analysis\r\nBasic Algorithms and Structures\r\nFormbook and Xloader have evolved along the years with new layers of obfuscation added in each new version.\r\nHowever, there is a set of basic algorithms that have been used since the first versions of Formbook. These\r\nalgorithms are combined in different ways to decrypt other blocks of code and data. The primary algorithms that\r\nare shared between different versions of Xloader are the following:\r\nCustom RC4: an RC4-based algorithm with two additional layers based on subtraction operations.\r\nCustom buffer decryption algorithm: a custom algorithm used by Xloader, mainly used to decrypt the first\r\nencryption layer of the PUSHEBP data blocks (described in the following sections).\r\nCustom SHA1: a SHA1 hash is calculated and the result is reversed DWORD by DWORD.\r\nThere is also a large global data structure that is used to store important information. When Xloader is executed,\r\nthis structure is allocated and initialized with information from PUSHEBP data blocks, or from hardcoded values\r\nin the code. This structure contains data and encryption keys that are used by other parts of the code. Previous\r\nblog posts have referred to this structure as the ConfigObj, with fields that are used to store flags, encryption\r\nparameters, pointers, etc. The most important offsets in the ConfigObj structure are identified in Table 1.\r\nOffset Description Size\r\n0x00 Value 0xffffffff 0x04\r\n0x04 Pointer to a second PE header used for process injection (e.g., explorer.exe) 0x04\r\n0x08 Result of RtlGetProcessHeaps() 0x04\r\n0x48 Branch ID – XLNG (XORed with 0x3c) 0x04\r\n0x90 Pointer to an extended config (located in memory following the ConfigObj structure) 0x04\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 1 of 15\n\nOffset Description Size\r\n0x2DC Decrypted content of the PUSHEBP block 2, which is an array of API hashes 0x220\r\n0x510 Array of library and process names hashes 0x254\r\n0x828 Seed of a random number generator (RNG) used by the malware 0x4\r\n0x83C\r\nFlag indicating that Xloader has generated the parameters necessary for the\r\ncommunications with the C2\r\n0x4\r\n0x970 The Xloader version number (XORed with 0x3c) 0x4\r\n0x990 RC4 key used to decrypt other parameters 0x14\r\n0x9A4\r\nRC4 key used to decrypt other parameters (this key is the SHA1 of the decrypted content\r\nof the PUSHEBP block 5)\r\n0x14\r\n0xCE8 RC4 key used to decrypt the C2s list 0x14\r\nTable 1. Important Xloader 4.3 ConfigObj fields.\r\nEncrypted PUSHEBP Data Blocks\r\nThroughout the Xloader code there is a set of encrypted data blocks with the structure shown in Figure 1.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 2 of 15\n\nFigure 1. Xloader PUSHEBP encrypted block\r\nThis structure is designed to resemble the beginning of a function, but are in fact blocks of encrypted data such as\r\nencryption keys and encrypted strings. These data blocks are decrypted using a custom buffer decryption\r\nalgorithm. Table 2 shows the PUSHEBP encrypted blocks that were found in Xloader 4.3.\r\nPUSHEBP Block Number Description Size\r\nPUSHEBP Block 1 Encrypted strings 0xA82\r\nPUSHEBP Block 2 API CRCs 0x222\r\nPUSHEBP Block 3 Encryption key involved in C2 communications 0x15\r\nPUSHEBP Block 4 Encryption key used to decrypt other data 0x14\r\nPUSHEBP Block 5 Hardcoded C2 0x78\r\nPUSHEBP Block 6 API CRCs 0x310\r\nTable 2. PUSHEBP encrypted block contents\r\nEncrypted PUSHEBP Functions\r\nFormbook and Xloader also contain functions that decrypt code. An example function to decrypt code (prior to\r\nXloader 2.9) is shown in Figure 2.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 3 of 15\n\nFigure 2. Encrypted code in earlier versions of Xloader and Formbook\r\nThis code starts with the well-known function preamble push ebp / mov ebp, esp, followed by a tag identifying the\r\nencrypted code (0x49909090 in Figure 2), the encrypted code, and an ending tag 0x90909090.\r\nIn older versions, the code was decrypted using a custom RC4 algorithm with a key stored in the ConfigObj\r\nstructure. In Xloader version 2.9, the code retained the custom RC4 algorithm and added a layer of encryption\r\nwith a second key built on the stack, as shown in Figure 3.\r\nFigure 3. Decryptor of the PUSHEBP functions in Xloader version 2.9\r\nIn Xloader version 4.3, there are still PUSHEBP encrypted functions. However, the tags identifying the start and\r\nthe end of the encrypted code have changed, and now they appear to be random bytes. Figure 4 shows an example\r\nof an encrypted function in Xloader 4.3.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 4 of 15\n\nFigure 4. Encrypted PUSHEBP function (Xloader version 4.3)\r\nFigure 5 shows the code that decrypts a PUSHEBP function (e.g., the\r\nfunction DecryptCriticalCodeType1_Set_909090909090), which accepts two encrypted tags and an ID value.\r\nInside 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\r\npassed as an argument and another hardcoded DWORD (XOR key 2). The resulting 0x14 byte key will be used to\r\ndecrypt the encrypted code using Xloader’s custom RC4 algorithm. The same RC4 key is also used to decrypt the\r\nencrypted TAG1 and TAG2, which are passed as arguments to the decryptor to derive the starting and ending tags\r\nthat delimit the encrypted PUSHEBP function.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 5 of 15\n\nFigure 5. PUSHEBP function decryption code (Xloader version 4.3)\r\nAfter the code is decrypted, the delimiter tags are replaced by 90 90 90 90 90 90 (NOP) opcodes. Figure 6 shows\r\nan encrypted function before and after being decrypted.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 6 of 15\n\nFigure 6. Example PUSHEBP function decrypted (Xloader version 4.3)\r\nEncrypted NO-PUSHEBP Functions\r\nIn Xloader version 4.3, a new type of encrypted function without the push ebp / mov ebp esp preamble has also\r\nbeen introduced. The limits of the encrypted code are located by searching for two tags that identify the start and\r\nthe end of the block. Figure 7 shows the code responsible for determining the limits of a NO-PUSHEBP encrypted\r\nfunction.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 7 of 15\n\nFigure 7. NO-PUSHEBP decryption code limit identification and layer 1 decryption (Xloader version 4.3)\r\nThe custom Xloader RC4 algorithm is again used to decrypt the encrypted code with two layers and two different\r\nkeys. The encryption key for the first layer is calculated in another function and stored in the global structure\r\nConfigObj (the value is the result of Xloader’s custom SHA1 algorithm of the decrypted content of the PUSHEBP\r\ndata block number 5). The encryption key for the second layer is built on the fly: an initial key is built on the stack\r\nand XORed with a DWORD (XOR key), producing the final key (Xloader never hardcodes exact values including\r\nfor encryption keys and delimiter tags). Figure 8 shows the code involved in the decryption of the second layer for\r\none of the encrypted NO-PUSHEBP functions.\r\nFigure 8. NO-PUSHEBP layer 2 decryption (Xloader version 4.3)\r\nAfter the code is decrypted, the tag before the encrypted code is replaced by the opcodes EC 8B 55 (push ebp /\r\nmov ebp esp function preamble). The tag after the encrypted code is replaced by 90 90 90 90 (NOP) opcodes.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 8 of 15\n\nEncrypted Configuration\r\nThe most important parameters of Xloader’s configuration are stored in the PUSHEBP encrypted data blocks or\r\ncalculated from hardcoded constants (that are also obfuscated).\r\nEncrypted Strings\r\nThe encrypted strings in Xloader are stored in the PUSHEBP data block 1. All the PUSHEBP data blocks have to\r\nbe decrypted with the custom buffer decryption algorithm as explained before. Once the block is decrypted, the\r\nresult is a sequential list of items that have the following format:\r\nstruct encrypted_string {\r\n BYTE length;\r\n BYTE content[length];\r\n}\r\nEach string is decrypted with the custom Xloader RC4 algorithm and an encryption key stored at offset 0x990 in\r\nthe ConfigObj. This RC4 key is generated in the function shown in Figure 9.\r\nFigure 9. Generation of the RC4 key for encrypted strings (Xloader version 4.3)\r\nThreatLabz has reproduced this algorithm to decrypt the encrypted strings in Xloader 4.3 in Python. The code is\r\navailable in our GitHub repository here.\r\nEncrypted C2s\r\nThe Xloader configuration contains a C2 that is stored separately from another list of C2 domains. The C2 that is\r\nstored separately was thought to be Xloader’s real C2 and the other C2s were used as decoys. However, in more\r\nrecent versions of Xloader, real C2s are likely hidden among the list of decoy C2s. In fact, the author behind\r\nXloader has made significant efforts to protect the list of C2s that were previously thought to be decoys.\r\nHardcoded C2\r\nThe code shown in Figure 10 is responsible for decrypting the hardcoded Xloader C2.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 9 of 15\n\nFigure 10. Hardcoded C2 decryption (Xloader version 4.3)\r\nThe code in Figure 10 combines a set of operations based on Xloader’s various encryption algorithms and the data\r\nstored in the PUSHEBP data blocks to generate the encryption key necessary to decrypt the hardcoded C2 (which\r\nis stored in the PUSHEBP data block 5).\r\nC2 List\r\nAs previously mentioned, there is another list of C2s that may contain decoy C2s and real C2s. In Formbook and\r\nin earlier versions of Xloader, these were stored as an encrypted string with no additional layers of encryption. In\r\nXloader 2.9, the developers introduced an additional custom RC4 layer and Base64 encoding for the C2 list as\r\nshown in Figure 11.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 10 of 15\n\nFigure 11. Additional encryption layer for the C2 list (Xloader version 2.9)\r\nIn Figure 11, the function StringsDecryptor2 decrypts the first layer of the encrypted strings. In version 2.9, an\r\nadditional Base64 layer is decoded followed by a layer of custom RC4 decryption. In Xloader version 4.3, they\r\nhave added an additional encryption layer to this C2 list. Figure 12 shows the code responsible for decrypting\r\nthese new layers.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 11 of 15\n\nFigure 12. New encryption layer for Xloader’s C2 list (Xloader version 4.3)\r\nIn the new version, the C2 list is first Base64 decoded and a custom RC4 layer is decrypted. A table of 4 byte keys\r\nis built on the stack. Each position of the table corresponds to a C2. Once decrypted, this custom RC4 layer is\r\nBase64 encoded again. After the new additional decryption layer is complete, Xloader decrypts the same layers as\r\nversion 2.9: decoding the Base64 layer again and decrypting an additional custom RC4 layer with a key stored in\r\na sub-structure of the ConfigObj. The way that this key (for the last RC4 layer) is generated has also changed in\r\nXloader 4.3. Figure 13 shows the code generating the RC4 key for the last encryption layer of the C2 list.\r\nFigure 13. Key generation for the final encryption layer of the C2 list (Xloader version 4.3)\r\nAs shown in Figure 13, the key is built on the stack and it is XORed with a value from the ConfigObj that was\r\ninitialized previously in a different part of the code. Once this last layer is decrypted, the plaintext C2s are\r\nobtained.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 12 of 15\n\nBranch ID and Version Number\r\nIn previous versions, the Xloader branch ID and version number were sent in the registration packet to the C2.\r\nThe format of the registration packet (before the last two RC4 layers) was the following:\r\nXLNG Bot ID Version Number Operating System Base64(Username)\r\nXLNG is the tag for the Xloader branch (FBNG was the branch ID for Formbook).\r\nIn Xloader version 4.3, the registration packet sent to the C2 includes an additional encryption layer as shown in\r\nFigure 14.\r\nFigure 14. Xloader 4.3 Registration packet with additional PKT2 layer\r\nThis new encryption layer is marked with the tag PKT2. Communications are performed in the context\r\nof explorer.exe (previously injected). However, this registration packet is built in the first injected process (a\r\nhollow process) and copied to the context of explorer together with the rest of the injected code. That first injected\r\nprocess exits after injecting into explorer, so the registration packet under the last encryption layer marked with\r\nthe PKT2 tag is no longer in plaintext after the first injected process terminates.\r\nThe PKT2 packet is built in one of the NO-PUSHEBP encrypted functions. That function is decrypted and\r\nexecuted in the context of the first injected process. The code first builds a string with the same format as the\r\nregistration packet in previous Xloader versions as shown in Figure 15.\r\nFigure 15. Registration packet with the new PKT2 encryption layer\r\nHowever, as we can see in Figure 15, Xloader 4.3 introduces a NULL character separating the bot ID and the\r\nmalware version number. This added NULL byte is likely a coding error.\r\nFigure 16 shows how the first registration packet is constructed (marked with XLNG tag) and encrypted with RC4\r\nand encoded with Base64, and then concatenated to the PKT2 tag to generate the final registration packet.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 13 of 15\n\nFigure 16. Xloader version 4.3 registration packet construction\r\nHowever, because of the coding error previously mentioned (an extra NULL character after the bot ID) the final\r\nregistration data contains just two fields for the XLNG branch and bot ID as shown below:\r\nXLNG Bot ID\r\nThe PKT2 tag is then prepended with the RC4 and Base64 encoded data as follows:\r\nPKT2 RC4_BASE64(registration_data)\r\nAs a result, the version_number, operating_system and user_name is never sent to the C2. This bug will likely be\r\nfixed in future versions of the malware.\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 14 of 15\n\nFigure 16 also shows that the branch ID and version number are no longer hardcoded unlike previous versions,\r\nwith the encrypted version number and branch ID decrypted with an XOR key (0x3c).\r\nSource: https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nhttps://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43\r\nPage 15 of 15",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://www.zscaler.com/blogs/security-research/technical-analysis-xloaders-code-obfuscation-version-43"
	],
	"report_names": [
		"technical-analysis-xloaders-code-obfuscation-version-43"
	],
	"threat_actors": [],
	"ts_created_at": 1775434299,
	"ts_updated_at": 1775826787,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/849317054564c7d84356f6b9679818b2ca2d6401.pdf",
		"text": "https://archive.orkl.eu/849317054564c7d84356f6b9679818b2ca2d6401.txt",
		"img": "https://archive.orkl.eu/849317054564c7d84356f6b9679818b2ca2d6401.jpg"
	}
}