This is the first in a series looking at part of the REvil malware. I will start off by showing a brief triage overview of the sample and then dive into the initial details of the stage 1 unpacker. Let us get into it!
The Revil (aka Sodinokibi) malware is ransomware that encrypts files on a victim’s disk and leaves a note to head to a Tor link to send payment to decrypt your files. The sample I am analyzing has the following has the hash.
λ sha256sum.exe revil.bin 329983dc2a23bd951b24780947cb9a6ae3fb80d5ef546e8538dfd9459b176483 *revil.bin
Uploading the sample to Virustotal showed that it was detected as malicious by the majority of antivirus engines.
I ran the sample using the sandbox Any.Run, and during the run, you can see it encrypt the files and change the background to instruct the user to look at the ransom note.
After the quick triage showing what this sample does to the victim’s computer, we will start to dive deeper into various aspects of how this sample operates, starting with the initial unpacking.
The Revil malware has two stages, the first stage contains an RC4 encrypted second-stage payload that is unpacked into memory. The second stage payload executes the ransomware functions encrypting files on disk. This executable follows a few steps where the second stage data is decrypted, placed into memory, and then executed.
The main function reflects this flow, looking at the marked-up IDA de-compiler screenshot. You can see the RC4 key copied into a memory buffer used to set up the RC4 KSA.
The resulting S array is passed into the decryption payload function.
The decryption loop pulls data from a pointer I named PAYLOAD_DATA that points to the start of the .enc section of the binary file. The data is decrypted and written back into the .enc section.
To simplify second stage extraction for further analysis, I have written a simple python script to extract the payload, decrypting it, and writing the second stage content to disk.
import pefile import ARC4 pe = pefile.PE(firststage) key = "kZlXjn3o373483wb6ne1LIBNWD3KWBEK" section_name = "enc" for section in pe.sections: if bytes(section_name, 'utf-8') in section.Name: section_data = section.get_data() cipher = ARC4(key) dump = cipher.decrypt(section_data) print (dump[:20]) f = open("stage2.bin", "wb") f.write(bytearray(dump)) f.close()
After this data is decrypted, it is loaded into memory using Windows Native API calls. First, it allocates a memory space using NtAllocateVirtualMemory and then writes the decrypted data to the newly allocated memory location.
It then dynamically resolves some Imports and executes the second stage code by calling into ecx, which points to the new memory region.
Now the second stage is unpacked and running! In the next post in this series, we will cover how to extract the configuration and parse the configuration data.