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
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.
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!