๋ฌธ์ : DreamHack โ Function Network ๋ถ๋ฅ: reversing ๋์ด๋: ๐ฅ Gold 1 FLAG:
DH{b510ce2d7599e75f79b6bf613384dda282bcedea47184155e55d1d22139afaab}
๋ฌธ์ ์ค๋ช ์ ๋ฑ ํ ์ค์ด๋ค. "Functions form a network, and our values go through it..."
๋ฐ์ ๊ฑด stripped ELF ํ๋๋ฟ์ด๊ณ , ํ์ผ ํฌ๊ธฐ 283KB ์ค ์คํ ์ฝ๋๋ 25KB๋ฐ์ ์ ๋๋ค. ๋๋จธ์ง๋
์ ๋ถ .data ๋ค. ๊ทธ ๋น์จ์ด ์ด ๋ฌธ์ ์ ์ฑ๊ฒฉ์ ๊ฑฐ์ ๋ค ๋งํด ์ค๋ค โ ๋ก์ง์ด ์๋๋ผ ๋ฐ์ดํฐ๊ฐ ํ๋ก๊ทธ๋จ์ด๋ค.
file extracted/chal; readelf -SW extracted/chal | grep -E "Name|\.text|\.rodata|\.data|\.bss"
๋ฌธ์ ๊ฐ์
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ๋ช | Function Network |
| ๋์ด๋ | ๐ฅ Gold 1 |
| ๋ถ๋ฅ | reversing |
| ์ ๊ณต ํ์ผ | chal (ELF 64-bit LSB pie, x86-64, dynamically linked, stripped) |
| ์๋ฒ | ์์ โ ์คํ๋ผ์ธ ๋ฌธ์ |
| ํต์ฌ ์ทจ์ฝ์ / ๊ธฐ๋ฒ | ๊ฐ์ญ ์ฐ์ฐ๋ง์ผ๋ก ์ง์ธ ๋๋ ํ ํ์ดํ๋ผ์ธ์ ์ญ๋ฐฉํฅ ์คํ |
ํ์ด ํ๋ฆ์ ์ด๋ ๋ค. main ์ด 64๊ธ์ ์
๋ ฅ์ ์์
๋ฒํผ๋ก ์ฎ๊ธด ๋ค, .data ์ ์๋ 10000์ค์ง๋ฆฌ
ํ
์ด๋ธ์ ์์๋๋ก ์ฝ์ด ๋ฐ์ดํธ ๋ ๊ฐ์ฉ ์ฃผ๋ฌผ๋ญ๊ฑฐ๋ฆฌ๊ณ , ๋ง์ง๋ง์ ๊ณ ์ ๋ 64๋ฐ์ดํธ์ memcmp ํ๋ค.
ํ
์ด๋ธ์ ์ฒซ ์นธ์ ํจ์ ํฌ์ธํฐ์ฒ๋ผ ์๊ฒผ์ง๋ง ์ค์ ๋ก๋ ๊ฒฝ๋ก๊ณ , ๊ทธ ๊ฒฝ๋ก๊ฐ ๊ฐ๋ฆฌํค๋ ์ข
์ ์
add ยท xor ยท sub ยท swap ๋ค ๊ฐ์ง๋ฟ์ด๋ค. ๋ท ๋ค ๋๋๋ฆด ์ ์์ผ๋ ๋ชฉํ๊ฐ์์ ๊ฑฐ๊พธ๋ก ๊ฐ์ผ๋ฉด ๋๋๋ค.
๐งฉ ๋ฐฐ๊ฒฝ โ "ํจ์ ๋คํธ์ํฌ"๋ผ๋ ๋๋ ํ
์ ์ดํ๋ฆ ํํํ(control-flow flattening)๋ฅผ ์๋ ์ฌ๋์ด๋ผ๋ฉด ์ต์ํ ๊ทธ๋ฆผ์ด๋ค. ์๋๋ ํจ์ ํ๋์ ๋ด๊ฒผ์ ๋ก์ง์ ์๊ฒ ์ชผ๊ฐ์, ์ด๋ ์กฐ๊ฐ์ ์คํํ ์ง ๋ฐํ์ ๊ฐ์ด ๊ฒฐ์ ํ๊ฒ ๋ง๋๋ ๋ฐฉ์์ด๋ค. ์ ์ ๋ถ์๊ธฐ๊ฐ ๊ทธ๋ํ๋ฅผ ๊ทธ๋ ค๋ "์ฌ๊ธฐ์ ์ ๊ธฐ๋ก ๊ฐ๋ค"๊ฐ ์ ๋ณด์ธ๋ค.
์ด ๋ฌธ์ ๋ ๊ฑฐ๊ธฐ์ ํ ๊ฒน์ ๋ ์น์๋ค. ์กฐ๊ฐ์ ๊ณ ๋ฅด๋ ๊ฐ์ด ํ ๋ฒ์ ์๋น๋์ง ์๋๋ค.
64๋นํธ ๊ฐ์ 2๋นํธ์ฉ ์๋ผ 32๋ฒ์ ๊ฑธ์ณ ์ฐ๊ณ , ๋งค ๋จ๊ณ๋ง๋ค ๋ค์์ ๋ฌผ์ด๋ณผ ํจ์๊ฐ ๋ฌ๋ผ์ง๋ค.
๊ทธ๋์ ๋์ค์ด์
๋ธ๋ฌ์์ call ์ ์๋ฌด๋ฆฌ ๋ฐ๋ผ๊ฐ๋ "์ด key ๊ฐ ๊ฒฐ๊ตญ ๋ฌด์จ ์ฐ์ฐ์ด๋"๋ ๋ต์ด ์ ๋์จ๋ค.
๊ทธ๋ํ๋ฅผ ํต์งธ๋ก ๋ณต์ํด์ ์ง์ ๊ฑธ์ด ๋ด์ผ ๋์จ๋ค.
๋ฐ๊ฟ ๋งํ๋ฉด ์ด ๋ฌธ์ ์ ๋์ด๋๋ ์ํธ ํด๋ ์ด ์๋๋ผ ์๋ฃ๊ตฌ์กฐ ๋ณต์์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ๋ณต์๋ง ๋๋๋ฉด ๋๋จธ์ง๋ ํ๋ฌดํ ๋งํผ ์ฝ๋ค.
๐ฌ ์ ์ฐฐ โ ๋ฌธ์์ด๋ถํฐ
๋จผ์ ํ๋ก๊ทธ๋จ์ด ๋ฌด์จ ๋ง์ ํ๋์ง ๋ณธ๋ค.
strings -n 5 extracted/chal | grep -nE "Give me|Wrong|flag is|memcmp|strlen"
Yes, the flag is ๋ค์ %s ๊ฐ ๋ถ๋๋ค. ํ๋๊ทธ๋ฅผ ํ๋ก๊ทธ๋จ์ด ๋ง๋ค์ด ๋ด๋ ๊ฒ ์๋๋ผ ๋ด๊ฐ ๋ฃ์
์
๋ ฅ์ ๊ทธ๋๋ก ๋๋๋ ค ์ค๋ค. ์ฆ ์ ๋ต ์
๋ ฅ ์์ฒด๊ฐ ํ๋๊ทธ ๋ณธ๋ฌธ์ด๋ค. ์ด๋ฐ ํํ๋ "๋ง๋ ์
๋ ฅ์ ์ฐพ์๋ผ"
๋ฌธ์ ๋ผ๋ ๋ป์ด๊ณ , ๋น๊ต ๋ก์ง์ด ์ด๋๊ฐ์ ํต์งธ๋ก ๋ค์ด ์๋ค๋ ๋ป์ด๊ธฐ๋ ํ๋ค.
์ํฌํธ๋ ๋ฑ ํ์ํ ๊ฒ๋ง ์๋ค. strlen, memcmp, read, printf, puts. ์ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋
์๋ค. ๊ทธ๋ผ ๋ณํ์ ์ ๋ถ ์์ฒด ์ฝ๋๋ก ํ๊ณ ์๋ค.
main ์ฐพ๊ธฐ
stripped ๋ผ ์ฌ๋ณผ์ด ์์ง๋ง _start ๊ฐ main ์ ๋๊ฒจ ์ฃผ๋ ์๋ฆฌ๋ ํญ์ ๊ฐ๋ค. ์ํธ๋ฆฌ์์
lea rdi,[rip+0x5f10] ์ด ๊ฐ๋ฆฌํค๋ 0x704f ๊ฐ main ์ด๋ค. ๊ฑฐ๊ธฐ์๋ถํฐ ์ฝ์ผ๋ฉด ์
๋ ฅ ์ฒ๋ฆฌ๊ฐ ๋์จ๋ค.
objdump -d -M intel --start-address=0x7133 --stop-address=0x71b2 extracted/chal | sed -f annot_main.sed
์ฝ์ด ๋ณด๋ฉด ์กฐ๊ฑด์ด ํ๋๋ฟ์ด๋ค.
read(0, rbp-0x50, 0x41)โ ์ต๋ 65๋ฐ์ดํธ- ๋ง์ง๋ง ๊ธ์๊ฐ
0x0a๋ฉด0x00์ผ๋ก ๋ฎ๋๋ค (๊ฐํ ์ ๊ฑฐ) strlen๊ฒฐ๊ณผ๊ฐ ์ ํํ0x40(64) ์ด์ด์ผ ํต๊ณผ
๊ธธ์ด 64๊ฐ ์๋๋ฉด Wrong! ์ ์ฐ๊ณ ๋๋๋ค. NUL ๋ฐ์ดํธ๊ฐ ์์ด๋ฉด strlen ์ด ์งง์์ง๋๊น
์
๋ ฅ 64๋ฐ์ดํธ์๋ 0x00 ์ด ์์ด์ผ ํ๋ค๋ ์ ์ฝ๋ ์ฌ๊ธฐ์ ๋ฐ๋ผ ๋์จ๋ค.
objdump์ถ๋ ฅ์<--์ฃผ์์ ๋ด๊ฐannot_main.sed๋ก ์น์ ๊ฒ์ด๋ค. ๋๊ตฌ๊ฐ ์๋ ๊ทธ๋ ๊ฒ ์ฐ์ด ์ฃผ๋ ๊ฒ ์๋๋ค. ์นํ ๊ท์น ํ์ผ์ ์๋ ํ ๊ธ์ ๊ทธ๋๋ก ์ค์ด ๋๋ค.
๐งฑ main ์ด ํ๋ ์ผ โ 10000๋ฒ์ ๋ณํ
๊ธธ์ด ๊ฒ์ฌ๋ฅผ ํต๊ณผํ๋ฉด ์
๋ ฅ 64๋ฐ์ดํธ๋ฅผ rbp-0x50 ์์ rbp-0x90 ์ผ๋ก ๋ณต์ฌํ๋ค. ์ดํ ๋ชจ๋ ๋ณํ์
๋ณต์ฌ๋ณธ์์ ์ผ์ด๋๊ณ , ์๋ณธ์ ๋ง์ง๋ง์ ํ๋๊ทธ๋ฅผ ์ถ๋ ฅํ ๋ ๋ค์ ์ฐ์ธ๋ค. ๊ทธ๋์ ์ฑ๊ณต ๋ฉ์์ง์
์ฐํ๋ ๊ฑด ๋ณํ๋ ๊ฐ์ด ์๋๋ผ ๋ด๊ฐ ์น ์
๋ ฅ ๊ทธ๋๋ก๋ค.
ํต์ฌ์ ๊ทธ๋ค์ ๋ฃจํ๋ค.
objdump -d -M intel --start-address=0x7207 --stop-address=0x72cb extracted/chal | sed -f annot_loop.sed
๋ฃจํ๋ฅผ ๊ทธ๋๋ก ์ฎ๊ธฐ๋ฉด ์ด๋ ๋ค.
char *buf = rbp - 0x90; // ์
๋ ฅ 64๋ฐ์ดํธ ์ฌ๋ณธ
for (int i = 0; i <= 0x270f; i++) { // 10000ํ
long key = *(long *)(0xb020 + i * 24);
long off_a = *(long *)(0xb028 + i * 24);
long off_b = *(long *)(0xb030 + i * 24);
dispatch(key, buf + off_a, buf + off_b); // 0x1209 โ ๊ณ ์ ์ฃผ์
}
if (memcmp((void *)0x459a0, buf, 0x40) == 0)
printf("Yes, the flag is DH{%s}\n", rbp - 0x50);์ธ๋ฑ์ค ๊ณ์ฐ์ด i + i + i ๋ค์ shl rax,0x3 ์ด๋ผ ์คํธ๋ผ์ด๋๊ฐ 24๋ฐ์ดํธ๋ค. ์ฆ ํ
์ด๋ธ ํ ์ค์ด
qword ์ธ ๊ฐ๊ณ , ์ธ ํ
์ด๋ธ ์ฃผ์ 0xb020 / 0xb028 / 0xb030 ์ ๊ฐ์ ๋ฐฐ์ด์ 0๋ฒยท1๋ฒยท2๋ฒ ์นธ์ด๋ค.
์ฃผ์๋ฅผ ๋ง์ถฐ ๋ณด๋ฉด ๋ฑ ๋จ์ด์ง๋ค. 0xb020 + 10000 ร 24 = 0x459a0 ์ด๊ณ , ๊ทธ๊ฒ ๋ฐ๋ก memcmp ์
๋ชฉํ ๋ฒํผ ์ฃผ์๋ค. ๋ชฉํ 64๋ฐ์ดํธ๊น์ง ํฉ์น๋ฉด 0x459e0 โ .data ์น์
์ ์ ํํ ๋์ด๋ค.
ํ
์ด๋ธ๊ณผ ๋ชฉํ๊ฐ์ด ๋นํ์์ด ์ด์ด์ ธ ์๋ค.
โถ๐ ์ฝ์ง 1 โ ํ ์ด๋ธ ์ฒซ ์นธ์ ํจ์ ํฌ์ธํฐ๋ก ์ฝ์ผ๋ฉด ์ด๋์ ๋งํ๋
0xb020 ์์ qword ๋ฅผ ์ฝ์ด mov rdi,rax ๋ก ๋๊ธฐ๋ ๊ฑธ ๋ณด๋ฉด ๋ฐ์ฌ์ ์ผ๋ก "ํจ์ ํฌ์ธํฐ ๋ฐฐ์ด"์ด๋ผ๊ณ
์ฝ๊ฒ ๋๋ค. ์ค์ ๋ก ๊ทธ๋ฐ ๋ฌธ์ ๊ฐ ํํ๊ณ , ๊ทธ๋ ๊ฒ ์ฝ์ผ๋ฉด ๋ค์ ์์์ call rdi ๋ฅผ ์ฐพ๋ ๊ฒ์ด๋ค.
๊ทธ๋ฐ๋ฐ ์ด ๋ฃจํ์๋ ๊ฐ์ ํธ์ถ์ด ์๋ค. call 1209 ๋ ๊ณ ์ ์ฃผ์๊ณ , ๋ฐฉ๊ธ ์ฝ์ ๊ฐ์ ๊ทธ ํจ์์
์ฒซ ๋ฒ์งธ ์ธ์๋ก ๋ค์ด๊ฐ๋ค. ๊ฐ ์์ฒด๋ฅผ ๋ด๋ ํฌ์ธํฐ์ผ ์๊ฐ ์๋ค.
key ์ต์ 0x0000101de1bdc038 ์ต๋ 0xfffa892705a02627์ด ๋ฐ์ด๋๋ฆฌ๋ PIE ๋ผ ๋ก๋ ์ฃผ์๊ฐ 0x55... ๋์ญ์ด๊ณ , .text ๋ ์คํ์
0x1120~0x7314 ๋ฟ์ด๋ค.
0xfffa8927... ๊ฐ์ ๊ฐ์ด ์ฝ๋ ์ฃผ์์ผ ๋ฆฌ ์๊ณ , ์ ๋ ฌ์กฐ์ฐจ ์ ๋ง๋๋ค(๋์๋ฆฌ๊ฐ ํ์์ธ key ๊ฐ ๋๋ ค ์๋ค).
ํฌ์ธํฐ๊ฐ ์๋๋ผ๋ฉด ์ธ๋ฑ์ค์ด๊ฑฐ๋ ๊ฒฝ๋ก๋ค. ๊ทธ๋ฆฌ๊ณ dispatch ์์์ and eax,0x3 ์ด ๋์ค๋ ์๊ฐ
๋ต์ด ์ ํด์ง๋ค โ 2๋นํธ์ฉ ์ชผ๊ฐ ์ฐ๋ ๊ฒฝ๋ก๋ค.
โถ๐ ์ฝ์ง 2 โ off_a ์ off_b ๊ฐ ๊ฐ์ผ๋ฉด ์ญ์ฐ์ด ๊นจ์ง๋ค
๋ค์์ ๋ณด๊ฒ ์ง๋ง ๋ฆฌํ๋ ๋ชฉ์ ์ง์ ๋ ๋ฒ ์ด๋ค. buf[off_b] = a ๋ฅผ ๋จผ์ , buf[off_a] = t ๋ฅผ
๋์ค์. ๋ง์ฝ off_a == off_b ์ธ ์ค์ด ํ๋๋ผ๋ ์์ผ๋ฉด ๋ ๋ฒ์งธ ์ฐ๊ธฐ๊ฐ ์ฒซ ๋ฒ์งธ๋ฅผ ๋ฎ์ด์ ์ ๋ณด๊ฐ
์ฌ๋ผ์ง๊ณ , ๊ทธ ์ง์ ๋ถํฐ๋ ์ญ์ฐ์ด ์ ์ผํ์ง ์๊ฒ ๋๋ค. add ๋ผ๋ฉด t = a + a = 2a ๋ผ ํ์ง ์ ๋ณด๊ฐ
ํต์งธ๋ก ๋ ์๊ฐ๋ค.
๊ทธ๋์ ์ญ์ฐ ์ฝ๋๋ฅผ ์ง๊ธฐ ์ ์ ๋จผ์ ์ธ์ด ๋ดค๋ค.
same = [(i, o) for i, o in enumerate(ops) if o[1] == o[2]]
print('oa==ob ์ธ ํญ๋ชฉ:', len(same), same[:5])oa==ob ์ธ ํญ๋ชฉ: 0 []0๊ฑด์ด๋ค. 10000์ค ์ ๋ถ off_a != off_b ๋ผ ๋ ์ฐ๊ธฐ๊ฐ ์๋ก ๋ค๋ฅธ ์นธ์ผ๋ก ๊ฐ๊ณ , ๊ฐ ์คํ
์ด 64๋ฐ์ดํธ
๊ณต๊ฐ์ ์ ๋จ์ฌ๊ฐ ๋๋ค. ์ด๊ฑธ ํ์ธํ์ง ์๊ณ ๋์ด๊ฐ์ผ๋ฉด "์ ์ด๋ค ์
๋ ฅ์์ ์ ๋ง์ง" ํ๊ณ ํ์ฐธ ํค๋งธ์ ๊ฒ์ด๋ค.
๐ฃ ํต์ฌ โ ๋์คํจ์ฒ๋ 4์ง ํธ๋ผ์ด๋ค
0x1209 ๋ฅผ ์ด์ด ๋ณด๋ฉด ๊ตฌ์กฐ๊ฐ ๋จ์ํ๋ค.
objdump -d -M intel --start-address=0x1209 --stop-address=0x127a extracted/chal | sed -f annot_disp.sed
์์ฌ์ฝ๋๋ก๋ ์ด๋ ๋ค.
void dispatch(long key, char *p, char *q) {
switch (key & 3) {
case 0: f_a(key >> 2, p, q); break;
case 1: f_b(key >> 2, p, q); break;
case 2: f_c(key >> 2, p, q); break;
default: f_d(key >> 2, p, q); break;
}
}์ด๋ฐ ํจ์๊ฐ 125๊ฐ ์๋ค. ์ ๋ถ ํฌ๊ธฐ๊ฐ ์ ํํ 0xbf(191)๋ฐ์ดํธ๋ก ๊ฐ๊ณ , ๋ค๋ฅธ ๊ฑด ๋ค ๊ฐ์ call
๋์๋ฟ์ด๋ค. 0x1209 ๋ถํฐ 0x6e8d ๊น์ง 191๋ฐ์ดํธ ๊ฐ๊ฒฉ์ผ๋ก ์ด์ดํ ๋ถ์ด ์๋ค.
์ฆ ์ ์ฒด๊ฐ ํ๋์ 4์ง ํธ๋ผ์ด๋ค. key ์ ํ์ 2๋นํธ๊ฐ ์ฒซ ๊ฐ๋ฆผ๊ธธ, ๊ทธ๋ค์ 2๋นํธ๊ฐ ๋ ๋ฒ์งธ ๊ฐ๋ฆผ๊ธธ, ์ด๋ฐ ์์ผ๋ก ๋ด๋ ค๊ฐ๋ค. ๊ทธ๋ฐ๋ฐ ๋ ธ๋๊ฐ 125๊ฐ๋ฟ์ด๋ฉด ํธ๋ฆฌ๋ก๋ ๋ง์ด ์ ๋๋ค โ ๊น์ด 32์ง๋ฆฌ 4์ง ํธ๋ฆฌ๋ ๋ ธ๋๊ฐ ์ฒ๋ฌธํ์ ์ผ๋ก ๋ง์์ผ ํ๋ค. ๋ต์ ์ธต๋ง๋ค ๋ ธ๋๋ฅผ ๊ณต์ ํ๋ DAG ๋ค.

์ด๊ฑธ ๋๋์ค์ด ์๋๋ผ ์ค์ ๋ก ์ธ์ด์ ํ์ธํ๋ค.
# verify_graph.py ๋ฐ์ท โ ๊ณ์ธต ๊ตฌ์กฐ ํ์ธ
level, frontier, seen = {0x1209: 0}, [0x1209], {0x1209}
while frontier:
nxt = []
for u in frontier:
for v in children.get(u, []):
if v not in seen:
seen.add(v); level[v] = level[u] + 1; nxt.append(v)
assert level[v] == level[u] + 1, (hex(u), hex(v))
frontier = nxtassert ๊ฐ ํต์ฌ์ด๋ค. ์ด๋ค ๊ฐ์ ์ด๋ ๋ ๋ฒจ์ ์ ํํ 1๋ง ์ฌ๋ฆฐ๋ค๋ฉด ์ด ๊ทธ๋ํ๋ ์ธต์ผ๋ก ๊ฐ๋ผ์ ธ ์๊ณ ,
๊ฐ์ ๋
ธ๋์ ์๋ก ๋ค๋ฅธ ๊น์ด๋ก ๋๋ฌํ๋ ๊ฒฝ๋ก๊ฐ ์๋ค๋ ๋ป์ด๋ค.
python3 verify_graph.py
๋ ๋ฒจ 0์ root ํ๋, ๋ ๋ฒจ 1๋ถํฐ 31๊น์ง ๊ฐ 4๊ฐ(์ด 124๊ฐ), ๊ทธ๋์ ๋์คํจ์ฒ๊ฐ ์ ํํ 125๊ฐ. ๋ฆฌํ 4๊ฐ๋ ์ ๋ถ ๋ ๋ฒจ 32์ ์๋ค. ์์ ์ฌ๋กฏ์ 500๊ฐ(root 4 + 31์ธต ร 16)์ธ๋ฐ ์๋ก ๋ค๋ฅธ ์์์ 128๊ฐ๋ฟ โ ๊ณต์ ๊ฐ ์ฌํ๋ค๋ ๊ฒ ์ซ์๋ก ๋์จ๋ค.
์ฌ๊ธฐ์ ์ค์ํ ๊ฒฐ๋ก ์ด ํ๋ ๋์จ๋ค. key 10000๊ฐ๊ฐ ์ ๋ถ ์๋ก ๋ค๋ฅธ๋ฐ, ๋๋ฌํ ์ ์๋ ์ข ์ ์ ๋ท๋ฟ์ด๋ค. 64๋นํธ๋ฅผ ๋ค ์ฐ๋ ํ๋ คํ ๊ฒฝ๋ก๊ฐ ๊ฒฐ๊ตญ 2๋นํธ์ง๋ฆฌ ์ ๋ณด๋ก ์๋ ดํ๋ค. ๋๋ ํ์ ์ ๋ถ๊ฐ ์ฌ๊ธฐ์ ์๋ค.
โถ๐ ์ฝ์ง 3 โ ์ด์ ๋ฑ์ฅ ์์๋ฅผ case ์์๋ก ๋ฏฟ์ด๋ ๋๋
๋์คํจ์ฒ์์ ์์ ๋ท์ ๋ฝ์ ๋ ์ฒ์์ ๊ทธ๋ฅ call ์ด ๋์ค๋ ์์๋๋ก case 0/1/2/3 ์ ๋ฐฐ๋นํ๋ค.
์ปดํ์ผ๋ฌ๊ฐ ๋ณดํต ๊ทธ๋ ๊ฒ ๋ฑ๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋ฐ๋ฐ ์ด๊ฒ ํ๋ฆฌ๋ฉด ์ฐ์ฐ ์ข
๋ฅ๊ฐ ํต์งธ๋ก ๋ค๋ฐ๋ ์ฑ ์กฐ์ฉํ ์งํ๋๋ค. add ์๋ฆฌ์ xor ์ด
๋ค์ด๊ฐ๋ ์ฝ๋๋ ๋ฉ์ฉกํ ๋๊ณ , ์ญ์ฐ ๊ฒฐ๊ณผ๋ง ์ฐ๋ ๊ธฐ๊ฐ ๋๋ค. ์ด๋๊ฐ ํ๋ ธ๋์ง ์ถ์ ํ ๋จ์๋ ์๋ค.
๊ทธ๋์ ์์ ๊ฐ์ ์ ์ ์ฐ๋ ๋ ๋ฒ์งธ ํ์๋ฅผ ๋ฐ๋ก ๋ง๋ค์ด ๋์กฐํ๋ค. test rax,rax ๋ case 0,
cmp rax,0x1 ์ case 1, cmp rax,0x2 ๋ case 2, ๋น๊ต ์์ด ๋์ค๋ ๋ง์ง๋ง call ์ด default.
explicit, pending = {}, None
for l in body:
if re.search(r"test\s+rax,rax", l):
pending = 0
elif (m := re.search(r"cmp\s+rax,0x([0-9a-f]+)", l)):
pending = int(m.group(1), 16)
elif (m := re.search(r"call\s+([0-9a-f]+)", l)):
explicit[3 if pending is None else pending] = int(m.group(1), 16)
pending = None125๊ฐ ์ ๋ถ ๋ถ์ผ์น 0๊ฑด์ด์๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ์์ ๊ฐ์ ์ ๋ง์์ง๋ง, ๋ง์๋ค๋ ๊ฑธ ํ์ธํ ๊ฒ๊ณผ ๋ง๊ฒ ๊ฑฐ๋ ๋์ด๊ฐ ๊ฒ์ ๋ค๋ฅด๋ค. ์ด๋ฐ ์ข ๋ฅ์ ๊ฐ์ ์ ํ๋ ธ์ ๋ ์ฆ์์ด ์ ๋์ค๋ ์ชฝ์ด๋ผ ๋ ๋ฌด์ญ๋ค.
๐ ๋ฆฌํ 4๊ฐ โ ๋ท ๋ค ๋๋๋ฆด ์ ์๋ค
ํธ๋ผ์ด์ ์ข
์ ์ 191๋ฐ์ดํธ ๊ท์น์์ ๋ฒ์ด๋ ์์ ํจ์ ๋ค ๊ฐ๋ค. 0x6f4c, 0x6f8f, 0x6fd0, 0x7017.
objdump -d -M intel --start-address=0x6f4c --stop-address=0x6fd0 extracted/chal | grep -vE "endbr64|push rbp|mov rbp,rsp|QWORD PTR \[rbp-0x(18|20|28)\],r|pop rbp|nop|file format|Disassembly" | sed -f annot_leaf.sed
objdump -d -M intel --start-address=0x6fd0 --stop-address=0x704f extracted/chal | grep -vE "endbr64|push rbp|mov rbp,rsp|QWORD PTR \[rbp-0x(18|20|28)\],r|pop rbp|nop|file format|Disassembly" | sed -f annot_leaf.sed
๋ค ํจ์๊ฐ ํ๋ ์ผ์ ์์ ํ ๊ฐ์ ๊ณจ๊ฒฉ์ด๋ค. a = *p, b = *q ๋ฅผ ์ฝ๊ณ , ์์๊ฐ t ๋ฅผ ๋ง๋ ๋ค์
*q = a ๋ฅผ ๋จผ์ , *p = t ๋ฅผ ๋์ค์ ์ด๋ค. ๋ค๋ฅธ ๊ฑด t ๋ฅผ ๋ง๋๋ ํ ์ค๋ฟ์ด๋ค.
| ์ฃผ์ | ์ฐ์ฐ | t | ๊ฐ์ |
|---|---|---|---|
0x6f4c | add | (a + b) & 0xff | 2470 |
0x6f8f | xor | a ^ b | 2512 |
0x6fd0 | sub | (a - b) & 0xff | 2468 |
0x7017 | swap | b | 2550 |
์ฌ๊ธฐ๊ฐ ๋ฌธ์ ์ ์ ๋ถ๋ค. ์ *q ๊ฐ์ด ์ *p ๊ฐ์ด๋ผ, ๊ฒฐ๊ณผ๋ง ๋ณด๊ณ ๋ ์
๋ ฅ ํ๋๋ฅผ ๊ณต์ง๋ก ์ ์ ์๋ค.
๋๋จธ์ง ํ๋๋ ์ฐ์ฐ์ ๋ค์ง์ผ๋ฉด ๋์จ๋ค.

a = nb # ์ buf[off_a] โ ๊ทธ๋ฅ ์ฝ์ผ๋ฉด ๋๋ค
b = { # ์ buf[off_b] โ ์ฐ์ฐ๋ง ๋ค์ง๋๋ค
"add": (na - a) & 0xff,
"sub": (a - na) & 0xff,
"xor": na ^ a,
"swap": na,
}[op]sub ์ ์ญ์ฐ์ด (a - na) ์ธ ๊ฒ ์ ๊น ํท๊ฐ๋ฆฌ๋๋ฐ, ์๋ฐฉํฅ์ด t = a - b ๋๊น b = a - t ๋ค.
t ๊ฐ ์ *p ๊ฐ(na)์ด๋ฏ๋ก b = a - na. add ๋ b = t - a ๋ผ na - a. ๋บ์
๋ ๊ฐ๊ฐ
๋ฐฉํฅ์ด ๋ฐ๋๋ค.
10000์คํ ์ ์ฒด๊ฐ ์ด๋ฐ ์ ๋จ์ฌ์ ํฉ์ฑ์ด๋, ์ ์ฒด ๋ณํ๋ 64๋ฐ์ดํธ ๊ณต๊ฐ์ ์ ๋จ์ฌ๋ค. ๋ชฉํ๊ฐ์ ํด๋นํ๋ ์ ๋ ฅ์ด ์ ํํ ํ๋ ์กด์ฌํ๊ณ , ๊ทธ๊ฑธ ์ฐพ๋ ๋ฐฉ๋ฒ์ด ์ญ๋ฐฉํฅ ์คํ์ด๋ค.
๐งฎ ๊ทธ๋ํ์ ํ ์ด๋ธ ๋ฝ์๋ด๊ธฐ
์ด์ ํ์ํ ๊ฑด ์ธ ๊ฐ์ง๋ค. ๋์คํจ์ฒ 125๊ฐ์ ์์ ๋ชฉ๋ก, ํ ์ด๋ธ 10000์ค, ๋ชฉํ 64๋ฐ์ดํธ. ์ ๋ถ ๋ฐ์ด๋๋ฆฌ์์ ์ ์ ์ผ๋ก ๋ฝ์ ์ ์๋ค.
#!/usr/bin/env python3
"""Function Network - ๋์คํจ์ฒ ๊ทธ๋ํ + ์ฐ์ฐ ํ
์ด๋ธ ์ถ์ถ
chal ์ stripped PIE ๋ค. ํจ์ ์ฌ๋ณผ์ด ์์ผ๋ฏ๋ก
1) objdump ์ถ๋ ฅ์์ `endbr64; push rbp` ๋ก ํจ์ ์์์ ์ฐพ๊ณ
2) 191๋ฐ์ดํธ์ง๋ฆฌ ๋์คํจ์ฒ 125๊ฐ์ call ํ๊ฒ 4๊ฐ๋ฅผ ์์๋๋ก ๋ฝ์ 4์ง ํธ๋ผ์ด๋ฅผ ๋ณต์ํ๊ณ
3) .data ์ (key, off_a, off_b) 10000๊ฐ๋ฅผ ์ฝ์ด ๊ฐ key ๋ฅผ ๊ทธ๋ํ๋ก ๊ฑธ์ด ๋ฆฌํ(์ฐ์ฐ)๋ฅผ ํ์ ํ๋ค.
"""
import re, subprocess, json, sys
from pathlib import Path
BASE = Path(__file__).resolve().parent
BIN = BASE / "extracted" / "chal"
# --- 1. ๋์ค์ด์
๋ธ ---------------------------------------------------------
asm = subprocess.run(["objdump", "-d", "-M", "intel", str(BIN)],
capture_output=True, text=True, check=True).stdout.split("\n")
starts = []
for i, l in enumerate(asm):
m = re.match(r"^\s+([0-9a-f]+):\s+f3 0f 1e fa\s+endbr64", l)
if m and i + 1 < len(asm) and "push" in asm[i + 1] and "rbp" in asm[i + 1]:
starts.append(int(m.group(1), 16))
starts.sort()
# ๋ฆฌํ 4๊ฐ(์ฐ์ฐ)์ main ์ ํฌ๊ธฐ๋ก ๊ตฌ๋ถ๋๋ค โ ๋์คํจ์ฒ๋ง ์ ํํ 0xbf ๋ฐ์ดํธ
LEAF = {0x6f4c: "add", 0x6f8f: "xor", 0x6fd0: "sub", 0x7017: "swap"}
MAIN = 0x704f
# --- 2. ๋์คํจ์ฒ์ ์์ 4๊ฐ --------------------------------------------------
addr_of = {}
for i, l in enumerate(asm):
m = re.match(r"^\s+([0-9a-f]+):", l)
if m:
addr_of.setdefault(int(m.group(1), 16), i)
children = {}
for idx, a in enumerate(starts):
if a in LEAF or a == MAIN:
continue
end = starts[idx + 1]
tgts = []
for l in asm[addr_of[a]:addr_of[end]]:
m = re.search(r"call\s+([0-9a-f]+)", l)
if m:
tgts.append(int(m.group(1), 16))
assert len(tgts) == 4, (hex(a), [hex(t) for t in tgts])
children[a] = tgts # [case0, case1, case2, case3(default)]
print(f"[+] ๋์คํจ์ฒ {len(children)}๊ฐ ยท ๋ฆฌํ {len(LEAF)}๊ฐ")
# --- 3. key -> ์ฐ์ฐ --------------------------------------------------------
ROOT = 0x1209
cache = {}
def op_of(key):
if key in cache:
return cache[key]
node, k, hops = ROOT, key, 0
while node not in LEAF:
node = children[node][k & 3]
k >>= 2
hops += 1
if hops > 64:
raise RuntimeError(f"key={key} ์ํ")
cache[key] = (LEAF[node], hops)
return cache[key]
# --- 4. .data ์ ์ฐ์ฐ ํ
์ด๋ธ ------------------------------------------------
raw = BIN.read_bytes()
# .data: addr 0xb000, off 0xa000
DATA_ADDR, DATA_OFF = 0xB000, 0xA000
def rd(addr, n):
o = DATA_OFF + (addr - DATA_ADDR)
return raw[o:o + n]
TBL, N = 0xB020, 10000
ops = []
for i in range(N):
key, oa, ob = [int.from_bytes(rd(TBL + i * 24 + j * 8, 8), "little") for j in range(3)]
name, hops = op_of(key)
ops.append((name, oa, ob, key, hops))
target = rd(0x459A0, 64)
from collections import Counter
print("[+] ์ฐ์ฐ ๋ถํฌ:", Counter(o[0] for o in ops))
print("[+] ์คํ์
๋ฒ์:", min(min(o[1], o[2]) for o in ops), "~", max(max(o[1], o[2]) for o in ops))
print("[+] ๊ฒฝ๋ก ๊น์ด:", Counter(o[4] for o in ops))
print("[+] target[0:16] =", target[:16].hex())
json.dump({"ops": [(o[0], o[1], o[2]) for o in ops], "target": target.hex()},
open(BASE / "network.json", "w"))
print("[+] network.json ์ ์ฅ")ํ์ผ ์คํ์
๋ณํ์ readelf -SW ๊ฐ ์๋ ค ์ค ๊ฐ์ ๊ทธ๋๋ก ์ผ๋ค. .data ๋ ์ฃผ์ 0xb000 ์
ํ์ผ ์คํ์
0xa000 ์ด๋ผ ์ฐจ์ด๊ฐ 0x1000 ์ด๋ค.
python3 extract.py
์ธ ์ค์ด ํ๊บผ๋ฒ์ ์ฌ๋ฌ ๊ฐ์ ์ ํ์ธํด ์ค๋ค.
- ์ฐ์ฐ ๋ถํฌ๊ฐ ๊ณ ๋ฅด๋ค. ๋ค ์ข ๋ฅ๊ฐ 2468~2550 ์ฌ์ด๋ก ํฉ์ด์ ธ ์๋ค. ํธ์ค์ด ์์๋ค๋ฉด ํ์ฑ์ด ํ๋ ธ์ ๊ฐ๋ฅ์ฑ์ ์์ฌํด์ผ ํ๋ค.
- ์คํ์ ๋ฒ์๊ฐ 0~63. ๋ฒํผ ๋ฐ์ ๊ฑด๋๋ฆฌ๋ ์ค์ด ํ๋๋ ์๋ค. ์คํ ์ค์ผ ๊ฐ์ ๊ณ๊ฐ์ง๋ฅผ ๋ณผ ํ์๊ฐ ์๋ค.
- ๊ฒฝ๋ก ๊น์ด๊ฐ ์ ๋ถ 32. ์์ธ์ ์ผ๋ก ์ผ์ฐ ๋๋๋ key ๊ฐ ์๋ค๋ ๋ป์ด๊ณ , 64๋นํธ๋ฅผ ๋จ๊น์์ด ์ด๋ค๋ ๋ป์ด๋ค.
ํ ์ด๋ธ ์๋ถ๋ถ์ ์ฌ๋์ด ์ฝ์ ์ ์๊ฒ ์ฐ์ด ๋ณด๋ฉด ์ด๋ ๋ค.
#!/usr/bin/env python3
"""์ฐ์ฐ ํ
์ด๋ธ ์๋ถ๋ถ๊ณผ ๋ชฉํ ๋ฒํผ๋ฅผ ์ฌ๋์ด ์ฝ์ ์ ์๊ฒ ์ฐ๋๋ค."""
import json
from pathlib import Path
BASE = Path(__file__).resolve().parent
raw = (BASE / "extracted" / "chal").read_bytes()
DATA_ADDR, DATA_OFF, TBL = 0xB000, 0xA000, 0xB020
def rd(addr, n):
o = DATA_OFF + (addr - DATA_ADDR)
return raw[o:o + n]
net = json.load(open(BASE / "network.json"))
ops = net["ops"]
keys = [int.from_bytes(rd(TBL + i * 24, 8), "little") for i in range(10000)]
print("idx key(64bit) base4(ํ์->์์, ์ 8์๋ฆฌ) op off_a off_b")
for i in range(8):
k = keys[i]
b4 = "".join(str((k >> (2 * j)) & 3) for j in range(8))
name, oa, ob = ops[i]
print(f"{i:<4} 0x{k:016x} {b4}... {name:<5} {oa:<5} {ob}")
print()
print(f"์๋ก ๋ค๋ฅธ key: {len(set(keys))}๊ฐ / ์ ์ฒด {len(keys)}๊ฐ")
print(f"key ์ต์ 0x{min(keys):016x} ์ต๋ 0x{max(keys):016x}")
print()
print("๋ชฉํ ๋ฒํผ 0x459a0 (64๋ฐ์ดํธ):")
tgt = bytes.fromhex(net["target"])
for r in range(0, 64, 16):
print(f" +0x{r:02x} " + " ".join(f"{c:02x}" for c in tgt[r:r + 16]))python3 show_table.py
key 10000๊ฐ๊ฐ ์ ๋ถ ์๋ก ๋ค๋ฅด๋ค. ๊ฐ๋ 0x0000101d... ๋ถํฐ 0xfffa8927... ๊น์ง 64๋นํธ ์ ๋ฒ์์
ํผ์ ธ ์๋ค. ์ด๋ฐ ์ซ์๋ฅผ ๋ณด๋ฉด ํด์๋ ๋์ ์๋์ฒ๋ผ ๋ณด์ด์ง๋ง, ์ค์ ๋ก๋ 4์ง์ 32์๋ฆฌ ๊ฒฝ๋ก์ผ ๋ฟ์ด๋ค.
๐ฏ ๋ฐํ์์ผ๋ก ํ ๋ฒ ๋ ํ์ธ
์ฌ๊ธฐ๊น์ง๋ ์ ๋ถ ์ ์ ๋ถ์์ด๋ค. ๊ทธ๋ํ๋ฅผ ์๋ชป ๊ฑธ์์ ๊ฐ๋ฅ์ฑ์ด ๋จ์ ์์ผ๋ ์ค์ ๋ก ๋๋ ค์ ๋ง์ถฐ ๋ณธ๋ค.
gdb ๋ฐฐ์น ๋ชจ๋๋ก ์ธ ๊ฐ์ง๋ฅผ ํ์ธํ๋ค.
- 0๋ฒ ์ํธ๋ฆฌ์
keyยทoff_aยทoff_b๊ฐ ์ ์ ์ผ๋ก ์ฝ์ ๊ฐ๊ณผ ๊ฐ์๊ฐ - ๊ทธ key ๊ฐ ์ค์ ๋ก
extract.py๊ฐ ์์ธกํ ๋ฆฌํ(sub)๋ก ๋จ์ด์ง๋๊ฐ - ์ฐ๋ฆฌ๊ฐ ๋ง๋ ์
๋ ฅ์ ๋ฃ์์ ๋
memcmp์ง์ ๋ ๋ฒํผ๊ฐ ์ ๋ง ๊ฐ์๊ฐ
PIE ๋ผ์ ๋ฒ ์ด์ค ์ฃผ์๊ฐ ํ์ํ๋ฐ, 0x555555554000 ์ ๊ด๋ก๋ก ๊ฐ์ ํ์ง ์๊ณ info files ๊ฐ
์๋ ค ์ฃผ๋ ์ฌ๋ฐฐ์น๋ .text ์์์์ ์ญ์ฐํ๋ค. ASLR ์ค์ ์ด ๋ค๋ฅธ ํ๊ฒฝ์์๋ ๊ทธ๋๋ก ๋๋ค.
# Function Network โ ๋ฐํ์ ๊ฒ์ฆ (gdb -q -batch -x runtime.gdb --args ./extracted/chal)
# 1) PIE ๋ฒ ์ด์ค๋ ๊ด๋ก๋ก ๊ฐ์ ํ์ง ์๊ณ `info files` ์ ์ฌ๋ฐฐ์น๋ .text ์์ ์ญ์ฐํ๋ค
# 2) main ์ dispatch ํธ์ถ ์ง์ ์์ 0๋ฒ ์ํธ๋ฆฌ์ key / off_a / off_b ๋ฅผ ์ค๋ฌผ๋ก ํ์ธ
# 3) ๊ทธ key ๊ฐ ์ค์ ๋ก ์ด๋ ๋ฆฌํ๋ก ๋จ์ด์ง๋์ง ๋ฆฌํ 4๊ณณ์ ๋ธ๋ ์ดํฌํฌ์ธํธ๋ฅผ ๊ฑธ์ด ์ก๋๋ค
# 4) memcmp ์ง์ ์์
๋ฒํผ์ ๋ชฉํ๋ฒํผ๊ฐ ์ค์ ๋ก ๊ฐ์์ง 64๋ฐ์ดํธ ํต์งธ๋ก ๋น๊ต
set pagination off
set confirm off
starti
python
import re, gdb
q = lambda c: gdb.execute(c, to_string=True)
u64 = lambda r: int(gdb.parse_and_eval(r)) & 0xFFFFFFFFFFFFFFFF
byte = lambda p: int(gdb.parse_and_eval("*(unsigned char*)%d" % p))
base = int(re.search(r"0x([0-9a-f]+) - 0x[0-9a-f]+ is \.text$",
q("info files"), re.M).group(1), 16) - 0x1120
gdb.write("PIE base = 0x%x (.text ์ฌ๋ฐฐ์น๊ฐ์์ ์ญ์ฐ)\n" % base)
LEAF = {0x6f4c: "add", 0x6f8f: "xor", 0x6fd0: "sub", 0x7017: "swap"}
q("break *0x%x" % (base + 0x728d)) # main ์ dispatch ํธ์ถ
q("run < input.txt")
key, pa, pb = u64("$rdi"), u64("$rsi"), u64("$rdx")
buf = u64("$rbp") - 0x90
gdb.write("work buffer = 0x%x (rbp-0x90)\n" % buf)
gdb.write("entry[0] key = 0x%016x base4 ํ์8์๋ฆฌ %s\n"
% (key, "".join(str((key >> (2 * j)) & 3) for j in range(8))))
gdb.write("entry[0] off_a/b = %d / %d\n" % (pa - buf, pb - buf))
a, b = byte(pa), byte(pb)
gdb.write("ํธ์ถ ์ : buf[%d]=0x%02x buf[%d]=0x%02x\n" % (pa - buf, a, pb - buf, b))
q("delete")
for off in LEAF:
q("break *0x%x" % (base + off))
q("continue")
hit = u64("$pc") - base
gdb.write("๋๋ฌํ ๋ฆฌํ = 0x%x -> %s (extract.py ์์ธก: sub)\n" % (hit, LEAF[hit]))
q("finish")
gdb.write("ํธ์ถ ํ : buf[%d]=0x%02x buf[%d]=0x%02x (0x%02x-0x%02x=0x%02x, ์ a ๊ฐ b ์๋ฆฌ๋ก)\n"
% (pa - buf, byte(pa), pb - buf, byte(pb), a, b, (a - b) & 0xFF))
q("delete")
q("break *0x%x" % (base + 0x72c2)) # memcmp ํธ์ถ ์ง์
q("continue")
tgt, wrk = u64("$rdi"), u64("$rsi")
inf = gdb.selected_inferior()
tb, wb = bytes(inf.read_memory(tgt, 64)), bytes(inf.read_memory(wrk, 64))
gdb.write("memcmp target[:16]= %s\n" % tb[:16].hex())
gdb.write("memcmp work [:16]= %s\n" % wb[:16].hex())
gdb.write("64๋ฐ์ดํธ ์ ์ฒด ์ผ์น = %s\n" % (tb == wb))
endinput.txt ์๋ ๋ณต์ํ 64๊ธ์๋ฅผ ๋ฃ์ด ๋๋ค.
gdb -q -batch -x runtime.gdb --args ./extracted/chal
buf[42] ๊ฐ 0x31(๊ธ์ 1), buf[22] ๊ฐ 0x36(๊ธ์ 6) ์ด์๊ณ , ํธ์ถ์ด ๋๋์
buf[42] ๊ฐ 0xfb, buf[22] ๊ฐ 0x31 ์ด ๋๋ค. 0x31 - 0x36 = 0xfb ์ด๊ณ ์ a ๊ฐ b ์๋ฆฌ๋ก
๊ฐ๋ค โ ์ ์ ์ผ๋ก ์ฝ์ sub ์ ์ ์ ๊ทธ๋๋ก๋ค.
๋ง์ง๋ง ์ค์ด ๊ฒฐ๋ก ์ด๋ค. memcmp ์ง์ ์ ์์
๋ฒํผ์ ๋ชฉํ๋ฒํผ๊ฐ 64๋ฐ์ดํธ ์ ๋ถ ๊ฐ๋ค.
๋น๊ต๊ฐ ์ฑ๊ณตํ๋ค๋ ๊ฑธ memcmp ์ ๋ค์ด๊ฐ ๋ณด๊ณ ๋ ์ ์ ์๋ค.
๐ Full Exploit
์ญ์ฐ์ network.json ํ๋๋ง ์์ผ๋ฉด ๋๋ค. ์๋ฐฉํฅ ์๋ฎฌ๋ ์ดํฐ๋ ๊ฐ์ด ๋ฃ์ด์, ๋ณต์ํ ๊ฐ์
๋ค์ ์์ผ๋ก ๊ตด๋ ค ๋ชฉํ๊ฐ์ด ๋์ค๋์ง ์ค์ค๋ก ๋์กฐํ๊ฒ ํ๋ค.
#!/usr/bin/env python3
"""Function Network (Dreamhack, Gold 1 / reversing) โ solver
main ์ 64๋ฐ์ดํธ ์
๋ ฅ์ ์์
๋ฒํผ๋ก ๋ณต์ฌํ ๋ค, .data ์ (key, off_a, off_b) 10000๊ฐ๋ฅผ
์์๋๋ก dispatch(key, &buf[off_a], &buf[off_b]) ๋ก ํ๋ ค๋ณด๋ด๊ณ ๊ฒฐ๊ณผ๋ฅผ 0x459a0 ์
64๋ฐ์ดํธ์ memcmp ํ๋ค.
key ๋ 4์ง ํธ๋ผ์ด(๋์คํจ์ฒ 125๊ฐ)๋ฅผ 32๋จ๊ณ ํ๊ณ ๋ด๋ ค๊ฐ ๋ฆฌํ ์ฐ์ฐ 4๊ฐ ์ค ํ๋๋ก ๋จ์ด์ง๋ค.
๋ฆฌํ๋ ์ ๋ถ (a,b) -> (f(a,b), a) ๊ผด์ด๋ผ **์ ๋จ์ฌ**๋ค. ๊ทธ๋์ ๋ชฉํ๊ฐ์์ ์ญ์์ผ๋ก
10000๋ฒ ๋๋๋ฆฌ๋ฉด ์
๋ ฅ์ด ๊ทธ๋๋ก ๋์จ๋ค.
a = buf[off_a], b = buf[off_b] ์ผ ๋ ์๋ฐฉํฅ์
add : buf[off_a], buf[off_b] = (a+b)&0xff, a
sub : buf[off_a], buf[off_b] = (a-b)&0xff, a
xor : buf[off_a], buf[off_b] = a^b, a
swap: buf[off_a], buf[off_b] = b, a
์ญ๋ฐฉํฅ์ ์ b ๊ฐ ๊ณง ์ a ์ด๋ฏ๋ก
a = new_b, b = {add: new_a-a, sub: a-new_a, xor: new_a^a, swap: new_a}
network.json ์ extract.py ๊ฐ ๋ฐ์ด๋๋ฆฌ์์ ๋ฝ์ ๋ ๊ฒ์ด๋ค(๋์คํจ์ฒ ๊ทธ๋ํ + ํ
์ด๋ธ + ๋ชฉํ๊ฐ).
"""
import json
from pathlib import Path
BASE = Path(__file__).resolve().parent
net = json.load(open(BASE / "network.json"))
ops = [(n, a, b) for n, a, b in net["ops"]]
target = bytearray(bytes.fromhex(net["target"]))
def forward(buf, ops):
buf = bytearray(buf)
for name, oa, ob in ops:
a, b = buf[oa], buf[ob]
if name == "add": t = (a + b) & 0xFF
elif name == "sub": t = (a - b) & 0xFF
elif name == "xor": t = a ^ b
else: t = b # swap
buf[ob] = a
buf[oa] = t
return bytes(buf)
def backward(buf, ops):
buf = bytearray(buf)
for name, oa, ob in reversed(ops):
na, nb = buf[oa], buf[ob]
a = nb # ์ buf[off_a]
if name == "add": b = (na - a) & 0xFF
elif name == "sub": b = (a - na) & 0xFF
elif name == "xor": b = na ^ a
else: b = na # swap
buf[oa] = a
buf[ob] = b
return bytes(buf)
flag_body = backward(target, ops)
print("recovered :", flag_body)
print("printable :", all(0x20 <= c < 0x7F for c in flag_body))
check = forward(flag_body, ops)
print("forward :", check.hex())
print("target :", bytes(target).hex())
print("MATCH" if check == bytes(target) else "MISMATCH")
print()
print("FLAG: DH{%s}" % flag_body.decode())backward ์์ reversed(ops) ๋ก ๋๋ ๊ฒ ์ ๋ถ๊ณ , ๊ฐ ์คํ
์ ์ธ ์ค์ด๋ค.
python3 solve.py
๋ณต์๋ ๊ฐ์ด ์ ๋ถ ์ถ๋ ฅ ๊ฐ๋ฅํ ASCII ๋ก ๋์จ๋ค. ์ญ์ฐ ์ฝ๋๊ฐ ํ๋ ธ๋ค๋ฉด ์ด๋ด ํ๋ฅ ์ด ์ฌ์ค์ ์์ผ๋
(64๋ฐ์ดํธ๊ฐ ์ฐ์ฐํ ์ ๋ถ 0x20~0x7e ์ ๋ค์ด๊ฐ ํ๋ฅ ์ (95/256)^64) ์ด๊ฒ ์์ฒด๊ฐ ๊ฐ๋ ฅํ ์์ฒด ๊ฒ์ฆ์ด๋ค.
๊ฒ๋ค๊ฐ ๋ด์ฉ์ด ์๋ฌธ์ 16์ง์ 64๊ธ์, ์ฆ SHA-256 ํด์ ๋ชจ์์ด๋ค.
๋ง์ง๋ง์ผ๋ก ์ง์ง ๋ฐ์ด๋๋ฆฌ์ ๋จน์ฌ ๋ณธ๋ค. ํ์ดํ ๋์ subprocess ๋ก ๋ฃ์๋ค.
#!/usr/bin/env python3
"""๋ณต์ํ ์
๋ ฅ์ ์ค์ ๋ฐ์ด๋๋ฆฌ์ ๋จน์ฌ flag ๊ฐ ๋์ค๋์ง ํ์ธํ๋ค."""
import subprocess
from pathlib import Path
BASE = Path(__file__).resolve().parent
BODY = "b510ce2d7599e75f79b6bf613384dda282bcedea47184155e55d1d22139afaab"
r = subprocess.run([str(BASE / "extracted" / "chal")], input=BODY + "\n",
capture_output=True, text=True, timeout=60)
print("Input :", BODY, f"({len(BODY)} chars)")
print("Output :", r.stdout.strip())
print("Verdict:", "OK" if "Yes, the flag is" in r.stdout else "FAIL")python3 verify.py
DH{b510ce2d7599e75f79b6bf613384dda282bcedea47184155e55d1d22139afaab}โป๏ธ ์ฌํ โ ๋ฐฐํฌ๋ณธ๋ง ์์ผ๋ฉด ๋๋ค
๋์ค์ ์ด ํด๋๋ง ๋ค๊ณ ์๋ flag ๊ฐ ๋ค์ ๋์ค๋๋ก ํ ๋ฐฉ ์คํฌ๋ฆฝํธ๋ฅผ ๋จ๊ฒผ๋ค. ์ถ์ถ๋ถํฐ ์ญ์ฐ, ์ค์ ๋ฐ์ด๋๋ฆฌ ๋์กฐ๊น์ง ํ ๋ฒ์ ๋๋ค.
#!/usr/bin/env bash
# Function Network (Dreamhack 1082, Gold 1 / reversing) โ ํ ๋ฐฉ ์ฌํ.
# ๋์คํจ์ฒ ๊ทธ๋ํยท์ฐ์ฐ ํ
์ด๋ธ ์ถ์ถ โ 10000 ์คํ
์ญ์ฐ โ ์ค์ ๋ฐ์ด๋๋ฆฌ๋ก ๋์กฐ.
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/chal
python3 extract.py
OUT=$(timeout 300 python3 solve.py 2>&1) || true
echo "$OUT" | tail -8
BODY=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | head -1 | sed 's/^DH{//;s/}$//')
REAL=$(printf '%s\n' "$BODY" | ./extracted/chal 2>&1 | grep -aoE 'DH\{[^}]+\}' | head -1)
echo
if [ -n "$REAL" ] && { [ -z "$EXPECT" ] || [ "$REAL" = "$EXPECT" ]; }; then
echo "โ
PASS $REAL"; exit 0
fi
echo "โ FAIL (๋ฐ์ด๋๋ฆฌ๊ฐ ๋ธ ๊ฐ: '${REAL:-์์}' / ๊ธฐ๋: '$EXPECT')"; exit 1./reproduce.sh
โถ๐ ์บก์ณ์ ์ด objdump ์ฃผ์ ํ์ผ 4๊ฐ
์ ๋์ค์ด์
๋ธ ์บก์ณ์ <-- ์ฃผ์์ sed ์นํ์ผ๋ก ์น์ ๊ฒ์ด๋ค. --cmd ์์ ์ธ๋ผ์ธ์ผ๋ก ์ฐ๋ฉด
๋ฐ์ดํ๊ฐ ๊นจ์ ธ์ ํ์ผ๋ก ๋ถ๋ฆฌํ๋ค. ์ค์ ์ถ๋ ฅ ์์ ์ฃผ์๋ง ๋ํ ๊ฒ์ด๊ณ ๋ช
๋ นยท์ฃผ์ยท๋ฐ์ดํธ๋ ์๋์ง ์์๋ค.
annot_main.sed
s|\(call .*<read@plt>\)|\1 <-- ์
๋ ฅ 0x41๋ฐ์ดํธ|
s|\(call .*<strlen@plt>\)|\1 <-- ๊ธธ์ด ๊ฒ์ฌ|
s|\(cmp rax,0x40\)|\1 <-- ์ ํํ 64๊ธ์์ฌ์ผ ํต๊ณผ|
s|\(lea rax,\[rbp-0x50\]\)|\1 <-- ์
๋ ฅ ๋ฒํผ|
s|\(lea rax,\[rbp-0x90\]\)|\1 <-- ์์
๋ฒํผ(์ฌ๊ธฐ์ ๋ณํ๋๋ค)|annot_loop.sed
s|\(# b020 .*\)|\1 <-- ํ
์ด๋ธ[i].key|
s|\(# b028 .*\)|\1 <-- ํ
์ด๋ธ[i].off_a|
s|\(# b030 .*\)|\1 <-- ํ
์ด๋ธ[i].off_b|
s|\(shl rax,0x3\)|\1 <-- i*3*8 = 24๋ฐ์ดํธ ์คํธ๋ผ์ด๋|
s|\(call 1209 .*\)|\1 <-- ๋์คํจ์ฒ(๊ณ ์ ์ฃผ์)|
s|\(cmp DWORD PTR \[rbp-0x98\],0x270f\)|\1 <-- i <= 9999, ์ด 10000ํ|
s|\(call .*<memcmp@plt>\)|\1 <-- ๋ชฉํ 64๋ฐ์ดํธ์ ๋์กฐ|
s|\(# 459a0 .*\)|\1 <-- ๋ชฉํ ๋ฒํผ|annot_disp.sed
s|\(and eax,0x3\)|\1 <-- key ํ์ 2๋นํธ = ์ด๋ฒ ๋ ๋ฒจ์ ๊ฐ๋ฆผ๊ธธ|
s|\(shr rax,0x2\)|\1 <-- 2๋นํธ ์๋นํ๊ณ ๋ค์ ๋ ๋ฒจ๋ก|
s|\(cmp rax,0x1\)|\1 <-- case 1|
s|\(cmp rax,0x2\)|\1 <-- case 2|annot_leaf.sed
s|\(add eax,edx\)$|\1 <-- add : t = a + b|
s|\(xor eax,edx\)$|\1 <-- xor : t = a ^ b|
s|\(sub eax,ecx\)$|\1 <-- sub : t = a - b|
s|\(mov BYTE PTR \[rax\],dl\)|\1 <-- ์ a ๋ฅผ b ์๋ฆฌ์ / t ๋ฅผ a ์๋ฆฌ์|โถ๐ ๊ทธ๋ํ ๊ตฌ์กฐ๋ฅผ ํ์ธํ verify_graph.py ์ ๋ฌธ
#!/usr/bin/env python3
"""๋์คํจ์ฒ ํ์ฑ์ด '์ด์
๋ฑ์ฅ ์์ = case ์์' ๊ฐ์ ์ ๊ธฐ๋์ง ์๋์ง ๊ฒ์ฆํ๋ค.
๊ฐ ๋์คํจ์ฒ์ 191๋ฐ์ดํธ๋ฅผ ํต์งธ๋ก ํ์ด `cmp rax,N` ์์์ ๊ทธ ๋ค์ call ์ ์ง์ง์ด
case0/1/2/default ๋ฅผ ๋ช
์์ ์ผ๋ก ํ์ ํ ๋ค, extract.py ์ ์์ ๊ธฐ๋ฐ ํ์ฑ๊ณผ ๋์กฐํ๋ค.
"""
import re, subprocess
from pathlib import Path
BIN = Path(__file__).resolve().parent / "extracted" / "chal"
asm = subprocess.run(["objdump", "-d", "-M", "intel", str(BIN)],
capture_output=True, text=True, check=True).stdout.split("\n")
starts = []
for i, l in enumerate(asm):
m = re.match(r"^\s+([0-9a-f]+):\s+f3 0f 1e fa\s+endbr64", l)
if m and i + 1 < len(asm) and "push" in asm[i + 1] and "rbp" in asm[i + 1]:
starts.append(int(m.group(1), 16))
starts.sort()
LEAF = {0x6F4C, 0x6F8F, 0x6FD0, 0x7017}
MAIN = 0x704F
idx = {}
for i, l in enumerate(asm):
m = re.match(r"^\s+([0-9a-f]+):", l)
if m:
idx.setdefault(int(m.group(1), 16), i)
bad = 0
for n, a in enumerate(starts):
if a in LEAF or a == MAIN:
continue
body = asm[idx[a]:idx[starts[n + 1]]]
# ์์ ๊ธฐ๋ฐ
seq = [int(m.group(1), 16) for l in body if (m := re.search(r"call\s+([0-9a-f]+)", l))]
# ์์ ๊ธฐ๋ฐ: test rax,rax(=0) / cmp rax,1 / cmp rax,2 / ๋๋จธ์ง default
explicit, pending = {}, None
for l in body:
if re.search(r"test\s+rax,rax", l):
pending = 0
elif (m := re.search(r"cmp\s+rax,0x([0-9a-f]+)", l)):
pending = int(m.group(1), 16)
elif (m := re.search(r"call\s+([0-9a-f]+)", l)):
explicit[3 if pending is None else pending] = int(m.group(1), 16)
pending = None
if [explicit.get(k) for k in (0, 1, 2, 3)] != seq:
bad += 1
print("๋ถ์ผ์น", hex(a), explicit, [hex(x) for x in seq])
print(f"๋์คํจ์ฒ {len(starts) - len(LEAF) - 1}๊ฐ โ ์์ ๊ธฐ๋ฐ ํ์ฑ๊ณผ ์์ ๊ธฐ๋ฐ ํ์ฑ ๋ถ์ผ์น {bad}๊ฑด")
# ํธ๋ฆฌ์ธ๊ฐ DAG ์ธ๊ฐ
from collections import Counter
refs = Counter()
for n, a in enumerate(starts):
if a in LEAF or a == MAIN:
continue
body = asm[idx[a]:idx[starts[n + 1]]]
for l in body:
if (m := re.search(r"call\s+([0-9a-f]+)", l)):
refs[int(m.group(1), 16)] += 1
print("์์ ์ฌ๋กฏ ์ดํฉ:", sum(refs.values()), "ยท ์๋ก ๋ค๋ฅธ ์์:", len(refs))
print("๊ฐ์ฅ ๋ง์ด ์ฐธ์กฐ๋ ๋
ธ๋ 5๊ฐ:", [(hex(k), v) for k, v in refs.most_common(5)])
print("๋ฆฌํ ์ฐธ์กฐ ํ์:", {hex(k): refs[k] for k in sorted(LEAF)})
# --- ๊ณ์ธต ๊ตฌ์กฐ ------------------------------------------------------------
children = {}
for n, a in enumerate(starts):
if a in LEAF or a == MAIN:
continue
body = asm[idx[a]:idx[starts[n + 1]]]
children[a] = [int(m.group(1), 16) for l in body
if (m := re.search(r"call\s+([0-9a-f]+)", l))]
level, frontier, seen = {0x1209: 0}, [0x1209], {0x1209}
while frontier:
nxt = []
for u in frontier:
for v in children.get(u, []):
if v not in seen:
seen.add(v); level[v] = level[u] + 1; nxt.append(v)
assert level[v] == level[u] + 1, (hex(u), hex(v))
frontier = nxt
byl = Counter(level.values())
print("๋ ๋ฒจ๋ณ ๋
ธ๋ ์:", dict(sorted(byl.items())))
print("์ต๋ ๊น์ด:", max(level.values()), "ยท ๋ฆฌํ ๋ ๋ฒจ:", sorted({level[a] for a in LEAF}))๋ก์ปฌ ํ๊ฒฝ
| ํญ๋ชฉ | ๊ฐ |
|---|---|
| OS | Ubuntu (Linux 6.17, x86-64) |
| ๋๊ตฌ | objdump (GNU binutils), readelf, GNU gdb 16.3 |
| ํ์ด์ฌ | 3.13 โ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง ์ฌ์ฉ(์ธ๋ถ ํจํค์ง ์์) |
| ์คํ ์๊ฐ | extract.py ์ฝ 2์ด, solve.py 1์ด ๋ฏธ๋ง |
๐ ๊ฒฐ๋ก
๋๋ ํ์ ํฌ๊ธฐ์ ๊ฐ๋๋ ๋ค๋ฅด๋ค.
์ด ๋ฐ์ด๋๋ฆฌ๋ ํจ์ 129๊ฐ์ 240KB์ง๋ฆฌ ํ ์ด๋ธ์ ์น์๋ค. ๊ฒ๋ณด๊ธฐ ๊ท๋ชจ๋ง ๋ณด๋ฉด ์๋ ์๋๊ฐ ์ ๋๋ค.
๊ทธ๋ฐ๋ฐ ๊ทธ ๊ตฌ์กฐ๋ฌผ์ด ๋ง๋ค์ด ๋ด๋ ์ ๋ณด๋์ ๊ฒฐ๊ตญ ์ฐ์ฐ 4์ข
์ค ํ๋๋ค. ํธ๋ผ์ด๋ฅผ 32๋จ๊ณ ๋ด๋ ค๊ฐ๋
switch (key & 3) ํ ์ค์ ์ฐ๋ ๊ฒฐ๊ณผ๋ ๊ฐ๋ค. ๊ท๋ชจ๋ฅผ ํค์ด๋ค๊ณ ๋๋๋ฆฌ๊ธฐ ์ด๋ ค์์ง์ง๋ ์์๋ค.
๊ฐ์ญ ์ฐ์ฐ๋ง ์ฐ๋ฉด ๋๋๋ฆด ์ ์๋ค.
add ยท sub ยท xor ยท swap ์ ์ ๋ถ ์ ๋จ์ฌ๊ณ , ์ด ๋ฌธ์ ๋ ๊ทธ์ค ํ๋๋ ๊นจ์ง ์์๋ค. *q = a ๋ก
์ ๊ฐ์ ๋ณด์กดํ๋ ํจํด์ด ํนํ ๊ฒฐ์ ์ ์ด๋ค. ํ์ชฝ ์
๋ ฅ์ ๊ฒฐ๊ณผ์ ๊ทธ๋๋ก ๋จ๊ฒจ ๋๋ฉด ๋๋จธ์ง ํ๋๋
์ฐ์ฐ์ ๋ค์ง๊ธฐ๋ง ํ๋ฉด ๋๋ค.
์ค์ ๋ก ๋๋๋ฆด ์ ์๊ฒ ๋ง๋ค๋ ค๋ฉด ์ ๋ณด๋ฅผ ๋ฒ๋ ค์ผ ํ๋ค. buf[off_a] = a + b ๋ง ํ๊ณ buf[off_b] ๋
๊ทธ๋๋ก ๋๋ ์์ด๋ฉด ๋งค ์คํ
์์ ๊ฐ ํ๋๊ฐ ์ฌ๋ผ์ ธ ์ญ์ฐ์ด ๊ฐ์ง๋ฅผ ์น๋ค. ์๋๋ฉด off_a == off_b ์ธ
์ค์ ์๊ฑฐ๋(t = a + a ๋ ์ต์์ ๋นํธ๋ฅผ ๋ฒ๋ฆฐ๋ค), ์ ์ด์ ๊ฒฐ๊ณผ๋ฅผ ๋ชฉํ๊ฐ๊ณผ ๋น๊ตํ๋ ๋์ ํด์๋ฅผ
๋น๊ตํ๋ฉด ์ด ํ์ด๋ ํต์งธ๋ก ๋งํ๋ค.
์ ์ ๋ถ์์ ๊ฐ์ ์ ํ์ธํ๊ณ ๋์ด๊ฐ๋ ํธ์ด ์ธ๋ค.
์ด๋ฒ์ ๋ ๋ฒ ๋ฉ์ถฐ์ ํ์ธํ๋ค. ๋์คํจ์ฒ์ case ์์๊ฐ ์ด์
๋ฑ์ฅ ์์์ ๊ฐ์์ง, ๊ทธ๋ฆฌ๊ณ
off_a == off_b ์ธ ์ค์ด ์๋์ง.
๋ ๋ค "๋ง๊ฒ ๊ฑฐ๋" ํ๊ณ ๋์ด๊ฐ๋ ์ฝ๋๋ ์ ์์ ์ผ๋ก ๋๊ณ ๊ฒฐ๊ณผ๋ง ์กฐ์ฉํ ํ๋ฆฐ๋ค. ๊ทธ ์ํ์์ ๋๋ฒ๊น ์ ์์ํ๋ฉด ์ด๋์๋ถํฐ ๋ด์ผ ํ ์ง ๊ฐ์ด ์ ์กํ๋ค. ํ์ธ์ ๋ ์๊ฐ์ ๋ช ๋ถ์ด์๋ค.
๋ณต์๊ฐ์ด ์ค์ค๋ก ๋ง์ด ๋๋์ง ๋ด๋ผ.
์ญ์ฐ ๊ฒฐ๊ณผ๊ฐ ์ถ๋ ฅ ๊ฐ๋ฅํ ASCII 64๊ธ์, ๊ทธ๊ฒ๋ ์๋ฌธ์ 16์ง์์๋ค. ์ฑ์ ์ ๋๋ฆฌ๊ธฐ ์ ์ ์ด๋ฏธ "๋ง์๋ค"๋ ์ ํธ๊ฐ ๋์จ ์ ์ด๋ค. ๋ณต์ ๊ฒฐ๊ณผ๊ฐ ์๋ฏธ ์๋ ํํ๋ฅผ ๊ฐ์ถ๋์ง ๋ณด๋ ๊ฑด ๊ณต์ง์ธ๋ฐ ๋์น๊ธฐ ์ฝ๋ค.
Comments
๋๊ธ
๋๊ธ์ ๋จ๊ธฐ๋ ค๋ฉด ๋ก๊ทธ์ธ์ด ํ์ํด์. (๋ค์ด๋ฒ ยท ๊ตฌ๊ธ ๊ณ์ )
๋๊ธ ๋ถ๋ฌ์ค๋ ์คโฆ