← Reports

Ransomware · Ransomworm · Worm

WannaCry - invoice_greenanimals.pdf.exe

Author
Moise Medici
Updated
15 Feb 2026 · Completed
Difficulty
Medium
Platform
Windows
Capabilities
Command and Control C2 CommunicationCommand Execution via Powershell Cmd BashFile EncryptionPersistence Mechanisms
Tags
C++WannaCryptor

Question 8 and Question 11

I start looking in the original sample where the string mssecsvc.exe is found in Ghidra. It is shown in two locations:

  • 0040e048
  • 00417350

In both cases it is in the middle of a big string, without references to those addresses. The first address is part of the string DAT_0040b020, the second is part of the string DAT_0040f080. They have been renamed as long_str_with_mssecsvc.exe and long_str_with_mssecsvc.exe2. Opening the Symbol Reference in Ghidra, and filtering for “long_str” shows that both the strings are called in the same locations:

  • 00407ab6
  • 00407abd

These locations are both part of the same function, and are just a couple of lines apart. The function is FUN_00407a20. A few lines after the beginning of the function, there is a first if condition to initialize ESI to one of the two strings.

invoice_greenanimals.pdf.exe
LAB_00407a84 XREF[1]: 00407a6d (j)
00407a84 XOR EDX ,EDX
LAB_00407a86 XREF[1]: 00407acd (j)
00407a86 TEST EDX ,EDX
00407a88 MOV ESI ,long_str_with_mssecsvc.exe = 00905A4Dh
00407a8d JZ LAB_00407a94
00407a8f MOV ESI ,long_str_with_mssecsvc.exe2 = 00905A4Dh

However this test is inside a do.. while loop, as shown in the Function Graph. Currently both strings are treated as if they were the same.

The Ghidra function graph, where both mssecsvc.exe strings sit in a do loop that repeats while EDX is below 2.
Fig. 29: The Ghidra function graph, where both mssecsvc.exe strings sit in a do loop that repeats while EDX is below 2.

In the decompiled code a few variables have been renamed to make it easier to follow, since the rename is mirrored in the assembly code:

  • DAT_0070f864 in hGlobalAlloc1
  • DAT_0070f868 in hGlobalAlloc2
  • iVar4 (EDX) in i since it is used as the incrementer for the do.. while loop
  • the variable where the long_str variables are assigned (ESI) is now p_long_str

The point here is to understand what the loop is going to do with the long_str_with variables.

During the first iteration hGlobalAlloc[0] is loaded in EDI and in “local_8”, a local variable defined by ESP + ....

invoice_greenanimals.pdf.exe
00407a96 MOV EDI ,dword ptr [i*0x4 + hGlobalAlloc1 ] = ??
00407aa1 MOV dword ptr [ESP + i*0x4 + 0x10 ],EDI

Then:

invoice_greenanimals.pdf.exe
LAB_00407a94 XREF[1]: 00407a8d (j)
00407a94 MOV EAX ,i
00407a9d NEG EAX
00407a9f SBB EAX ,EAX
00407aa5 AND EAX ,0x8844
00407aaa ADD EAX ,0x4060

This is just an obfuscated way of saying “if i is zero use 0x4060, otherwise use 0xC8A4. More in detail it starts by copying i into EAX. The NEG EAX flips the sign of EAX. If i was zero, it stays zero. If i was non-zero, it becomes a negative number. The next instruction, SBB EAX, EAX, turns that result into either all zeros or all ones. If i was zero, EAX becomes 0x00000000. If i was non-zero, EAX becomes 0xFFFFFFFF.

After that, AND EAX, 0x8844 keeps either nothing or exactly 0x8844. So now EAX is either 0x0000 (when i = 0) or 0x8844 (when i ≠ 0). Finally, ADD EAX, 0x4060 shifts both cases up by the same base amount. That means when i = 0, the result is 0x4060, and when i = 1 (or any nonzero value), the result is 0x4060 + 0x8844 = 0xC8A4.

Now EAX contains either 0x4060 or 0xC8A4 based on which iteration we are at. Next the following is performed:

invoice_greenanimals.pdf.exe
00407aaf MOV ECX ,EAX
00407ab1 MOV EBX ,ECX
00407ab3 SHR ECX ,0x2
00407ab6 MOVSD.REP ES :EDI ,p_long_str => long_str_with_mssecsvc.exe2 = 00905A4Dh

It might seem very confusing, however: MOVSD is an instruction to move strings from one place (p_long_str) to another, EDI. The fact that it is .REP means that it chunks 4 bytes of move up to ECX times. This is just a faster way to move strings compared to moving byte by byte. ECX is EAX which is then divided by four (it shifts 2 bytes to the right (SHift Right), which means to divide by 4).

So EDI contains the first 0x4060 bytes of the first string in the first GlobalAlloc and the first 0xC8A4 bytes from the second string. Remember that EDI, at the beginning of the loop, was assigned to be a pointer to hGlobalALloc1 and to a local variable.

As soon as it is understood that the strings might be sent to a GlobalAlloc the first thought is: they are either shellcode or they are an actual executable. At this point, looking at the beginning of the strings the values 00905A4Dh can be seen, note the 5A 4D. These are the bytes defining the MZ magic header, to identify executables. At this point it is possible to extract the executable directly from Ghidra. To do that, starting with the first string, the analysis goes to the address 0x0040b020 by pressing the g key (which is the equivalent of Navigation → Go To). Typing the address and then opening the Select menu and clicking Bytes opens the Select Bytes window. In the Select Bytes window “Select Forward” is chosen and the address Length of 16480 is entered, which is the 0x4060 bytes that the program is going to read, transformed in decimal. After clicking on “Select bytes” and closing the window, the File menu → Export Program is used. The Binary format is selected and the “Selection only” flag is enabled. The output file is saved as mssecsvc.exe_1.

The same procedure is repeated for the second string, with start address of 0x0040f080 and Length of 51364 which is the conversion to decimal of 0xc8a4. The second bin is saved to mssecsvc.exe_2.

In fact, this is not an .exe. Loading the first one in pestudio shows it is a .dll with original name of launcher.dll. The imports are very small, which is expected given the size of 16KB. The exported function is only one and it’s called PlayGame.

The imports are:

  • CloseHandle
  • WriteFile
  • CreateFileA
  • SizeofResource
  • LockResource
  • LoadResource
  • FindResourceA
  • CreateProcessA

Which is a very easily recognizable pattern for loading a resource, saving it to file and starting it, as seen already before, see 1 for more on it.

However, there seems to be something wrong with extracting the resources from pestudio, as well as running both the samples with rundll32, since the first sample seems to just fork new instances of rundll32 though such behaviour in the code cannot be found, while the second one doesn’t run.

It might be that something during the extraction has not been considered. What have been tried to do next was to change the flow of execution of the main sample with x32dbg to go specifically in the function used to allocate the heap with the samples. It failed since after reaching these lines:

invoice_greenanimals.pdf.exe
LAB_00408101 XREF[1]: 004080cd (j)
00408101 LEA EAX => local_10 ,[ESP + 0x4 ]
00408105 MOV dword ptr [ESP + local_10 ],s_mssecsvc2.0_004312fc = "mssecsvc2.0"
0040810d PUSH EAX SERVICE_TABLE_ENTRYA * lpServiceStartTable for StartServiceCtrlDispatcherA
0040810e MOV dword ptr [ESP + local_c ],LAB_00408000
00408116 MOV dword ptr [ESP + local_8 ],0x0
0040811e MOV dword ptr [ESP + local_4 ],0x0
00408126 CALL dword ptr [-> ADVAPI32.DLL::StartServiceCtrlDispatcherA ] = 0000a6f6

The execution continues ignoring all the breakpoints inside the function executed by the service.