본문 바로가기

Dreamhack/Lecture & Practice

[Lecture] Use After Free

1. Use-After-Free

  • 메모리 참조에 사용한 포인터를 메모리 해제 후, 제대로 초기화하지 않아 발생하는 취약점이다.
    해제한 메모리를 초기화하지 않고 다음 청크에 재할당해주기 때문이다.
  • 즉, 해제된 메모리에 접근할 수 있을 때, 발생하는 취약점이다.

     Chunk(청크) 란?

     • malloc에 메모리 할당을 요청하면, 넓은 메모리의 영역을 다양한 크기의 덩어리(chunk)로 나눈다.
     • chunk는 사용중인 덩어리, 해제된 덩어리, Top 덩어리, Last Remainder 덩어리가 있다.
     • 위에서 말하고 있는 청크는 "해제된 덩어리"로 응용프로그램에서 시스템에 반환한 덩어리를 의미하는 것이다.


2. Dangling Pointer

2-1. 정의

  • 컴퓨터 과학에서 'Dangling Pointer'는 유효하지 않은 메모리 영역을 가리키는 포인터이다.
  • malloc 함수를 통해,메모리를 할당하고, 반환되는 할당된 메모리 주소를 포인터에 저장한다.
    이를 통해, 해당 포인터를 참조하여 할당된 메모리에 접근하는 것이다.
  • free 함수를 통해 할당한 메모리를 해제하는데, free함수는 청크를 ptmalloc에 반환하기만 하고 청크 주소를 담고 있던 포인터를 초기화하지 않는다.
  • free호출 이후에 프로그래머가 포인터를 초기화하지 않을 경우, 포인터는 해제된 청크를 가리키는 'Dangling Pointer'가 된다.

2-2. 예제

(1) 코드

 

// Name: dangling_ptr.c
// Compile: gcc -o dangling_ptr dangling_ptr.c

#include <stdio.h>
#include <stdlib.h>

int main() {
  char *ptr = NULL;
  int idx;
  
  while (1) {
    printf("> ");
    scanf("%d", &idx);
    
    switch (idx) {
      case 1:
        if (ptr) {
          printf("Already allocated\n");
          break;
        }
        ptr = malloc(256);
        break;
        
      case 2:
        if (!ptr) {
          printf("Empty\n");
        }
        free(ptr);
        break;
        
      default:
        break;
    }
  }
}

 

(2) 코드 분석

  • ptr이라는 포인터에 256byte만큼 동적할당을 한다.
  • free로 청크를 해제한 후, 청크를 가르키던 ptr변수를 초기화하지 않는다.

  • ptr을 초기화하지 않음으로써, 해제한 주소를 가르키는 Dangling Pointer가 된다.
  • ptr이 해제된 청크 주소를 가르키므로, 이를 다시 해제할 수 있다. 이러한 취약점을 'Double Free Bug'라고 한다.

3. Use After Free 실습

  • UAF취약점은 Dangling Pointer로 인해 발생하기도 하지만, 새롭게 할당한 영역을 초기화하지 않고 사용하면서 발생하기도 한다.
  • malloc과 free함수는 할당 또는 해제할 메모리의 데이터들을 초기화해주지 않는다.
    따라서, 새롭게 할당한 청크를 명시적으로 초기화하지 않으면, 남아있던 데이터가 유출되거나 사용될 수 있다.

3-1. 예제 코드

// Name: uaf.c
// Compile: gcc -o uaf uaf.c -no-pie
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct NameTag {
  char team_name[16];
  char name[32];
  void (*func)();
};

struct Secret {
  char secret_name[16];
  char secret_info[32];
  long code;
};

int main() {
  int idx;

  struct NameTag *nametag;
  struct Secret *secret;

  secret = malloc(sizeof(struct Secret));

  strcpy(secret->secret_name, "ADMIN PASSWORD");
  strcpy(secret->secret_info, "P@ssw0rd!@#");
  secret->code = 0x1337;

  free(secret);
  secret = NULL;
  
  nametag = malloc(sizeof(struct NameTag));

  strcpy(nametag->team_name, "security team");
  memcpy(nametag->name, "S", 1);

  printf("Team Name: %s\n", nametag->team_name);
  printf("Name: %s\n", nametag->name);

  if (nametag->func) {
    printf("Nametag function: %p\n", nametag->func);
    nametag->func();
  }
}

3-2. 코드 분석

  • 구조체로 NameTag, Secret이 정의되어 있다.
  • Secret 구조체를 할당한 후, secret_name, secret_info, code에 값을 입력하고 해제한다.
  • NameTag 구조체를 할당한 후, team_name, name에 각각 값을 입력하고 해당 데이터를 출력한다.
  • 함수 포인터 func가 NULL이 아니면, 포인터가 가리키는 주소를 출력하고, 해당 주소의 함수를 호출한다.

프로그램 실행결과

  • 실행결과를 살펴보면, Name으로 secret_info의 문자열이 출력되고, Name과 Nametag에는 입력하지 않은 값이 출력되는 것을 확인할 수 있다.

3-3. 실습과정 (uaf 동적 분석)

  • ptmalloc2는 새로운 할당 요청이 들어왔을 때, 요청된 크기와 비슷한 청크가 bin이나 tcache에 있는지 확인하고, 있다면 해당 청크를 꺼내 재사용한다.

     tcache & bin 이란?

     • tcache는 해제된 청크를 크기별로 7개씩 저장해둔 공간으로, bin보다 더 빠르게 비슷한 크기의 청크를 찾아 재할당한다.
     • tcache가 차면 bin이라는 공간에 해제된 청크가 저장되며, 이는 자료구조 형식으로된 알고리즘으로 복잡하게 되어 있어
         속도는 느리다.

(1) 예제 코드에서 NameTag와 Secret은 같은 크기의 구조이다.

  • Secret을 해제하고 NameTag를 할당하면, ptmalloc2를 통해 nametag는 secret과 같은 메모리 영역을 사용하게 된다.
  • free함수는 해제한 메모리의 데이터는 초기화지 않기 때문에, secret의 값이 일부 남아있기 때문에 문제가 발생한다.

 

(2) gdb를 통해, nametag에 secret과 같은 메모리 영역이 할당되는 것을 확인할 수 있다.

 

① free직후에 break point를 설정 후, 실행한다.

  • secret 해제 직후, secret이 사용하던 메모리 영역을 출력하면 확인할 수 있다.

 

 

② heap명령어를 통해, 해제된 청크들의 정보를 조회한다.

  • heap명령어는 pwndbg에서 지원하는 것으로, 할당 및 해제된 청크들의 정보를 조회하는 명령어이다.

 

③ 해제된 메모리 영역을 출력해보면, fd와 bk값은 초기화 됐지만, secret_info의 값은 그대로 남아있는 것을 볼 수 있다.

 

 


     ※ 참고
   
     • fd  (Forward pointer)   : 다음 Free Chunk의 포인터를 가진다.
     • bk (Backward pointer) : 이전 Free Chunk의 포인터를 가진다.
     • malloc()은 각 chunk를 관리하기 위해, malloc_chunk구조체를 선언한다.
     • malloc_chunk의 구조체를 보면, size, size, fd, bk ,,,등의 순서대로 작성되어 있으며, fd와 bk는 free된 메모리에만 존재한다.


 

④ name tag를 할당하고, printf 함수를 호출하는 부분에 break포인트를 설정하고, 실행한다.

 

  • 'security team'이 0x602260에 저장되어 있는 것을 보아, 해제된 메모리가 재할당된 것을 확인할 수 있다.

 

⑤ nametag의 메모리 영역을 출력한다.

  • nametag -> team_name에는 "security team"이 그대로 입력되었으나, namgetag -> name에는 초기화되지 않은 secret_info의 값이 존재하는 것을 확인할 수 있다.
  • nametag->func 위치에도, secret->code에 대입했던 0x1337이 남아있는 것을 확인할 수 있다.

3-4. 결론

  • 동적할당한 청크를 해제한 뒤에도 해제된 메모리 영역에 이전 객체의 데이터가 남는다.
  • 이러한 문제점을 통해, 초기화되지 않은 메모리의 값을 읽어내거나, 새로운 객체가 악의적인 값을 사용하게 유도하여 프로그램의 정상적인 실행을 발해할 수 있다.

'Dreamhack > Lecture & Practice' 카테고리의 다른 글

[Practice] Use After Free  (0) 2022.09.16
[Practice] 64bit_FSB (Format String Bug)  (0) 2022.08.20
[Lecture] Format String Bug (FSB)  (0) 2022.08.10
[Lecture] OOB (Out of bounds)  (0) 2022.08.09
[Practice] Hook Overwrite  (0) 2022.07.14