CSAW 2015

We didn’t have enough time and enough people to do CSAW 2015 seriously, but we did have fun on some of the challenges. Below are the two most memorable ones for me.

Reversing 500 - Wyvern

The binary

As soon as you start reversing this binary, the first thing you notice are those weird basic blocks everywhere:

0 x4020f7 mov eax , dword [ ds : x3 ] 0 x4020fe mov ecx , dword [ ds : y4 ] 0 x402105 mov edx , eax 0 x402107 sub edx , 0x1 0 x40210d imul eax , edx 0 x402110 and eax , 0x1 0 x402115 cmp eax , 0x0 0 x40211a sete si l 0 x40211e cmp ecx , 0xa 0 x402124 setl di l 0 x402128 or si l , di l 0 x40212b test si l , 0x1 0 x40212f jne 0x40213a 0 x402135 jmp 0x404264

What are those? Opaque predicates. Indeed, the branch taken by jne depends on the variables x3 and y4 but those are generated at runtime and always have the same value. This means the branch is already determined but you have to actually run the program to determine it. Here these opaque predicates are used in two ways:

splitting basic block and adding false branches in between them

obfuscating the control flow of real branches

Here it’s not too bad: the opaque predicates are always the same inside a function and the basic block are not too much shuffled. You can still deduce the order of the basic blocks and guess what a function is doing. This seems to be the basic flow of the program:

read user input (the dragon secret) at 0x401ef5 in main()

call the start_quest() (0x404350) function with the dragon secret as a C++ string

inside start_quest() a global std::vector (called hero) is initialized, then sanitize_input() (0x401cc0) is called with a copy of the dragon secret string

inside sanitize_input() a whole lot of bullshit happens where the hero vector is manipulated using the dragon string and transform_input() (0x4014b0) is called with some other std::vector .

somehow the whole thing needs to return 0x1337

As you can see I still have a very rough idea of what the program is doing although I solved the challenge. But you only really need to spot two things.

The first one is the length check on the dragon secret string inside start_quest(). As you can see it’s compared with a static variable from the data segment but binary shifted right by two (probably some constants obfuscation) which evaluate to 28. Again it’s mixed with an opaque predicate but it’s pretty obvious: the input string should be 28 characters long.

(note: I annotated some stack variables, that’s not in the actual binary)

0 x404698 mov rdi , qword [ ss : rbp + dragon_secret ] ; our input 0 x40469c call j__ZNKSs6lengthEv ; the length method of std::string 0 x4046a1 sub rax , 0x1 ; remove the

from the character count 0 x4046a7 mov r9d , dword [ ds : legend ] ; the static variable 0 x4046af sar r9d , 0x2 ; probably just for obfuscation 0 x4046b3 movsxd rcx , r9d 0 x4046b6 cmp rax , rcx ; the comparison 0 x4046b9 setne r10b ; set r10b if not equal 0 x4046bd mov r9d , dword [ ds : x25 ] ; start of the opaque predicate 0 x4046c5 mov r11d , dword [ ds : y26 ] 0 x4046cd mov ebx , r9d 0 x4046d0 sub ebx , 0x1 0 x4046d6 imul r9d , ebx 0 x4046da and r9d , 0x1 0 x4046e1 cmp r9d , 0x0 0 x4046e8 sete r14b 0 x4046ec cmp r11d , 0xa 0 x4046f3 setl r15b 0 x4046f7 or r14b , r15b 0 x4046fa test r14b , 0x1 0 x4046fe mov byte [ ss : rbp + var_41 ], r10b 0 x404702 jne 0x40470d 0 x404708 jmp 0x404c13 0 x40470d mov al , byte [ ss : rbp + var_41 ] 0 x404710 test al , 0x1 ; here the result is finally used 0 x404712 jne 0x40471d 0 x404718 jmp 0x4047b8

The loop increment inside sanitize_input(). This variable is used to index the input string (the dragon secret) at 0x402188. You can easily guess that there should be a loop going through all the characters, it’s supposed to check your input after all.

0 x40368e mov rax , qword [ ss : rbp + input_idx ] ; pointer to the variable 0 x403692 mov ecx , dword [ ds : rax ] ; derefence it 0 x403694 add ecx , 0x1 ; increment it 0 x40369a mov dword [ ds : rax ], ecx ; update the variable 0 x40369c mov ecx , dword [ ds : x17 ] ; again opaque predicate 0 x4036a3 mov edx , dword [ ds : y18 ] 0 x4036aa mov esi , ecx 0 x4036ac sub esi , 0x1 0 x4036b2 imul ecx , esi 0 x4036b5 and ecx , 0x1 0 x4036bb cmp ecx , 0x0 0 x4036c1 sete di l 0 x4036c5 cmp edx , 0xa 0 x4036cb setl r8b 0 x4036cf or di l , r8b 0 x4036d2 test di l , 0x1 0 x4036d6 jne 0x4036e1 0 x4036dc jmp 0x4040e2

If you set a breakpoint at the start of sanitize_input() and at the loop increment you can validate two things: it will only execute sanitize_input() if the dragon secret is 28 characters long, it never steps on the loop increment. Why? Because it detects your dragon secret is invalid at the first character and breaks early. And that’s a flawed design because it means I can count the loop iterations to know how many characters I got right (note: you can also use Intel pin or another DBI framework to count the number of instructions executed).

The attack is the following: run the program on dragon secrets of 28 caracters but starting with a different first character each time, if the program breaks on the loop increment it means the character is correct and you can attack the next one. At the end you will have the complete dragon secret.

Of course this takes many tries and you have to automate. Here’s how to do it with GDB and its python API. This script implements a “counting breakpoint” using the GDB API. Passing it to the --command flag of GDB will return at the end the loop iteration count of sanitize_input().

# inherit from gdb Breakpoint class class CountingBreakpoint ( gdb . Breakpoint ): # variable to hold the hit counter count = 0 # override the stop method which is called when this breakpoint is # triggered def stop ( self ): self . count += 1 def getCount ( self ): return self . count # breakpoint to check if we did call sanitize_input() bk1 = CountingBreakpoint ( "* 0x401cc0" ) # breakpoint to count the loop iterations bk2 = CountingBreakpoint ( "* 0x40368e" ) # called when the program exit, display the breakpoint counters gdb . events . exited . connect ( lambda event : print ( "Breakpoint 1 count: {}

Breakpoint 2 count: {}" . format ( bk1 . getCount (), bk2 . getCount ()))) # called when the program stops on a breakpoint, force it to continue gdb . events . stop . connect ( lambda event : gdb . execute ( "continue" )) # start the program immediately when this script is loaded gdb . execute ( "run" )

Now this other script uses the first one to bruteforce the dragon secret.

from subprocess import check_output # the shell command to run one attempt cmd = 'echo "{}" | gdb --command gdb_wyvern_count.py wyvern_c85f1be480808a9da350faaa6104a19b' passwd = [ 32 for i in range ( 0 , 28 )] # bruteforce character per character for i in range ( 0 , 28 ): # visible ascii range for j in range ( 32 , 127 ): # mutate the password passwd [ i ] = j passwd_str = '' . join ( map ( lambda x : chr ( x ), passwd )) # some shell escape passwd_str = passwd_str . replace ( ' \\ ' , ' \\\\ ' ) . replace ( '`' , ' \\ `' ) . replace ( '"' , ' \\ "' ) # execute wyvern result = check_output ( cmd . format ( passwd_str ), shell = True ) # just to make sure we hit sanitize_input() if 'Breakpoint 1 count: 1' not in result : print ( 'Execution failed ...' ) # that's what we hope for :) if 'Breakpoint 2 count: {}' . format ( i + 1 ) in result : print ( '' . join ( map ( lambda x : chr ( x ), passwd ))) break

It will take some time to run but in the end you will get the flag ;)

% python2 wyvern_attack.py 2>/dev/null d dr dr4 dr4g dr4g0 dr4g0n dr4g0n_ dr4g0n_o dr4g0n_or dr4g0n_or_ dr4g0n_or_p dr4g0n_or_p4 dr4g0n_or_p4t dr4g0n_or_p4tr dr4g0n_or_p4tri dr4g0n_or_p4tric dr4g0n_or_p4tric1 dr4g0n_or_p4tric1a dr4g0n_or_p4tric1an dr4g0n_or_p4tric1an_ dr4g0n_or_p4tric1an_i dr4g0n_or_p4tric1an_it dr4g0n_or_p4tric1an_it5 dr4g0n_or_p4tric1an_it5_ dr4g0n_or_p4tric1an_it5_L dr4g0n_or_p4tric1an_it5_LL dr4g0n_or_p4tric1an_it5_LLV dr4g0n_or_p4tric1an_it5_LLVM % ./wyvern_c85f1be480808a9da350faaa6104a19b +-----------------------+ | Welcome Hero | +-----------------------+ [!] Quest: there is a dragon prowling the domain. brute strength and magic is our only hope. Test your skill. Enter the dragon's secret: dr4g0n_or_p4tric1an_it5_LLVM success [+] A great success! Here is a flag{dr4g0n_or_p4tric1an_it5_LLVM}

Crypto 500 - Bricks of Gold

The mysterious file

No stegano involved, pure crypto, I swear.

First of all we have that crucial description:

We’ve captured this encrypted file being smuggled into the country. All we know is that they rolled their own custom CBC mode algorithm - its probably terrible.

I’m not going to explain what CBC is, just look it up if you don’t know. You should also have very quickly discovered at the end of the file this line:

000bbc90: 7ba3 b08e 01dc dae0 49b0 a5ff 5448 455f {.......I...THE_ 000bbca0: 5345 4352 4554 5f49 563d 4341 5348 0a SECRET_IV=CASH.

So we have the IV, “CASH”, and thus also the block size: 32 bits. And that’s all we have. So how do we break an unkown crypto function?

Any good crypto should behave as a PRNG thus if we want to break it we should find something not random, a pattern, and understand why it exists. The best place to check in a file is always the beginning (because there’s usually a header there) and the end (that’s purely empirical).

And indeed at the start of the file there are those weird 32 bytes:

00000130: 166f 76c6 0462 6abc 6962 5d87 4c4e 45fd .ov..bj.ib].LNE. 00000140: 214e 72c6 0462 6bbc 6962 5c87 4c4e 4ffd !Nr..bk.ib\.LNO.

This should never happen in any serious crypto implementation.

If you ignore the first 2 bytes of each line, the only difference is in the 6th nibble (hex digit) of each block. And if you start looking at the binary encoding of those nibbles you will see there’s only one bit flipped in that nibble. What does this means?

Remember it’s CBC, each ciphertext block is used to xor the next block of plaintext. So if one bit is flipped in a ciphertext block, the same bit will be flipped in the plaintext block before it goes through the encryption function. And in a good cipher this should have a pseudo random effect on the output known as diffusion. Here the exact same bit is flipped at the output of the encryption function and this happens on 3 consecutive blocks.

This means there’s no diffusion. This means it’s a bit level substitution function. This means this is a xor cipher (the only binary operation which is bijective).

So we know it’s a CBC mode xor cipher (seriously, wtf) and we know the IV, now we need the key.

Again this is a file, and a file has a header which often contains a predictable magic number.

So what we can do is:

guess a file format

use the magic to compute the key on the first block

decrypt the whole file and attempt to open it

repeat those steps on the most common file format until it works

After a few tries, the pdf magic number worked. Here’s the python script:

import sys data = list ( open ( sys . argv [ 1 ], 'rb' ) . read ()) block_size = 4 iv = 'CASH' magic = ' \x25 PDF' key = [] # compute the key from the first block for i in range ( 0 , block_size ): key . append ( chr ( ord ( iv [ i ]) ^ ord ( data [ i ]) ^ ord ( magic [ i ]))) # decrypt it from end to start, easier to do since it's in place decryption # make sure idx is block_size aligned idx = len ( data ) - ( len ( data ) % block_size ) - block_size while idx >= block_size : for i in range ( 0 , block_size ): data [ idx + i ] = chr ( ord ( data [ idx + i ]) ^ ord ( data [ idx - block_size + i ]) ^ ord ( key [ i ])) idx -= block_size for i in range ( 0 , block_size ): data [ i ] = chr ( ord ( data [ i ]) ^ ord ( iv [ i ]) ^ ord ( key [ i ])) open ( sys . argv [ 2 ], 'wb' ) . write ( '' . join ( data ))

Look at that beauty!

Anyway, thanks a lot to ISIS lab for those cool challenges ;)