> A lazy look at WannaCry

(12/20/2025), 5 minute read ⏰

Note: these are just my markdown notes, but a bit cleaned up. The original sample link is there too! Feel free to check out the original notes here!

In the words of my good acquaintance deluks on the OALabs server, I will be following the following workflow to analyze this sample (in their words, when giving advice to a newbie):

1. check if its packed & inspect strings (the basic stuff)
2. if its packed, unpack it < insert unpacme ad here >
3. run it in a sandbox and see what happens (e.g. are any files dropped?)
4. throw it into ida/ghidra/binja/whatever and see how it works (if its not packed)
   a) if you encounter obfuscation, try to identify the technique and if possible defeat it with some debugging, else just try scripting

Well, to determine if it was packed, we can straight up just chuck it in unpacme and see what's up.

unpacme results

We can see that 1) this isn't packed and 2) it was instantly signatured as WannaCry. What's also neat is the classification of zombieware, being malware that's been abandoned by its operators but is still out in the wild. Pretty neat.

This doesn't tell us much, but we can continue on to our next step, which is sandboxxing the sample. For this, I'm using tria.ge. I've never actually used it before, but we have a TON of options for configuring our sandbox.

sandbox customization

I'll be leaving everything as auto since I trust the platform is smart enough to at least pick a Windows sandbox for us.

While the sandbox was loading, I noticed that it also gave us a ton of fingerprinting data without us needing to do anything (massive skid energy).

malware fingerprinting

We do also see a list of imports, and of them, wininet is included, so there is like a 99% chance there is some C2 communication going on. Some socket functions also appear to be in use, further reinforcing this.

My tria.ge sandbox never came up, so I ended up abandoning this step of analysis. We can instead, use the marvelous VirusTotal (VT) and get some quick wins. virustotal findings

Of course, wannacry is very signatured, so this is expected. We can see some resources of high entropy contained in the sample as well. I haven't used Resource Hacker before, but this would be the time to break it out and try to snag these for further analysis.

resources in use

There are also a LOT of contacted URLs, most of which point to a .onion domain

contacted domains

At this point, we've exhausted our dynamic analysis needs for the sample. THough truthfully, an any.run sandbox would probably work nicely here to just be done with analysis. From here on out, I'll just be analyzing the sample in IDA Free and try to get as much info out as possible with my limited experience.

Upon disassembly, we're greated with WinMain which shows us that the sample basically performs some web requests through its wininet imports. What's most interesting are a few strings inside the binary.

function strings

We can confirm this through the presence of GetModuleHandleA and GetProcAddress, which are telltale signs of dynamic API resolution.

api resolution

Something else to note is the presence of srand, rand, and time. No binary would ever need these if it is not to perform some kind of jitter as a form of evasion (unless we're analyzing some sort of random number generation program), so we can include this to our list of IOCs.

jitter at runtime > Whilst looking through the list of strings for the binary, I found hints towards IPC (inter-process communication) through named pipes. Named pipes have been used a lot as a form of IPC with malware but also for tooling like Cobalt Strike. IPC works nicely when multiple operations are being done such that multiple modules can communicate back to a sort of master process. ipc at runtime w/ pipes

At this point, I tried X-REFing for the PIPE string and it crashed IDA, so I had to restart my analysis of the sample. As I went back to the string, I came across 2 new findings:

1) A named pipe is being used, called IPC$, given a format specifier for its parent directory (most likely the PIPE string from before).
2) A service name of mssecsvc2.0. If we take a look at its X-REFs, we can see it being used to create a new service.
  a) We can also see interaction w/ SCM (Service Control Manager) alongside this service, potentially to establish persistence of the service.
  b) Some jitter is also spotted alongside SCM during the service runtime.

ipc at runtime confirmed! scm interaction jitter w/ scm and the malicious service

At this point, we can easily call it a day and slam this thing as malware, given the amount of egregious actions its performing at runtime. Instead, we'll go ahead and decompile WinMain and try to dissect the internet connections the binary is making.

The decompilation looks like this:

int __stdcall WinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  void *v4; // esi
  CHAR szUrl[57]; // [esp+8h] [ebp-50h] BYREF
  int v7; // [esp+41h] [ebp-17h]
  int v8; // [esp+45h] [ebp-13h]
  int v9; // [esp+49h] [ebp-Fh]
  int v10; // [esp+4Dh] [ebp-Bh]
  int v11; // [esp+51h] [ebp-7h]
  __int16 v12; // [esp+55h] [ebp-3h]
  char v13; // [esp+57h] [ebp-1h]

  qmemcpy(szUrl, &unk_4313D0, 0x38u);
  szUrl[56] = unk_431408;
  v7 = 0;
  v8 = 0;
  v9 = 0;
  v10 = 0;
  v11 = 0;
  v12 = 0;
  v13 = 0;
  v4 = InternetOpenA(0, 1u, 0, 0, 0);
  InternetOpenUrlA(v4, szUrl, 0, 0, 0x84000000, 0);
  InternetCloseHandle(v4);
  InternetCloseHandle(0);
  sub_408090();
  return 0;
}

The MSDN documentation for InternetOpenA shows it takes 5 arguments:

1) A null-terminated string for the app/entity calling Wininet functions
2) The type of access rights requested. We can see that 1u is specified, or just 1, which resolves to INTERNET_OPEN_TYPE_DIRECT, so all hostnames are resolved locally.
3) A null-terminated string for a proxy server. No proxy is being used, so they supply 0.
4) A null-terminated string of an optional list of hostnames/IPs, which they didn't supply here since hostnames will be resolved locally. This is also most likely to make analysis harder and/or to be more stealthy at runtime.
5) A flag for handling how to perform network requests. As this is set to 0, none of the flags are used here.

The next core variable here is szUrl. It is a 57-byte long character array (char*), where 56 such bytes are allocated for the URL, and the final for the null terminator.

The assembly instructions near the next function call (InternetOpenUrlA) give us some insight into what may be the URL:

.text:0040817B                 call    ds:InternetOpenA
.text:00408181                 push    0               ; dwContext
.text:00408183                 push    84000000h       ; dwFlags
.text:00408188                 push    0               ; dwHeadersLength
.text:0040818A                 lea     ecx, [esp+64h+szUrl]
.text:0040818E                 mov     esi, eax
.text:00408190                 push    0               ; lpszHeaders
.text:00408192                 push    ecx             ; lpszUrl
.text:00408193                 push    esi             ; hInternet
.text:00408194                 call    ds:InternetOpenUrlA

The arguments get pushed onto the stack and ecx contains the URL, which is a slight offset from the stack pointer. At this point, it would just be best to break out a debugger, breakpoint before the call is made, and check ecx. I don't have a VM set up at the moment for this, but this would be my course of action at this point.

Continuing with our analysis of WinMain, we can annotate the entire function like so:

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpCmdLine, int nShowCmd)
{
  void *handle_InternetSession; // esi
  CHAR szUrl[57]; // [esp+8h] [ebp-50h] BYREF
  int v7; // [esp+41h] [ebp-17h]
  int v8; // [esp+45h] [ebp-13h]
  int v9; // [esp+49h] [ebp-Fh]
  int v10; // [esp+4Dh] [ebp-Bh]
  int v11; // [esp+51h] [ebp-7h]
  __int16 v12; // [esp+55h] [ebp-3h]
  char v13; // [esp+57h] [ebp-1h]

  qmemcpy(szUrl, &byte_4313D0, 0x38u);
  szUrl[56] = unk_431408; // ecx will contain the URL before InternetOpenUrlA
                          // is called, debugging here would be best
  v7 = 0;
  v8 = 0;
  v9 = 0;
  v10 = 0;
  v11 = 0;
  v12 = 0;
  v13 = 0;
  // resolve hostnames locally, no dwFlags
  handle_InternetSession = InternetOpenA(0, INTERNET_OPEN_TYPE_DIRECT, 0, 0, 0);
  // the dwFlag here may be INTERNET_FLAG_RELOAD, which forces a download of the
  // request file/object/directory according to msdn
  InternetOpenUrlA(handle_InternetSession, szUrl, 0, 0, 0x84000000, 0);
  InternetCloseHandle(handle_InternetSession);
  InternetCloseHandle(0);
  sub_408090();
  return 0;
}

This is where my analysis will conclude, since there is not a whole lot else to cover from what I can tell (not, there is a LOT to unpack w/ WannaCry, I'm just too lazy, hence the lovely title).


Thanks a bunch for checking out this writeup of mine! I should set up a reversing playground (dynamic analysis is for VT/any.run and things) sometime. I've seen cool things w/ retoolkit. FlareVM has been buggy in the past, so I won't be using it. I do also want to fix up the HTML styles here for code blocks n things, since both you and I can 100% agree that they are super ugly right now.

With all that being said, I'm done for the day. Thanks for reading, and have a good rest of your day!


Edit (12/21/25)

From the lovely eversinc33 and rattle on the OALabs discord server. Firstly, thanks a bunch for your pointers that I'm about to state. I figured I'd share them here as an update to this post to provide more info for anyone that visits this site to not be grossly misinformed by my laziness.

Here's is the dialogue we had:

(after reading my post) eversin33: just a heads up, presence of rand and time is not an indicator of c2 jitter xd theres a LOT more use case for random numbers and timestamps

me: could u expand on this a bit, also i didn't say it was c2 jitter but just delayed execution using sleep. Dunno if there's a distinction

rattle: Your exact quote is: Something else to note is the presence of srand, rand, and time. No binary would ever need these if it is not to perform some kind of jitter as a form of evasion (unless we're analyzing some sort of random number generation program), so we can include this to our list of IOCs. I agree with @eversinc33 that this is inaccurate reasoning; binaries can include this for a multitude of other reasons.

(replying to my first reply back) eversin33: oh yea, but still. e.g. u can use rand to generate a random filename or use time to get timestamps for logs. Every API exists for legitimate reasons

me: ic ic

rattle: Note we are not saying it is legitimate in this case, you can just not in general assume that rand is only used in programs dedicated to random number generation.

(jumping ahead) eversin33; i personally see these as indicators on what functionality could be there, but no api is malicious per se. E.g. IsDebuggerPresent is in almost any IAT as its used by compiler boilerplate, so it would be in many cases wrong to assume its for detecting debuggers as an anti debug method


Sources/Tools Used

MSDN documentation for API functions/flags
IDA Free
tria.ge
UnpacMe

TTPs

T1559: IPC communication through named pipes
T1569.002: Service Execution (mssecsvc2.0)
T1071.001: Application Layer Protocols: Web Protocols (C2 Communication)
T1678: Delay Execution (Sleep jitter)
T1140: Deobfuscate/Decode Files for Information (hidden resources)
T1027.007: Dynamic API Resolution (of several functions)

IOCs

SHA256: 07c44729e2c570b37db695323249474831f5861d45318bf49ccf5d2f5c8ea1cd


cd . cd .. cd ../notes-and-resources cd ~