What WannaCry was, and what the sources say
It is worth starting with the context, because it is what gives meaning to everything else. WannaCry was the ransomware that behaved like a worm: instead of waiting for somebody to open an attachment, it copied itself across the network on its own, through a flaw in SMBv1. On 12 May 2017 that was enough to stop hospitals, factories and railways over a single weekend.
What is publicly documented, with the sources alongside:
- The vulnerability had been patched two months earlier. Microsoft shipped
MS17-010 on 14 March 2017; the exploit WannaCry used — EternalBlue, against
CVE-2017-0145— was leaked by the Shadow Brokers on 14 April. The May outbreak happened on machines that had gone two months without applying an available update. - The scale is disputed between sources. On 14 May the director of Europol, Rob Wainwright, spoke of more than 200,000 victims in at least 150 countries. Other estimates from the same days put the figure at around 300,000. There is no single count, and the difference is not a detail: nobody counted machines, they counted sinkhole connections and reports from the affected.
- The British case is the best documented. In its report of 27 October 2017, the National Audit Office counted at least 81 of the 236 NHS trusts in England affected, 603 organisations infected — 595 of them GP practices — and estimated over 19,000 cancelled appointments. NHS Digital stated it does not believe any patient data was stolen: WannaCry encrypts, it does not exfiltrate.
- The kill switch was a domain check. The binary attempted an HTTP request to a fixed domain; if the request succeeded, it stopped. A researcher registered that domain on 12 May and the spread was halted. Microsoft described it in its own advisory the same day: "If connection to the domains is successful, the dropper does not infect the system further" — and explicitly advised administrators not to block those domains.
- The business was a failure. On 23 May 2017 the running total in the ransom wallets was just over $109,000; by August the figure discussed was around $140,000. Against 200,000 machines or more, that is a few hundred payments.
- The public attribution is state-level. In December 2017 the White House officially named North Korea, and said the United Kingdom, Australia, Canada, New Zealand and Japan shared the analysis. In September 2018 the US Department of Justice filed a criminal complaint — not an indictment — against Park Jin Hyok, placing him at the front company Chosun Expo and in the service of the Reconnaissance General Bureau, on two conspiracy counts. Pyongyang denied it.
What the sources do not say. This sample is not the WannaCry of 12 May: it is a patched variant, and public sources do not establish who patched it. On 14 May 2017 Mandiant described a variant "that does not contain the domain contact killswitch functionality" and added, in as many words, that the change "may have been implemented by a third party after the malware was compiled rather than by the operators". Of the same variant it said that "the ransomware component of this variant appears corrupted and does not function in test environments". Both observations match what is in front of me.
The following day, Bitdefender argued in the press that the variants with and without the switch
were run by the same group, because the ransom wallets were the same. That argument
distinguishes nothing, and the binary shows why: the three Bitcoin addresses are hardcoded
inside tasksche.exe, which is exactly the part the patch does not touch.
Identical wallets are what you would expect whoever edits the dropper. So I leave it where
the sources leave it: unknown.
A zeroed URL and a neutered jump
The file is the worm component, mssecsvc.exe: a native PE32 of 3,723,264 bytes, four
sections, image base 0x400000. 93% of its size is the resource section, because it
carries the ransomware installer inside it as a plain MZ.
The original behaviour, at 0x408140, is this:
mov esi, 0x4313d0 ; "http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea.com"
rep movsd ; copy the URL to the stack
call InternetOpenUrlA
test edi, edi
jne 0x4081bc ; domain answers -> close handles and EXIT
call sub_408090 ; no answer -> infect
In this sample there are two changes, and both are visible directly in the file:
| Where | Original | This sample | Effect |
|---|---|---|---|
0x4313d0(offset 0x313d0) | 57 bytes holding the URL | 00 00 00 … | InternetOpenUrlA("") always fails; no HTTP traffic and no DNS query |
0x4081a5(offset 0x81a5) | 75 15 — jne 0x4081bc | 75 00 — jne +0 | both branches fall into the infection |
The original exit branch is still there, as dead code right after the ret, at
0x4081bc: ff d6 57 ff d6 5f 33 c0 5e 83 c4 50 c2 10 00. Nobody deleted it,
it is simply unreachable.
And the decompiler confirms it by removing the comparison entirely, because a condition whose two branches go to the same place is not a condition:
uVar1 = InternetOpenA(0,1);
InternetOpenUrlA(uVar1, &uStack_64, 0, 0, 0x84000000, 0);
InternetCloseHandle(uVar1);
InternetCloseHandle(0);
FUN_00408090(); // unconditional infection
return 0;
A number that does not add up. The technical press of May 2017 reported that the "patched" variant differed from the original by only 2 bytes. In this sample the changed bytes number 58: one in the jump and 57 in the URL slot. Both can be true at once, because several variants circulated and the jump patch alone is already sufficient — in fact it is the minimum possible. What I cannot do is treat this sample as described by a figure that is not its own.
Where the switch actually sat
The usual way of telling the story is "the switch prevented encryption". That is imprecise, and this sample demonstrates it by construction.
The check lives in the worm's WinMain, before the service is created
and before the installer is written to disk. What sat behind the jump was not the
encryption: it was everything. Creating the mssecsvc2.0 service, starting the SMB
propagation, and dropping tasksche.exe into C:\WINDOWS to run it with
/i. If the domain answered, none of that happened. That is why Microsoft talks
about the dropper rather than the encryptor.
Neutering the jump unblocks the whole chain: the machine installs itself, spreads, and drops the payload every time. That the encryption never happens, in this sample, has nothing to do with the switch — it is a separate accident, and it is section 05.
Two more things the patch puts on display. The first is what the check was probably for: an anti-sandbox brake. Many analysis environments answer any domain so that no traffic leaves the box, so inside a sandbox the domain "resolves" and the malware sits still. The sinkhole effect of 12 May was, in that reading, the same mechanism used in reverse. The second is more uncomfortable: that global brake had no integrity protection whatsoever. No checksum over its own code, no check on the URL length, no second check anywhere else. One byte.
One thread for the LAN and 128 for the internet
With the brake removed, what stays operational is the part that actually did the damage in 2017. It
starts at 0x407bd0 and splits the work two ways: one thread for the local
network and 128 for the internet, launched one at a time every two seconds.
The internal target list is built by sub_409160 using GetAdaptersInfo
and GetPerAdapterInfo, and for each adapter it adds three things:
- the whole subnet per its mask, from
ip & maskup to the broadcast; - the /24 of each gateway, if it does not already fall inside the local subnet;
- the /24 of each DNS server, with the same filter.
It then sorts, deduplicates, and attacks with at most 10 concurrent threads and 50 ms between launches. That gateway-and-DNS detail is what explains why a small mask contained nothing: through the gateway and the DNS it reaches other VLANs even when the local segment is tiny.
The 128 internet threads are simpler and noisier. They generate a random IPv4 and only
discard a first octet of 127 and anything ≥ 224. They do not
exclude RFC1918, so they also aim at private ranges that happen to be routed over VPN or MPLS. If 445
answers, they sweep the entire /24, from .1 to .254. Each
exploitation attempt gets an hour of grace — 0x36ee80 ms — and then a
TerminateThread.
The volume that produces is measurable, and it is on public record for this very hash: 18,506 distinct IP addresses contacted across the sandbox runs aggregated by VirusTotal, with Suricata and Snort signatures for SMB sweeping and for EternalBlue. It is not stealthy and does not try to be.
About the exploitation itself I will say what I can: the buffers handed to each host with 445 open
are prepared at 0x407a20 and are an x86 DLL of 0x4060 bytes, an x64 one of
0xc8a4, and a copy of the executable itself. The strings
__TREEID__PLACEHOLDER__ and __USERID__PLACEHOLDER__ are the slots filled in
the SMB template. I did not decompile the inside of the exploitation threads: that
the chain is EternalBlue → DoublePulsar → remote execution is documented behaviour of the family, not
something verified instruction by instruction here.
The ZIP missing 177 KB
Here is the other half of the story, and it is the half that turns this sample into a curiosity rather than an emergency.
The first thing to undo is a common confusion: tasksche.exe does not
encrypt. It is an installer and a loader. The encryption lives in t.wnry, an
encrypted DLL that tasksche decrypts in memory and whose TaskStart export
does the work. And t.wnry is not in the binary: it travels inside a ZIP resource — type
XIA, ID 2058 — protected with the password WNcry@2ol7, fixed
and public since 2017.
That ZIP is broken. Not "suspiciously odd": broken, and in a specific way.
| Test | Result |
|---|---|
Local headers PK 03 04 | 32, all of them before the damage |
Central directory PK 01 02 | 0 occurrences |
End of directory PK 05 06 | 0 occurrences |
| Start of the damage | offset 0x31e2b7 (3,269,303), 94.9% into the file |
| Damaged tail | 177,022 bytes, entropy 7.9986 bits/byte, not a single header |
Password WNcry@2ol7 | correct: 31 entries decrypt with a valid CRC |
t.wnry, u.wnry, taskdl.exe, taskse.exe | do not exist in the file |
The chain of consequences is short and mechanical. The function that opens the resource calls
minizip — the string "unzip 0.15 Copyright 1998 Gilles Vollant" is in the
binary — and minizip starts with SearchCentralDir, at
0x405edf, which looks for 'P' 'K' 0x05 0x06 in the last 64 KB of the file.
Not finding it, it returns -1, unzOpen returns NULL, and
not one file is extracted, not even the ransom notes. The WinMain of
tasksche ignores that return value and carries on quite happily until it fails to open
t.wnry, at which point the function returns NULL and the call to
TaskStart never happens.
The detail I like most in the whole analysis is this one: my own extractors do recover 31 files, because they walk the local headers one by one and need no index. The malware cannot, because it depends on the central directory. The difference between a ransomware and a nuisance, in this sample, is a design decision in a library from 1998.
What static analysis cannot determine is the cause of the damage. That it is a continuous block of maximum-entropy data running to the end of the file points to an overwrite rather than a truncation, but that is an inference about the shape, not proof of the motive. An accident while patching, an accident in transfer, or deliberate neutralisation are the three hypotheses, and the file does not separate them.
Two caveats that matter before writing the sample off as harmless. First: there is an edge
case. If the working directory already held a valid t.wnry from an earlier
infection by a working variant, tasksche would load it and encrypt. It would still be
unable to show the ransom screen, because u.wnry is not extracted either: the victim
would end up with encrypted files and no instructions. Second: the real risk of this sample is not the
encryption, it is the exploit. EternalBlue against an unpatched machine can take it
down with a blue screen, and 128 threads hunting for port 445 across the internet are still doing
exactly that.
Public runs of the same hash agree point by point: the mssecsvc2.0 service is
created, C:\WINDOWS\tasksche.exe /i is launched as a child of the sample, the SMB-sweep
alert fires, and tasksche leaves almost no trace — no .WNCRY file, no
@WanaDecryptor@.exe, no taskdl.exe, no taskse.exe, and no
changed wallpaper.
One key per file, one wallet for everyone
Even though this variant does not encrypt, the cryptographic scheme is still readable, because
tasksche uses it to protect itself. That is the only part verified here,
and it is worth separating from what is family documentation.
Verified in this sample. The function that reads t.wnry, at
0x4014a6, imports an embedded RSA key with CryptImportKey, uses it to
decrypt a 256-byte block — the size of RSA-2048 — holding a 16-byte AES key, and with that key
decrypts the DLL. The container has this shape:
WANACRY! 0x100 AES key encrypted with RSA type plain size encrypted DLL
8 B 4 B 256 B 4 B 8 B AES-128
A textbook hybrid scheme: AES because it is fast, RSA so that only the holder of the private key
can recover the AES key. What is interesting is that the malware applies to itself the same treatment
it applies to victims, and that the format of .WNCRY files is this same
container, WANACRY! header included. The authors reused their own format.
Public documentation, not verified here — because t.wnry is missing —
the encryption of the victim's files chains three levels.
Four consequences follow, and they explain the story of 2017 rather well:
- Recovering one machine's private key only helps that machine. The same file encrypted on two hosts gives different results, because every file carries its own AES key.
- The single point of failure is the attacker's master private key, which would
decrypt any victim's
.eky. It was never published. - The
t.wnrykey is identical in every copy, because it travels in the binary. It protects against a superficial look, not against analysis: that is why the encryption engine can be extracted and studied. - There was an exploitable weakness, the one behind WannaKey and wanakiwi in 2017: on Windows XP and 7 the CryptoAPI did not scrub the primes of the victim's RSA key from memory, so if the machine had not been rebooted it could be reconstructed and the files decrypted without paying.
And then there is the contrast that says most about the whole thing. The cryptography is designed
per machine; the collection is not. The three Bitcoin addresses are hardcoded in
tasksche.exe, at offsets 0xf440, 0xf464 and
0xf488, one after another with a 36-byte stride, and the note references them through a
%s that the decryptor fills in at runtime. There is no per-victim identifier anywhere.
The operator had no reliable way of knowing which payment matched which .eky,
which explains both why paying almost never returned the files and why the total stayed in the
$109,000–$140,000 range against more than 200,000 infected machines.
In this variant, of course, none of this runs: with no t.wnry there is no key
generation and no encryption.
A vaccine that is a service doing nothing
There is a detail in the installation that can be turned against the worm. At
0x407c40, the malware only starts the spreading service if
CreateServiceA succeeds:
eax = CreateServiceA(scm, "mssecsvc2.0", "Microsoft Security Center (2.0) Service",
0xf01ff, 0x10, 0x2, 0x1, "<path> -m security", …);
if (eax != 0) {
StartServiceA(eax, 0, 0); // only this way does it enter service mode -> spreading
CloseServiceHandle(eax);
}
If a service with that name already exists, CreateServiceA fails with
ERROR_SERVICE_EXISTS, the malware does not start it, and that machine does not
spread. The service branch is only reached when the service control manager launches it with
-m security, and that does not happen if the service was never created. So occupying the
name with a disabled decoy service is enough:
sc create mssecsvc2.0 binPath= "C:\Windows\System32\svchost.exe -k vaccine_placeholder" start= disabled DisplayName= "WannaCry vaccine placeholder"
sc description mssecsvc2.0 "Defensive placeholder: blocks creation of the WannaCry service (mssecsvc2.0). Do not remove."
And now the limitations, which matter more than the trick.
It does not protect against exploitation. The machine remains vulnerable to EternalBlue and the exploit can cause a blue screen. The only thing it prevents is that machine infecting others.
The tasksche.exe drop still happens, because it is on another branch. In this variant
it is harmless; in a working one, it is not.
It is specific to this family and this service name: a variant that changes the name sidesteps it effortlessly. And it is a temporary, complementary measure, never a substitute for applying MS17-010 and disabling SMBv1.
It is reasoned from the decompilation, not proven at runtime. Before deploying it anywhere it must be confirmed in an isolated environment: create the service, run the sample, and verify that neither the SMB-sweep alert nor the mass connections to port 445 appear.
Origin: three dates and a 2010 compiler
These are two different questions, and mixing them is the easy mistake: who wrote the kit and who patched this build. The public context in section 01 speaks to the first. On the second there is nothing published, and the sources that touch it say explicitly that it is not known.
What the file does contribute is three dates that fall into a sequence:
tasksche.exe first seen publicly on 12 May 2017The compile stamp is the piece that settles the method: it did not change. Whoever touched this recompiled nothing, they edited bytes over an already-built binary — and the 2010 stamp is itself almost certainly faked in the original, so it says nothing about when the code was written. The installer inside is from the day of the outbreak; the patched dropper appears two days later, on the same 14 May that Europol gave the 200,000-victim figure.
On the origin of the kit, what stands out is how the clues are distributed:
| Where an origin clue usually sits | What is here |
|---|---|
| PE resource language | All 0x0409, the default value. Uninformative |
| Office language in the ransom notes | 28 of 28 carry \deflangfe1042 and \themelangfe1042: Korean as Word's East Asian language |
| Fonts declared in the notes | 28 of 28 declare Korean-charset fonts (fcharset129), with the name written as CP949 bytes: 맑은 고딕 (Malgun Gothic) in 28 of 28, 굴림체 in 5 and 바탕 (Batang) in 3 |
| Quality of the English template | Non-native speaker errors: "But you have not so enough time." |
| Chinese notes | Native quality; the rest machine-translated from English (Flashpoint, 2017) |
That is consistent with an Office environment configured in Korean, and with the official attribution of December 2017. But two limits apply, and both matter.
First: language artefacts are trivial to plant. A \deflangfe block
and a font name in CP949 are two strings in an RTF file. Anyone wanting to misdirect puts them there
in ten minutes. They work as cumulative indication, never as proof on their own.
Second, and more concretely: this evidence does not speak about whoever patched the sample. The 28 notes live inside the ZIP, which is upstream of the patch — it is content inherited from the original build. Of the author of the two patches the binary carries not one linguistic trace: no new strings, no added resources, no new section. Just 58 different bytes and a 177 KB tail that is no longer good for anything.
Quality as a piece of software
It is worth judging it as what it is, because that explains both why it stopped half the world over a weekend and why it collected less than a corner shop.
| What is done well | Where the seams show |
|---|---|
Self-contained propagation: it needs no C2 to spread; the worm's only HTTP call is the brake (0x408140) | That brake is a single point of failure with no integrity protection at all: one byte at 0x4081a5 changes the global behaviour |
| Clean component split: worm, installer, encryption engine and ransom interface, each in its own piece | No obfuscation and no packing: the payload travels as a plain MZ in resource R/1831 |
Target selection that crosses segments through the gateway's and the DNS server's /24, not just the local mask (0x409160) | Fixed, cleartext ZIP password: WNcry@2ol7, the same one for everybody |
| Concurrency control and timeouts in the scan: at most 10 LAN threads, 50 ms between launches, one hour per exploitation attempt | The unzip return value is never checked. unzOpen returns NULL and WinMain carries on as if nothing happened, until it crashes into a file that does not exist |
The encryption engine, encrypted with RSA-2048 + AES-128 and only ever loaded in memory (0x4014a6): the dropper never shows encryption code statically | Three shared, hardcoded wallets (0xf440 in tasksche), with no per-victim identifier: the collection layer, which is the product's whole purpose, was never built |
Fallback chain for the working directory: ProgramData, then Intel, then the Windows directory, then temp (0x401b5f) | Extremely noisy scanning: 128 threads and full /24 sweeps light up any IDS within seconds |
Two ways of starting itself, a randomly named service and, failing that, CreateProcessA, each with its own wait (0x401ce8, 0x401eff) | Those waits are 60 s + 60 s on a mutex that in this variant cannot exist: there is no error detection, only elapsed time |
It reuses its own WANACRY! container for the engine and for victim files, instead of inventing two formats | Cleartext IOCs: mssecsvc2.0, "Microsoft Security Center (2.0) Service", C:\%s\qeriuwjhrf, tasksche.exe |
The verdict: a state-grade exploit with half a business built on top of it. The part that really worked — the propagation — is borrowed: EternalBlue, leaked a month earlier. What is its own is uneven: there are well-considered network engineering decisions, such as reaching other VLANs through the gateway, sitting next to an error handler that amounts to waiting a minute to see what happens. And the collection layer, which is a ransomware's reason to exist, was never designed: you cannot invoice without knowing who paid.
About this particular build there is little to praise and one thing to point out: a third party's patch left the worm working and the extortion broken, which is the worst possible combination for a defender. There is nothing to pay, there is no note to warn you, and it still scans port 445 across half the internet.
Detection and containment
First, because it is this variant's particular trap: do not rely on the kill-switch sinkhole. This sample makes no DNS query and no HTTP request to the domain. A detection anchored on that domain does not see it at all.
Containment:
- Apply MS17-010 and disable SMBv1, which is the only thing that actually solves the problem.
- Block TCP/445 at the perimeter and between segments and VLANs. Internal propagation does not stop at the perimeter, and the gateway-and-DNS trick is designed precisely to cross segments.
- Isolate hosts generating bursts of connections to port 445.
Hunting ideas:
- A host with many connections to 445 towards many unique destinations within a few minutes, internal and external at once.
- Creation of the
mssecsvc2.0service, or of any service whoseImagePathcontainscmd.exe /candtasksche.exe. tasksche.exeprocesses outside legitimate paths or carrying the/iargument.icacls . /grant Everyone:F /T /C /Qandattrib +h .launched by a non-administrative process.
Indicators. Everything below comes from the binary or its resources:
| Type | Value |
|---|---|
| Dropper SHA-256 | 07c44729e2c570b37db695323249474831f5861d45318bf49ccf5d2f5c8ea1cd |
tasksche.exe SHA-256 | 2584e1521065e45ec3c17767c065429038fc6291c091097ea8b22c8a502c41dd |
| Service | mssecsvc2.0 — "Microsoft Security Center (2.0) Service", ImagePath = <path> -m security |
| Service | Random name, ImagePath = cmd.exe /c "<dir>\tasksche.exe" |
| Files | C:\WINDOWS\tasksche.exe, C:\WINDOWS\qeriuwjhrf |
| Directory | C:\ProgramData\<random>\ or …\Intel\<random>\, hidden and with Everyone:F permissions |
| Command lines | tasksche.exe /i, attrib +h ., icacls . /grant Everyone:F /T /C /Q |
| Mutex | Global\MsWinZonesCacheCounterMutexA0 — created by t.wnry, so it never appears with this variant |
| PE resources | Dropper: type R, ID 1831. tasksche: type XIA, ID 2058 |
| Network | High-volume outbound TCP/445; sequential sweep of the local subnet and of the gateway's and DNS server's /24; Suricata SMB-sweep signature |
Tor hidden services (from c.wnry) | gx7ekbenv2riucmf.onion, 57g7spgrzlojinas.onion, xxlvbrloxvriy2c5.onion, 76jdd2ir2embyv47.onion, cwwnhwhlz52maqm7.onion |
Tor download (from c.wnry) | https://dist.torproject.org/torbrowser/6.5.1/tor-win32-0.2.9.10.zip |
BTC wallets (in tasksche) | 115p7UMMngoj1pMvkpHijcRdfJNXj6LrLn, 12t9YDPgwueZ9NyMgw519p7AA8isjr6SMw, 13AM4VW2dhxYgXeQepoHkHSQuy6NgaEb94 |
What cannot be claimed from here
The whole analysis is static: nothing was executed, and the extracted artefacts were left read-only. This is what holds up each claim, and what does not:
| Claim | Confidence | Basis |
|---|---|---|
| The switch is neutralised by two patches | High | File bytes and matching decompilation in two independent tools |
Service installation, tasksche drop and scanning logic | High | Decompilation, and agreement with public runs of the same hash |
The ZIP is damaged, minizip cannot open it, and t.wnry is absent | High | Byte-by-byte verification of the resource plus decompilation of the library |
| This variant does not encrypt | High | The above, plus the absence of encryption across several public runs |
Hybrid RSA-2048 + AES-128 scheme protecting t.wnry | High | Decompilation of 0x4014a6 |
| The decoy-service vaccine blocks propagation from the host | Medium-high | Decompilation of 0x407c40; dynamic test still pending |
| Key scheme used to encrypt the victim's files | Medium | Public documentation of the family. Not verifiable here: t.wnry is missing |
| Inside of the exploitation threads (EternalBlue, DoublePulsar) | Medium | Known behaviour of the family; not decompiled in this work |
| Who applied the patches | Low | Nothing in the binary. Public sources say it is not clear |
| Cause of the ZIP damage: accident or neutralisation | Low | The shape of the damage suggests an overwrite, but the motive is not statically determinable |
One loose end I preferred to leave open: the internet thread has 20- and 40-minute timers —
0x124f80 and 0x249f00 — that appear to control the renewal of the random
prefix. It fits that role, but I did not verify it instruction by instruction, so I describe it and
leave it unlabelled.
Is a dynamic pass worth it? Yes, but narrow, and for two concrete things. One: to
test the vaccine, because deploying into production a measure reasoned only from the decompilation is
exactly what should not be done. Two: to watch tasksche with every process visible, not
only the "important" ones, in order to pin the exact step where it dies — whether it gets as far as
launching cmd.exe, attrib and icacls before crashing into
t.wnry, or whether it stalls in the two 60-second waits. For understanding the
encryption, by contrast, a dynamic pass adds nothing: there is no encryption here to run.
Public-context sources
- Microsoft, 12 May 2017 — the domain check in the dropper, the
mssecsvc2.0service, thetasksche.exepaths, MS17-010 andCVE-2017-0145 - Mandiant, 15–17 May 2017 — the variants with changed domains, the 14 May one with no killswitch and a corrupted ransomware component, and that it is unclear whether the change came from the operators or a third party
- SecurityWeek, 15 May 2017 — the hex-editor-patched variant, the "only 2 bytes" figure, and Bitdefender's wallet argument
- Al Jazeera, 14 May 2017 — Europol's figure: more than 200,000 victims in at least 150 countries
- National Audit Office, 27 October 2017 — 81 of 236 trusts, 603 organisations, 595 GP practices, over 19,000 cancelled appointments, and no patient data stolen
- Infosecurity Magazine, 23 May 2017 — the $109,000 running total and the comparison with the number of victims
- The Global Treasurer, 3 August 2017 — the total of around $140,000 and Elliptic's tracking of the withdrawals
- Foreign Policy, 19 December 2017 — the official attribution to North Korea and the countries that backed it
- CyberScoop, 6 September 2018 — the criminal complaint against Park Jin Hyok, Chosun Expo, the Reconnaissance General Bureau and the two conspiracy counts
- Threatpost, May 2017 — the Shadow Brokers' EternalBlue leak on 14 April and its use in the outbreak