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