This is an ARM 32b exploitation challenge part of the BCTF competition, which I’ve enjoyed playing with the team TheGoonies. During the competition, only 18 teams (out of the 500+) were able to solve it. All props to them!
The technique I used to solve it is a bit twisted but it works fine and reliably. So hang on ☺
The vulnerable file is here.
Since the target is an ARM binary, I heavily relied on the tool I wrote, GDB-GEF to help me in the exploitation process.
ruin is an ARM ELF binary that allows you to store “securely” messages in
memory, acting like a safe.
main() function starts at 0x00008A88 and starts by allocating on the
malloc()) an 8-byte chunk, then jump
to a function at 0x89CC (which I’ve called
get_key_security) to authenticate
and unlock the safe.
strncmp() call trivially shows the expected initial key, in this case
security. Once the safe is unlocked, 4 different operations are possible:
- Update the key (function @0x000087D0, which I will call
update_key()in the rest of this write-up): this function will allocate a 16-byte
keychunk, and copy the content from stdin.
- Edit a secret stored (function @0x0000884C, which I will call
edit_secret()): if the
secretchunk is not allocated, then the function invokes
malloc(8). Then it performs an
fgets()to store 24 bytes from stdin (us!) into this buffer. We immediately spot a heap overflow here.
- Sign the secret with your name (function @0x000088B8,
sign_name()): if the
namechunk is not NULL (i.e. already allocated), then the function returns. Otherwise, it calls the
read_int()function at 0x0875C which prompts the user for the name size with
atoi(), checks it’s higher than 0x20 bytes, if so,
malloc(size)and reads its contents from stdin using
- Leave (function @0x00008978,
free()to de-allocate the 3 buffers allocated by the steps above, and then exit cleanly.
We have 2 vulnerabilities here:
- The heap overflow explained in the
- The integer overflow from the
sign_name()function, since the
atoi()call is not checked for negative integer before being compared. This allows us to control the size of the next
malloc()call (the one used to store the name).
The House of Force
With those 2 vulnerabilities, and the fact that we can control another chunk to
be allocated (through the
update_key() function), we have now a perfect
scenario for an “House of Force” heap exploitation. If you need a reminder, I
recommend you read this and
The idea behind this attack (which still works against recent libc heap allocator), is to be able to control the size of one chunk. By making the value of the size for this new chunk very big, it will allow us to overflow the address space, and make the chunk upper bound finish in an “interesting” writable location, for example, the Global Offset Table.
When we reach the “main” loop, the
secret chunk (8 bytes) is already
allocated. So we can use the
malloc(name_size) to create a chunk that will
overflow the address space and end in the GOT, which starts at 0x00010F48.
So what size do we need for the name chunk? We know that the key chunk can write 16 bytes, so 4 DWORD. And also, the target address must be aligned to 2 DWORD (8 bytes - because it is an ARM 32 bits).
Leaking heap memory
But we have a problem, we don’t know where the heap pages are located in the memory
layout. Lucky for us, the “authentication” function (
fread(), which unlike
fgets() does not append a NULL byte at the end of the string.
This allows to leak addresses some precious bytes from the heap, doing something like this:
From the heap memory leak, we know the address of the
secret chunk, which
means that the
name chunk headers will be located exactly 8 bytes after.
So we must set the length for the
name chunk dynamically by using the
The heap is now set dynamically with the correct offset. The next call to
malloc() will overwrite the GOT entry of
atoi@got with our data!
Which produces the following result in
Bingo! We control the execution flow! Good! But now where do we go?
The binary is dynamically linked, and does not contains any gadget that would
allow us to call directly
execve so we need a leak from the libc.
Using function indirection to leak memory using printf
I’m not sure if this is the best way to do, but I like this approach: the idea
is that, when you can overwrite the GOT, point an “interesting” function of the
control flow to
printf@plt. This way, if you can control the parameter of this
call, you can use a regular format string attack to read/write everywhere!!
read_int() (at 0x875c) offers a perfect exploitation case:
fgets at 0x878c allows us to provide 32 bytes in the stack, which will be
atoi as a parameter. So if we overwrite
atoi@got with the address
printf@plt, we have a good case for a format string attack.
So using the technique above, we can overwrite
atoi@got with the address of
printf in the PLT:
So now every time the control flow will hit the
atoi() function, the
stub will be executed, and we will receive the argument from the socket! So
every time the banner will prompt for a choice (1-4), the buffer we send will be
the argument to
Triggering the exploit
By leaking the memory, we find that an address to the libc can be found (at least) at the offset 21:
On the C library I tested, the
system() function was located at an offset of
0x37524 from the base. So now, we know the address of
And to complete the exploitation, all we must do is overwrite again
with the address of
system(), and when
fgets() will be triggered, simply
enter the command we want to execute, in this case
/bin/sh will do:
The exploit is complete, we can run it:
And as always, go here for the full exploit.
Peace out ✌
Share this post: