[๐ฑ ์์น] ํ ๋ฐ์ดํธ์ ๋๋ธ์ ๋ง๋ฐ๊พธ๋ ๊ฒ์ฆ โ DreamHack rev-basic-4 ํ์ด โ ZINO2026-06-08ยท1๋ถ ์ฝ๊ธฐยท
rev-basic-3์ด XORยท์ฐ์ ์ ๋ผ์ ๋ค๋ฉด 4๋ฒ์ ๋นํธ๋ก ๋
ผ๋ค. ๊ฐ ๊ธ์์ ์์ 4๋นํธ์ ํ์ 4๋นํธ๋ฅผ ๋ง๋ฐ๊พผ(๋๋ธ ์ค์) ๊ฐ์ด .data ํ
์ด๋ธ๊ณผ ๊ฐ์์ผ ํต๊ณผํ๋ค. ๋๋ธ ์ค์์ ๋ ๋ฒ ํ๋ฉด ์ ์๋ฆฌ๋ผ ํ
์ด๋ธ์ ํ ๋ฒ ๋ ์ค์ํ๋ฉด ๊ทธ๋๋ก ์ ๋ต์ด ๋์จ๋ค.
๋ฌธ์ : DreamHack โ rev-basic-4 (Reversing Basic Challenge #4)
๋ถ๋ฅ: Reversing
๋์ด๋: ๐ฑ ์์น
FLAG: DH{Br1ll1ant_bit_dr1bble_<<_>>}
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|
| ๋ฌธ์ ๋ช
| rev-basic-4 |
| ๋์ด๋ | ๐ฑ ์์น |
| ๋ถ๋ฅ | Reversing |
| ์ ๊ณต ํ์ผ | chall4.exe (PE32+ x86-64, Windows ์ฝ์) |
| ํต์ฌ ๊ธฐ๋ฒ | ๊ธ์๋ง๋ค ์ยทํ์ ๋๋ธ(4๋นํธ)์ ๋ง๋ฐ๊พผ ๊ฐ๊ณผ ๋น๊ต โ ์๊ธฐ์ญ์์ด๋ผ ํ ๋ฒ ๋ ์ค์ |
rev-basic-3์ด cmp ์ง์ ์ XOR๊ณผ ๋ง์
์ ๋ผ์ ๋ค๋ฉด, 4๋ฒ์ ๊ฐ์ ์๋ฆฌ์์ ๋นํธ๋ก ๋
ผ๋ค. input[i]์ ์์ชฝ 4๋นํธ์ ์๋์ชฝ 4๋นํธ๋ฅผ ๋ง๋ฐ๊พผ(๋๋ธ ์ค์) ๊ฐ์ด .data์ ํ
์ด๋ธ๊ณผ ๊ฐ์์ผ ํต๊ณผํ๋ค.
๋ณํ์ด ์ฐ์ ์์ ๋นํธ ์ฐ์ฐ์ผ๋ก ๋ฐ๋์์ ๋ฟ, ์ฑ์ง์ 3๋ฒ๊ณผ ๊ฐ๋ค. ๋๋ธ ์ค์์ ๋ ๋ฒ ํ๋ฉด ์๋๋๋ก ๋์์ค๋ ์๊ธฐ์ญ์์ด๋ผ, ํ
์ด๋ธ์ ํ ๋ฒ๋ง ๋ ์ค์ํ๋ฉด ์ ๋ต์ด ๊ทธ๋๋ก ๋์จ๋ค.
file chall4.exe && strings -a chall4.exe | grep
์ด ๊ธ์ด ๋์์ด ๋๋์?
-iE
"Input|Correct|Wrong|256s"
๊ธฐ๋ณธ ์ ๋ณด โ PE32+ x86-64 ์ฝ์ ๋ฐ์ด๋๋ฆฌ / strings์ InputยทCorrectยทWrong
๐ฌ ๋ถ์
fileยทstrings๋ ์๋ฆฌ์ฆ ๋ด๋ด ๊ฐ๋ค. Input : ๋ก ์
๋ ฅ์ ๋ฐ์(%256s) 0x140001000(check)์ผ๋ก ๊ฒ์ฆํ๊ณ Correct / Wrong์ผ๋ก ๊ฐ๋ฆฐ๋ค. check๋ ์ธ๋ฑ์ค i๋ฅผ 0๋ถํฐ ์ฌ๋ฆฌ๋ ๋ฃจํ ํ๋๋ค.
; check(input) @ 0x140001000
mov dword [rsp], 0 ; i = 0
.loop:
movsxd rax, [rsp] ; rax = i
cmp rax, 0x1c ; i < 28 ? (0x1c = 28)
jae .ok ; 28๊ธ์ ๋ค ํต๊ณผ โ return 1
mov rcx, [rsp+0x20] ; ์
๋ ฅ ๋ฒํผ
movzx eax, byte [rcx+rax] ; eax = input[i]
sar eax, 4 ; eax = input[i] >> 4 โ ์์ ๋๋ธ์ ์๋๋ก
mov rdx, [rsp+0x20]
movzx ecx, byte [rdx+rax] ; ecx = input[i] (๋ค์ ์ฝ์)
shl ecx, 4 ; ecx = input[i] << 4
and ecx, 0xf0 ; ecx = (input[i] << 4) & 0xf0 โ ํ์ ๋๋ธ์ ์๋ก
or eax, ecx ; eax = ์ค์๋ ๋ฐ์ดํธ
lea rdx, [0x140003000] ; ์ ๋ต ํ
์ด๋ธ
movzx ecx, byte [rdx+rax] ; ecx = table[i]
cmp eax, ecx ; table[i] == swap(input[i]) ?
je .next
xor eax, eax ; ํ๋ฆฌ๋ฉด return 0 โ Wrong
jmp .ret
.next:
inc dword [rsp] ; i++
jmp .loop
.ok:
mov eax, 1 ; ์ ๋ถ ํต๊ณผ โ return 1 โ Correct
.ret:
ret
์ธ ์ค์ด ํต์ฌ์ด๋ค. sar eax, 4๋ก ์์ 4๋นํธ๋ฅผ ์๋๋ก ๋ด๋ฆฌ๊ณ , shl ecx, 4 + and ecx, 0xf0์ผ๋ก ํ์ 4๋นํธ๋ฅผ ์๋ก ์ฌ๋ฆฐ ๋ค or๋ก ํฉ์น๋ค. ๊ฒฐ๊ณผ๋ input[i]์ ๋๋ธ ๋ ๊ฐ๋ฅผ ๋ง๋ฐ๊พผ ๊ฐ์ด๋ค.
sar(์ฐ์ ์ํํธ)์ ์ผ์ง๋ง input[i]๋ movzx๋ก 0~255๋ผ 32๋นํธ ๋ถํธ๋นํธ๊ฐ 0์ด๋ค. ๊ทธ๋์ sar์ด shr์ฒ๋ผ ๋์ํด ์์ชฝ์ 0๋ง ์ฑ์์ง๋ค โ ๊ฒฐ๊ตญ ์์ํ ๋๋ธ ์ค์์ด๋ค. ๋ฃจํ ์ํ์ 0x1c(28)์ด๋ผ 28์นธ์ ๊ฒ์ฌํ๋ค.
objdump -d -M intel --start-address=0x140001000 --stop-address=0x14000106d chall4.exe | grep -E "^140001"
objdump โ check ํจ์. sar 4 / shl 4 / and 0xf0 / or / cmp
Ghidra ๋์ปดํ์ผ๋ฌ(analyzeHeadless)๋ก ๋ณด๋ฉด ๋๋ธ ์ค์์ด ํ ์์ผ๋ก ๋๋ฌ๋๋ค โ (input[i] >> 4 | (input[i] & 0xf) << 4) != DAT_140003000[i]์ด๋ฉด ๋ถ์ผ์น๋ก ๋๋๋ค.
Ghidra headless ๋์ปดํ์ผ โ check() ๋ฃจํ๊ฐ ์
๋ ฅ ๋ฐ์ดํธ์ ์ยทํ์ ๋๋ธ์ ๋ง๋ฐ๊ฟ DAT_140003000[i] ํ
์ด๋ธ๊ณผ ๋น๊ต
์ ๋ฆฌํ๋ฉด ๋ชจ๋ i์ ๋ํด ์ด ์์ด ์ฑ๋ฆฝํด์ผ ํ๋ค.
table[i] == (input[i] >> 4) | ((input[i] << 4) & 0xf0)
๐ฃ ํต์ฌ โ ๋๋ธ ์ค์์ ์๊ธฐ์ญ์
๋๋ธ ์ค์์ ๋ ๋ฒ ํ๋ฉด ์ ์๋ฆฌ๋ก ๋์์จ๋ค. ์์๋๋ฅผ ๋ง๋ฐ๊พผ ๊ฑธ ๋ค์ ๋ง๋ฐ๊พธ๋ฉด ์๋ ์๋ฆฌ๋๊น. ๊ทธ๋ฌ๋ ๊ฒ์ฆ์์ ์ญ์ฐ์ฐ๋ ๋๊ฐ์ ์ค์์ด๋ค.
table[i] == swap(input[i])
swap(table[i]) == swap(swap(input[i])) == input[i]
์ฆ input[i] = swap(table[i]) โ ํ
์ด๋ธ ๋ฐ์ดํธ๋ฅผ ํ ๋ฒ ๋ ์ค์ํ๋ฉด ์ ๋ต์ด๋ค.
ํ ๋ฐ์ดํธ์ ๋๋ธ์ ๋ง๋ฐ๊พธ๋ ๋ณํ๊ณผ ๊ทธ ์ญ์ฐ
์๋ฅผ ๋ค์ด 'B'(0x42)๋ ์์ ๋๋ธ 0x4, ํ์ ๋๋ธ 0x2๋ค. ์ค์ํ๋ฉด 0x24๊ฐ ๋๊ณ , ์ด๊ฒ ํ
์ด๋ธ ์ฒซ ๋ฐ์ดํธ๋ค. ๊ฑฐ๊พธ๋ก ํ
์ด๋ธ์ 0x24๋ฅผ ์ค์ํ๋ฉด ๋ค์ 0x42 = 'B'๋ก ๋์์จ๋ค.
๐ฏ ํ์ด โ ํ
์ด๋ธ 28๋ฐ์ดํธ๋ฅผ ๋ ์ ์ค์
0x140003000์ .data์์ ๋ ๋ณด๋ฉด 28๋ฐ์ดํธ๊ฐ ๋์ด์ ์๋ค.
objdump -s -j .data chall4.exe | sed -n "1,7p"
objdump -s -j .data โ ์ ๋ต ํ
์ด๋ธ 28๋ฐ์ดํธ @ 0x140003000
140003000 24 27 13 c6 c6 13 16 e6 47 f5 26 96 47 f5 46 27
140003010 13 26 26 c6 56 f5 c3 c3 f5 e3 e3 00
์ด 28๋ฐ์ดํธ๊ฐ table[i]๋ค. ๊ฐ ๋ฐ์ดํธ๋ฅผ ํ ๋ฒ ๋ ์ค์ํ๋ฉด ์ ๋ต ๋ฌธ์์ด์ด ๋์จ๋ค. ๋ง์ง๋ง ์นธ 0x00์ ์ค์ํด๋ 0x00์ด๋ผ ์
๋ ฅ์ ๋ ์ข
๋จ๊ณผ ๋ง์๋จ์ด์ง๋ค โ ์ค์ ์ ๋ต์ ์ 27๊ธ์๋ค.
#!/usr/bin/env python3
# rev-basic-4 solve
# check(): for i in 0..27: table[i] == (input[i] >> 4) | ((input[i] << 4) & 0xf0)
# == ๋๋ธ ์ค์(์์4๋นํธ <-> ํ์4๋นํธ)
# ๋๋ธ ์ค์์ ์๊ธฐ์ญ์ โ input[i] = nibbleswap(table[i])
table = bytes.fromhex(
"24 27 13 c6 c6 13 16 e6 47 f5 26 96 47 f5 46 27"
"13 26 26 c6 56 f5 c3 c3 f5 e3 e3 00".replace(" ", "")
)
def swap(b):
return ((b >> 4) | ((b << 4) & 0xF0)) & 0xFF
raw = bytes(swap(t) for t in table)
flag = raw.rstrip(b"\x00").decode() # ๋ง์ง๋ง ๋ฐ์ดํธ(0x00)๋ ์
๋ ฅ์ NUL ์ข
๋จ
print("table len :", len(table))
print("recovered :", raw)
print("flag :", flag)
print("DH{%s}" % flag)
# check() ๋ก์ง๋๋ก ์๊ฐ๊ฒ์ฆ
ok = all(table[i] == swap(raw[i]) for i in range(len(table)))
print("verify :", "PASS" if ok else "FAIL")
solve.py โ ํ
์ด๋ธ์ ์ค์ํด flag ๋ณต์, ์๊ฐ๊ฒ์ฆ PASS
๋ณต์๋ ์
๋ ฅ๊ฐ์ Br1ll1ant_bit_dr1bble_<<_>>. ๋์ << >>๊ฐ ์ํํธ ์ฐ์ฐ์ ๊ฐ๋ฆฌํค๋ ๋๋ด์ด๋ค. ์ค์ ๋ฐ์ด๋๋ฆฌ์ ๋ฃ์ด ํ์ธํ๋ค. ๋ฆฌ๋
์ค๋ผ wine์ผ๋ก ๋๋ ธ๋ค.
# verify.py
import subprocess, os
env = dict(os.environ,
WINEPREFIX=os.path.expanduser("~/.wine_rev"),
WINEDEBUG="-all",
DISPLAY="") # wine์ ์์ ํค๋๋ฆฌ์ค๋ก
flag = "Br1ll1ant_bit_dr1bble_<<_>>" # DH{} ๋ ์ ์ถ์ฉ ๋ํผ์ผ ๋ฟ
r = subprocess.run(
["wine", "chall4.exe"],
input=flag + "\n",
capture_output=True, text=True, timeout=60, env=env,
)
print("Input :", flag)
print("Output:", r.stdout.strip())
verify.py โ wine์ผ๋ก ์ค์ ์คํ, Output์ Correct
Correct. ํ๋๊ทธ๋ DH{Br1ll1ant_bit_dr1bble_<<_>>}.
๐ ์ ๋ฆฌ
์ฐ์ฐ์ด ๋ฐ๋์ด๋ ๊ฐ์ญ์ด๋ฉด ํธ๋ ๋ฒ์ ๊ฐ๋ค.
3๋ฒ์ XOR๊ณผ ๋ง์
, 4๋ฒ์ ๋๋ธ ์ค์์ด๋ค. ๋ผ์ด๋ ์ฐ์ฐ๋ง ๋ฌ๋ผ์ก์ง, "๋น๊ต ์ง์ ๋ณํ์ ์ ํํ ์ฝ๊ณ ๊ฑฐ๊พธ๋ก ๋๋ฆฐ๋ค"๋ ํ๋ฆ์ ๊ทธ๋๋ก๋ค. sar/shl/and/or ๋ค ์ค์ ๋นํธ ๋จ์๋ก ๋ฐ๋ผ๊ฐ๋ฉด ๊ฒฐ๊ตญ ๋๋ธ ๋ ๊ฐ๋ฅผ ๋ง๋ฐ๊พธ๋ ๊ฒ๋ฟ์ด๋ค.
XOR๊ณผ ๋๋ธ ์ค์์ ๋ ๋ค ์๊ธฐ์ญ์์ด๋ค.
a ^ k ^ k == a, swap(swap(a)) == a โ ๊ฐ์ ์ฐ์ฐ์ ๋ ๋ฒ ์ ์ฉํ๋ฉด ์ ์๋ฆฌ๋ก ๋์์จ๋ค. ๊ทธ๋์ ์ญ์ฐ ์ฝ๋๊ฐ ๊ฒ์ฆ ์ฝ๋์ ๋๊ฐ์ด ์๊ธด๋ค(๊ฐ์ swap ํจ์๋ฅผ ํ
์ด๋ธ์ ํ ๋ฒ ๋ ์ ์ฉ). ์ด๋ฐ ๋์นญ์ ์์๋ณด๋ฉด ์ญ์ฐ ์์ ๋ฐ๋ก ์ ๋ํ ํ์๋ ์๋ค. rev-basic ๋จ๊ณ์ ๋ณํ์ ์ฌ๊ธฐ๊น์ง โ ์ ๋ถ ํ ์ค๋ก ๋๋๋ฆด ์ ์๋ ๊ฐ์ญ ์ฐ์ฐ์ด๊ณ , ๋น๊ฐ์ญ(ํด์ ๋ฑ)์ผ๋ก ๋์ด๊ฐ๋ ์๊ฐ๋ถํฐ๊ฐ ๋ค์ ๋จ๊ณ๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ