split
split 32bit
Solution
Examine the strings contained in the binary:
The idea is using ret2system to call system("/bin/cat flag.txt")
. For 32-bit binaries, the arguments for a function call is stored on the stack. Pictorially, the stack frame looks like the following:
In our case, we should set up the stack into the following state:
Note that we don't really know the address of exit()
. It is okay to use b"B" * 4
to replace exit()
for this challenge. However, this dummy padding would destory a process in real-world scenarios, so make sure you always use exit()
as the return address for system()
.
Exploit
split 64bit
Solution
For 64-bit binaries, the calling convention is completely different. Instead of storing arguments on the stack, 64-bit binaries store the first 6 arguments in registers, in the following order:
arg1 => RDI
arg2 => RSI
arg3 => RDX
arg4 => RCX (user space) or R10 (kernel space)
arg5 => R8
arg6 => R9
If there are more arguments, those extra arguments will be stored on the stack. However, it is rare to see function calls with more than 6 arguments.
As a result, now we need to store the address of "/bin/cat flag.txt"
in RDI before calling system()
. The trick is to use a pop rdi ; ret
gadget. This gadget can be easily found with ROPgadget if it exists in the binary.
Another tricky thing is stack alignment. Starting from Ubuntu 18.04 and onward, the stack is aligned in 16-byte boundaries. Without this alignment, we would call:
With this alignment, we should call:
The ret
gadget here is a padding that makes sure the stack is properly aligned. Keep this in mind, it will save you a lot of time from debugging.
Exploit
Last updated