Use-After-Free 란?
메모리 참조에 사용한 포인터를 메모리 해제 후에 적절히 초기화하지 않아서 (Dangling pointer), 또는 해제한 메모리를 초기화하지 않고 다음 청크에 재할당해주면서 발생하는 취약점이다. 즉, 해제된 메모리에 접근할 수 있을 때 발생하는 취약점이다.
Dangling Pointer: 해제된 메모리를 가리키고 있는 포인터. UAF가 발생하는 원인이 될 수 있다.
Use-After-Free (UAF): 해제된 메모리에 접근할 수 있을 때 발생하는 취약점
이 취약점은 현재까지도 브라우저 및 커널에서 자주 발견되고 있으며, 익스플로잇 성공률도 다른 취약점에 비해 높아 상당히 위험하다고 알려져 있다.
1. Dangling Pointer
유효하지 않은 메모리 영역을 가리키는 포인터를 의미한다.
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가 된다.
Dangling Pointer가 생긴다고 해서 프로그램이 보안적으로 취약한 것은 아니다. 그러나 Dangling Pointer는 프로그램이 예상치 못한 동작을 할 가능성을 키우며, 경우에 따라서는 공격자에게 공격 수단으로 활용될 수도 있다.
2. 새롭게 할당한 영역을 초기화하지 않고 사용
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();
}
}
ptmalloc2는 새로운 할당 요청이 들어왔을 때, 요청된 크기와 비슷한 청크가 bin이나 tcache에 있는지 확인한다. 그리고 만약 있다면, 해당 청크를 꺼내어 재사용한다.
코드에서 Nametag와 Secret은 같은 크기의 구조체다. 그러므로 앞서 할당한 secret을 해제하고 nametag를 할당하면, nametag는 secret과 같은 메모리 영역을 사용하게 된다. 이때 free는 해제한 메모리의 데이터를 초기화하지 않으므로, nametag에는 secret의 값이 일부 남아있게 된다.