Chaining primitives
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")
.