Dangling Pointer
유효하지 않은 메모리 영역을 가리키는 포인터
메모리의 동적 할당에 사용되는 malloc 함수는 할당한 메모리의 주소를 반환한다.
메모리를 동적 할당할 때는 포인터를 선언하고, 그 포인터에 malloc함수가 할당한 메모리의 주소를 저장한 뒤에 그 포인터를 참조하여 할당한 메모리에 접근한다.
메모리를 해제할 때는 free 함수를 호출하는데, free 함수는 청크를 ptmalloc에 반환하기만 하고 청크의 주소를 담고 있던 포인터를 초기화하지는 않는다. 그러므로 free의 호출 이후에 프로그래머가 포인터를 초기화하지 않으면 포인터는 해제된 청크를 가리키는 Dangling Pointer가 된다.
#include <stdio.h>
#include <stdlib.h>
int main() {
char *ptr = NULL;
int idx;
while (1) {
printf("> ");
scanf("%d", &idx);
switch (idx) {
case 1:
if (ptr) {
printf("Already allocated\n");
break;
}
ptr = malloc(256);
break;
case 2:
if (!ptr) {
printf("Empty\n");
}
free(ptr);
break;
default:
break;
}
}
}
위 코드에서는 청크를 해제한 후에 청크를 가리키던 ptr변수를 초기화하지 않아서... 청크를 할당하고 해제하면, ptr은 이전에 할당한 청크의 주소를 가리키는 Dangling Pointer가 된다.
ptr 이 해제된 청크의 주소를 가리키고 있기에 다시 해제할 수 있다.
Use After Free
Use-After-Free (UAF),
해제된 메모리에 접근할 수 있을 때 발생하는 취약점
앞서 살펴봤던 dangling_ptr.c와 같이 Dangling Pointer로 인해 발생하기도 하고 새롭게 할당한 영역을 초기화하지 않고 사용하면서 발생하기도 한다..
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();
}
}
아래는 Use-After-Free 취약점이 있는 uaf.c 이다. 구조체 NameTag와 Secret이 정의되어 있는데, 위에서는 그 중 외부에 유출되면 안 되는 Secret 구조체를 먼저 할당한다. 그리고 secret_name, secret_info, code에 값을 입력하고, 이를 해제한다.
코드의 34 번째 줄부터는 사원의 정보를 담고 있는 nametag를 생성한다. team_name, name에 각각의 값을 입력하고, 입력한 데이터를 출력한다. 이후에 함수 포인터 func가 NULL이 아니라면 포인터가 가리키는 주소를 출력하고, 해당 주소의 함수를 호출한다.

uaf 동적 분석
ptmalloc2 는 새로운 할당 요청이 들어왔을 때, 요청된 크기와 비슷한 청크가 bin이나 tcache에 있는지 확인한다. 그리고 만약 있다면, 해당 청크를 꺼내어 재사용한다. 위에서 Nametag와 Secret은 같은 크기의 구조체이다. 그러므로 앞서 할당한 secret을 해제하고 nametag를 할당한다면!!!! nametag는 secret과 같은 메모리 영역을 사용하게 된다. 이때, free는 해제한 메모리의 데이터를 초기화하지 않으니까 nametag에는 secret의 값이 일부 남아있게 된다.
gdb로 uaf 바이너리를 열고, secret을 해제(free)하는 다음 명령어 부분에 중단점을 설정한 후 실행해주었다.
heap 명령어로 할당 및 해제된 청크들의 정보를 조회해봤다.
3개의 청크가 존재한다. 0x405000이 우리가 살펴보고자 하는 secret에 해당하는 청크이다. free되었기 때문에 tcache의 엔트리에 들어가 있는 상태이다. 0x405000는 tcache와 관련된 공간으로 tcache_perthread_struct 구조체에 해당하며, libc 단에서 힙 영역을 초기화할 때 할당하는 청크이며, 0x405290 는 탑 청크에 해당한다.
다음은 이미 해제된 secret이 사용하던 메모리 영역을 출력한 모습이다. secret_name에 해당하는 부분은 적절한 fd와 bk값으로 초기화됐지만, secret_info에 해당하는 부분은 값이 그대로 남아있는 모습을 확인할 수 있다.
nametag 를 할당하고, printf 함수를 호출하는 시점에서 nametag 멤버 변수들의 값을 보자.
nametag->team_name 에는 “security team”이 그대로 입력되었으나, nametag->name 에는 초기화되지 않은 secret_info 의 값이 존재하는 것을 확인할 수 있다. 또한, nametag->func 위치에 secret->code 에 대입했던 0x1337 이 남아있는 게 보인다!!! 이 값이 0이 아니므로 코드의 42번째 줄에서 nametag->func 이 호출되고, Segmentation Fault가 발생한다.
###동적 할당한 청크를 해제한 뒤에는 해제된 메모리 영역에 이전 객체의 데이터가 남는다!###
'Pwnable' 카테고리의 다른 글
[pwnable.kr] unlink (0) | 2023.11.21 |
---|---|
[Dreamhack] tcache_dup (0) | 2023.11.21 |
[DreamHack] ptmalloc2 문서화 (0) | 2023.11.14 |
[DreamHack] Format String Bug 문서화 (0) | 2023.11.14 |
[Dreamhack] uaf_overwrite (0) | 2023.11.14 |