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๋ฅผ ์ด์šฉํ•˜์—ฌ ๋ณดํ˜ธ ๊ธฐ๋ฒ•์˜ ์ ์šฉ ๊ณผ์ •์„ ๋™์  ๋ถ„์„ ํ•ด๋ณผ ๊ฒƒ์ด๋‹ค.

dreamhack ํŽ˜์ด์ง€์— ๋‚˜์™€์žˆ๋Š” double_free๊ฐ€ dfb ์ฝ”๋“œ์ธ ๊ฒƒ ๊ฐ™์•„์„œ ์ด๊ฑธ๋กœ ์ง„ํ–‰ํ–ˆ๋‹ค.

์Ÿ‡

disassemble main

์ฒญํฌ ํ• ๋‹น ์ง€์ ์— ํ•ด๋‹นํ•˜๋Š” main+13์˜ ์งํ›„์ธ main+18 ๋ถ€๋ถ„์— ์ค‘๋‹จ์ ์„ ์„ค์ •ํ•˜๊ณ  ์‹คํ–‰ํ•œ๋‹ค.

 

 

 

heap ๋ช…๋ น์–ด๋กœ ์ฒญํฌ๋“ค์˜ ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

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๊ฐ€ ๊ฐ€์žฅ ์˜์‹ฌ๋œ๋‹ค.

๋ณต์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค!