Reverse Engineering and Malware Analysis

by crudd

start:
call [words]
cmp rax, [challenges]
jz [tools]
mov rax, [links]
ret
1 hits

Getting Intimate with Emotet

Part 1: Dissecting the Emotet loader

Emotet is malware that starts out as a spam email, usually sending some kind of document as an attachment (PDF, word, XML) that contains a malicious script that will download the Emotet malware from a (often compromised) URL.

This sample of Emote was originally downloaded from a link posted on Twitter on 11/07/18. The sample that I will be using was named 95.exe and has the hash of cf2cecf0b7c8dfbde7d2e98afb7a1926442f8a4e30a0a951750513359df74470 (file size: 132 KB). If you are looking at a different sample than me, your code will be different (random API imports, various levels of obfuscation, different jmp/call locations), but I will discuss some methods that you can use to follow along with any Emotet sample.

First, we will do some initial analysis. There are various tools and methods to do this. I usually fire it up in IDA and load it up in my favorite PE viewer/hexeditor of the day. For a lot of malware, running strings and/or scanning with a hex editor aren't very useful. This time we can see a bunch of WinApis (some of which look interesting), a few uninteresting or normal looking Windows strings, and a bunch of 4 char meaningless gibberish.

Although I am not using them much in this part of the analysis, I will get a few tools started up to help keep an eye on whats going on. I will be using Autoruns, ProcMon, Process Explorer, and FakeNet-NG. I start by saving the current setup in Autoruns, start FakeNet and Process Explorer. The output from these three tools will be covered more in the next part(s) of this analysis.

On to the code...

Now that we have our monitoring tools setup and the file loaded in IDA, we can load the executable up in our debugger of choice. Now would be a good time to take a VM snapshot so that we can revert back to this state when we need to (and it will happen a lot). Also, Emotet uses ASLR (address space layout randomization) which means the program (and its various stages) will be loaded at different addresses in memory. This can make debugging the malware more confusing and difficult to analyze. We can edit the PE header to not load the file into memory using ASLR, but this does not affect the other stages of the loader and they are still loaded at random addresses. While the section/segment address is different, the file offset will remain the same and I will not disable ASLR for this analysis and instead will label the offsets like: section.offset (ie loader.249D).

NOTE: Although patching the file to not use ASLR will make it load the sections at the same location FOR THIS FILE, for different versions/variations of the Emotet loader the offsets may be different.

Many VM resets were initiated during this study of Emotet. Stepping over the wrong function or breakpointing on the wrong API(s) can lead to a quick infection, so the VM snapshots will come in very handy. Snapshot often (or maybe give this a try: https://twitter.com/mayahustle/status/1065351756345995264>). The file also deletes itself, so you may want to make a backup.

For the most part, the loader appears to be trash code, making random WinAPI calls (different for each variation of the loader) to the random imported APIs. There are some interesting looking APIs in our variant (shown in the image above: DeleteTimerQueueTimer, WritePrivateProfileStringA, GetCommandLineW, OpenFileById, GetNamedPipeServerSessionId, GetCurrentProcessId, GetCurrentThreadId), but it does not look like any of the imported APIs are actually used by the malware and are probably there to help conceal the malware or hamper analysis.

Finally at loader.196A, there is a 'call eax' that calls LdrGetProcedureAddress. The first time it is called it retrieves GetNamedPipeClientSessionId, which it then calls. But the call to GetNamedPipClientSessionId appears to fail (called at loader.2D7E). I bet we will run into this again later on, but for now it does not do anything useful. LdrGetProcedureAddress is called again, but this time it gets VirtualAlloc.

The first call to VirtualAlloc is at loader.22BA and is filled at loader.2194 with bytes from the DATA section. I am not sure what this is used for yet (if anything), but we should know by the end of this series (We could set a hardware breakpoint to track down where it is used, but for now we will just ignore this section).

Then there is another call to LdrGetProcedureAddress to retrieve VirtualAlloc again, and then the second VirtualAlloc'd space is created at loader.1E00. This space is the next stage of the loader (I should look to see where/how this is populated). Once the second VirtualAlloc's space is populated, execution is transferred to stage1 at loader.249D - call eax

If you have a different variant of the loader, you can set breakpoint(s) on LdrGetProcedureAddress/VirtualAlloc and then set a hardware breakpoint on the second VirtualAlloc'd space to track down the jump to the first stage.

Stage 1

After the call eax we end up at stage1.2C01. Stepping through, this stage copies some strings around and then jumps to main handler function, located at stage1.23b6. As we will see, this handler is also used in the later stages of the malware.

Stage 1 of the loader's main purpose seems to be to create two more VirutalAlloc'd spaces, which will be comprise stage 2 of the loader. Our breakpoint on VirtualAlloc should get us everything we need. The first call to VirtualAlloc is at stage1.1F16 and the second is at stage1.266E. The jump to stage 2 is located at stage1.2A41 and it jumps to the first VirtualAlloc'd space that was created in this stage. I will refer to this as stage2a and the jump lands us at stage2a.F290.

Stage 2

Stage 2 gets a bit more interesting (although we are only getting started with our infection). Stage 2 consist of the two VirtualAlloc'd spaces created in stage1 and jumps back and forth between them and the main handler function of stage1. This makes debugging a bit more difficult to follow, dumping the memory and loading it in IDA can help keep track of things. Stage2b does not seem to contain much relevant code and is just used to jump/call around to Stage1 and Stage2a. Stage 2 starts at stage2a.F290 and we will see the below interesting looking code.

The first call in stage 2 resolves ntdll by hash and then resolves the APIs that the malware needs from ntdll.dll. The second call does the same thing but for kernel32.dll (and is used later on to resolve a few more dll's (advapi, user32, shell32)).

The next function will finally get us somewhere a little more action packed. At stage2a.1099 the program makes an call to CreateMutexW (mutex name is created by beforehand, not sure how?). This call to CreateMutex is to check whether the process is already running. The mutex is created and then GetLastError is called to see if the last error was ERROR_ALREADY_EXISTS. We will talk about this a bit more in a moment, but for now GetLastError returns 0 (ERROR_SUCCESS) and we take the first jump (the jne).

After the jump, the handle to the created mutex is closed (at stage2a.1111). A second call to CreateMutex is made at stage2a.1160 using the same mutex name (calculated/stored in decrypted HeapAlloc'd space?). An event (CreateEventW) is created at Stage2a.11B3. GetModuleFileName is called at 2a.11cd to retrieve the name and path of the current file. Then CreateProcessW is called on this file at stage2a.2055. This creates another process of the same file (95.exe). The first process then jumps down too stage2a.11F2 and calls WaitForSingleObject and waits for our second process. Setting a break point at the next instruction will allow us to regain control after the WaitForSingleObject, but I believe it is safe to just let the first process continue and run, as it finishes up and just calls ExitProcess (stage2a.F2B0). At this point, the loader has transferred control over to the new process and is waiting.
NOTE: to gain control of the second process, we can set a breakpoint on CreateProcessW and change dwCreationFlags to CREATE_SUSPENDED or we could use WinDbg and set '.childdbg 1'.

We can easily jump to the second process by setting .childdbg 1 in WinDbg, and a breakpoint somewhere to regain control. I use VirtualAlloc and then CreateMutexW once the break on VirtualAlloc happens. This will allow us to gain control of the second process, without working through the first one, and land right at the first CreateMutex call. Something similar can be done using CreateProcessW also.

The second process seems to follow the same path as the first. LdrGetProcedureAddress is used to located GetNamedPipeClientSessionId again, and it is called and fails. The two VirtualAlloc'd spaces are created (unknown space and the Stage 1), and stage1 is called. Two more spaces are allocated for Stage2 and the jump to Stage 2 is followed. The paths of the two processes differ after the first call to CreateMutexW. Since the mutex already exists, the call to GetLastError will return ERROR_ALREADY_EXISTS. Here the code diverges from the previous executable and follows the second (non-conditional) jump, making its way back to the handler at stage1.23B6. It goes through this loop building the needed code to drop the malware and setup persistance.

Emotet chooses a different method of persistence, depending on wheter it has administrative rights or not. While running with administrative rights, it drops the file in the Windows\SysWOW64 folder and creates and starts a service. When it is executed without admin privileges, it creates a registry key to start the malware and drops the file at \User\username\App Data\Local\Microsoft\Windows (this may be different for different variations of the loader). In both cases, whether it is setup to run as a service or started by a registry key, the 'dropped' file is the same as the original executable but with a different filename. The filename seems to be generated using some unique values (ComputerName,drive info, ?) because the filename is different on various sytems (my VM: historyaddin.exe vs SndBox: noticesnirmala.exe vs Any.Run: lpiograd.exe). Emotet then starts the newly dropped file. And this seems like a good place to end the first part of the analysis.
GetWindowDir @ .CB35
GetVolumeInfo @ .CB7B
CreateMutex @ .CBD5 named GLOBAL\
WaitForSingleEvent @ CCC3
Another GLOBAL CreateMutex @ .CC35
CreateEvent
SetEvent resumes first process
SignalObjectAndWait @ .CCF6 and it switches back to the original process
.FB09
.F821
CreateFileW @ .F545
CreateFileMappingW @ .F55E
MapViewOfFile @ .F574
GetFileSize
RtlComputeCrc32
GetComputerName F5D6
F4BA - interesting strings
SHFileOperationW @ .12E7
GetTempFolder/File
OpenSCManager @ .F93A
CreateServiceW @.F9A8
ChangeServiceConfig2W @ .F9DB
StartService @ .F9F1S

Coming in Part 2:

Part 2 will start off at the begining of the newly created service. Some things that I hope to uncover in the next installment will be: The usage of the call to GetNamedPipeClientSessionId
The purpose of the first VirtualAlloc'd space
Why is 'somy' copied to alloc'd spaces?
Find where original .exe is deleted
Start tracking the network activity and Command and Control servers
HeapAlloc'd spaces (GetProcessHeap and RtlAllocateHeap from Stage1 jmp ecx handler) seem to hold crypted mutex/event names (and possibly dropped filename and other data).

You can see there are still some unanswered question, but I hope to uncover their serets in the next part of this analysis. If you happen to have any insight to any of the questions or notice any errors (things I have missed or misinterpreted), feel free to drop me an email or contact me on twitter.

Thanks to 0x22 for some insights and to the Emotet Hunters on Twitter that keep me supplied with fresh samples.