The first thing that I did after unpacking the zip file was check what files were included in this program.
There is just one file to analyze. For this crackme I will be using Radare2 for both my static and dynamic analysis. This first thing that I always do is take a look at some of the basic file information. Let’s get started!
Cool, so it looks like this is a 64 bit binary written in C. Now let’s take a look at the functions.
Nothing really out of the ordinary here. Mostly standard library imports but there are a couple of user defined functions – fcn.00000bca and fcn.00001090. We will keep our eye on these later. For now let’s jump into main.
Here is the first part of the main function. Looks like it is doing normal normal start-of-function operations at first (establishing the stack frame, setting a canary, initializing variables). The first really interesting thing that that the program does is move 0xdeadbeef into EDI and then call srand. This tells us that the “random” number that will presumably be used to help encrypt our files is actually static. Let’s look at the next block.
Now that we have our random number, there is going to be a bunch of arithmetic applied to it and it eventually ends up being stored in local_a4h. To help me keep track of some of my variables, I am going to rename the local_a4h to randomNum and local_8h to stackCanary.
Moving on, the next function call that we reach is a call to strlen. This probably isn’t important right now so we will keep moving.
Here is something interesting. It looks like the program is making a call to opendir with 0x000010b8 (ASCII . ) as a parameter and then making a jump based on whether the open was successful or not. This would be a good time to take a bigger picture look at the program. Let’s open it up in visual mode with the VV command:
If the open is successful, then the program continues on to another jump and if the open fails…
The program prints its ransom message. Let’s continue exploring the first branch.
The next branch leads to a new function call: readdir. From the man page we know that it returns a pointer to a dirent structure representing the next file or directory entry in the directory stream pointed to by dirp. local_98h must be a pointer to the next file entry in the current folder. The jump here is saying if the returned pointer is not zero (in other words, if there is still another file in our folder) then jump to 0xdab. Otherwise we have gone through every file in this folder already and need to print the ransom message that we saw earlier.
In the next block we are moving some data around in our registers, making a comparison, and then jumping to 0xecf if the strings match. Before trying to dig into what these register values might be, let’s take a look at where this jump could lead.
There isn’t much going on here before starting the readdir block again. This file is basically being skipped. But why would the program do that? Let’s review what we know.
- Two strings – one unknown and one filename in our directory – are being compared
- If these two strings are equal to each other, then skip the file.
Maybe our unknown string is the filename of the program? That would make sense as our program would not want to encrypt itself. We can find this out for sure by examining the memory, but for now I think that this is a good enough theory to continue with the analysis.
The other branch to our “name test” block looks like this. Here we see the first user defined function call being made. Just like before, let’s examine where this function call could land us before diving into the function itself. If you are familiar with 8086 assembly then you see that test EAX, EAX is just another way of saying “if EAX = 0″. Since we know that function return values are passed in EAX we can see that if EAX = 0 then jump to 0xecf, else continue to 0xde8.
Wait a second, we know that 0xecf is that same address we went to earlier when we wanted to skip a file. Let’s look at the other block. Immediately the calls to fopen jump out at me. Since our ultimate goal is to write a decrypter for this ransomware, this section will definitely be of interest because we know that to encrypt the data in our files, we will need to open them for reading/writing. Let’s examine this block more closely:
The first few lines are the program prepping for the call to fopen. We already know based on the function definition that fopen takes two parameters: a filename and a mode. Let’s start with the value that is being loaded into the RSI. If we examine the memory at that address, this is what we see:
Here we can see that the two most significant bytes at this memory address are 72 and 62 (rb in ASCII). This is probably our mode for fopen as rb will cause the file to be opened in binary read mode. Let’s look at what is being passed in RDI.
You may notice that we have already seen this before although it did look a little different. In our 0xdab block it was shown like this:
Recall that the 0xdab block was checking the filename against another string. Clearly [rax + 0x13] must be the name of our file. Now for this sub.strcat_c30 chunk.
We know that the fopen returns a pointer to the object controlling the stream so that must be what is in local_90h. Next the program moves the name of the file to RDX and some value in local_80h to RAX. We know that strcat concatenates two strings and stores them in a destination so the program must be concatenating the filename with whatever has been loaded into RAX.
After the strcat call the program loads the resulting concatenated string into RAX and then a value at 0x000010bd. Let’s see what that value is.
Looks like a w so we will be opening the new filename in write mode. To recap, the program opened the file that is being targeted for encryption in read mode and then opened a second file consisting of the original filename + some other string in write mode.
Looks like the next thing we do is make a call to feof with our new filename as a parameter. Our branching logic says that if we have not reached the end of file jump to 0xe3f else proceed to 0xe9e. The 0xe9e block will make a call to fclose with the original filename as a parameter, make a call to remove with our original filename, and then another call to fclose with the new filename as a parameter. The 0xe3f block using a call to getc to read a byte from our original file. The byte is then stored in local_a5h and we check again for EOF. Now that we have our data loaded into memory, we can infer that we are getting close to where the actual encryption takes place.
If the program has reached the EOF then it jumps back to our 0xe8b block. EOF is checked for again and when it is found, we proceed to close out the files as we saw earlier in our 0xe9e block. I am not sure why the compiler decided that it would be optimal to jump to 0xe8b rather than straight to 0xe9e. Anyways:
Here is what we have been looking for! The “random” number that we found at the beginning of this post is loaded into EAX and then the lowest 8 bits of this number are XORed with the byte that the program read from our original file earlier. The result is then written to the new file using fputc. Now that we know how the data is being encrypted, we can write a decrypter. First we will need to figure out what our psuedorandom number is. Let’s take another look at how the random number is being generated.
Ugh. Lots of operations and register transfers happening here. We could get out a notepad and go through the operations line by to determine what the magic number is but this would take a while. A better solution would be to run the code in a debugger and set a breakpoint just after the final mov at 0x00000d4d.
The first step is to open the program in debug mode with radare2. Now let’s find the correct memory location.
So we will need to set the breakpoint at 0x558a0337ad53. We can do that by running the following command:
If we run pdf again we should see a b next to the memory address where we set the breakpoint.
Great! Let’s run the code now.
When we tell the debugger to continue the program is stopped when it hits the breakpoint. Here we have two options; we can either dump the eax register or we can peek at the local_a4h variable as they should both contain the same value. As you can see, they do. The value that is being XORed with our data is 0x0000005c. This should be all the information that we need. Let’s write that decrypter!
I’ll leave it to the reader to step through the code in detail, but I will point out a couple things. The magic number is the number that we found was being XORed with the char data. The decrypter works by looping through the encrypted file (passed as a command line argument), XORing each character with the magic number, and then writing the decrypted character to a new file.
This concludes this crackme! If you would like to run the code yourself it is available upon request. Just contact me via the contact form and I will get back to you!