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

References

  1. Arbitrary read primitives
  2. https://ctftime.org/task/2370
  3. The House of Force