본문 바로가기
Security/System Hacking

[Dreamhack System Hacking] STAGE 11

by 단월໒꒱ 2022. 4. 1.

Memory Corruption : Use After Free

1. 서론

 - Use After Free란?

   - 메모리 참조에 사용한 포인터를 메모리 해제 후에 적절히 초기화하지 않아서 발생하는 취약점

   - 해제한 메모리를 초기화하지 않고 다음 청크에 재할당해주면서 발생하는 취약점

   - ptmalloc2 함수를 이용하여 메모리 관리할 때 주의 필요

 

2. Use After Free

 1) Dangling Pointer

   - 유효하지 않은 메모리 영역을 가리키는 포인터

   - 해제된 메모리를 가리키고 있는 포인터

   - UAF가 발생하는 원인이 될 수 있음

   - 일반적으로 malloc 함수로 메모리를 할당하고 free 함수로 메모리를 해제하는데, free 함수는 청크를 ptmalloc에 반환하기만 할 뿐, 청크의 주소를 담고 있던 포인터를 초기화하지 않음

       -> 따라서 free 호출 이후 포인터를 초기화하지 않으면, 포인터는 해제된 청크를 가리키는 Dangling Pointer가 됨

   - Dangling Pointer에 의해 프로그램이 예상치 못한 동작을 할 가능성이 커지고, 공격자에 의해 공격 수단이 될 수 있음

 

cf. chunk(청크)

   - header와 data 영역으로 구성됨

   - malloc 함수로 할당 받은 영역과 header를 포함한 영역을 뜻함

   - 32bit에서는 8byte의 배수, 64bit에서는 16byte의 배수로 할당됨

 

- 예제

   - 청크를 해제한 후에 청크를 가리키던 ptr 변수를 초기화하지 않아 Dangling Pointer의 위험성을 보이는 예제

 

 

// 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;
    }
  }
}

 

 

코드를 보면 1을 입력하면 청크가 할당되고 2를 입력하면 청크를 해제하는데

 

 

 

 

위처럼 청크를 할당하고 해제하면 ptr은 이전에 할당한 청크의 주소를 가리키는 Dangling Pointer가 된다.

ptr이 해제된 청크의 주소를 가리키고 있으므로 이를 다시 해제할 수 있는데, 이를 Double Free Bug 취약점이라고 한다.

 

 2) Use After Free

   - 해제된 메모리에 접근할 수 있을 때 발생하는 취약점

   - Dangling Pointer로 인해 발생 or 새롭게 할당한 영역을 초기화하지 않고 사용하여 발생

   - malloc, free 함수는 할당 또는 해제할 메모리의 데이터들을 초기화하지 않음

       -> 새롭게 할당한 청크를 초기화하지 않으면 메모리에 남아있던 데이터가 유출되거나 사용될 수 있음

 

  - 예제

   - UAF 취약점이 있는 예제

 

 

// 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();
  }
}

 

 

- 구조체 NameTag와 Secret이 정의되어 있는데 예제에서는 그 중 외부에 유출되면 안되는 Secret 구조체를 먼저 할당함

  그리고 secret_name, secret_info, code에 값을 입력하고 이를 해제함

- 사원의 정보를 담고 있는 nametage를 생성함

   team_name, name에 각각의 값을 입력하고, 입력한 데이터를 출력

   이후에 함수 포인터 func가 NULL이 아니라면 포인터가 가리키는 주소를 출력하고, 해당 주소의 함수를 호출함

 

 

 

 

- 출력결과를 보면 Name으로 secret_info의 문자열이 출력되고, 값을 입력한적 없는 함수 포인터가 0x1337을 가리키고 있는 것을 확인할 수 있음

 

 

 3) uaf 동적 분석 

   - 위와 같은 예제 코드 사용

   - ptmalloc2 : 새로운 할당 요청이 들어왔을 때, 요청된 크기와 비슷한 청크가 bin이나 tcache에 있는지 확인

       - 있으면 해당 청크를 꺼내서 재사용

   - 예제에서 Nametag와 Secret은 같은 크기의 구조체

       -> 앞서 할당한 secret을 해제하고 nametag를 할당하면 nametag는 secret과 같은 메모리 영역을 사용하게 됨

       -> 이때, free는 해제한 메모리의 데이터를 초기화하지 않으므로 nametag에는 secret의 값이 일부 남아있게 됨

 

   - gdb를 이용하여 분석해보자

       - 아래는 secret을 해제한 직후 secret이 사용하던 메모리 영역을 출력한 것

 

 

 

 

 

   - heap 명령어 : 할당 및 해제된 청크들의 정보를 조회

 

 

 

 

- 0x602250으로부터 8바이트씩 16진수 형식으로 10개 출력

 

cf) examine 명령어 : 특정 주소에서 원하는 길이만큼 데이터를 원하는 형식으로 인코딩하여 출력

   x/[숫자][첫번째 옵션][두번째 옵션] 

    ① 첫번째 옵션 : 진법 결정

       - o : octal

       - x : hex

       - d : decimal

       - u : unsigned decimal

       - t : binary

       - f : float

       - a : address

       - i : instruction

       - c : char

       - s : string

 

    ② 두번째 옵션 : 바이트 결정

       - b : 단일 바이트

       - h : 2바이트

       - w : 4바이트

       - g : 8바이트

 

 

 

 

 

- 0x602270에 저장된 문자열 확인

- secret_info의 값이 그대로 남아있는 것 확인 가능

 

       - 아래는 nametag를 할당하고 printf 함수를 호출하는 시점에서 nametag 멤버 변수들의 값을 확인하는 부분

 

 

 

 

 

 

- 0x602250으로부터 8바이트씩 16진수 형식으로 10개 출력

 

 

 

 

- 0x602260에 저장된 문자열 확인 -> security team 그대로 입력되어있음

- 0x602270에 저장된 문자열 확인 -> 초기화되지 않은 secret_info 값이 그대로 입력되어 있음

- 0x602250으로부터 8바이트씩 16진수 형식으로 출력 -> secret->code에 대입했던 0x1337이 남아있음

 

3. 마치며

 - UAF 취약점을 통해 공격자는 초기화되지 않은 메모리의 값을 읽어내거나, 새로운 객체가 악의적인 값을 사용하도록 유도할 수 있음

 

댓글