Double Free
ptmalloc2 할당자에서 fastbin dup에서는 이전에 해제된 청크인 old와 현재 해제될 청크인 p가 같은지 여부를 비교해서 동일한 힙 청크를 연속해서 해제할 경우 "double free or corruption (fasttop)"을 출력하고 비정상 종료한다.
if (__builtin_expect (old == p, 0)
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
★ 취약점 원인: Double Free 미검증, size 비교 미검증
#if USE_TCACHE
{
size_t tc_idx = csize2tidx (size);
if (tcache
&& tc_idx < mp_.tcache_bins
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);
return;
}
}
tcache에서는 사용하는 tcache_put과 tcache_get 함수에서는 old와 p를 검증하지 않는다.
따라서 한 개의 청크를 연속해서 해제할 수 있다.
공격 방법
Double Free 이용해서 tcache_entry를 조작해 이미 할당된 메모리에 다시 힙 청크를 할당하는 공격 기법이다.
예시1
// gcc -o tcache_dup1 tcache_dup1.c -no-pie
#include <stdio.h>
#include <stdlib.h>
int main()
{
char *ptr = malloc(0x20);
free(ptr);
free(ptr);
fprintf(stderr, "malloc: %p\n", malloc(0x20));
fprintf(stderr, "malloc: %p\n", malloc(0x20));
return 0;
}
0x20 크기의 힙을 할당하고 연속으로 두 번 해제 후 할당된 힙 청크의 주소를 출력하는 코드이다.
(1) 첫 번째 청크 한 번 해제 후
첫 번째 청크를 한 번 해제한 후 tcache의 tcache_entries에 사이즈에 따라 두 번째 인덱스에 해제된 힙 청크 포인터가 적혔고, tcache_entry에 해당하는 tcache_count도 증가한 것을 확인할 수 있다.
다음 해제된 청크는 없으므로 ptr의 next 포인터는 NULL이다.
(2) 첫 번째 청크 두 번째 해제 후(Double Free)
해제된 힙 청크의 next가 자신을 가리킨다.
따라서 이후 같은 bin 크기로 두 번 할당 시 같은 주소에 두 개의 힙을 할당할 수 있다.
처음엔 free chunk를, 두 번째는 free chunk의 next에 적힌 청크인데 자기 자신이므로 같은 주소의 청크를 할당해준다.
힙 영역에 데이터를 쓸 수 있으면 next 포인터를 덮어서 원하는 영역에 힙을 할당할 수 있다.
(3) 실행 결과
예시2
// gcc -o tcache_dup2 tcache_dup2.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
char name[16];
int overwrite_me;
int main()
{
int ch, idx;
int i = 0;
char *ptr[10];
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
printf("Name : ");
read(0, name, 16);
while (1) {
printf("> ");
scanf("%d", &ch);
switch(ch) {
case 1:
if( i >= 10 ) {
printf("Do not overflow\n");
exit(0);
}
ptr[i] = malloc(32);
printf("Data: ");
read(0, ptr[i], 32-1);
i++;
break;
case 2:
printf("idx: ");
scanf("%d", &idx);
free(ptr[idx]);
break;
case 3:
if( overwrite_me == 0xDEADBEEF ) {
system("/bin/sh");
}
break;
default:
break;
}
}
return 0;
}
tcache는 Double Free와 size 비교에도 검증이 없어서 전역변수에 fake chunk를 구성할 필요없다.
공격 시나리오
(1) 힙 할당
(2) Double Free 미검증 취약점 사용: 2번 메뉴-> 할당한 힙 청크 2번 해제
첫 번째 free 후
free chunk 주소에 대한 포인터가 tcache_entries에 적혔다.
그리고 free chunk의 next에는 다음 free chunk가 아직 없으므로 NULL로 초기화되어있다.
2번째 free 후(Doueble Free 이후)
Double Free 검증을 하지 않아 오류없이 Double Free가 되었고, 해제되었던 청크의 next에 자신의 청크 주소가 적혔다.
(3) 힙 재할당: 데이터에 overwrite_me 전역변수의 주소를 줘서 next 포인터가 overwrite_me 주소를 가리키도록 한다.
0x4195260 주소에서 힙을 할당해서 next 포인터를 overwrite_me 전역변수 주소값으로 덮는다.
이때, tcache_entries에는 현재 청크 주소에 대한 포인터가 적혀있었으므로 다음으로는 현재 청크가 할당된다.
(4) 힙 2번째 재할당: 현재 해제된 청크를 할당해준다.
재할당 시 tcache_entries에 적힌 0x1495260 주소의 힙 청크를 할당해주고, tcache_entries에 next 포인터에 적힌 overwrite_me 값이 적힌다.
그리고 size에 대한 검증이 없기 때문에 fake chunk를 구성하지 않아도 overwrite_me에 청크를 할당할 수 있다.
(5) 힙 3번째 재할당: next 포인터를 참조하여 overwrite_me 전역변수 부분에서 할당받는다. 이때 데이터로 0xDEADBEEF를 입력한다.
overwrite_me에서 청크를 할당받아서 0xdeadbeef 데이터를 입력할 수 있다.
(6) 3번 메뉴: 쉘 획득한다.
overwrite_me 주소 값이 0xdeadbeef이라서 쉘을 획득할 수 있다.
Tcache House of Spirit (0) | 2020.06.11 |
---|---|
fastbin dup (0) | 2020.06.10 |
Tcache (0) | 2020.06.10 |
Master canary (0) | 2020.06.08 |
Stack Frame & 함수 호출 규약 (32bit, 64bit) (0) | 2020.05.30 |