tl;dr
- Linux userspace exploitation by parsing ELF for symbol addresses with an arbitrary read
Challenge points: 491
No. of solves: 14
Solved by : sherl0ck, slashb4sh
Challenge Description
Writeup
The challenge program is rather simple. You get an infinite number of arbitrary reads and one arbitrary write. The libc is not given and the path of the loader is specified as ./loader.so
, hinting that a custom libc and loader is used. This is confirmed as no libc version matches are found on libc database search - https://libc.blukat.me/ with the leaks obtained from the server.
A custom libc and loader means that we cannot get a shell by calling system
. This is because the custom loader crashes when trying to load the libc of sh binary as the path to libc is specified as /lib/x86_64-linux-gnu/libc.so.6
. We have to call execve
with 2nd and 3rd argument as execve
.
With n number of arbitrary reads, we can actually parse symbol tables in the ELF structure of the libc to obtain the address of any function we want. This technique is explained with the sample code in this https://uaf.io/exploitation/misc/2016/04/02/Finding-Functions.html. Using the sample code with some modifications we can obtain the address of any symbol like system
or execve
. Note that this step has to be done only once, we obtain the offset from the base then hardcode it in the final exploit.
Finding Symbol address
Code for finding symbol address :
1 | from pwn import * |
The server times out after 1 minute. Within each step; finding the libc base, scanning symbol table, etc, the function breaks before finding the desired address, so the script is run again form where it broke, and initial values of iterative variables are hardcoded after each step to speed up the process and stay within the 1 minute time frame.
Exploitation
Now that we have the offset of execve
at 0xb7e80
, the only thing left is to get a shell. No mitigations are enabled on the binary except NX.
1 | gdb-peda$ checksec |
So the GOT table is an attack vector. But which function’s GOT entry should we overwrite with system
?. We don’t control the first argument of any of the imported libc functions used in the binary and nothing much can be done with an 8-byte write
. We have to find a way to extend the write primitive. One idea was to overwrite fflush
with the arbitrary write code path 0x400A31
, so every time fflush
is called we get an arbitrary write. This didn’t work as expected as scanf
and printf
crashed due to unaligned stack.
Exploit technique to get the shell
We have the addresses of the file structure’s of stdin
, stdout
and stderr
in the bss section. Leak the address of stdin
and overwrite stdin->_IO_buf_base
with an address above GOT table. So in the next call to scanf
we have large write to the GOT table.
We have to call execve("/bin/sh",0,0)
and not system(“/bin/sh”) as system tries
After scanf
, fflush(stdout)
is called so we overwrite the GOT entry of fflush
with init_proc+21
and also overwrite stdout
with pointer to “/bin/sh” in bss.
1 | gdb-peda$ pd init_proc |
Why call init_proc
? Within init_proc
, setvbuf
is called, which we overwrite with execve
. At flush(stdout)
first argument is a pointer to “/bin/sh” and the 3rd argument is a pointer to NULL, and jumping to init_proc+21
sets the 2nd argument as NULL.
execve
is successfully called with all arguments set.
Final exploit :
1 | from pwn import * |
1 | slashb4sh@ubuntu:~/HackTM/think-twice$ python exploit.py |
Flag
FLAG: HackTM{th3_my5t3r13s_0f_th3_ELF_h4v3_b33n_r3v34l3d}