What’s up Emotet? Archived: 2026-04-05 22:39:53 UTC Emotet is one of the most widespread and havoc-wreaking malware families currently out there. Due to its modular structure, it’s able to easily evolve over time and gain new features without having to modify the core. Its first version dates back to 2014. Back then it was primarily a banking trojan. These days Emotet is known mostly for its spamming capabilities and as a delivery mechanism of other malware strains. It has recently undergone a substantial change in communication protocol and obfuscation techniques. This might be a response to the release of tools allowing researchers to easily download payloads from the C2 servers1 and detect machines infected with Emotet2. In this article, we will go over the standard Emotet features and take a look at some of the changes that have been spotted. Sample analysed: 500221e174762c63829c2ea9718ca44f Unpacked Emotet core: e8143ef2821741cff199eeda513225d7 Table of Contents Anti-analysis features Extracting static configuration Communication Summary & References Anti-analysis features Code Flow Obfuscation In order to make reverse engineering more difficult for researchers, a VM-like obfuscation was implemented. To achieve this, every function was split into basic blocks which were then repositioned into a simple state machine. Demangling the functions back to their original form is nontrivial, although possible. However, it was found that reverse engineering obfuscated binaries is still possible. Function graph of the main function Encrypted Strings https://www.cert.pl/en/news/single/whats-up-emotet/ Page 1 of 10 All used strings are encrypted almost like in the previous versions. Most noticeable difference is related to the xor key – it’s not passed as a parameter anymore. Instead, it’s located at the beginning of the data to be decrypted. Example encrypted string Encrypted string structure One can decrypt those strings pretty easily using a quick Python script. from malduck import xor, idamem, p32 ida = idamem() def decrypt_string(addr): key = ida.uint32v(addr) str_len = ida.uint32v(addr + 4) ^ key str_data = [p32(ida.uint32v(addr + 8 + i * 4) ^ key) for i in range(str_len//4 + 1)] return b"".join(str_data)[:str_len] Python function used for decrypting strings WinAPI Another method of slowing down the analysis that the malware authors really like is hiding the Window API calls by replacing them with a custom lookup function. Executing API calls using hash lookups isn’t a new thing in Emotet. In contrast to previous versions however, the new version fetches them on a need-to-use basis instead of loading them all at once and storing them in a data section. https://www.cert.pl/en/news/single/whats-up-emotet/ Page 2 of 10 Api lookup function being used Simple hash function used for function name hashing It can be solved rather easily. All one has to do is just reimplement the hashing function, iterate over common WinAPI function names and create an enum with all recovered hashes. from ida_bytes import dec_flag from malduck import UInt32 import glob def hash(string): s = UInt32(0) for l in string: s = (s << 16) + (s << 6) + ord(l) - s # the xor dword varies between binaries return s ^ 0x6C60CFB2 data = [] libraries = glob.glob('hash-lookup/libraries/*') for f in libraries: data += open(f).read().split('\n') hashes = {hash(x): x for x in data} https://www.cert.pl/en/news/single/whats-up-emotet/ Page 3 of 10 api_hashes = [-1023523628,-1028205339,-1078414159,-1104536776, #... api_hashes = list(set(api_hashes)) # add an enum containing all resolved api hashes enum_id = add_enum(-1, "api_commands", dec_flag()) for api_hash in api_hashes: # signed -> unsigned num = (api_hash + 2**32) % (2**32) add_enum_member(enum_id, hashes[num], num, -1) It’s very important to set the accepted type in find_api to the newly-created enum type. This will allow IDA to automatically place the enum values in function calls. Comparison of a single function before and after applying the enum type Deleting previous versions of itself While analysing the encrypted strings, one of lists of keywords present in earlier versions was noticed. It was used to generate random system paths in which to put the Emotet core binary. This seemed weird because this method was replaced with completely random file paths. After closer inspection and confirmation by @JRoosen3 it turned out that these keywords are used to delete Emotet binaries that were dropped there by previous versions. Part of the function used for deleting older versions of Emotet Public key The RSA public key is stored as a regular encrypted string. It’s embedded in the binary in order to encrypt the AES keys used for secure communication with the C2. This will deter all communication eavesdropping attempts. The public key isn’t stored in plaintext, but fetched like rest of the encrypted strings. Thus, it can be decrypted using the same script: https://www.cert.pl/en/news/single/whats-up-emotet/ Page 4 of 10 Python>decrypt_string(0x0040A2A0) b"0h\x02a\x00\xd4\x0ep\x12\xaf\x87\x9cD[\xb5\\'\xdbh\xb6\xc8\xdd\x07\x0b\x80r\\RCG\xb4\xf5\xf9 bytes: output = [0] * decompressed_len comp_pos = 0 decomp_pos = 0 while comp_pos < len(input_data): code_word = input_data[comp_pos] comp_pos += 1 if code_word <= 0x1f: for _ in range(code_word+1): output[decomp_pos] = input_data[comp_pos] decomp_pos += 1 comp_pos += 1 else: copy_len = code_word >> 5 if copy_len == 7: copy_len += input_data[comp_pos] comp_pos += 1 dict_dist = ((code_word & 0x1f) << 8) | input_data[comp_pos] comp_pos += 1 copy_len += 2 https://www.cert.pl/en/news/single/whats-up-emotet/ Page 8 of 10 decomp_dist_begins_pos = decomp_pos -1 - dict_dist for i in range(copy_len): output[decomp_pos] = output[decomp_dist_begins_pos + i] decomp_pos += 1 return bytes(output) Register packet structure As mentioned earlier, the protobuf structures have been abandoned in favour of custom structures. One of the observed packet types is the command used to register the bot on the botnet and receive modules to execute. The register packet structure can be easily presented using the following c struct: struct hello_packet { uint32 bot_name_len; char bot_name[bot_name_len]; uint32 os_version; uint32 session_id; uint32 magic; uint32 some_another_magic; uint32 proclist_len; char proclist[proclist_len]; }; Register packet dissection presented using dissect.cstruct Summary The goal of this article was to help other researchers with their Emotet research after recent changes. Emotet has once again proven to be an advanced threat capable of adapting and evolving quickly in order to wreak more havoc. This article barely scratches the surface of the Emotet’s inner workings, and should be treated as a good entry point, not as a complete guide. We encourage everyone to use this information, and hopefully share further results and/or discrupt the botnet’s operations. https://www.cert.pl/en/news/single/whats-up-emotet/ Page 9 of 10 Further reading https://twitter.com/cryptolaemus1 https://github.com/d00rt/emotet_research https://blog.malwarebytes.com/botnets/2019/09/emotet-is-back-botnet-springs-back-to-life-with-new-spam-campaign/ https://www.cert.pl/en/news/single/analysis-of-emotet-v4/ References 1: https://d00rt.github.io/emotet_network_protocol/ 2: https://github.com/JPCERTCC/EmoCheck 3: https://twitter.com/JRoosen/status/1225188513584467968 4: https://github.com/mistydemeo/quickbms/blob/master/unz.c#L5501 Source: https://www.cert.pl/en/news/single/whats-up-emotet/ https://www.cert.pl/en/news/single/whats-up-emotet/ Page 10 of 10