
free๋ก ํด์ ํ ์ฒญํฌ๋ฅผ free๋ก ๋ค์ ํด์ ํ์ ๋ ๋ฐ์ํ๋ ํ์์ ์ฃผ๋ชฉ
- free : ์ฒญํฌ๋ฅผ ์ถ๊ฐํ๋ ํจ์, malloc : ์ฒญํฌ๋ฅผ ๊บผ๋ด๋ ํจ์
- ์์์ ์ฒญํฌ์ ๋ํด free๋ฅผ ๋ ๋ฒ์ด์ ์ ์ฉํ ์ ์๋ค โท ์ฒญํฌ๋ฅผ free list์ ์ฌ๋ฌ ๋ฒ ์ถ๊ฐํ ์ ์์
- ์ฒญํฌ๊ฐ free list์ ์ค๋ณตํด์ ์กด์ฌํ๋ฉด ์ฒญํฌ๊ฐ duplicated ๋์๋ค๊ณ ํ๋ค.
-> duplocated free list๋ฅผ ์ด์ฉํ๋ฉด ์์ ์ฃผ์์ ์ฒญํฌ๋ฅผ ํ ๋นํ ์ ์๋ค.
โถ ๊ฐ์ ์ฒญํฌ๋ฅผ ์ค๋ณตํด์ ํด์ ํ ์ ์๋ ์ฝ๋๋ ๋ณด์์์ ์ฝ์ ์ผ๋ก ๋ถ๋ฅ, Double Free Bug๋ผ๊ณ ๋ถ๋ฆ
1. Double Free Bug (DFB)
- ๊ฐ์ ์ฒญํฌ๋ฅผ ๋ ๋ฒ ํด์ ํ ์ ์๋ ๋ฒ๊ทธ
- ptmalloc2์์ free list์ ๊ฐ ์ฒญํฌ๋ค์ fd์ bk๋ก ์ฐ๊ฒฐ๋๋ค.
→ fd : ์์ ๋ณด๋ค ์ดํ์ ํด์ ๋ ์ฒญํฌ
→ bk : ์์ ๋ณด๋ค ์ด์ ์ ํด์ ๋ ์ฒญํฌ
* ํด์ ๋ ๋งํฌ์์ fd์ bk๊ฐ์ ์ ์ฅํ๋ ๊ณต๊ฐ์ ํ ๋น๋ ์ฒญํฌ์์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ๋ฐ ์ฌ์ฉ๋จ
>> ์ด๋ค ๋งํฌ๊ฐ free list์ ์ค๋ณตํด์ ํฌํจ๋๋ค๋ฉด, ์ฒซ ๋ฒ์งธ ์ฌํ ๋น์์ fd์ bk์ ๊ฐ์ ์กฐ์ํด์ free list์ ์์ ์ฃผ์๋ฅผ ํฌํจ์ํฌ ์
์๋ค.
์ต๊ทผ์๋ ๋ณดํธ ๊ธฐ๋ฒ์ด glibc์ ๊ตฌํ๋๋ฉด์, ์ด๋ฅผ ์ฐํํ์ง ์์ผ๋ฉด ๊ฐ์ ์ฒญํฌ๋ฅผ ๋ ๋ฒ ํด์ ํ๋ ์ฆ์ ํ๋ก์ธ์ค๊ฐ ์ข ๋ฃ๋จ.
Tcache Double Free
๋ค์์ ๊ฐ์ ์ฒญํฌ๋ฅผ ๋ ๋ฒ ํด์ ํ๋ ์์ ์ฝ๋์ด๋ค.
// Name: dfb.c
// Compile: gcc -o dfb dfb.c
#include <stdio.h>
#include <stdlib.h>
int main() {
char *chunk;
chunk = malloc(0x50);
printf("Address of chunk: %p\n", chunk);
free(chunk);
free(chunk); // Free again
}
์ปดํ์ผ ์คํํ๋ฉด tcache์ ๋ํ Double Free๊ฐ ๊ฐ์ง๋์ด ํ๋ก๊ทธ๋จ์ด ๋น์ ์ ์ข ๋ฃ๋๋ ๊ฑธ ํ์ธํ ์ ์๋ค.
2. Mitigation for Tcache DFB
์ ์ ํจ์น ๋ถ์
tcache_entry
tcache์ ๋์ ๋ ๋ณดํธ ๊ธฐ๋ฒ์ ๋ถ์ํ๊ธฐ ์ํด, ํจ์น๋ ์ฝ๋์ diff๋ฅผ ์ดํด๋ณด๊ณ ์ ํ๋ค.
#tcahe_entry ๊ตฌ์กฐ์ฒด์ Diff
typedef struct tcache_entry {
struct tcache_entry *next;
+ /* This field exists to detect double frees. */
+ struct tcache_perthread_struct *key;
} tcache_entry;
์ ์ฝ๋๋ฅผ ๋ณด๋ฉด double free๋ฅผ ํ์งํ๊ธฐ ์ํด key ํฌ์ธํฐ๊ฐ tcahe_entry์ ์ถ๊ฐ๋์์์ ์ ์ ์๋ค.
(tcache_entry๋ ํด์ ๋ tcache ์ฒญํฌ๋ค์ด ๊ฐ๋ ๊ตฌ์กฐ)
์ผ๋ฐ ์ฒญํฌ์ fd๊ฐ next๋ก ๋์ฒด๋๊ณ , LIFO๋ก ์ฌ์ฉ๋๋ฏ๋ก bk์ ๋์๋๋ ๊ฐ์ ์๋ค.
* tcache_perthread_struct : tcache๋ฅผ ์ฒ์ ์ฌ์ฉํ๋ฉด ํ ๋น๋๋ ๊ตฌ์กฐ์ฒด
tcache_put
tcache_put์ ํด์ ํ ์ฒญํฌ๋ฅผ tcache์ ์ถ๊ฐํ๋ ํจ์์ด๋ค.
tcache_put(mchunkptr chunk, size_t tc_idx) {
tcache_entry *e = (tcache_entry *)chunk2mem(chunk);
assert(tc_idx < TCACHE_MAX_BINS);
+ /* Mark this chunk as "in the tcache" so the test in _int_free will detect a
double free. */
+ e->key = tcache;
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}
์ ์ฝ๋๋ฅผ ๋ณด๋ฉด tcache_putํจ์๋ ํด์ ๋๋ ์ฒญํฌ์ key์ tcache๋ผ๋ ๊ฐ์ ๋์ ํ๋๋ก ๋ณ๊ฒฝ๋์๋ค.
tcache๋ tcache_perthread๋ผ๋ ๊ตฌ์กฐ์ฒด ๋ณ์๋ฅผ ๊ฐ๋ฆฌํจ๋ค.
tcache_get
- tcache์ ์ฐ๊ฒฐ๋ ์ฒญํฌ๋ฅผ ์ฌ์ฌ์ฉํ ๋ ์ฌ์ฉํ๋ ํจ์
tcache_get (size_t tc_idx)
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
+ e->key = NULL;
return (void *) e;
}
์์ ์ฝ๋๋ฅผ ๋ณด๋ฉด tcache_get ํจ์๋ ์ฌ์ฌ์ฉํ๋ ์ฒญํฌ์ key ๊ฐ์ NULL์ ๋์ ํ๋๋ก ๋ณ๊ฒฝ๋์๋ค.
_int_free
- ์ฒญํฌ๋ฅผ ํด์ ํ ๋ ํธ์ถ๋๋ ํจ์
_int_free (mstate av, mchunkptr p, int have_lock)
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
-
- if (tcache
- && tc_idx < mp_.tcache_bins
- && tcache->counts[tc_idx] < mp_.tcache_count)
+ if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
- tcache_put (p, tc_idx);
- return;
+ /* Check to see if it's already in the tcache. */
+ tcache_entry *e = (tcache_entry *) chunk2mem (p);
+
+ /* This test succeeds on double free. However, we don't 100%
+ trust it (it also matches random payload data at a 1 in
+ 2^<size_t> chance), so verify it's not an unlikely
+ coincidence before aborting. */
+ if (__glibc_unlikely (e->key == tcache))
+ {
+ tcache_entry *tmp;
+ LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
+ for (tmp = tcache->entries[tc_idx];
+ tmp;
+ tmp = tmp->next)
+ if (tmp == e)
+ malloc_printerr ("free(): double free detected in tcache 2");
+ /* If we get here, it was a coincidence. We've wasted a
+ few cycles, but don't abort. */
+ }
+
+ if (tcache->counts[tc_idx] < mp_.tcache_count)
+ {
+ tcache_put (p, tc_idx);
+ return;
+ }
}
}
#endif
20๋ฒ์งธ ์ค ์ดํ๋ฅผ ๋ณด๋ฉด,
์ฌํ ๋นํ๋ ค๋ ์ฒญํฌ์ key๊ฐ์ด tcache์ด๋ฉด Double Free๊ฐ ๋ฐ์ํ๋ค๊ณ ๋ณด๊ณ ํ๋ก๊ทธ๋จ์ ์ค๋จ์ํจ๋ค.
20๋ฒ์งธ ์ค์ ์กฐ๊ฑด๋ฌธ์ ํต๊ณผํ๋ฉด Double Free๋ฅผ ์ผ์ผํฌ ์ ์๋ค.
๋์ ๋ถ์
gdb๋ฅผ ์ด์ฉํ์ฌ ๋ณดํธ ๊ธฐ๋ฒ์ ์ ์ฉ ๊ณผ์ ์ ๋์ ๋ถ์ ํด๋ณผ ๊ฒ์ด๋ค.
์
์ฒญํฌ ํ ๋น ์ง์ ์ ํด๋นํ๋ main+13์ ์งํ์ธ main+18 ๋ถ๋ถ์ ์ค๋จ์ ์ ์ค์ ํ๊ณ ์คํํ๋ค.
heap ๋ช ๋ น์ด๋ก ์ฒญํฌ๋ค์ ์ ๋ณด๋ฅผ ์กฐํํ๋ฉด ๋ค์๊ณผ ๊ฐ๋ค.
์ด ์ค์์ malloc(0x50)์ผ๋ก ์ค์ ํ chunk์ ์ฃผ์๋ 0x555555756250 (ํฌ๊ธฐ๊ฐ ๊ฐ์ฅ ๋น์ทํ ๋ถ๋ถ)
ํด๋น ๋ฉ๋ชจ๋ฆฌ ๊ฐ์ ๋คํํด๋ณผ ๊ฒ์ด๋ค.
์์ง ์๋ฌด๋ฐ ๋ฐ์ดํฐ๊ฐ ์ ๋ ฅ๋์ง ์์์์ ํ์ธํ ์ ์๋ค.
์ดํ์ ์ฐธ์กฐ๋ฅผ ์ํด gdb ๋ณ์๋ก ์ ์ํ๋ค.
3. Tcache Duplication
// Name: tcache_dup.c
// Compile: gcc -o tcache_dup tcache_dup.c
#include <stdio.h>
#include <stdlib.h>
int main() {
void *chunk = malloc(0x20);
printf("Chunk to be double-freed: %p\n", chunk);
free(chunk);
*(char *)(chunk + 8) = 0xff; // manipulate chunk->key
free(chunk); // free chunk in twice
printf("First allocation: %p\n", malloc(0x20));
printf("Second allocation: %p\n", malloc(0x20));
return 0;
}
์์ ์ฝ๋๋ tcache์ ์ ์ฉ๋ double free ๋ณดํธ ๊ธฐ๋ฒ์ ์ฐํํ์ฌ Double Free Bug์ ํธ๋ฆฌ๊ฑฐํ๋ ์ฝ๋์ด๋ค.
์คํํด๋ณด๋ chunk๊ฐ tcache์ ์ค๋ณต ์ฐ๊ฒฐ๋์ด ์ฐ์์ผ๋ก ์ฌํ ๋น๋๋ ๊ฑธ ํ์ธํ ์ ์๋ค.
-> double free bug๋ก ๋ฐ์์ํฌ ์ ์์ผ๋ฉฐ, tcache poisoning์ผ๋ก ์์ฉ๋ ์ ์๋ค.
*tcache poisoning
: tcache chunk์ next๋ฅผ ์ํ๋ ์ฃผ์๋ก ๋ฐ๊ฟ ๊ณต๊ฒฉํ๋ ๊ธฐ๋ฒ
Quiz: Double Free Bug
๋ต) C
์ด๋ค ๋งํฌ๊ฐ free list์ ์ค๋ณต๋ผ์ ํฌํจ๋๋ ๊ฒฝ์ฐ double free๋ฅผ ์์ฌํด๋ณผ ์ ์์ ๊ฒ์ด๋ค.
๋ฐ๋ผ์ C๊ฐ ๊ฐ์ฅ ์์ฌ๋๋ค.
'DreamHack > SystemHacking' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[System Hacking] 2์ฃผ์ฐจ dreamhack stage 12 - (3) (0) | 2022.04.30 |
---|---|
[System Hacking] 2์ฃผ์ฐจ dreamhack stage 12 - (2) (0) | 2022.04.30 |
[System Hacking] 2์ฃผ์ฐจ dreamhack stage 12 (0) | 2022.04.07 |
[System Hacking] 1์ฃผ์ฐจ dreamhack stage 11 - (2) (0) | 2022.04.07 |
[System Hacking] 1์ฃผ์ฐจ dreamhack stage 11 - (1) (0) | 2022.04.07 |