[๐Ÿฅ‡ Gold 3] ๊ณต์œ  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ 1,257๊ฐœ๋กœ ๋งŒ๋“  ์˜คํ† ๋งˆํƒ€ โ€” DreamHack .so what? ํ’€์ด

2026-08-07ยท1๋ถ„ ์ฝ๊ธฐยท

[๐Ÿฅ‡ Gold 3] ๊ณต์œ  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ 1,257๊ฐœ๋กœ ๋งŒ๋“  ์˜คํ† ๋งˆํƒ€ โ€” DreamHack .so what? ํ’€์ด

main ์€ 64์ž hex ๋ฅผ ๋ฐ›์•„ dlopen ๊ณผ dlsym ๋งŒ์œผ๋กœ ๊ฒ€์‚ฌํ•œ๋‹ค. .so ํ•˜๋‚˜๊ฐ€ ์ƒํƒœ์ด๊ณ  f_d ๊ฐ€ ๋ผ๋ฒจ d ์ธ ์ „์ด๋ผ, ํŒŒ์ผ 1,257๊ฐœ๊ฐ€ ํ†ต์งธ๋กœ ๊ฒฐ์ •์  ์˜คํ† ๋งˆํƒ€๋‹ค. ์ „์ดํ‘œ๋ฅผ objdump ๋กœ ๋ฝ‘์•„ ๊ธธ์ด 63 ๊ฒฝ๋กœ๋ฅผ ์ฐพ์œผ๋ฉด ๊ฐ„์„  19,792๊ฐœ ์ค‘ ์„ฑ๊ณต ๊ฒฝ๋กœ๊ฐ€ ์ •ํ™•ํžˆ ํ•˜๋‚˜ ๋‚˜์˜จ๋‹ค.

๋ฌธ์ œ: DreamHack โ€” .so what? ๋ถ„๋ฅ˜: reversing ๋‚œ์ด๋„: ๐Ÿฅ‡ Gold 3 FLAG: DH{20654ccdb7c43bd1ab398283f9895ac285e8c419c4c157db2f3f50de92599bd8}

๋ฌธ์ œ ์„ค๋ช…์€ ํ•œ ์ค„์ด๋‹ค.

... .so... wait, what???

์••์ถ•์„ ํ’€๋ฉด ์™œ ์ €๋ ‡๊ฒŒ ์ผ๋Š”์ง€ ๋ฐ”๋กœ ์•Œ๊ฒŒ ๋œ๋‹ค.

file extracted/main; ls extracted/lib | wc -l; find extracted/lib -name "*.so" -printf "%s\n" | sort -n | uniq -c

file ๊ณผ ํฌ๊ธฐ ๋ถ„ํฌ ํ™•์ธ โ€” main ์€ PIE ์‹คํ–‰ ํŒŒ์ผ์ด๊ณ  lib ์•„๋ž˜ so ํŒŒ์ผ์ด 1257๊ฐœ์ด๋ฉฐ ํฌ๊ธฐ๊ฐ€ 15560์ด 20๊ฐœ, 16192๊ฐ€ 1๊ฐœ, 16200์ด 1236๊ฐœ๋กœ ์„ธ ์ข…๋ฅ˜๋ฟ์ด๋‹ค
file ๊ณผ ํฌ๊ธฐ ๋ถ„ํฌ ํ™•์ธ โ€” main ์€ PIE ์‹คํ–‰ ํŒŒ์ผ์ด๊ณ  lib ์•„๋ž˜ so ํŒŒ์ผ์ด 1257๊ฐœ์ด๋ฉฐ ํฌ๊ธฐ๊ฐ€ 15560์ด 20๊ฐœ, 16192๊ฐ€ 1๊ฐœ, 16200์ด 1236๊ฐœ๋กœ ์„ธ ์ข…๋ฅ˜๋ฟ์ด๋‹ค

.so ํŒŒ์ผ์ด 1,257๊ฐœ๋‹ค.


๋ฌธ์ œ ๊ฐœ์š”

ํ•ญ๋ชฉ๋‚ด์šฉ
๋ฌธ์ œ๋ช….so what? (id 1347)
๋‚œ์ด๋„๐Ÿฅ‡ Gold 3
๋ถ„๋ฅ˜reversing
์ œ๊ณต ํŒŒ์ผmain (PIE, not stripped) + lib/*.so 1,257๊ฐœ (20MB)
์ž„ํฌํŠธread ยท printf ยท puts ยท dlopen ยท dlsym ยท exit
ํ•ต์‹ฌ.so = ์ƒํƒœ, f_d = ๋ผ๋ฒจ d ์ธ ์ „์ด โ†’ 1,257์ƒํƒœ ๊ฒฐ์ •์  ์˜คํ† ๋งˆํƒ€
๋‹ต64์ž hex, ๊ฐ„์„  19,792๊ฐœ ์ค‘ ์„ฑ๊ณต ๊ฒฝ๋กœ 1๊ฐœ

ํฌ๊ธฐ๊ฐ€ ์„ธ ์ข…๋ฅ˜๋ฟ์ด๋ผ๋Š” ๊ฒŒ ์ฒซ ๋‹จ์„œ๋‹ค. 16,200๋ฐ”์ดํŠธ๊ฐ€ 1,236๊ฐœ, 15,560๋ฐ”์ดํŠธ๊ฐ€ 20๊ฐœ, ๊ทธ๋ฆฌ๊ณ  16,192๋ฐ”์ดํŠธ๊ฐ€ ํ•˜๋‚˜. ์†Œ์ˆ˜ํŒŒ๋ฅผ ๋จผ์ € ๋ณด๋ฉด ๋œ๋‹ค.

ํ’€์ด ํ๋ฆ„์€ ์ด๋ ‡๊ฒŒ ํ˜๋Ÿฌ๊ฐ”๋‹ค. main ์ด dlopen/dlsym ๋งŒ ์“ด๋‹ค๋Š” ๊ฑธ ๋ณด๊ณ  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํƒ€๊ณ  ๊ฐ€๋Š” ๊ตฌ์กฐ๋ผ๊ณ  ์ง์ž‘ํ–ˆ๋‹ค. .so ํ•˜๋‚˜๋ฅผ ์—ด์–ด ๋ณด๋‹ˆ ํ•จ์ˆ˜๊ฐ€ f_0~f_f ์—ด์—ฌ์„ฏ ๊ฐœ์˜€๊ณ , ๊ฐ๊ฐ ๋‹ค์Œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์—ฌ๋Š” ๊ฒƒ๊นŒ์ง€ ํ™•์ธํ•˜๋ฉด์„œ ์˜คํ† ๋งˆํƒ€๋ผ๋Š” ๊ฒŒ ํ™•์ •๋๋‹ค.

๊ฑฐ๊ธฐ์„œ๋ถ€ํ„ฐ๋Š” ๋ฆฌ๋ฒ„์‹ฑ์ด ์•„๋‹ˆ๋ผ ๊ทธ๋ž˜ํ”„ ๋ฌธ์ œ๋‹ค. ์ „์ดํ‘œ๋ฅผ ๋ฝ‘๊ณ  ๊ฒฝ๋กœ๋ฅผ ์ฐพ์œผ๋ฉด ๋๋‚œ๋‹ค.



๐Ÿงฉ ๋ฐฐ๊ฒฝ โ€” dlopen ๊ณผ dlsym

๋‘ ํ•จ์ˆ˜๋งŒ ์•Œ๋ฉด ์ด ๋ฌธ์ œ์˜ ๊ณจ๊ฒฉ์ด ๋ณด์ธ๋‹ค.

dlopen(๊ฒฝ๋กœ, ํ”Œ๋ž˜๊ทธ) ๋Š” ๊ณต์œ  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‹คํ–‰ ์ค‘์— ๋ฉ”๋ชจ๋ฆฌ๋กœ ์˜ฌ๋ฆฐ๋‹ค. ์ปดํŒŒ์ผํ•  ๋•Œ ๋งํฌํ•ด ๋‘๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ํ”„๋กœ๊ทธ๋žจ์ด ๋„๋Š” ๋„์ค‘์— ํŒŒ์ผ ์ด๋ฆ„์„ ์ •ํ•ด์„œ ๋ถ€๋ฅผ ์ˆ˜ ์žˆ๋‹ค. ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ตฌ์กฐ๊ฐ€ ๋Œ€ํ‘œ์ ์ธ ์šฉ๋„๋‹ค.

dlsym(ํ•ธ๋“ค, ์ด๋ฆ„) ์€ ๊ทธ๋ ‡๊ฒŒ ์˜ฌ๋ฆฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์ด๋ฆ„์œผ๋กœ ์‹ฌ๋ณผ์„ ์ฐพ์•„ ์ฃผ์†Œ๋ฅผ ๋Œ๋ ค์ค€๋‹ค. ์ด๋ฆ„์ด ๋ฌธ์ž์—ด์ด๋‹ˆ, ๊ทธ ๋ฌธ์ž์—ด์„ ์‹คํ–‰ ์ค‘์— ์กฐ๋ฆฝํ•  ์ˆ˜ ์žˆ๋‹ค.

๋‘˜์„ ํ•ฉ์น˜๋ฉด "์–ด๋–ค ํŒŒ์ผ์„ ์—ด์ง€"๋„ "์–ด๋–ค ํ•จ์ˆ˜๋ฅผ ๋ถ€๋ฅผ์ง€"๋„ ๋Ÿฐํƒ€์ž„์— ์ •ํ•ด์ง„๋‹ค. ์ •์  ๋ถ„์„๊ธฐ๊ฐ€ ํ˜ธ์ถœ ๊ทธ๋ž˜ํ”„๋ฅผ ๊ทธ๋ฆด ์ˆ˜ ์—†๋‹ค๋Š” ๋œป์ด๊ธฐ๋„ ํ•˜๋‹ค.

์ด ๋ฌธ์ œ๋Š” ๊ทธ ์„ฑ์งˆ์„ ๊ทธ๋Œ€๋กœ ๋ฌด๊ธฐ๋กœ ์“ด๋‹ค. ํŒŒ์ผ ์ด๋ฆ„๊ณผ ์‹ฌ๋ณผ ์ด๋ฆ„์„ ์ž…๋ ฅ ๊ธ€์ž๋กœ ๊ฒฐ์ •ํ•˜๊ฒŒ ๋งŒ๋“ค์–ด, ์ฝ”๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ํŒŒ์ผ ๋ฐฐ์น˜ ์ž์ฒด์— ์ •๋‹ต์„ ์ˆจ๊ฒจ ๋’€๋‹ค.

๋ฌด์—‡์ด ๋“ค์–ด ์žˆ๋‚˜

nm -D extracted/main | grep " U "; strings -a extracted/main | grep -E "lib/|Flag|input|Wrong"

nm ์œผ๋กœ ๋ณธ main ์˜ ์ž„ํฌํŠธ์™€ ์ฃผ์š” ๋ฌธ์ž์—ด โ€” dlopen ๊ณผ dlsym ์ด ๋“ค์–ด ์žˆ๊ณ  lib ์Šฌ๋ž˜์‹œ start ์  so ๊ฒฝ๋กœ์™€ Flag is DH ์„œ์‹ ๋ฌธ์ž์—ด, ๋‘ ์ข…๋ฅ˜์˜ ์‹คํŒจ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ณด์ธ๋‹ค
nm ์œผ๋กœ ๋ณธ main ์˜ ์ž„ํฌํŠธ์™€ ์ฃผ์š” ๋ฌธ์ž์—ด โ€” dlopen ๊ณผ dlsym ์ด ๋“ค์–ด ์žˆ๊ณ  lib ์Šฌ๋ž˜์‹œ start ์  so ๊ฒฝ๋กœ์™€ Flag is DH ์„œ์‹ ๋ฌธ์ž์—ด, ๋‘ ์ข…๋ฅ˜์˜ ์‹คํŒจ ๋ฉ”์‹œ์ง€๊ฐ€ ๋ณด์ธ๋‹ค

์ž„ํฌํŠธ์— dlopenยทdlsym, ๋ฌธ์ž์—ด์— lib/start.so ์™€ Flag is DH{%s}. %s ๋กœ ์ฐ๋Š”๋‹ค๋Š” ๊ฑด ์šฐ๋ฆฌ ์ž…๋ ฅ์ด ๊ทธ๋Œ€๋กœ ํ”Œ๋ž˜๊ทธ๋ผ๋Š” ๋œป์ด๋‹ค.


๐Ÿ”ฌ main ์ฝ๊ธฐ

์ž…๋ ฅ์„ ๋ฐ›๋Š” ๊ณณ

# annot_main.sed โ€” ์ฃผ์†Œ๋กœ ์ค„์„ ์ง€์ •ํ•ด ์ฃผ์„์„ ์–น๋Š”๋‹ค
s|^    1368:.*|&      <-- setvbuf 3์ข…|
s|^    1394:.*|&                 <-- 0x41 = 65๋ฐ”์ดํŠธ ์ฝ๋Š”๋‹ค|
s|^    13a1:.*|&        <-- read(0, buf, 0x41)|
s|^    13a6:.*|&        <-- buf[64] = 0|
s|^    13b1:.*|&        <-- verify(buf) : 64๊ธ€์ž๊ฐ€ 0-9a-f ์ธ๊ฐ€|
s|^    13b6:.*|&           <-- 0x101 = RTLD_LAZY + RTLD_GLOBAL|
s|^    13c5:.*|&       <-- dlopen("lib/start.so")|
s|^    13ee:.*|&      <-- ์‹ฌ๋ณผ ์ด๋ฆ„ ๋งŒ๋“ค๊ธฐ : 'f'|
s|^    13f2:.*|&      <-- '_'|
s|^    13f6:.*|&    <-- buf[0]  โ†’ "f_<์ฒซ ๊ธ€์ž>"|
s|^    140f:.*|&        <-- dlsym(handle, "f_X")|
s|^    143c:.*|&                 <-- ์ธ์ž๋Š” buf+1 (ํ•œ ๊ธ€์ž ์†Œ๋น„)|
s|^    1447:.*|&                <-- ์ฐพ์€ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœ|
s|^    144b:.*|&    <-- 0 ์ด๋ฉด Wrong!|
s|^    1463:.*|&       <-- printf("Flag is DH{%s}", buf)|
objdump -d -M intel --no-show-raw-insn --start-address=0x1304 --stop-address=0x13ee extracted/main | sed -f annot_main.sed

objdump ๋กœ ๋ฝ‘์€ main ์•ž๋ถ€๋ถ„ โ€” read ๋กœ 0x41 ๋ฐ”์ดํŠธ๋ฅผ ๋ฐ›๊ณ  buf 64๋ฒˆ์งธ๋ฅผ 0 ์œผ๋กœ ๋งŒ๋“  ๋’ค verify ๋ฅผ ๋ถ€๋ฅด๊ณ , ํ†ต๊ณผํ•˜๋ฉด lib ์Šฌ๋ž˜์‹œ start ์  so ๋ฅผ dlopen ํ•œ๋‹ค
objdump ๋กœ ๋ฝ‘์€ main ์•ž๋ถ€๋ถ„ โ€” read ๋กœ 0x41 ๋ฐ”์ดํŠธ๋ฅผ ๋ฐ›๊ณ  buf 64๋ฒˆ์งธ๋ฅผ 0 ์œผ๋กœ ๋งŒ๋“  ๋’ค verify ๋ฅผ ๋ถ€๋ฅด๊ณ , ํ†ต๊ณผํ•˜๋ฉด lib ์Šฌ๋ž˜์‹œ start ์  so ๋ฅผ dlopen ํ•œ๋‹ค

read(0, buf, 0x41) ๋กœ 65๋ฐ”์ดํŠธ๋ฅผ ๋ฐ›๊ณ  buf[64] = 0. ๊ทธ๋ฆฌ๊ณ  verify(buf) ๋กœ ๊ฒ€์‚ฌํ•œ ๋’ค dlopen("lib/start.so", 0x101).

0x101 ์€ RTLD_LAZY | RTLD_GLOBAL ์ด๋‹ค. RTLD_GLOBAL ์ด ๋ถ™์–ด ์žˆ์–ด ๋‚˜์ค‘์— ์—ด๋ฆฌ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์ด ์„œ๋กœ์˜ ์‹ฌ๋ณผ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์‹ฌ๋ณผ ์ด๋ฆ„์„ ๋งŒ๋“ค์–ด ๋ถ€๋ฅด๋Š” ๊ณณ

objdump -d -M intel --no-show-raw-insn --start-address=0x13ee --stop-address=0x1494 extracted/main | sed -f annot_main.sed

objdump ๋กœ ๋ฝ‘์€ main ๋’ท๋ถ€๋ถ„ โ€” ์Šคํƒ์— f ์™€ ๋ฐ‘์ค„๊ณผ ์ž…๋ ฅ ์ฒซ ๊ธ€์ž๋ฅผ ๋„ฃ์–ด f_X ๋ผ๋Š” ์‹ฌ๋ณผ ์ด๋ฆ„์„ ๋งŒ๋“ค๊ณ  dlsym ์œผ๋กœ ์ฐพ์•„ buf+1 ์„ ๋„˜๊ฒจ ํ˜ธ์ถœํ•œ ๋’ค ๋ฐ˜ํ™˜๊ฐ’์ด 0 ์ด ์•„๋‹ˆ๋ฉด ํ”Œ๋ž˜๊ทธ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค
objdump ๋กœ ๋ฝ‘์€ main ๋’ท๋ถ€๋ถ„ โ€” ์Šคํƒ์— f ์™€ ๋ฐ‘์ค„๊ณผ ์ž…๋ ฅ ์ฒซ ๊ธ€์ž๋ฅผ ๋„ฃ์–ด f_X ๋ผ๋Š” ์‹ฌ๋ณผ ์ด๋ฆ„์„ ๋งŒ๋“ค๊ณ  dlsym ์œผ๋กœ ์ฐพ์•„ buf+1 ์„ ๋„˜๊ฒจ ํ˜ธ์ถœํ•œ ๋’ค ๋ฐ˜ํ™˜๊ฐ’์ด 0 ์ด ์•„๋‹ˆ๋ฉด ํ”Œ๋ž˜๊ทธ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค

์—ฌ๊ธฐ๊ฐ€ ํ•ต์‹ฌ์ด๋‹ค. ์Šคํƒ์— 'f', '_', buf[0], '\0' ์„ ์ฐจ๋ก€๋กœ ๋„ฃ์–ด "f_<์ฒซ ๊ธ€์ž>" ๋ผ๋Š” ๋ฌธ์ž์—ด์„ ๋งŒ๋“ค๊ณ , ๊ทธ ์ด๋ฆ„์œผ๋กœ dlsym ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ฐพ์€ ํ•จ์ˆ˜๋ฅผ buf+1 ๋กœ ํ˜ธ์ถœํ•œ๋‹ค.

C ๋กœ ์˜ฎ๊ธฐ๋ฉด ์ด๋ ‡๋‹ค.

int main(void)
{
    char buf[0x41] = {0};
    initialize();
    puts("Please give me your input!");
    printf("> ");
    read(0, buf, 0x41);
    buf[0x40] = 0;
 
    verify(buf);                                   // 64๊ธ€์ž๊ฐ€ 0-9a-f ์ธ์ง€
 
    void *h = dlopen("lib/start.so", RTLD_LAZY | RTLD_GLOBAL);
    if (!h) { puts("Failed to load a library."); exit(1); }
 
    char name[4] = { 'f', '_', buf[0], 0 };         // โ† ์‹ฌ๋ณผ ์ด๋ฆ„์„ ์ž…๋ ฅ์œผ๋กœ ๋งŒ๋“ ๋‹ค
    int (*fn)(char *) = dlsym(h, name);
    if (!fn) { puts("Failed to load a function."); exit(1); }
 
    if (fn(buf + 1))                                // โ† ํ•œ ๊ธ€์ž ์†Œ๋น„ํ•˜๊ณ  ๋„˜๊น€
        printf("Flag is DH{%s}\n", buf);
    else
        puts("Wrong!");
}

์ž…๋ ฅ์ด ๊ณง ํ”Œ๋ž˜๊ทธ์ด๋ฏ€๋กœ, ์šฐ๋ฆฌ๊ฐ€ ํ•  ์ผ์€ fn ์ด 0 ์•„๋‹Œ ๊ฐ’์„ ๋Œ๋ ค์ฃผ๊ฒŒ ๋งŒ๋“œ๋Š” 64์ž๋ฅผ ์ฐพ๋Š” ๊ฒƒ์ด๋‹ค.

์ž…๋ ฅ ์•ŒํŒŒ๋ฒณ

# annot_verify.sed
s|^    1299:.*|&                 <-- c <= '/' ์ด๋ฉด hex ๋ฌธ์ž ๊ฒ€์‚ฌ๋กœ|
s|^    12ad:.*|&                 <-- c <= '9' ์ด๋ฉด ํ†ต๊ณผ (0-9)|
s|^    12c1:.*|&                 <-- c <= '`' ์ด๋ฉด ์‹คํŒจ|
s|^    12d5:.*|&                 <-- c <= 'f' ์ด๋ฉด ํ†ต๊ณผ (a-f)|
s|^    12e3:.*|&        <-- ์•„๋‹ˆ๋ฉด "Wrong input." ํ›„ exit|
s|^    12fa:.*|&    <-- i <= 0x3f : ์ •ํ™•ํžˆ 64๊ธ€์ž|
objdump -d -M intel --no-show-raw-insn --start-address=0x1270 --stop-address=0x1304 extracted/main | sed -f annot_verify.sed

objdump ๋กœ ๋ฝ‘์€ verify ํ•จ์ˆ˜ โ€” ์ธ๋ฑ์Šค 0 ๋ถ€ํ„ฐ 0x3f ๊นŒ์ง€ ๋Œ๋ฉด์„œ ๊ฐ ๊ธ€์ž๊ฐ€ 0 ์—์„œ 9 ์‚ฌ์ด์ด๊ฑฐ๋‚˜ a ์—์„œ f ์‚ฌ์ด์ธ์ง€ ๊ฒ€์‚ฌํ•˜๊ณ  ์•„๋‹ˆ๋ฉด Wrong input ์„ ์ฐ๊ณ  ์ข…๋ฃŒํ•œ๋‹ค
objdump ๋กœ ๋ฝ‘์€ verify ํ•จ์ˆ˜ โ€” ์ธ๋ฑ์Šค 0 ๋ถ€ํ„ฐ 0x3f ๊นŒ์ง€ ๋Œ๋ฉด์„œ ๊ฐ ๊ธ€์ž๊ฐ€ 0 ์—์„œ 9 ์‚ฌ์ด์ด๊ฑฐ๋‚˜ a ์—์„œ f ์‚ฌ์ด์ธ์ง€ ๊ฒ€์‚ฌํ•˜๊ณ  ์•„๋‹ˆ๋ฉด Wrong input ์„ ์ฐ๊ณ  ์ข…๋ฃŒํ•œ๋‹ค

0x30~0x39(0-9)์™€ 0x61~0x66(a-f), ๊ทธ๋ฆฌ๊ณ  i <= 0x3f ๋ผ ์ •ํ™•ํžˆ 64๊ธ€์ž.

64์ž ์†Œ๋ฌธ์ž hex โ€” ํƒ์ƒ‰ ๊ณต๊ฐ„์€ 16โถโด ์ด๋‹ค. ๋ฌด์ฐจ๋ณ„ ๋Œ€์ž…์€ ์• ์ดˆ์— ๋…ผ์™ธ๊ณ , ๊ตฌ์กฐ๋ฅผ ๋ด์•ผ ํ•œ๋‹ค.



๐Ÿ’ฃ ํ•ต์‹ฌ โ€” .so ๊ฐ€ ์ƒํƒœ, f_d ๊ฐ€ ์ „์ด

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•˜๋‚˜๋ฅผ ์—ด์–ด ๋ณธ๋‹ค

nm -D --defined-only extracted/lib/start.so; echo ---; strings -a extracted/lib/start.so | grep -c "^lib/"

nm ์œผ๋กœ ๋ณธ start.so ์˜ ๋‚ด๋ณด๋‚ด๊ธฐ ์‹ฌ๋ณผ โ€” f_0 ๋ถ€ํ„ฐ f_f ๊นŒ์ง€ ์—ด์—ฌ์„ฏ ๊ฐœ๊ฐ€ ์ •ํ™•ํžˆ ์žˆ๊ณ  lib ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ๋กœ ๋ฌธ์ž์—ด๋„ ์—ด์—ฌ์„ฏ ๊ฐœ๋‹ค
nm ์œผ๋กœ ๋ณธ start.so ์˜ ๋‚ด๋ณด๋‚ด๊ธฐ ์‹ฌ๋ณผ โ€” f_0 ๋ถ€ํ„ฐ f_f ๊นŒ์ง€ ์—ด์—ฌ์„ฏ ๊ฐœ๊ฐ€ ์ •ํ™•ํžˆ ์žˆ๊ณ  lib ๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒฝ๋กœ ๋ฌธ์ž์—ด๋„ ์—ด์—ฌ์„ฏ ๊ฐœ๋‹ค

๋‚ด๋ณด๋‚ด๋Š” ํ•จ์ˆ˜๊ฐ€ f_0 ~ f_f ์ •ํ™•ํžˆ 16๊ฐœ. ๊ทธ๋ฆฌ๊ณ  lib/... ๊ฒฝ๋กœ ๋ฌธ์ž์—ด๋„ 16๊ฐœ.

hex ์ˆซ์ž ํ•˜๋‚˜๋‹น ํ•จ์ˆ˜ ํ•˜๋‚˜, ํ•จ์ˆ˜ ํ•˜๋‚˜๋‹น ๋‹ค์Œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ•˜๋‚˜. ์—ฌ๊ธฐ์„œ ๋ชจ์–‘์ด ์žกํžŒ๋‹ค.

f_d ์•ˆ์„ ๋ณธ๋‹ค

# annot_fd.sed
s|^    11b8:.*|&           <-- 0x101 = RTLD_LAZY + RTLD_GLOBAL|
s|^    11c7:.*|&       <-- dlopen("lib/<์ด ๊ธ€์ž์— ์ •ํ•ด์ง„ ๋‹ค์Œ>.so")|
s|^    11f0:.*|&      <-- 'f'|
s|^    11f4:.*|&      <-- '_'|
s|^    11fc:.*|&      <-- p[0] = ๋‹ค์Œ ๊ธ€์ž|
s|^    1214:.*|&       <-- dlsym(handle, "f_<๋‹ค์Œ ๊ธ€์ž>")|
s|^    1241:.*|&            <-- ์ธ์ž๋Š” p+1|
s|^    124c:.*|&                 <-- ์žฌ๊ท€ ํ˜ธ์ถœ. ๋ฐ˜ํ™˜๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๋„˜๊ธด๋‹ค|
objdump -d -M intel --no-show-raw-insn extracted/lib/start.so | sed -n "/<f_0>:/,/<f_1>:/p" | sed -f annot_fd.sed

objdump ๋กœ ๋ฝ‘์€ start.so ์˜ f_0 ํ•จ์ˆ˜ โ€” ์ •ํ•ด์ง„ ๋‹ค์Œ so ํŒŒ์ผ์„ dlopen ํ•˜๊ณ  ์ธ์ž ์ฒซ ๊ธ€์ž๋กœ f_ ์‹ฌ๋ณผ ์ด๋ฆ„์„ ๋งŒ๋“ค์–ด dlsym ํ•œ ๋’ค ์ธ์ž๋ฅผ ํ•˜๋‚˜ ์•ž์œผ๋กœ ๋ฐ€์–ด ์žฌ๊ท€ ํ˜ธ์ถœํ•˜๊ณ  ๋ฐ˜ํ™˜๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๋„˜๊ธด๋‹ค
objdump ๋กœ ๋ฝ‘์€ start.so ์˜ f_0 ํ•จ์ˆ˜ โ€” ์ •ํ•ด์ง„ ๋‹ค์Œ so ํŒŒ์ผ์„ dlopen ํ•˜๊ณ  ์ธ์ž ์ฒซ ๊ธ€์ž๋กœ f_ ์‹ฌ๋ณผ ์ด๋ฆ„์„ ๋งŒ๋“ค์–ด dlsym ํ•œ ๋’ค ์ธ์ž๋ฅผ ํ•˜๋‚˜ ์•ž์œผ๋กœ ๋ฐ€์–ด ์žฌ๊ท€ ํ˜ธ์ถœํ•˜๊ณ  ๋ฐ˜ํ™˜๊ฐ’์„ ๊ทธ๋Œ€๋กœ ๋„˜๊ธด๋‹ค

main ๊ณผ ๋˜‘๊ฐ™์€ ์ง“์„ ํ•œ๋‹ค.

int f_0(char *p)
{
    void *h = dlopen("lib/6fb3f5f2b4.so", RTLD_LAZY | RTLD_GLOBAL);  // โ† ์ด ๊ธ€์ž์— ๊ณ ์ •๋œ ๋‹ค์Œ ํŒŒ์ผ
    char name[4] = { 'f', '_', p[0], 0 };
    int (*fn)(char *) = dlsym(h, name);
    return fn(p + 1);                                                // ๋ฐ˜ํ™˜๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์˜ฌ๋ ค๋ณด๋‚ธ๋‹ค
}

์ •๋ฆฌํ•˜๋ฉด ์ด๋ ‡๋‹ค.

dlopen ๋Œ€์ƒ์€ "์ง€๊ธˆ ํ•จ์ˆ˜์˜ ์ด๋ฆ„"์ด ๊ฒฐ์ •ํ•œ๋‹ค. f_0 ์•ˆ์—์„œ๋Š” ํ•ญ์ƒ ๊ฐ™์€ ํŒŒ์ผ์„ ์—ฐ๋‹ค. ์ฆ‰ ์ง์ „์— ์†Œ๋น„ํ•œ ๊ธ€์ž๊ฐ€ ๋‹ค์Œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ณ ๋ฅธ๋‹ค.

dlsym ์ด๋ฆ„์€ "๋‹ค์Œ ๊ธ€์ž"๊ฐ€ ๊ฒฐ์ •ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ธ์ž๋ฅผ ํ•œ ์นธ ๋ฐ€์–ด ์žฌ๊ท€ํ•œ๋‹ค.

๋ฐ˜ํ™˜๊ฐ’์€ ๊ทธ๋Œ€๋กœ ์œ„๋กœ ์ „๋‹ฌ๋œ๋‹ค. ์ค‘๊ฐ„์—์„œ ๊ฐ’์„ ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค.

์ด๊ฒŒ ์ •ํ™•ํžˆ ๊ฒฐ์ •์  ์œ ํ•œ ์˜คํ† ๋งˆํƒ€๋‹ค. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ = ์ƒํƒœ, f_d = ๋ผ๋ฒจ d ์ธ ์ „์ด, ์ž…๋ ฅ ๋ฌธ์ž์—ด = ์ „์ด ์—ด.

.so ํŒŒ์ผ ํ•˜๋‚˜๊ฐ€ ์ƒํƒœ์ด๊ณ  f_d ๊ฐ€ ๋ผ๋ฒจ d ์ธ ์ „์ด์ž„์„ ๋ณด์ธ ๋„์‹ โ€” ์ •๋‹ต ๊ฒฝ๋กœ ์œ„ ์ƒํƒœ๋“ค์ด ์„ธ๋กœ๋กœ ์ด์–ด์ง€๊ณ  ๊ฐ ๋‹จ๊ณ„์—์„œ ๋‚˜๋จธ์ง€ ์—ด๋‹ค์„ฏ ์ „์ด๋Š” ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ํฉ์–ด์ง„๋‹ค
.so ํŒŒ์ผ ํ•˜๋‚˜๊ฐ€ ์ƒํƒœ์ด๊ณ  f_d ๊ฐ€ ๋ผ๋ฒจ d ์ธ ์ „์ด์ž„์„ ๋ณด์ธ ๋„์‹ โ€” ์ •๋‹ต ๊ฒฝ๋กœ ์œ„ ์ƒํƒœ๋“ค์ด ์„ธ๋กœ๋กœ ์ด์–ด์ง€๊ณ  ๊ฐ ๋‹จ๊ณ„์—์„œ ๋‚˜๋จธ์ง€ ์—ด๋‹ค์„ฏ ์ „์ด๋Š” ๋‹ค๋ฅธ ๊ณณ์œผ๋กœ ํฉ์–ด์ง„๋‹ค

๋์€ ์–ด๋””์ธ๊ฐ€

์ „์ด ์ƒํƒœ๋Š” dlopen ์„ ํ•˜์ง€๋งŒ, ์–ด๋”˜๊ฐ€์—๋Š” ๊ทธ๋ƒฅ ๋‹ต์„ ๋Œ๋ ค์ฃผ๋Š” ์ƒํƒœ๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค. ํฌ๊ธฐ๊ฐ€ ์ž‘์€ 20๊ฐœ(15,560๋ฐ”์ดํŠธ)๊ฐ€ ๊ทธ ํ›„๋ณด๋‹ค. ํฌ๊ธฐ์— ๊ธฐ๋Œ€์ง€ ๋ง๊ณ  ์ฝ”๋“œ๋กœ ํŒ๋ณ„ํ•œ๋‹ค โ€” dlopen@plt ๋ฅผ ์•ˆ ๋ถ€๋ฅด๋Š” .so ๋ฅผ ๊ณ ๋ฅด๋ฉด ๋œ๋‹ค.

#!/usr/bin/env python3
"""dlopen ์„ ํ•˜์ง€ ์•Š๋Š” .so(= ์ข…๋ฃŒ ์ƒํƒœ)๋ฅผ ์ฐพ์•„, 16๊ฐœ ํ•จ์ˆ˜๊ฐ€ ๋ญ˜ ๋Œ๋ ค์ฃผ๋Š”์ง€ ๋ณธ๋‹ค."""
import pathlib
import re
import subprocess
from concurrent.futures import ProcessPoolExecutor
 
HERE = pathlib.Path(__file__).resolve().parent
LIB = HERE / "extracted" / "lib"
DIGITS = "0123456789abcdef"
FUNC_RE = re.compile(r"^[0-9a-f]+ <f_([0-9a-f])>:$")
RET_RE = re.compile(r"mov\s+eax,0x([0-9a-f]+)\b")
 
 
def scan(path_str: str):
    path = pathlib.Path(path_str)
    out = subprocess.run(["objdump", "-d", "-M", "intel", "--no-show-raw-insn", str(path)],
                         capture_output=True, text=True, check=True).stdout
    if "<dlopen@plt>" in out:
        return None                      # ์ „์ด ์ƒํƒœ
    rets, cur = {}, None
    for line in out.splitlines():
        m = FUNC_RE.match(line.strip())
        if m:
            cur = m.group(1)
            continue
        if cur is None:
            continue
        m = RET_RE.search(line)
        if m and cur not in rets:
            rets[cur] = int(m.group(1), 16)
    return path.name, rets
 
 
def main():
    libs = sorted(str(p) for p in LIB.glob("*.so"))
    with ProcessPoolExecutor() as ex:
        found = [r for r in ex.map(scan, libs, chunksize=16) if r]
 
    print(f"[*] ์ „์ฒด {len(libs)}๊ฐœ ์ค‘ dlopen ์—†๋Š” .so = {len(found)}๊ฐœ (์ข…๋ฃŒ ์ƒํƒœ)")
    total_one = 0
    for name, rets in sorted(found):
        ones = [d for d in DIGITS if rets.get(d) == 1]
        total_one += len(ones)
        if ones:
            print(f"    โ˜… {name}  โ†’  f_{','.join(ones)} ๊ฐ€ 1 ์„ ๋Œ๋ ค์ค€๋‹ค")
    print(f"[+] ์ข…๋ฃŒ ์ƒํƒœ์˜ ํ•จ์ˆ˜ {len(found) * 16}๊ฐœ ์ค‘ 1 ์„ ๋Œ๋ ค์ฃผ๋Š” ๊ฒƒ : {total_one}๊ฐœ")
    print("    ๋‚˜๋จธ์ง€๋Š” ์ „๋ถ€ 0 โ€” ์ฆ‰ ์„ฑ๊ณต์œผ๋กœ ๊ฐ€๋Š” ๊ธธ์€ ํ•˜๋‚˜๋ฟ์ด๋‹ค")
 
 
if __name__ == "__main__":
    main()
python3 scan_terminals.py

์ข…๋ฃŒ ์ƒํƒœ ์Šค์บ” ๊ฒฐ๊ณผ โ€” 1257๊ฐœ ์ค‘ dlopen ์„ ํ•˜์ง€ ์•Š๋Š” so ๊ฐ€ 20๊ฐœ์ด๊ณ  ๊ทธ์ค‘ 219f2e3164 ์˜ f_8 ๋งŒ 1 ์„ ๋Œ๋ ค์ฃผ๋ฉฐ ๋‚˜๋จธ์ง€ 319๊ฐœ ํ•จ์ˆ˜๋Š” ์ „๋ถ€ 0 ์ด๋‹ค
์ข…๋ฃŒ ์ƒํƒœ ์Šค์บ” ๊ฒฐ๊ณผ โ€” 1257๊ฐœ ์ค‘ dlopen ์„ ํ•˜์ง€ ์•Š๋Š” so ๊ฐ€ 20๊ฐœ์ด๊ณ  ๊ทธ์ค‘ 219f2e3164 ์˜ f_8 ๋งŒ 1 ์„ ๋Œ๋ ค์ฃผ๋ฉฐ ๋‚˜๋จธ์ง€ 319๊ฐœ ํ•จ์ˆ˜๋Š” ์ „๋ถ€ 0 ์ด๋‹ค

์ข…๋ฃŒ ์ƒํƒœ 20๊ฐœ ร— 16ํ•จ์ˆ˜ = 320๊ฐœ ์ค‘ 1 ์„ ๋Œ๋ ค์ฃผ๋Š” ๊ฑด ๋”ฑ ํ•˜๋‚˜, 219f2e3164.so ์˜ f_8.

์ˆ˜๋ฝ ์ƒํƒœ์™€ ๋งˆ์ง€๋ง‰ ๊ธ€์ž๊ฐ€ ๋™์‹œ์— ํ™•์ •๋๋‹ค.


๐Ÿ› ์‚ฝ์งˆ

โ–ถ๐Ÿ•ณ๏ธ ์‚ฝ์งˆ 1 โ€” ํŒŒ์ผ ํฌ๊ธฐ๋กœ ์ข…๋ฅ˜๋ฅผ ๊ฐˆ๋ž๋‹ค๊ฐ€ ๊ทผ๊ฑฐ๋ฅผ ๋ฐ”๊ฟจ๋‹ค

ํฌ๊ธฐ๊ฐ€ ์„ธ ์ข…๋ฅ˜๋ผ๋Š” ๊ฑธ ๋ณด๊ณ  ์ฒ˜์Œ์—” ๊ทธ๊ฑธ ๊ทธ๋Œ€๋กœ ํŒ๋ณ„ ๊ธฐ์ค€์œผ๋กœ ์ผ๋‹ค.

for f in $(ls -l lib/*.so | awk '{if ($5==15560) print $NF}'); do ... done

๋‹ต์€ ๋งž์•˜๋‹ค. 15,560๋ฐ”์ดํŠธ 20๊ฐœ๊ฐ€ ์ •๋ง๋กœ ์ข…๋ฃŒ ์ƒํƒœ์˜€๋‹ค. ๊ทธ๋Ÿฐ๋ฐ ๊ทผ๊ฑฐ๊ฐ€ ์•ฝํ•˜๋‹ค.

ํฌ๊ธฐ๊ฐ€ ๋‹ค๋ฅธ ์ด์œ ๋Š” "dlopen ์„ ์•ˆ ํ•ด์„œ ์ฝ”๋“œ์™€ ๋ฌธ์ž์—ด์ด ์ค„์—ˆ๋‹ค" ์ธ๋ฐ, ๊ทธ๊ฑด ๊ฒฐ๊ณผ์ง€ ์ •์˜๊ฐ€ ์•„๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ๋ฐฐํฌ๋ณธ์ด๋‚˜ ๋‹ค๋ฅธ ์ปดํŒŒ์ผ ์˜ต์…˜์ด๋ฉด ์ˆซ์ž๊ฐ€ ๋‹ฌ๋ผ์ง€๊ณ , ํ•˜ํ•„ ํฌ๊ธฐ๊ฐ€ ๊ฐ™์€ ์ „์ด ์ƒํƒœ๊ฐ€ ํ•˜๋‚˜๋ผ๋„ ์žˆ์œผ๋ฉด ์กฐ์šฉํžˆ ํ‹€๋ฆฐ๋‹ค.

๊ทธ๋ž˜์„œ scan_terminals.py ๋Š” ํฌ๊ธฐ๋ฅผ ์•„์˜ˆ ์•ˆ ๋ณธ๋‹ค. objdump ์ถœ๋ ฅ์— <dlopen@plt> ๊ฐ€ ์žˆ๋Š”๊ฐ€๋กœ ๊ฐ€๋ฅธ๋‹ค.

if "<dlopen@plt>" in out:
    return None                      # ์ „์ด ์ƒํƒœ

๊ฒฐ๊ณผ๋Š” ๊ฐ™์•˜์ง€๋งŒ(20๊ฐœ), ์ด์ œ๋Š” "์™œ 20๊ฐœ์ธ์ง€"๊ฐ€ ์„ค๋ช…๋œ๋‹ค. ๋ถ„๋ฅ˜ ๊ธฐ์ค€์€ ๊ด€์ฐฐ๋œ ์ƒ๊ด€๊ด€๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ ์ •์˜๋ฅผ ์จ์•ผ ํ•œ๋‹ค โ€” ์—ฌ๊ธฐ์„œ๋Š” ๊ทธ ์ฐจ์ด๊ฐ€ ๊ฒฐ๊ณผ๋ฅผ ์•ˆ ๋ฐ”๊ฟจ์„ ๋ฟ์ด๋‹ค.

โ–ถ๐Ÿ•ณ๏ธ ์‚ฝ์งˆ 2 โ€” sed ์ฃผ์„ ํ•œ ์ค„์ด ์กฐ์šฉํžˆ ์‚ฌ๋ผ์กŒ๋‹ค

๋””์Šค์–ด์…ˆ๋ธ”์— <-- ์ฃผ์„์„ ์–น๋Š” ๊ทœ์น™์„ 16๊ฐœ ์ผ๋Š”๋ฐ, ๋ถ™์€ ๊ฑด 15๊ฐœ์˜€๋‹ค.

์—๋Ÿฌ๋„ ์•ˆ ๋‚˜๊ณ  ๊ฒฝ๊ณ ๋„ ์—†๋‹ค. sed ๋Š” ๋งค์นญ์ด ์•ˆ ๋˜๋ฉด ๊ทธ๋ƒฅ ๋„˜์–ด๊ฐ„๋‹ค. ๊ฐœ์ˆ˜๋ฅผ ์„ธ์ง€ ์•Š์•˜์œผ๋ฉด ๋ชป ์•Œ์•„์ฑ˜์„ ๊ฒƒ์ด๋‹ค.

objdump ... | sed -f annot_main.sed | grep -c '<--'

๋น ์ง„ ๊ฑด ์ด ์ค„์ด์—ˆ๋‹ค.

s|^    13b6:.*|&           <-- RTLD_LAZY \| RTLD_GLOBAL|

s|...|...| ์—์„œ ๊ตฌ๋ถ„์ž๊ฐ€ | ์ธ๋ฐ ์น˜ํ™˜๋ฌธ ์•ˆ์— | ๋ฅผ ๊ทธ๋Œ€๋กœ ์ผ๋‹ค. sed ๋Š” ๊ฑฐ๊ธฐ์„œ ์น˜ํ™˜๋ฌธ์ด ๋๋‚œ ์ค„ ์•Œ๊ณ  ๋’ค๋ฅผ ์˜ต์…˜์œผ๋กœ ์ฝ๋‹ค๊ฐ€ ๊ทœ์น™์„ ๋ฒ„๋ฆฐ๋‹ค.

dlopen ํ”Œ๋ž˜๊ทธ๋ฅผ ์„ค๋ช…ํ•˜๋ ค๋‹ˆ RTLD_LAZY | RTLD_GLOBAL ์ด๋ผ๊ณ  ์“ฐ๋Š” ๊ฒŒ ์ž์—ฐ์Šค๋Ÿฌ์›Œ์„œ ์ƒ๊ธด ์ผ์ด๋‹ค. ๋ฌธ๊ตฌ๋ฅผ RTLD_LAZY + RTLD_GLOBAL ๋กœ ๋ฐ”๊ฟ” ํ•ด๊ฒฐํ–ˆ๋‹ค(๊ตฌ๋ถ„์ž๋ฅผ # ๋“ฑ์œผ๋กœ ๋ฐ”๊ฟ”๋„ ๋œ๋‹ค).

์ฃผ์„์„ ์–น์—ˆ์œผ๋ฉด grep -c '<--' ๋กœ ๊ฐœ์ˆ˜๋ฅผ ํ™•์ธํ•œ๋‹ค. ์•ˆ ๊ทธ๋Ÿฌ๋ฉด ๊ธ€์— ์‹ค๋ฆฌ๋Š” ์บก์ณ์—์„œ ์„ค๋ช… ํ•œ ์ค„์ด ์กฐ์šฉํžˆ ๋น ์ง„๋‹ค.

๐ŸŽฏ ์ „์ดํ‘œ๋ฅผ ๋ฝ‘๋Š”๋‹ค

f_d ์•ˆ์˜ dlopen ์ธ์ž ๋ฌธ์ž์—ด์„ ์ฝ์œผ๋ฉด ์ „์ด ๋Œ€์ƒ์ด ๋‚˜์˜จ๋‹ค. ๋””์Šค์–ด์…ˆ๋ธ”์—์„œ call ...<dlopen@plt> ๋ฐ”๋กœ ์•ž์˜ lea rax,[rip+...] ๊ฐ€ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ฃผ์†Œ์˜ C ๋ฌธ์ž์—ด์ด ๊ทธ๊ฒƒ์ด๋‹ค.

#!/usr/bin/env python3
"""1257๊ฐœ .so ์—์„œ ์ „์ดํ‘œ๋ฅผ ๋ฝ‘์•„ DFA ๋กœ ๋งŒ๋“ ๋‹ค."""
import json
import pathlib
import re
import subprocess
from concurrent.futures import ProcessPoolExecutor
 
from elftools.elf.elffile import ELFFile
 
HERE = pathlib.Path(__file__).resolve().parent
LIB = HERE / "extracted" / "lib"
 
FUNC_RE = re.compile(r"^[0-9a-f]+ <f_([0-9a-f])>:$")
LEA_RE = re.compile(r"lea\s+rax,\[rip\+0x[0-9a-f]+\]\s+#\s+([0-9a-f]+)")
RET0_RE = re.compile(r"mov\s+eax,0x([01])\b")
 
 
def read_cstring(elf_path: pathlib.Path, vaddr: int) -> str:
    """๊ฐ€์ƒ์ฃผ์†Œ vaddr ์— ์žˆ๋Š” NUL ์ข…๋ฃŒ ๋ฌธ์ž์—ด์„ ํŒŒ์ผ์—์„œ ์ฝ๋Š”๋‹ค."""
    with open(elf_path, "rb") as fh:
        elf = ELFFile(fh)
        for seg in elf.iter_segments():
            if seg["p_type"] != "PT_LOAD":
                continue
            lo, hi = seg["p_vaddr"], seg["p_vaddr"] + seg["p_filesz"]
            if lo <= vaddr < hi:
                fh.seek(seg["p_offset"] + (vaddr - lo))
                raw = fh.read(256)
                return raw.split(b"\x00", 1)[0].decode()
    raise ValueError(f"{elf_path.name}: vaddr {vaddr:#x} ๋ฅผ ๋ชป ์ฐพ์Œ")
 
 
def parse_one(path_str: str):
    path = pathlib.Path(path_str)
    out = subprocess.run(
        ["objdump", "-d", "-M", "intel", "--no-show-raw-insn", str(path)],
        capture_output=True, text=True, check=True).stdout
 
    edges, rets, cur, pending_lea = {}, {}, None, None
    for line in out.splitlines():
        line = line.strip()
        m = FUNC_RE.match(line)
        if m:
            cur, pending_lea = m.group(1), None
            continue
        if cur is None:
            continue
        m = LEA_RE.search(line)
        if m:
            pending_lea = int(m.group(1), 16)
            continue
        if "<dlopen@plt>" in line and pending_lea is not None:
            s = read_cstring(path, pending_lea)
            if s.startswith("lib/"):
                edges[cur] = s[len("lib/"):]
            pending_lea = None
            continue
        m = RET0_RE.search(line)
        if m and cur not in edges:
            rets[cur] = int(m.group(1))
    return path.name, edges, rets
 
 
def main():
    libs = sorted(str(p) for p in LIB.glob("*.so"))
    print(f"[*] .so {len(libs)}๊ฐœ ํŒŒ์‹ฑ")
    dfa, terminal = {}, {}
    with ProcessPoolExecutor() as ex:
        for name, edges, rets in ex.map(parse_one, libs, chunksize=16):
            if edges:
                dfa[name] = edges
            else:
                terminal[name] = rets
 
    (HERE / "dfa.json").write_text(json.dumps(dfa, indent=0, sort_keys=True))
    (HERE / "terminal.json").write_text(json.dumps(terminal, indent=0, sort_keys=True))
 
    print(f"[+] ์ „์ด ์ƒํƒœ {len(dfa)}๊ฐœ / ์ข…๋ฃŒ ์ƒํƒœ {len(terminal)}๊ฐœ")
    bad = [k for k, v in dfa.items() if len(v) != 16]
    print(f"[*] ์ „์ด 16๊ฐœ๊ฐ€ ์•„๋‹Œ ์ƒํƒœ: {len(bad)}  {bad[:3]}")
    accept = {k: [d for d, v in r.items() if v == 1] for k, r in terminal.items()}
    accept = {k: v for k, v in accept.items() if v}
    print(f"[+] 1 ์„ ๋Œ๋ ค์ฃผ๋Š” ์ข…๋ฃŒ ์ƒํƒœ: {accept}")
 
 
if __name__ == "__main__":
    main()
python3 extract_dfa.py

์ „์ดํ‘œ ์ถ”์ถœ ๊ฒฐ๊ณผ โ€” so ํŒŒ์ผ 1257๊ฐœ๋ฅผ ํŒŒ์‹ฑํ•ด ์ „์ด ์ƒํƒœ 1237๊ฐœ์™€ ์ข…๋ฃŒ ์ƒํƒœ 20๊ฐœ๋กœ ๋‚˜๋‰˜๊ณ  ์ „์ด๊ฐ€ 16๊ฐœ๊ฐ€ ์•„๋‹Œ ์ƒํƒœ๋Š” ํ•˜๋‚˜๋„ ์—†์œผ๋ฉฐ ์ˆ˜๋ฝ ์ƒํƒœ๋Š” 219f2e3164 ์˜ f_8 ํ•˜๋‚˜๋‹ค
์ „์ดํ‘œ ์ถ”์ถœ ๊ฒฐ๊ณผ โ€” so ํŒŒ์ผ 1257๊ฐœ๋ฅผ ํŒŒ์‹ฑํ•ด ์ „์ด ์ƒํƒœ 1237๊ฐœ์™€ ์ข…๋ฃŒ ์ƒํƒœ 20๊ฐœ๋กœ ๋‚˜๋‰˜๊ณ  ์ „์ด๊ฐ€ 16๊ฐœ๊ฐ€ ์•„๋‹Œ ์ƒํƒœ๋Š” ํ•˜๋‚˜๋„ ์—†์œผ๋ฉฐ ์ˆ˜๋ฝ ์ƒํƒœ๋Š” 219f2e3164 ์˜ f_8 ํ•˜๋‚˜๋‹ค

์ „์ด ์ƒํƒœ 1,237๊ฐœ ร— 16 = ๊ฐ„์„  19,792๊ฐœ. ์ „์ด๊ฐ€ 16๊ฐœ๊ฐ€ ์•„๋‹Œ ์ƒํƒœ๋Š” ํ•˜๋‚˜๋„ ์—†๋‹ค โ€” ํŒŒ์‹ฑ์ด ๋น ์ง์—†์ด ๋๋‹ค๋Š” ๋œป์ด๋‹ค.





๐Ÿš€ Full Exploit

verify ๊ฐ€ ์ •ํ™•ํžˆ 64๊ธ€์ž๋ฅผ ์š”๊ตฌํ•˜๋ฏ€๋กœ ๊ฒฝ๋กœ ๊ธธ์ด๊ฐ€ ์ •ํ™•ํžˆ 63์ด์–ด์•ผ ํ•œ๋‹ค. ๋งˆ์ง€๋ง‰ 64๋ฒˆ์งธ ๊ธ€์ž๋Š” ์ข…๋ฃŒ ์ƒํƒœ์˜ ํ•จ์ˆ˜๋ฅผ ๊ณ ๋ฅผ ๋ฟ ์ „์ด๋ฅผ ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์—ฌ๊ธฐ์„œ "์ตœ๋‹จ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์œผ๋ฉด ๋˜์ง€ ์•Š๋‚˜" ์‹ถ์€๋ฐ, ์ „์ด ๊ทธ๋ž˜ํ”„์—๋Š” ์‚ฌ์ดํด์ด ์žˆ์–ด์„œ ์ตœ๋‹จ ๊ฒฝ๋กœ๊ฐ€ 63๋ณด๋‹ค ์งง๊ฒŒ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด verify ์—์„œ ๊ฑธ๋ฆฐ๋‹ค. ๊ทธ๋ž˜์„œ ๊ธธ์ด๋ณ„ ๋„๋‹ฌ ๊ฐ€๋Šฅ ์ง‘ํ•ฉ์„ ๋’ค์—์„œ๋ถ€ํ„ฐ ๊ณ„์‚ฐํ–ˆ๋‹ค. reach[k] ๋ฅผ "k๋ฒˆ ๋” ์ „์ดํ•˜๋ฉด ์ˆ˜๋ฝ ์ƒํƒœ์— ๋‹ฟ์„ ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ๋“ค"๋กœ ๋‘๊ณ , ์•ž์œผ๋กœ ๊ฑธ์„ ๋•Œ reach[k-1] ์•ˆ์— ์žˆ๋Š” ์ „์ด๋งŒ ๊ณ ๋ฅด๋ฉด ๊ธธ์ด๊ฐ€ ์ •ํ™•ํžˆ ๋งž๋Š”๋‹ค.

์‹ค์ œ๋กœ ์ด ๊ทธ๋ž˜ํ”„๊ฐ€ ์–ด๋• ๋Š”์ง€๋Š” ์„ธ์–ด ๋ณด๋ฉด ๋‚˜์˜จ๋‹ค.

# path_stats.py โ€” ์ˆ˜๋ฝ ์ƒํƒœ๊นŒ์ง€ ๊ฐ€๋Š” ๊ฒฝ๋กœ๊ฐ€ ๊ธธ์ด๋ณ„๋กœ ๋ช‡ ๊ฐœ์ธ์ง€ ์„ผ๋‹ค
cnt, per_len = {goal: 1}, {}
for k in range(1, MAXLEN + 1):
    nxt = {}
    for src, e in dfa.items():
        t = sum(cnt.get(e[d], 0) for d in DIGITS)
        if t:
            nxt[src] = t
    cnt = nxt
    if cnt.get(START):
        per_len[k] = cnt[START]
python3 path_stats.py

๊ฒฝ๋กœ ๊ธธ์ด ํ†ต๊ณ„ โ€” BFS ์ตœ๋‹จ ์ „์ด ์ˆ˜๊ฐ€ 63 ์œผ๋กœ verify ๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๊ฐ’๊ณผ ๊ฐ™๊ณ , ๊ธธ์ด 80 ์ดํ•˜์—์„œ ์ˆ˜๋ฝ ์ƒํƒœ๋กœ ๊ฐ€๋Š” ๊ฒฝ๋กœ๋Š” 63์งœ๋ฆฌ ํ•˜๋‚˜๋ฟ์ด๋ผ ์„ฑ๊ณต ๊ฒฝ๋กœ ์œ„์—๋Š” ์‚ฌ์ดํด๋„ ๊ฐˆ๋ฆผ๊ธธ๋„ ์—†๋‹ค
๊ฒฝ๋กœ ๊ธธ์ด ํ†ต๊ณ„ โ€” BFS ์ตœ๋‹จ ์ „์ด ์ˆ˜๊ฐ€ 63 ์œผ๋กœ verify ๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๊ฐ’๊ณผ ๊ฐ™๊ณ , ๊ธธ์ด 80 ์ดํ•˜์—์„œ ์ˆ˜๋ฝ ์ƒํƒœ๋กœ ๊ฐ€๋Š” ๊ฒฝ๋กœ๋Š” 63์งœ๋ฆฌ ํ•˜๋‚˜๋ฟ์ด๋ผ ์„ฑ๊ณต ๊ฒฝ๋กœ ์œ„์—๋Š” ์‚ฌ์ดํด๋„ ๊ฐˆ๋ฆผ๊ธธ๋„ ์—†๋‹ค

BFS ์ตœ๋‹จ์ด ๋งˆ์นจ 63์ด์—ˆ๊ณ , ๊ธธ์ด 80 ์ดํ•˜์—์„œ ์ˆ˜๋ฝ ์ƒํƒœ๋กœ ๊ฐ€๋Š” ๊ฒฝ๋กœ๋Š” 63์งœ๋ฆฌ ํ•˜๋‚˜๋ฟ์ด๋‹ค. ์„ฑ๊ณต ๊ฒฝ๋กœ ์œ„์—๋Š” ์‚ฌ์ดํด๋„ ๊ฐˆ๋ฆผ๊ธธ๋„ ์—†๋‹ค๋Š” ๋œป์ด๋ผ, ์ด ๋ฌธ์ œ์— ํ•œํ•ด์„œ๋Š” ์ตœ๋‹จ ๊ฒฝ๋กœ๋กœ ํ’€์–ด๋„ ๊ฐ™์€ ๋‹ต์ด ๋‚˜์™”์„ ๊ฒƒ์ด๋‹ค.

๊ทธ๋ž˜๋„ ๊ธธ์ด๋ฅผ ๊ณ ์ •ํ•ด ์ฐพ๋Š” ์ชฝ์„ ํƒํ–ˆ๋‹ค. ๊ทธ๋ž˜์•ผ "์šด ์ข‹๊ฒŒ ์ตœ๋‹จ์ด 63์ด์—ˆ๋‹ค"์— ๊ธฐ๋Œ€์ง€ ์•Š๋Š”๋‹ค.

#!/usr/bin/env python3
""".so what? (DreamHack, Gold 3 / reversing) โ€” solver
 
๊ตฌ์กฐ:
  main ์ด 64์ž hex ๋ฅผ ๋ฐ›์•„ verify() ๋กœ 0-9a-f ์ธ์ง€ ํ™•์ธํ•œ ๋’ค
    dlopen("lib/start.so") โ†’ dlsym("f_" + s[0]) โ†’ ๊ทธ ํ•จ์ˆ˜๋ฅผ s+1 ๋กœ ํ˜ธ์ถœ
  ํ•œ๋‹ค. ๊ฐ .so ์˜ f_d ๋Š” ๋‹ค์‹œ
    dlopen("lib/<์ •ํ•ด์ง„ ๋‹ค์Œ>.so") โ†’ dlsym("f_" + p[0]) โ†’ f(p+1)
  ์ด๋ฏ€๋กœ **.so = ์ƒํƒœ, f_d = ๋ผ๋ฒจ d ์ธ ์ „์ด**๋ฅผ ๊ฐ–๋Š” ๊ฒฐ์ •์  ์˜คํ† ๋งˆํƒ€๋‹ค.
 
  ์ข…๋ฃŒ ์ƒํƒœ(dlopen ์—†์ด ์ƒ์ˆ˜๋งŒ ๋Œ๋ ค์ฃผ๋Š” .so) 20๊ฐœ ์ค‘
  `219f2e3164.so` ์˜ `f_8` ๋งŒ 1 ์„ ๋Œ๋ ค์ค€๋‹ค. ๋‚˜๋จธ์ง€๋Š” ์ „๋ถ€ 0.
 
  ๋”ฐ๋ผ์„œ ์ •๋‹ต์€
    start.so ์—์„œ ์ถœ๋ฐœํ•ด 63๋ฒˆ ์ „์ดํ•ด 219f2e3164.so ์— ๋‹ฟ๊ณ 
    ๋งˆ์ง€๋ง‰ 64๋ฒˆ์งธ ๊ธ€์ž๊ฐ€ 8
  ์ธ 64์ž hex ๋ฌธ์ž์—ด์ด๋‹ค.  flag = DH{๊ทธ ๋ฌธ์ž์—ด}
 
  โ˜… ์ฃผ์˜: ์ „์ด ๊ทธ๋ž˜ํ”„์— ์‚ฌ์ดํด์ด ์žˆ์–ด "์ตœ๋‹จ ๊ฒฝ๋กœ"๊ฐ€ ์•„๋‹ˆ๋ผ
     "๊ธธ์ด๊ฐ€ ์ •ํ™•ํžˆ 63" ์ธ ๊ฒฝ๋กœ๋ฅผ ์ฐพ์•„์•ผ ํ•œ๋‹ค. ๊ธธ์ด๋ณ„ ๋„๋‹ฌ ๊ฐ€๋Šฅ ์ง‘ํ•ฉ์„
     ๋’ค์—์„œ๋ถ€ํ„ฐ ๊ณ„์‚ฐ(์—ญ๋ฐฉํ–ฅ BFS)ํ•ด ๊ทธ ์•ˆ์—์„œ๋งŒ ์•ž์œผ๋กœ ๊ฑธ์–ด๊ฐ€๋ฉด ๋œ๋‹ค.
"""
import argparse
import json
import pathlib
import sys
 
HERE = pathlib.Path(__file__).resolve().parent
DIGITS = "0123456789abcdef"
START = "start.so"
LENGTH = 64          # verify() ๊ฐ€ ๊ฒ€์‚ฌํ•˜๋Š” ๊ธ€์ž ์ˆ˜
 
 
def load():
    dfa = json.loads((HERE / "dfa.json").read_text())
    terminal = json.loads((HERE / "terminal.json").read_text())
    return dfa, terminal
 
 
def accepting(terminal: dict) -> tuple[str, str]:
    """1 ์„ ๋Œ๋ ค์ฃผ๋Š” (์ข…๋ฃŒ์ƒํƒœ, ๋งˆ์ง€๋ง‰๊ธ€์ž) ๋ฅผ ์ฐพ๋Š”๋‹ค."""
    hits = [(lib, d) for lib, rets in terminal.items()
            for d, v in rets.items() if v == 1]
    if len(hits) != 1:
        sys.exit(f"์ˆ˜๋ฝ ์ƒํƒœ๊ฐ€ {len(hits)}๊ฐœ๋‹ค: {hits}")
    return hits[0]
 
 
def solve(dfa: dict, goal: str, steps: int) -> str | None:
    """start ์—์„œ goal ๊นŒ์ง€ ์ •ํ™•ํžˆ steps ๋ฒˆ ์ „์ดํ•˜๋Š” ๊ฒฝ๋กœ์˜ ๋ผ๋ฒจ ๋ฌธ์ž์—ด."""
    # reach[k] = k ๋ฒˆ ๋” ์ „์ดํ•˜๋ฉด goal ์— ๋‹ฟ์„ ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ ์ง‘ํ•ฉ
    reach = [set() for _ in range(steps + 1)]
    reach[0] = {goal}
    back: dict[str, list[str]] = {}
    for src, edges in dfa.items():
        for d in DIGITS:
            back.setdefault(edges[d], []).append(src)
    for k in range(1, steps + 1):
        nxt = set()
        for st in reach[k - 1]:
            nxt.update(back.get(st, ()))
        reach[k] = nxt
    if START not in reach[steps]:
        return None
 
    out, cur = [], START
    for k in range(steps, 0, -1):
        for d in DIGITS:                      # ์‚ฌ์ „์ˆœ์œผ๋กœ ์ฒซ ๋ฒˆ์งธ๋ฅผ ๊ณ ๋ฅธ๋‹ค
            if dfa[cur][d] in reach[k - 1]:
                out.append(d)
                cur = dfa[cur][d]
                break
        else:
            sys.exit("๊ฒฝ๋กœ ์žฌ๊ตฌ์„ฑ ์‹คํŒจ")
    return "".join(out)
 
 
def count_paths(dfa: dict, goal: str, steps: int) -> int:
    """๊ธธ์ด๊ฐ€ ์ •ํ™•ํžˆ steps ์ธ ๊ฒฝ๋กœ๊ฐ€ ๋ช‡ ๊ฐœ์ธ์ง€ ์„ผ๋‹ค (์œ ์ผ์„ฑ ํ™•์ธ)."""
    cnt = {goal: 1}
    for _ in range(steps):
        nxt: dict[str, int] = {}
        for src, edges in dfa.items():
            t = sum(cnt.get(edges[d], 0) for d in DIGITS)
            if t:
                nxt[src] = t
        cnt = nxt
    return cnt.get(START, 0)
 
 
def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument("--count", action="store_true", help="๊ฒฝ๋กœ ๊ฐœ์ˆ˜๋„ ์„ผ๋‹ค")
    a = ap.parse_args()
 
    dfa, terminal = load()
    goal, last = accepting(terminal)
    print(f"[*] ์ƒํƒœ {len(dfa)}๊ฐœ(์ „์ด) + {len(terminal)}๊ฐœ(์ข…๋ฃŒ)")
    print(f"[+] ์ˆ˜๋ฝ ์ƒํƒœ : {goal}  ์˜  f_{last}")
 
    prefix = solve(dfa, goal, LENGTH - 1)
    if prefix is None:
        print("[-] ๊ธธ์ด๊ฐ€ ๋งž๋Š” ๊ฒฝ๋กœ๊ฐ€ ์—†๋‹ค")
        return 1
 
    flag_body = prefix + last
    print(f"[+] ๊ฒฝ๋กœ ๊ธธ์ด : {len(flag_body)}์ž")
    if a.count:
        n = count_paths(dfa, goal, LENGTH - 1)
        print(f"[*] ๊ธธ์ด {LENGTH - 1} ๊ฒฝ๋กœ ์ˆ˜ : {n}๊ฐœ")
    print(f"[+] FLAG: DH{{{flag_body}}}")
    return 0
 
 
if __name__ == "__main__":
    sys.exit(main())
python3 solve.py --count

solve.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ์ „์ด ์ƒํƒœ 1237๊ฐœ์™€ ์ข…๋ฃŒ ์ƒํƒœ 20๊ฐœ๋ฅผ ์ฝ์–ด ์ˆ˜๋ฝ ์ƒํƒœ 219f2e3164 ์˜ f_8 ์„ ์ฐพ๊ณ  ๊ธธ์ด 63 ๊ฒฝ๋กœ๊ฐ€ ์ •ํ™•ํžˆ 1๊ฐœ์ž„์„ ์„ธ์–ด 64์ž ํ”Œ๋ž˜๊ทธ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค
solve.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ์ „์ด ์ƒํƒœ 1237๊ฐœ์™€ ์ข…๋ฃŒ ์ƒํƒœ 20๊ฐœ๋ฅผ ์ฝ์–ด ์ˆ˜๋ฝ ์ƒํƒœ 219f2e3164 ์˜ f_8 ์„ ์ฐพ๊ณ  ๊ธธ์ด 63 ๊ฒฝ๋กœ๊ฐ€ ์ •ํ™•ํžˆ 1๊ฐœ์ž„์„ ์„ธ์–ด 64์ž ํ”Œ๋ž˜๊ทธ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค

--count ๋Š” ๊ธธ์ด 63 ๊ฒฝ๋กœ๊ฐ€ ๋ช‡ ๊ฐœ์ธ์ง€ ๋™์ ๊ณ„ํš๋ฒ•์œผ๋กœ ์„ธ๋Š” ์˜ต์…˜์ด๋‹ค. ๊ฒฐ๊ณผ๋Š” 1๊ฐœ โ€” ๋‹ต์ด ์œ ์ผํ•˜๋‹ค.

์ •์  ์ „์ดํ‘œ๊ฐ€ ๋Ÿฐํƒ€์ž„๊ณผ ๊ฐ™์€๊ฐ€

objdump ๋กœ ์ฝ์€ ํ‘œ๊ฐ€ ์‹ค์ œ ์‹คํ–‰๊ณผ ์–ด๊ธ‹๋‚˜๋ฉด ๋‹ต๋„ ํ‹€๋ฆฐ๋‹ค. strace ๋กœ main ์ด ์—ฌ๋Š” .so ์ˆœ์„œ๋ฅผ ๋ฐ›์•„ ์˜ˆ์ธก๊ณผ ํ•œ ์นธ์”ฉ ๋Œ€์กฐํ–ˆ๋‹ค.

#!/usr/bin/env python3
"""์ •์ ์œผ๋กœ ๋ฝ‘์€ ์ „์ดํ‘œ๊ฐ€ ์‹ค์ œ ์‹คํ–‰๊ณผ ๋งž๋Š”์ง€ ๋Œ€์กฐํ•œ๋‹ค."""
import json
import pathlib
import re
import subprocess
import sys
 
HERE = pathlib.Path(__file__).resolve().parent
ANSWER = "20654ccdb7c43bd1ab398283f9895ac285e8c419c4c157db2f3f50de92599bd8"
OPEN_RE = re.compile(r'"lib/([0-9a-z]+\.so)"')
 
 
def traced_order() -> list[str]:
    p = subprocess.run(
        ["strace", "-f", "-e", "trace=openat", "./main"],
        input=ANSWER + "\n", cwd=HERE / "extracted",
        capture_output=True, text=True, timeout=120)
    return OPEN_RE.findall(p.stderr)
 
 
def predicted_order() -> list[str]:
    dfa = json.loads((HERE / "dfa.json").read_text())
    order, cur = ["start.so"], "start.so"
    for d in ANSWER[:-1]:          # ๋งˆ์ง€๋ง‰ ๊ธ€์ž๋Š” ์ข…๋ฃŒ ์ƒํƒœ์˜ ํ•จ์ˆ˜๋ฅผ ๊ณ ๋ฅผ ๋ฟ dlopen ์„ ์•ˆ ํ•œ๋‹ค
        cur = dfa[cur][d]
        order.append(cur)
    return order
 
 
def main() -> int:
    got, want = traced_order(), predicted_order()
    print(f"[*] ์ž…๋ ฅ      : {ANSWER[:16]}... (64์ž)")
    print(f"[*] strace ๊ฐ€ ์—ฐ .so : {len(got)}๊ฐœ")
    print(f"[*] ์ „์ดํ‘œ ์˜ˆ์ธก      : {len(want)}๊ฐœ")
    print()
    for i in (0, 1, 2, 31, 61, 62, 63):
        if i < min(len(got), len(want)):
            mark = "์ผ์น˜" if got[i] == want[i] else "โ˜…๋ถˆ์ผ์น˜"
            print(f"    [{i:2d}] ์‹คํ–‰={got[i]:<16} ์˜ˆ์ธก={want[i]:<16} {mark}")
    print()
    if got == want:
        print("[+] 64๊ฐœ๊ฐ€ ํ•œ ์นธ๋„ ์–ด๊ธ‹๋‚˜์ง€ ์•Š๋Š”๋‹ค โ€” ์ •์  ์ „์ดํ‘œ๊ฐ€ ๋Ÿฐํƒ€์ž„๊ณผ ๊ฐ™๋‹ค")
        return 0
    bad = next(i for i, (a, b) in enumerate(zip(got, want)) if a != b)
    print(f"[-] {bad}๋ฒˆ์งธ์—์„œ ๊ฐˆ๋ฆฐ๋‹ค: ์‹คํ–‰={got[bad]} / ์˜ˆ์ธก={want[bad]}")
    return 1
 
 
if __name__ == "__main__":
    sys.exit(main())
python3 trace_check.py

strace ๋กœ ์žก์€ ์‹ค์ œ dlopen ์ˆœ์„œ์™€ ์ •์  ์ „์ดํ‘œ ์˜ˆ์ธก์„ ๋Œ€์กฐํ•œ ๊ฒฐ๊ณผ โ€” 64๊ฐœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•œ ์นธ๋„ ์–ด๊ธ‹๋‚˜์ง€ ์•Š๊ณ  ๋งˆ์ง€๋ง‰์ด ์ˆ˜๋ฝ ์ƒํƒœ 219f2e3164 ๋กœ ๋๋‚œ๋‹ค
strace ๋กœ ์žก์€ ์‹ค์ œ dlopen ์ˆœ์„œ์™€ ์ •์  ์ „์ดํ‘œ ์˜ˆ์ธก์„ ๋Œ€์กฐํ•œ ๊ฒฐ๊ณผ โ€” 64๊ฐœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•œ ์นธ๋„ ์–ด๊ธ‹๋‚˜์ง€ ์•Š๊ณ  ๋งˆ์ง€๋ง‰์ด ์ˆ˜๋ฝ ์ƒํƒœ 219f2e3164 ๋กœ ๋๋‚œ๋‹ค

start.so ํ•˜๋‚˜ + ์ „์ด 63๋ฒˆ = 64๊ฐœ. ์ˆœ์„œ๊ฐ€ ํ•œ ์นธ๋„ ์–ด๊ธ‹๋‚˜์ง€ ์•Š๋Š”๋‹ค.

์‹ค์ œ ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ

#!/usr/bin/env python3
"""์ฐพ์€ ๋ฌธ์ž์—ด์„ ์‹ค์ œ main ์— ๋จน์—ฌ Flag is DH{...} ๊ฐ€ ๋‚˜์˜ค๋Š”์ง€ ํ™•์ธํ•œ๋‹ค."""
import subprocess
 
ANSWER = "20654ccdb7c43bd1ab398283f9895ac285e8c419c4c157db2f3f50de92599bd8"
CWD = "extracted"
 
 
def run(s: str) -> str:
    p = subprocess.run(["./main"], input=s + "\n", cwd=CWD,
                       capture_output=True, text=True, timeout=30)
    return p.stdout.strip().splitlines()[-1] if p.stdout.strip() else "(์ถœ๋ ฅ ์—†์Œ)"
 
 
cases = [
    ("์ •๋‹ต", ANSWER),
    ("๋งˆ์ง€๋ง‰ ๊ธ€์ž๋งŒ ๋ฐ”๊ฟˆ", ANSWER[:-1] + "9"),
    ("์ฒซ ๊ธ€์ž๋งŒ ๋ฐ”๊ฟˆ", "3" + ANSWER[1:]),
    ("๊ฐ€์šด๋ฐ ํ•œ ๊ธ€์ž ๋ฐ”๊ฟˆ", ANSWER[:32] + ("0" if ANSWER[32] != "0" else "1") + ANSWER[33:]),
]
 
for label, s in cases:
    print(f"{label:20s} len={len(s):3d}  โ†’  {run(s)}")
python3 verify.py

verify.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ์ •๋‹ต๋งŒ Flag is DH ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ”Œ๋ž˜๊ทธ๋ฅผ ์ถœ๋ ฅํ•˜๊ณ  ๋งˆ์ง€๋ง‰ ๊ธ€์ž, ์ฒซ ๊ธ€์ž, ๊ฐ€์šด๋ฐ ํ•œ ๊ธ€์ž๋ฅผ ๊ฐ๊ฐ ๋ฐ”๊พผ ์„ธ ๋Œ€์กฐ๊ตฐ์€ ๋ชจ๋‘ Wrong ์ด ๋‚˜์˜จ๋‹ค
verify.py ์‹คํ–‰ ๊ฒฐ๊ณผ โ€” ์ •๋‹ต๋งŒ Flag is DH ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํ”Œ๋ž˜๊ทธ๋ฅผ ์ถœ๋ ฅํ•˜๊ณ  ๋งˆ์ง€๋ง‰ ๊ธ€์ž, ์ฒซ ๊ธ€์ž, ๊ฐ€์šด๋ฐ ํ•œ ๊ธ€์ž๋ฅผ ๊ฐ๊ฐ ๋ฐ”๊พผ ์„ธ ๋Œ€์กฐ๊ตฐ์€ ๋ชจ๋‘ Wrong ์ด ๋‚˜์˜จ๋‹ค

DH{20654ccdb7c43bd1ab398283f9895ac285e8c419c4c157db2f3f50de92599bd8}

ํ•œ ๊ธ€์ž๋งŒ ๋ฐ”๊ฟ”๋„ ์ „๋ถ€ Wrong!. ์–ด๋А ์œ„์น˜๋ฅผ ๊ฑด๋“œ๋ฆฌ๋“  ๊ทธ ์ง€์ ์—์„œ ๊ฒฝ๋กœ๊ฐ€ ๊ฐˆ๋ผ์ ธ ๋‹ค๋ฅธ ์ข…๋ฃŒ ์ƒํƒœ๋กœ ๋–จ์–ด์ง„๋‹ค.




๐Ÿงช ์žฌํ˜„

./reproduce.sh

์˜คํ”„๋ผ์ธ ๋ฌธ์ œ๋ผ ์ธ์ž๊ฐ€ ์—†๋‹ค. ํ•„์š”ํ•œ ๊ฑด python3(+pyelftools), objdump, strace ์…‹์ด๋‹ค.

reproduce.sh ๊ฐ€ ํ•˜๋Š” ์ผ์€ ์ด ๊ธ€์˜ ์ˆœ์„œ์™€ ๊ฐ™๋‹ค. ์ข…๋ฃŒ ์ƒํƒœ๋ฅผ ์ฐพ๊ณ  โ†’ ์ „์ดํ‘œ๋ฅผ ๋ฝ‘๊ณ  โ†’ ๊ธธ์ด 63 ๊ฒฝ๋กœ๋ฅผ ์„ธ์–ด ์œ ์ผํ•œ์ง€ ํ™•์ธํ•˜๊ณ  โ†’ strace ๋กœ ๋Ÿฐํƒ€์ž„๊ณผ ๋Œ€์กฐํ•˜๊ณ  โ†’ ์‹ค์ œ main ์œผ๋กœ ํ”Œ๋ž˜๊ทธ๋ฅผ ๋ฐ›๋Š”๋‹ค.

ํŒ์ •์€ ์ด๋ ‡๊ฒŒ ์ฝ๋Š”๋‹ค.

ํ™”๋ฉด๋œป๋Œ€์ฒ˜
์ „์ด 16๊ฐœ๊ฐ€ ์•„๋‹Œ ์ƒํƒœ: N (N>0)lea/dlopen ์ง์ง“๊ธฐ๊ฐ€ ์‹คํŒจobjdump ๋ฒ„์ „ ํ™•์ธ
์ˆ˜๋ฝ ์ƒํƒœ๊ฐ€ 0๊ฐœ๋‹ค์ข…๋ฃŒ ์ƒํƒœ ํŒ๋ณ„์ด ์–ด๊ธ‹๋‚จ๋‹ค๋ฅธ ๋ฐฐํฌ๋ณธ์ธ์ง€ ํ™•์ธ
๊ธธ์ด๊ฐ€ ๋งž๋Š” ๊ฒฝ๋กœ๊ฐ€ ์—†๋‹ค๊ธธ์ด ์ƒ์ˆ˜๊ฐ€ ํ‹€๋ฆผverify ์˜ 0x3f ๋ฅผ ๋‹ค์‹œ ํ™•์ธ
๊ฒฝ๋กœ ์ˆ˜ : 2๊ฐœ ์ด์ƒ๋‹ต์ด ์—ฌ๋Ÿฟ โ€” ํ•ด์„ ์˜ค๋ฅ˜ ๊ฐ€๋Šฅ์ „์ดํ‘œ๋ฅผ trace_check ๋กœ ๋Œ€์กฐ
strace ๋Œ€์กฐ์—์„œ ๋ถˆ์ผ์น˜์ •์  ํ‘œ๊ฐ€ ํ‹€๋ฆผ๊ทธ ์ธ๋ฑ์Šค์˜ .so ๋ฅผ ์ง์ ‘ ๋””์Šค์–ด์…ˆ๋ธ”
Wrong!์‹คํ–‰ ๊ถŒํ•œ ์—†์Œchmod +x extracted/main

extract_dfa.py ๋Š” .so 1,257๊ฐœ๋ฅผ objdump ๋กœ ์ „๋ถ€ ํŒŒ์‹ฑํ•˜์ง€๋งŒ ํ”„๋กœ์„ธ์Šค ํ’€์„ ์จ์„œ 7์ดˆ๋ฉด ๋๋‚œ๋‹ค. ํŒŒ์ผ์ด ๋งŽ๋‹ค๊ณ  ๊ฒ๋จน์„ ๊ทœ๋ชจ๋Š” ์•„๋‹ˆ๋‹ค.



๐Ÿ“ ๊ฒฐ๋ก 

๋Ÿฐํƒ€์ž„์— ์ด๋ฆ„์œผ๋กœ ๋ถ€๋ฅด๋ฉด ์ •์  ํ˜ธ์ถœ ๊ทธ๋ž˜ํ”„๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค.

dlopen ์˜ ์ธ์ž๋„ dlsym ์˜ ์‹ฌ๋ณผ ์ด๋ฆ„๋„ ์‹คํ–‰ ์ค‘์— ๋งŒ๋“ค์–ด์ง€๋‹ˆ, ๋””์Šค์–ด์…ˆ๋ธ”๋Ÿฌ๊ฐ€ "์ด ํ•จ์ˆ˜๊ฐ€ ์ € ํ•จ์ˆ˜๋ฅผ ๋ถ€๋ฅธ๋‹ค"๋ฅผ ๊ทธ๋ ค ์ค„ ์ˆ˜ ์—†๋‹ค. ์ด ๋ฌธ์ œ๋Š” ๊ทธ ์„ฑ์งˆ์„ ์ด์šฉํ•ด ๋กœ์ง์ด ์•„๋‹ˆ๋ผ ํŒŒ์ผ ๋ฐฐ์น˜์— ์ •๋‹ต์„ ์ˆจ๊ฒผ๋‹ค. main ๊ณผ .so ํ•˜๋‚˜๋งŒ ์ฝ์œผ๋ฉด ์ฝ”๋“œ๋Š” 30์ค„๋„ ์•ˆ ๋˜๋Š”๋ฐ, ๋‹ต์€ 20MB ์–ด์น˜ ํŒŒ์ผ ์ด๋ฆ„ ์†์— ์žˆ๋‹ค.

๊ตฌ์กฐ๊ฐ€ ๋ณด์ด๋ฉด ๋ฆฌ๋ฒ„์‹ฑ์ด ์•„๋‹ˆ๋ผ ๊ทธ๋ž˜ํ”„ ๋ฌธ์ œ๊ฐ€ ๋œ๋‹ค.

.so = ์ƒํƒœ, f_d = ์ „์ด๋ผ๋Š” ๊ฑธ ์•Œ์•„์ฐจ๋ฆฐ ๋’ค๋กœ๋Š” ๋””์Šค์–ด์…ˆ๋ธ”์ด ํ•„์š” ์—†๋‹ค. ํ•„์š”ํ•œ ๊ฑด ์ „์ดํ‘œ ํ•˜๋‚˜๊ณ , ๊ทธ๊ฑด ๊ธฐ๊ณ„์ ์œผ๋กœ ๋ฝ‘ํžŒ๋‹ค. 1,257๊ฐœ๋ฅผ ํ•˜๋‚˜์”ฉ ์ฝ์„ ์ผ์ด ์•„๋‹ˆ๋ผ 1,257๊ฐœ์—์„œ ๊ฐ™์€ ๊ฐ’์„ ๋ฝ‘์•„๋‚ด๋Š” ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์“ธ ์ผ์ด๋‹ค.

"์ตœ๋‹จ"์ด ์•„๋‹ˆ๋ผ "์ •ํ™•ํžˆ ๊ทธ ๊ธธ์ด"๋ฅผ ์ฐพ์•„์•ผ ํ•œ๋‹ค.

์‚ฌ์ดํด์ด ์žˆ๋Š” ๊ทธ๋ž˜ํ”„์—์„œ ๊ธธ์ด๋ฅผ ๋งž์ถ”๋Š” ๊ฑด ์ตœ๋‹จ๊ฒฝ๋กœ์™€ ๋‹ค๋ฅธ ๋ฌธ์ œ๋‹ค. ๋’ค์—์„œ๋ถ€ํ„ฐ reach[k] ๋ฅผ ์ฑ„์›Œ ๋‘๊ณ  ์•ž์œผ๋กœ ๊ฑธ์œผ๋ฉด ๊ฐ„๋‹จํžˆ ํ•ด๊ฒฐ๋˜๋Š”๋ฐ, BFS ๋ฅผ ๊ทธ๋Œ€๋กœ ์“ฐ๋ฉด ์งง์€ ๋‹ต์ด ๋‚˜์™€ verify ์—์„œ ๋ง‰ํžŒ๋‹ค. ์ œ์•ฝ์ด "๋„๋‹ฌ"์ด ์•„๋‹ˆ๋ผ "๋„๋‹ฌ + ๊ธธ์ด"์ผ ๋•Œ ๋Š˜ ๋‚˜์˜ค๋Š” ํ•จ์ •์ด๋‹ค.

์ •์ ์œผ๋กœ ์ฝ์€ ๋ชจ๋ธ์€ ๋Ÿฐํƒ€์ž„๊ณผ ๋Œ€์กฐํ•ด ๋‘๋ฉด ๋งˆ์Œ์ด ํŽธํ•˜๋‹ค.

strace -e trace=openat ํ•œ ์ค„๋กœ ์‹ค์ œ dlopen ์ˆœ์„œ๋ฅผ ๋ฐ›์•„ ์˜ˆ์ธก๊ณผ ๋งž์ถฐ ๋ดค๋‹ค. 64์นธ์ด ์ „๋ถ€ ์ผ์น˜ํ•˜๋‹ˆ ์ „์ดํ‘œ๊ฐ€ ๋งž๋‹ค๋Š” ๊ฒŒ ํ™•์ธ๋๊ณ , ๋งŒ์•ฝ ์–ด๊ธ‹๋‚ฌ๋‹ค๋ฉด ์–ด๋А ์ธ๋ฑ์Šค์—์„œ ๊ฐˆ๋ฆฌ๋Š”์ง€๊นŒ์ง€ ๋ฐ”๋กœ ๋‚˜์™”์„ ๊ฒƒ์ด๋‹ค. ํŒŒ์‹ฑ์œผ๋กœ ๋งŒ๋“  ๋ชจ๋ธ์€ ์ด๋ ‡๊ฒŒ ํ•œ ๋ฒˆ ๋Œ€์กฐํ•ด ๋‘๋Š” ๊ฐ’์ด ํฌ๋‹ค.

๋ฐฉ์–ด ์ชฝ์—์„œ ๋ณด๋ฉด, ์ด๋Ÿฐ ๊ตฌ์กฐ๋Š” ๋ถ„์„์„ ๊ท€์ฐฎ๊ฒŒ ๋งŒ๋“ค ๋ฟ ๋ง‰์ง€๋Š” ๋ชปํ•œ๋‹ค. ํŒŒ์ผ 1,257๊ฐœ๊ฐ€ ์œ„์••์ ์œผ๋กœ ๋ณด์—ฌ๋„ ๊ทœ์น™์ด ํ•˜๋‚˜๋กœ ํ†ต์ผ๋ผ ์žˆ์œผ๋ฉด ์Šคํฌ๋ฆฝํŠธ ํ•œ ํŽธ์— ๋ฌด๋„ˆ์ง„๋‹ค. ์˜คํžˆ๋ ค ๊ทœ์น™์ด ๊ท ์ผํ•˜๋‹ค๋Š” ์  โ€” ๋ชจ๋“  .so ๊ฐ€ ์ •ํ™•ํžˆ 16๊ฐœ ํ•จ์ˆ˜๋ฅผ ๊ฐ–๊ณ  ์ „๋ถ€ ๊ฐ™์€ ๋ชจ์–‘์ด๋ผ๋Š” ์  โ€” ์ด ์ž๋™ ์ถ”์ถœ์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด ์คฌ๋‹ค.

์ด ๊ธ€์ด ๋„์›€์ด ๋๋‚˜์š”?

Comments

๋Œ“๊ธ€

0๊ฐœ

๋Œ“๊ธ€์„ ๋‚จ๊ธฐ๋ ค๋ฉด ๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ด์š”. (๋„ค์ด๋ฒ„ ยท ๊ตฌ๊ธ€ ๊ณ„์ •)

๋Œ“๊ธ€ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘โ€ฆ

Related

๊ด€๋ จ ๊ธ€

3๊ฐœ
[๐Ÿฅ‡ Gold 1] ์„ธ๊ทธํดํŠธ๊ฐ€ ๊ณง ์ •๋‹ต์ด๋‹ค โ€” DreamHack Nonsense ํ’€์ด
blog

[๐Ÿฅ‡ Gold 1] ์„ธ๊ทธํดํŠธ๊ฐ€ ๊ณง ์ •๋‹ต์ด๋‹ค โ€” DreamHack Nonsense ํ’€์ด

main ์€ ํ•ด์‹œ ๊ฒฐ๊ณผ์ธ 32๋น„ํŠธ ๊ฐ’์„ ํฌ์ธํ„ฐ๋กœ ์—ญ์ฐธ์กฐํ•ด ๋งค๋ฒˆ SIGSEGV ๋ฅผ ๋‚ธ๋‹ค. .init_array ๊ฐ€ ๊ฑธ์–ด ๋‘” ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ํดํŠธ ์ฃผ์†Œ๋ฅผ ์ •๋‹ตํ‘œ์™€ ๋Œ€์กฐํ•˜๊ณ  ๋ ˆ์ง€์Šคํ„ฐ๋ฅผ ๊ณ ์ณ ์žฌ๊ฐœํ•˜๋ฏ€๋กœ, ํŒ์ •์€ main ์ด ์•„๋‹ˆ๋ผ ํดํŠธ 24๋ฒˆ์˜ ์ฃผ์†Œ์—ด์ด ํ•œ๋‹ค. ํ•ด์‹œ๋Š” ์ž…๋ ฅ์ด 2๋ฐ”์ดํŠธ๋ฟ์ด๋ผ ๋ฐ”์ด๋„ˆ๋ฆฌ ์ž์ฒด๋ฅผ dlopen ํ•ด ์˜ค๋ผํด๋กœ ์“ฐ๊ณ  ์ „์ˆ˜์กฐ์‚ฌ๋กœ 48๊ธ€์ž๋ฅผ ๋ณต์›ํ–ˆ๋‹ค.
#dreamhack#ctf#reversing+7
2026-08-19#dreamhack +4
[๐Ÿฅˆ Silver 4] ํ™”๋ฉด์— ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ๋œจ๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ์—์„œ flag ๋ฝ‘์•„๋‚ด๊ธฐ โ€” DreamHack fake ํ’€์ด
blog

[๐Ÿฅˆ Silver 4] ํ™”๋ฉด์— ์•„๋ฌด๊ฒƒ๋„ ์•ˆ ๋œจ๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ์—์„œ flag ๋ฝ‘์•„๋‚ด๊ธฐ โ€” DreamHack fake ํ’€์ด

์‹คํ–‰ํ•ด๋„ ์•„๋ฌด๊ฒƒ๋„ ์ถœ๋ ฅํ•˜์ง€ ์•Š๋Š” ELF ๋ฐ”์ด๋„ˆ๋ฆฌ ํ•˜๋‚˜. strace๋กœ ํ™•์ธํ•ด๋ณด๋‹ˆ read/write ๊ณ„์—ด syscall์ด ๋‹จ ํ•œ ๋ฒˆ๋„ ์—†๋‹ค โ€” ์ž„ํฌํŠธ๋œ ํ•จ์ˆ˜์กฐ์ฐจ malloc/free๋ฟ์ด๋‹ค. objdump๋กœ ๋œฏ์–ด๋ณด๋‹ˆ main()์€ ์Šคํƒ์— movabs ์ฆ‰์‹œ๊ฐ’์œผ๋กœ 23๋ฐ”์ดํŠธ ์‹œ๋“œ ๋ฒ„ํผ๋ฅผ ์กฐ๋ฆฝํ•ด mallocํ•œ ๋’ค, reverse โ†’ ์ธ์ ‘ ๋ฐ”์ดํŠธ ๊ตํ™˜ โ†’ XOR๋ผ๋Š” ์„ธ ๋‹จ๊ณ„ ๋ณ€ํ™˜๋งŒ ๊ฑฐ์ณ ๊ณง๋ฐ”๋กœ freeํ•ด๋ฒ„๋ฆฐ๋‹ค. ํ™”๋ฉด์— ์ฐํžˆ๋Š” ๊ฑด ์—†์ง€๋งŒ, ๊ทธ ๋ณ€ํ™˜ ๊ฒฐ๊ณผ๋ฅผ ํŒŒ์ด์ฌ์œผ๋กœ ๊ทธ๋Œ€๋กœ ์žฌํ˜„ํ•˜๊ณ  gdb๋กœ free() ์ง์ „ ํž™ ๋ฒ„ํผ๋ฅผ ์ง์ ‘ ๋“ค์—ฌ๋‹ค๋ณด๋Š” ๊ฒƒ์œผ๋กœ flag๋ฅผ ํ™•์ธํ–ˆ๋‹ค.
#dreamhack#ctf#reversing+4
2026-07-21#dreamhack +4
[๐Ÿฅ‡ Gold 4] WAT ์ •๋…์œผ๋กœ 68๋ฐ”์ดํŠธ ๋˜๋Œ๋ฆฌ๊ธฐ โ€” DreamHack Wasm rev for beginners ํ’€์ด
blog

[๐Ÿฅ‡ Gold 4] WAT ์ •๋…์œผ๋กœ 68๋ฐ”์ดํŠธ ๋˜๋Œ๋ฆฌ๊ธฐ โ€” DreamHack Wasm rev for beginners ํ’€์ด

๋ธŒ๋ผ์šฐ์ €์—์„œ ๋„๋Š” wasm-bindgen ์‚ฐ์ถœ๋ฌผ ํ•˜๋‚˜๊ฐ€ ์ „๋ถ€์ธ ๋ฆฌ๋ฒ„์‹ฑ ๋ฌธ์ œ. wabt ์œผ๋กœ WAT ์„ ๋ฝ‘์•„ check ํ•จ์ˆ˜๋ฅผ ์ฝ์œผ๋ฉด 68๋ฐ”์ดํŠธ XOR ํ‚ค์™€ x*13+37 ์•„ํ•€ ๋ณ€ํ™˜ ๋‘ ๋‹จ๊ณ„๊ฐ€ ์ „๋ถ€๋‹ค. 13 ์˜ mod 256 ์—ญ์› 197 ๋กœ ๋ธŒ๋ฃจํŠธํฌ์Šค ์—†์ด ํ”Œ๋ž˜๊ทธ๋ฅผ ํ•œ ๋ฒˆ์— ๋ณต์›ํ–ˆ๋‹ค.
#dreamhack#ctf#reversing+5
2026-08-20#dreamhack +3