๋ฌธ์ : DreamHack โ Vernichtet ๋ถ๋ฅ: reversing ๋์ด๋: ๐ฅ Gold 4 FLAG:
DH{e309147b588c517bb4100064d6185e5430ebad23d83e601327c4907bb0232292}
๋ฌธ์ ์ค๋ช ์ ๋ฑ ํ ์ค์ด๋ค. "I destroyed the binary, but it still works :>"
๋ ์ผ์ด ์ ๋ชฉ Vernichtet ๋ ๊ฐ์ ๋ง์ด๋ค โ ํ๊ดด๋๋ค. ๊ทธ๋ฐ๋ฐ ๋ญ๊ฐ ํ๊ดด๋๋์ง๋ ์ ์๋ ค ์ค๋ค.
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | Vernichtet |
| ๋์ด๋ | ๐ฅ Gold 4 |
| ๋ถ๋ฅ | reversing |
| ์ ๊ณต ํ์ผ | main (ELF64 PIE, stripped, 15,160 ๋ฐ์ดํธ) |
| ์๋ฒ | ์์ โ ์คํ๋ผ์ธ ๋ฌธ์ |
| ํต์ฌ ๊ธฐ๋ฒ | ๊ฒน์นจ ๋ช ๋ น(overlapping instruction)์ ์ด์ฉํ ์ํฐ ๋์ค์ด์ ๋ธ๋ฆฌ + 15ร15 Hidato ํผ์ฆ |
ํ์ด ํ๋ฆ์ ์ด๋ ๋ค. ํ๊ดด๋ ์ ์ฒด๊ฐ ์ํฐ ๋์ค์ด์ ๋ธ๋ฆฌ ์คํ ์ด๋ผ๋ ๊ฑธ ํ์ธํ๊ณ โ ๊ธธ์ด๋ฅผ ์ ์งํ ์ฑ NOP ์ผ๋ก ๋ฎ์ด ์ฝ๋๋ฅผ ๋์ด๋ฆฐ ๋ค์ โ ๋์ด์๋ ๋ ๊ฒ์ฌ ํจ์๋ฅผ ์ฝ์ด ์ ๋ต ํ์ผ์ ์กฐ๊ฑด์ ์์๋ด๊ณ โ ๊ทธ ์กฐ๊ฑด์ด ์ฌ์ค ํผ์ฆ์ด๋ผ๋ ๊ฑธ ๋์น์ฑ๊ณ ํ๋ฉด, ๋ฐ์ด๋๋ฆฌ๊ฐ ์ค์ค๋ก ํ๋๊ทธ๋ฅผ ๋ฑ๋๋ค.
๐ฌ ์ ์ฐฐ โ ๋ฌธ์์ด์ด ์ ๋ฐ์ ๋งํด ์ค๋ค
๋ฐ์ ๊ฑด ํ์ผ ํ๋๋ฟ์ด๋ค. ๋ ํ๋ ๊ฒ๋ถํฐ ํ๋๋ค.
#!/usr/bin/env bash
# cap_recon.sh โ ๋ฐฐํฌ ๋ฐ์ด๋๋ฆฌ ์ ์ฐฐ: ํฌ๋งท / ๋ณดํธ๊ธฐ๋ฒ / ๋์ ๋๋ ๋ฌธ์์ด
file extracted/main
readelf -h extracted/main | grep -E 'Type:|Entry point|Number of section headers'
readelf -d extracted/main | grep -E 'NEEDED|BIND_NOW|PIE'
strings -a extracted/main | grep -E 'Usage|Correct|Wrong answer|Fread|sha256sum'./cap_recon.sh
๋ง์ง๋ง ๋ฌธ์์ด์ด ์ด ๋ฌธ์ ์ ๊ท์น์ ํต์งธ๋ก ์๋ ค ์ค๋ค.
bash -c "echo DH{$(sha256sum '%s' | awk '{print $1}')}"ํ๋๊ทธ๋ ๋ฐ์ด๋๋ฆฌ ์์ ์๋ค. ์ฐ๋ฆฌ๊ฐ ๋ง๋ค์ด ๋ธ ์ ๋ต ํ์ผ์ sha256 ์ด ๊ณง ํ๋๊ทธ๋ค. ๊ทธ๋ฌ๋ ์ ๋ต ํ์ผ์ ๋ฐ์ดํธ๋ฅผ ํ๋๋ ํ๋ฆฌ์ง ์๊ฒ ๋ณต์ํด์ผ ํ๋ค. "๋์ถฉ ๋ง๋ ๋ต"์ด๋ผ๋ ๊ฑด ์กด์ฌํ์ง ์๋๋ค.
Usage ./main <answer file> ๋ก ์ธ์ ํํ๋ฅผ ์ ์ ์๊ณ , Wrong answer. / Fread failed / Correct! ๋ก ๋ถ๊ธฐ ๊ตฌ์กฐ๋ ๋๋ต ๊ทธ๋ ค์ง๋ค.
โถ๐ ์ฝ์ง 1 โ 'ํ๊ดด๋๋ค'๋ฅผ ์น์ ํค๋ ์๊ธฐ๋ก ์ฝ์๋ค
"destroyed the binary but it still works" ๋ฅผ ๋ณด๊ณ ์ ์ผ ๋จผ์ ๋ ์ฌ๋ฆฐ ๊ฑด ์น์
ํค๋ ์ญ์ ์๋ค. ํ๋ก๊ทธ๋จ ํค๋๋ง ์์ผ๋ฉด ์คํ์ ๋๊ณ , ์น์
ํค๋๊ฐ ์์ผ๋ฉด objdump ๊ฐ ์ฌ๋ณผ๋ ์น์
๋ ๋ชป ์ก์ผ๋ ๋ฑ ๋ง๋ ์ด์ผ๊ธฐ ์๋๊ฐ.
๊ทธ๋์ readelf -S ๋ถํฐ ์ณค๋๋ฐ, 29๊ฐ๊ฐ ์ ๋ถ ๋ฉ์ฉกํ๋ค.
[16] .text PROGBITS 0000000000001180 00001180
000000000000074c 0000000000000000 AX 0 0 16
[18] .rodata PROGBITS 0000000000002000 00002000
0000000000000091 0000000000000000 A 0 0 8
[25] .data PROGBITS 0000000000004000 00003000
00000000000002c3 0000000000000000 WA 0 0 32.text ๋ .rodata ๋ .data ๋ ์ฃผ์ยทํฌ๊ธฐ๊ฐ ์ ์์ด๊ณ , readelf -l ์ LOAD ์ธ๊ทธ๋จผํธ๋ ์ด๊ธ๋ ๋ฐ๊ฐ ์๋ค. ELF ๊ตฌ์กฐ๋ ์๋ ํ์ ์ด ์์๋ค. ํ๊ดด๋ ๊ฑด ๋ ์์ชฝ์ด์๋ค.
๐ฃ ํ๊ดด์ ์ ์ฒด โ eb ff c1 ff c9
.text ๋ฅผ ํต์งธ๋ก ๋ฏ์ด๋ณด๋ฉด ์ ์ฒด๊ฐ ๋ฐ๋ก ๋๋ฌ๋๋ค.
objdump -d -M intel --start-address=0x1269 --stop-address=0x12b7 extracted/main
jmp ๊ฐ ์๊ธฐ ์์ ์ ๋ค์ ๋ฐ์ดํธ๋ก ๋ด๋ค. ๊ทธ๋ฆฌ๊ณ ๊ทธ ๋ค์ sar edi,0xc9 ๋ผ๋, ์ด ํ๋ก๊ทธ๋จ ์ด๋์๋ ์ธ๋ชจ๊ฐ ์๋ ๋ช
๋ น์ด ๋ฐ๋ผ๋ถ๋๋ค. ๋์ธ ๋ช
๋ น๋ง๋ค ํ ๋ฒ์ฉ, ๋๋ ์์ด.
๋ฐ์ดํธ๋ก ๋ณด๋ฉด ๋ค์ฏ ๊ฐ๋ค. eb ff c1 ff c9.
x86 ์ ๊ฐ๋ณ ๊ธธ์ด ๋ช
๋ น์ด๋ผ ์ด๋์๋ถํฐ ์ฝ๊ธฐ ์์ํ๋๋์ ๋ฐ๋ผ ๊ฐ์ ๋ฐ์ดํธ๊ฐ ์์ ํ ๋ค๋ฅธ ๋ช
๋ น์ด ๋๋ค. eb ff ๋ jmp $+1 โ ์ฆ jmp ๋ช
๋ น ์์ ์ ์คํผ๋๋ ๋ฐ์ดํธ(0xff)๋ก ์ ํํ๋ค. ๋์ค์ด์
๋ธ๋ฌ๋ eb ff ๋ฅผ ํ ๋ช
๋ น์ผ๋ก ์๋นํ๊ณ ๋ค์ ๋ฐ์ดํธ์ธ 0xc1 ๋ถํฐ ์ฝ์ง๋ง, CPU ๋ ์ ํ๋ฅผ ๋ฐ๋ผ 0xff ๋ถํฐ ์ฝ๋๋ค. ๊ทธ๋์ ๋ ํด์์ด ๊ฐ๋ฆฐ๋ค.

๋ง๋ก๋ง ํ๋ฉด ๋ชป ๋ฏฟ์ ์๊ธฐ๋ผ ๊ฐ์ 5๋ฐ์ดํธ๋ฅผ ๋ ๋ฒ ๋์ค์ด์
๋ธํด ๋ดค๋ค. objdump ์ --start-address ๋ฅผ ํ ๋ฐ์ดํธ๋ง ์ฎ๊ธฐ๋ฉด ๋๋ค.
#!/usr/bin/env bash
# cap_decode.sh โ ๊ฐ์ 5๋ฐ์ดํธ๋ฅผ ๋ ๊ฐ์ง๋ก ๋์ฝ๋ฉํด ๋ณธ๋ค.
# objdump ๋ 0x1271 ๋ถํฐ ์ ํ์ผ๋ก ์ฝ๊ณ , CPU ๋ jmp ๋ฅผ ๋ฐ๋ผ 0x1272 ๋ถํฐ ์ฝ๋๋ค.
echo '# ์๋ณธ ๋ฐ์ดํธ (ํ์ผ ์คํ์
= ๊ฐ์์ฃผ์, .text ๋ 1:1 ๋งคํ)'
xxd -s 0x1271 -l 5 extracted/main
echo
echo '# objdump ์ ํ ์ค์์ด ๋ณธ ๊ฒ (0x1271 ๋ถํฐ)'
objdump -d -M intel --start-address=0x1271 --stop-address=0x1276 extracted/main | tail -3
echo
echo '# CPU ๊ฐ ์ค์ ๋ก ์คํํ๋ ๊ฒ (jmp ๋ชฉ์ ์ง 0x1272 ๋ถํฐ)'
objdump -d -M intel --start-address=0x1272 --stop-address=0x1276 extracted/main | tail -3./cap_decode.sh
0x1272 ๋ถํฐ ์ฝ์ผ๋ฉด ff c1 = inc ecx, ff c9 = dec ecx ๋ค. ํ๋ ์ฌ๋ฆฌ๊ณ ํ๋ ๋ด๋ฆฌ๋ ๊ฒฐ๊ณผ๋ ์๋ฌด ์ผ๋ ์ ํ ๊ฒ๊ณผ ๊ฐ๋ค. ์คํ ์๋ฏธ๋ก๋ ์๋ฒฝํ no-op ์ด๊ณ , ํ๋ฉด์๋ ์กด์ฌํ์ง ์๋ ๋ช
๋ น์ด ์ฐํ๋ค.
gdb ๋ก ํ ๋ฒ ๋ โ ์ ๋ง ๊ทธ ๊ธธ๋ก ๊ฐ๋๊ฐ
์ ์ ํด์์ ์ ์ ํด์์ผ ๋ฟ์ด๋ค. ์ค์ ๋ก CPU ๊ฐ 0x1272 ๋ฅผ ๋ฐ๋์ง ๋ฐํ์์ผ๋ก ํ์ธํ๋ค. PIE ๋ผ ๋ก๋ ๋ฒ ์ด์ค๋ฅผ ๊ด๋ก๋ก ๊ฐ์ ํ์ง ์๊ณ info proc mappings ์์ ์ง์ ์ฝ๋๋ค.
set pagination off
set confirm off
set disassembly-flavor intel
starti
python
import gdb
base = None
for line in gdb.execute("info proc mappings", to_string=True).splitlines():
f = line.split()
if len(f) >= 5 and f[-1].endswith("/main"):
base = int(f[0], 16)
break
print("PIE base (info proc mappings) = %#x" % base)
stub = base + 0x1271
print("\n[objdump ๊ฐ ๋ณธ ๊ฒ] 0x1271 ๋ถํฐ ์ ํ ํด์")
gdb.execute("x/2i %#x" % stub)
gdb.execute("break *%#x" % stub)
gdb.execute("continue")
print("\n[CPU ๊ฐ ์ค์ ๋ก ๋ฐ๋ ๊ฒ] stepi ๋ก ๋ฐ๋ผ๊ฐ๋ค")
for _ in range(3):
pc = int(gdb.parse_and_eval("$pc"))
ins = gdb.execute("x/i $pc", to_string=True).strip().replace("=> ", "")
rcx = int(gdb.parse_and_eval("$rcx")) & 0xFFFFFFFF
print(" +%#06x ecx=%#010x %s" % (pc - base, rcx, ins.split(":", 1)[1].strip()))
gdb.execute("stepi", to_string=True)
pc = int(gdb.parse_and_eval("$pc"))
ins = gdb.execute("x/i $pc", to_string=True).strip().replace("=> ", "")
print(" +%#06x %s <- ์คํ
์ ๋น ์ ธ๋์จ ์ฒซ ์ง์ง ๋ช
๋ น" % (pc - base, ins.split(":", 1)[1].strip()))
end
kill
quitgdb -q -batch -x stub.gdb --args ./extracted/main answer.bin
rip ๊ฐ +0x1271 โ +0x1272 โ +0x1274 โ +0x1276 ์ผ๋ก ๊ฐ๋ค. 0x1273 ์ ๋ฐํ์ง๋ ์๋๋ค. ecx ๊ฐ ...f0 โ ...f1 โ ...f0 ์ผ๋ก ์ค๋ฅด๋ด๋ฆฌ๋ ๊ฒ๊น์ง ๋ณด์ด๋, sar edi,0xc9 ๋ ํ์คํ ์ ๋ น์ด๋ค.
์ด๋ฐ ์คํ
์ด .text ์์ 97๊ฐ, 485๋ฐ์ดํธ ์๋ค. .text ํฌ๊ธฐ๊ฐ 1,868๋ฐ์ดํธ๋ ์ฝ๋์ 26%๊ฐ ์คํ๋์ง ์์ ๋ชฉ์ ์ผ๋ก ๋ค์ด๊ฐ ์
์ด๋ค.
๐งฐ ๋๊ตฌ๋ง๋ค ๋ค๋ฅด๊ฒ ์๋๋ค โ objdump vs radare2
์ฌ๊ธฐ๊ฐ ์ฌ๋ฏธ์๋ ์ง์ ์ด๋ค. ๋์ค์ด์ ๋ธ๋ฌ์ ์ ๋ต์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ ๋ค๋ฅด๊ฒ ๋ง๊ฐ์ง๋ค.
objdump ๋ ์ ํ ์ค์์ด๋ผ ์์์๋ถํฐ ์์๋๋ก ๊ธธ์ด๋ฅผ ์ฌ๋ฉฐ ์ฝ๋๋ค. ๊ทธ๋์ ์ ๋ น ๋ช
๋ น์ ๋ณด์ฌ ์ฃผ์ง๋ง, ์คํ
์ด ์ ํํ 5๋ฐ์ดํธ์ ๋ฑ ๋ง์ถฐ ๋๋๊ธฐ ๋๋ฌธ์ ๊ทธ ๋ค์ ์ง์ง ๋ช
๋ น๋ถํฐ๋ ๋ค์ ๋๊ธฐํ๋๋ค. ์ฆ ๋ก์ง ์์ฒด๋ ์ฝํ๋ค โ ๋
ธ์ด์ฆ๊ฐ 26% ์์์ ๋ฟ์ด๋ค.
radare2 ๋ ์ฌ๊ท ํ๊ฐ์ด๋ผ ๋ถ๊ธฐ๋ฅผ ๋ฐ๋ผ๊ฐ๋ค. ๊ทธ๋์ ์คํ
์ ์ ๋๋ก ํด์ํ์ง๋ง, ๋์ 5๋ฐ์ดํธ๋ง๋ค ๊ธฐ๋ณธ ๋ธ๋ก์ด ๊ฐ๋ผ์ ธ ์ ์ด ํ๋ฆ ๊ทธ๋ํ๊ฐ ์๊ฒ ๋ถ์์ง๋ค.
#!/usr/bin/env bash
# cap_r2.sh โ radare2 ๋ jmp ๋ฅผ ๋ฐ๋ผ๊ฐ๋ ์ฌ๊ท ํ๊ฐ์ด๋ผ ์คํ
์ "๋ง๊ฒ" ์ฝ๋๋ค.
# ๋์ 5๋ฐ์ดํธ๋ง๋ค ๊ธฐ๋ณธ๋ธ๋ก์ด ๊ฐ๋ผ์ ธ CFG ๊ฐ ์๊ฒ ๋ถ์์ง๋ค. ๊ทธ ์ฐจ์ด๋ฅผ ์์น๋ก ๋ณธ๋ค.
echo '### ์๋ณธ (์คํ
์ด์ ์์) โ 0x1269 ํจ์'
r2 -qq -e scr.color=0 -e log.level=0 -c 'aaa; s 0x1269; af; afi~num-bbs,edges,num-instrs' extracted/main
echo
echo '### deobf.py ๋ก NOP ํจ์นํ ๋ค ๊ฐ์ ํจ์'
r2 -qq -e scr.color=0 -e log.level=0 -c 'aaa; s 0x1269; af; afi~num-bbs,edges,num-instrs' extracted/main_clean
echo
echo '### ์๋ณธ ์๋จธ๋ฆฌ โ r2 ๊ฐ ํ์ํ ๊ฒน์นจ ํ์(~)๊ณผ CODE XREF'
r2 -qq -e scr.color=0 -e log.level=0 -c 'aaa; s 0x1269; af; pd 12' extracted/main./cap_r2.sh
๊ธฐ๋ณธ ๋ธ๋ก 25 โ 9, ์ฃ์ง 27 โ 11. ๊ฒจ์ฐ ์คํ
๋ช ๊ฐ ๋ ํจ์ ํ๋๊ฐ ์ด ์ ๋๋ค. r2 ๋ 0x1271 ์ค ์์ ~ ๋ฅผ ๋ถ์ฌ "์ด ๋ช
๋ น์ ๋ค๋ฅธ ๋ช
๋ น๊ณผ ๋ฐ์ดํธ๊ฐ ๊ฒน์น๋ค"๊ณ ํ์ํด ์ค๋ค.
๊ทธ๋ฆฌ๊ณ ํ ๊ตฐ๋ฐ๋ r2 ๋ ํ๋ฆฐ๋ค. 0x1286 ์ ์คํ
์ ๋ฐ๋ก ์ jmp 0x137a ๋๋ฌธ์ ์๋ฌด๋ ๋๋ฌํ์ง ์๋ ์๋ฆฌ๋ผ, ๋ถ๊ธฐ๋ฅผ ๋ฐ๋ผ์ฌ ๋ฐฉ๋ฒ์ด ์์ด r2 ์ญ์ ์ ํ์ผ๋ก ์ฝ์ด sar edi, 0xc9 ๋ฅผ ๊ทธ๋๋ก ๋ฑ๋๋ค. ์ฌ๊ท ํ๊ฐ๋ ๋ง๋ฅ์ ์๋๋ค.
๐ฉน ๋์ด๋ฆฌ๊ธฐ โ ๊ธธ์ด๋ฅผ ์ ์งํ ์ฑ ๋ฎ๋๋ค
ํด๊ฒฐ์ ๊ฐ๋จํ๋ค. ์คํ
5๋ฐ์ดํธ๋ฅผ nop 5๊ฐ๋ก ๋ฎ์ผ๋ฉด ๋๋ค.
๊ธธ์ด๋ฅผ ์ ์งํ๋ ๊ฒ ํต์ฌ์ด๋ค. ์ง์ฐ๋ฉด ์ ๋๋ค.
โถ๐ ์ฝ์ง 2 โ ๊ทธ๋ฅ ์ง์ฐ๋ฉด ๋์ง ์๋ (์ ๋๋ค)
์ฒ์์ ์คํ ์ ์ญ์ ํด ์ฝ๋๋ฅผ ์งง๊ฒ ๋ง๋ค ์๊ฐ์ ํ๋ค. ์ค์ ๋ก ํด ๋ณด๋ฉด ์ ์ ๋๋์ง ๋ฐ๋ก ๋์จ๋ค.
#!/usr/bin/env python3
"""์คํ
์ '์ง์์' ์์ ๋ฉด ์ ์ ๋๋์ง ์ง์ ํ์ธํ๋ ๋ฐ๋ก.
.text(0x1000~0x18d9) ์์ 5๋ฐ์ดํธ ์คํ
๋ง ์ญ์ ํ๊ณ , ์ค์ด๋ ๋งํผ ์น์
๋์ 0xcc ๋ก ์ฑ์
ํ์ผ ๋ ์ด์์์ ๊ทธ๋๋ก ๋๋ค. ๊ทธ๋๋ ๊ฒฐ๊ณผ๋ ๋ชป ์ด๋ค โ ๋ค์ชฝ ์ฝ๋๊ฐ ํต์งธ๋ก ์๋น๊ฒจ์ง๋๋ฐ
jmp/jle ์ ์คํผ๋๋๋ ํ์ผ์ ๋ฐํ ์๋ ๋ณ์๋ผ ๊ทธ๋๋ก ๋จ๊ธฐ ๋๋ฌธ์ด๋ค.
๊ธธ์ด๋ฅผ ์ ์งํ ์ฑ NOP ์ผ๋ก ๋ฎ์ด์ผ ํ๋ ์ด์ ๊ฐ ์ด๊ฒ์ด๋ค.
"""
from pathlib import Path
STUB = bytes.fromhex("ebffc1ffc9")
TEXT_LO, TEXT_HI = 0x1000, 0x18D9 # readelf -S ์ .init ~ .fini ๋
HERE = Path(__file__).resolve().parent
data = HERE.joinpath("extracted/main").read_bytes()
text = data[TEXT_LO:TEXT_HI]
shrunk = text.replace(STUB, b"")
removed = len(text) - len(shrunk)
patched = data[:TEXT_LO] + shrunk + b"\xcc" * removed + data[TEXT_HI:]
assert len(patched) == len(data)
dst = HERE / "extracted" / "main_deleted"
dst.write_bytes(patched)
print(f".text ์์ {removed} bytes ์ญ์ , ๊ฐ์ ํฌ๊ธฐ๋งํผ 0xcc ๋ก ๋ค๋ฅผ ์ฑ์ -> {dst.name}")
print(f"ํ์ผ ํฌ๊ธฐ {len(patched)} ์ ์ง (์น์
ํค๋ยทํ๋ก๊ทธ๋จ ํค๋๋ ๊ฑด๋๋ฆฌ์ง ์์๋ค)")python3 deobf_bad.py && objdump -d -M intel --start-address=0x1269 --stop-address=0x12b7 extracted/main_deleted | tail -n +7
๋ฌด์์ด ๊ฑด ๊ฒ๋ณด๊ธฐ์ ๋ฉ์ฉกํ๋ค๋ ์ ์ด๋ค. ์๋ถ๋ถ ๋ช
๋ น์ ์คํ๋ ค ๋ ๊นจ๋ํด ๋ณด์ธ๋ค. ๊ทธ๋ฐ๋ฐ lea rax,[rip+0x2d7c] ์ ์ฃผ์์ด # 4022 ๊ฐ ์๋๋ผ # 4013 ์ด๋ค. ๋ณ์ 0x2d7c ๋ ํ์ผ์ ๋ฐํ ์์๋ผ ๊ทธ๋๋ก์ธ๋ฐ ๋ช
๋ น์ด 15๋ฐ์ดํธ ์๋น๊ฒจ์ก์ผ๋, ์คํจ ์ฃผ์๊ฐ ๊ทธ๋งํผ ์ด๊ธ๋ ๊ฒ์ด๋ค.
์ด ์ํ๋ก ๋ถ์ํ์ผ๋ฉด ์๋ฑํ ์ฃผ์์์ ํ
์ด๋ธ์ ์ฝ๊ณ "๋ฐ์ดํฐ๊ฐ ์ด์ํ๋ฐ" ํ๋ฉฐ ํ์ฐธ์ ํค๋งธ์ ๊ฒ์ด๋ค. ์กฐ๊ธ ๋ ๋ค๋ก ๊ฐ๋ฉด rex.W / .byte 0x8d ์ฒ๋ผ ์์ ๋์ฝ๋ฉ์ด ๊นจ์ง๊ธฐ๋ ํ๋ค.
์ฐธ๊ณ ๋ก .text ๋ฐ๊น์ง ์ธ์ก์ ์ญ์ ํ๋ฉด ์น์
ํค๋ ์คํ์
์ด ๋ฐ๋ ค objdump ๊ฐ ์์ file format not recognized ๋ฅผ ๋ฑ๋๋ค. ๊ทธ๊ฑด ์คํ๋ ค ํฐ๊ฐ ๋์ ๋ซ๋ค. ์์ฒ๋ผ ์กฐ์ฉํ 15๋ฐ์ดํธ๋ง ์ด๊ธ๋๋ ์ชฝ์ด ํจ์ฌ ์ํํ๋ค.
๊ทธ๋์ ์ญ์ ๊ฐ ์๋๋ผ ๋ฎ์ด์ฐ๊ธฐ๋ค. ์๋ ๋ถ๊ธฐ ๋ณ์๊ฐ ์ ๋ถ ๊ทธ๋๋ก ์ด์ ์์ผ๋, NOP ์ผ๋ก ๋ฎ์ ์ฌ๋ณธ์ ๋ค์ objdump ์ ๋ฃ๊ธฐ๋ง ํ๋ฉด ์๋ ์ฝ๋๊ฐ ๋์จ๋ค.
#!/usr/bin/env python3
"""Vernichtet โ anti-disassembly ์คํ
์ ๊ฑฐ.
main ์ .text ์ ์ฒด์ 5๋ฐ์ดํธ ์ก์ `eb ff c1 ff c9` ๊ฐ ๋ฐํ ์๋ค.
eb ff jmp $+1 ; ์๊ธฐ ์์ ์ ๋ค์ ๋ฐ์ดํธ(=์คํผ๋๋ 0xff)๋ก ์ ํ
ff c1 inc ecx ; ์ ํํด์ ๋ค์ด์จ ๋ค ์ค์ ๋ก ์คํ๋๋ ๋ช
๋ น
ff c9 dec ecx ; ๋๋๋ฆฐ๋ค -> ์คํ ๊ฒฐ๊ณผ๋ no-op
์ ํ ์ค์ ๋์ค์ด์
๋ธ๋ฌ(objdump)๋ 0xeb 0xff ๋ฅผ ํ๋์ ๋ช
๋ น์ผ๋ก ์ฝ์ด ๋ฒ๋ ค
๊ทธ ๋ค 3๋ฐ์ดํธ๋ฅผ ์๋ฑํ ๋ช
๋ น(`sar edi,0xc9`)์ผ๋ก ํด์ํ๊ณ , ๊ทธ ์ค์ ๋ ฌ์ด
๋ฐ๋ก ๋ค ์ค์ ๋ช
๋ น๊น์ง ๊ฐ์๋จน๋๋ค.
๊ธธ์ด๋ฅผ ์ ์งํ ์ฑ NOP 5๊ฐ๋ก ๋ฎ์ผ๋ฉด ์๋ ๋ถ๊ธฐ ์คํ์
์ด ์ ๋ถ ๊ทธ๋๋ก ์ด์ ์์ด
๊ทธ๋ฅ ๋ค์ objdump ํ๋ฉด ์๋ ์ฝ๋๊ฐ ๋์จ๋ค.
"""
import sys
from pathlib import Path
STUB = bytes.fromhex("ebffc1ffc9") # jmp $+1 ; inc ecx ; dec ecx
NOPS = b"\x90" * len(STUB)
src = Path(sys.argv[1] if len(sys.argv) > 1 else "extracted/main")
dst = Path(sys.argv[2] if len(sys.argv) > 2 else "extracted/main_clean")
data = bytearray(src.read_bytes())
count = 0
i = 0
while True:
i = data.find(STUB, i)
if i < 0:
break
data[i:i + len(STUB)] = NOPS
count += 1
i += len(STUB)
dst.write_bytes(bytes(data))
dst.chmod(0o755)
print(f"patched {count} stubs ({count * len(STUB)} bytes) -> {dst}")python3 deobf.py && objdump -d -M intel --start-address=0x1269 --stop-address=0x12b6 extracted/main_clean | grep -v nop$
grep -v nop$ ๋ก NOP ์ค๋ง ๊ฑท์ด๋ด๋ฉด ์๋ ์ปดํ์ผ๋ฌ๊ฐ ๋ฝ์์ ์ฝ๋๊ฐ ๊ทธ๋๋ก ๋จ๋๋ค. ์ด์ ๋ถํฐ๋ ํ๋ฒํ ๋ฆฌ๋ฒ์ฑ์ด๋ค.
๐ ์ฒซ ๋ฒ์งธ ๊ฒ์ฌ โ .data ์ ๋ฐํ ํํธ ํ
์ด๋ธ
main ์ ์ ๋ต ํ์ผ์ ์ฝ์ด ๋ ํจ์์ ์ฐจ๋ก๋ก ๋๊ธด๋ค. ๋จผ์ 0x1269.
์ฃผ์๋ง ์๋ ์ด์
์ ๋์ด ์ํ๋ sed ๋ก ์ฃผ์์ ์น์๋ค. ์ค์ ์ถ๋ ฅ์ ์ค๋ช
๋ง ๋ถ์ด๋ ๊ฒ์ด๋ผ ํ๋ฉด์ ์ง์ง ๊ทธ๋๋ก๋ค.
s|.*2d7c.*|& <-- ํํธ ํ
์ด๋ธ 0x4022 = i๋ฒ์งธ ์ํธ๋ฆฌ์ "๊ฐ"|
s|.*2d45.*|& <-- 0x4021 = "ํ"|
s|.*2d0a.*|& <-- 0x4020 = "์ด"|
s|.*je 136b.*|& <-- ๊ฐ์ด 0 ์ด๋ฉด ํํธ ์์, ๋ค์ ์ํธ๋ฆฌ๋ก|
s|.*shl eax,0x4.*|& <-- ํ * 16|
s|.*sub eax,edx.*|& <-- ... - ํ = ํ * 15|
s|.*add eax,esi.*|& <-- ์ด + ํ*15 = ๊ฒฉ์ ์ธ๋ฑ์ค|
s|.*cmp sil,al.*|& <-- buf ์ ๊ทธ ์นธ์ด ํํธ ๊ฐ๊ณผ ๊ฐ์๊ฐ|
s|.*mov eax,0x0.*|& <-- ํ๋๋ผ๋ ํ๋ฆฌ๋ฉด ์ฆ์ 0 ๋ฐํ|
s|.*,0xe0.*|& <-- i <= 224, ์ฆ ์ํธ๋ฆฌ 225๊ฐ|objdump -d -M intel --start-address=0x1269 --stop-address=0x138e extracted/main_clean | grep -v nop$ | sed -f annot_clues.sed
shl eax,0x4 ํ๊ณ sub eax,edx ํ๋ ๊ฑด x*16 - x = x*15 ๋ค. ์ปดํ์ผ๋ฌ๊ฐ 15 ๊ณฑํ๊ธฐ๋ฅผ ์ด๋ ๊ฒ ์ชผ๊ฐ ๋ค. 15 ๋ผ๋ ์ซ์๊ฐ ์ฌ๊ธฐ์ ์ฒ์ ๋์จ๋ค.
์ ๋ฆฌํ๋ฉด ์ด๋ ๋ค.
// 0x1269 โ .data 0x4020 ๋ถํฐ (์ด, ํ, ๊ฐ) 3๋ฐ์ดํธ ร 225 ์ํธ๋ฆฌ
int check_clues(unsigned char *buf)
{
for (int i = 0; i <= 224; i++) {
unsigned char val = TBL[3*i + 2];
if (val == 0) // ๊ฐ 0 = ํํธ ์๋ ์๋ฆฌ
continue;
unsigned char row = TBL[3*i + 1];
unsigned char col = TBL[3*i + 0];
if (buf[col + 15*row] != val)
return 0;
}
return 1;
}.data ๋ 0x4000 ์์ ์์ํด ํฌ๊ธฐ๊ฐ 0x2c3 ์ด๋ค. ํ
์ด๋ธ์ 0x4020 ๋ถํฐ 3 ร 225 = 675 ๋ฐ์ดํธ๋ ์ ํํ 0x42c3 ์์ ๋๋๋ค. ์น์
๋๊ณผ ๋ฑ ๋ง๋ฌผ๋ฆฐ๋ค โ ์๋ฆฌ๋ฅผ ์ ๋๋ก ์ง์๋ค๋ ๋ป์ด๋ค.
๐ ๋ ๋ฒ์งธ ๊ฒ์ฌ โ ๊ฒฝ๋ก๊ฐ ์ด์ด์ง๋๊ฐ
๋ ๋ฒ์งธ ํจ์ 0x138e ๋ ํจ์ฌ ๊ธธ๋ค. ์ธ ํ ๋ง์ผ๋ก ๋๋ ๋ณธ๋ค. ๋จผ์ ์์์ ์ฐพ๊ธฐ.
s|.*QWORD PTR \[rbp-0x28\],rdi.*|& <-- ๋ต์ ๋ฒํผ 225๋ฐ์ดํธ|
s|.*BYTE PTR \[rbp-0x1b\],0x0.*|& <-- cx = 0 (ํ์ฌ ์ด)|
s|.*BYTE PTR \[rbp-0x1a\],0x0.*|& <-- cy = 0 (ํ์ฌ ํ)|
s|.*DWORD PTR \[rbp-0x14\],0x0.*|& <-- row = 0|
s|.*shl eax,0x4.*|& <-- row * 16|
s|.*sub eax,edx.*|& <-- ... - row = row * 15|
s|.*add eax,esi.*|& <-- row*15 + col|
s|.*cmp al,0x1.*|& <-- ์ด ์นธ์ ๊ฐ์ด 1 ์ธ๊ฐ|
s|.*BYTE PTR \[rbp-0x1b\],al.*|& <-- cx = col|
s|.*BYTE PTR \[rbp-0x1a\],al.*|& <-- cy = row|
s|.*\[rbp-0x10\],0xe$|& <-- col <= 14|
s|.*\[rbp-0x14\],0xe$|& <-- row <= 14|
s|.*\[rbp-0xc\],0x1$|& <-- k = 1 ๋ถํฐ ์์|objdump -d -M intel --start-address=0x138e --stop-address=0x1443 extracted/main_clean | grep -v nop$ | sed -f annot_path_find.sed
row ๋ col ๋ 0๋ถํฐ 14๊น์ง, ์ธ๋ฑ์ค๋ row*15 + col. 15ร15 ๊ฒฉ์๊ฐ ํ์ ๋๋ค. ๊ฐ์ด 1 ์ธ ์นธ์ ์ฐพ์ ํ์ฌ ์์น (cx, cy) ๋ก ์ก๋๋ค.
๊ทธ๋ค์์ด ๊ฒฝ๊ณ ํด๋จํ๋ค.
s|.*cmp BYTE PTR \[rbp-0x1b\],0x0.*|& <-- cx > 0 ์ธ๊ฐ|
s|.*cmp BYTE PTR \[rbp-0x1b\],0xd.*|& <-- cx <= 13 ์ธ๊ฐ|
s|.*cmp BYTE PTR \[rbp-0x1a\],0x0.*|& <-- cy > 0 ์ธ๊ฐ|
s|.*cmp BYTE PTR \[rbp-0x1a\],0xd.*|& <-- cy <= 13 ์ธ๊ฐ|
s|.*BYTE PTR \[rbp-0x19\],al.*|& <-- x0 = ์ผ์ชฝ ๊ฒฝ๊ณ|
s|.*BYTE PTR \[rbp-0x18\],al.*|& <-- x1 = ์ค๋ฅธ์ชฝ ๊ฒฝ๊ณ|
s|.*BYTE PTR \[rbp-0x17\],al.*|& <-- y0 = ์์ชฝ ๊ฒฝ๊ณ|
s|.*BYTE PTR \[rbp-0x16\],al.*|& <-- y1 = ์๋์ชฝ ๊ฒฝ๊ณ|
s|.*BYTE PTR \[rbp-0x15\],0x0.*|& <-- found = 0|
s|.*\[rbp-0xc\],0x1$|& <-- k = 1 ๋ถํฐ ์์|objdump -d -M intel --start-address=0x1443 --stop-address=0x14fd extracted/main_clean | grep -v nop$ | sed -f annot_path_clamp.sed
cx-1, cx+1, cy-1, cy+1 ์ ๊ฒฉ์ ์์ผ๋ก ๋๋ฌ ๋ด๋๋ค. ๋๊ฐ์ ๊น์ง ํฌํจํ 8๋ฐฉํฅ ์ด์์ ๋ง๋ค๋ ค๋ ๊ฒ์ด๋ค.
๋ง์ง๋ง์ด ์ค์ ํ์์ด๋ค.
s|.*movsx eax,BYTE PTR \[rbp-0x17\].*|& <-- r = y0 ๋ถํฐ|
s|.*movsx eax,BYTE PTR \[rbp-0x19\].*|& <-- c = x0 ๋ถํฐ|
s|.*movsx eax,BYTE PTR \[rbp-0x1a\].*|& <-- cy ์ ๋น๊ต|
s|.*movsx eax,BYTE PTR \[rbp-0x1b\].*|& <-- cx ์ ๋น๊ต (์๊ธฐ ์์ ์นธ์ ๊ฑด๋๋ด๋ค)|
s|.*shl eax,0x4.*|& <-- r * 16|
s|.*sub eax,edx.*|& <-- ... - r = r * 15|
s|.*add eax,esi.*|& <-- r*15 + c|
s|.*add eax,0x1$|& <-- ์ฐพ๋ ๊ฐ = k + 1|
s|.*cmp edx,eax$|& <-- ์ด ์ด์ ์นธ์ด k+1 ์ธ๊ฐ|
s|.*BYTE PTR \[rbp-0x15\],0x1.*|& <-- found = 1, ์ฌ๊ธฐ๋ก ์ด๋|
s|.*movsx eax,BYTE PTR \[rbp-0x18\].*|& <-- c <= x1|
s|.*movsx eax,BYTE PTR \[rbp-0x16\].*|& <-- r <= y1|
s|.*xor eax,0x1.*|& <-- ๋ชป ์ฐพ์์ผ๋ฉด|
s|.*mov eax,0x0.*|& <-- 0 ๋ฐํ (๊ฒฝ๋ก ๋๊น)|
s|.*,0xe0$|& <-- k <= 224, ์ฆ 225 ๊น์ง ์ด์ด์ ธ์ผ ํ๋ค|
s|.*mov eax,0x1.*|& <-- ๋๊น์ง ์ด์ด์ก๋ค|objdump -d -M intel --start-address=0x14fd --stop-address=0x15f5 extracted/main_clean | grep -v nop$ | sed -f annot_path_scan.sed
์ ์ฒด๋ฅผ C ๋ก ์ฎ๊ธฐ๋ฉด ์ด๋ ๊ฒ ๋๋ค.
// 0x138e
int check_path(unsigned char *buf)
{
signed char cx = 0, cy = 0;
for (int row = 0; row <= 14; row++) // ๊ฐ 1 ์ด ์๋ ์นธ์ ์ฐพ๋๋ค
for (int col = 0; col <= 14; col++)
if (buf[row*15 + col] == 1) { cx = col; cy = row; }
for (int k = 1; k <= 224; k++) {
signed char x0 = (cx > 0) ? cx - 1 : cx; // 8๋ฐฉํฅ ์ด์ ๋ฒ์
signed char x1 = (cx <= 13) ? cx + 1 : cx;
signed char y0 = (cy > 0) ? cy - 1 : cy;
signed char y1 = (cy <= 13) ? cy + 1 : cy;
char found = 0;
for (int r = y0; r <= y1; r++)
for (int c = x0; c <= x1; c++) {
if (r == cy && c == cx) continue; // ์๊ธฐ ์นธ์ ์ ์ธ
if (buf[r*15 + c] == k + 1) { found = 1; cx = c; cy = r; }
}
if (!found) return 0; // ๊ฒฝ๋ก๊ฐ ๋๊ธฐ๋ฉด ์คํจ
}
return 1;
}๊ทธ๋ฆฌ๊ณ main ์ด ์ด ๋์ ๋ฌถ๋๋ค.
// 0x15f5
int main(int argc, char **argv)
{
if (argc <= 1) { puts("Usage ./main <answer file>"); return 0; }
FILE *f = fopen(argv[1], "rb");
if (!f) { puts("File Not Found"); return 0; }
fseek(f, 0, SEEK_END);
long size = ftell(f);
rewind(f);
if (size != 0xe1) { puts("Wrong answer."); return 0; } // ์ ํํ 225 ๋ฐ์ดํธ
unsigned char *buf = malloc(size);
if (fread(buf, 1, size, f) != size) { puts("Fread failed"); return 0; }
if (!check_clues(buf)) { puts("Wrong answer."); return 0; }
if (!check_path(buf)) { puts("Wrong answer."); return 0; }
char cmd[0x110];
sprintf(cmd, "bash -c \"echo DH{$(sha256sum '%s' | awk '{print $1}')}\"", argv[1]);
puts("Correct!");
system(cmd);
return 0;
}ํ์ผ ํฌ๊ธฐ๊ฐ 0xe1 = 225 ๋ก ๋ชป ๋ฐํ ์๋ค. 15ร15 = 225 ์ด๊ณ , ์ฑ์์ผ ํ ๊ฐ์ 1๋ถํฐ 225 ๊น์ง. ํ ์นธ์ด ์ ํํ ํ ๋ฐ์ดํธ๊ณ 225 ๋ ํ ๋ฐ์ดํธ์ ๋ค์ด๊ฐ๋ค.
๐งฉ ์ ์ฒด โ ์ด๊ฑด Hidato ํผ์ฆ์ด๋ค
๋ ๊ฒ์ฌ๋ฅผ ํฉ์น๋ฉด ๊ท์น์ด ์ด๋ ๊ฒ ์ ๋ฆฌ๋๋ค.
- 15ร15 ๊ฒฉ์์ 1๋ถํฐ 225 ๊น์ง๋ฅผ ํ ๋ฒ์ฉ ๋๋๋ค
- ์ฐ์ํ ๋ ์๋ ์๋ก 8๋ฐฉํฅ(๋๊ฐ์ ํฌํจ) ์ด์์ด์ด์ผ ํ๋ค
- ์ผ๋ถ ์นธ์ ๊ฐ์ด ๋ฏธ๋ฆฌ ์ ํด์ ธ ์๋ค
Hidato ๋ค. ๋น์ทํ ํผ์ฆ๋ก Numbrix ๊ฐ ์๋๋ฐ ๊ทธ์ชฝ์ ์ํ์ข์ฐ 4๋ฐฉํฅ๋ง ํ์ฉํ๋ค. ์ฌ๊ธฐ๋ ๋๊ฐ์ ๋ ๋๋ Hidato ์ชฝ์ด๋ค.
.data ์์ ํํธ๋ฅผ ๊บผ๋ด ๊ฒฉ์๋ก ์ฐ์ด ๋ดค๋ค.
#!/usr/bin/env python3
"""Vernichtet โ .data ์ ๋ฐํ ํํธ ํ
์ด๋ธ์ 15x15 ๊ฒฉ์๋ก ๋ณต์ํ๋ค.
check_clues(0x1269) ๊ฐ ์ฝ๋ ๋ฐฐ์ด์ 0x4020 ๋ถํฐ 3๋ฐ์ดํธ์ฉ 225์ํธ๋ฆฌ:
b0 = data[0x4020 + 3*i] -> ์ด(column)
b1 = data[0x4021 + 3*i] -> ํ(row)
b2 = data[0x4022 + 3*i] -> ๊ทธ ์นธ์ ๋ค์ด๊ฐ์ผ ํ๋ ๊ฐ (0 ์ด๋ฉด ํํธ ์์)
๊ฒ์ฌ์์ buf[b0 + 15*b1] == b2.
.data ๋ vaddr 0x4000 = ํ์ผ ์คํ์
0x3000 ์ด๋ฏ๋ก 0x4020 -> 0x3020.
"""
from pathlib import Path
BIN = Path(__file__).with_name("extracted") / "main"
DATA_VADDR, DATA_OFF = 0x4000, 0x3000
TABLE = 0x4020
N = 15
raw = BIN.read_bytes()
tbl = raw[TABLE - DATA_VADDR + DATA_OFF: TABLE - DATA_VADDR + DATA_OFF + 3 * N * N]
assert len(tbl) == 675, len(tbl)
grid = [[0] * N for _ in range(N)]
clues = []
for i in range(N * N):
c, r, v = tbl[3 * i], tbl[3 * i + 1], tbl[3 * i + 2]
if v == 0:
continue
assert 0 <= c < N and 0 <= r < N, (i, c, r, v)
grid[r][c] = v
clues.append((r, c, v))
print(f"clue entries: {len(clues)} / {N*N}")
print(f"values: min={min(v for _,_,v in clues)} max={max(v for _,_,v in clues)}")
print()
for r in range(N):
print(" ".join(f"{grid[r][c]:3d}" if grid[r][c] else " ." for c in range(N)))python3 parse_clues.py
150์นธ์ด ์ด๋ฏธ ์ฑ์์ ธ ์๊ณ ๋น์นธ์ 75์นธ. ๊ฐ์ 1๋ถํฐ 225 ๊น์ง ๊ณ ๋ฅด๊ฒ ํฉ์ด์ ธ ์๋ค. ๊ฒฉ์ ๋ชจ์์ผ๋ก ์ฐ์ด ๋์ผ๋ ํผ์ฆ์ฑ ์์ ์ค๋ ค ๋ถ์ธ ๊ฒ์ฒ๋ผ ๋ณด์ธ๋ค.
โถ๐ ์ฝ์ง 3 โ objdump ๋ง ๋ณด๊ณ ๋ ์ฝํ์ ์ ๋ฆฌ๋ฅผ ๋ฏธ๋ค๋ค
์์งํ ๊ณ ๋ฐฑํ์๋ฉด, ์ ๋ก์ง์ NOP ํจ์น๋ฅผ ํ๊ธฐ ์ ์ ์๋ณธ objdump ์ถ๋ ฅ๋ง ๋ณด๊ณ ๋จผ์ ์ฝ์๋ค. ์คํ
์ด ์ ํํ 5๋ฐ์ดํธ์์ ๋๋ ๊ทธ ๋ค ๋ช
๋ น๋ถํฐ ๋ค์ ๋๊ธฐํ๋๊ธฐ ๋๋ฌธ์, sar edi,0xc9 ์ค๋ง ๋์ผ๋ก ๊ฑด๋๋ฐ๋ฉด ๋ก์ง ์์ฒด๋ ๋ฐ๋ผ๊ฐ ์ ์๋ค.
๊ทธ๋์ ํ๋์ "์ธ๋ฐ์๋ ๋ช
๋ น์ด ๋ง์ด ๋ผ์ด ์๋ค" ์ ๋๋ก๋ง ์๊ฐํ๊ณ ๋์ด๊ฐ๋ค. ๋ฌธ์ ๋ฅผ ์ค๊ฐํ ๊ฑด radare2 ๋ก ํจ์ ๊ทธ๋ํ๋ฅผ ๋ณด๋ ค ํ์ ๋๋ค โ 25๊ฐ์ง๋ฆฌ ๊ธฐ๋ณธ ๋ธ๋ก ์คํ๊ฒํฐ๊ฐ ๋์ค๋ ์๊ฐ ์ด๊ฒ ๋
ธ์ด์ฆ๊ฐ ์๋๋ผ ์๋๋ ์ฅ์น๋ผ๋ ๊ฒ ๋ถ๋ช
ํด์ก๋ค.
๊ตํ์ด๋ผ๋ฉด, "์ฝํ๋๊น ๊ด์ฐฎ๋ค"์ "์ ๋ฆฌํ๊ณ ์ฝ๋๋ค"๋ ๋ค๋ฅด๋ค๋ ๊ฒ. 686์ค์ง๋ฆฌ ์ถ๋ ฅ์์ 26%๋ฅผ ๋์ผ๋ก ๊ฑธ๋ฌ ๋ด๋ฉฐ ์ฝ๋ ๋์ ๋์น ๊ฒ ์์๋์ง๋ ์๋ฌด๋ ๋ณด์ฅ ๋ชป ํ๋ค. deobf.py ๋ ๋ค์ฏ ์ค์ง๋ฆฌ ๋ฃจํ์ธ๋ฐ ๊ทธ๊ฑธ ๋ฏธ๋ฃฌ ๊ฒ ์ด ๋ฌธ์ ์์ ์ ์ผ ๋นํจ์จ์ ์ธ ์ ํ์ด์๋ค.
๐ฏ ํ์ด โ DFS ์ ๋๋ฌ์ฑ ๊ฐ์ง์น๊ธฐ ํ๋
Hidato ๋ 1์์ ์ถ๋ฐํด ์ฌ์ฌ์ ๋ฐ๋ผ๊ฐ๋ ๋ฐฑํธ๋ํน์ผ๋ก ํผ๋ค. ๋ค์ ๊ฐ k+1 ์ ์๋ฆฌ๊ฐ ํํธ๋ก ์ด๋ฏธ ์ ํด์ ธ ์์ผ๋ฉด ์ธ์ ํ์ง๋ง ํ์ธํ๊ณ ๊ทธ ์นธ์ผ๋ก ๊ฑด๋๋ฐ๊ณ , ์ ์ ํด์ ธ ์์ผ๋ฉด ๋น ์ด์์ ํ๋์ฉ ์๋ํ๋ค.
์ฌ๊ธฐ์ ๊ฐ์ง์น๊ธฐ ํ๋๋ฅผ ์น์๋ค. ๋งค ์คํ ๋ง๋ค ํ์ฌ ์์น์์ flood fill ๋ก "๋จ์ ๋น์นธ ์ ๋ถ์ ๋ฟ์ ์ ์๋๊ฐ"๋ฅผ ๋ณธ๋ค. ์ฌ์ฌ์ด ๊ฒฉ์๋ฅผ ๋ ์กฐ๊ฐ์ผ๋ก ๊ฐ๋ผ ๋์ผ๋ฉด ๊ทธ ์๊ฐ ๋๋๋ฆฐ๋ค. ์ด๊ฒ ์์ผ๋ฉด ๋ง๋ค๋ฅธ ๊ณจ๋ชฉ์ ๋๊น์ง ํ๊ณ ๋ค์ด ์๊ฐ์ด ํญ๋ฐํ๋ค.
flag ๋ฅผ ์ค์ ๋ก ๋ฝ์๋ธ solve.py ์ ๋ฌธ์ด๋ค. ํํธ๋ ํ๋์ฝ๋ฉํ์ง ์๊ณ ๋งค๋ฒ ๋ฐ์ด๋๋ฆฌ์์ ์ง์ ์ฝ๋๋ค.
#!/usr/bin/env python3
"""Vernichtet (DreamHack, Gold 4 / reversing) โ ์์ฒด ์๊ฒฐ ์๋ฒ.
๋ฐ์ด๋๋ฆฌ๊ฐ ์๊ตฌํ๋ ๊ฒ:
1. ๋ต์ ํ์ผ ํฌ๊ธฐ๊ฐ ์ ํํ 0xe1 = 225 ๋ฐ์ดํธ
2. check_clues(0x1269): .data 0x4020 ๋ถํฐ์ (์ด,ํ,๊ฐ) 3๋ฐ์ดํธ ์ํธ๋ฆฌ 225๊ฐ ์ค
๊ฐ์ด 0 ์ด ์๋ 150๊ฐ์ ๋ํด buf[์ด + 15*ํ] == ๊ฐ
3. check_path(0x138e): ๊ฐ 1 ์ด ์๋ ์นธ์์ ์ถ๋ฐํด k = 1..224 ์ ๋ํด
๊ฐ k+1 ์ด ํ์ฌ ์นธ์ 8๋ฐฉํฅ ์ด์(ํน ๋ฌด๋ธ)์ ์์ด์ผ ํ๋ค
=> 15x15 ๊ฒฉ์์ 1..225 ๋ฅผ ํน ๋ฌด๋ธ๋ก ์ด์ด์ง๊ฒ ์ฑ์ฐ๋ Hidato ํผ์ฆ.
flag ๋ ๋ฐ์ด๋๋ฆฌ๊ฐ `sha256sum <๋ต์ํ์ผ>` ๋ก ๊ณ์ฐํด ์ถ๋ ฅํ๋ฏ๋ก
๋ต์ ํ์ผ์ ๋ฐ์ดํธ ๋ฐฐ์ด์ด ์ ํํ ์ผ์นํด์ผ ํ๋ค.
"""
import hashlib
import sys
from pathlib import Path
N = 15
HERE = Path(__file__).resolve().parent
BIN = HERE / "extracted" / "main"
OUT = HERE / "answer.bin"
DATA_VADDR, DATA_OFF, TABLE = 0x4000, 0x3000, 0x4020
def load_clues(path=BIN):
"""๋ฐ์ด๋๋ฆฌ .data ์์ ํํธ ํ
์ด๋ธ์ ์ง์ ์ฝ๋๋ค (ํ๋์ฝ๋ฉํ์ง ์๋๋ค)."""
raw = path.read_bytes()
base = TABLE - DATA_VADDR + DATA_OFF
tbl = raw[base:base + 3 * N * N]
grid = [[0] * N for _ in range(N)]
for i in range(N * N):
col, row, val = tbl[3 * i], tbl[3 * i + 1], tbl[3 * i + 2]
if val:
grid[row][col] = val
return grid
NB = [(dr, dc) for dr in (-1, 0, 1) for dc in (-1, 0, 1) if (dr, dc) != (0, 0)]
def solve(grid):
fixed = {} # ๊ฐ -> (ํ,์ด)
for r in range(N):
for c in range(N):
if grid[r][c]:
fixed[grid[r][c]] = (r, c)
board = [row[:] for row in grid]
start = fixed[1]
neigh = [[[] for _ in range(N)] for _ in range(N)]
for r in range(N):
for c in range(N):
for dr, dc in NB:
nr, nc = r + dr, c + dc
if 0 <= nr < N and 0 <= nc < N:
neigh[r][c].append((nr, nc))
def reachable(r, c, k):
"""k..225 ๋ฅผ ์ฑ์ธ ๋น ์นธ์ด ํ์ฌ ์์น์์ ์ ๋ถ ๋ฟ๋์ง (flood fill ๊ฐ์ง์น๊ธฐ)."""
need = sum(1 for rr in range(N) for cc in range(N) if board[rr][cc] == 0)
if need == 0:
return True
seen = set()
stack = [(r, c)]
got = 0
while stack:
cr, cc = stack.pop()
for nr, nc in neigh[cr][cc]:
if (nr, nc) in seen:
continue
v = board[nr][nc]
if v == 0:
seen.add((nr, nc))
got += 1
stack.append((nr, nc))
elif v > k:
seen.add((nr, nc))
stack.append((nr, nc))
return got == need
sys.setrecursionlimit(10000)
def dfs(k, r, c):
if k == N * N:
return True
nxt = k + 1
if nxt in fixed:
fr, fc = fixed[nxt]
if max(abs(fr - r), abs(fc - c)) != 1:
return False
return dfs(nxt, fr, fc)
cands = [(nr, nc) for nr, nc in neigh[r][c] if board[nr][nc] == 0]
for nr, nc in cands:
board[nr][nc] = nxt
if reachable(nr, nc, nxt) and dfs(nxt, nr, nc):
return True
board[nr][nc] = 0
return False
if not dfs(1, *start):
raise SystemExit("no solution")
return board
def verify(board, clues):
for r in range(N):
for c in range(N):
if clues[r][c] and board[r][c] != clues[r][c]:
return f"ํํธ ๋ถ์ผ์น ({r},{c})"
if sorted(v for row in board for v in row) != list(range(1, N * N + 1)):
return "1..225 ์์ด์ด ์๋๋ค"
pos = {}
for r in range(N):
for c in range(N):
pos[board[r][c]] = (r, c)
for k in range(1, N * N):
(r1, c1), (r2, c2) = pos[k], pos[k + 1]
if max(abs(r1 - r2), abs(c1 - c2)) != 1:
return f"{k} -> {k+1} ์ด ํน ๋ฌด๋ธ๊ฐ ์๋๋ค"
return None
def main():
clues = load_clues()
given = sum(1 for row in clues for v in row if v)
print(f"[*] .data 0x4020 ํํธ {given}๊ฐ / {N*N}์นธ")
board = solve(clues)
err = verify(board, clues)
if err:
raise SystemExit(f"[!] ๊ฒ์ฆ ์คํจ: {err}")
print("[*] ๊ฒ์ฆ ํต๊ณผ: ํํธ ์ผ์น + 1..225 ์์ด + ์ ๊ตฌ๊ฐ ํน ๋ฌด๋ธ")
print()
for r in range(N):
print(" ".join(f"{board[r][c]:3d}" for c in range(N)))
print()
blob = bytes(board[r][c] for r in range(N) for c in range(N))
assert len(blob) == 0xE1
OUT.write_bytes(blob)
digest = hashlib.sha256(blob).hexdigest()
print(f"[*] {OUT.name} ({len(blob)} bytes) ์ ์ฅ")
print(f"[*] sha256 = {digest}")
print(f"FLAG: DH{{{digest}}}")
if __name__ == "__main__":
main()python3 solve.py
0.12์ด. ํํธ๊ฐ 150๊ฐ๋ ์ฃผ์ด์ ธ ์์ด ํ์ ๊ณต๊ฐ์ด ๊ฑฐ์ ๋จ์ง ์๋๋ค.
๊ฒฝ๋ก๊ฐ ์ด๋ป๊ฒ ์ง๋๊ฐ๋์ง ๊ถ๊ธํด์ ๊ทธ๋ ค ๋ดค๋ค. ํฐ ์ซ์๊ฐ ์๋ ์ฃผ์ด์ ธ ์๋ ์นธ, ์ฃผํฉ ์ซ์๊ฐ ์ฐ๋ฆฌ๊ฐ ์ฑ์ด ์นธ์ด๋ค.
#!/usr/bin/env python3
"""answer.bin ์ 15x15 ๊ฒฉ์๋ก ๊ทธ๋ ค 1 -> 225 ๊ฒฝ๋ก๋ฅผ ์๊ฐํํ๋ค.
.data ํํธ๋ก ์ด๋ฏธ ์ ํด์ ธ ์๋ ์นธ(150๊ฐ)๊ณผ ์ฐ๋ฆฌ๊ฐ ์ฑ์ด ์นธ(75๊ฐ)์ ์์ผ๋ก ๊ตฌ๋ถํ๋ค.
SVG ๋ฅผ ๋ง๋ ๋ค rsvg-convert ๋ก PNG ๋ก ๊ตฝ๋๋ค.
"""
import subprocess
import sys
from pathlib import Path
from solve import N, load_clues
HERE = Path(__file__).resolve().parent
ANS = HERE / "answer.bin"
SVG = HERE / "hidato_path.svg"
PNG = Path(sys.argv[1]) if len(sys.argv) > 1 else HERE / "hidato_path.png"
CELL, PAD = 62, 34
W = H = CELL * N + PAD * 2
board = list(ANS.read_bytes())
clues = load_clues()
pos = {board[r * N + c]: (r, c) for r in range(N) for c in range(N)}
def cx(c):
return PAD + c * CELL + CELL / 2
def cy(r):
return PAD + r * CELL + CELL / 2
out = [
f'<?xml version="1.0" encoding="UTF-8"?>',
f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}">',
f'<rect width="{W}" height="{H}" fill="#0d1117"/>',
]
# ์นธ ๋ฐฐ๊ฒฝ: ํํธ๋ก ์ฃผ์ด์ง ์นธ์ ์ง์ ํ๋, ์ฐ๋ฆฌ๊ฐ ์ฑ์ด ์นธ์ ์ง์ ์ฃผํฉ
for r in range(N):
for c in range(N):
given = clues[r][c] != 0
fill = "#132033" if given else "#2a1a0d"
stroke = "#30363d"
out.append(f'<rect x="{PAD + c*CELL}" y="{PAD + r*CELL}" width="{CELL}" height="{CELL}" '
f'fill="{fill}" stroke="{stroke}" stroke-width="1"/>')
# 1 -> 225 ๊ฒฝ๋ก: ์งํ๋์ ๋ฐ๋ผ ํ๋ -> ์ฃผํฉ
for k in range(1, N * N):
(r1, c1), (r2, c2) = pos[k], pos[k + 1]
t = (k - 1) / (N * N - 2)
red = int(0x79 + (0xF0 - 0x79) * t)
green = int(0xC0 + (0x88 - 0xC0) * t)
blue = int(0xFF + (0x3E - 0xFF) * t)
out.append(f'<line x1="{cx(c1):.1f}" y1="{cy(r1):.1f}" x2="{cx(c2):.1f}" y2="{cy(r2):.1f}" '
f'stroke="rgb({red},{green},{blue})" stroke-width="5" stroke-linecap="round" opacity="0.85"/>')
# ์ซ์
for r in range(N):
for c in range(N):
v = board[r * N + c]
given = clues[r][c] != 0
color = "#e6edf3" if given else "#f0883e"
weight = "normal" if given else "bold"
out.append(f'<text x="{cx(c):.1f}" y="{cy(r)+6:.1f}" font-family="DejaVu Sans Mono, monospace" '
f'font-size="19" fill="{color}" font-weight="{weight}" text-anchor="middle">{v}</text>')
# ์์ยท๋ ํ์
for v, col in ((1, "#7ee787"), (N * N, "#f97583")):
r, c = pos[v]
out.append(f'<circle cx="{cx(c):.1f}" cy="{cy(r):.1f}" r="26" fill="none" stroke="{col}" stroke-width="3"/>')
out.append("</svg>")
SVG.write_text("\n".join(out), encoding="utf-8")
subprocess.run(["rsvg-convert", "-w", str(W), "-h", str(H), str(SVG), "-o", str(PNG)], check=True)
print(f"{SVG.name} -> {PNG} ({W}x{H})")
print(f"ํํธ๋ก ์ฃผ์ด์ง ์นธ {sum(1 for row in clues for v in row if v)}๊ฐ(ํฐ ๊ธ์จ) / "
f"์ฐ๋ฆฌ๊ฐ ์ฑ์ด ์นธ {sum(1 for row in clues for v in row if not v)}๊ฐ(์ฃผํฉ ๊ธ์จ)")python3 draw_path.py /home/jinho/homepage/public/images/blog/dreamhack-vernichtet-writeup/15_hidato_path.png
์ผ์ชฝ ์๋ ์ด๋ก ์์ด 1, ๊ฒฉ์ ํ๋ณตํ ์กฐ๊ธ ์ ๋ถํ ์์ด 225 ๋ค. ์ฌ์ฌ ํ๋๊ฐ 225์นธ์ ๋น ์ง์์ด ํ๊ณ ์ง๋๊ฐ๋ค.
๐ ๊ฒ์ฆ โ ๋ฐ์ด๋๋ฆฌ๊ฐ ์ง์ ํ๋๊ทธ๋ฅผ ๋ฑ๋๋ค
๋ง๋ ํ์ผ์ ๋ฌธ์ ๋ฐ์ด๋๋ฆฌ์ ๊ทธ๋๋ก ๋จน์ธ๋ค. ์ฐ๋ฆฌ๊ฐ ๊ณ์ฐํ sha256 ๊ณผ ๋ฐ์ด๋๋ฆฌ๊ฐ ์ถ๋ ฅํ ๊ฐ์ด ๊ฐ์์ผ ํ๋ค.
sha256sum answer.bin && ./extracted/main answer.bin
Correct! ์ ํจ๊ป ํ๋๊ทธ๊ฐ ๋์จ๋ค.
DH{e309147b588c517bb4100064d6185e5430ebad23d83e601327c4907bb0232292}ํ๋๊ทธ๊ฐ ๋ต์ ํ์ผ์ ํด์๋ผ๋ ๊ฑด, ํด๊ฐ ์ ์ผํ์ง ์์ผ๋ฉด ํ๋๊ทธ๋ ์ฌ๋ฌ ๊ฐ๊ฐ ๋๋ค๋ ๋ป์ด๋ค. ๊ทธ๋ด ๋ฆฌ๋ ์๊ฒ ์ง๋ง ํ์ธ์ ํด ๋ดค๋ค. ์ฒซ ํด์์ ๋ฉ์ถ์ง ์๊ณ ๋ฐฑํธ๋ํน์ ๋๊น์ง ๋๋ ค ์์ฑํด์ ๊ฐ์๋ฅผ ์ผ๋ค.
#!/usr/bin/env python3
"""ํํธ 150๊ฐ๋ก Hidato ํด๊ฐ ์ ์ผํ์ง ์ ์ ํ์ธ.
flag ๊ฐ ๋ต์ ํ์ผ์ sha256 ์ด๋ฏ๋ก, ํด๊ฐ ๋ ์ด์์ด๋ฉด flag ๋ ์ฌ๋ฌ ๊ฐ๊ฐ ๋์ด ๋ฒ๋ฆฐ๋ค.
(์ค์ ๋ก๋ ์ ์ผํด์ผ ์ ์) ๋ฐฑํธ๋ํน์ผ๋ก ํด๋ฅผ ๋๊น์ง ์ธ์ด ๋ณธ๋ค.
"""
import sys
from solve import N, NB, load_clues
def count(grid, limit=5):
fixed = {}
for r in range(N):
for c in range(N):
if grid[r][c]:
fixed[grid[r][c]] = (r, c)
board = [row[:] for row in grid]
neigh = [[[(r + dr, c + dc) for dr, dc in NB
if 0 <= r + dr < N and 0 <= c + dc < N] for c in range(N)] for r in range(N)]
found = 0
nodes = 0
def reachable(r, c, k):
need = sum(1 for rr in range(N) for cc in range(N) if board[rr][cc] == 0)
if need == 0:
return True
seen, stack, got = set(), [(r, c)], 0
while stack:
cr, cc = stack.pop()
for nr, nc in neigh[cr][cc]:
if (nr, nc) in seen:
continue
v = board[nr][nc]
if v == 0:
seen.add((nr, nc)); got += 1; stack.append((nr, nc))
elif v > k:
seen.add((nr, nc)); stack.append((nr, nc))
return got == need
def dfs(k, r, c):
nonlocal found, nodes
nodes += 1
if k == N * N:
found += 1
return found >= limit
nxt = k + 1
if nxt in fixed:
fr, fc = fixed[nxt]
if max(abs(fr - r), abs(fc - c)) != 1:
return False
return dfs(nxt, fr, fc)
for nr, nc in neigh[r][c]:
if board[nr][nc]:
continue
board[nr][nc] = nxt
if reachable(nr, nc, nxt) and dfs(nxt, nr, nc):
board[nr][nc] = 0
return True
board[nr][nc] = 0
return False
sys.setrecursionlimit(10000)
dfs(1, *fixed[1])
return found, nodes
if __name__ == "__main__":
n, nodes = count(load_clues())
print(f"ํ์ ๋
ธ๋ {nodes}๊ฐ, ์์ฑํด {n}๊ฐ")
print("=> ํด๊ฐ ์ ์ผํ๋ค" if n == 1 else "=> ํด๊ฐ ์ฌ๋ฌ ๊ฐ๋ค(!)")python3 count_solutions.py
๋ ธ๋ 9,948๊ฐ๋ฅผ ์ ๋ถ ํ์ด ์์ฑํด๋ ํ๋. ์ถ์ ์๊ฐ ํํธ 150๊ฐ๋ฅผ ์ ์ผํด๊ฐ ๋๋๋ก ๊ณจ๋ผ ๋์ ๊ฒ์ด๋ค.
์ฌํ ํ๊ฒฝ๊ณผ ํ ๋ฐฉ ์คํฌ๋ฆฝํธ
๋ฐฐํฌ๋ณธ ์์ถ์ extracted/ ์ ํ์ด ๋๊ณ ์๋ ์คํฌ๋ฆฝํธ ํ๋๋ง ๋๋ฆฌ๋ฉด ์คํ
์ ๊ฑฐ๋ถํฐ flag ๋์กฐ๊น์ง ๋๋๋ค. ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ ์ด๋ค.
#!/usr/bin/env bash
# Vernichtet (DreamHack Gold 4 / reversing) โ ํ ๋ฐฉ ์ฌํ.
# anti-disassembly ์คํ
์ ๊ฑฐ -> ํํธ ํ
์ด๋ธ ํ์ฑ -> Hidato ํ์ด -> ๋ต์ ํ์ผ -> flag ๋์กฐ
# ํ์ํ ๊ฒ: python3, objdump(์ ํ), ๋ฌธ์ ๋ฐฐํฌ๋ณธ extracted/main
set -eu
cd "$(dirname "$(readlink -f "$0")")"
EXPECT=$(python3 -c "import json;print(json.load(open('๋ฌธ์ .json'))['flag'])" 2>/dev/null || echo '')
[ -f extracted/main ] || { echo "โ extracted/main ์ด ์๋ค (challenge_2343.zip ์ ํ์ด๋ผ)"; exit 1; }
chmod +x extracted/main
# 1) ์ํฐ๋์ค์ด์
๋ธ๋ฆฌ ์คํ
97๊ฐ๋ฅผ NOP ์ผ๋ก ๋ฎ์ ์ฌ๋ณธ (๋ถ์์ฉ, ํ์ด์๋ ์์ด๋ ๋๋ค)
python3 deobf.py
# 2) ํํธ ํ
์ด๋ธ(.data 0x4020) ํ์ฑ + Hidato ํ์ด + answer.bin ์์ฑ
OUT=$(timeout 300 python3 solve.py 2>&1) || true
echo "$OUT" | tail -12
FLAG=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | head -1)
# 3) ๋ฌธ์ ๋ฐ์ด๋๋ฆฌ๋ก ์ง์ ์ฌํ์ธ โ Correct! ์ flag ๋ฅผ ๋ฐ์ด๋๋ฆฌ๊ฐ ์ค์ค๋ก ์ถ๋ ฅํ๋ค
echo
echo "--- ./extracted/main answer.bin ---"
BINOUT=$(./extracted/main answer.bin 2>&1 || true)
echo "$BINOUT"
BINFLAG=$(printf '%s' "$BINOUT" | grep -aoE 'DH\{[^}]+\}' | head -1)
echo
if [ -z "$FLAG" ] || [ "$FLAG" != "$BINFLAG" ]; then
echo "โ FAIL solve.py='$FLAG' / binary='$BINFLAG'"; exit 1
fi
if [ -n "$EXPECT" ] && [ "$FLAG" != "$EXPECT" ]; then
echo "โ FAIL ๊ธฐ๋ '$EXPECT' / ์ป์ ๊ฐ '$FLAG'"; exit 1
fi
echo "โ
PASS $FLAG"๋๋ฆฐ ํ๊ฒฝ์ ์ด๋ ๋ค. ๊ธ์ ๋์จ ์ฃผ์ยท๋ฐ์ดํธ๋ ๋ฐฐํฌ๋ณธ ๊ณ ์ ๊ฐ์ด๋ผ ๋ฒ์ ์ด ๋ฌ๋ผ๋ ๊ทธ๋๋ก์ง๋ง, objdump ์ถ๋ ฅ ํฌ๋งท๊ณผ r2 ์ afi ํ๋ ์ด๋ฆ์ ๋ฒ์ ์ ํ๋ค.
| ํญ๋ชฉ | ๋ฒ์ |
|---|---|
| OS / ์ปค๋ | Ubuntu 25.10 ยท 6.17.0-41-generic |
| Python | 3.13.7 (ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง) |
| objdump | GNU Binutils 2.45 |
| gdb | 16.3 |
| radare2 | 5.9.8 |
๐ ๋ค โ ํ์ผ ์ด๋ฆ์ผ๋ก ๋ช ๋ น์ด ๋ค์ด๊ฐ๋ค
ํ๋๊ทธ๋ฅผ ๋ง๋๋ ๊ทธ sprintf ๋ฅผ ๋ค์ ๋ณด์.
sprintf(cmd, "bash -c \"echo DH{$(sha256sum '%s' | awk '{print $1}')}\"", argv[1]);
system(cmd);argv[1] ์ด ์์๋ฐ์ดํ ์์ ๊ทธ๋๋ก ๋ค์ด๊ฐ๋ค. ์ด์ค์ผ์ดํ๊ฐ ์๋ค. ํ์ผ ์ด๋ฆ์ ์์๋ฐ์ดํ๋ฅผ ๋ฃ์ด ๋ฐ์ดํ๋ฅผ ๋ซ์ ๋ฒ๋ฆฌ๋ฉด ๊ทธ ๋ค๋ ์
ธ ๋ช
๋ น์ด ๋๋ค.
์ ๋ต ํ์ผ์ ๊ทธ๋ฐ ์ด๋ฆ์ผ๋ก ๋ณต์ฌํด ๋๊ณ ๋๋ ค ๋ดค๋ค.
ls injtest/ && ./extracted/main "injtest/x'; id -un; echo '"
ํ๋๊ทธ ์๋ฆฌ์ jinho ๊ฐ ์ฐํ๋ค. id -un ์ด ์ค์ ๋ก ๋์๊ฐ ๊ฒ์ด๋ค.
์ด ๋ฌธ์ ์์๋ ์๋ฌด ์ด๋์ด ์๋ค. ์ฑ์ ๋๋ ํ๋๊ทธ๋ ์ ๋ต ํ์ผ์ ํด์๋ก ์ด๋ฏธ ์ ํด์ ธ ์์ผ๋ ๋ช
๋ น์ ๋ฃ์ด ๋ด์ผ ๋ค๋ฅธ ๊ฐ์ด ๋์ฌ ๋ฟ์ด๋ค. ๋ค๋ง ๊ฐ์ ์ฝ๋๊ฐ setuid ๋ฐ์ด๋๋ฆฌ๋ ์๋ฒ ํ๋ก์ธ์ค์๋ค๋ฉด ์ด๊ฑด ๊ทธ๋๋ก RCE ๋ค. sprintf ๋ก ์
ธ ๋ช
๋ น์ ์กฐ๋ฆฝํ๊ณ system ์ ๋๊ธฐ๋ ํจํด์ CTF ๋ฐ์์ ๋ง๋๋ฉด ๋ฐ๋ก ์ทจ์ฝ์ ์ด๋ค.
๐ ๊ฒฐ๋ก
"ํ๊ดด๋๋ค"๋ ๋ง์ ํ์ผ์ด ์๋๋ผ ๋๊ตฌ๋ฅผ ๋๊ณ ํ ๋ง์ด์๋ค
ELF ๋ ํ ๊ตฐ๋ฐ๋ ์์๋์ง ์์๋ค. ์น์
ํค๋๋, ํ๋ก๊ทธ๋จ ํค๋๋, ์ฌ์ง์ด ์ฝ๋์ ์๋ฏธ์กฐ์ฐจ ๊ทธ๋๋ก๋ค. ๋ถ์์ง ๊ฑด objdump ๊ฐ ํ๋ฉด์ ๊ทธ๋ ค ์ฃผ๋ ๊ทธ๋ฆผ๋ฟ์ด๋ค.
x86 ์ ๊ฐ๋ณ ๊ธธ์ด ๋ช
๋ น์ด๋ "๋ช
๋ น์ ์์"์ด๋ผ๋ ๊ฒ ๋ฐ์ดํธ ์์ฒด์ ์ ์ ํ ์๋ค. ์ด๋์๋ถํฐ ์ฝ๋๋๋ฅผ ์ ํ๋ ๊ฑด ์ฝ๋ ์ชฝ์ด๊ณ , jmp $+1 ํ๋๋ฉด ๊ทธ ํ๋จ์ ๊ฐ๋ผ๋์ ์ ์๋ค.
๋๊ตฌ๊ฐ ํ๋ฆฌ๋ ๋ฐฉ์์ด ๋๊ตฌ๋ง๋ค ๋ค๋ฅด๋ค
objdump ๋ ์ ํ์ด๋ผ ์ ๋ น ๋ช
๋ น์ ๋ณด์ฌ ์ฃผ๊ณ , radare2 ๋ ์ฌ๊ท ํ๊ฐ์ด๋ผ ๋ช
๋ น์ ๋ง๊ฒ ์ฝ์ง๋ง CFG ๊ฐ 25๊ฐ ๋ธ๋ก์ผ๋ก ๋ถ์์ง๋ค. ๊ทธ๋ฆฌ๊ณ ๋๋ฌ ๋ถ๊ฐ๋ฅํ ์๋ฆฌ์์๋ r2 ๋ ์ ํ์ผ๋ก ๋์๊ฐ ๋๊ฐ์ด ํ๋ฆฐ๋ค.
๊ฐ์ ๋ฐ์ด๋๋ฆฌ๋ฅผ ๋ ๋๊ตฌ์ ๋ฃ์ด ๋ณด๋ ๊ฒ ์ ์ต๊ด์ด ๋์ด์ผ ํ๋์ง ๋ณด์ฌ ์ฃผ๋ ์๋ค. ๋์ ๊ฒฐ๊ณผ๊ฐ ์ด๊ธ๋๋ ์ง์ ์ด ๊ณง "์ฌ๊ธฐ์ ๋ฌด์จ ์ฅ๋์ ์ณค๋ค"๋ ํ์๋ค.
ํจ์น๋ ์ง์ฐ์ง ๋ง๊ณ ๋ฎ์ด๋ผ
๋ฐ์ดํธ๋ฅผ ์ญ์ ํ๋ฉด ์๋ ๋ถ๊ธฐ ๋ณ์๊ฐ ์ ๋ถ ์ด๊ธ๋๋ค. ๊ทธ๋ฐ๋ฐ ๊ทธ ์คํจ๊ฐ ์๋ํ๊ฒ ํฐ์ง์ง ์๋๋ค๋ ๊ฒ ์ง์ง ํจ์ ์ด๋ค. ๋ช
๋ น์ ์ฌ์ ํ ๊ทธ๋ด์ธํ๊ฒ ๋์ฝ๋ฉ๋๊ณ , lea ๊ฐ ๊ฐ๋ฆฌํค๋ ์ฃผ์๋ง 15๋ฐ์ดํธ์ฉ ๋ฐ๋ฆฐ๋ค. ๋ฐ์ดํฐ ํ
์ด๋ธ์ ์๋ฑํ ๋ฐ์ ์ฝ์ผ๋ฉด์ "๊ฐ์ด ์ด์ํ๋ฐ" ํ๊ณ ๋ช ์๊ฐ์ ํ์ธ ์ ์๋ค. ๊ธธ์ด๋ฅผ ์ ์งํ๋ NOP ๋ฎ์ด์ฐ๊ธฐ๋ ๊ทธ๋์ ์์ ํ๋ค.
๋ฐฉ์ด ๊ด์ ์์
์ํฐ ๋์ค์ด์
๋ธ๋ฆฌ๋ ๋ถ์์ ๋ฆ์ถ ๋ฟ ๋ง์ง๋ ๋ชปํ๋ค. ์ฌ๊ธฐ ์ฐ์ธ ์คํ
์ ํจํด์ด ์์ ํ ๊ณ ์ ๋ผ ์์ด์ 5์ค์ง๋ฆฌ bytes.replace ํ๋๋ก ์ ๋ถ ๊ฑทํ๋ค. ์์ฉ ๋ณดํธ์์ ์ด๋ฐ ๊ธฐ๋ฒ์ ์ด๋ค๋ฉด ์ต์ํ ํจํด์ ๋งค๋ฒ ๋ค๋ฅด๊ฒ ์์ฑํด์ผ ์๋ฏธ๊ฐ ์๊ณ , ๊ทธ๋๋ ๋์ ๋ถ์ ์์์๋ stepi ๋ช ๋ฒ์ด๋ฉด ๋๋ฌ๋๋ค.
์ ์ ์ด ๋ฐ์ด๋๋ฆฌ์์ ์ค์ ๋ณด์ ๋ฌธ์ ๊ฐ ๋ ๋งํ ๊ณณ์ ๋๋
ํ๊ฐ ์๋๋ผ ๋ง์ง๋ง์ sprintf + system ์ด์๋ค. ์ฌ์ฉ์ ์
๋ ฅ์ ์
ธ ๋ฌธ์์ด์ ๋ผ์ ๋ฃ์ง ๋ง๊ณ , ๊ผญ ์ธ๋ถ ๋ช
๋ น์ ์จ์ผ ํ๋ฉด execve ๊ณ์ด๋ก ์ธ์๋ฅผ ๋ฐฐ์ด์งธ ๋๊ธฐ๋ ๊ฒ ๋ง๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ