Search

Flickr

Labels

WanaCrypt0r Ransomworm

Written by Sergei Shevchenko and Adrian Nish

BACKGROUND

Since the release of the ETERNALBLUE exploit by ‘The Shadow Brokers’ last month security researchers have been watching for a mass attack on global networks. This came on Friday 12th May when it was bundled with ransomware called WanaCrypt0r and let loose. Initial reports of attacks were highlighted by Telefonica in Spain but the malware quickly spread to networks in the UK where the National Health Service (NHS) was impacted, followed by many other networks across the world.

The infographic below illustrates the key components of the WanaCrypt0r ransomware. This is described in further detail in subsequent sections of this report along with initial clues on attribution.

ANALYSIS: Initial Vector

The initial infection vector is still unknown. Reports by some of phishing emails have been dismissed by other researchers as relevant only to a different (unrelated) ransomware campaign, called Jaff.

There is also a working theory that initial compromise may have come from SMB shares exposed to the public internet. Results from Shodan show over 1.5 million devices with port 445 open – the attacker could have infected those shares directly.

The Dropper/Worm

The infection starts from a 3.6Mb executable file named mssecsvc.exe or lhdfrgui.exe. Depending on how it's executed, it can function as a dropper or as a worm.

When run, the executable first checks if it can connect to the following URL:

http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea[.]com

The connection is checked with the WinINet functions, shown below:

01

qmemcpy(&szUrl,

02

"http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea[.]com",

03

57u);

04

h1 = InternetOpenA(0, INTERNET_OPEN_TYPE_DIRECT, 0, 0, 0);

05

h2 = InternetOpenUrlA(h1, &szUrl, 0, 0,

06

INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE,

07

0);

08

if (h2)

09

{

10

InternetCloseHandle(h1); // if connection succeeds, then quit

11

InternetCloseHandle(h2);

12

result = 0;

13

}

14

else

15

{

16

InternetCloseHandle(h1); // if connection fails

17

InternetCloseHandle(0);

18

PAYLOAD(); // then call the payload

19

result = 0;

20

}

21

return result;

That means that if the executable is unable to connect to the URL above, it will call the payload. Alternatively, it will activate a payload on an air-gapped system, such as a system within a hospital network.

It is also worth noting that this connection is not proxy aware, therefore in an enterprise IT environment it is unlikely to be able to connect to the domain triggering the payload.

If the executable is run with no command line parameters, it will register and then run itself as a service:

where %ORIGINAL_NAME% is the original name of the executable, such as mssecsvc.exe or lhdfrgui.exe.

Next, it will start the created service. The payload of the executable will load its own resource called "R/1831", and save it as:

c:\windows\tasksche.exe

The original c:\windows\tasksche.exe file is renamed into c:\windows\qeriuwjhrf.

Finally, the executable will execute the dropped resource as:

"c:\windows\tasksche.exe /i"

If this executable is started as a service, its service handling procedure will invoke a network replication code, explained below.

EternalBlue Port

Since the Shadow Brokers leaked the EquationGroup / NSA FuzzBunch software, a researcher with the handle @zerosum0x0 has reverse engineered the ETERNALBLUE SMBv1/SMBv2 exploit against Windows Server 2008 R2 SP1 x64. This was released on 21st April 2017.

As @zerosum0x0 predicted:

“Every major malware family, from botnets to ransomware to banking spyware, will eventually add the exploits in the FuzzBunch toolkit to their arsenal. This payload is simply a mechanism to load more malware with full system privileges... This is a jewel compared to the scraps that were given to Stuxnet. It comes in a more dangerous era than the days of Conficker. Given the persistence of the missing MS08-067 patch, we could be in store for a decade of breaches emanating from MS17-010 exploits. It is the perfect storm for one of the most damaging malware infections in computing history.”

This work was further expanded on with an open-source project "MS17-010 Windows SMB RCE", developed by RiskSense Operations, and includes both a Metasploit scanner and a Python port.

On 9th of May 2017, the Python port was further improved to "Store original shellcode in binary, rather than python string representation".

In order to "Make it faster", the shellcode was now declared as binary, further lowering the barrier of porting it into C++ code.

It appears that the ransomware took advantage of the published Python source, along with the shellcode binaries – the SMB structures found in the ransomware are identical to the published ones (e.g. the “Exploits” section of this project was used to infect remote hosts with DOUBLEPULSAR backdoor). The published raw SMB packets appear to be copy-pasted into C++ code, and then recompiled using ported blobs – most likely without even understanding how the EternalBlue SMBv1/SMBv2 exploit actually works.

A detailed description of the network replication and worm functionality is described in Appendix B.

The Payload

The payload is a 3.4Mb file called tasksche.exe, created from the worm's resource "1831". Such a large size is explained by the bundled TOR executables along with other tools and configuration files.

Internal name of this executable is diskpart.exe.

This file contains another embedded resource in it, named as "XIA/2058". This resource is a ZIP file.

If the file detects it was executed without the "/i" switch – that is, it was not executed by the worm, it will register itself as a service to provide itself with a persistence mechanism that does not require the worm.

For that, it will first generate a pseudo-random name that is derived from the current computer name. For example:

tdyhddeaprj852

Next, it will create read-only directories, and copy itself into those directories, such as:

• c:\ProgramData\%RANDOM_NAME%\%EXE_NAME%

• c:\Intel\%RANDOM_NAME%\%EXE_NAME%

where %RANDOM_NAME% is the previously generated pseudo-random name, and %EXE_NAME% is the name of its own executable.

Hence, the total amount of the collected ransom at the time of writing is ~USD$68K.

The selected Bitcoin address is then saved back into c.wnry file. Thus, the purpose of this file is to store configuration.

Next, the ransomware runs the following commands to assign 'hidden' attribute to all of its files and to allow full access rights for all users:

"attrib +h ."
"icacls . /grant Everyone:F /T /C /Q"

It then imports a 2048-bit public RSA key from a hard-coded 1,172-byte blob, stored within the executable.
Next, it reads the unzipped resource file t.wnry that starts from a "WANACRY!" marker, and decrypts an AES key from here, using an RSA public key.

The recovered AES key is then used to decrypt the rest of t.wnry file contents, using AES-128 (CBC).

The blob decrypted from t.wnry turns out to be a PE-file - the malware parses its PE header, then dynamically loads into a newly allocated memory, and calls its entry point.

This PE file is a DLL, and the called entry point corresponds to its DllEntryPoint() export.

Internal name of this DLL is kbdlv.dll. The malware locates and then calls its export TaskStart().

The Ransomware DLL

The main DLL module of the ransomware has an internal name kbdlv.dll. Its export TaskStart() is called to invoke the ransomware’s file encryption logic.

The DLL first creates a mutex "MsWinZonesCacheCounterMutexA" to make sure there is only one copy of ransomware activated. Next, it reads c.wnry - a configuration file that stores the list of TOR services.

The ransomware will attempt to terminate a number of processes, such as SQL server and MS Exchange server, by running commands:

Both old and new version extract the ZIP file into the TaskData folder.

It's worth noting that the older variant of ransomware also attempted to replicate across \\%IP%\ipc$ network shares. Hence, the idea of the network replication was brewing in the attackers' minds long before 'The Shadow Brokers' release.

The older version of WanaCrypt0r ransomware relies on a function that generates a random buffer, using an internal table that consists of 75 WORDs:

The implementation of this function is very unique - it cannot be found in any legitimate software. The only other sample where this function can also be found (almost identical, but with minor tweaks) is a sample of Contopee backdoor (MD5: ac21c8ad899727137c4b94458d7aa8d8), first submitted to VirusTotal on 15th August 2015.

This code overlap was first noticed and tweeted by Google researcher Neel Mehta. This was quickly followed up on by Kaspersky Labs in a blogpost.

The Contopee backdoor sample uses this function as part of its communication protocol with the C&C server. This backdoor family is a tool from the Lazarus threat actors.

The re-use of code is a characteristic of the Lazarus group we noted in our report last year on attacks against SWIFT systems. This re-use is at the source-code level, providing strong evidence of common development environment.

This, along with other overlaps with Lazarus’ previous campaigns is described below:

Lazarus has targeted Bitcoin related companies in recent months – possibly looking for ways to steal/launder funds.A watering-hole (same as described in our blog) was setup in February on a popular Bitcoin website.

WanaCrypt0r uses Bitcoin addresses to receive ransom payments.

As noted in our attribution post last year, use of Visual Studio 6.0 is not a significant observation on its own – however, this development environment dates from 1998 and is rarely used by malware coders. Nonetheless, it has been seen repeatedly with Lazarus attacks.

CONCLUSIONS

Coupling an SMB worm to ransomware has created a highly effective threat – albeit one which wreaks havoc for relatively little monetary gain. Even though $68K may represent a modest profit for the attackers, moving the money from those bitcoin wallets will attract significant attention from law-enforcement and could identify their money-laundering networks. It is very likely they will not get their hands on any money once this is all over.

Whilst the SMB worm code has been copy/pasted from elsewhere, the ransomware author is clearly an experienced malware-dev. They include checks such as filepaths for anti-ransomware products to avoid detection of their operation. There are mistakes though, such as the “kill-switch” which has been widely discussed. Assuming they used the Python port of code released on 9th May, it implies a very short turn around between development and attack; it is therefore possible the worm got loose whilst the code was still in testing. Either way, the attackers will learn from this campaign, and may return with updated code whilst vulnerabilities remain unpatched.

The linkages to the Lazarus campaign are tantalising clues as to who may be ultimately behind this. Following on from last year's attacks on SWIFT systems and this year's attacks on banks in Poland & Mexico they continue to demonstrate that they are a considerable menace to network defenders. Understanding their tools, techniques and procedures is challenging given the shifting nature of attacks seen, however deserves maximum focus and co-operation across the security community.

The biggest lesson to be learned from this attack though is the on-going challenge which organisations running critical infrastructure face with patching. This isn’t the first case of self-propagating malware impacting healthcare networks we’ve investigated; indeed this reminds us a lot of the QBot/Qakbot episode last year. Then, as now, hospitals are exposed by running on out-of-date systems and with minimal resources to spend on security. The WanaCrypt0r campaign has brought this to international attention – how to fix the problem going forward will need swift debate among technology experts and policy makers to avert similar crises in future.

RECOMMENDATIONS

• Install patch MS17-010 as a matter of urgency. For out of support operating systems such as XP, Win8 and Server 2003 apply the out of band patch.

• Block all incoming connections on ports 137,139, 445 and 3389 (i.e external to internal) to stop the worm coming into the network.

• Consider blocking connections on port 445 (SMB shares) internally if not business critical until the worm has subsided.

• Ensure that connections to the domain: www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwea[.]com are permitted, This is site is reported to act as a kill switch, for some variants, preventing encryption. Connectivity can be tested with the following python script.

APPENDIX B – The Network Replicator

The worm replicates across the network using two threads: the first one provides replication across the local network, and the second one - across random IP ranges, thus affecting external addresses (such as honeypots or other exposed SMB shares).

To replicate across internal network, the worm first calls GetAdaptersInfo() to obtain network configuration for each network adapter associated with the system.

The network configuration allows it to use current IP address and mask to build a list of local IP addresses.

For example, if the local IP address is 192.168.78.132, and the subnet mask is 255.255.255.0, the worm may build a list of 254 IP addresses that are displayed below in their binary format, such as 014EA8C0 ("192.168.78.1"), 024EA8C0 ("192.168.78.2"), and up to FE4EA8C0 ("192.168.78.254"):

NOTE: the constructed list is trailed with the BAADF00D markers.

This list is then passed to a newly spawned thread to enumerate it, and the worm will then attempt to replicate to each target in the list.

The second network replication thread is spawned each 2 seconds up to 128 times. Each instance of this thread will generate a random IP consisting of 4 octets:

IP1.IP2.IP3.IP4

Each octet is a random value from 0 to 255, generated using CryptGenRandom() API - a cryptographically secure pseudorandom number generator.

First octet IP1 cannot be set to 127, 224, or 225. If the worm is able to connect to a target with IP address IP1.IP2.IP3.IP4 over port 445, it will then enumerate 255 IP addresses from IP1.IP2.IP3.1 to IP1.IP2.IP3.255. The worm will attempt to replicate to each enumerated target.

This thread is spawned 128 times - the round number is passed to the thread as an argument, so it is aware about the current round of its own execution. The thread uses it along with an internal timer (using 20 and 40 minute intervals) to define the logic of regeneration of IP1 and IP2 parts of the random IPs.

Both threads rely on the same network propagation mechanism: for each target IP, the worm first attempts to connect on port 445 and submit it two SMB requests, with an attempt to establish if the MS17_010 SMB Vulnerability exists:

• negotiate_proto_request

• session_setup_andx_request

The code below shows how these packets are submitted:

01

name.sa_family = 2;

02

*(_DWORD *)&name.sa_data[2] = inet_addr(cp);

03

*(_WORD *)&name.sa_data[0] = htons(hostshort);

04

hSocket = socket(2, 1, 0);

05

__hSocket = hSocket;

06

if ( hSocket != -1 )

07

{

08

if ( connect(hSocket, &name, 16) != -1

09

&& send(__hSocket, negotiate_proto_request, 88, 0) != -1

10

&& recv(__hSocket, &buf, 1024, 0) != -1

11

&& send(__hSocket, session_setup_andx_request, 103, 0) != -1

12

&& recv(__hSocket, &buf, 1024, 0) != -1 )

On a network level, WireShark recognises these two packets as Negotiate Protocol Request and Session Setup AndX Request.

Negotiate Protocol Request:

Session Setup AndX Request:

The disassembled source of the worm shows how the Negotiate Protocol Request is built:

The disassembled source shows the Session Setup AndX Request (only the end of it is shown):

The Session Setup AndX Request will get a response, and the code parses it to extract the native_os field from it.

Following this, the worm composes an IPC share name such as:

\\%IP_ADDRESS%\IPC

Next, the ransomware submits two other SMB requests:

• tree_connect_andx_request

•
peeknamedpipe_request

First, the Tree Connect AndX Request:

Once the host responds, the code will read tree_id, process_id, user_id, and multiplex_id, in order construct a new SMB request. In that new request, the following placeholders within request templates will be replaced with the extracted values:

• __TREEID__PLACEHOLDER__

• __USERID__PLACEHOLDER__

• __TREEPATH_REPLACE__

The PeekNamedPipe Request is then submitted, recognised in WireShark as:

The SMB header extracted from the received response is then parsed to see if nt_status contained in it equals 0x0C000205. Here is how the malware parses the four bytes of such status (bytes 05, 02, 00, 0C):

01

if (send(__hSocket, peeknamedpipe_request, 78, 0) != -1 // if sent

02

&& recv(__hSocket, &buf, 1024, 0) != -1 // and recv() is Ok

03

&& nt_status_0 == 5 // and nt_status byte #0=05

04

&& nt_status_1 == 2 // and nt_status byte #1=02

05

&& !nt_status_2 // and nt_status byte #2=00

06

&& nt_status_3 == 0xC0u) // and nt_status byte #3=0C

07

{ // if nt_status==0x0C000205

08

closesocket(__hSocket);

09

return 1; // return TRUE, host is vulnerable to MS17-010

10

}

11

...

12

return 0; // return FALSE – the host is NOT vulnerable

If the host is vulnerable to MS17-010, the worm waits for three seconds and then checks if it is already infected with DOUBLEPULSAR – in order to replicate itself, it needs an active DOUBLEPULSAR backdoor to be installed at the host.

In order to check that, it builds and then submits SMB Trans2 Request or trans2_request.

As seen below, the subcommand field within trans2_request request is set to SESSION_SETUP, which is a covert beacon request to the DOUBLEPULSAR backdoor:

If the host is infected with DOUBLEPULSAR, the response will contain "Multiplex ID" set to 81 (0x51). Here, the worm sends trans2_request request, and checks if multiplex_id equals 0x51:

01

if (send(hSocket, trans2_request, 82, 0) != -1 // if send() Ok

02

&& recv(hSocket, &buf, 1024, 0) != -1 // and recv() Ok

03

&& multiplex_id == 0x51) // and DoublePulsar is active

04

...

05

return 1; // return TRUE, is backdoored

If the scanned host is infected with DOUBLEPULSAR, the worm will calculate an XOR key from the SMB’s Signature1 field (sig):

01

unsignedint calculate_doublepulsar_xor_key(unsignedint sig)

02

{

03

return 2 * sig ^ ((((sig >> 16) | sig & 0xFF0000) >> 8) |

04

(((sig <

05

}

This XOR key will later be used as a basic stream cipher to encrypt the payload submitted over SMB:

01

int xor_payload(int xor_key, int buf, int size)

02

{

03

int i;

04

char __xor_key[5];

05

i = 0;

06

*&__xor_key[1] = 0;

07

*__xor_key = xor_key;

08

if (size <= 0)

09

return 0;

10

do

11

{

12

*(i + buf) ^= __xor_key[i % 4];

13

++i;

14

}

15

while ( i

16

return 0;

17

}

The worm next constructs a new SMB packet. The data contained in the packet will contain malicious shellcode. For example, if the target is x64, the shellcode will first walk backwards to find ntoskrnl.exe in kernel memory:

Next, it parses ntoskrnl.exe’s export table, and dynamically obtains addresses for a number of its exports – the exports are found by hashes, a common approach used in shellcode. The hash calculation function is reconstructed below:

01

__int64 get_name_hash(_BYTE *arg_name)

02

{

03

_BYTE *name;

04

int i;

05

__int64 hash;

06

name = arg_name;

07

for (i = 0; ; i = (unsigned__int8)*name++ + (_DWORD)hash)

08

{

09

hash = (unsignedint)(127 * i);

10

if (!*name)

11

break;

12

}

13

return hash;

14

}

For example, a hash of 3E1481DFh corresponds to PsLookupProcessByProcessID(), as explained in an article from Countercept.

NOTE: the 32-bit version of the code is identical in its functionality to its x64 version.

The shellcode will then use kernel’s ZwQuerySystemInformation() API to obtain the list of loaded drivers. Among those drivers, it will be looking for a driver named Srv.sys – the driver is also found by its hash name:

It will then locate the Srv.sys driver’s .data section with the purpose of patching its SrvTransaction2DispatchTable – namely, placing a hook on its SrvTransactionNotImplemented() function, making sure that the shellcode is invoked as a hook handler, as explained by @zerosum0x0.

Next, the worm will construct a payload wrapped into a new SMB packet. For this, it will build a new DLL out of its own .data section. The internal name of the DLL is launcher.dll, and its only export is PlayGame(). The DLL is built using the worm's own file contents, and thus, the DLL is constructed as a thin wrapper around the worm's own executable.

The constructed DLL will be passed to the remote host along with the shellcode to load it up, via SMB, in 4Kb chunks, making sure each chunk is encrypted with the earlier derived XOR key.

With the hook in place, when such a payload packet arrives via SMB, it will be seen by Srv.sys (an SMB driver) as an invalid SMB request. Therefore, it will call SrvTransactionNotImplemented() function from its own dispatch table. Since this function will be hooked, the shellcode with DLL injection logic will be invoked instead, that in turn relies on KeInsertQueueApc().

As a result, the shellcode invoked as a hook handler will allocate memory in the executable region of memory, extract the received DLL in it, and run it in the userspace. This will lead to the execution of the ransomware on the remote host.

The newly built DLL launcher.dll delivered and executed at the host has very little functionality: when its PlayGame() export is called, it only loads up its own resource "W/101", saves and then runs it under a fixed name:

C:\WINDOWS\mssecsvc.exe

Since mssecsvc.exe is extracted from the DLL resource, which in turn is built by worm from its own body, it will be equivalent to the worm executable itself.

If it turns out that the remote host is not infected with DOUBLEPULSAR, the worm will attempt to infect the host with DOUBLEPULSAR, using the same technique as ETERNALBLUE explained above. This attempt will be repeated up to 5 times, with a 3 second interval between the attempts.

A high-level description of the worm’s logic is shown below:

01

if (IS_VULNERABLE_TO_MS17_010(&target, 445))

02

{

03

i = 0;

04

do

05

{

06

Sleep(3000); // wait for 3 seconds

07

if (IS_BACKDOORED(&target, 1, 445)) // DoublePulsar installed?

08

break; // then quit the loop

09

Sleep(3000); // otherwise, wait 3 sec.

10

INFECT_WITH_DOUBLEPULSAR(&target, 445);// install DoublePulsar

11

++i;

12

}

13

while ( i // repeat up to 5 times

14

} // ..until backdoor-ed

15

Sleep(3000); // wait for 3 seconds

16

if (IS_BACKDOORED(&target, 1, 445)) // finally backdoor-ed?

17

SEND_PAYLOAD_RANSOMWARE(&target, 1, 445);// send WCry as DLL

18

endthreadex(0, *&target); // quit the thread

According to this logic, if the host already has DOUBLEPULSAR backdoor installed on it, the worm will send it the ransomware payload to execute it on the remote host. In turn, that instance of the ransomware will try to further replicate.

If the DOUBLEPULSAR backdoor is not installed on the remote host, the worm will try to install it. Only if the DOUBLEPULSAR backdoor is found to be installed on the remote host, only then the worm will try to replicate to it, via the backdoor.