Part 10 of Flare-On 6 CTF Writeup

This is the tenth part of the Flare-On 6 CTF WriteUp Series.

10 – Mugatu

The challenge reads

Hello,
I’m working an incident response case for Derek Zoolander. He clicked a link and was infected with MugatuWare! As a result, his new headshot compilation GIF was encrypted. To secure an upcoming runway show, Derek needs this GIF decrypted; however, he refuses to pay the ransom. We received an additional encrypted GIF from an anonymous informant. The informant told us the GIF should help in our decryption efforts, but we were unable to figure it out. We’re reaching out to you, our best malware analyst, in hopes that you can reverse engineer this malware and decrypt Derek’s GIF.

I’ve included a directory full of files containing:
MugatuWare malware
Ransom note (GIFtToDerek.txt)
Encrypted headshot GIF (best.gif.Mugatu)
Encrypted informant GIF (the_key_to_success_0000.gif.Mugatu)

Thanks, Roy

The challenge is a reference to the movie Zoolander 2. The main objective is to decrypt a GIF file which has been encrypted by the ransomware. In addition to these two files we have an additional encrypted GIF which has been provided for our help. Since we are dealing with a ransomware it’s best to use a Virtual Machine. Doing so will also enable us to run the sample and observe its behavior.

Dynamic Analysis

For dynamic analysis I’ll be using API Monitor. This free tool allows us to trace all the API calls made by an program while its running. Using the tool we notice the following:

Figure 1: Twitrss.me

Figure 1: Twitrss.meA POST request to mugatu.flare-on.com. Interestingly, querying with nslookup from the terminal returns NXDOMAIN which implies it’s a non existent domain.

Figure 2: POST request to mugatu.flare-on.com

Figure 2: POST request to mugatu.flare-on.com

Figure 3: NXDOMAIN

Figure 3: NXDOMAIN

Let’s add the following entry to our host file (C:\Windows\System32\drivers\etc\host) for redirecting traffic to mugatu.flare-on.com to localhost.

127.0.0.1 mugatu.flare-on.com

We need to set up a HTTP server for receiving the POST request. We can code one in Python (like below) or use a tool like Packet Sender. Once the server is setup let’s run the binary.

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):

def do_POST(self):

content_length = int(self.headers[‘Content-Length’])

body = self.rfile.read(content_length)

print body

self.send_response(200)

self.end_headers()

httpd = HTTPServer((‘localhost’, 80), SimpleHTTPRequestHandler)

httpd.serve_forever()

The malware sends out POST requests which looks like Base64 encoded data.

λ python post_server.py

7u6sH0UAAAAIQk4RXEsKXQ5UQVxLHX8dVUIfDX4WV14IYQVNGRwGAAATFRFPBhQmGi8+EwtEABFTHRBRQF1VVlZVRhcDHllUVhRbEVg=

127.0.0.1 – – [30/Sep/2019 13:54:11] “POST / HTTP/1.1” 200 –

7u6sH0UAAABBZG1pbnwxMC4wLjIuMTV8Ni0xLTc2MDF8QWRtaW5pc3RyYXRvcnxDOlxXaW5kb3dzfDA5LzMwLzIwMTktMDg6MjQ6MTA=

127.0.0.1 – – [30/Sep/2019 13:54:17] “POST / HTTP/1.1” 200 –

7u6sH0UAAAAIQk4RXEsKXQ5UQVxLHX8dVUIfDX4WV14IYQVNGRwGAAATFRFPBhQmGi8+EwtEABFTHRBRQF1VVlZVRhcDHllUVhRbEVg=

127.0.0.1 – – [30/Sep/2019 13:54:22] “POST / HTTP/1.1” 200 –

If we look at the POST request in a debugger we can find expects a response to the POST request which apparently should also be Base64 encoded as shown in Figure 4.

Figure 4: The response should be base64 encoded

Figure 4: The response should be base64 encoded

After Base64 decoding the response, it’s xored with 0x4D and it must match the string orange mocha frappuccino\0. Appended with this string we need to send another 4 byte value which will later be used as a key to encrypt files as we will soon see. In the code below, we are sending the key \x01\x02\x03\x04.

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler

import base64

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):

def do_POST(self):

content_length = int(self.headers[‘Content-Length’])

body = self.rfile.read(content_length)

print body

self.send_response(200)

self.end_headers()

start = str(bytearray(map(lambda x: x^0x4d, bytearray(‘orange mocha frappuccino\0’))))

key = ‘\x01\x02\x03\x04’

to_send = start + key + ‘\xff’*34

self.wfile.write(base64.b64encode(to_send))

httpd = HTTPServer((‘localhost’, 80), SimpleHTTPRequestHandler)

httpd.serve_forever()

Let’s go back to API Monitor. We spot a series of the following function calls

GetLogicalStringsAGetDriveTypeAFindFirstFileA

Typically, in a ransomware these functions are generally used to enumerate all the files in a directory.

Figure 5: Call to FindFirstFileA

Figure 5: Call to FindFirstFileA

Let’s set a breakpoint on the functions and try to know what it’s really doing. To thwart analysis, all the API calls have been obfuscated. Shown in Figure 6 are the stubs which ultimately lead to the real API.

Figure 6: Obfuscated API calls stub

Figure 6: Obfuscated API calls stub

For example in Figure 7, it is actually a call to FindFirstFileA. Instead of a direct call, it calls the corresponding stub which ultimately lands on the real function.

Figure 7: Obfuscated call to FindFirstFileA

Figure 7: Obfuscated call to FindFirstFileA

Deobfuscating API calls

Figure 8: Addresses of the stubs

Figure 8: Addresses of the stubs

The addresses of the stubs are stored in a pointer table as shown in Figure 8. To deobfuscate, we can simply write the address of the final function in the pointer table bypassing the stub. The following x64dbg script does that

addr = dump.sel()

i = 0

loop_1:

mov api_jump, [addr]

api = dis.imm(api_jump)

not api

mov [addr], api

add addr, 4

inc i

cmp i, 0x50

jl loop_1

After running the script, the pointer table look like Figure 9.

Figure 9: Updated pointer table

Figure 9: Updated pointer table

Correspondingly, the disassembly is now readable as we have removed all the indirect calls.

Figure 10: Call indirection removed

Figure 10: Call indirection removed

The malware recursively iterates over all directories starting from C:\ drive. As we can see in Figure 11, it compares the directory name with the string “really, really, really, ridiculously good looking gifs”.

Figure 11: The malware it searching for a special directory

Figure 11: The malware it searching for a special directory

Which means its searching for a directory with that specific name. Lets create one such directory like C:\$\really, really, really, ridiculously good looking gifs. We have created the directory under a directory named $ so that it will be found first. Within the directory we keep a GIF file since we know it encrypts them. Instead of a real GIF we can use a dummy file filled with the a bunch of A’s such that it will be easier to spot in the debugger If at this point we let the malware run freely, we’ll see that it goes on to encrypt the dummy GIF with .Mugatu extension appended.

Finding the encryption algorithm

We need to locate the encryption code by which it encrypts the files. A quick way is to set breakpoint on File Handling APIs like CreateFileA, ReadFile, WriteFile etc. Using this approach we can quickly zero in on the relevant code as shown in Figure 12.

Figure 12: Code related to encryption

Figure 12: Code related to encryption

The malware loads the file in memory using CreatFileA, CreateFileMappingA and MapViewOfFile. A few lines below we notice an indirect call to a function which does the encryption.

Figure 13: Call to encrypt function

Figure 13: Call to encrypt function

This function takes in three parameters:

A pointer to the buffer containing the file contents to encryptLength of the above bufferPointer to the 4 byte key (which in our case was \x01\x02\x03\x04)push ebp
mov ebp,esp
push ebx
push esi
mov esi,dword ptr ss:[ebp+C]
push edi
————–snip————–
add edx,esi
xor ecx,eax
sub esi,61C88647
add ecx,ebx
————–snip————–
pop esi
pop ebx
pop ebp
ret

Inspecting the code we notice the constant 61C88647. Searching for this value on Google points us to Tiny Encryption Algorithm (TEA). Note that the actual constant used in TEA source code is 9E3779B9 (which is the same as -61C88647when treated as an unsigned 32-bit integer). Apart from TEA, the XTEA cipher also uses the same constant and have a similar structure.

Let’s try to decompile the encryption code. We can do this by copying the assembly to a new file and assemble it using fasm.

format PE

encrypt:

push ebp

mov ebp, esp

push ebx

push esi

mov esi, dword [ebp+0xC]

push edi

mov edi, dword [esi]

mov ebx, dword [esi+0x4]

xor esi, esi

here:

mov ecx, dword [ebp+0x10]

mov eax, esi

and eax, 0x3

movzx edx, byte [eax+ecx*1]

mov ecx, ebx

shl ecx, 0x4

mov eax, ebx

shr eax, 0x5

add edx, esi

xor ecx, eax

sub esi, 0x61C88647

add ecx, ebx

xor ecx, edx

mov edx, dword [ebp+0x10]

add edi, ecx

mov ecx, edi

mov eax, edi

shr eax, 0x5

shl ecx, 0x4

xor ecx, eax

mov eax, esi

shr eax, 0xB

add ecx, edi

and eax, 0x3

movzx eax, byte [eax+edx]

add eax, esi

xor ecx, eax

add ebx, ecx

dec dword [ebp+0x8]

jne short here

mov esi, dword [ebp+0xC]

mov dword [esi], edi

pop edi

mov dword [esi+0x4], ebx

pop esi

pop ebx

pop ebp

ret

After renaming the variables appropriately, our decompiled code looks like Figure 14.

Figure 14: Decompiled encryption function

Figure 14: Decompiled encryption function

The TEA cipher uses a fixed number (32) of rounds which is not the case with here. This encryption algorithm here is actually a modified version of the XTEA cipher using a 32 bit key instead of the standard 64 bit.

Decrypting the informant GIF

The informant GIF has the filename the_key_to_success_0000.gif.Mugatu. The name hints that the key used to encrypt this file is \x00\x00\x00\x00. Using the following code we can decrypt the informant GIF.

#include <stdio.h>

typedef unsigned int uint32_t;

typedef unsigned char uint8_t;

void xtea_decipher(unsigned int num_rounds, uint32_t v[2], uint8_t const key[4]) {

unsigned int i;

uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;

for (i=0; i < num_rounds; i++) {

v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum>>11) & 3]);

sum -= delta;

v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);

}

v[0]=v0; v

Latest articles

Related articles