Memory Corruption: Use After Free
Dangling Pointer
Dangling Pointer는 유효하지 않은 메모리와 영역을 가리키는 포인터를 말한다. 메모리의 동적 할당에 사용되는 malloc 함수는 할당한 메모리의 주소를 반환한다.
일반적으로 메모리를 동적 할당할 때는 포인터를 선언하고, 그 포인터에 malloc 함수가 할당한 메모리의 주소를 저장한다. 그리고 그 포인터를 참조하여 할당한 메모리에 접근한다. 메모리를 해제할 때는 free를 호출하고 free 함수는 청크를 ptmalloc에 반환하기만 할 뿐, 청크의 주소를 담고 있던 포인터는 초기화하지 않는다.
즉, free 호출 후 포인터를 초기화하지 않으면 포인터는 해제된 청크를 가리키는 Dangling Pointer가 된다.
// Name: dangling_ptr.c
// Compile: gcc -o dangling_ptr dangling_ptr.c
#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가 된다.
Use After Free
Use-After-Free (UAF)는 해제된 메모리에 접근할 수 있을 때 발생하는 취약점이다. Dangling Pointer로 인해 발생하기도 하지만, 새롭게 할당한 영역을 초기화하지 않고 사용하면서 발생하기도 한다.
malloc과 free 함수는 할당 또는 해제할 메모리의 데이터들을 초기화하지 않는다. 새롭게 할당한 청크를 프로그래머가 명시적으로 초기화하지 않으면 메모리에 남아있던 데이터가 유출되거나 사용될 수 있다.
// Name: uaf.c
// Compile: gcc -o uaf uaf.c -no-pie
#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@ssw@rd!@#");
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();
}
}
위 예제에서 NameTag, Secret 구조체를 정의하고 외부에 유출되면 안되는 Secret 구조체의 name, info, code에 값을 입력하고 해제한다.
이후 nametag를 생성해 team name, name에 각각 값을 입력하고, 입력한 데이터를 출력한다. 이후 함수 포인터 func가 NULL이 아니라면 포인터가 가리키는 주소를 출력하고 해당 주소의 함수를 호출한다.
출력 결과를 보면 Name secret_info의 문자열이 출력되고 값을 입력한 적없는 함수 포인터가 0x1337을 가리키는 것을 확인할 수 있다.
동적 분석을 통해 확인해보면 secret 청크의 값이 들어가 있는것을 확인할 수 있다.
free를 통해 secret 청크를 해제하고 이후에 nametag 청크를 할당할건데 Secret 구조체와 NameTag의 구조체는 동일한 크기를 가지고 있기 때문에 이전에 해제한 청크를 nametag가 사용할 수 있다.
nametag를 malloc을 한후 청크의 상태를 살펴보면 P@ssw@rd!@#와 1337의 값이 그대로 남아 있는것을 볼 수 있다. 이상태로 nametag의 team_name과 name의 값이 들어가는걸 확인해보자.
nametag에 memcpy, strcpy를 한상태를 보면 security team 그리고 P문자가 S로 바뀌었다. 이와 같이 메모리를 할당하고 해제후 초기화를 하지않으면 메모리에 데이터가 그대로 남아있기 때문에 이를 이용한다면 메모리의 값을 읽어내거나, 새로운 객체가 악의적인 값을 사용하게 유도하여 프로그램의 정상적인 실행을 방해할 수 있다.