시스템 해킹 실습 -5 . Type Confusion 데이터 유형 혼동

2024. 7. 19. 00:39Information Security 정보보안/Vulnerability Analysis 취약점 분석

728x90

배울내용: 

시스템 해킹

단위공격 기술

Type Confusion 발생 원리

Uninitialized 실습 

데이터 유형 혼동 취약점  방지 방법

메모리 취약점

데이터 유형 혼동 취약점 이란? 

 

 

 

 

 

 

Type Confusion (데이터 유형 혼동) 이란? 

 소프트웨어에서 데이터의 유형을 잘못 처리하여 발생하는 보안 취약점으로 프로그램이 특정 유형의 데이터를 기대할 때 다른 유형의 데이터가 들어오면, 메모리 오류나 프로그램 충돌, 악성 코드 실행 같은 문제가 생길 수 있는 취약점을 말한다.

 

 

 

 

발생 원인 

보통 자료형의 잘못된 캐스팅 혹은 여러자료형으로 해석될 수 있는 Union 타입의 잘못된 사용에서 나타난다. Use After Free 등 다른 취약점의 Exploit 진행 과정에서 고의적으로 Type Confusion 을 유도하는 경우도 있다.

 

의미 : Type Confusion은 단순한 기본 자료형에서만 일어나는 것이 아니라, 특히 포인터나 크기가 다른 구조체에서 발생할 때 더 위험합니다. 이러한 경우, 로컬 권한 상승(LPE)이나 원격 코드 실행(RCE) 같은 심각한 보안 문제로 발전할 가능성이 매우 높습니다.

 

 

 

 

 

 

 

Uninitialized와 관련하여 발생한 Crash(Segmentation fault )는 초기화되지 않은 데이터로 인해 발생한 것이다.

구조체의 포인터 자리에는 초기화되지 않은 더미 데이터가 남아 있어서, 프로그램이 해당 포인터를 올바르게 인식하지 못하고 유효하지 않은 메모리 주소를 참조하게 되어서 발생한 것이다.

 

Type Confusion은 이와 비슷한 문제다. 여기서는 구조체를 잘못 사용하거나 잘못된 데이터 유형을 가정하여 프로그램이 예상대로 동작하지 않게 만들 수 있다.

 

 

아래는 이전 Uninitialized 링크


https://sarimus.tistory.com/141

 

시스템 해킹 실습 -4 . Uninitialized 미초기화 취약점

배울내용: 시스템 해킹단위공격 기술미초기화 발생 원리Uninitialized 실습 Uninitialized    방지 방법메모리 취약점미초기화 취약점 이란?          Uninitialized 란?  초기화되지않은 상태에서

sarimus.tistory.com

 

 

 

 

 

코드작성 

더보기

type_confusion.c

/*
    gcc ./type_confusion.c -o type_confusion
*/

#include "type_confusion.h"

void flushbuf(){
    int c;
    while ((c = getchar()) != '\n' && c != EOF);
}

int searchEmpty(){
    for(int i = 0; i < TOWN_SIZE; i++){
        if(town[i] == NULL){
            return i;
        }
    }
    return -1;
}

void makePerson(){
    int idx = searchEmpty();
    if(idx < 0){
        printf("[-] Town is full.\n");
        return;
    }
    
    struct person * person = (struct person*)malloc(sizeof(struct person));

    printf("Name > ");
    uint8_t *name = (uint8_t*)malloc(NAME_SIZE);
    fgets(name, NAME_SIZE, stdin);
    name[strlen(name) - 1] = '\0';
    person->name = name;

    printf("Job > ");
    uint8_t *job = (uint8_t*)malloc(JOB_SIZE);
    fgets(job, JOB_SIZE, stdin);
    job[strlen(job) - 1] = '\0';

    printf("Age > ");
    uint64_t age;
    scanf("%ld",&age);
    flushbuf();

    uint64_t type;
    if(age > 18){
        type = ADULT;
    }else{
        type = CHILD;
    }

    if(type == ADULT){
        person->type.adult.job = job;
        person->type.adult.age = age;
    }else if(type == CHILD){
        person->type.child.job = job;
        person->type.child.age = age;
    }

    person->typeflag = type;
    town[idx] = person;
    printf("[+] %s created at %p\n",person->name, person);
    return;
}

void upAge(){
    int idx;
    printf("idx in town> ");
    scanf("%d",&idx);
    flushbuf();

    if(idx > TOWN_SIZE - 1){
        printf("[-] invalid index.");
        return;
    }
    struct person *person = town[idx];
    if(person == NULL){
        printf("[-] invalid person.");
        return;
    }

    if(person->typeflag == ADULT){
        person->type.adult.age++;
    }else if(person->typeflag == CHILD){
        person->type.child.age++;
    }

    return;
}

void downAge(){
    int idx;
    printf("idx in town> ");
    scanf("%d",&idx);
    flushbuf();

    if(idx > TOWN_SIZE - 1){
        printf("[-] invalid index.");
        return;
    }
    struct person *person = town[idx];
    if(person == NULL){
        printf("[-] invalid person.");
        return;
    }

    if(person->typeflag == ADULT){
        person->type.adult.age--;
    }else if(person->typeflag == CHILD){
        person->type.child.age--;
    }

    return;
}

void transform(){
    int idx;
    printf("idx in town> ");
    scanf("%d",&idx);
    flushbuf();

    if(idx > TOWN_SIZE - 1){
        printf("[-] invalid index.");
        return;
    }

    struct person *person = town[idx];
    if(person == NULL){
        printf("[-] invalid person.");
        return;
    }

    printf("New Name >");
    memset(person->name, 0, NAME_SIZE);
    fgets(person->name, NAME_SIZE, stdin);
    person->name[strlen(person->name) - 1] = '\0';

    uint8_t *job = NULL;
    if(person->typeflag == ADULT){
        job =  person->type.adult.job;
    }else if(person->typeflag == CHILD){
        job = person->type.child.job;
    }

    memset(job, 0, JOB_SIZE);
    printf("New Job > ");
    fgets(job, JOB_SIZE, stdin);
    job[strlen(job) - 1] = '\0';

    if(person->typeflag == CHILD){
        if(person->type.child.age > 18){
            person->typeflag = ADULT;
        }
    }
    return;
}

void deletePerson(){
    int idx;
    printf("idx in town> ");
    scanf("%d",&idx);
    flushbuf();

    if(idx > TOWN_SIZE - 1){
        printf("[-] invalid index.");
        return;
    }
    struct person *person = town[idx];
    if(person == NULL){
        printf("[-] invalid person.");
        return;
    }
    free(person->name);

    if(person->typeflag == ADULT){
        free(person->type.adult.job);
    }else if(person->typeflag == CHILD){
        free(person->type.child.job);
    }
    free(person);
    town[idx] = NULL;
    return;
}

void printTown(){
    printf("\n[TOWN]\n");
    struct person *person;
    for(int i = 0; i < TOWN_SIZE; i++){
        if(town[i] != NULL){
            person = town[i];
            if(person->typeflag == ADULT){
                printf("[%d] %s(%s) \n", i, person->name, "Adult");
            }else if(person->typeflag == CHILD){
                printf("[%d] %s(%s) \n", i , person->name, "Child");
            }
        }
    }
    printf("\n");
}

int menu(){
    printTown();
    printf("[MENU]\n");
    printf("1. Make Person\n");
    printf("2. Age Up\n");
    printf("3. Age Down\n");
    printf("4. Transform\n");
    printf("5. Kill\n");
    printf("6. Leave\n");
    printf("> ");

    int idx = 0;
    scanf("%d",&idx);
    flushbuf();
    switch(idx){
        case 1:
            makePerson();
            break;
        case 2:
            upAge();
            break;
        case 3:
            downAge();
            break;
        case 4:
            transform();
            break;  
        case 5:
            deletePerson();
            break;
        case 6:
            printf("[-] Good Bye.\n");
            exit(0);
            break;
        default:
            break;
    }
}

int main(){
    printf("Hello. This is God Simulator.\n\n");
    while(1){
        menu();
    }
}
더보기

 

 

type_confusion.h

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

struct person{
    uint8_t * name;
    uint64_t typeflag;
    union{
        struct adult{
            uint64_t age;
            uint8_t * job;
        } adult;

        struct child{
            uint8_t * job;
            uint64_t age;
        } child;
    }type;
};


#define TOWN_SIZE 16
struct person * town[TOWN_SIZE] = {};

#define CHILD 1
#define ADULT 2
#define NAME_SIZE 0x10
#define JOB_SIZE 0x10

 

 

 

 

 

아래는 표는 코드 설명이다 

 


만약 type_confusion.c 를 컴파일 해서 실행하면  아래처럼 나오고 위에 나오는 표에서의 기능을 작동한다

 

 

 

 

 

 

 

 

먼저 1번을 이용해 사람을 생성 해준다

 

 

 

그리고 Name, Job ,Age 를 입력해준다 

그러면 [+}  created at  주소가 나온다 저기를 나중에 gdb로 확인해보면 될것같다

 

 

 

 


지금은 sarimus 가 나이가 17 살이라서 성인이 아니라 Child 로 되어있지만 성인으로 만들어보자

2번을 여러번하여 나이가 많이들게 만들어준다

 

 

 

 

그리고나서 4번으로 이름과 직업을 변경해주면 sarimus가 Adult 로 바뀌는걸 볼수가있다

 

 

 

 

이번에는 나이를 다시 줄여서 Child 로 만들어서 4 번으로 본래 이름과 직업을 넣어보자(다른글넣어도 상관없음)

 

 

 

 

 

 

그렇게 이름을 바꾸려는 찰나 

직업을 입력하는 창이 안나고 Crash (Segmentation Fault) 가 났다

 

 

 

 

이제 GDB 를 열어서 소스코드와 함꼐 열어서 문제점이 뭔지를 찾아보자 

 

 

 

 

 

 

프롬프트 창을 2개를 켜서 한쪽은 소스코드, 한쪽은 GDB 를 켜서 동시에 볼수있도록하자

 

 

 

먼저 소스코드에서 중요한 부분은 아래부분이다

 

 

if (person -> typeflag == CHILD) {
    if (person -> type.child.age > 18) {
        person -> typeflag = ADULT;
    }
}

 

여기에선 나이가 18 을 넘으면 성인이 되고 그렇지않으면  CHILD 로 되어있는데 

 

    if(type == ADULT){
        person->type.adult.job = job;
        person->type.adult.age = age;
    }else if(type == CHILD){
        person->type.child.job = job;
        person->type.child.age = age;
    }

 

여기에서 뭔가가 잘못된걸 알수있다 

이건 코드를 보고 주소안에 값을 보면 이해가 될것이다 

 

 



디버거 연결상태에서 1  → name = AAAA  BBBB 17 로 입력

 

 

그러면 AAAA 가 저장된 주소가 나오는데 0x555555559ac0 이 나옴

애를  p *(struct person*) 0x555555559ac0  

하면 아래처럼 보이게 된다  (struct person 은 헤더에서 확인가능)

 

 

 

 

 

이걸 생메모리로 보면 x/4gx 0x555555559ac0   하면

 

 

4개 나오는데  1번쨰는 이름, 2번쨰는 typeflag (1이니깐 CHILD) , 3번쨰 멤버는 job 이다 그리고 4번쨰는 age 인것이다 

이후에 나이를 올려서 바꿔보려 하지만 이 객체는 adult 타입이기 때문에 변환이 되지 않는다.

 

 

그리고 pd deletePerson 했을떄 



free 를 한이후에 cmp rax,0x2 가 보이는데 여기에 한번 break point 를 걸어본다

 b *deletePerson+197 

 

 

 

 

그리고 아까 나이를 올렸기 떄문에 18세를 넘겼으면 adult 로 되있어서 즉, type flag 가 2로 변경되있는걸 볼수있다.

 

 

c 로 다시 시작한다음에 kill person (5) 해주면 브레이크 포인트에 걸리는데 

 

 

 

x/4gx 9ac0 하면  type flag 가 2 로 나오고 그러면 저기 breakpoint 를 건부분에서  ni 하면 통과됨(같으니깐 통과됨)

 

 

  • 여기서 rax + 0x18은 24가 되고(HEX 18 --> DEC 24)  , 이는 person의 adult.job에 해당한다. (8 씩 넣으면 됨) 
  • 잘못된 주소 참조: 0x16 값을 rdi에 넣고, free@plt를 호출하는데, rdi에 잘못된 주소가 들어가므로 에러가 발생한다.

 

Rbp -0x10 → ni 해서 보면 rax에 9ac0 이 들어가있고

 

 

 

x/4gx 0x555555559ac0  로 메모리 보면 이전과 똑같이 나온다 

 

그리고 rax +0x18→ 24  이니깐 person 에 ault.job 에 해당하는곳

(길이에해당 각각 8 byte씩  들어감name, typeflag, age, job)

 

 

 

이걸 rdi 에 넣음 그리고 free @plt 하는데 인자라는 당시 주소가들어가야하는데

RAX가 0x14 이고 이걸 rdi 에 복사하고 이걸  free 에다가 넣는데 이게 주소가 아니니 free에서는 주소만 받아야하는데 주소가 아닌 다른 값이 들어가게 된다

 

 

 

 

 

 

 

RAX 에서 복사한 0x14 가 RDI 에 똑같이 들어가있는걸 볼수있고 그값이 free 하려고할때 CRASH 가 난것이다 

 

 

 

free 함수는 메모리 해제 함수로, 해제할 메모리 블록의 주소를 인자로 받아야 한다.

x86_64 아키텍처에서는 함수 호출 시 인자를 레지스터를 통해 전달하며, 첫 번째 인자는 rdi 레지스터를 통해 전달된다.

따라서, free 함수가 호출될 때 해제할 메모리 블록의 주소는 rdi 레지스터에 저장되어 있어야 한다.

이는 free(ptr)와 같은 호출에서 ptr이 rdi 레지스터로 전달된다는 것을 의미하기 떄문이다.

 

요약

  • free 함수는 메모리 블록의 주소를 인자로 받는다.
  • x86_64 아키텍처에서는 함수 호출 시 첫 번째 인자를 rdi 레지스터를 통해 전달.
  • 따라서 free 함수가 호출될 때 rdi 레지스터에 해제할 메모리 블록의 주소가 들어 있어야 한다.

 

 

 

해결방안 

 

 

If (person -> typeflag==CHILD){

if(person -> type.child.age >18) {

Person -> typeflag = ADULT;

}

}

이부분 안에있는 flag 내용도 같이 바꿔줬어야했다 

 

 

 

 

 

728x90