Study of the WannaCry Malware
WannaCry Malware Analysis
WannaCry is a ransomware that encrypts a victim’s machine, specifically targeting the Windows Operating System. The coordinated attack in May 2017 targeted vulnerable Windows around the world and encrypted thousands of workstations. The only solution to decrypt the files, which was not guaranteed, was to pay using Bitcoin due to its nature of anonymity. The malware specifically targeted unsupported versions of Windows XP, Windows Server 2003, and Windows 7. The exploit of EternalBlue, the backbone vulnerability of the WannaCry malware, was discovered by the NSA. It was privately disclosed but was leaked to the ShadowBrokers, an underground marketplace for vulnerabilities and exploits. It is hard to trace a single block to a user unless the user discloses it using their E-Wallet. While ransomware combined with Bitcoin created a challenge for tracing the attack, the preliminary testing showed that the attack originated from North Korea. The additional investigation revealed that the attack is highly suspected to be the infamous Lazarus group from North Korea. WannaCry consists of two parts: a ransomware portion and worm with a kill switch. In this study, we solely focus on the ransomware portion using the poweful tool IDAPro.
We begin the investigation using static analysis.
STATIC ANALYSIS
Imports
The malware uses about 64 imported functions. Listed below are the names of the functions and by referencing the MSDN, their usage:
Address | Ordinal Name | Library | |
---|---|---|---|
00408000 | CreateServiceA | ADVAPI32 | |
* 1) Creates a service and passes it to a service control manager. | |||
00408004 | OpenServiceA | ADVAPI32 | |
* 2) Open the service and returns the handle if it succeeds. | |||
00408008 | StartServiceA | ADVAPI32 | |
* 3) Starts a given service with the following nonzero return if succeded. | |||
0040800C | CloseServiceHandle | ADVAPI32 | |
* 4) Returns the handle to the service control manager. The handle returned after the open service was requested (service opened must be closed). | |||
00408010 | CryptReleaseContext | ADVAPI32 | |
* 5) Function releases the handler to the CSP (Cryptographic service provide) and a key container to store a key. | |||
00408014 | RegCreateKeyW | ADVAPI32 | |
* 6) Creates the specified registry key. However, if the key already exists in the registry it opens it. | |||
00408018 | RegSetValueExA | ADVAPI32 | |
* 7) Sets the data and type of the created open registry key. | |||
0040801C | RegQueryValueExA | ADVAPI32 | |
* 8) Retrieves the type and data of the specified open registry key. | |||
00408020 | RegCloseKey | ADVAPI32 | |
* 9) Closes the handle to the registry key. | |||
00408024 | OpenSCManagerA | ADVAPI32 | |
* 10) Establishes a connection to the service control manager and open the database of the specified target machine. | |||
0040802C | GetFileAttributesW | KERNEL32 | |
* 11) Retrieves the file system attributes of a file/directory. | |||
00408030 | GetFileSizeEx | KERNEL32 | |
* 12) Retrieves the size of the files in bytes. | |||
00408034 | CreateFileA | KERNEL32 | |
* 13) Creates/Open the file/IO device. The file lpFileName should be given to specify the file being opened or created. | |||
00408038 | InitializeCriticalSection | KERNEL32 | |
* 14) Initialize a critical section of an object. Critical section allowed for mutually exclusive access to shared resources. Multiple threads are synchronized when accessing shared resources. | |||
0040803C | DeleteCriticalSection | KERNEL32 | |
* 15) Release all resources used by an unowned critical object. | |||
00408040 | ReadFile | KERNEL32 | |
* 16) Read data from a specified file or IO device. Lbuffer must be set to create a buffer where the data being read will be stored. | |||
00408044 | GetFileSize | KERNEL32 | |
* 17) Retrieves the file size in bytes. Usually, the GetFileSizeEx is recommended instead of this API. | |||
00408048 | WriteFile KERNEL32 | ||
* 18) Writes data to a specified file. | |||
0040804C | LeaveCriticalSection | KERNEL32 | |
* 19) Release the ownership of the critical section object. | |||
00408050 | EnterCriticalSection | KERNEL32 | |
* 20) Waits for the ownership of a critical section object and once the thread gains ownership, the function returns. | |||
00408054 | SetFileAttributesW | KERNEL32 | |
* 21) Set the attributes of a file/directory. | |||
00408058 | SetCurrentDirectoryW | KERNEL32 | |
* 22) Change the current directory based on the parameters of the lpPathName. | |||
0040805C | CreateDirectoryW | KERNEL32 | |
* 23) Creates a new Directory. | |||
00408060 | GetTempPathW | KERNEL32 | |
* 24) Retrieves the path of the directory designated for temporary files. | |||
00408064 | GetWindowsDirectoryW | KERNEL32 | |
* 25) Retrieves the path of the Windows directory excluding the last backslash. | |||
00408068 | GetFileAttributesA | KERNEL32 | |
* 26) Retrieves file system attribute of a file or a directory. Unlike GetFileAttributeW, GetFleAttributeA handles only strings. GetFileAttributeW allows for handling Unicode. | |||
0040806C | SizeofResource | KERNEL32 | |
* 27) Retrieves the size of a specific resource in bytes. | |||
00408070 | LockResource | KERNEL32 | |
* 28) Retrieves a pointer to the specified resource in memory. | |||
00408074 | LoadResource | KERNEL32 | |
* 29) Retrieves a handle that can be used to obtain the first byte of the specified resource. This is used before LockResource, which requires a parameter of the handle. | |||
00408078 | MultiByteToWideChar | KERNEL32 | |
* 30) Maps a string to a UTF-16 string (Usually requires two calls to use) {: style=”background: grey”} | |||
0040807C | Sleep | KERNEL32 | |
* 31) Suspends the execution of the thread until the timeout interval ends (given the parameter of milliseconds). | |||
00408080 | OpenA | KERNEL32 | |
* 32) Creates/Opens a named or unnamed mutex object. | |||
00408084 | GetFullPathNameA | KERNEL32 | |
* 33) Retrieves the full path and file name of a specific file. | |||
00408088 | CopyFileA | KERNEL32 | |
* 34) Copies an existing file to a new file. | |||
0040808C | GetModuleFileNameA | KERNEL32 | |
* 35) Retrieves the full qualified path of a file that has a specified module. | |||
00408090 | VirtualAlloc | KERNEL32 | |
* 36) Allocates memory in the address space of the calling process. | |||
00408094 | VirtualFree | KERNEL32 | |
* 37) Release a region of pages within the virtual address space. (VirtualFreeEx frees memory in another | |||
00408098 | FreeLibrary | KERNEL32 | |
* 38) Frees the loaded DLL and decrements the reference count. | |||
0040809C | HeapAlloc | KERNEL32 | |
* 39) Allocates a block of non-movable memory from the heap. | |||
004080A0 | GetProcessHeap | KERNEL32 | |
* 40) Retrieves a handle to default heap to the calling process. A process can allocate memory without needing to create a private heap. | |||
004080A4 | GetModuleHandleA | KERNEL32 | |
* 41) Retrieves a module handle for the specified module that has been loaded by a current process. | |||
004080A8 | SetLastError | KERNEL32 | |
* 42) Sets the last error code for the calling thread. Functions usually call the SetLastError when it fails. | |||
004080AC | VirtualProtect | KERNEL32 | |
* 43) VirtualProtectEx changes the access protection of a process. However, VirtualProtect changes the protection of a page in the virtual address space of a process. If the page is not committed, the function fails and returns a 0. | |||
004080B0 | IsBadReadPtr | KERNEL32 | |
* 44) Verifies that the current process has access to read the specified range of memory. | |||
004080B4 | HeapFree | KERNEL32 | |
* 45) Frees the memory block allocated from heap usdeHeapAlloc. | |||
004080B8 | SystemTimeToFileTime | KERNEL32 | |
* 46) Converts system time to file time. System time is in UTC, file time is a 64-bit value. | |||
004080BC | LocalFileTimeToFileTime | KERNEL32 | |
* 47) Converts a local file time to UTC. | |||
004080C0 | CreateDirectoryA | KERNEL32 | |
* 48) Creates a directory given a parameter of pathname and security attributes. | |||
004080C4 | GetStartupInfoA | KERNEL32 | |
* 49) Retrieve contents of the STARTUPINFO structure that was specified. | |||
004080C8 | SetFilePointer | KERNEL32 | |
* 50) Moves the file pointer of an open file. | |||
004080CC | SetFileTime | KERNEL32 | |
* 51) Set the time and date of a specific file/directory last access, created and modified. | |||
004080D0 | GetComputerNameW | KERNEL32 | |
* 52) Retrieves the NETBIOS name of the local computer that is established during startup. | |||
004080D4 | GetCurrentDirectoryA | KERNEL32 | |
* 53) Retrieves the current directory of the current process. | |||
004080D8 | SetCurrentDirectoryA | KERNEL32 | |
* 54) Changes the current directory for the current process. The pathname must be given as a parameter. | |||
004080DC | GlobalAlloc | KERNEL32 | |
* 55) Allocates a specified number of bytes from the heap. Unlike HeapAlloc, this memory is movable. | |||
004080E0 | LoadLibraryA | KERNEL32 | |
* 56) Loads the specified module in the space of the current process. | |||
004080E4 | GetProcAddress | KERNEL32 | |
* 57) Retrieves the address of an exported function from a specified DLL. | |||
004080E8 | GlobalFree | KERNEL32 | |
* 58) Frees the specified global memory called by GlobalAlloc and nullifies the handles. | |||
004080EC | CreateProcessA | KERNEL32 | |
* 59) Creates a new process and its main thread. | |||
004080F0 | CloseHandle | KERNEL32 | |
* 60) Closes an Object handle. Usually, the close handle is called once a handle is opened. | |||
004080F4 | WaitForSingleObject | KERNEL32 | |
* 61) Wait for an object to be in a signaled state or when the time out interval passes (which is given as milliseconds) | |||
004080F8 | TerminateProcess | KERNEL32 | |
* 62) Terminates the process and its corresponding threads. | |||
004080FC | GetExitCodeProcess | KERNEL32 | |
* 63) Get the termination status of a specified process. | |||
00408100 | FindResourceA | KERNEL32 | |
* 64) Determines the location of resource given the module, name, and type. | |||
Foot1 | Foot2 | Foot3 |
An image from IDAPro showing an excerpt of the imports:
Packed
It is common for malware to be packed to avoid firewall detection and reverse engineering analysis. Packing is a subset of obfuscation to make the file more difficult to analyze through encryption, compression, or both. A part of the packed program is a stub, a small portion of the code containing the decryption or decompression agent used on the packed file. In most instances, the packaging tool used by the program are commercially or open-sourced available, but custom made packers can be used. To analyze if the WannaCry ransomware is packed, we use Krypt ANAlyzer(KANAL) in PEiD.
As shown above, the malware is not packed, but it was created using Microsoft Visual C++.
We see quite a bit of cryptosystem being used in the program, see figure below. The first one is Adler32, a checksum to detect errors during transmission. Another checksum program is CRC32, which offers the same functionality as Adler32 but is more popularly used. CryptEncrypt and CryptDecrypt are two functions that work mutually; if the file is encrypted with CryptEncrypt, the decrypt API must be called (CryptDecrypt). Towards the bottom of the list, we can see Rijndael’s cryptosystem, or what is commonly referred to as the AES cryptosystem. AES is a ubiquitous cryptosystem due to its robustness against decryption techniques. Lastly, the Zip is easy to describe as the zip extension.
From the imports, our guess is that the program is using startservice. Let us jump to sub_401F5D. We see:
We see a hard-code .exe file name, which could be a duplicate of the ransomware in the system. Let’s jump into sub_401CE8, and look into the funtion calls:
![](/assets/images/WannaCry/Snip20191107_6.png)
![](/assets/images/WannaCry/Snip20191107_7.png)
When using any service or API, the required parameters must be initialized or passed. In most instances of regular programs, the service parameters are hardcoded. However, we can see above that both the parameters lpDisplayName and lpServiceName are initialized using the ESI (the ESI register, a pointer type register). Hence, the name is not hardcoded but is passed by a pointer. We can only know the name of the service using the dynamic analysis (running the malware). Even in the OpenService, the service Name is still relatively hidden.
Mutex
A mutex is a unique identifier to a program. A mutex is designed to allow only one program with the given mutex to run its processes and child threads and avoiding race conditions. It’s a locking mechanism to protect resources from being visited twice or in a racing manner. Mutex allows the program to operate normally, and in a malware sense, it avoids competing with a copy of itself for the resources.
For the malware, we can find its mutex by proceeding into the sub_401EFF function. It is easy to assume that the mutex is Global\MsWinZoneCacheCounterMutexA, but there is a padded 0 at the end. Using sprintf and push offset aSD, the program concatenates the strings Global\xx with the variable 0, as shown above.
DYNAMIC ANALYSIS - Debugger
Unique identifier
Now let us go to WinMain(x,x,x,x), and go into the function call sub_401225. We can see a function call (malware signature) and also some random number generations (e.g. seed). First, we will use an advanced feature in IDAPro called Win32 debugger; Select Debugger->Local Win32 debugger, and then set a breakpoint before call sub_401225. Next, we go into sub_401225 and press F5 to change into pseudocode, which will make it easier for us to analyze. As shown below, we can recognize some if and do while condition, and as well as a srand function. The srand (or the seed generator before rand() is called) is set to 1.
Set a breakpoint at 0x0040202F, as shown below. If we examine the EAX in the general register, it should have the address: 0x0040F8AC. A unique string of values can be seen in the following address, called rfoufmcqgjq199. The malware generates the naming scheme based on the computers name (8-15 random lowercase letters followed by 3 numbers).
Further in our investigation, we face a conditional branch here:
We can suspect that this may be a secret command line start-up. It seems the program is comparing the argument with 2 before the conditional branch is set up. If no, it will take the left branch; if *yes, it will take the right branch. If we run the program to the jnz short loc_40208E, we will get the following result:
The green arrow points to the location of 0040208E, meaning we did not go into the correct branch of operation.
Let us redo the dynamic analysis, but in this case, we put the proper argument as the parameter. Using Debugger->Process Options, we set up the Parameters to be “/i”, as seen on the push offset al :”/i” assembly code on our conditional branch (two images above).
When we run the malware again with the correct parameters, we can see that we arrive at the right branch. We run the program until the code .text 00402057 call sub_401B5F, and view inside sub_401B5F. We can see some PathName such as C:\ProgramData, etc. We can guess that it creates something inside these folders. Now let us F8 through sub_401B5F, and then go to C:\Intel or ProgramData, if the folder exists, using File Explorer. We will see that the new folder that was created and the folder name should be the unique identifier we found earlier (rfoufmcqgjq199).
Hidden Folder
After running sub_401B5F we can see that there was a folder being created inside c:\ Intel folder. By examining the folder, we can see the executable tasksche.exe inside the folder; presumably, the executable that runs the malware. If the folder inside ProgramData or Intel is hidden, we can change the File Explorer settings to show hidden files and folders.
How is it possible to write hidden files? When creating a file, we usually have to set the file attributes and the file name first. To create a hidden file, we must specify it under the dwFileAttributes parameter. For example, a 0x02 input would set the file to be hidden. However, that is just one way to hide files and folders. The command “attrib +h”, which we will see late will do the same function.
Service Name
We previously found that lpDisplayName and lpServicename (and at the .data:DisplayName) are pointing to the address of the following string rfoufmcqgjq199. Additionally, the folder name where taskche.exe is stored is called the rfoufmcqgjq199 folder under Intel. To prove our speculation on the service name, we need to examine the running services after running WannaCry. As shown below, we can see the malware service at the list of Services:
STATIC ANALYSIS Part 2
Let us stop the debugging process and continue our static analysis: For this part, we will examine call sub_401F5D. Let us rename it StartWannacry since if we run the function call, the malware will infect our VM completely. Before running the function, make sure to have a clean Snapshot of the VM. Now let us run to cursor at .text:0040207F jz short loc_40208E”, a line before StartWannacry. Then we will see a file called tasksche.exe being dropped inside the folder created previously. The file is just another copy of the WannaCry Ransomware we are currently investigating (used for persistence).
Let’s proceed with our analysis in the static analysis by restoring to the previous clean snapshot. After “SetCurrentDirectoryA”, we see a function call “sub_4010FD”, which has the following pseudocode on the image below. By using the tool RegShot before and after the instruction calls, we will notice that the malware is trying to create a registry entry under HKLM\Software as Wannacry0r. The function call sub_4010FD is designed is to create a service for the persistence of the taskche.exe in the system.
![](/assets/images/WannaCry/Wanna11.png)
![](/assets/images/WannaCry/Wanna12.png)
We see can instruction ahead call sub_4010FD with the string WNcry@2ol7, which is passed to the function call sub_401DAB. The function is quite complex, but to my general knowledge, it is loading a Resource module called XIA and decompressing it. It compares if one of the loaded modules is called c.wncry and loads it up in memory.
Let us assume that WNcry@2ol7 is a hard-coded password used to unzip the resources (recall this is ransomware). Let us confirm this by using the tool ResourceHacker to open WannaCry and save it as a Zip file. If we unzip this the resource will be released (even if we receive a warning that says your password is incorrect). By opening the /wcry folder, there should be two .exe files, several .wnry files, and a msg folder. The malware authors may have zipped the resources to avoid firewall detection due to its sheer size.
The message folder contains the infamous “Warning Message” in different languages, which WannaCry posts detailing that all the files have been encrypted.
![](/assets/images/WannaCry/Snip20191115_2.png)
![](/assets/images/WannaCry/Snip20191113_16.png)
Bitcoins
Let’s move into sub_401E9E. We see a combination of a hard-coded hash string and a rand number 3. These peculiar hashes are the attacker’s Bitcoin address and the program will randomly choose one Bitcoin address among the 3. If we use www.blockchain.com or other blockchain websites to trace the number of transactions and the money received by those Bitcoin addresses, we can observe the balances accumulated by each account. About 54.44 BTC were paid, assuming the malware authors accumulated all the bitcoins and sold them around Dec. 2017 (shows that people paid for the ransom even if it was not guaranteed).
![](/assets/images/WannaCry/Wanna7_0.png)
![](/assets/images/WannaCry/Wanna7_1.png)
![](/assets/images/WannaCry/Wanna7_2.png)
Hidden files again
We see a command line right before sub_401064:
If we trace back our steps in this task with the XIA resource loading and the current directory being Software\WanaCrypt\wd. We may conclude that “attrib +h ” is a command-line command that hides the files inside this directory. The only way to see hidden files would be to set the current configuration to display hidden files (File Explorer) or under the command prompt, type the command “dir /A”. Finally, the malware grants all users permissions using the command: “icacls ./grant Everyone:F /T /C /Q”.
Next, we click into sub_401064. Now, let us retrace a bit, we have renamed sub_401F5D into StartService, and we identified that the malware wants to start itself as a service. By following the logic flow, we see that sub_401064 is running in parallel with StartService; tasksche.exe is executed as command-line commands much like attribute +h and icalc.After sub_401064, we see another function sub_40170A. Using the cross-reference tool, we can see it calls another function sub_401A45. From the keywords, we know that it focuses on cryptography. Sub_401A45 generates the key that may be needed for encrypting a file. It calls the CryptAPI for Microsoft, which generates a key for encryption and decryption.
This page is part of final for CS495-Reverse Software Engineering , Fall 19’, at Old Dominion University by Dr. Cong Wang.
For more info: Practical Malware Analysis, by Michael Sikorski and Andrew Honig.