๋ฌธ์ : DreamHack โ SelfStatus ๋ถ๋ฅ: reversing ๋์ด๋: ๐ฅ Bronze 2 FLAG:
DH{S3lfSt4tus_VM_By7ec0d3}
๋ฌธ์ ์ค๋ช ์ ๋ ์ค์ด ์ ๋ถ๋ค. "Yet Another Self-Aware Flag Checker." ๊ทธ๋ฆฌ๊ณ "Anti-Debugging VM."
๋ฐ์ ๊ฒ๋ ํ์ผ ํ๋๋ฟ์ด๋ค. 14KB์ง๋ฆฌ stripped ELF ํ๋. ์ด๋ฆ๋ถํฐ SelfStatus ๋ผ์ ๋ญ ์ฝ๋ ํ๋ก๊ทธ๋จ์ธ์ง๋ ๋์ถฉ ์ง์์ด ๊ฐ๋๋ฐ, ์ ์ ์๊ฐ์ ์ก์๋จน๋ ์ชฝ์ anti-debug ๊ฐ ์๋๋ผ ๊ทธ ๋ค์ ์จ์ VM ์ด์๋ค.
file extracted/prob && checksec --file=extracted/prob
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | SelfStatus (DreamHack #1983) |
| ๋์ด๋ | ๐ฅ Bronze 2 |
| ๋ถ๋ฅ | reversing |
| ์ ๊ณต ํ์ผ | prob โ 14,456๋ฐ์ดํธ x86-64 PIE, stripped |
| ์๋ฒ | ์์ (์คํ๋ผ์ธ ๋ฌธ์ ) |
| ๋ณดํธ ๊ธฐ๋ฒ | Partial RELRO ยท Canary ยท NX ยท PIE |
| ํต์ฌ ๊ธฐ๋ฒ | procfs ๊ธฐ๋ฐ anti-debug + .rodata ์์ฃผ ์คํ VM ๋ฐ์ดํธ์ฝ๋ |
ํ์ด ํ๋ฆ์ ์งง๋ค. main ์ ์ธ ๊ฐ์ง๋ง ํ๋ค.
/proc/self/status๋ฅผ ์ฝ์ดTracerPid๊ฐ0์ด ์๋๋ฉด_exit(0)์ผ๋ก ์กฐ์ฉํ ์ฃฝ๋๋ค.- ์ ๋ ฅ์ 127๋ฐ์ดํธ๊น์ง ๋ฐ๊ณ ๋์ ๊ฐํ๋ง ์๋ผ๋ธ๋ค.
.rodata์ ์๋ 247๋ฐ์ดํธ๋ฅผ ์คํ VM ์ผ๋ก ๋๋ ค ๋ง๋ ๋ฌธ์์ด๊ณผ ๊ทธ ์ ๋ ฅ์strcmpํ๋ค.
์ธ ๋ฒ์งธ๊ฐ ์ด ๋ฌธ์ ์ ์ ๋ถ๋ค. ๊ทธ๋ฆฌ๊ณ VM ์ ์ ๋ ฅ์ ์ธ์๋ก ๋ฐ์ง๋ ์๋๋ค. ๊ทธ๋ฌ๋ "์ ๋ ฅ์ ์ด๋ค ๋ณํ์ ํต๊ณผ์์ผ ์์์ ๋น๊ต"ํ๋ ํํ ํฌ๋๋ฏธ ๊ตฌ์กฐ๊ฐ ์๋๋ผ, ์ ๋ต์ด ๋ฐ์ดํธ์ฝ๋ ์์ ํต์งธ๋ก ์ ํ ์๋ ๊ตฌ์กฐ๋ค.

๐ฌ ์ ์ฐฐ โ import ์ผ๊ณฑ ๊ฐ๊ฐ ์ ๋ถ๋ค
stripped ๋ผ ์ฌ๋ณผ์ ์์ง๋ง, PLT ๋ฅผ ํตํด ๋ถ๋ฅด๋ libc ํจ์๋ ๊ทธ๋๋ก ๋จ๋๋ค. ์ฌ๊ธฐ๊ฐ ์ ์ผ ์ ๋ณด๋์ด ๋ง์ ์๋ฆฌ๋ค.
readelf --dyn-syms -W extracted/prob | grep UND && strings -a -t x extracted/prob | grep -aE "proc/self|TracerPid|Enter flag|Correct|Wrong"
open ยท read ยท close ยท write ยท strcmp ยท strstr ยท _exit. ์ด๊ฒ ์ ๋ถ๋ค.
์ฌ๊ธฐ์ ์ด๋ฏธ ๋ง์ ๊ฒ ์ ํด์ง๋ค. printf ๋ malloc ๋ ์์ผ๋ ์ถ๋ ฅ์ ์ ๋ถ write(1, ...) ์ด๊ณ , ํ์ ์ ์ด๋ค. ๊ทธ๋ฆฌ๊ณ strstr ์ด ์๋ค๋ ๊ฑด ์ด๋๊ฐ์์ ๋ฌธ์์ด ์์ ๋ค์ง๋ค๋ ๋ป์ธ๋ฐ, ํ๋ณด๋ ๋ฌธ์์ด ๋ชฉ๋ก์ ํ๋๋ฟ์ด๋ค โ TracerPid:.
ํ์ผ๋ช
์ด SelfStatus ์ธ ์ด์ ๋ ์ฌ๊ธฐ์ ๋์จ๋ค. /proc/self/status ๋ฅผ ์ฝ๋๋ค.
์ด์ ์์ ์์ญ์ ํต์งธ๋ก ๋ณธ๋ค.
objdump -s -j .rodata extracted/prob
0x2117 ๋ถํฐ๋ ์๊น ๋ณธ ๋ฌธ์์ด๋ค์ด๋ค. ๋ฌธ์ ๋ ๊ทธ ์. 0x2020 ๋ถํฐ 0x2116 ๊น์ง 247๋ฐ์ดํธ๊ฐ ์์คํค๋ ์๋๊ณ ํฌ์ธํฐ ๋ฐฐ์ด๋ ์๋ ๋ฌด์ธ๊ฐ๋ก ์ฑ์์ ธ ์๋ค.
๋์ ๋๋ ๊ท์น์ด ํ๋ ์๋ค. ee ๊ฐ ์ ๋ํ ๋ง๊ณ , a1 ยท a3 ยท f0 ์ด ์งง์ ๊ฐ๊ฒฉ์ผ๋ก ๋ํ์ด๋๋ค. a342f0 ์ด๋ผ๋ ์ธ ๋ฐ์ดํธ ๋ฌถ์์ด ๊ณ์ ๋ณด์ธ๋ค. ์ด๊ฑด ๋ฐ์ดํฐ๋ผ๊ธฐ๋ณด๋ค ๋ช
๋ น ์คํธ๋ฆผ์ฒ๋ผ ์๊ฒผ๋ค.
๐งฉ ๋ฐฐ๊ฒฝ โ ๋ฆฌ๋ ์ค์์ "์ง๊ธ ๋๊ฐ ๋๋ฅผ ์ถ์ ์ค์ธ๊ฐ"
๋ฆฌ๋
์ค์์ ํ๋ก์ธ์ค๋ ์๊ธฐ ์์ ์ ๋ํ ๊ฑฐ์ ๋ชจ๋ ๊ฒ์ ํ์ผ๋ก ์ฝ์ ์ ์๋ค. /proc/self/ ์๋๊ฐ ๊ทธ ์ฐฝ๊ตฌ๋ค.
๊ทธ์ค status ์๋ TracerPid ๋ผ๋ ์ค์ด ์๋ค. ptrace ๋ก ์ด ํ๋ก์ธ์ค์ ๋ถ์ด ์๋ ์ถ์ ์์ PID ๊ฐ ์ ํ๊ณ , ์๋ฌด๋ ์ ๋ถ์์ผ๋ฉด 0 ์ด๋ค.
์ง์ ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋ํ ์ฐ์ด ๋ณด๋ฉด ์ด๋ ๋ค.
#!/usr/bin/env bash
# TracerPid ๊ฐ ์ค์ ๋ก ๋ฌด์์ ๋ด๋์ง ๋ ๊ฒฝ์ฐ๋ก ๋น๊ตํ๋ค.
# โ ์๋ฌด๋ ์ ๋ถ์์ ๋ โก gdb ๊ฐ ๋ถ์ด ์์ ๋
echo "[1] ์๋ฌด๋ ์ ๋ถ์ ์ํ (์ด ์
ธ ์์ )"
grep TracerPid /proc/self/status
echo
echo "[2] gdb ๋ก prob ์ ๋ถ์ก์ ๋ ์ํ โ ๊ทธ ํ๋ก์ธ์ค์ status ๋ฅผ ๋ณธ๋ค"
gdb -q -batch -ex starti \
-ex 'shell grep TracerPid /proc/$(pgrep -n prob)/status' \
-ex kill --args ./extracted/prob./tracerpid_demo.sh
[2] ์์ ๋์จ ์ซ์๋ ๋ถ์ด ์๋ gdb ์ PID ๋ค. ํ๋ก์ธ์ค๊ฐ ์๊ธฐ ํ์ผ ํ๋๋ง ์ฝ์ด๋ ์ถ์ ์์ ์ ์ฒด๊น์ง ์ ์ ์๋ค.
ptrace(PTRACE_TRACEME) ๋ฅผ ์๊ธฐ๊ฐ ๋จผ์ ๊ฑธ์ด ๋๋ ๊ณ ์ ์ ์ธ anti-debug ์ ๋น๊ตํ๋ฉด ์ฑ๊ฒฉ์ด ๋ค๋ฅด๋ค. ๊ทธ ๋ฐฉ์์ "๋๋ฒ๊ฑฐ๋ณด๋ค ๋จผ์ ์๋ฆฌ๋ฅผ ์ฐจ์งํ๋ค"๋ ์ ์ ์ด๊ณ , ์ด์ชฝ์ ๊ทธ๋ฅ ๋ฌผ์ด๋ณธ๋ค. ์ฝ๋๋ ํจ์ฌ ์งง๊ณ , ๋์ ์ฐํ๋ ํจ์ฌ ์ฝ๋ค. ํ์ผ ํ๋๋ง ๋ค๋ฅธ ๊ฑธ๋ก ๋ฐ๊ฟ์น๊ธฐํ๋ฉด ๋๋๊น.
gdb ๋ strace ๋ ltrace ๋ ์ ๋ถ ptrace ์์ ์ ์์ผ๋ฏ๋ก ์
๋ค ๋๊ฐ์ด ๊ฑธ๋ฆฐ๋ค.
๐ก๏ธ anti_debug() ๋ฅผ ๋ช ๋ น ๋จ์๋ก ์ฝ๋๋ค
0x11a9 ์์ ์์ํ๋ ํจ์๊ฐ ๊ทธ ๊ฒ์ฌ๋ค. ์ฝ๊ธฐ ํธํ๊ฒ objdump ์ถ๋ ฅ์ ์ฃผ์์ ์น์๋ค. ์ฃผ์์ ๋๊ตฌ๊ฐ ์ฐ์ด ์ฃผ๋ ๊ฒ ์๋๋ผ ๋ด๊ฐ sed ๋ก ๋ง๋ถ์ธ ๊ฒ์ด๊ณ , ๊ท์น ํ์ผ์ ๋ฌธ์ ํด๋์ annot_antidbg.sed ๋ก ๋จ๊ฒผ๋ค.
s|# 2117 .*|& <-- "/proc/self/status"|
s|# 2129 .*|& <-- "TracerPid:"|
s|call.*<open@plt>|& <-- open(path, O_RDONLY)|
s|mov edx,0x1ff|& <-- ์ต๋ 511๋ฐ์ดํธ๋ง ์ฝ๋๋ค|
s|call.*<read@plt>|& <-- read(fd, buf, 511)|
s|call.*<strstr@plt>|& <-- strstr(buf, "TracerPid:")|
s|add QWORD PTR \[rbp-0x220\],0xa|& <-- ํฌ์ธํฐ๋ฅผ "TracerPid:" 10๊ธ์๋งํผ ์ ์ง|
s|cmp al,0x20|& <-- ๊ณต๋ฐฑ ๊ฑด๋๋ฐ๊ธฐ|
s|cmp al,0x9|& <-- ํญ ๊ฑด๋๋ฐ๊ธฐ|
s|cmp al,0x30|& <-- ์ฒซ ๊ธ์๊ฐ '0' ์ธ๊ฐ|
s|mov eax,0x1$|& <-- '0' ์ด ์๋๋ฉด 1(=์ถ์ ๋นํ๋ ์ค) ๋ฐํ|objdump -d -M intel --start-address=0x11c3 --stop-address=0x1239 extracted/prob | sed -f annot_antidbg.sed
๋ฒํผ๋ ์คํ์ rbp-0x210, ํฌ๊ธฐ๋ 0x1ff = 511๋ฐ์ดํธ. ์ฝ์ ๊ธธ์ด๊ฐ 0 ์ดํ๋ฉด ๊ทธ๋ฅ 0 ์ ๋ฐํํ๊ณ ๋๋๋ค. ์ฆ ํ์ผ์ ๋ชป ์ฝ์ผ๋ฉด "์ถ์ ์ ๋นํจ"์ผ๋ก ์น๋ค. ๋ค์์ ์ด ์ฑ์ง์ ๊ทธ๋๋ก ์ด์ฉํ๋ค.
๋ท๋ถ๋ถ์ด ํ์ ์ด๋ค.
objdump -d -M intel --start-address=0x125c --stop-address=0x12cf extracted/prob | sed -f annot_antidbg.sed
C ๋ก ์ฎ๊ธฐ๋ฉด ์ด ์ ๋๋ค.
int anti_debug(void)
{
char buf[512];
int fd = open("/proc/self/status", O_RDONLY);
if (fd < 0) return 0; /* ๋ชป ์ด๋ฉด ํต๊ณผ */
ssize_t n = read(fd, buf, 0x1ff);
close(fd);
if (n <= 0) return 0; /* ๋ชป ์ฝ์ด๋ ํต๊ณผ */
buf[n] = 0;
char *p = strstr(buf, "TracerPid:");
if (!p) return 0; /* ๊ทธ ์ค์ด ์์ด๋ ํต๊ณผ */
p += 10;
while (*p == ' ' || *p == '\t') p++;
return (*p == '0') ? 0 : 1;
}๋น ์ ธ๋๊ฐ ๊ตฌ๋ฉ์ด ์ธ ๊ตฐ๋ฐ๋ ์ด๋ ค ์๋ค. ํ์ผ์ด ์์ด๋, ์ฝ๊ธฐ๊ฐ ์คํจํด๋, TracerPid: ๋ผ๋ ๋ฌธ์์ด์ด ์์ ์์ด๋ ์ ๋ถ "์ ์"์ผ๋ก ํต๊ณผํ๋ค. ๊ฒ์ฌ๋ฅผ ๋ง๋ค ๋ ์คํจ๋ฅผ ์์ ํ ์ชฝ์ผ๋ก ๋จ์ด๋จ๋ฆฐ ์
์ธ๋ฐ, anti-debug ๋ก์๋ ์ ๋ฐ๋ ๋ฐฉํฅ์ด๋ค.
์ค์ ๋ก ๋๋์ง๋ strace ๋ก ๋ฐ๋ก ๋ณด์ธ๋ค. ๊ทธ๋ฐ๋ฐ strace ์์ ์ด ptrace ๋ก ๋ถ๋ ๋๊ตฌ๋ผ์, ๋ถ๋ ์๊ฐ ๊ฒ์ฌ์ ๊ฑธ๋ฆฐ๋ค.
strace -f -e trace=openat,open,read,write,exit_group ./extracted/prob
/proc/self/status ๋ฅผ 511๋ฐ์ดํธ ์ฝ๊ณ ๊ณง์ฅ exit_group(0). write ๋ ํ ๋ฒ๋ ์ ๋์จ๋ค. ํ๋กฌํํธ์กฐ์ฐจ ๋ชป ์ฐ๊ณ ์ฃฝ์ ๊ฒ์ด๋ค. ๊ฒ์ฌ๊ฐ ์ค์ ๋ก ๋์ํ๋ค๋ ์ฆ๊ฑฐ๋ก๋ ์ด๋ณด๋ค ๊น๋ํ ๊ฒ ์๋ค.
โถ๐ ํจ์ โ ๋๋ฒ๊ฑฐ๋ก ๋ถ์ผ๋ฉด ์๋ฌด ์ผ๋ ์ ์ผ์ด๋๋ค
์ ์ ๋ถ์ ์์ด ๋จผ์ gdb ๋ก ๋ถ์ด ๋ณด๋ฉด ์ด๋ ๊ฒ ๋๋ค.
gdb -q -batch -ex run --args ./extracted/prob
exited normally ๋ค. ํฌ๋์๋ ์๋๊ณ ์๋ฌ ๋ฉ์์ง๋ ์๋ค. ์ข
๋ฃ ์ฝ๋๊น์ง 0 ์ด๋ผ "๋๋ฒ๊ฑฐ๊ฐ ๋ญ ์๋ชปํ๋", "๋ฐ์ด๋๋ฆฌ๊ฐ ๊นจ์ก๋" ์ชฝ์ ๋จผ์ ์์ฌํ๊ฒ ๋๋ค.
_exit(0) ์ ๊ณ ๋ฅธ ๊ฒ ์ด ๋ฌธ์ ์ ์๋ฏธ์ด ๋ถ๋ถ์ด๋ค. _exit(1) ์ด๋ abort() ์๋ค๋ฉด ๋น์ ์ ์ข
๋ฃ๊ฐ ๋์ ๋์ด์ ๋ฐ๋ก anti-debug ๋ฅผ ๋ ์ฌ๋ ธ์ ๊ฒ์ด๋ค. ์๋ฌด ์๋ฆฌ๋ ์ ๋ด๋ ์คํจ๊ฐ ์ ์ผ ์ฐพ๊ธฐ ์ด๋ ต๋ค.
ํฐ๋ฏธ๋์์ ๊ทธ๋ฅ ./prob ๋ฅผ ์คํํ๋ฉด ๋ฉ์ฉกํ ํ๋กฌํํธ๊ฐ ๋จ๋๋ฐ ๋๋ฒ๊ฑฐ ์์์๋ง ์กฐ์ฉํ ์ฃฝ๋๋ค๋ฉด, ๊ทธ ์์ ์ /proc/self/status ยท ptrace ยท getppid ์ชฝ์ ๋จผ์ ๋ค์ง๋ ๊ฒ ๋ง๋ค.
๐ฃ ํต์ฌ โ .rodata 247๋ฐ์ดํธ๋ ์คํ VM ๋ฐ์ดํธ์ฝ๋๋ค
main ์ anti-debug ๋ฅผ ํต๊ณผํ ๋ค write(1, "Enter flag: ", 13) ์ผ๋ก ํ๋กฌํํธ๋ฅผ ์ฐ๊ณ read(0, buf, 0x7f) ๋ก ์
๋ ฅ์ ๋ฐ๋๋ค. ๊ทธ๋ฆฌ๊ณ 0x12e5 ์ ํจ์๋ฅผ ์ด๋ ๊ฒ ๋ถ๋ฅธ๋ค.
15f9: lea rax,[rbp-0x90] ; 3๋ฒ์งธ ์ธ์: ์ถ๋ ฅ ๋ฒํผ
1600: mov rdx,rax
1603: mov esi,0xf7 ; 2๋ฒ์งธ ์ธ์: 247 (= 0x2116 - 0x2020 + 1)
1608: lea rax,[rip+0xa11] ; 1๋ฒ์งธ ์ธ์: 0x2020 โ .rodata ์ ๊ทธ ๋ฐ์ดํธ์ด
160f: mov rdi,rax
1612: call 12e5 ; vm(code, len, out)
1617: lea rdx,[rbp-0x110] ; ์ฌ์ฉ์ ์
๋ ฅ
161e: lea rax,[rbp-0x90] ; VM ์ถ๋ ฅ
1625: mov rsi,rdx
1628: mov rdi,rax
162b: call 1080 <strcmp@plt>์ธ์ ์
์ด๋์๋ ์ฌ์ฉ์ ์
๋ ฅ์ด ์๋ค. ์
๋ ฅ์ 0x1617 ์์ strcmp ๋ก ๋์ด๊ฐ ๋ ์ฒ์ ๋ฑ์ฅํ๋ค.
0x12e5 ํจ์ ์์ ๋ณด๋ฉด ์ ์ด๊ฒ VM ์ธ์ง ๋ฐ๋ก ๋๋ฌ๋๋ค. ์ฌ๊ธฐ์๋ objdump ์ถ๋ ฅ์ sed ๋ก ์ฃผ์์ ์น์๊ณ , ๊ท์น์ annot_vm.sed ์ ์๋ค.
s|cmp BYTE PTR \[rbp-0xa9\],0xa1|& <-- PUSH imm|
s|cmp BYTE PTR \[rbp-0xa9\],0xa2|& <-- POP|
s|cmp BYTE PTR \[rbp-0xa9\],0xa3|& <-- XOR imm|
s|cmp BYTE PTR \[rbp-0xa9\],0xa4|& <-- ADD imm|
s|cmp BYTE PTR \[rbp-0xa9\],0xa5|& <-- SKIP (์คํผ๋๋ 1๋ฐ์ดํธ ๋ฒ๋ฆผ)|
s|cmp BYTE PTR \[rbp-0xa9\],0xee|& <-- SKIP (0xa5 ์ ์์ ํ ๊ฐ์ ๋์)|
s|cmp BYTE PTR \[rbp-0xa9\],0xf0|& <-- OUT (pop ํด์ ์ถ๋ ฅ ๋ฒํผ๋ก)|
s|cmp BYTE PTR \[rbp-0xa9\],0xff|& <-- HALT|
s|mov QWORD PTR \[rbp-0x98\],rdx|& <-- pc ๊ฐฑ์ |
s|mov BYTE PTR \[rbp-0xa9\],al|& <-- op = code\[pc++\]|
s|mov BYTE PTR \[rbp+rax\*1-0x90\],dl|& <-- stack\[sp++\] = code\[pc++\]|
s|add QWORD PTR \[rbp-0x98\],0x1$|& <-- pc += 1 (์คํผ๋๋๋ฅผ ์ผํค๊ธฐ๋ง ํ๋ค)|
s|sub QWORD PTR \[rbp-0xa8\],0x1$|& <-- sp -= 1|
s|mov BYTE PTR \[rax\],dl|& <-- out\[oi++\] = stack\[sp\]|๋จผ์ ๋ฃจํ์ ๋จธ๋ฆฌ๋ค.
objdump -d -M intel --start-address=0x133a --stop-address=0x13a6 extracted/prob | sed -f annot_vm.sed
์ง์ญ๋ณ์ ์ ์ด VM ์ํ ์ ๋ถ๋ค.
| ์คํ ์ฌ๋กฏ | ์ญํ |
|---|---|
rbp-0x98 | pc โ ๋ฐ์ดํธ์ฝ๋ ์์์์ ์์น |
rbp-0xa8 | sp โ ๊ฐ ์คํ์ ๋์ด |
rbp-0x90 ์ดํ | ๊ฐ ์คํ (๋ฐ์ดํธ ๋จ์) |
rbp-0xa0 | ์ถ๋ ฅ ๋ฒํผ์ ์ด ๊ฐ์ |
๋์คํจ์น๋ ์ ํ ํ
์ด๋ธ์ด ์๋๋ผ cmp ๋ฅผ ์ค์ค์ด ๋์ด๋์ ํํ๋ผ์, ๊ทธ ๋น๊ต๋ฌธ๋ง ๋ฝ์ผ๋ฉด ๋ช
๋ น์ด ์งํฉ์ด ํ ํ๋ฉด์ ๋ค์ด์จ๋ค.
objdump -d -M intel extracted/prob | grep -E "cmp +BYTE PTR \[rbp-0xa9\]" | sed -f annot_vm.sed
์ ๋ฆฌํ๋ฉด ์ด๋ ๋ค.
| opcode | ๋์ | ์คํผ๋๋ |
|---|---|---|
0xa1 | stack[sp++] = imm | 1๋ฐ์ดํธ |
0xa2 | sp ๊ฐ 0 ์ด ์๋๋ฉด sp-- | ์์ |
0xa3 | ์คํ ๋งจ ์๋ฅผ imm ๊ณผ XOR | 1๋ฐ์ดํธ |
0xa4 | ์คํ ๋งจ ์์ imm ์ ๋ํจ | 1๋ฐ์ดํธ |
0xa5 | ์๋ฌด ์ผ๋ ์ ํจ | 1๋ฐ์ดํธ (๋ฒ๋ฆผ) |
0xee | ์๋ฌด ์ผ๋ ์ ํจ | 1๋ฐ์ดํธ (๋ฒ๋ฆผ) |
0xf0 | sp-- ํ ๊ทธ ๊ฐ์ ์ถ๋ ฅ ๋ฒํผ๋ก | ์์ |
0xff | ์ข ๋ฃ | ์์ |
0xa5 ์ 0xee ๋ ๊ฐ๊ฐ ๊ฐ์ ์ผ์ ํ๋ค. ๋ ๋ค pc ๋ฅผ 1 ์ฌ๋ฆฌ๊ธฐ๋ง ํ๋๋ฐ, fetch ๋จ๊ณ์์ ์ด๋ฏธ pc++ ๋ฅผ ํ์ผ๋ ๋ค์ 1๋ฐ์ดํธ๋ฅผ ์คํผ๋๋๋ก ์ผ์ผ์ ๋ฒ๋ฆฌ๋ ๋์์ด๋ค. ์ด๊ฒ ์ด ๋ฌธ์ ์ ๋๋
ํ ์ฅ์น๋ค.
๋ง์ง๋ง์ผ๋ก ์ถ๋ ฅ ํธ๋ค๋ฌ๋ฅผ ํ์ธํ๋ค.
objdump -d -M intel --start-address=0x14a1 --stop-address=0x1505 extracted/prob | sed -f annot_vm.sed
0xf0 ์ ์คํ ๋งจ ์๋ฅผ ๊บผ๋ด ์ถ๋ ฅ ๋ฒํผ์ ์์๋๋ก ๋ถ์ธ๋ค. ๋ฃจํ๊ฐ ๋๋๋ฉด ๊ทธ ๋ค์ 0 ์ ๋ถ์ฌ C ๋ฌธ์์ด๋ก ๋ง๊ฐํ๋ค. ๊ทธ๊ฒ strcmp ์ ์ฒซ ์ธ์๊ฐ ๋๋ค.
๋ช ๋ น ์ฌ๋ ๊ฐ, ๋ชจ๋ 1๋ฐ์ดํธ ์คํผ๋๋ ์๋๋ฉด ๋ฌด์ธ์. ๋ ์ง์คํฐ๋ ๋ถ๊ธฐ๋ ์๋ค. VM ์ด๋ผ๊ธฐ์ ์๋ฐํ์ง๋ง ๋ชฉ์ ์๋ ์ถฉ๋ถํ๋ค โ ์ ๋ต ๋ฌธ์์ด์ ์์คํค๋ก ์ ๋ณด์ด๊ฒ ๊ฐ์ถ๋ ๊ฒ.
๐ฏ VM ์ ํ์ด์ฌ์ผ๋ก ๊ทธ๋๋ก ์ฎ๊ธด๋ค
์ญ์ฐํ ๊ฒ ์์ผ๋ ํ ์ผ์ ํ๋๋ค. .rodata ์์ 247๋ฐ์ดํธ๋ฅผ ๊บผ๋ด ์ ํ๋๋ก ์คํํ๋ค.
๋ฐ์ดํธ์ฝ๋๋ฅผ ๊บผ๋ผ ๋ ์คํ์
์ ์์ผ๋ก ๊ณ์ฐํด ํ๋์ฝ๋ฉํ์ง ์์๋ค. ์น์
ํค๋๋ฅผ ํ์ด 0x2020 ์ด ๋ค์ด ์๋ ์น์
์ ์ฐพ๊ณ ํ์ผ ์คํ์
์ผ๋ก ํ์ฐํ๋ค. ๊ทธ๋์ผ ๋์ค์ ์ด ์คํฌ๋ฆฝํธ๋ง ๋ด๋ ์ด๋์ ์จ ๊ฐ์ธ์ง ์ ์ ์๋ค.
#!/usr/bin/env python3
"""SelfStatus (DreamHack, Bronze 2, reversing) - solver
prob ์ .rodata 0x2020 ์ ๋ฐํ 247๋ฐ์ดํธ ๋ฐ์ดํธ์ฝ๋๋ฅผ ๊ทธ๋๋ก ๊บผ๋ด
0x12e5 ์ VM ์ ํ์ด์ฌ์ผ๋ก ์ฌํํ๋ค. VM ์ถ๋ ฅ์ด ๊ณง ์ ๋ต ํ๋๊ทธ๋ค
(main ์ ๊ทธ ์ถ๋ ฅ๊ณผ ์ฌ์ฉ์ ์
๋ ฅ์ strcmp ๋ก ๋น๊ตํ ๋ฟ์ด๋ค).
"""
import sys
from pathlib import Path
ELF = Path(__file__).with_name("extracted") / "prob"
CODE_VADDR = 0x2020 # main ์ด vm ์ ๋๊ธฐ๋ ์ฒซ ์ธ์ (lea rax,[rip+0xa11])
CODE_LEN = 0xF7 # ๋ ๋ฒ์งธ ์ธ์ (mov esi,0xf7)
# --- .rodata ๋ฅผ ์น์
ํค๋๋ก ์ฐพ์ ํ์ผ ์คํ์
์ผ๋ก ํ์ฐ -------------------
def read_code(path: Path) -> bytes:
b = path.read_bytes()
e_shoff = int.from_bytes(b[0x28:0x30], "little")
e_shentsize = int.from_bytes(b[0x3A:0x3C], "little")
e_shnum = int.from_bytes(b[0x3C:0x3E], "little")
for i in range(e_shnum):
sh = b[e_shoff + i * e_shentsize : e_shoff + (i + 1) * e_shentsize]
addr = int.from_bytes(sh[0x10:0x18], "little")
off = int.from_bytes(sh[0x18:0x20], "little")
size = int.from_bytes(sh[0x20:0x28], "little")
if addr <= CODE_VADDR < addr + size: # .rodata ๊ฐ ์กํ๋ค
start = off + (CODE_VADDR - addr)
return b[start : start + CODE_LEN]
raise SystemExit("๋ฐ์ดํธ์ฝ๋๊ฐ ๋ ์น์
์ ๋ชป ์ฐพ์๋ค")
# --- 0x12e5 ์ VM ์ ๊ทธ๋๋ก ์ฎ๊ธด ๊ฒ ------------------------------------
PUSH, POP, XOR, ADD, NOP1, NOP2, OUT, HALT = 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xEE, 0xF0, 0xFF
def run(code: bytes):
stack, out, pc = [], bytearray(), 0
while pc < len(code):
op = code[pc]; pc += 1
if op == PUSH: # stack[sp++] = code[pc++]
stack.append(code[pc]); pc += 1
elif op == POP: # if sp: sp--
if stack: stack.pop()
elif op == XOR: # if sp: stack[-1] ^= code[pc++]
if not stack: continue
stack[-1] ^= code[pc]; pc += 1
elif op == ADD: # if sp: stack[-1] += code[pc++]
if not stack: continue
stack[-1] = (stack[-1] + code[pc]) & 0xFF; pc += 1
elif op in (NOP1, NOP2): # ์คํผ๋๋ 1๋ฐ์ดํธ๋ง ์ผํค๊ณ ์๋ฌด๊ฒ๋ ์ ํ๋ค
pc += 1
elif op == OUT: # if sp: out[oi++] = stack[--sp]
if stack: out.append(stack.pop())
elif op == HALT:
break
# ๊ทธ ๋ฐ์ ๋ฐ์ดํธ๋ ์คํผ๋๋๋ฅผ ์๋นํ์ง '์๊ณ ' ๊ทธ๋ฅ ๋ฌด์๋๋ค
return bytes(out)
# --- ๋ฐ์ดํธ์ฝ๋ ๋์ค์ด์
๋ธ๋ฌ (๊ธ/๊ฒ์ฆ์ฉ) --------------------------------
def disasm(code: bytes):
lines, pc = [], 0
while pc < len(code):
at, op = pc, code[pc]; pc += 1
if op in (PUSH, XOR, ADD, NOP1, NOP2):
arg = code[pc]; pc += 1
name = {PUSH: "PUSH", XOR: "XOR ", ADD: "ADD ", NOP1: "SKIP", NOP2: "SKIP"}[op]
lines.append(f"{at:04x}: {op:02x} {arg:02x} {name} 0x{arg:02x}")
else:
name = {POP: "POP", OUT: "OUT", HALT: "HALT"}.get(op, "??? (๋ฌด์)")
lines.append(f"{at:04x}: {op:02x} {name}")
if op == HALT: break
return lines
if __name__ == "__main__":
code = read_code(ELF)
if "-d" in sys.argv:
lines = disasm(code)
if "--limit" in sys.argv:
n = int(sys.argv[sys.argv.index("--limit") + 1])
lines, rest = lines[:n], len(lines) - n
if rest > 0:
lines.append(f"... (๊ฐ์ ๋ชจ์์ผ๋ก {rest}์ค ๋)")
print("\n".join(lines))
sys.exit(0)
flag = run(code)
print(f"๋ฐ์ดํธ์ฝ๋ {len(code)}๋ฐ์ดํธ -> ์ถ๋ ฅ {len(flag)}๋ฐ์ดํธ")
print("FLAG:", flag.decode())๋๋ฆฌ๊ธฐ ์ ์ ๋จผ์ ๋์ค์ด์ ๋ธ๋ง ํด ๋ณธ๋ค. ์ 24์ค์ด๋ฉด ํจํด์ด ๋ค ๋ณด์ธ๋ค.
python3 solve.py -d --limit 24
ํ๋ก๊ทธ๋จ ์ ์ฒด๊ฐ ์ด ํ ๋ฌธ์ฅ์ด๋ค.
(SKIP ์ฌ๋ฌ ๊ฐ) PUSH v XOR 0x42 OUT๋์ค์ด์
๋ธ์ ์ ์ฒด 137์ค์ธ๋ฐ, ๊ทธ์ค XOR 26๋ฒ์ ์์๊ฐ ์ ๋ถ 0x42 ํ๋๋ค. ๊ฒฐ๊ตญ ์ถ๋ ฅ ํ ๊ธ์๋ v ^ 0x42 ๋ค. ์๋ฅผ ๋ค์ด ๋งจ ์ PUSH 0x06 ยท XOR 0x42 ๋ 0x44 = 'D' ๋ฅผ ๋ธ๋ค.
0xa2(POP)์ 0xa4(ADD)๋ ํธ๋ค๋ฌ๊ฐ ์๋๋ฐ ํ ๋ฒ๋ ์คํ๋์ง ์๋๋ค. 247๋ฐ์ดํธ ์์์ 0xa2 ๋ ๋ฑ ํ ๋ฒ ๋์ค๋๋ฐ ๊ทธ๋ง์ SKIP ์ ์คํผ๋๋ ์๋ฆฌ๊ณ , 0xa4 ๋ ์์ ํ ๋ฒ๋ ์ ๋์จ๋ค. ์ธํฐํ๋ฆฌํฐ๋ฅผ ์ฝ๋ ์ฌ๋ ํท๊ฐ๋ฆฌ๋ผ๊ณ ๋ฃ์ด ๋ ๋ช
๋ น๋ค์ด๋ค.
๊ทธ๋์ ์คํ ๊ฒฐ๊ณผ๋ ์์๋๋ก๋ค.
python3 solve.py
DH{S3lfSt4tus_VM_By7ec0d3}. 26๊ธ์.
๐ ์กฐ์ฉํ ํ๋ฆฌ๋ ์ง์
์ด VM ์์ ์ด๊ธ๋ ์ ์๋ ์๋ฆฌ๋ ์ฌ์ค์ ํ๋๋ฟ์ธ๋ฐ, ๊ทธ ํ๋๊ฐ ํฐ๊ฐ ์ ์ ๋๋ค. ์ง์์ผ๋ก ๋๊ธฐ์ง ์์ผ๋ ค๊ณ ํ๋ฆฐ ํด์์ ์ค์ ๋ก ๋๋ ค ๋ดค๋ค.
โถ๐ ํจ์ โ 0xee ๋ฅผ ๊ทธ๋ฅ NOP ์ผ๋ก ์ฝ์ผ๋ฉด 'DH' ๊น์ง๋ง ๋์จ๋ค
0xa5 ยท 0xee ํธ๋ค๋ฌ๋ add QWORD PTR [rbp-0x98],0x1 ํ ์ค์ด ์ ๋ถ๋ค. ์ฌ๊ธฐ์ rbp-0x98 ์ pc ๋ค.
fetch ๋จ๊ณ์์ ์ด๋ฏธ pc++ ๋ฅผ ํ ๋ค์ด๋ฏ๋ก ์ด ํ ์ค์ ๋ค์ 1๋ฐ์ดํธ๋ฅผ ์คํผ๋๋๋ก ์ผ์ผ ๋ฒ๋ฆฐ๋ค๋ ๋ป์ด๋ค. ๊ทธ๋ฐ๋ฐ ํธ๋ค๋ฌ ๋ณธ๋ฌธ๋ง ๋ผ์ด ๋๊ณ ๋ณด๋ฉด "pc ๋ฅผ 1 ์ฆ๊ฐ์ํจ๋ค = ๊ทธ๋ฅ ๋ค์ ๋ช
๋ น์ผ๋ก ๊ฐ๋ค = NOP" ์ผ๋ก ์ฝ๊ธฐ ๋ฑ ์ข๋ค. ๊ทธ๋ฌ๋ฉด ์คํผ๋๋ ์๋ฆฌ ๋ฐ์ดํธ๊ฐ ์ ๋ถ opcode ๋ก ์ฌํด์๋๋ฉด์ ์คํธ๋ฆผ์ด ํต์งธ๋ก ์ด๊ธ๋๋ค.
์ค์ ๋ก ์ด๋ป๊ฒ ๋๋์ง ์ฌ ๋ดค๋ค.
#!/usr/bin/env python3
"""SelfStatus โ ๋ฐ์ดํธ์ฝ๋๋ฅผ ์๋ชป ์ฝ์ผ๋ฉด ์ด๋ป๊ฒ ๋๋์ง ์ค์ธกํ๋ ์คํฌ๋ฆฝํธ.
์ธ ๊ฐ์ง๋ฅผ ์ฌ ๋ณธ๋ค.
[A] 0xee ๋ฅผ '์คํผ๋๋ ์๋ NOP' ์ผ๋ก ์ฝ์์ ๋ (pc += 0)
[B] 0xa5 / 0xee ๋ฅผ ์์ '์ ์ ์๋ opcode' ๋ก ๋ฌด์ํ์ ๋
[C] ์ฌ๋ฐ๋ฅธ ํด์ (๋ ๋ค ์คํผ๋๋ 1๋ฐ์ดํธ๋ฅผ ์ผํจ๋ค)
๊ทธ๋ฆฌ๊ณ ๊ฐ ๊ฒฐ๊ณผ๋ฅผ ๋ฌธ์ ๋ฐ์ด๋๋ฆฌ์ ๊ทธ๋๋ก ๋ฃ์ด ํ์ ์ ๋ฐ์ ์จ๋ค.
"""
import subprocess
from solve import read_code, ELF
code = read_code(ELF)
def run(code, ee_skips=1, a5_skips=1):
stack, out, pc = [], bytearray(), 0
stats = {"unknown": 0, "ops": 0, "junk_bytes": 0}
while pc < len(code):
op = code[pc]; pc += 1; stats["ops"] += 1
if op == 0xA1:
stack.append(code[pc]); pc += 1
elif op == 0xA2:
if stack: stack.pop()
elif op == 0xA3:
if stack: stack[-1] ^= code[pc]; pc += 1
elif op == 0xA4:
if stack: stack[-1] = (stack[-1] + code[pc]) & 0xFF; pc += 1
elif op == 0xA5:
pc += a5_skips; stats["junk_bytes"] += 1 + a5_skips
elif op == 0xEE:
pc += ee_skips; stats["junk_bytes"] += 1 + ee_skips
elif op == 0xF0:
if stack: out.append(stack.pop())
elif op == 0xFF:
break
else:
stats["unknown"] += 1
return bytes(out), stats
def ask_binary(candidate: bytes) -> str:
p = subprocess.run([str(ELF)], input=candidate + b"\n",
capture_output=True)
return p.stdout.decode(errors="replace").replace("Enter flag: ", "").strip()
for label, kw in (("[A] 0xee ๋ฅผ pc+=0 NOP ์ผ๋ก", dict(ee_skips=0)),
("[B] 0xa5/0xee ๋ฅผ ๋ฏธ์ง opcode ๋ก", dict(ee_skips=0, a5_skips=0)),
("[C] ์ฌ๋ฐ๋ฅธ ํด์ (pc+=1)", dict())):
out, st = run(code, **kw)
shown = out.decode("latin-1").encode("unicode_escape").decode()
print(f"{label}: {len(out)}๋ฐ์ดํธ")
print(f" out = {shown[:120]}")
print(f" ํ์ = {ask_binary(out)} (๋ฏธ์ง opcode {st['unknown']}ํ)")
out, st = run(code)
print()
print(f"๋ฐ์ดํธ์ฝ๋ ์ด {len(code)}๋ฐ์ดํธ ์ค SKIP(0xa5/0xee)์ด ๋จน์ ์์ฌ = {st['junk_bytes']}๋ฐ์ดํธ")
print(f"์ค์ ๋ก ์ถ๋ ฅ์ ๊ธฐ์ฌํ ๋ฐ์ดํธ = {len(code) - st['junk_bytes']}๋ฐ์ดํธ")python3 traps.py
DH ๋ค. ํ๋๊ทธ ์ ๋์ฌ๊ฐ ์ ํํ ๋์จ๋ค.
์ด๊ฒ ๊ณ ์ฝํ ์ด์ ๊ฐ ์ฌ๊ธฐ ์๋ค. ์ด๊ธ๋ ํด์์ด ์ฒ์ ๋ ๊ธ์๋ฅผ ๋งํ๊ณ ๋์ ๋ฉ์ถ๋๊น, "ํฌ๋งท์ ๋ง๋๋ฐ ๋ค๊ฐ ์ ์ ๋์ค์ง" ์ชฝ์ผ๋ก ์๊ฐ์ด ํ๋ฌ๊ฐ๋ค. ์ธ์ฝ๋ฉ์ ์์ฌํ๊ฑฐ๋ ๊ธธ์ด ํ๋๋ฅผ ์ฐพ๊ฒ ๋์ง, ์ ์ pc ๊ณ์ฐ์ ๋ค์ ๋ณด๊ฒ ๋์ง๋ ์๋๋ค.
๋ฉ์ถ ์์น๋ ์ฐ์ฐ์ด ์๋๋ค. ์คํ์
0x0018 ์ ๋ฐ์ดํธ ์์ ee ff ์ธ๋ฐ, ์ด๊ธ๋ ํด์์์๋ ์ด 0xff ๊ฐ ์คํผ๋๋๊ฐ ์๋๋ผ HALT opcode ๋ก ์ฝํ๋ค. ๋๋
ํ์ฉ ์ฐ๋ ๊ธฐ ๋ฐ์ดํธ ์ฌ์ด์ 0xff ๊ฐ ์์ฌ ์์ด์, ์คํธ๋ฆผ์ด ๋ฐ๋ฆฌ๋ ์๊ฐ ์กฐ๊ธฐ ์ข
๋ฃ๊ฐ ๊ฑธ๋ฆฌ๋ ๊ตฌ์กฐ๋ค. ์ค์ ๋ก 0xff ๋ 247๋ฐ์ดํธ ์์ ์ธ ๋ฒ ๋์ค๋๋ฐ opcode ์๋ฆฌ์ธ ๊ฑด ๋งจ ๋ ํ๋๋ฟ์ด๋ค. ๋๋จธ์ง ๋์ ์คํผ๋๋๋ก ์จ์ด ์๋ค๊ฐ ํด์์ด ์ด๊ธ๋ ๋๋ง ๊นจ์ด๋๋ค.
์ซ์๋ก๋ ํ์ธ๋๋ค. 247๋ฐ์ดํธ ์ค SKIP ์ด ๋จน์ด์น์ฐ๋ ๊ฒ 116๋ฐ์ดํธ๋ก ์ ์ฒด์ 47%๋ค. ์ง์ง ์ผํ๋ ๊ฑด 131๋ฐ์ดํธ๋ฟ์ด๊ณ , ๊ทธ ์์์ PUSH ยท XOR ยท OUT ์ด 26์ธํธ๋ฅผ ์ด๋ฃฌ๋ค.
๊ทธ๋ฆฌ๊ณ ํ์ ์ ๋์ผ๋ก ํ์ง ์์๋ค๋ ์ ์ด ์ค์ํ๋ค. ์ธ ํด์์ ๊ฒฐ๊ณผ๋ฅผ ์ ๋ถ ๋ฌธ์ ๋ฐ์ด๋๋ฆฌ์ ๊ทธ๋๋ก ๋จน์ฌ Wrong! / Correct! ๋ฅผ ๋ฐ์ ์๋ค. ํ๋ณด๊ฐ ๊ทธ๋ด๋ฏํด ๋ณด์ด๋์ง๋ ํ๋จ ๊ทผ๊ฑฐ๊ฐ ๋ชป ๋๋ค.
๐ ๋ฐํ์์ผ๋ก ๋ชป๋ฐ๊ธฐ โ anti-debug ๋ง ๊บผ ๋ณธ๋ค
์ฌ๊ธฐ๊น์ง๋ ์ ๋ถ ์ ์ ๋ถ์์ด๋ค. strcmp ์ ์ฒซ ์ธ์๊ฐ ์ ๋ง ์ ๋ฌธ์์ด์ธ์ง ์คํ ์ค์ ์ง์ ๋ณด๊ณ ์ถ์๋ฐ, ๋๋ฒ๊ฑฐ๋ฅผ ๋ถ์ด๋ฉด ํ๋ก๊ทธ๋จ์ด ์ฃฝ๋๋ค.
์์์ ๋ณธ ๋๋ก ๊ฒ์ฌ๋ open("/proc/self/status") ํ ์ค์ ์์กดํ๊ณ , prob ์ ๊ทธ open ์ PLT ๋ก ๋ถ๋ฅธ๋ค. ๊ทธ๋ฌ๋ฉด LD_PRELOAD ๋ก open ๋ง ๊ฐ๋ก์ฑ๋ฉด ๋๋ค. ๋ค๋ฅธ ๊ฑด ์๋ฌด๊ฒ๋ ์ ๊ฑด๋๋ฆฐ๋ค.
/* nostatus.c โ prob ์ anti-debug ๋ฅผ ๋ฌด๋ ฅํํ๋ LD_PRELOAD ์ฌ.
*
* prob ์ open("/proc/self/status") ๋ก ์๊ธฐ ์ํ๋ฅผ ์ฝ์ด TracerPid ๋ฅผ ๋ณธ๋ค.
* ๊ทธ open ํธ์ถ๋ง ๊ฐ๋ก์ฑ ์ฐ๋ฆฌ๊ฐ ๋ง๋ fake_status ๋ก ๋๋ฆฐ๋ค.
* gcc -shared -fPIC -o nostatus.so nostatus.c -ldl
*/
#define _GNU_SOURCE
#include <dlfcn.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
#include <sys/types.h>
int open(const char *path, int flags, ...)
{
static int (*real_open)(const char *, int, ...);
if (!real_open)
real_open = dlsym(RTLD_NEXT, "open");
mode_t mode = 0;
if (flags & O_CREAT) {
va_list ap;
va_start(ap, flags);
mode = va_arg(ap, int);
va_end(ap);
}
if (strcmp(path, "/proc/self/status") == 0)
path = "./fake_status";
return real_open(path, flags, mode);
}gcc -shared -fPIC -o nostatus.so nostatus.c -ldl
printf 'Name:\tprob\nState:\tR (running)\nTracerPid:\t0\nUid:\t1000\t1000\t1000\t1000\n' > fake_status
printf 'AAAA\n' > input.txt์ด์ gdb ๋ก strcmp ํธ์ถ ์ง์ ์ ๋ฉ์ถฐ ์ธ์ ๋์ ๊ทธ๋๋ก ์ฝ๋๋ค. PIE ๋ผ์ ๋ก๋ ์ฃผ์๋ฅผ ๊ด๋ก๋ก ๊ฐ์ ํ์ง ์๊ณ info proc mappings ์์ ์ค์ ๊ฐ์ ๋ฐ์ ์๋ค.
# dump_expected.gdb โ VM ์ด ๋ง๋ค์ด ๋ธ "์ ๋ต ๋ฌธ์์ด"์ ์คํ ์ค์ ์ง์ ๊บผ๋ด ๋ณธ๋ค.
# LD_PRELOAD ์ฌ์ผ๋ก anti-debug ๋ฅผ ๋ ๋ค strcmp ํธ์ถ ์ง์ (ํ์ผ ์คํ์
0x162b)์ ๋ฉ์ถ๊ณ
# rdi(= VM ์ถ๋ ฅ ๋ฒํผ) ์ rsi(= ์ฌ์ฉ์ ์
๋ ฅ) ๋ฅผ ๊ทธ๋๋ก ์ฝ๋๋ค.
set confirm off
set pagination off
set environment LD_PRELOAD ./nostatus.so
starti < input.txt
python
import gdb
base = None
for line in gdb.execute("info proc mappings", to_string=True).splitlines():
parts = line.split()
if parts and parts[-1].endswith("/prob"):
base = int(parts[0], 16)
break
print("PIE base = %#x (info proc mappings ๋ก ํ์ธ, ๊ด๋ก ๊ฐ์ ๊ธ์ง)" % base)
gdb.execute("break *%#x" % (base + 0x162b))
gdb.execute("continue")
gdb.execute('printf "rdi (VM ์ด ์กฐ๋ฆฝํ ์ ๋ต) = %s\\n", (char*)$rdi')
gdb.execute('printf "rsi (๋ด๊ฐ ๋ฃ์ ์
๋ ฅ) = %s\\n", (char*)$rsi')
end
killgdb -q -batch -x dump_expected.gdb --args ./extracted/prob
Enter flag: ํ๋กฌํํธ๊ฐ ์ ์์ ์ผ๋ก ๋ฌ๋ค. anti-debug ๊ฐ ๊บผ์ง ๊ฒ์ด๋ค.
๊ทธ๋ฆฌ๊ณ ๋ธ๋ ์ดํฌํฌ์ธํธ์์ rdi ๋ DH{S3lfSt4tus_VM_By7ec0d3}, rsi ๋ ๋ด๊ฐ ๋ฃ์ AAAA ๋ค. ์ ์ ๋ถ์์ด ๋งํ ๊ฒ โ "VM ์ถ๋ ฅ์ด ๊ณง ์ ๋ต์ด๊ณ ์
๋ ฅ์ ๋ง์ง๋ง ๋น๊ต์์๋ง ์ฐ์ธ๋ค" โ ์ด ์คํ ์ค์๋ ๊ทธ๋๋ก๋ค.
printf ์์ ํ๊ธ ๋ผ๋ฒจ์ ๋ด๊ฐ dump_expected.gdb ์ ์ ์ด ๋ฃ์ ๊ฒ์ด์ง gdb ๊ฐ ์๋ ์ฐ์ด ์ฃผ๋ ๋ฌธ๊ตฌ๊ฐ ์๋๋ค.
๐ ์ฌํ
ํ๋๊ทธ๊ฐ ๋ง๋์ง๋ ๋์ผ๋ก ๋์กฐํ์ง ์๊ณ ๋ฌธ์ ๋ฐ์ด๋๋ฆฌ์๊ฒ ์ง์ ๋ฌผ์๋ค.
#!/usr/bin/env python3
"""solve.py ๊ฐ ๋ฝ์ ๊ฐ์ ๋ฌธ์ ๋ฐ์ด๋๋ฆฌ์ ๊ทธ๋๋ก ๋จน์ฌ ํ์ ์ ๋ฐ์ ์จ๋ค."""
import subprocess
from solve import read_code, ELF, run
flag = run(read_code(ELF)).decode()
p = subprocess.run([str(ELF)], input=flag + "\n", capture_output=True, text=True)
print(f"์
๋ ฅ : {flag}")
print(f"์ถ๋ ฅ : {p.stdout.replace(chr(0), '').strip()}")
print(f"ํ์ : {'PASS' if 'Correct' in p.stdout else 'FAIL'}")python3 verify.py
Correct!.
๋ฌธ์ ํด๋์๋ ์ด๊ฑธ ํ ๋ฒ์ ๋๋ reproduce.sh ๋ฅผ ๊ฐ์ด ๋จ๊ฒจ ๋๋ค. ๋์ค์ ๋ฐฑ์
๋ง ๊บผ๋ด๋ ๋ฐ๋ก ๋์๊ฐ๋ค.
#!/usr/bin/env bash
# SelfStatus (DreamHack #1983, Bronze 2, reversing) โ ํ ๋ฐฉ ์ฌํ.
# .rodata ์ VM ๋ฐ์ดํธ์ฝ๋๋ฅผ ํ์ด์ฌ์ผ๋ก ๋๋ ค flag ๋ฅผ ๋ง๋ค๊ณ ,
# ๊ทธ ๊ฐ์ ๋ฌธ์ ๋ฐ์ด๋๋ฆฌ์ ๋ฃ์ด Correct! ๊ฐ ๋์ค๋์ง๊น์ง ํ์ธํ๋ค.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
EXPECT=$(python3 -c "import json;print(json.load(open('๋ฌธ์ .json'))['flag'])" 2>/dev/null || echo '')
chmod +x extracted/prob
OUT=$(timeout 60 python3 verify.py 2>&1) || true
echo "$OUT"
FLAG=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | head -1)
if [ -n "$FLAG" ] && printf '%s' "$OUT" | grep -q 'PASS' \
&& { [ -z "$EXPECT" ] || [ "$FLAG" = "$EXPECT" ]; }; then
echo; echo "โ
PASS $FLAG"; exit 0
fi
echo; echo "โ FAIL (์ป์ ๊ฐ: '${FLAG:-์์}' / ๊ธฐ๋: '$EXPECT')"; exit 1๋๋ฆฐ ํ๊ฒฝ์ Ubuntu 25.10 ยท glibc 2.42 ยท gcc 15.2.0 ยท gdb 16.3 ยท binutils 2.45 ยท Python 3.13.7 ์ด๋ค.
prob ์ libc ๋ง๊ณ ๋ ์๋ฌด๊ฒ๋ ์ ์ฐ๊ณ ๋ฐฐํฌ๋ณธ ๊ทธ๋๋ก ์คํ๋๋ฏ๋ก libc ๋ฒ์ ์ ๋ง์ถ ํ์๊ฐ ์๋ค. ์ฐธ๊ณ ๋ก ๋ฐฐํฌ๋ณธ์ .comment ์๋ GCC 14.2.1 20250207 ์ด ๋ฐํ ์๋๋ฐ, ๊ทธ๊ฑด ์ถ์ ์๊ฐ ๋น๋ํ ์ปดํ์ผ๋ฌ์ด์ง ์ด ๊ธ์ ์ฐ๋ฉฐ ์ด ๊ฒ์ด ์๋๋ค. nostatus.so ๋ง ์ gcc ๋ก ์๋ก ๋น๋ํ๋ค.
๐ ๊ฒฐ๋ก
anti-debug ๋ ๋ฐฉ์ด๊ฐ ์๋๋ผ ํ์งํ์ด์๋ค.
/proc/self/status ๋ฅผ ์ฝ๋ ๊ฒ์ฌ๋ ์ฐํ๊ฐ ์ฝ๋ค. ํ์ผ์ ๋ชป ์ด๋ฉด ํต๊ณผ, ๋ชป ์ฝ์ด๋ ํต๊ณผ, ๊ทธ ์ค์ด ์์ด๋ ํต๊ณผ โ ์คํจ๊ฐ ์ ๋ถ ์์ ํ ์ชฝ์ผ๋ก ๋จ์ด์ง๋ค. open ํ๋๋ง ๊ฐ๋ก์ฑ๋ฉด ๋๋๋ค.
์คํ๋ ค ์ด ๊ฒ์ฌ์ ์กด์ฌ ์์ฒด๊ฐ "์ฌ๊ธฐ ๋ญ๊ฐ ์จ๊ฒจ์ ธ ์๋ค"๋ ์ ํธ์๋ค. ํ๋ก๊ทธ๋จ์ด ์๊ธฐ ์ํ๋ฅผ ํ์ธํ๋ ์ฝ๋๋ฅผ ๋ฃ์๋ค๋ ๊ฑด ์คํ ์ค ๊ด์ฐฐ์ ๋ง๊ณ ์ถ๋ค๋ ๋ป์ด๊ณ , ๋ง๊ณ ์ถ์ ๊ฒ ๋ญ์ง๋ ๊ทธ ๋ค์ ์๋ค.
์ง์ง ๋ฐฉ์ด๋ VM ์ชฝ์ด์๋ค.
.rodata ์ ๋ฌธ์์ด์ ๊ทธ๋ฅ ๋๋ฉด strings ํ ์ค์ ๋๋๋ค. ๋ฐ์ดํธ์ฝ๋๋ก ์ ์ด ๋๋ฉด ์ต์ํ ์ธํฐํ๋ฆฌํฐ๋ฅผ ์ฝ์ด์ผ ํ๋ค. ์ฌ๊ธฐ์ ์๋ฌด ์ผ๋ ์ ํ๋ฉด์ ๋ค์ ๋ฐ์ดํธ๋ฅผ ์ผํค๋ ๋ช
๋ น์ ์ ๋ฐ์ฏค ์์ด ๋๋, ๋์ผ๋ก ํ์ ๋๋ ๊ท์น์ด ์ ๋ณด์ธ๋ค.
๊ทธ๋๋ ํ๊ณ๋ ๋ถ๋ช
ํ๋ค. ์ ๋ต์ด ์คํ ์ค ์ด๋๊ฐ์๋ ๋ฐ๋์ ํ๋ฌธ์ผ๋ก ์กด์ฌํ๊ธฐ ๋๋ฌธ์ด๋ค. strcmp ๋ฅผ ์ฐ๋ ์ด์ ๋น๊ต ์ง์ ์ ์์ฑ๋ ๋ฌธ์์ด์ด ๋ฉ๋ชจ๋ฆฌ์ ๋์ธ๋ค. ๋ธ๋ ์ดํฌํฌ์ธํธ ํ๋๋ฉด ๋๋๋ค.
์ฐ์ฐ ๊ฒฐ๊ณผ๊ฐ ์๋๋ผ ํ๋ฆ์ ๋จผ์ ๋ณธ๋ค.
์ด ๋ฌธ์ ์์ ์๊ฐ์ ์๋ ํ๋จ์ "์
๋ ฅ์ด ์ด๋ ์ง์ ์์ ์ฒ์ ์ฐ์ด๋๊ฐ"๋ฅผ ๋จผ์ ํ์ธํ ๊ฒ์ด๋ค. vm(code, len, out) ์ ์ธ์ ์
์ ์
๋ ฅ์ด ์๋ค๋ ๊ฑธ ๋ณด๋ ์๊ฐ ์ญ์ฐ์ด ํ์ ์๋ค๋ ๊ฒ ์ ํด์ก๋ค. ํฌ๋๋ฏธ๋ผ๊ณ ํด์ ํญ์ ์
๋ ฅ์ ๋ณํํด ๋น๊ตํ๋ ๊ฑด ์๋๋ค.
๋งํ ๋ ํ์ ์ ๋ฐ์ด๋๋ฆฌ์๊ฒ ๋๊ธด๋ค.
0xee ๋ฅผ ์๋ชป ์ฝ์ ํด์์ DH ๋ผ๋ ๊ทธ๋ด๋ฏํ ๋ ๊ธ์๋ฅผ ๋ฑ๋๋ค. ์ฌ๋ ๋์ผ๋ก๋ "๊ฑฐ์ ๋ค ์๋ค"๋ก ๋ณด์ธ๋ค. ํ๋ณด๊ฐ ๋์ฌ ๋๋ง๋ค ๋ฌธ์ ๋ฐ์ด๋๋ฆฌ์ ๊ทธ๋๋ก ๋จน์ฌ Correct! / Wrong! ๋ฅผ ๋ฐ์ ์ค๋ฉด ๊ทธ๋ฐ ์ฐฉ๊ฐ์ด ๋ ์๋ฆฌ๊ฐ ์๋ค.
๋ฐฉ์ดํ๋ ์ชฝ์์๋, ํด๋ผ์ด์ธํธ์ ์ ๋ต์ ๋ฃ์ด ๋๊ณ ๋น๊ตํ๋ ๊ตฌ์กฐ ์์ฒด๊ฐ ๊ทผ๋ณธ ๋ฌธ์ ๋ค. ๋ฐ์ดํธ์ฝ๋๋ก ์ ๋ XOR ๋ก ๊ฐ๋ฆฌ๋ ์คํ ์์ ์๋ ๋ณต์๋ผ์ผ ํ๋ฏ๋ก ์๊ฐ๋ง ๋ฒ ๋ฟ์ด๋ค. ๊ฒ์ฆ์ด ์ค์ ๋ก ํ์ํ ๊ฐ์ด๋ผ๋ฉด ๋น๊ต๋ฅผ ์๋ฒ์์ ํ๊ฑฐ๋, ์ต์ํ ์ ๋ต์ ์ ์ฅํ์ง ๋ง๊ณ ์ ๋ ฅ์ ํด์๋ง ๋์กฐํ๋ ์ชฝ์ผ๋ก ๊ฐ๋ ๊ฒ ๋ง๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ