TWCTF 2016 - reverse_box writeup


• Posted by hugsy on September 6, 2016
• twctf-2016 • reverse • binary-ninja • gef • unicorn-engine

The reverse_box challenge of TWCTF 2016 was a warmup challenge (only 50 points), not really hard. There are plenty of writeups for it, but none of them used the technique I used to solve it in only a few minutes. So I figured I could throw in my 50c and write this post.

Info

The vulnerable file is here (sha1: 1e11da1636e4a6b71683de5c23634b98827d3b3d), given with the description:

$ ./reverse_box ${FLAG}
95eeaf95ef94234999582f722f492f72b19a7aaf72e6e776b57aee722fe77ab5ad9aaeb156729676ae7a236d99b1df4a

So we had to reverse the hash to determine the value of ${FLAG} of this x32 binary.

$ file ./reverse_box
./reverse_box: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=5403acba0427c34695b1ebda8f0c678905b33456, stripped

Static analysis

Using Binary-Ninja disassembler, we can see that the main function is not doing much, but something like

binja-1

fill_buffer(buffer[0x100]);
for (i=0; i<strlen(argv[1]); i++)
    printf("%02x", buffer[ argv[1][i] ]);
putchar('\n');

So the interesting part is in fill_buffer(). It starts by selecting a random int stored in buffer[0]. Then the rest of the function performs some permutations and rotations on this buffer. This means that there is a finite number (exactly 256) of possible configurations for the buffer used to generate the hash.

binja-2

Since I was feeling lazy and didn’t want to reverse the whole thing, I decided to use my tool gef and its Unicorn-Engine command to bruteforce the initial random integer. We can do so because we know that the flag has to start with TWCTF{.

Dynamic analysis

It is possible to use gef to generate a fully working environment for Unicorn-Engine to emulate anything. Here, all we need is to emulate from 0x80485b1 (where the random integer is generated) until 0x80486e0 (end of the function fill_buffer()).

gef➤  unicorn-emulate -f 0x80485b1 -t 0x80486e0 -e /tmp/revbox.py
[*] Cannot copy page=0xf7fd4000-0xf7fd7000 : Cannot access memory at address 0xf7fd4000
[+] Unicorn script generated as '/tmp/revbox.py'

You can then easily update the script to make it bruteforce the correct value for eax (i.e. the random integer), and let unicorn transform the buffer (located in the stack at 0xffffd26c - which we know thanks to GDB).

def emulate(eax):
    emu = unicorn.Uc(unicorn.UC_ARCH_X86, unicorn.UC_MODE_32 + unicorn.UC_MODE_LITTLE_ENDIAN)
    emu.reg_write(unicorn.x86_const.UC_X86_REG_EAX, eax)
    emu.reg_write(unicorn.x86_const.UC_X86_REG_EBX, 0x0)
    [...]
    emu.emu_start(0x80485b1, 0x80486e0)
[...]

def find_init_randint():
    for i in range(256):
        emu = emulate(i)
        mem = emu.mem_read(0xffffd26c, 0x100)
        if mem[ord('T')]==0x95 and mem[ord('W')]==0xee:
            return i
    return None

Then run the script:

$ py /tmp/revbox.py
>>> init randint is d6

It worked! Now we know the correct initial random integer, we can re-use the emulate() with this value, and read the flag:

emu = emulate(init)
c = emu.mem_read(0xffffd26c, 0x100)
b = ""
for i in range(len(t)):
    j = c.find(t[i])
    b+= chr(j)
print "The flag is", b

Wrap it all and:

$ py /tmp/revbox.py
>>> init randint is d6
The flag is TWCTF{5UBS717U710N_C1PH3R_W17H_R4ND0M123D_5-B0X}

The entire unicorn script generated by gef can be found here.

This was quite fun to solve this challenge with this method, since it only took a few minutes to adapt the script generated by gef for Unicorn, and absolutely no knowledge of what the function fill_buffer() was actually doing in terms of permutations and stuff to get the flag. It made me realize again that Unicorn-Engine is an awesome emulation framework.

Hope you enjoyed!


Share this post: