System Hacking - UAF (Use After Free)

1. UAF란

프로그램이 해제된 (free) 메모리를 다시 사용하는 경우에 발생하는 취약점이다. 보통 free함수를 통해 해제된 포인터를 다시 참조하거나 사용하는 코드가 존재할 때 발생하며, 이 메모리를 공격자가 악의적으로 제어 가능한 데이터로 덮어쓰면 프로그램 흐름이 조작되거나 정보를 유출할 수 있다.

발생 조건은 아래와 같다.

  • 동적으로 할당된 메모리를 free함수로 해제한 후, 해당 포인터를 다시 사용할 경우

2. 예시 코드

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char *name;
} User;

int main() {
    User *user = (User *)malloc(sizeof(User));
    user->name = strdup("Alice");

    free(user);   // 메모리 해제

    // [UAF 발생] 이미 해제된 user->name을 참조하거나 수정
    printf("User name: %s\n", user->name);

    return 0;
}

user포인터에 할당한 후 해제하였지만, 이미 해제된 user->name에 접근이 가능하여 정의되지 않은 동작을 일으킬 수 있다.

3. Exploit 예제

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char *name;
    void (*print_func)();
} User;

void print_name() {
    printf("Hello from print_name!\n");
}

void hacked() {
    printf("🔥 HACKED! Shellcode or arbitrary code execution!\n");
}

int main() {
    User *u1 = malloc(sizeof(User));
    u1->name = strdup("Alice");
    u1->print_func = print_name;

    free(u1); // 메모리 해제

    // 🌟 UAF 발생
    User *u2 = malloc(sizeof(User)); // 동일한 크기의 객체 재할당

    // 공격자가 덮어쓰는 부분 (메모리 구조를 조작했다고 가정)
    u2->name = strdup("Eve");
    u2->print_func = hacked; // u1이 참조하던 함수 포인터가 덮어써짐

    // 여전히 u1을 사용하려는 코드
    u1->print_func(); // 👈 hacked() 함수가 실행됨 (exploit 성공)

    return 0;
}

u1 구조체를 동적으로 할당하고, free를 통해 해제하였다. 이후 같은 크기의 u2를 다시 할당하면 동일한 힙 주소를 재사용하게되고, u2->print_func 값을 공격자가 원하는 함수로 설정하면 u1->print_func를 호출하더라도 hacked함수가 실행된다.

4. GLIBC의 힙 구조

glibcmalloc함수는 free된 청크들을 사이즈별로 분류하여 재사용한다. 이 때 사용하는 구조는 아래와 같다.

구조 청크 크기 범위 특징
fastbin ≤ 0x80 (128 bytes) 매우 빠르게 재사용됨. FIFO 아님 (LIFO). tcache보다 먼저 사용되지 않음
tcache ≤ 0x410 (1040 bytes) glibc 2.26+부터 도입된 스레드별 캐시. 매우 빠름. 최대 7개까지 유지
smallbin 0x20 ~ 0x3f0 FIFO 방식. 메인 아레나에 존재. double free 검사 등 안전성 높음
unsorted bin 모든 크기 free 직후 들어가는 일시적 공간. 이후 small/large bin으로 이동

4.1. tcache

tcacheThread-Local Cache로서, glibc 2.26부터 도입된 기능이다. 각 스레드에 대해 독립적인 캐시를 유지하여 할당/해제를 빠르게 할 수 있고, 크기별로 최대 7개까지 free된 청크를 저정한다. 또한 LIFO 방식을 사용하여 최근에 해제된 것부터 재사용한다.

매우 빠르게 동작하는 장점이 있지만, double free, UAF 공격 등에 대해 검사가 느슨하여 안전성이 낮다. 이에 따라 마지막으로 해제되었던 chunk와 같은 크기의 chunk를 재할당하면 동일한 위치로 할당될 가능성이 높아서 UAF 공격이 수행될 수 있다.

4.2. smallbin

smallbin은 특정 크기 (0x20 ~ 0x3f0)에 해당하는 작은 청크들을 위한 bin이다. free된 청크는 일단 unsorted bin에 들어갔다가, 적절한 사이즈의 smallbin으로 이동한다. FIFO방식을 사용하며, fd (forward), bk (backward) 포인터로 연결된 이중 연결 리스트로 되어있다.

tcache 대비 double free와 같은 안전 검사를 강화하여 안전하지만 느리다.