As stated in the previous discussion, an attacker
can acquire these primitives but in order for these primitives to be useful, an
attacker has to understand the implications and side-effects of using these
primitives. Some of the best examples for chaining these primitives to build an
exploit in the challenges directory of this repository are the
funge-and-games challenge from Cyberstakes 2017 and the feap challenge from
ASIS CTF Quals 2016.
In the funge-and-games challenge we use a relative read / write primitive on
the stack to leak __libc_start_main+231, a symbol that we can expect to always
be present in the stack if a target is compiled with glibc. It might be a
different offset of __libc_start_main across different versions of glibc,
but this is the subroutine called by _start of the ELF that then calls
main - a pretty important piece of the pie. Leaking this symbol from the stack
allows us to derive the base address of glibc in process memory, and from here
we derive the location of our one_gadget. We use our relative write primitive
to overwrite the return address of main’s stack frame with the address of
_start. This provides us the ability to restart the program from a clean
state, and from here we can provide another payload to the program. Finally, we
use the same buffer indexing vulnerability to overwrite the return address of
simulate to our one_gadget, and when the program exits the simulator we gain
an interactive shell.
For the feap challenge, I’ve already discussed how the attacker implements an
arbitrary read primitive in [1]. What’s important is that the
attacker utilizes the arbitrary read primitive to expose some sensitive and
important information for building the final exploit. The attacker exposes the
base address of the heap, using the previously mentioned arbitrary read
primitive to read a heap address contained within the .bss section of the ELF.
This is possible because the vulnerable ELF is not a position independent
executable (PIE), we can always expect the base of the .bss section to loaded
into the same location in virtual memory upon each invocation of the program.
Next, the attacker does the same to uncover puts@GOT (symbol contained within
the Global Offset Table (GOT)). This is important because the attacker needs to
derive the base address of glibc in virtual memory in order to calculate the
location of the system() symbol. The attacker reads puts@GOT because, as
stated earlier, the target ELF is not a PIE - the GOT will always be loaded into
the same location in virtual memory each time the program is invoked. Finally,
the attacker implements an arbitrary write primitive using
The House of Force to write the address of system@glibc to
free@GOT. The next time free@plt is called on a chunk in the heap, the call
will resolve to system and the memory of the chunk will be passed as an
argument to system. The attacker uses this to free() a chunk containing
"/bin/sh" in its user data, causing the program to execute
system("/bin/sh").