FLARE on Challenge 2015

  2015-08-31


Instructions

The FireEye Labs Advanced Reverse Engineering (FLARE) team is an elite technical group of malware analysts, researchers, and hackers. We are looking to hire smart individuals interested in reverse engineering. We have created this series of binary challenges to test your skills. We encourage anyone to participate and practice their skills while having fun!

Challenge 0x01

Walkthrough

Somewhere in the file an email address is hidden. First, let’s extract the exe with 7zip. The next step is to find the information. The cabinet file is the obvious option to do this.

$ 7z l rsrc/RCDATA/CABINET
Path = rsrc/RCDATA/CABINET
Type = Cab
Method = LZX
Blocks = 1
Volumes = 1

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
2015-07-27 17:30:22 ....A         1536               i_am_happy_you_are_to_playing_the_flareon_challenge.exe
------------------- ----- ------------ ------------  ------------------------
                                  1536          634  1 files, 0 folders

Voila, now let’s take a look at the binary in OllyDbg. Quite simple. The user input is checked in an XOR encryption loop. The loop starts at 0x0040104D and uses the encryption key 0x7d.

40104d:	8a 81 58 21 40 00    	mov    0x402158(%ecx),%al
401053:	34 7d                	xor    $0x7d,%al
401055:	3a 81 40 21 40 00    	cmp    0x402140(%ecx),%al
40105b:	75 1e                	jne    0x40107b
40105d:	41                   	inc    %ecx
40105e:	83 f9 18             	cmp    $0x18,%ecx
401061:	7c ea                	jl     0x40104d

Obviously, the password is stored at 0x00402140. With a python two liner we can decrypt the password on the go.

foo="1F 08 13 13 04 22 0E 11 4D 0D 18 3D 1B 11 1C 0F 18 50 12 13 53 1E 12 10".split(" ")
"".join([chr(int(hex(0x7D ^ int(xx, 16)),16)) for xx in foo])

solution

bunny_sl0pe@flare-on.com

Challenge 0x02

Walkthrough

This challenge should also be quite simple. Attach OllyDbg and take a look at very_success. In there, an encryption routine is called at 0x0040105F. The first parameter for the function are 37 bytes at 0x004010E4. The second parameter is the user input. The encryption loop first checks whether the user input is at least 37 bytes long. Then the magic happens and looks too complicated to explain. The XOR key is 0x1C7 in line 0x004010A9.

00401098  |. 8B75 0C        MOV ESI,DWORD PTR SS:[EBP+C]             ;  load user input
0040109B  |. 8B7D 08        MOV EDI,DWORD PTR SS:[EBP+8]             ;  load bytes
0040109E  |. 8D7C0F FF      LEA EDI,DWORD PTR DS:[EDI+ECX-1]         ;  &bytes + 37 - 1
004010A2  |> 66:89DA        /MOV DX,BX
004010A5  |. 66:83E2 03     |AND DX,3                                ;  only keep 3 Bits
004010A9  |. 66:B8 C701     |MOV AX,1C7                              ;  XOR key
004010AD  |. 50             |PUSH EAX                                ;   pushed to stack
004010AE  |. 9E             |SAHF
004010AF  |. AC             |LODS BYTE PTR DS:[ESI]                  ;  load byte from user input
004010B0  |. 9C             |PUSHFD
004010B1  |. 324424 04      |XOR AL,BYTE PTR SS:[ESP+4]              ;  encrypt
004010B5  |. 86CA           |XCHG DL,CL
004010B7  |. D2C4           |ROL AH,CL
004010B9  |. 9D             |POPFD
004010BA  |. 10E0           |ADC AL,AH
004010BC  |. 86CA           |XCHG DL,CL
004010BE  |. 31D2           |XOR EDX,EDX
004010C0  |. 25 FF000000    |AND EAX,0FF                             ;  keep only LSB
004010C5  |. 66:01C3        |ADD BX,AX
004010C8  |. AE             |SCAS BYTE PTR ES:[EDI]                  ;  compare against calculated byte
004010C9  |. 66:0F45CA      |CMOVNE CX,DX
004010CD  |. 58             |POP EAX
004010CE  |. E3 07          |JECXZ SHORT very_suc.004010D7
004010D0  |. 83EF 02        |SUB EDI,2                               ;  iterate backwards
004010D3  |.^E2 CD          \LOOPD SHORT very_suc.004010A2

Again, with a few lines of python magic we can decrypt the password. The code basically reimplements the assembly.

foo="AF AA AD EB AE AA EC A4 BA AF AE AA 8A C0 A7 B0 BC 9A BA A5 A5 BA AF B8 9D B8 F9 AE 9D AB B4 BC B6 B3 90 9A A8".split(" ")
EBX = 0
res = ""
for xx in reversed(foo):
  EDX_shift = EBX & 3
  AH = (1 << EDX_shift) + 1
  EBX += int(xx,16)
  res += chr((int(xx,16) - AH) ^ 0xC7)
print(res)

solution

a_Little_b1t_harder_plez@flare-on.com

Challenge 0x03

Walkthrough

Debug in elfi OllyDbg gives no interesting results. What should we do! The file contains a few references to python. Maybe it is compiled python code? The online tool pyinstallerextractor does not work. Extracting the file from the magic PYZ.pyz header does not yield a result: dd skip=198144 if=elfie of=elfie.pyz bs=1 Running strings on the file however shows some obfuscated python code. Just grepping it all and piping it into a file should allow us to execute the code. Running the newly created elfie.py yields some more obfuscated code. Before doing that, replace the call to exec with a print. After looking long enough at the code, the solution is obvious.

$ strings elfie | sed "s/exec/print/g" | python | grep -oP "in reversed\('moc.+'\)"

Solution

Elfie.L0000ves.YOOOO@flare-on.co

Challenge 0x04

Walkthrough

The file is packed with UPX. Consequently, we have to unpack the file to get a better idea what is going on. Execute the file and let it unpack itself and stop just before the JMP to the code. Trying to dump the unpacked binary with OllyDmp results in an error. Luckily, PeExplorer is able to unpack the file for us.

Upon executing the unpacked binary, the output changed from “2 + 2 = 4” to “2 + 2 = 5”. We probably should not unpack the file, as there are some kind of checks implemented. Nevertheless, analyzing the unpacked file is much easier. First, let’s patch the binary at address 0x001D524C with 0x35 to fix the calculation. Math should always be correct! Next, the code checks for the number of arguments in line 0x004014EE. With an integer as first argument the binary continues it’s magic. After that the binary compares two strings the loop at line 0x00401BB0. Further analysis show, that one hash value is calculated in function 0x004012E0. Only one byte is hashed, and that is the integer we provided as an argument. The second hash value is calculated based on some unknown value.

Why not write a script to iterate all possible values. Win.

import subprocess

for i in range(0,24):
    process = subprocess.Popen(['youPecks', str(i)], stdout=subprocess.PIPE)
    out, err = process.communicate()
    #Do we have a winner
    if "flare" in out:
        print(i, out.strip())

Alternativeley, we can manipulate the value for the check in 0x00401BB0. The time_struct is used to store pointers and lengths while copying the base64 encoded strings to the heap.

Solution

Uhr1them3tic@flare-on.com

Challenge 0x05

Walkthrough

This time we have got a network capture and a binary. The binary reads a file “key.txt”. The file contains a base64 encoded binary string. Obviously, this is where the solution should be. One option is reverse engineering. The alternative is brute-force attack. To be more precise, in this case we have got chosen cipher text attack. We can control the contents of “key.txt” and check the encrypted string. The sender encrypts it and sends the base64 encoded string to the server. Even better so, base64 encodes three bytes in 4 characters independently. We can compare the data from sender with the data from the packet capture.

UDYs1D7bNmdE1o3g5ms1V6RrYCVvODJF1DpxKTxAJ9xuZW==

Python to the help. We need a web server on localhost that the binary can POST to. With subprocess we can start the sender automagically with the current guess. After some rounds, the solution should be presented.

Some issues persist in my solution and still required some manual tweaks. Maybe someone wants to improve the code to handle this issue automagically. Yet it worked. Get it now on github

The script produces this wonderful output.

2015-08-04 14:28:13,154 Sp1cy_7_l       UDYs1D7bNmdE
2015-08-04 14:28:14,358 Sp1cy_7_l0      UDYs1D7bNmdEOW==
2015-08-04 14:28:15,546 Sp1cy_7_l1      UDYs1D7bNmdEPa==
2015-08-04 14:28:16,749 Sp1cy_7_l2      UDYs1D7bNmdEPq==
2015-08-04 14:28:17,951 Sp1cy_7_l3      UDYs1D7bNmdEPG==
2015-08-04 14:28:19,154 Sp1cy_7_l4      UDYs1D7bNmdEPW==
2015-08-04 14:28:20,390 Sp1cy_7_l5      UDYs1D7bNmdEQa==
2015-08-04 14:28:21,654 Sp1cy_7_l6      UDYs1D7bNmdEQq==
2015-08-04 14:28:22,890 Sp1cy_7_l7      UDYs1D7bNmdEQG==
2015-08-04 14:28:24,108 Sp1cy_7_l8      UDYs1D7bNmdEQW==
2015-08-04 14:28:25,326 Sp1cy_7_l9      UDYs1D7bNmdERa==
2015-08-04 14:28:26,499 Sp1cy_7_la      UDYs1D7bNmdE1a==
2015-08-04 14:28:27,671 Sp1cy_7_lb      UDYs1D7bNmdE1q==
2015-08-04 14:28:28,921 Sp1cy_7_lb0     UDYs1D7bNmdE1Aq=

Solution

Sp1cy_7_layer_OSI_dip@flare-on.com

Challenge 0x06

Walkthrough

The, first challenge with an android apk. Take a look at the source code after unpacking the app. The juicy stuff seems to happen in the libvalidate.so. So let’s dive in ARM assembly.

First, let’s install the apk on a native device. During testing an emulated device proofed to be quite unstable. In order to debug the library we have to attach gdbserver to the running app.

#Grep process id
ps | grep flare
#attach to running app
gdbserver :1234 --attach 4444

Next, we have to copy the libraries from the phone to the debugging machine. For this, we simply create a temporary directory and pull the necessary files via adb.

#prepare directory structure
mkdir /tmp/my_android
cd /tmp/my_android
mkdir system_lib
mkdir vendor_lib
#copy system libs
cd system_lib
~/Android/Sdk/platform-tools/adb pull /system/lib
~/Android/Sdk/platform-tools/adb pull /system/vendor/lib/egl
cd ..
#copy vendor libs
cd vendor_lib
~/Android/Sdk/platform-tools/adb pull /vendor/lib
cd ..
#copy rest
~/Android/Sdk/platform-tools/adb pull /system/bin/app_process
~/Android/Sdk/platform-tools/adb pull /system/bin/linker
~/Android/Sdk/platform-tools/adb pull /data/app/com.flare_on.flare-1/lib/arm/libvalidate.so

With the necessary files in place we can start up the arm debugger.

#start arm
./arm-linux-androideabi-gdb
#connect to remote gdbserver
target remote 192.168.1.2:1234
#set up logging
set logging overwrite off
set logging file /tmp/my_android/debug.txt
set logging redirect off
set logging on
#set location of libraries
set auto-solib-add on
set solib-search-path /tmp/my_android/system_lib/:/tmp/my_android/vendor_lib/:/tmp/my_android/
show solib-search-path
info proc mappings
info sharedlibrary
backtrace
#verify logging
show logging

The disassembly points out, that the relevant input comparison happens in line Java_com_flareon_flare_ValidateActivity_validate+188. The memcmp takes two pointers and a size. To understand what’s happening here let’s add breakpoints that print out the relevant information.

break Java_com_flareon_flare_ValidateActivity_validate+188
commands 1
set logging on
i r
x/1738xh $r0
x/1738xh $r1
c
end

With the above command gdb prints the registers and the relevant memory sections that are compared. 1738 is the size 0xD94 divided by 2. A little experimenting showed, that the data is stored in half bytes. The structure that is compared corresponds to a short array. Furthermore, the data structure referenced in R0 is static and probably resembles the solution.

The modular division and division in lines 142 and 164 are the basis for the data in the second memory location. This combination of the two mathematical functions is usually utilized in prime number factorization. A look at the library in a hex editor also provides a good clue towards this direction. In the lines from 0x00002210 to 00003d30 a unique pattern emerges. A short analysis points out, that these are basically prime numbers stored in an array of short integers.

So, we also know, that two bytes of input are worked on per iteration. The algorithm factorizes the prime numbers of this short. The result is stored in the R1 buffer.

In order to solve the issues we have to interpret the array and add the prime numbers back together for two bytes of the solution. The pointer to the arrays with the solution is stored in 0xbeb84484.

#Lookup Table
0xbeb84484:     0xa244b5d0      0xa2449aa8      0xa2447f80      0xa2446458
0xbeb84494:     0xa2444930      0xa2442e08      0xa24412e0      0xa243f7b8
0xbeb844a4:     0xa243dc90      0xa243c168      0xa243a640      0xa2438b18
0xbeb844b4:     0xa2436ff0      0xa24354c8      0xa24339a0      0xa2431e78
0xbeb844c4:     0xa2430350      0xa242e828      0xa242cd00      0xa242b1d8
0xbeb844d4:     0xa24296b0      0xa2427b88      0xa2426060      0x00000000

So we simply have to dump that memory with the afore mentioned command. Subsequently, we have to interpret the data and recalculate the input bytes. The last step can be achieved with a short python script.

def main():
    global myprimes
    #calculate prime numbers
    myprimes = list(primesfrom2to(0x7e7d))

    solution = ""
    path = "debug-run-4"
    for filename in sorted(listdir(path)):
        if "target_" in filename:
            #calculates the sum of the primes at the addresses
            # of the lookup table. The memory has been dumped
            # with gdb
            p = primesfromfile(join(path,filename))
            print(filename,p,hex(p))

            p1 = p & 0xff
            p2 = p >> 8
            print(chr(p2),chr(p1))
            solution += chr(p2)+chr(p1)

    print(solution)

main()

This gives the wonderful output.

('target_01', 21352, '0x5368L')
('S', 'h')
('target_02', 28533, '0x6f75L')
('o', 'u')
('target_03', 27748, '0x6c64L')
('l', 'd')
('target_04', 24424, '0x5f68L')
('_', 'h')
('target_05', 24950, '0x6176L')
('a', 'v')

Solution

Should_have_g0ne_to_tashi_$tation@flare-on.com

Challenge 7

Walkthrough

This time we have got an obfuscated .Net binary. To take a look at the source code let’s utilize de4dot. The resulting binary can be viewed with ILSpy.

Basically, the solution is encrypted with Rijndael. The input is the key. But also, the key is calculated by the binary. This is done in order to check that the user input is correct. If the input is wrong, the binary prints out some nasty message. Just step through the program to the switch statement that is responsible for failure or success. From there you can find the encryption key within memory: metaprogrammingisherd_DD9BE1704C690FB422F1509A46ABC988

Solution

Justr3adth3sourc3@flare-on.com

Challenge 8

Walkthrough

There is base64 encoded data in the binary. The image shows a calming image of some trees. So, the solution is probably steganographically hidden. Analysis points out, that the LSB of the RGB channels probably holds some secret data. All three channels are equal. The first 6600 pixels (600x11) have very likely been modified to contain a secret message. This proofed to be quite wrong. The solution can be found with the zsteg tool.

zsteg base64.png -E b1,rgb,msb,xy > steg2.bin

Solution

Im_in_ur_p1cs@flare-on.com

Challenge 9

Walkthrough

Obfuscated binary with anti debugging tricks: Non-conditional jump instructions throw off the debugger. This are basically partial commands. Furthermore, the binary relies heavily on return-oriented programming. It directly creates the required parameters on the stack.

We can identify the relevant commands and their respective values via pydbg.

0x401aee XOR
0x401b14 ROL
0x401b16 MOV
0x401B37 CMPXCHG

With pydbg we can attach a breakpoint at the relevant positions. The following function will log all relevant register values.

def myhandler1(dbg):
    #XOR
    if dbg.exception_address == 0x00401AEE:
        ah = dbg.get_register('AH')
        al = dbg.get_register('AL')
        print(hex(ah),hex(al))
    #ROL
    elif dbg.exception_address == 0x00401B14:
        al = dbg.get_register('AL')
        cl = dbg.get_register('CL')
        print(hex(al),hex(cl))
    #MOV
    elif dbg.exception_address == 0x00401B16:
        esp = dbg.get_register('ESP')
        ebx = dbg.get_register('EBX')
        ss = ESP+EBX+0x2C
        ebx2 = dbg.read_process_memory(ss,4)
        print(hex(ss),hex(ebx2))
    else:
        disasm    = dbg.disasm(dbg.exception_address)
        print "%08x: %s" % (dbg.exception_address, disasm)

We can grep the register values from the log file. Subsequently, let’s reverse engineer the encryption.

# Rotate left: 0b1001 --> 0b0011
rol = lambda val, r_bits, max_bits: \
    (val << r_bits%max_bits) & (2**max_bits-1) | \
    ((val & (2**max_bits-1)) >> (max_bits-(r_bits%max_bits)))

# Rotate right: 0b1001 --> 0b1100
ror = lambda val, r_bits, max_bits: \
    ((val & (2**max_bits-1)) >> r_bits%max_bits) | \
    (val << (max_bits-(r_bits%max_bits)) & (2**max_bits-1))

xor = [0x46L,0x15L,0xf4L,0xbdL,0xffL,0x4cL,0xefL,0x46L,0xebL,0xe6L,0xb2L,0xebL,0xf1L,0xc4L,0x34L,0x67L,0x39L,0xb5L,0x8eL,0xefL,0x40L,0x1bL,0x74L,0xdL,0x60L,0x26L,0x45L,0xa8L,0x4aL,0x96L,0xc9L,0x65L,0xe2L,0x32L,0x60L,0x64L,0x8cL,0x65L,0xe3L,0x8eL,0x9fL]
rol = [0x56L,0xf5L,0xacL,0x1bL,0xb5L,0x93L,0x7eL,0xb8L,0x23L,0xdaL,0xaL,0xf2L,0x1L,0x61L,0x5cL,0xc8L,0x4cL,0xd6L,0x16L,0x55L,0x67L,0xb8L,0xc1L,0xf8L,0xbcL,0x11L,0xfaL,0x9bL,0x6bL,0xf9L,0xd4L,0x75L,0x87L,0xcaL,0xceL,0xbeL,0x4eL,0x6eL,0xf1L,0xb9L,0x6eL]
cmpxchg = [0xc3f44e06,0xccb880f0,0xbac2ccb8,0x4e0642bc,0xf25d5630,0xeb165a6b,0x275dc3f4,0x19535366,0xc6c30e8e,0x42bcf25d,0x0642bcf2,0x165a6bc6,0x5d56302e,0x535366d2,0x55dc01e1,0x0e8e275d,0x66d2f903,0xf44e0642,0xf99a5055,0x302e1953,0x9a5055dc,0x77f99a50,0x56302e19,0x6bc6c30e,0xf04877f9,0x8e275dc3,0xdc01e1eb,0x2e195353,0x5055dc01,0xe1eb165a,0x5a6bc6c3,0x80f04877,0x4877f99a,0x5dc3f44e,0x5366d2f9,0xc2ccb880,0xb880f048,0xd2f903cf,0x01e1eb16,0xc30e8e27,0xbcf25d56]

out = ""
cnt = 0
while cnt < len(xor):
    z = cmpxchg[cnt] >> 24
    x = ror(z, rol[cnt],8)
    y = x ^ xor[cnt]

    print(hex(z),hex(rol[cnt]%8),hex(x),hex(xor[cnt]),hex(y),chr(y))
    out += chr(y)
    cnt += 1

print(out)

Solution

Is_th1s_3v3n_mai_finul_foarm@flare-on.com

Challenge 10

Walkthrough

This time we have got a super complex device driver. Finding the ioctls was quite simple in the disassembly.

22E068  jumptable 0029CD91 case 100
22E108  jumptable 0029CD91 case 260
22E164  jumptable 0029CD91 case 352

At the end of the complex function tree, is a simple TEA implementation. The input for the function is a buffer location. Each brach in the function sets one character in this buffer. Without WinDBG the best option is to parse all the relevant memory operations. This can be done statically with pydbg. With multiple potential values for each byte, the buffer still had to be decrypted. This I could not figure out in time.

Links