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!
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.
The user input is checked in an XOR encryption loop.
The loop starts at
0x0040104D and uses the encryption key
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
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])
This challenge should also be quite simple.
Attach OllyDbg and take a look at
In there, an encryption routine is called at
The first parameter for the function are 37 bytes at
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
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)
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
$ strings elfie | sed "s/exec/print/g" | python | grep -oP "in reversed\('moc.+'\)"
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
0x35 to fix the calculation.
Math should always be correct!
Next, the code checks for the number of arguments in line
With an integer as first argument the binary continues it’s magic.
After that the binary compares two strings the loop at line
Further analysis show, that one hash value is calculated in function
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
The time_struct is used to store pointers and lengths while copying the base64 encoded strings to the heap.
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.
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=
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
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
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
#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')
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:
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
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)
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.