{
	"id": "f12ce4ce-a0ee-4252-875f-74cfe36c63c9",
	"created_at": "2026-04-06T00:08:55.542139Z",
	"updated_at": "2026-04-10T13:11:26.599837Z",
	"deleted_at": null,
	"sha1_hash": "d5508c77331814f7aa13652ed035b6b5f7b9aaca",
	"title": "What’s up Emotet?",
	"llm_title": "",
	"authors": "",
	"file_creation_date": "0001-01-01T00:00:00Z",
	"file_modification_date": "0001-01-01T00:00:00Z",
	"file_size": 1313783,
	"plain_text": "What’s up Emotet?\r\nArchived: 2026-04-05 22:39:53 UTC\r\nEmotet is one of the most widespread and havoc-wreaking malware families currently out there. Due to its\r\nmodular structure, it’s able to easily evolve over time and gain new features without having to modify the\r\ncore.\r\nIts first version dates back to 2014. Back then it was primarily a banking trojan. These days Emotet is known\r\nmostly for its spamming capabilities and as a delivery mechanism of other malware strains.\r\nIt has recently undergone a substantial change in communication protocol and obfuscation techniques. This\r\nmight be a response to the release of tools allowing researchers to easily download payloads from the C2\r\nservers1 and detect machines infected with Emotet2.\r\nIn this article, we will go over the standard Emotet features and take a look at some of the changes that have\r\nbeen spotted.\r\nSample analysed: 500221e174762c63829c2ea9718ca44f\r\nUnpacked Emotet core: e8143ef2821741cff199eeda513225d7\r\nTable of Contents\r\nAnti-analysis features\r\nExtracting static configuration\r\nCommunication\r\nSummary \u0026 References\r\nAnti-analysis features\r\nCode Flow Obfuscation\r\nIn order to make reverse engineering more difficult for researchers, a VM-like obfuscation was implemented.\r\nTo achieve this, every function was split into basic blocks which were then repositioned into a simple state\r\nmachine.\r\nDemangling the functions back to their original form is nontrivial, although possible. However, it was found\r\nthat reverse engineering obfuscated binaries is still possible.\r\nFunction graph of the main function\r\nEncrypted Strings\r\nhttps://www.cert.pl/en/news/single/whats-up-emotet/\r\nPage 1 of 10\n\nAll used strings are encrypted almost like in the previous versions. Most noticeable difference is related to the\r\nxor key – it’s not passed as a parameter anymore. Instead, it’s located at the beginning of the data to be\r\ndecrypted.\r\nExample encrypted string\r\nEncrypted string structure\r\nOne can decrypt those strings pretty easily using a quick Python script.\r\nfrom malduck import xor, idamem, p32\r\nida = idamem()\r\ndef decrypt_string(addr):\r\nkey = ida.uint32v(addr)\r\nstr_len = ida.uint32v(addr + 4) ^ key\r\nstr_data = [p32(ida.uint32v(addr + 8 + i * 4) ^ key) for i in range(str_len//4 + 1)]\r\nreturn b\"\".join(str_data)[:str_len]\r\nPython function used for decrypting strings\r\nWinAPI\r\nAnother method of slowing down the analysis that the malware authors really like is hiding the Window API\r\ncalls by replacing them with a custom lookup function.\r\nExecuting API calls using hash lookups isn’t a new thing in Emotet. In contrast to previous versions however,\r\nthe new version fetches them on a need-to-use basis instead of loading them all at once and storing them in a\r\ndata section.\r\nhttps://www.cert.pl/en/news/single/whats-up-emotet/\r\nPage 2 of 10\n\nApi lookup function being used\r\nSimple hash function used for function name hashing\r\nIt can be solved rather easily. All one has to do is just reimplement the hashing function, iterate over common\r\nWinAPI function names and create an enum with all recovered hashes.\r\nfrom ida_bytes import dec_flag\r\nfrom malduck import UInt32\r\nimport glob\r\ndef hash(string):\r\ns = UInt32(0)\r\nfor l in string:\r\ns = (s \u003c\u003c 16) + (s \u003c\u003c 6) + ord(l) - s\r\n# the xor dword varies between binaries\r\nreturn s ^ 0x6C60CFB2\r\ndata = []\r\nlibraries = glob.glob('hash-lookup/libraries/*')\r\nfor f in libraries:\r\ndata += open(f).read().split('\\n')\r\nhashes = {hash(x): x for x in data}\r\nhttps://www.cert.pl/en/news/single/whats-up-emotet/\r\nPage 3 of 10\n\napi_hashes = [-1023523628,-1028205339,-1078414159,-1104536776, #...\r\napi_hashes = list(set(api_hashes))\r\n# add an enum containing all resolved api hashes\r\nenum_id = add_enum(-1, \"api_commands\", dec_flag())\r\nfor api_hash in api_hashes:\r\n# signed -\u003e unsigned\r\nnum = (api_hash + 2**32) % (2**32)\r\nadd_enum_member(enum_id, hashes[num], num, -1)\r\nIt’s very important to set the accepted type in find_api to the newly-created enum type. This will allow IDA to\r\nautomatically place the enum values in function calls.\r\nComparison of a single function before and after applying the enum type\r\nDeleting previous versions of itself\r\nWhile analysing the encrypted strings, one of lists of keywords present in earlier versions was noticed. It was\r\nused to generate random system paths in which to put the Emotet core binary. This seemed weird because this\r\nmethod was replaced with completely random file paths.\r\nAfter closer inspection and confirmation by @JRoosen3 it turned out that these keywords are used to delete\r\nEmotet binaries that were dropped there by previous versions.\r\nPart of the function used for deleting older versions of Emotet\r\nPublic key\r\nThe RSA public key is stored as a regular encrypted string. It’s embedded in the binary in order to encrypt the\r\nAES keys used for secure communication with the C2. This will deter all communication eavesdropping\r\nattempts.\r\nThe public key isn’t stored in plaintext, but fetched like rest of the encrypted strings. Thus, it can be decrypted\r\nusing the same script:\r\nhttps://www.cert.pl/en/news/single/whats-up-emotet/\r\nPage 4 of 10\n\nPython\u003edecrypt_string(0x0040A2A0)\r\nb\"0h\\x02a\\x00\\xd4\\x0ep\\x12\\xaf\\x87\\x9cD[\\xb5\\\\'\\xdbh\\xb6\\xc8\\xdd\\x07\\x0b\\x80r\\\\RCG\\xb4\\xf5\\xf9\u003cWNw\\x96Ki[t\\xd1\\xaf0\\x16\\xeb|\\xad\r\n{\\x85\\x0f\\xb4\\x82\\x12~L\\xe3\\x10\\x8b\\xc6\\xd6\\xff\\xd96}d\\xd0\\x9d\\x0f|T\\xf1lQ^PK7\\x03\\x02\\x03\\x01\\x00\\x01\"\r\nThe resulting key is encoded using DER format and can be parsed using the following script:\r\nfrom pyasn1.codec.der import decoder\r\nfrom Cryptodome.PublicKey import RSA\r\ndecrypted = decrypt_string(0x0040A2A0)\r\nattr_value = decoder.decode(decrypted)[0]\r\nn = int(attr_value.getComponentByPosition(0))\r\ne = int(attr_value.getComponentByPosition(1))\r\npubkey = RSA.construct((n, e))\r\n# example: export to PEM\r\nprint(pubkey.exportKey(format='PEM'))\r\n-----BEGIN PUBLIC KEY-----\r\nMHwwDQYJKoZIhvcNAQEBBQADawAwaAJhANQOcBKvh5xEW7VcJ9totsjdBwuAclxS\r\nQ0e09fk8V053lktpW3TRrzAW63yt6j1KWnyxMrU3igFXypBoI4lVNmkje4UPtIIS\r\nfkzjEIvG1v/ZNn1k0J0PfFTxbFFeUEs3AwIDAQAB\r\n-----END PUBLIC KEY-----\r\nResult PEM-encoded public key\r\nC2 list\r\nThe method of retrieving C2 hosts has not changed. They are still stored as 8-byte blocks containing packed\r\nIP address and port.\r\nCommunication\r\nhttps://www.cert.pl/en/news/single/whats-up-emotet/\r\nPage 5 of 10\n\nPath generation\r\nKeyword-generated paths have been abandoned in favour of fully random ones.\r\nEach new path consists of a random amount of alphanumeric segments separated by slashes.\r\nPath generation algorithm\r\nAdditionally, instead of simply uploading the payload data inside the POST body, it is now sent as a file\r\nupload using multipart/form-data enctype.\r\nThe method of generating random attachment names and filenames is quite similar to the one used in\r\ngenerating URL paths.\r\nPart of function responsible for encoding the data as a file\r\nExample request and response dissected in Wireshark\r\nRequest structure\r\nhttps://www.cert.pl/en/news/single/whats-up-emotet/\r\nPage 6 of 10\n\nThis the part that has gone under the most changes. Protocol buffers have been dropped in favour of a custom\r\nbinary protocol.\r\nPacket encryption\r\nJust like in previous versions, all packets are encrypted using AES-CBC with 16 nullbytes as IV. The AES key\r\nis generated using the CryptGenKey function, encrypted using the decoded RSA public key and appended to\r\neach request.\r\nAdditionally, an SHA-1 hash of the packets contents is also sent for integrity verification purposes.\r\nThe packet encryption structure\r\nPacket structure\r\nCommand packets are compressed and encapsulated in a simple packet structure.\r\nstruct packet {\r\nuint32 command;\r\nuint32 packet_len;\r\nchar packet_data[packet_len];\r\n};\r\nOuter packet dissection presented using dissect.cstruct\r\nPacket compression\r\nAnother change is the compression algorithm used for compressing and decompressing packet body.\r\nHistorically, the zlib algorithm has been used for that. It’s hard to pinpoint the exact algorithm that is now\r\nused, but the procedure evolution_unpack4 from quickbms project was found to correctly uncompress the data\r\nreceived from the C2 servers\r\nhttps://www.cert.pl/en/news/single/whats-up-emotet/\r\nPage 7 of 10\n\nPseudocode of the new algorithm used to uncompress packets\r\nIt was decided to reimplement the uncompression procedure in Python, the resulting script is listed below.\r\ndef uncompress(input_data: bytes, decompressed_len: int) -\u003e bytes:\r\noutput = [0] * decompressed_len\r\ncomp_pos = 0\r\ndecomp_pos = 0\r\nwhile comp_pos \u003c len(input_data):\r\ncode_word = input_data[comp_pos]\r\ncomp_pos += 1\r\nif code_word \u003c= 0x1f:\r\nfor _ in range(code_word+1):\r\noutput[decomp_pos] = input_data[comp_pos]\r\ndecomp_pos += 1\r\ncomp_pos += 1\r\nelse:\r\ncopy_len = code_word \u003e\u003e 5\r\nif copy_len == 7:\r\ncopy_len += input_data[comp_pos]\r\ncomp_pos += 1\r\ndict_dist = ((code_word \u0026 0x1f) \u003c\u003c 8) | input_data[comp_pos]\r\ncomp_pos += 1\r\ncopy_len += 2\r\nhttps://www.cert.pl/en/news/single/whats-up-emotet/\r\nPage 8 of 10\n\ndecomp_dist_begins_pos = decomp_pos -1 - dict_dist\r\nfor i in range(copy_len):\r\noutput[decomp_pos] = output[decomp_dist_begins_pos + i]\r\ndecomp_pos += 1\r\nreturn bytes(output)\r\nRegister packet structure\r\nAs mentioned earlier, the protobuf structures have been abandoned in favour of custom structures.\r\nOne of the observed packet types is the command used to register the bot on the botnet and receive modules to\r\nexecute.\r\nThe register packet structure can be easily presented using the following c struct:\r\nstruct hello_packet {\r\nuint32 bot_name_len;\r\nchar bot_name[bot_name_len];\r\nuint32 os_version;\r\nuint32 session_id;\r\nuint32 magic;\r\nuint32 some_another_magic;\r\nuint32 proclist_len;\r\nchar proclist[proclist_len];\r\n};\r\nRegister packet dissection presented using dissect.cstruct\r\nSummary\r\nThe goal of this article was to help other researchers with their Emotet research after recent changes.\r\nEmotet has once again proven to be an advanced threat capable of adapting and evolving quickly in order to\r\nwreak more havoc.\r\nThis article barely scratches the surface of the Emotet’s inner workings, and should be treated as a good entry\r\npoint, not as a complete guide. We encourage everyone to use this information, and hopefully share further\r\nresults and/or discrupt the botnet’s operations.\r\nhttps://www.cert.pl/en/news/single/whats-up-emotet/\r\nPage 9 of 10\n\nFurther reading\r\nhttps://twitter.com/cryptolaemus1\r\nhttps://github.com/d00rt/emotet_research\r\nhttps://blog.malwarebytes.com/botnets/2019/09/emotet-is-back-botnet-springs-back-to-life-with-new-spam-campaign/\r\nhttps://www.cert.pl/en/news/single/analysis-of-emotet-v4/\r\nReferences\r\n1: https://d00rt.github.io/emotet_network_protocol/\r\n2: https://github.com/JPCERTCC/EmoCheck\r\n3: https://twitter.com/JRoosen/status/1225188513584467968\r\n4: https://github.com/mistydemeo/quickbms/blob/master/unz.c#L5501\r\nSource: https://www.cert.pl/en/news/single/whats-up-emotet/\r\nhttps://www.cert.pl/en/news/single/whats-up-emotet/\r\nPage 10 of 10",
	"extraction_quality": 1,
	"language": "EN",
	"sources": [
		"Malpedia"
	],
	"origins": [
		"web"
	],
	"references": [
		"https://www.cert.pl/en/news/single/whats-up-emotet/"
	],
	"report_names": [
		"whats-up-emotet"
	],
	"threat_actors": [],
	"ts_created_at": 1775434135,
	"ts_updated_at": 1775826686,
	"ts_creation_date": 0,
	"ts_modification_date": 0,
	"files": {
		"pdf": "https://archive.orkl.eu/d5508c77331814f7aa13652ed035b6b5f7b9aaca.pdf",
		"text": "https://archive.orkl.eu/d5508c77331814f7aa13652ed035b6b5f7b9aaca.txt",
		"img": "https://archive.orkl.eu/d5508c77331814f7aa13652ed035b6b5f7b9aaca.jpg"
	}
}