๋ฌธ์ : DreamHack โ Branches and Leaves ๋ถ๋ฅ: reversing ๋์ด๋: ๐ฅ Silver 3 FLAG:
DH{d8f794325872bab95cfaa117545c6c0b77059a74f4dd31d8c2bf130a59745d77}
๋ฌธ์ ์ค๋ช ์ ํํธ๊ฐ ์๋ค. ์ ๋ค ์ค์ด ์ ๋ถ๋ค.
Branches in twilight, a silhouette's embrace, Leaves in the moonlight, a shimmering grace.
๊ฐ์ง์ ์์ฌ๊ท. ๋์ค์ ๋ณด๋ ์ด๊ฒ ํํธ์ด๊ธด ํ๋๋ฐ, ์ฒ์ ๋ฐ์์ ๋ ๊ทธ๋ฅ ๋ถ์๊ธฐ ๊ธ์ธ ์ค ์์๋ค.
๋ฐ์ ํ์ผ์ main ํ๋. 2,111,704๋ฐ์ดํธ์ง๋ฆฌ stripped PIE ELF๋ค. ์ด ํฌ๊ธฐ๋ฉด ๋ณดํต Go๋ Rust๋ก ์ ์ ๋งํฌํ ๋ฌผ๊ฑด์ ์์ฌํ๋๋ฐ, ์ฌ๋ณผ ํ
์ด๋ธ์ ์ด๋ฉด ๊ทธ๊ฒ ์๋๋ผ๋ ๊ฒ ๋ฐ๋ก ๋๋ฌ๋๋ค.
file extracted/main; readelf -W --dyn-syms extracted/main
์ํฌํธ๊ฐ strlen, __printf_chk, exit ๊ทธ๋ฆฌ๊ณ ์์ยท์ข
๋ฃ ํ
๊น์ง ๋ค์ฏ ๊ฐ๋ค. 2MB๋ฅผ ์ฐจ์งํ๋ ๊ฒ ์ฝ๋์ผ ๋ฆฌ๊ฐ ์๋ค.
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | Branches and Leaves |
| ๋์ด๋ | ๐ฅ Silver 3 |
| ๋ถ๋ฅ | reversing |
| ์ ๊ณต ํ์ผ | main (2,111,704B, stripped PIE, x86-64) |
| ์๋ฒ | ์์ โ ํ์ผ๋ง ์ค๋ค |
| ํต์ฌ ๊ธฐ๋ฒ | ๋ฐฐ์ด๋ก ์ ์ ๊น์ด 16 ์ด์งํธ๋ฆฌ + ๋ฆฌํ๊ฐ ์ ๋จ์ฌ๋ผ ์ญ์์ด ์ ์ผ |
ํ๋ก๊ทธ๋จ์ ์ธ์ ํ๋๋ฅผ ๋ฐ์ ๊ฒ์ฆํ๊ณ , ํต๊ณผํ๋ฉด ๊ทธ ์ธ์๋ฅผ ๊ทธ๋๋ก ํ๋๊ทธ ์์์ ๋ผ์ ์ถ๋ ฅํ๋ค. ์ฆ ์ ๋ ฅ์ด ๊ณง ํ๋๊ทธ๋ค. 64๊ธ์๋ฅผ ๋งํ๋ฉด ๋์ด๋ค.
๐ฌ ์ ์ฐฐ โ ์ฝ๋๊ฐ 509๋ฐ์ดํธ๋ค
์น์ ํฌ๊ธฐ๋ถํฐ ๋ณธ๋ค. ํ์ผ์ด 2MB์ธ๋ฐ ์ด๋๊ฐ 2MB์ธ์ง ์์์ผ ํ๋ค.
ls -l extracted/main; readelf -S -W extracted/main | grep -E "\[Nr\]|\.text|\.rodata|\.data|\.bss"
.text ๊ฐ 0x1fd, 509๋ฐ์ดํธ๋ค. .rodata ๋ 36๋ฐ์ดํธ. ๊ทธ๋ฆฌ๊ณ .data ๊ฐ 0x200060, 2,097,248๋ฐ์ดํธ.
ํ์ผ์ 99% ์ด์์ด ๋ฐ์ดํฐ๋ค. ๋ก์ง์ 500๋ฐ์ดํธ ์์ ๋ค ๋ค์ด ์๊ณ ๋๋จธ์ง๋ ์ ๋ถ ๊ทธ ๋ก์ง์ด ์ฝ์ ํ๋ผ๋ ๋ป์ด๋ค. ์ด ์์ ์์ ๋์ปดํ์ผ๋ฌ๋ฅผ ๋ถ์ผ ํ์๊ฐ ์๋ค๋ ๊ฒ๋ ์ ํด์ง๋ค โ 509๋ฐ์ดํธ๋ฉด ์์ผ๋ก ์ฝ๋ ๊ฒ ๋น ๋ฅด๋ค.
๋ฌธ์์ด๋ ๋ง์ฐฌ๊ฐ์ง๋ก ์งง๋ค.
objdump -s -j .rodata extracted/main; strings -a -n 8 extracted/main | grep -aE "use|DH"
How to use: %s <input> ๊ณผ ํ๋๊ทธ ์์ ํ๋. ์์์ด ์
๋ ฅ์ ๊ทธ๋๋ก ๋ฐ์ ์ฐ๋ ํํ๋ผ, ํ๋๊ทธ๊ฐ ํ์ผ ์ด๋๊ฐ์ ์ํธํ๋ผ ์จ์ด ์๋ ๊ฒ ์๋๋ค. ๊ฒ์ฆ์ ํต๊ณผํ๋ ์
๋ ฅ ์์ฒด๊ฐ ๋ต์ด๋ค.
๐งฉ ๋ฐฐ๊ฒฝ โ ๋ฐฐ์ด๋ก ์ ์ ์ด์งํธ๋ฆฌ
ํธ๋ฆฌ๋ฅผ ํฌ์ธํฐ๋ก ๋ง๋ค๋ฉด ๋ ธ๋๋ง๋ค ์ฃผ์๊ฐ ํ์ํ๊ณ , ๊ทธ ์ฃผ์๋ PIE์์ ์ฌ๋ฐฐ์น ๋์์ด ๋๋ค. ๊ทธ๋์ ํ๋ก ๊ตณํ ๋ฃ์ ๋ ๋ณดํต ๋ฐฐ์ด๋ก ์ ๋๋ค.
๋
ธ๋์ ๋ฒํธ๋ฅผ ๋งค๊ธฐ๊ณ ์์ = ํ[2 * ๋
ธ๋๋ฒํธ + ๋ฐฉํฅ] ์ผ๋ก ์์ธํ๋ฉด ํฌ์ธํฐ๊ฐ ํ์ ์๋ค. ํ(heap)์ ๋ฐฐ์ด๋ก ๋ค๋ฃจ๋ ๋ฐฉ์๊ณผ ๊ฐ์ ์ฐฉ์์ธ๋ฐ, ๋ค๋ฅธ ์ ์ ๋ฒํธ๋ฅผ ์์๋๋ก ์ ์ค๋ ๋๋ค๋ ๊ฒ์ด๋ค. ์๋ฌด ๋ฒํธ๋ ์ฃผ๊ณ ํ๋ฅผ ๊ทธ๋งํผ ํฌ๊ฒ ์ก์ผ๋ฉด ๊ทธ๋ง์ด๋ค.
์ด ๋ฌธ์ ์ ํ๋ ๊ทธ๋ ๊ฒ ๋ง๋ค์ด์ก๋ค. ๋ค์์ ์ธ์ด ๋ณด๊ฒ ์ง๋ง, ์ค์ ๋ก ์ฐ๋ ๋ ธ๋๋ 65,535๊ฐ์ธ๋ฐ ๋ฒํธ๋ 262,139๋ฒ๊น์ง ํฉ์ด์ ธ ์๋ค. ๊ทธ ์ฐจ์ด๊ฐ ๊ณง ํ์ผ ํฌ๊ธฐ๋ค.
๐ฃ ๊ฒ์ฆ ํจ์ โ ๋ฃจํ ๋ ๊ฐ๊ฐ ์ ๋ถ
main ์ ์ธ์ ๊ฐ์๋ฅผ ์ธ๊ณ 0x11e0 ์ ํจ์๋ฅผ ๋ถ๋ฅธ ๋ค ์์์ ์ถ๋ ฅํ๋ ๊ฒ ์ ๋ถ๋ค. ํ์ ์ ์ ๋ถ ๊ทธ ํจ์ ์์ ์๋ค.
์ฝ๊ธฐ ํธํ๊ฒ objdump ์ถ๋ ฅ์ ์ฃผ์์ ์น์๋ค. ์นํ ๊ท์น์ ์์
ํด๋์ annot.sed ์ ๊ทธ๋๋ก ์๋ค.
s|\(cmp rax,0x40\)|\1 <-- ์
๋ ฅ์ ์ ํํ 64๊ธ์|
s|\(lea r10,\[rip+0x2e14\]\).*|\1 <-- 0x4020 expected[16]|
s|\(lea rdi,\[rip+0x2e5b\]\).*|\1 <-- 0x4060 tree[524288]|
s|\(shl edx,0x4\)|\1 <-- ๋๋ธ์ ์์์๋ถํฐ ์๋๋ค|
s|\(cmp sil,0x9\)|\1 <-- '0'-'9' ์ธ๊ฐ|
s|\(cmp sil,0x5\)|\1 <-- 'a'-'f' ์ธ๊ฐ (๋๋ฌธ์ ๋ถ๊ฐ)|
s|\(mov ecx,0x10\)|\1 <-- ํธ๋ฆฌ๋ฅผ 16๋ฒ ๋ด๋ ค๊ฐ๋ค|
s|\(sar edx,1\)|\1 <-- ๋ค์ ๋นํธ๋ก (LSB ๋ถํฐ)|
s|\(and esi,0x1\)|\1 <-- ์ด๋ฒ ๋นํธ|
s|\(lea rax,\[rsi+rax\*2\]\)|\1 <-- ์์ ์์น = 2*node + bit|
s|\(movsxd rax,DWORD PTR \[rdi+rax\*4\]\)|\1 <-- node = tree[...]|
s|\(cmp eax,0xffffffff\)|\1 <-- -1 ์ ๋ง๋ค๋ฅธ ๊ฐ์ง|
s|\(cmp eax,0x3ffff\)|\1 <-- ๋ด๋ถ๋
ธ๋ ์ํ 262143|
s|\(cmp eax,DWORD PTR \[r10+rdx\*4\]\)|\1 <-- 16์คํ
๋ค ๊ฐ == expected[i] ?|
s|\(cmp r9d,0x40\)|\1 <-- 16๋ฉ์ด๋ฆฌ๋ฅผ ๋ค ๋ดค๋|์์ชฝ ์ ๋ฐ๋ถํฐ.
objdump -d --start-address=0x11e0 --stop-address=0x1240 -M intel extracted/main | sed -f annot.sed
์ฒซ ๊ด๋ฌธ์ ๊ธธ์ด๋ค. strlen ๊ฒฐ๊ณผ๋ฅผ 0x40 ๊ณผ ๋น๊ตํ๋ ์ ํํ 64๊ธ์.
๊ทธ๋ค์ r8 ์ rbx+4 ๋ก ์ก๊ณ 4๊ธ์์ฉ ๋๋๋ค. shl edx,0x4 ๋ค์์ add edx,esi โ ์ ํ์ ์ธ 16์ง์ ํ์๋ค. ์ซ์๋ -0x30, ๊ทธ๊ฒ ์๋๋ฉด 0x1280 ์ผ๋ก ํ์ด์ -0x57 ์ ๋บ๋ค. ๋๋ฌธ์ ๋ถ๊ธฐ๋ ์์ ์๋ค. 'A' - 'a' ๋ ์์๊ณ ๊ทธ ๋น๊ต๊ฐ unsigned(ja)๋ผ ๊ทธ๋๋ก ํ๊ธด๋ค.
C๋ก ์ฎ๊ธฐ๋ฉด ์ด๋ ๋ค.
unsigned edx = 0;
for (char *c = chunk; c < chunk + 4; c++) {
edx <<= 4;
if ((unsigned char)(*c - '0') <= 9) edx += *c - '0';
else if ((unsigned char)(*c - 'a') <= 5) edx += *c - 'a' + 10;
else exit(1);
}์ ๋ ฅ์ ์๋ฌธ์ 16์ง์ 64๊ธ์์ด๊ณ , 4๊ธ์๋ง๋ค 16๋นํธ ์ ์๊ฐ ํ๋์ฉ ๋์จ๋ค. ์ด 16๊ฐ.
์ด์ ๋ค์ชฝ ์ ๋ฐ.
objdump -d --start-address=0x1240 --stop-address=0x129d -M intel extracted/main | sed -f annot.sed
mov ecx,0x10 ์ด ๋ฐ๋ณต ํ์ 16์ด๋ค. ๋ณธ์ฒด๋ ๋ค์ฏ ์ค๋ฟ์ด๋ค.
mov esi,edx ; esi = ์ง๊ธ ๊ฐ
sar edx,1 ; ๊ฐ์ ์ค๋ฅธ์ชฝ์ผ๋ก ํ ์นธ
and esi,0x1 ; ๋ฐฉ๊ธ ๋ฐ๋ ค๋ ์๋ฆฌ์ ๋นํธ
lea rax,[rsi+rax*2] ; ์์ธ = 2 * node + bit
movsxd rax,[rdi+rax*4] ; node = tree[์์ธ]rdi ๋ .data+0x60(ํ), r10 ์ .data+0x20(๊ธฐ๋๊ฐ)์ด๋ค. ๋นํธ๋ฅผ LSB๋ถํฐ ํ๋์ฉ ๊บผ๋ด ํ๋ฅผ ๋ฐ๋ผ ๋ด๋ ค๊ฐ๋ค. ์์ ๋ฐฐ๊ฒฝ ๊ทธ๋๋ก๋ค.
๋ฃจํ ์๋จธ๋ฆฌ์ ๋ ๊ฒ์ฌ๊ฐ ๊ฒฝ๊ณ๋ค. ๊ฐ์ด -1 ์ด๋ฉด ์ฃฝ๊ณ , 0x3ffff ๋ฅผ ๋์ด๋ ์ฃฝ๋๋ค. ์ฆ ๋ด๋ถ ๋
ธ๋ ๋ฒํธ์ ์ํ์ด 262,143์ด๋ค. ์ด ๊ฒ์ฌ๋ ๋งค ๋ฐ๋ณต์ ์์์ ์์ผ๋ฏ๋ก ๋ง์ง๋ง 16๋ฒ์งธ๋ก ์ฝ์ ๊ฐ์๋ ์ ๊ฑธ๋ฆฐ๋ค. ๊ทธ ๊ฐ์ด ๊ณง ๋ฆฌํ๋ค.
16์คํ
์ด ๋๋๋ฉด cmp eax,[r10+rdx*4] ๋ก r9d/4 ๋ฒ์งธ ๊ธฐ๋๊ฐ๊ณผ ๋ง์ถ๋ค. 16๋ฉ์ด๋ฆฌ๋ฅผ ์ ๋ถ ํต๊ณผํด์ผ ret ์ด๋ค.
์ ์ฒด๋ฅผ C๋ก ์ฎ๊ธฐ๋ฉด ์ด๋งํผ์ด๋ค.
for (int i = 0; i < 16; i++) {
unsigned v = parse4(input + i * 4);
int node = 0;
for (int s = 0; s < 16; s++) {
if (s && (node == -1 || node > 0x3ffff)) exit(1);
node = tree[2 * node + (v & 1)];
v >>= 1;
}
if (node != expected[i]) exit(1);
}์ฌ๊ธฐ์ ๊ฒฐ์ ์ ์ธ ๊ฒ ํ๋ ์๋ค. ๋ฉ์ด๋ฆฌ 16๊ฐ๊ฐ ์๋ก ์๋ฌด ์๊ด์ด ์๋ค. ์ ๋ฉ์ด๋ฆฌ์ ์ํ๊ฐ ๋ค๋ก ์ด์ด์ง์ง ์๊ณ , ๋งค๋ฒ node = 0 ์์ ๋ค์ ์์ํ๋ค.
๊ทธ๋์ ํ์ ๊ณต๊ฐ์ 16์ 64์น์ด ์๋๋ผ 2์ 16์น์ง๋ฆฌ ๋ฌธ์ 16๊ฐ๋ค. ์ด๊ฑธ ์์์ฑ๋ฉด ๋ฌธ์ ๊ฐ ์ฌ์ค์ ๋๋๋ค.
๐ฒ ํ๋ฅผ ์ด์ด ๋ณธ๋ค
๊ธฐ๋๊ฐ 16๊ฐ์ ํ ์๋จธ๋ฆฌ๋ฅผ ์ง์ ๊บผ๋๋ค.
#!/usr/bin/env python3
"""main ์ .data ์์ ๋ ํ๋ฅผ ๊บผ๋ด ๋ณธ๋ค: expected[16] ๊ณผ tree[524288] ์ ์๋จธ๋ฆฌ."""
import struct
from pathlib import Path
blob = (Path(__file__).with_name("extracted") / "main").read_bytes()
off = lambda va: va - 0x4000 + 0x3000 # .data vaddr 0x4000 -> file 0x3000
expected = struct.unpack("<16i", blob[off(0x4020):off(0x4020) + 64])
print("expected[16] @ .data+0x20")
for i in range(0, 16, 4):
print(" [%2d..%2d] %s" % (i, i + 3, " ".join("0x%08x" % (x & 0xFFFFFFFF) for x in expected[i:i + 4])))
head = struct.unpack("<32i", blob[off(0x4060):off(0x4060) + 128])
print("\ntree[0..31] @ .data+0x60 (2*node+bit ๋ก ์์ธ, -1 ์ ๋น ์ฌ๋กฏ)")
for i in range(0, 32, 8):
print(" [%2d..%2d] %s" % (i, i + 7, " ".join("%9d" % x for x in head[i:i + 8])))
print("\n๋ฃจํธ์ ๋ ์์ = tree[0]=%d, tree[1]=%d" % (head[0], head[1]))
print("ํ์ผ ์ ์ฒด์์ 0xff ๋ฐ์ดํธ ๋น์จ = %.1f%% (๋น ์ฌ๋กฏ -1 ์ด ๊ทธ๋งํผ ๋ง๋ค)"
% (100 * blob.count(0xFF) / len(blob)))python3 dump_tables.py
-1 ์ด ๋์ ๋ฐํ๋ค. ์ 32์นธ ์ค ์ ๋ฐ ์ด์์ด -1 ์ด๊ณ , ํ์ผ ์ ์ฒด ๋ฐ์ดํธ์ 74.5%๊ฐ 0xff ๋ค. ํ๊ฐ ์ฑ๊ธฐ๊ฒ ์ฐจ ์๋ค๋ ๋ป์ธ๋ฐ, ์ผ๋ง๋ ์ฑ๊ธด์ง๋ ์ธ์ด ๋ด์ผ ํ๋ค.
๋ฃจํธ์์ ์ค์ ๋ก ๋ฟ๋ ๋ ธ๋๋ง ๊น์ด๋ณ๋ก ์ธ๋ ์คํฌ๋ฆฝํธ๋ฅผ ๋ง๋ค์๋ค.
#!/usr/bin/env python3
"""Branches and Leaves โ .data ์ ๋ฐํ ํ๊ฐ ์ ๋ง 'ํธ๋ฆฌ'์ธ์ง ์ธ์ด ๋ณธ๋ค."""
import struct
from collections import Counter
from pathlib import Path
BIN = Path(__file__).with_name("extracted") / "main"
DATA_VADDR, DATA_OFF = 0x4000, 0x3000
TREE_VA, TREE_N = 0x4060, (0x204060 - 0x4060) // 4
NODE_MAX = 0x3FFFF
blob = BIN.read_bytes()
o = TREE_VA - DATA_VADDR + DATA_OFF
tree = struct.unpack("<%di" % TREE_N, blob[o:o + TREE_N * 4])
print("[table] ์ํธ๋ฆฌ %d๊ฐ = %.1f MB" % (TREE_N, TREE_N * 4 / 1048576))
print("[table] ๊ฐ ๋ฒ์ min=%d max=%d" % (min(tree), max(tree)))
print("[table] ์์ ์ํธ๋ฆฌ %d๊ฐ, ๊ทธ์ค -1 ์ด %d๊ฐ"
% (sum(1 for x in tree if x < 0), sum(1 for x in tree if x == -1)))
print("[table] ๋ด๋ถ๋
ธ๋๋ก ์ธ ์ ์๋ ๊ฐ(0..0x3ffff) %d๊ฐ" % sum(1 for x in tree if 0 <= x <= NODE_MAX))
# ๋ฃจํธ 0 ์์ ์ค์ ๋ก ๋ฟ๋ ๋
ธ๋๋ง ๊น์ด๋ณ๋ก ์ผ๋ค
level, reach, leaves = {0}, set(), []
for depth in range(16):
nxt = set()
for n in level:
reach.add(n)
for bit in (0, 1):
child = tree[2 * n + bit]
if depth == 15:
leaves.append(child)
else:
nxt.add(child)
print("[walk ] depth %2d: ๋
ธ๋ %6d๊ฐ -> ์์ %6d๊ฐ" % (depth, len(level), len(nxt)))
level = nxt
used = len(reach) * 2
print("[reach] ์ค์ ๋ก ์ฝํ๋ ๋ด๋ถ๋
ธ๋ %d๊ฐ (์ํธ๋ฆฌ %d๊ฐ)" % (len(reach), used))
print("[reach] ํ ๋ฒ๋ ์ ์ฝํ๋ ์ํธ๋ฆฌ %d๊ฐ = %.1f%% ๊ฐ ๋ฏธ๋ผ" % (TREE_N - used, 100 * (TREE_N - used) / TREE_N))
print("[reach] ์ฐ์ธ ๋
ธ๋ ์ธ๋ฑ์ค min=%d max=%d (0x3ffff ๊น์ง ํฉ์ด๋์๋ค)" % (min(reach), max(reach)))
print("[leaf ] ๋ฆฌํ %d๊ฐ ์ค ์๋ก ๋ค๋ฅธ ๊ฐ %d๊ฐ" % (len(leaves), len(set(leaves))))
print("[leaf ] ๋ฆฌํ ์ต์ 0x%08x / ์ต๋ 0x%08x โ ์ ๋ถ 0x3ffff ์ด๊ณผ๋ผ ๊ฒฝ๊ณ๊ฒ์ฌ์ ๊ฑธ๋ฆฐ๋ค"
% (min(leaves) & 0xFFFFFFFF, max(leaves) & 0xFFFFFFFF))
dup = [v for v, c in Counter(leaves).items() if c > 1]
print("[leaf ] ์ค๋ณต ๋ฆฌํ %d๊ฐ -> ์ ๋จ์ฌ์ด๋ฏ๋ก ๊ฐ ๊ธฐ๋๊ฐ์ ์ญ์์ ์ ์ผํ๋ค" % len(dup))python3 analyze_tree.py
์ฝ์ด ๋ธ ๊ฒ ๋ค ๊ฐ์ง.
-
๊น์ด๋ง๋ค ๋ ธ๋ ์๊ฐ ์ ํํ ๋ ๋ฐฐ๋ก ๋์ด๋๋ค. 1, 2, 4, ... 32768. ํฉ์น๋ ๊ฐ์ง๊ฐ ํ๋๋ ์๋ ์์ ์ด์งํธ๋ฆฌ๋ค.
-
์ค์ ๋ก ์ฝํ๋ ๋ด๋ถ ๋ ธ๋๋ 65,535๊ฐ(์ํธ๋ฆฌ 131,070์นธ). ๋๋จธ์ง 393,218์นธ์ ์ ๋ถ
-1์ด๊ณ ํ ๋ฒ๋ ์ ์ฝํ๋ค. ํ์ 75%๊ฐ ๊ทธ๋ฅ ๋น์นธ์ด๋ผ ํ์ผ์ 74.5%๊ฐ0xff์๋ ๊ฒ์ด๋ค. -
๋ ธ๋ ๋ฒํธ๋ 0๋ถํฐ 262,139๊น์ง ํฉ์ด์ ธ ์๋ค. ์์๋๋ก ์ฑ์ ์ผ๋ฉด 65,535๋ฒ๊น์ง๋ก ์ถฉ๋ถํ๋ค. ์ํ ๊ทผ์ฒ๊น์ง ์ผ๋ถ๋ฌ ๋ฟ๋ ค ๋์ ํ๊ฐ 4๋ฐฐ๋ก ๋ถํผ ๊ฒ์ด๊ณ , ๊ทธ๊ฒ 2MB์ ์ ์ฒด๋ค.
-
๋ฆฌํ 65,536๊ฐ๊ฐ ์ ๋ถ ์๋ก ๋ค๋ฅด๋ค.
๋ง์ง๋ง ์ค์ด ์ด ๋ฌธ์ ์ ๋ต์ด๋ค. 16๋นํธ ๊ฐ์์ ๋ฆฌํ๋ก ๊ฐ๋ ์ฌ์์ด ์ ๋จ์ฌ๋ฉด, ๊ธฐ๋๊ฐ ํ๋๋ง๋ค ๊ทธ๊ฑธ ๋ง๋๋ ๋ฉ์ด๋ฆฌ๊ฐ ์ ํํ ํ๋ ์๋ค. ํ๋ณด๊ฐ ๊ฐ๋ฆด ์ผ์ด ์๋ค.
๋ฉ์ด๋ฆฌ ํ๋๊ฐ ์ค์ ๋ก ์ด๋ป๊ฒ ๋ด๋ ค๊ฐ๋์ง ๊ทธ๋ ค ๋ณด๋ฉด ์ด๋ ๋ค.

๐ ํธ๋ฆฌ๊ฐ ์ ๋จ์ฌ๋ผ์ ์๊ธฐ๋ ํจ์
โถ๐ ์ฝ์ง โ ํ๋ณด๊ฐ ํ๋ ๋์๋ค๋ ๊ฑด ์๋ฌด ๋ณด์ฆ๋ ์๋๋ค
์ ๋จ์ฌ๋ผ๋ ์ฑ์ง์ด ํธํ๊ธฐ๋ง ํ ๊ฑด ์๋๋ค. ์ฝ๊ธฐ๋ฅผ ํ๋ ค๋ ํ๋ณด๊ฐ ๋๊ฐ์ด ํ๋์ฉ ๋์จ๋ค.
๋นํธ ์์๋ ๋๋ธ ์์๋ฅผ ์๋ชป ์ก์๋ ๊ทธ๊ฑด ๊ทธ๋ฅ "๋ค๋ฅธ ๋ผ๋ฒจ๋ง"์ผ ๋ฟ์ด๋ผ ์ฌ์ ํ ์ ๋จ์ฌ๋ค. ๊ทธ๋์ ์คํจ ์ ํธ๊ฐ ์ ๋ฌ๋ค. ํ๋ณด 0๊ฐ๋ก ์ฃฝ์ด ์ฃผ๋ฉด ์คํ๋ ค ๊ณ ๋ง๋ค.
์ค์ ๋ก ๋ ๊ฐ์ง๋ฅผ ํ๋ฆฌ๊ฒ ๋ง๋ค์ด ๋๋ ค ๋ดค๋ค.
#!/usr/bin/env python3
"""์ฝ๊ธฐ๋ฅผ ํ ๊ตฐ๋ฐ์ฉ ํ๋ฆฌ๋ฉด ์ด๋ป๊ฒ ๋๋์ง โ ์ค์ ๋ก ๋๋ ค ๋ณธ๋ค.
์ธ ๋ชจ๋ธ ๋ชจ๋ 'ํ๋ณด ์์'์ผ๋ก ์ฃฝ์ง ์๋๋ค. ํธ๋ฆฌ๊ฐ ์ ๋จ์ฌ๋ผ์ **ํ๋ฆฐ ๋ชจ๋ธ๋
64๊ธ์์ง๋ฆฌ ๊ทธ๋ด๋ฏํ ๋ต์ ํ๋์ฉ ๋ง๋ค์ด ๋ธ๋ค.** ๊ทธ๋์ ๋ต์ด ๋์๋ค๋ ์ฌ์ค์
๊ฒ์ฆ์ด ๋ชป ๋๋ค โ ๋ฐ์ด๋๋ฆฌ์ ๋ฃ์ด ๋ด์ผ ์๋ค.
"""
import struct
import subprocess
from pathlib import Path
HERE = Path(__file__).parent
BIN = HERE / "extracted" / "main"
blob = BIN.read_bytes()
off = lambda va: va - 0x4000 + 0x3000
expected = struct.unpack("<16i", blob[off(0x4020):off(0x4020) + 64])
tree = struct.unpack("<524288i", blob[off(0x4060):off(0x4060) + 524288 * 4])
def build(bit_msb_first):
"""v -> 16์คํ
๋ค leaf. bit_msb_first ๋ฉด ์์ ๋นํธ๋ถํฐ ์๋นํ๋ค."""
table = {}
for v in range(1 << 16):
node, x = 0, v
for _ in range(16):
bit = (x >> 15) & 1 if bit_msb_first else x & 1
node = tree[2 * node + bit]
x = (x << 1) & 0xFFFF if bit_msb_first else x >> 1
table[node] = v
return table
def answer(table, nibble_reversed):
out = []
for want in expected:
v = table.get(want)
if v is None:
return None
s = "%04x" % v
out.append(s[::-1] if nibble_reversed else s)
return "".join(out)
def run(s):
r = subprocess.run([str(BIN), s], capture_output=True, text=True)
return r.returncode, r.stdout.strip()
lsb, msb = build(False), build(True)
for label, cand in (
("A. ๋นํธ๋ฅผ MSB ๋ถํฐ ์๋น", answer(msb, False)),
("B. ํ ๋ฉ์ด๋ฆฌ ์ ๋๋ธ ์์๋ฅผ ๋ค์ง์", answer(lsb, True)),
("C. ์ ๋๋ก ์ฝ์ ๊ฒ", answer(lsb, False)),
):
rc, out = run(cand)
print("%-28s %s" % (label, cand))
print("%-28s -> exit=%d stdout=%s" % ("", rc, out if out else "(์์)"))python3 traps.py
A๋ sar edx,1 ์ ๋ฐฉํฅ์ ๋ฐ๋๋ก ์ฝ์ ๊ฒ์ด๊ณ , B๋ shl edx,0x4 ๊ฐ ๋๋ธ์ ์ด๋ ์ชฝ๋ถํฐ ์๋์ง ๋ค์ง์ ๊ฒ์ด๋ค. ๋ ๋ค ์๋ฌธ์ 16์ง์ 64๊ธ์๊ฐ ๋ฉ์ฉกํ๊ฒ ๋์จ๋ค. ๋์ผ๋ก๋ ์ ๋ต๊ณผ ๊ตฌ๋ถ์ด ์ ๋๋ค.
๋ฐ์ด๋๋ฆฌ์ ๋ฃ์ด์ผ ๊ฐ๋ฆฐ๋ค. A์ B๋ exit=1, ์ ๋๋ก ์ฝ์ C๋ง ํ๋๊ทธ๋ฅผ ๋ธ๋ค.
๊ทธ๋์ solve ์คํฌ๋ฆฝํธ๊ฐ "candidates=1" ์ 16๋ฒ ์ฐ๋ ๊ฒ์ ์ฝ๊ธฐ๊ฐ ๋ง๋ค๋ ๊ทผ๊ฑฐ๊ฐ ์๋๋ค. ๊ฒ์ฆ์ ๋ฌธ์ ๋ฐ์ด๋๋ฆฌํํ ์์ผ์ผ ํ๋ค.
๐ฏ ํ์ด โ 65536 ๊ฒฝ๋ก๋ฅผ ํ ๋ฒ ํ๋๋ค
์ญ๋ฐฉํฅ์ผ๋ก ํธ๋ฆฌ๋ฅผ ๋์ง์ ํ์๊ฐ ์๋ค. ๋ฉ์ด๋ฆฌ ํ๋๊ฐ 16๋นํธ๋ฟ์ด๋ 65,536๊ฐ๋ฅผ ์ ๋ถ ์์ผ๋ก ๊ตด๋ ค ๋ฆฌํ โ ๊ฐ ์ญ์ธ๋ฑ์ค๋ฅผ ๋ง๋ค๊ณ , ๊ธฐ๋๊ฐ 16๊ฐ๋ฅผ ์กฐํํ๋ฉด ๊ทธ๋ง์ด๋ค. ํ ์ ๊ทผ์ด 100๋ง ๋ฒ์ฏค์ธ๋ฐ ํ์ด์ฌ์ผ๋ก๋ ์์๊ฐ์ด๋ค.
#!/usr/bin/env python3
"""Branches and Leaves (DreamHack, Silver 3, reversing) โ solver.
check() at 0x11e0 splits the 64-char argv[1] into 16 chunks of 4 hex digits,
turns each chunk into a 16-bit integer v, then walks an array-encoded binary
tree stored in .data: node = tree[2*node + bit], consuming the bits of v from
LSB to MSB, exactly 16 times. The node reached after the 16th step must equal
expected[chunk] (16 int32 at .data+0x20).
Since every chunk is independent and only 16 bits wide, we replay all 65536
paths forward and index them by the leaf they reach.
"""
import struct
import sys
from pathlib import Path
BIN = Path(__file__).with_name("extracted") / "main"
DATA_VADDR = 0x4000 # .data virtual address
DATA_OFF = 0x3000 # .data file offset
EXPECT_VA = 0x4020 # int32 expected[16]
TREE_VA = 0x4060 # int32 tree[524288]
TREE_N = (0x204060 - 0x4060) // 4
DEPTH = 16 # bits consumed per chunk == loop counter in ecx
NODE_MAX = 0x3FFFF # `cmp eax,0x3ffff; jg fail` bound on internal nodes
def load():
blob = BIN.read_bytes()
off = lambda va: va - DATA_VADDR + DATA_OFF
expected = struct.unpack("<16i", blob[off(EXPECT_VA):off(EXPECT_VA) + 64])
tree = struct.unpack("<%di" % TREE_N, blob[off(TREE_VA):off(TREE_VA) + TREE_N * 4])
return expected, tree
def walk(tree, v):
"""Replay the 0x124c loop: 16 x node = tree[2*node + (v & 1)], v >>= 1."""
node = 0
for step in range(DEPTH):
if step and not (-1 < node <= NODE_MAX): # bounds check at 0x1240
return None
node = tree[2 * node + (v & 1)]
v >>= 1
return node
def main():
expected, tree = load()
leaves = {}
for v in range(1 << DEPTH):
leaf = walk(tree, v)
if leaf is None:
continue
leaves.setdefault(leaf, []).append(v)
print("[*] tree entries : %d" % TREE_N)
print("[*] distinct leaves : %d reached out of 65536 paths" % len(leaves))
chunks = []
for i, want in enumerate(expected):
cands = leaves.get(want, [])
print("[%2d] expected=0x%08x candidates=%d -> %s"
% (i, want & 0xFFFFFFFF, len(cands),
" ".join("%04x" % c for c in cands) or "NONE"))
if len(cands) != 1:
sys.exit("chunk %d is not uniquely determined" % i)
chunks.append("%04x" % cands[0])
key = "".join(chunks)
print("[+] input : %s" % key)
print("[+] flag : DH{%s}" % key)
if __name__ == "__main__":
main()python3 solve.py
distinct leaves : 65536 reached out of 65536 paths โ ๊ฒฝ๋ก๊ฐ ํ๋๋ ์ ์ฃฝ๊ณ ๋ฆฌํ๋ ์ ๊ฒน์น๋ค. ์์ ํต๊ณ์ ๊ฐ์ ๋ง์ด๋ค.
๊ธฐ๋๊ฐ 16๊ฐ๊ฐ ์ ๋ถ candidates=1 ๋ก ๋จ์ด์ง๊ณ , ์ด์ด ๋ถ์ด๋ฉด 64๊ธ์๋ค. ์ค์ธก 0.21์ด.
๐งช ๋ฐํ์์ผ๋ก ํ ๋ฒ ๋ ํ์ธ
์ ์ ์ผ๋ก ์ฝ์ ๊ฒ ์คํ ์ค์๋ ๋ง๋์ง ๋ณธ๋ค. ๋น๊ต ์ง์ 0x1269 ์ ๋ธ๋ ์ดํฌ๋ฅผ ๊ฑธ๊ณ eax(16์คํ
๋ค ๊ฐ)์ expected[i] ๋ฅผ ๊ฐ์ด ์ฐ์๋ค.
PIE๋ผ ๋ฒ ์ด์ค ์ฃผ์๋ ๊ด๋ก๋ก ๊ฐ์ ํ์ง ์๊ณ info proc mappings ์์ ์ค์ธกํด ์ด๋ค.
set pagination off
set confirm off
starti
python
import gdb
base = None
for line in gdb.execute("info proc mappings", to_string=True).splitlines():
if line.rstrip().endswith("/main"):
base = int(line.split()[0], 16)
break
gdb.execute("set $base = %d" % base)
print("[gdb] PIE base = 0x%x (info proc mappings ๋ก ์ค์ธก)" % base)
end
break *($base + 0x1269)
commands
silent
printf "chunk %2d: 16์คํ
๋ค node=0x%08x expected[%d]=0x%08x %s\n", $r9d/4, $eax, $r9d/4, *(int*)($r10 + ($r9d>>2)*4), ($eax == *(int*)($r10 + ($r9d>>2)*4)) ? "์ผ์น" : "๋ถ์ผ์น"
continue
end
continuegdb -q -batch -x cmds.gdb --args ./extracted/main d8f794325872bab95cfaa117545c6c0b77059a74f4dd31d8c2bf130a59745d77
๋ฒ ์ด์ค๋ 0x555555554000, ๋ธ๋ ์ดํฌํฌ์ธํธ๋ 0x555555555269. 16๋ฉ์ด๋ฆฌ๊ฐ ์ ๋ถ ์ผ์นํ๊ณ ๊ทธ๋๋ก ํ๋๊ทธ๊ฐ ๋์จ๋ค. ์ ์ ๋ถ์๊ณผ ์คํ์ด ์ด๊ธ๋๋ ๊ณณ์ ์๋ค.
๐ ์ฌํ
๊ฒฝ๊ณ ์กฐ๊ฑด๋ ๊ฐ์ด ํ์ธํ๋ค. ํ ๊ธ์๋ง ๋ฐ๊พธ๊ฑฐ๋, ๋๋ฌธ์๋ก ์ฐ๊ฑฐ๋, 63๊ธ์๋ก ์ค์ด๋ฉด ์ ๋ถ exit=1 ์ด์ด์ผ ํ๋ค.
#!/usr/bin/env python3
"""solve.py ๊ฐ ๋ฝ์ ์
๋ ฅ์ ๋ฌธ์ ๋ฐ์ด๋๋ฆฌ์ ๊ทธ๋๋ก ๋ฃ์ด ๋ณธ๋ค."""
import subprocess
from pathlib import Path
BIN = Path(__file__).with_name("extracted") / "main"
KEY = "d8f794325872bab95cfaa117545c6c0b77059a74f4dd31d8c2bf130a59745d77"
for label, arg in (("์ ๋ต", KEY),
("ํ ๊ธ์ ๋ฐ๊ฟ", KEY[:-1] + ("0" if KEY[-1] != "0" else "1")),
("๋๋ฌธ์๋ก", KEY.upper()),
("63๊ธ์", KEY[:-1])):
r = subprocess.run([str(BIN), arg], capture_output=True, text=True)
print("%-12s exit=%d stdout=%s" % (label, r.returncode, r.stdout.strip() or "(์์)"))python3 verify.py
๋๋ฌธ์๊ฐ ํ๊ธฐ๋ ๊ฒ ๋์ ๋ณด์ธ๋ค. ์์์ ์ฝ์ 'a'-'f' ์ ์ฉ ๋ถ๊ธฐ ๊ทธ๋๋ก๋ค.
๋ง์ง๋ง์ผ๋ก ๋ฐฐํฌ ๋ฐ์ด๋๋ฆฌ๋ง ๊ฐ์ง๊ณ ์ฒ์๋ถํฐ ๋๊น์ง ๋๋ ์คํฌ๋ฆฝํธ๋ฅผ ๋จ๊ธด๋ค. ํ๋ฅผ ๋ค์ ์ฝ์ด ์ ๋ ฅ์ ๋ณต์ํ๊ณ , ๊ทธ ์ ๋ ฅ์ ๋ฐ์ด๋๋ฆฌ์ ๋๋จน์ฌ ๊ธฐ๋ ํ๋๊ทธ์ ๋์กฐ๊น์ง ํ๋ค.
#!/usr/bin/env bash
# Branches and Leaves (DreamHack Silver 3, reversing) โ ํ ๋ฐฉ ์ฌํ.
# ๋ฐฐํฌ ๋ฐ์ด๋๋ฆฌ์ .data ์์ ํธ๋ฆฌ๋ฅผ ์ฝ์ด ์
๋ ฅ์ ๋ณต์ํ๊ณ , ๊ทธ ์
๋ ฅ์ ๋ฐ์ด๋๋ฆฌ์ ๋ค์ ๋จน์ฌ flag ๋ฅผ ๋ฐ๋๋ค.
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/main
KEY=$(timeout 300 python3 solve.py | sed -n 's/^\[+\] input : //p')
if [ -z "$KEY" ]; then
echo "โ FAIL (solve.py ๊ฐ ์
๋ ฅ์ ๋ชป ๋ฝ์๋ค)"; exit 1
fi
echo "๋ณต์ํ ์
๋ ฅ: $KEY"
OUT=$(./extracted/main "$KEY" || true)
echo "$OUT"
FLAG=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | head -1)
if [ -n "$FLAG" ] && { [ -z "$EXPECT" ] || [ "$FLAG" = "$EXPECT" ]; }; then
echo; echo "โ
PASS $FLAG"; exit 0
fi
echo; echo "โ FAIL (์ป์ ๊ฐ: '${FLAG:-์์}' / ๊ธฐ๋: '$EXPECT')"; exit 1./reproduce.sh
๋ก์ปฌ ํ๊ฒฝ์ Ubuntu 25.10 x86_64, Python 3.13.7, GNU gdb 16.3, binutils objdump 2.45๋ค. ๋น๋ ๊ณผ์ ์ ์๋ค โ ๋ฐฐํฌ ๋ฐ์ด๋๋ฆฌ๋ฅผ ๊ทธ๋๋ก ์ฝ๊ณ ๊ทธ๋๋ก ์คํํ๋ค.
์ต์ข ํ๋๊ทธ.
DH{d8f794325872bab95cfaa117545c6c0b77059a74f4dd31d8c2bf130a59745d77}๐ ๊ฒฐ๋ก
ํ์ผ์ด ํฌ๋ค๊ณ ์ด๋ ค์ด ๋ฌธ์ ๊ฐ ์๋๋ค
2.1MB ์ค 1.5MB๊ฐ 0xff ํจ๋ฉ์ด์๋ค. ํฌ๊ธฐ์ ๋๋ ค์ "์ ์ ๋งํฌ ๋ฐ์ด๋๋ฆฌ์ธ๊ฐ" ํ๊ณ ๋์ปดํ์ผ๋ฌ๋ถํฐ ์ผฐ๋ค๋ฉด ํ์ฐธ ๋์๊ฐ์ ๊ฒ์ด๋ค. ์น์
ํฌ๊ธฐ ํ ์ค์ด ๊ทธ๊ฑธ ๋จผ์ ์ ๋ฆฌํด ์ค๋ค. .text ๊ฐ 509๋ฐ์ดํธ๋ฉด ์์ผ๋ก ์ฝ๋ ๊ฒ ์ ์ผ ๋น ๋ฅด๋ค.
๋ฉ์ด๋ฆฌ๊ฐ ๋ ๋ฆฝ์ด๋ฉด ํ์ ๊ณต๊ฐ์ด ๋ฌด๋์ง๋ค
64๊ธ์ ์ ์ฒด๋ฅผ ํ ๋ฉ์ด๋ฆฌ๋ก ๋ณด๋ฉด ๊ฒฝ์ฐ์ ์๊ฐ ์ฒ๋ฌธํ์ ์ด์ง๋ง, ๋งค ๋ฉ์ด๋ฆฌ๊ฐ node = 0 ์์ ๋ค์ ์์ํ๋ ์๊ฐ ๋ฌธ์ ๋ 2์ 16์น์ง๋ฆฌ 16๊ฐ๋ก ์ชผ๊ฐ์ง๋ค. ๊ฒ์ฆ ๋ฃจํ๋ฅผ ์ฝ์ ๋ "์์ ์ํ๊ฐ ๋ค๋ก ์ด์ด์ง๋๊ฐ"๋ฅผ ๋จผ์ ๋ณด๋ ์ต๊ด์ด ๊ฐ์ ํ๋ค.
๋ต์ด ํ๋ ๋์จ ๊ฒ์ ๊ฒ์ฆ์ด ์๋๋ค
์ด ๋ฌธ์ ๋ ํ๋ฆฌ๊ฒ ์ฝ์ด๋ ํ๋ณด๊ฐ ํ๋์ฉ ๋์จ๋ค. ๋นํธ ์์๋ฅผ ๋ค์ง์ ๋ต๋ 64๊ธ์ 16์ง์๋ก ๊ทธ๋ด๋ฏํ๊ฒ ์๊ฒผ๋ค. ์ต์ข
ํ์ ์ ๋ฌธ์ ๋ฐ์ด๋๋ฆฌํํ
์ํค๋ ๊ฒ ๋ง๋ค. ๊ทธ๋์ reproduce.sh ๊ฐ ๋ณต์ํ ์
๋ ฅ์ ๋ฐ์ด๋๋ฆฌ์ ๋๋จน์ด๋ ๊ฒ์ผ๋ก ๋๋๊ฒ ์งฐ๋ค.
๋ง๋๋ ์ชฝ์์ ๋ณด๋ฉด
๊ฒ์ฌ๋ฅผ ์ฝ๋์์ ๋ฐ์ดํฐ๋ก ์ฎ๊ธฐ๋ฉด ๋์ค์ด์ ๋ธ๋ฌ๋ก ๋ณด์ด๋ ๋ก์ง์ด ํ ์ค์ด๋ ๋ค. ์ฌ๊ธฐ์๋ ํ์ ์ ์ฒด๊ฐ 60์ค์ด ์ ๋๋ค. ๋ค๋ง ์ฎ๊ฒจ ๊ฐ ๋ฐ์ดํฐ๊ฐ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋๋ก ๋๋ฌ๋ธ๋ค๋ ๊ฒ ๋๊ฐ๋ค. ๊น์ด๋ง๋ค ๋ ธ๋๊ฐ ๋ ๋ฐฐ์ฉ ๋๊ณ ๋ฆฌํ๊ฐ ์ ๊ฒน์น๋ค๋ ์ฌ์ค์ ํ๋ฅผ ํ ๋ฒ ์ธ๋ ๊ฒ๋ง์ผ๋ก ๋์ค๊ณ , ๊ทธ ์๊ฐ ์ญ์ฐ์ด 65,536ํ ์ํ๋ก ๋๋๋ค.
๋ ธ๋ ๋ฒํธ๋ฅผ ํฉ๋ฟ๋ ค ํ๋ฅผ 4๋ฐฐ๋ก ๋ถํ๋ฆฐ ๊ฒ๋ ํ์ผ๋ง ํค์ ์ง ๋ถ์์ ๋ง์ง๋ ๋ชปํ๋ค. ํ ๊ธฐ๋ฐ ๊ฒ์ฆ์ ์ ๋ง ์ด๋ ต๊ฒ ๋ง๋ค๋ ค๋ฉด ๋ฉ์ด๋ฆฌ๋ผ๋ฆฌ ์ํ๋ฅผ ์ด์ด ๋ถ์ด๊ฑฐ๋ ๋ฆฌํ๋ฅผ ์ฌ๋ฌ ๊ฐ์ ๊ฒน์น๊ฒ ํด์ ์ญ์์ด ๊ฐ๋ฆฌ๊ฒ ๋ง๋ค์ด์ผ ํ๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ