Defeating a Ransomware with Cutter

Introduction

Last week I attended the r2con conference for the first time (for those who haven't heard about it before, it is a reverse engineering conference focused on radare2) and apart from listening to great talks, I signed up for the basic trainings since I had not used r2 in the past and my RE experience is quite basic. One of the trainings was "Dissecting binaries with Cutter" given by Antide Petit, Itay cohen, and Florian Märkl. It was an introduction to the official GUI application for r2, Cutter.

During the training, there were 3 different exercises and one of them was about reversing a binary called "M1ghty Ransomware.exe" which by the name of it, you can guess what it is. Along with the binary, there was also a PNG file called "flag.png" that was encrypted by the malware. We had to use Cutter to analyse the binary and figure out the encryption algorithms, the keys used for the encryption, and finally write a script to decrypt the png file. Sounds easy, right? Well, it took me a while to figure it out. Let's dissect it.

Malware Dissection

When the binary is open with Cutter, the first thing that it shows is an overview of the binary that helps to have a general idea about it.

Cutter Binary Overview

It shows useful information like the architecture, the format, the language it was written in, the entropy, etc.

As a first step for the analysis, I identified the main function. For that I just sorted the list of functions by size and assumed that the largest one was main. This assumption turned out to be right.

Cutter Functions List

After this, I had to identify the encryption algorithm which I couldn't do and had to wait for the hints given during the training:

M1ghty Ransomware Hints

I was curious to know how I could have known that RC4 was the algorithm and the answer was that I should have reversed it in the past so I would have never figured that out at that time or it would have taken me a long time.

I googled a bit and found this post from 2014: An Introduction to Recognizing and Decoding RC4 Encryption in Malware. Hopefully, I'll be able to recognise it in the future.

After the hints, I renamed the functions and searched for the references of the rc4 function:

RC4 Function References in Cutter

As you can see, I saw that there are two calls to this function:

  1. From the fcn.1400012c0 function
  2. From the main function

The code of the call from the main function looks like this:

RC4 Function Call from main in Cutter

Note that the call to the fcn.1400012c0 function happens before the call to rc4 and as shown in the previous image, that function also calls rc4. You'll understand this in a bit. Since there are no syscalls between offsets 0x14000168b and 0x1400016bc, this chunk can be emulated with ESIL. I found this to be one of the most powerful features of r2 and the integration in Cutter is sleek.

Here's a gif showing the emulation:

Gif showing the emulation feature in Cutter

After the emulation, I learnt that:

  1. The function fcn.140001340 returns the string "EMUL4TION+RuLEZ!_R2C0N2o19" which I'll call key1

get_key1 function in Cutter

  1. The function fcn.1400012c0 receives key1 as parameter, calls the rc4 function, and it returns the string "DEFEATING_A_R4NSoMWAR3_W1TH_CUTT3R" which I'll call key2

decrypt_key2 function in Cutter

This means that key1 is used to decrypt key2 using RC4. With this knowledge, I renamed both functions and this is how the code looked like:

Renamed functions in Cutter

As I have seen before, after the call to decrypt_key2 there's a call to rc4 and now I knew that it receives key2 as parameter. After the call to rc4, there are a couple of calls to the randfunction which I'm going to ignore for the moment and I'll come back to them later. Then, I see that there's a string with the value "RANS0MW4RED_" which I'll call ransom_header and finally, there's a call to the WriteFile function.

Up until this point, I knew 2 things that happen to the file in this block:

  1. It is encrypted using RC4
  2. A header, ransom_header, is written at the beginning

I confirmed this by reviewing a hexdump of the file:

Hexdump of the encrypted file

The reason I didn't explain the rand calls shown in the previous block of code is because I ignored them the first time I looked into this and I spent quite a lot of time creating a script to decrypt the file with the information I had from what I explained above.

After many failed attempts, frustrations, and discussions with different people (thanks all!), I came back to Cutter to continue analysing the binary because I was clearly missing something. Breathe.

I started analysing again by reviewing the references to the WriteFile function:

References to the WriteFile function in Cutter

That was interesting, there are 3 calls which means that the file is written 3 times and I have analysed only the first one so I decided to analyse the other two.

This is the relevant code for the second write:

Second call to the WriteFile function

Here's where the rand calls are important. First, I saw that a random number is generated and stored in a variable I called random_number. Then, a little bit after the first call to WriteFile, there's the second call to it and what is it writing? the random_number! The offset 0x14000174a is specifying that only 4 bytes will be written.

Up until here, I knew that the structure of the file was something like [ransom_header|random_number|rc4_encrypted_data], but there was one more call to WriteFile left.

Here's the relevant code for the third write:

Third call to the WriteFile function

Before the call to WriteFile, there's a loop between offsets 0x140001786 and 0x1400017ef. That loop is xoring the file's content at offset 0x1400017df and before the xor instruction, there's a reference to random_number at offset 0x1400017c0. That means that the random_number is the key used for the xor.

And that's all there was to analyse, after this the file is closed and the execution ends. This is the summary of the analysis on what the binary does:

  1. Gets key1
  2. Gets key2 by decrypting it using key1
  3. Use key2 to encrypt the content of the file using rc4
  4. Writes ransom_header in the file
  5. Writes random_number in the file
  6. Use random_number to encrypt the content of the file again using XOR

So, the structure of the file is: [ransom_header|random_number|xor_and_rc4_encrypted_data]

Encrypted file structure

The bytes in red are the ransom_header, the bytes in green are the random_number, and the rest is the encrypted data with xor and rc4.

Decrypting the file

To decrypt the file, I needed to write a script to:

  1. Read the file content ignoring ransom_header
  2. Read the first 4 bytes of the file content which represent random_number
  3. Use random_number to decrypt the file content using XOR
  4. Use key2 to decrypt the file content using RC4
  5. Write a new file with the decrypted content

To do this, I wrote the following script in Go.

package main

import (
    "bufio"
    "crypto/rc4"
    "io/ioutil"
    "os"
)

func main() {
    path := "./flag.png"
    ransomHeader := "RANS0MW4RED_"
    f, err := os.Open(path)
    defer f.Close()
    check(err)

    reader := bufio.NewReader(f)
    _, err = reader.Discard(len(ransomHeader))
    check(err)

    randomNumber := make([]byte, 4)
    _, err = reader.Read(randomNumber)
    check(err)

    data, err := ioutil.ReadAll(reader)
    check(err)

    xoredData := xorEncryptDecrypt(randomNumber, data)

    key2 := []byte("DEFEATING_A_R4NSoMWAR3_W1TH_CUTT3R")
    rc4edData := rc4EncryptDecrypt(key2, xoredData)

    ioutil.WriteFile("./dflag.png", rc4edData, 0644)
}

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func rc4EncryptDecrypt(key, data []byte) []byte {
    cipher, _ := rc4.NewCipher(key)
    dst := make([]byte, len(data))
    cipher.XORKeyStream(dst, data)
    return dst
}

func xorEncryptDecrypt(key, data []byte) (output []byte) {
    for i := 0; i < len(data); i++ {
        output = append(output, data[i]^key[i%len(key)])
    }

    return output
}

After executing this with go run main.go I got the following file:

Decrypted file

Final Thoughts

That was it! I had a lot of fun with this challenge and I got to experience how powerful r2 and Cutter are so if you have any interest in RE, make sure to check them out! I can also say that the r2 community is super friendly and they are more than happy to answer any question.

If you'd like to play with this exercise, you can find find it at https://github.com/radareorg/r2con2019/tree/master/trainings/cutter along with the rest of the material from the conference.

I hope you found this useful and if you have any comment, suggestion, or question you can reach me on Twitter (my DMs are open) @_camaya or you can send me an email to <cam at camaya.co>.