TWCTF 2016 - reverse_box writeup

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 (sha1: 1e11da1636e4a6b71683de5c23634b98827d3b3d) was 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 brute-force 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 brute-force 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!