๐Ÿ”ฅ Flame (2012) โ€” 'MD5 ์ถฉ๋Œ๋กœ MS ์ธ์ฆ์„œ๋ฅผ ์œ„์กฐํ•œ' 20MB ์ŠคํŒŒ์ด ํ”Œ๋žซํผ

2026-09-22 ยท Malware Deep-Dive (Attack Chain)

1. ๋ฐฐ๊ฒฝ/์—ญ์‚ฌ โ€” ์–ธ์ œ, ๋ˆ„๊ฐ€, ์™œ ์œ ๋ช…ํ•œ๊ฐ€

2012๋…„ 5์›” 28์ผ, ์ด๋ž€ ๊ตญ๊ฐ€ CERT ์‚ฐํ•˜ MAHER ์„ผํ„ฐ์™€ ์นด์Šคํผ์Šคํ‚ค ๋žฉ, ํ—๊ฐ€๋ฆฌ ๋ถ€๋‹คํŽ˜์ŠคํŠธ๊ณต๋Œ€ CrySyS ๋žฉ์ด ๋™์‹œ์— ํ•˜๋‚˜์˜ ์•…์„ฑ์ฝ”๋“œ๋ฅผ ๊ณต๊ฐœํ–ˆ๋‹ค. ์ด๋ฆ„์€ Flame(๋ณ„์นญ FlamerยทsKyWIper). CrySyS ๋žฉ์˜ ํ‘œํ˜„์€ ์ด๋žฌ๋‹ค โ€” "์šฐ๋ฆฌ ์‹ค๋ฌด์—์„œ ๋งŒ๋‚œ ๊ฐ€์žฅ ์ •๊ตํ•œ ์•…์„ฑ์ฝ”๋“œ์ด๋ฉฐ, ์–ด์ฉŒ๋ฉด ์ง€๊ธˆ๊นŒ์ง€ ๋ฐœ๊ฒฌ๋œ ๊ฐ€์žฅ ๋ณต์žกํ•œ ์•…์„ฑ์ฝ”๋“œ๋‹ค." ์นด์Šคํผ์Šคํ‚ค๋Š” "Stuxnet๋ณด๋‹ค 20๋ฐฐ ๋ณต์žกํ•˜๋‹ค"๊ณ  ํ‘œํ˜„ํ–ˆ๊ณ , ์ „์ฒด ๋ถ„์„์—๋Š” 10๋…„์ด ๊ฑธ๋ฆด ์ˆ˜๋„ ์žˆ๋‹ค๊ณ  ๋ง๋ถ™์˜€๋‹ค.

์ถœ๋ฐœ์ ์€ 2012๋…„ 4์›” ์ด๋ž€ ์„์œ ๋ถ€์˜ ์‚ฌ๊ณ ์˜€๋‹ค. ์ด๋ž€ ๋‹น๊ตญ์€ ์ •์œ  ํ„ฐ๋ฏธ๋„์„ ์ธํ„ฐ๋„ท์—์„œ ๋ฌผ๋ฆฌ์ ์œผ๋กœ ๋ถ„๋ฆฌํ–ˆ๊ณ , ๊ตญ์˜ํ†ต์‹ ์‚ฌ๋Š” ๊ทธ ์›์ธ์„ "Wiper"๋ผ๊ณ  ๋ถˆ๋ €๋‹ค. ๊ตญ์ œ์ „๊ธฐํ†ต์‹ ์—ฐํ•ฉ(ITU)์˜ ์š”์ฒญ์„ ๋ฐ›์€ ์นด์Šคํผ์Šคํ‚ค๊ฐ€ ์กฐ์‚ฌ์— ๋“ค์–ด๊ฐ”๊ณ , ๊ณง ์ค‘๋™ ๊ตญ๊ฐ€ ๊ณ ๊ฐ๋“ค์—์„œ๋งŒ ๋‚˜ํƒ€๋‚˜๋Š” ํŠน์ • MD5 ํ•ด์‹œยทํŒŒ์ผ๋ช… ํŒจํ„ด์„ ๋ฐœ๊ฒฌํ•œ๋‹ค. ๋‚ด๋ถ€ ๋ชจ๋“ˆ ์ด๋ฆ„(FROG.DefaultAttacks.A-InstallFlame)์—์„œ ๋”ฐ์˜จ "Flame"์ด ๊ทธ๋ ‡๊ฒŒ ์„ธ์ƒ์— ๋‚˜์™”๋‹ค. ์นด์Šคํผ์Šคํ‚ค๋Š” ์ด ์•”ํ˜ธํ™”๋œ ๋Œ€ํ˜• ํ”Œ๋žซํผ์ด Stuxnet(2010)๊ณผ ๊ฐ•ํ•œ ๊ณ„๋ณด ๊ด€๊ณ„์— ์žˆ๋‹ค๊ณ  ๊ฒฐ๋ก ์ง€์—ˆ๋‹ค โ€” ์ดˆ๊ธฐ Stuxnet์— ๋“ค์–ด ์žˆ๋˜ USB ์ „ํŒŒ ์ฝ”๋“œ๊ฐ€ Flame์˜ ํŠน์ • ๋ชจ๋“ˆ๊ณผ ๊ฑฐ์˜ ๋™์ผํ–ˆ๊ณ , ๋‘ ์•…์„ฑ์ฝ”๋“œ๋Š” ๊ฐ™์€ ์ œ๋กœ๋ฐ์ด ์ทจ์•ฝ์  2๊ฐœ๋ฅผ ๊ทธ๋Œ€๋กœ ์žฌ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

Flame์ด ๋ณด์•ˆ์‚ฌ์— ๋‚จ๊ธด ๊ฒฐ์ •์  ์œ ์‚ฐ์€ ํฌ๊ธฐ๊ฐ€ ์•„๋‹ˆ๋‹ค. MD5 ํ•ด์‹œ ์ถฉ๋Œ(chosen-prefix collision)๋กœ ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ์˜ ์ฝ”๋“œ์„œ๋ช… ์ธ์ฆ์„œ๋ฅผ ์œ„์กฐํ•ด, ์•…์„ฑ ๊ตฌ์„ฑ์š”์†Œ๋ฅผ "๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ๊ฐ€ ์„œ๋ช…ํ•œ ๊ฒƒ"์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ๋งŒ๋“  ์‚ฌ๊ฑด์ด๋‹ค. ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ํ•˜๋‚˜๊ฐ€ PKI ์‹ ๋ขฐ ์‚ฌ์Šฌ ์ „์ฒด๋ฅผ ๋– ๋ฐ›์น˜๊ณ  ์žˆ์Œ์„ ์‹ค์ „์—์„œ ์ฆ๋ช…ํ–ˆ๊ณ , ๊ทธ ๊ฒฐ๊ณผ ์—…๊ณ„์˜ MD5 โ†’ SHA-2 ์ „๋ฉด ์ „ํ™˜์ด ์•ž๋‹น๊ฒจ์กŒ๋‹ค.

  • ์•…์„ฑ์ฝ”๋“œ๋ช…: Flame (Worm.Win32.Flame) ยท ๋ณ„์นญ Flamer / sKyWIper
  • ๊ณต๊ฐœ: 2012-05-28 (MAHERยท์นด์Šคํผ์Šคํ‚คยทCrySyS ๋™์‹œ ๋ฐœํ‘œ)
  • ํ™œ๋™ ๊ธฐ๊ฐ„: ์นด์Šคํผ์Šคํ‚ค ์ถ”์ • ์ตœ์†Œ 2010-02๋ถ€ํ„ฐ ยท CrySyS ๊ด€์ธก ํŒŒ์ผ๋ช… 2007-12 ยท ์œ„์กฐ๋œ ํŒŒ์ผ ์‹œ๊ฐ์€ 1994๋…„๊นŒ์ง€ ์†Œ๊ธ‰
  • ๊ทœ๋ชจ: ์•ฝ 20 MB ยท Lua + C++ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ยท SQLite DB ยท ์•”ํ˜ธํ™” ๋ฐฉ์‹ 5์ข…
  • ๊ฐ์—ผ: ์•ฝ 1,000๋Œ€ (2012-05 ์นด์Šคํผ์Šคํ‚ค ์ถ”์ •) ยท ์ด๋ž€ยท์ด์Šค๋ผ์—˜ยทํŒ”๋ ˆ์Šคํƒ€์ธยท์ˆ˜๋‹จยท์‹œ๋ฆฌ์•„ยท๋ ˆ๋ฐ”๋…ผยท์‚ฌ์šฐ๋””ยท์ด์ง‘ํŠธ, "์••๋„์  ๋‹ค์ˆ˜๊ฐ€ ์ด๋ž€"
  • C2: ์•„์‹œ์•„ยท์œ ๋Ÿฝยท๋ถ๋ฏธ์— ๊ฑธ์นœ ์•ฝ 80๊ฐœ ์„œ๋ฒ„ (์ดํ›„ ์‹ฑํฌํ™€ ๋ถ„์„ ๊ทผ๊ฑฐ)
  • ํ•ต์‹ฌ ๊ธฐ์ˆ : MD5 ์„ ํƒ ์ ‘๋‘์‚ฌ ์ถฉ๋Œ๋กœ ์œ„์กฐ ์ฝ”๋“œ์„œ๋ช… ์ธ์ฆ์„œ ์ƒ์„ฑ โ†’ MS ๋ฃจํŠธ CA๊นŒ์ง€ ์‚ฌ์Šฌ ์—ฐ๊ฒฐ
  • ๋Œ€์‘: MS ๋ณด์•ˆ ๊ณต์ง€ 2718704 / KB2718704(2012-06-03) โ€” ๋ฌธ์ œ ์ธ์ฆ์„œ ํ๊ธฐ + ํ•ด์‹œ ๊ฐ•ํ™”
  • ์ดํ›„: 2012-06-08 ์‹ฑํฌํ™€ C2๊ฐ€ "suicide" ๋ช…๋ น ์ „์†ก(์ „๋Ÿ‰ ์žํญ) ยท 2019๋…„ Flame 2.0 ์žฌ๋ฐœ๊ฒฌ(2014๋…„๊ฒฝ ์ปดํŒŒ์ผ, ํƒ€์ž„์Šคํ†ฐํ•‘)

2. ๊ฐ์—ผยท์ „ํŒŒ ๊ฒฝ๋กœ โ€” ์„์œ ๋ถ€ ์ฑ…์ƒ์—์„œ ๊ฐ™์€ ์ธต ์‚ฌ๋ฌด์‹ค๋กœ

Flame์€ ์›œ์ฒ˜๋Ÿผ ๋Œ€๋Ÿ‰์œผ๋กœ ํผ์ง€๋Š” ๊ฒƒ์„ ๋ชฉํ‘œ๋กœ ํ•˜์ง€ ์•Š์•˜๋‹ค. ํ‘œ์ ์€ ๊ฐ์ฒญยท์ˆ˜์ง‘์ด์—ˆ๊ณ , ๊ทธ๋ž˜์„œ ์ „ํŒŒ ๊ฒฝ๋กœ๋„ "์กฐ์ง ๋‚ด๋ถ€"์— ๋งž์ถฐ ์„ค๊ณ„๋๋‹ค.

3. ๋™์ž‘ ์›๋ฆฌ ์ƒ์„ธ โ€” ๊ทœ๋ชจ๊ฐ€ ์•„๋‹ˆ๋ผ ๊ตฌ์กฐ๊ฐ€ ๋ฌด๊ธฐ์˜€๋‹ค

20 MB๋Š” 2012๋…„ ๊ธฐ์ค€์œผ๋กœ ๋ง๋„ ์•ˆ ๋˜๋Š” ํฌ๊ธฐ๋‹ค. ๋‹น์‹œ ์•…์„ฑ์ฝ”๋“œ๋Š” ๋ณดํ†ต ์ˆ˜์‹ญ~์ˆ˜๋ฐฑ KB์˜€๊ณ , ํ‘œ์  ์ŠคํŒŒ์ด๋Š” "์ž‘๊ณ  ์กฐ์šฉํ•œ" ๊ฒƒ์ด ์ƒ์‹์ด์—ˆ๋‹ค. Flame์€ ์ •๋ฐ˜๋Œ€๋กœ ๊ฐ”๋‹ค.

4. ํ•ต์‹ฌ ๊ธฐ์ˆ  โ€” MD5 ์ถฉ๋Œ๋กœ ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ ์ธ์ฆ์„œ๋ฅผ ์œ„์กฐํ•˜๋‹ค

Flame์˜ ๋ฐฑ๋ฏธ๋Š” ์œ„์กฐ ์ฝ”๋“œ์„œ๋ช… ์ธ์ฆ์„œ๋‹ค. ์ ˆ์ฐจ๋Š” ์ด๋ ‡๋‹ค.

๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ๋Š” 2012๋…„ 6์›” 3์ผ ๋ณด์•ˆ ๊ณต์ง€ 2718704(KB2718704)๋กœ ๋ฌด๋‹จ ๋ฐœ๊ธ‰๋œ ์ธ์ฆ์„œ๋“ค์„ ํ๊ธฐํ•˜๊ณ , ์ฝ”๋“œ์„œ๋ช…ยท์—…๋ฐ์ดํŠธ ๊ฒ€์ฆ์—์„œ MD5 ์‚ฌ์šฉ์„ ์ค‘๋‹จ(SHA-2๋กœ ์ „ํ™˜)ํ–ˆ๋‹ค. MD5๋Š” ์ด๋•Œ "์—ฐ๊ตฌ์‹ค ์ด์•ผ๊ธฐ"์—์„œ "์‹ค์ „ ๋ฌด๊ธฐ"๋กœ ๋„˜์–ด๊ฐ”๋‹ค.

5. ํ”ผํ•ด ๊ทœ๋ชจ/์˜ํ–ฅ โ€” ์ˆซ์ž๋Š” ์ž‘๊ณ , ํŒŒ๊ธ‰์€ ์ปธ๋‹ค

6. PoC โ€” ๊ต์œก์šฉ ๋ชจ์˜ ์ฝ”๋“œ 4์ข… (์‹ค์ œ ์•…์„ฑ์ฝ”๋“œ ์•„๋‹˜)

์•„๋ž˜ ์ฝ”๋“œ๋Š” ๋™์ž‘ ์›๋ฆฌ ์žฌํ˜„์šฉ ๋ชจ์˜ ์ฝ”๋“œ๋‹ค. ์‹ค์ œ Flame ์ƒ˜ํ”Œยท์‹ค์ œ MD5 ์ถฉ๋Œ์Œยท์‹ค์ œ ์ต์Šคํ”Œ๋กœ์ž‡ยทC2 ์ฃผ์†Œ๋Š” ํฌํ•จํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ชจ๋‘ ์ธํ„ฐ๋„ท๊ณผ ๋ถ„๋ฆฌ๋œ ๊ฒฉ๋ฆฌ ๋žฉ(๋กœ์ปฌ VM, ๋ฃจํ”„๋ฐฑ ์ „์šฉ)์—์„œ ์‹คํ–‰์„ ์ „์ œ๋กœ ํ•˜๋ฉฐ, PoC #4์˜ ์„œ๋ฒ„๋Š” 127.0.0.1์—๋งŒ ๋ฐ”์ธ๋”ฉ๋œ๋‹ค.

PoC โ‘  ์„ ํƒ ์ ‘๋‘์‚ฌ ์ถฉ๋Œ์˜ ๊ณจ๊ฒฉ โ€” ์ถ•์†Œ ํ•ด์‹œ์—์„œ ์ง์ ‘ ์ฐพ์•„๋ณธ๋‹ค

MD5 ์ถฉ๋Œ ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์“ฐ๋Š” ๋Œ€์‹ , ํ‘œ์ค€ ํ•ด์‹œ ์ถœ๋ ฅ์„ 20๋น„ํŠธ๋กœ ์ž๋ฅธ ์ถ•์†Œ ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด ๊ฐ™์€ ์ƒ์ผ ๊ณต๊ฒฉ(meet-in-the-middle) ๊ณจ๊ฒฉ์„ ์‹คํ–‰ํ•œ๋‹ค. ์„œ๋กœ ๋‹ค๋ฅธ ๋‘ ์ธ์ฆ์„œ ๋ณธ๋ฌธ (์ •ํ’ˆ/์œ„์กฐ)์— ๊ฐ๊ฐ ์ถฉ๋Œ ์ ‘๋ฏธ์‚ฌ๋ฅผ ๋ถ™์—ฌ ๊ฐ™์€ ํ•ด์‹œ๊ฐ’์„ ์–ป๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ๋‹ค.

#!/usr/bin/env python3
"""Flame(2012) ๊ต์œก์šฉ PoC #1 โ€” '์„ ํƒ ์ ‘๋‘์‚ฌ ์ถฉ๋Œ(chosen-prefix collision)'์˜ ๊ตฌ์กฐ ์žฌํ˜„.

์‹ค์ œ MD5 ์ถฉ๋Œ์Œ์ด๋‚˜ Flame ์ธ์ฆ์„œ ์ž๋ฃŒ๋Š” ํฌํ•จํ•˜์ง€ ์•Š๋Š”๋‹ค.
์—ฌ๊ธฐ์„œ๋Š” '์ถฉ๋Œ์„ ์ฐพ์„ ์ˆ˜ ์žˆ์„ ๋งŒํผ ์•ฝํ•œ ํ•ด์‹œ'๋ฅผ ๋งŒ๋“œ๋Š” ๋Œ€์‹ , ํ‘œ์ค€ ํ•ด์‹œ(SHA-256)์˜
์ถœ๋ ฅ์„ 20๋น„ํŠธ๋กœ ์ž˜๋ผ ์ถ•์†Œ ๋ชจ๋ธ์„ ๋งŒ๋“ ๋‹ค. MD5 ์ฒ˜๋Ÿผ '์ถฉ๋Œ ์ €ํ•ญ์„ฑ์ด ์‚ฌ์‹ค์ƒ ์—†๋Š”'
ํ•ด์‹œ ์œ„์—์„œ ์„ ํƒ ์ ‘๋‘์‚ฌ ์ถฉ๋Œ์ด ์–ด๋–ป๊ฒŒ ๊ณ„์‚ฐ๋˜๋Š”์ง€, ๊ทธ ๊ณจ๊ฒฉ(์ƒ์ผ ๊ณต๊ฒฉ /
meet-in-the-middle)์„ ๊ทธ๋Œ€๋กœ ์‹คํ–‰ํ•ด ๋ณด๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ด๋‹ค.

        H(A || suffix_A) == H(B || suffix_B)  ์ธ ์ ‘๋ฏธ์‚ฌ ์Œ ์ฐพ๊ธฐ

์‹คํ–‰: python3 poc1_collision.py     (๊ฒฉ๋ฆฌ ๋žฉ ์ „์šฉ, ๋„คํŠธ์›Œํฌ ์ ‘๊ทผ ์—†์Œ)
"""
import hashlib

OUT_BITS = 20                              # MD5=128๋น„ํŠธ์˜€๋˜ ๊ฒƒ์„ 20๋น„ํŠธ๋กœ ์ถ•์†Œ
OMASK = (1 << OUT_BITS) - 1


def toy_hash(data: bytes) -> int:
    """์ถ•์†Œ ํ•ด์‹œ ๋ชจ๋ธ: SHA-256 ์ถœ๋ ฅ์„ 20๋น„ํŠธ๋กœ ์ž๋ฅธ๋‹ค.
    (์ถœ๋ ฅ์ด ์งง์•„ ์ถฉ๋Œ์ด ํ”ํ•˜๋ฉฐ, 'ํ•ด์‹œ๊ฐ€ ๊นจ์ง„ ํ•ด์‹œ'์˜ ๊ฑฐ๋™์„ ํ‰๋‚ด๋‚ธ๋‹ค.)"""
    return int.from_bytes(hashlib.sha256(data).digest()[:4], "big") & OMASK


def chosen_prefix_collision(prefix_a: bytes, prefix_b: bytes, tries=1 << 16):
    """A ์ ‘๋‘์‚ฌ์šฉ ์ ‘๋ฏธ์‚ฌ ์‚ฌ์ „์„ ๋งŒ๋“  ๋’ค B ์ ‘๋‘์‚ฌ์—์„œ ์กฐํšŒ(meet-in-the-middle).
    MD5 ๊ณต๊ฒฉ๋„ ๋™์ผํ•œ ๊ตฌ์กฐ๋กœ, Stevens ๋“ฑ์˜ ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ ์ด ํƒ์ƒ‰์„
    'Joux ํŠน์„ฑ + ๊ทผ์ ‘ ์ถฉ๋Œ ๋ธ”๋ก' ์œผ๋กœ ์ตœ์ ํ™”ํ•œ ๊ฒƒ์ด๋‹ค."""
    table = {}
    for i in range(tries):
        s = i.to_bytes(3, "big")
        table.setdefault(toy_hash(prefix_a + s), s)
    for j in range(tries):
        s = j.to_bytes(3, "big")
        h = toy_hash(prefix_b + s)
        if h in table:
            return table[h], s, h
    raise SystemExit("์ถฉ๋Œ ๋ฏธ๋ฐœ๊ฒฌ โ€” OUT_BITS ๋ฅผ ์ค„์ด์„ธ์š”")


if __name__ == "__main__":
    # '์ •ํ’ˆ ์ธ์ฆ์„œ ๋ณธ๋ฌธ'๊ณผ '์œ„์กฐ ์ธ์ฆ์„œ ๋ณธ๋ฌธ' ์—ญํ• ์„ ํ•˜๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ๋‘ ์ ‘๋‘์‚ฌ
    CERT_GOOD = b"TBSCertificate|issuer=Microsoft Enforced Licensing PCA|key=GOOD|"
    CERT_EVIL = b"TBSCertificate|issuer=Microsoft Enforced Licensing PCA|key=EVIL|"

    sa, sb, h = chosen_prefix_collision(CERT_GOOD, CERT_EVIL)
    good, evil = CERT_GOOD + sa, CERT_EVIL + sb

    print("[์ถ•์†Œ ํ•ด์‹œ: ์ถœ๋ ฅ %d๋น„ํŠธ] ์„ ํƒ ์ ‘๋‘์‚ฌ ์ถฉ๋Œ ๋ฐœ๊ฒฌ" % OUT_BITS)
    print("  ์ ‘๋ฏธ์‚ฌ A :", sa.hex(), "| ์ ‘๋ฏธ์‚ฌ B :", sb.hex())
    print("  H(์ •ํ’ˆ||A) =", hex(h), " H(์œ„์กฐ||B) =", hex(h))
    print("  ๋™์ผ ํ•ด์‹œ? ->", toy_hash(good) == toy_hash(evil))
    print("  ๋‘ ๋ฐ”์ดํŠธ์—ด ์ž์ฒด๋Š” ๋‹ค๋ฅธ๊ฐ€? ->", good != evil)
    print()
    print("[๊ฒ€์ฆ] ๊ฐ™์€ ๋‘ ๋ฐ”์ดํŠธ์—ด์„ '์ž๋ฅด์ง€ ์•Š์€' SHA-256 ์œผ๋กœ ๊ณ„์‚ฐํ•˜๋ฉด ์ฆ‰์‹œ ๊ฐˆ๋ผ์ง„๋‹ค")
    print("  sha256(์ •ํ’ˆ||A) =", hashlib.sha256(good).hexdigest()[:32])
    print("  sha256(์œ„์กฐ||B) =", hashlib.sha256(evil).hexdigest()[:32])
    print("  -> ์ถœ๋ ฅ์„ 20๋น„ํŠธ์—์„œ 256๋น„ํŠธ๋กœ ๋˜๋Œ๋ฆฌ๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ์ด ๊ณต๊ฒฉ์€ ์‚ฌ๋ผ์ง„๋‹ค")

์‹คํ–‰ ๊ฒฐ๊ณผ(์ด๋ฒˆ ์‹คํ–‰ ์‹ค์ œ ์ถœ๋ ฅ):

[์ถ•์†Œ ํ•ด์‹œ: ์ถœ๋ ฅ 20๋น„ํŠธ] ์„ ํƒ ์ ‘๋‘์‚ฌ ์ถฉ๋Œ ๋ฐœ๊ฒฌ
  ์ ‘๋ฏธ์‚ฌ A : 00aa64 | ์ ‘๋ฏธ์‚ฌ B : 000000
  H(์ •ํ’ˆ||A) = 0x66956  H(์œ„์กฐ||B) = 0x66956
  ๋™์ผ ํ•ด์‹œ? -> True
  ๋‘ ๋ฐ”์ดํŠธ์—ด ์ž์ฒด๋Š” ๋‹ค๋ฅธ๊ฐ€? -> True

sha256(์ •ํ’ˆ||A) = a9966956aca8d181f8319a164568b2ff
sha256(์œ„์กฐ||B) = 62b66956033f8262f5d2af119f10f635
  -> ์ถœ๋ ฅ์„ 20๋น„ํŠธ์—์„œ 256๋น„ํŠธ๋กœ ๋˜๋Œ๋ฆฌ๋Š” ๊ฒƒ๋งŒ์œผ๋กœ ์ด ๊ณต๊ฒฉ์€ ์‚ฌ๋ผ์ง„๋‹ค

PoC โ‘ก ๊ฒ€์ฆ๊ธฐ๋Š” ์™œ ์œ„์กฐ ์ธ์ฆ์„œ๋ฅผ ํ†ต๊ณผ์‹œ์ผฐ๋‚˜ (๋ชจ์˜)

๊ฐ™์€ ์ถฉ๋Œ์Œ์„ ์ถ•์†Œ X.509 ๊ฒ€์ฆ๊ธฐ์— ํ†ต๊ณผ์‹œํ‚จ๋‹ค. ๊ฒ€์ฆ๊ธฐ๋Š” "์ธ์ฆ์„œ ๋ณธ๋ฌธ ํ•ด์‹œ == ์„œ๋ช…์— ๋‹ด๊ธด ํ•ด์‹œ"๋ฉด ํ†ต๊ณผ๋กœ ํŒ์ •ํ•œ๋‹ค โ€” Flame์ด ์‹ค์ œ๋กœ ๋šซ์€ ์ง€์ ์ด ๋ฐ”๋กœ ์ด ์ „์ œ๋‹ค. ์ทจ์•ฝ ํ•ด์‹œ ๋ชจ๋ธ๊ณผ SHA-256 ๋ชจ๋ธ์„ ๋‚˜๋ž€ํžˆ ๋Œ๋ ค ์ฐจ์ด๋ฅผ ๋ณธ๋‹ค.

#!/usr/bin/env python3
"""Flame(2012) ๊ต์œก์šฉ PoC #2 โ€” 'ํ•ด์‹œ ์ถฉ๋Œ๋กœ ์ฝ”๋“œ์„œ๋ช… ๊ฒ€์ฆ์„ ํ†ต๊ณผ์‹œํ‚ค๋Š”' ํ๋ฆ„(๋ชจ์˜).

Flame ์˜ ํ•ต์‹ฌ์€ ์ฝ”๋“œ์„œ๋ช… ์ธ์ฆ์„œ๋ฅผ ํ•ด์‹œ ์ถฉ๋Œ๋กœ ์œ„์กฐํ•ด ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ ๋ฃจํŠธ CA
๊นŒ์ง€ ์‹ ๋ขฐ ์‚ฌ์Šฌ์ด ์ด์–ด์ง€๊ฒŒ ๋งŒ๋“  ๊ฒƒ์ด๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” PoC #1 ์˜ ์ถ•์†Œ ํ•ด์‹œ๋ฅผ
'๋‹น์‹œ์˜ ์ทจ์•ฝ ํ•ด์‹œ(MD5 ๊ณ„์—ด)' ๋ชจ๋ธ๋กœ, SHA-256 ์„ '๊ฐ•ํ™” ์ดํ›„(SHA-2)' ๋ชจ๋ธ๋กœ ๋‘๊ณ 
๊ฐ™์€ ๊ฒ€์ฆ๊ธฐ๋ฅผ ๋‘ ๋ฒˆ ๋Œ๋ฆฐ๋‹ค.

  - ์ทจ์•ฝ ๋ชจ๋ธ : ์œ„์กฐ ์ธ์ฆ์„œ๊ฐ€ '์„œ๋ช… ์ผ์น˜' ๋กœ ์Šน์ธ๋œ๋‹ค   (2012๋…„ 6์›” ์ด์ „ ์ƒํ™ฉ)
  - ๊ฐ•ํ™” ๋ชจ๋ธ : ์œ„์กฐ ์ธ์ฆ์„œ๊ฐ€ ๊ฑฐ๋ถ€๋œ๋‹ค                  (KB2718704 ์ดํ›„ ์ƒํ™ฉ)

์‹คํ–‰: python3 poc2_verify_bypass.py   (๊ฒฉ๋ฆฌ ๋žฉ ์ „์šฉ, ๋„คํŠธ์›Œํฌ ์ ‘๊ทผ ์—†์Œ)
"""
import hashlib
import sys

sys.path.insert(0, "/tmp")
from poc1_collision import OUT_BITS, toy_hash, chosen_prefix_collision  # ์ถ•์†Œ ํ•ด์‹œ ์žฌ์‚ฌ์šฉ


def verify(cert_body: bytes, signed_digest, hashfn) -> bool:
    """์ถ•์†Œ X.509 ๊ฒ€์ฆ๊ธฐ. ์‹ค์ œ ๊ฒ€์ฆ์€ RSA ์„œ๋ช… ๊ฒ€์ฆ๊นŒ์ง€ ํฌํ•จํ•˜์ง€๋งŒ,
    Flame ์ด ์‹ค์ œ๋กœ ๋šซ์€ ์ง€์ ์€ '์ธ์ฆ์„œ ๋ณธ๋ฌธ ํ•ด์‹œ == ์„œ๋ช…์— ๋‹ด๊ธด ํ•ด์‹œ'
    ๋ผ๋Š” ์ „์ œ์˜€์œผ๋ฏ€๋กœ ๊ทธ ์ง€์ ๋งŒ ๋ชจ๋ธ๋งํ•œ๋‹ค."""
    return hashfn(cert_body) == signed_digest


if __name__ == "__main__":
    GOOD = b"TBSCertificate|serial=0x1B|key=MS_TERMINAL_SERVER_GOOD|"
    EVIL = b"TBSCertificate|serial=0x1B|key=ATTACKER_CONTROLLED_EVIL|"

    sa, sb, _ = chosen_prefix_collision(GOOD, EVIL)
    good_cert, evil_cert = GOOD + sa, EVIL + sb

    # ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ๊ฐ€ '์ •ํ’ˆ' ์ธ์ฆ์„œ์— ๋Œ€ํ•ด ์„œ๋ช…ํ–ˆ์„ ๋•Œ ๋‹ด๊ธด ๋‹ค์ด์ œ์ŠคํŠธ
    ms_digest = toy_hash(good_cert)

    print("[2012๋…„ 6์›” ์ด์ „ โ€” MD5 ๊ณ„์—ด ์ทจ์•ฝ ํ•ด์‹œ (์—ฌ๊ธฐ์„œ๋Š” %d๋น„ํŠธ ์ถ•์†Œ ๋ชจ๋ธ)]" % OUT_BITS)
    print("  ์„œ๋ช…์— ๋‹ด๊ธด ๋‹ค์ด์ œ์ŠคํŠธ :", hex(ms_digest))
    print("  ์ •ํ’ˆ ์ธ์ฆ์„œ ๊ฒ€์ฆ       :", verify(good_cert, ms_digest, toy_hash))
    print("  ์œ„์กฐ ์ธ์ฆ์„œ ๊ฒ€์ฆ       :", verify(evil_cert, ms_digest, toy_hash), " <-- ํ†ต๊ณผ (๋ฌธ์ œ!)")
    print()
    sha = lambda d: hashlib.sha256(d).hexdigest()[:16]
    print("[KB2718704 ์ดํ›„ โ€” SHA-256 ๊ฐ•์ œ]")
    ms_digest_sha = sha(good_cert)
    print("  ์„œ๋ช…์— ๋‹ด๊ธด ๋‹ค์ด์ œ์ŠคํŠธ :", ms_digest_sha)
    print("  ์ •ํ’ˆ ์ธ์ฆ์„œ ๊ฒ€์ฆ       :", verify(good_cert, ms_digest_sha, sha))
    print("  ์œ„์กฐ ์ธ์ฆ์„œ ๊ฒ€์ฆ       :", verify(evil_cert, ms_digest_sha, sha), " <-- ๊ฑฐ๋ถ€ (์ •์ƒ)")
    print()
    print("๊ตํ›ˆ: ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜์€ '์„œ๋ช… ์ฒด๊ณ„ ์ „์ฒด'๋ฅผ ๋– ๋ฐ›์น˜๋Š” ๋ถ€ํ’ˆ์ด๋‹ค.")
    print("      MD5โ†’SHA-2 ์ „ํ™˜ + ๋ฌธ์ œ ์ธ์ฆ์„œ ํ๊ธฐ(KB2718704)๋กœ๋งŒ ๋‹ซํ˜”๊ณ ,")
    print("      ๊ฐ™์€ ๊ณ„์—ด์˜ ์ถฉ๋Œ ๊ณต๊ฒฉ์€ 2008๋…„(์œ„์กฐ CA ์ธ์ฆ์„œ), 2017๋…„(SHA-1, SHAttered)์— ๋ฐ˜๋ณต๋๋‹ค.")

์‹คํ–‰ ๊ฒฐ๊ณผ(์ด๋ฒˆ ์‹คํ–‰ ์‹ค์ œ ์ถœ๋ ฅ):

[2012๋…„ 6์›” ์ด์ „ โ€” MD5 ๊ณ„์—ด ์ทจ์•ฝ ํ•ด์‹œ (์—ฌ๊ธฐ์„œ๋Š” 20๋น„ํŠธ ์ถ•์†Œ ๋ชจ๋ธ)]
  ์„œ๋ช…์— ๋‹ด๊ธด ๋‹ค์ด์ œ์ŠคํŠธ : 0xf3fe1
  ์ •ํ’ˆ ์ธ์ฆ์„œ ๊ฒ€์ฆ       : True
  ์œ„์กฐ ์ธ์ฆ์„œ ๊ฒ€์ฆ       : True  <-- ํ†ต๊ณผ (๋ฌธ์ œ!)

[KB2718704 ์ดํ›„ โ€” SHA-256 ๊ฐ•์ œ]
  ์„œ๋ช…์— ๋‹ด๊ธด ๋‹ค์ด์ œ์ŠคํŠธ : b5ef3fe168bbc968
  ์ •ํ’ˆ ์ธ์ฆ์„œ ๊ฒ€์ฆ       : True
  ์œ„์กฐ ์ธ์ฆ์„œ ๊ฒ€์ฆ       : False  <-- ๊ฑฐ๋ถ€ (์ •์ƒ)

PoC โ‘ข ์ˆจ์€ ๋ชจ๋“ˆ ์ฐพ๊ธฐ โ€” "๋ชจ๋“ˆ ๋ชฉ๋ก"์„ ๋ฏฟ์ง€ ์•Š๋Š” ํƒ์ง€

Flame์˜ ๋ชจ๋“ˆ์€ ํ”„๋กœ์„ธ์Šค๊ฐ€ ๋ณด๊ณ ํ•˜๋Š” ๋ชจ๋“ˆ ๋ชฉ๋ก์— ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋ž˜์„œ ํƒ์ง€์˜ ์ถœ๋ฐœ์ ์€ "๋ฌด์—‡์ด ๋กœ๋“œ๋๋‚˜"๊ฐ€ ์•„๋‹ˆ๋ผ "์„ค๋ช…๋˜์ง€ ์•Š๋Š” ๋ฉ”๋ชจ๋ฆฌยท๋””์Šคํฌ ํ”์ ์ด ์žˆ๋‚˜"๋‹ค. ํ•ฉ์„ฑ ๋ฐ์ดํ„ฐ๋กœ ๊ทธ ๋Œ€์กฐ ๋กœ์ง์„ ์‹คํ–‰ํ•œ๋‹ค.

#!/usr/bin/env python3
"""Flame(2012) ๊ต์œก์šฉ PoC #3 โ€” ๋ชจ๋“ˆํ˜• ์•…์„ฑ์ฝ”๋“œ์˜ '์€๋‹‰ ๋ชจ๋“ˆ' ํƒ์ง€ (๋ชจ์˜ ๋ฐ์ดํ„ฐ).

Flame ์˜ ๋ชจ๋“ˆ์€ ํ”„๋กœ์„ธ์Šค๊ฐ€ ์Šค์Šค๋กœ ๋ณด๊ณ ํ•˜๋Š” '๋กœ๋“œ๋œ ๋ชจ๋“ˆ ๋ชฉ๋ก'์— ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๊ฒŒ
๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์กด์žฌํ–ˆ๋‹ค. ์ฆ‰ ์ •์ƒ์ ์ธ ๋ชจ๋“ˆ ์—ด๊ฑฐ API ๋กœ๋Š” ์•ˆ ๋ณด์ธ๋‹ค. ์ด PoC ๋Š”
'๋ณด๊ณ ๋œ ๋ชจ๋“ˆ ๋ชฉ๋ก' ๊ณผ '์‹ค์ œ ๋ฉ”๋ชจ๋ฆฌ ํ”์ ' ์„ ๋Œ€์กฐํ•ด ๊ทธ ๊ฐ„๊ทน์„ ์ฐพ์•„๋‚ด๋Š”
ํƒ์ง€ ๋กœ์ง์„ ํ•ฉ์„ฑ ๋ฐ์ดํ„ฐ๋กœ ์‹คํ–‰ํ•œ๋‹ค. (์‹ค์ œ ์ฃผ์ž…/์€๋‹‰ ๊ธฐ๋ฒ• ์ฝ”๋“œ๋Š” ์—†์Œ)

์‹คํ–‰: python3 poc3_hidden_module_audit.py   (๊ฒฉ๋ฆฌ ๋žฉ ์ „์šฉ)
"""
import json

# โ”€โ”€ 1. ํ•ฉ์„ฑ(toy) ๋ฐ์ดํ„ฐ: ์นจํ•ด ํ˜ธ์ŠคํŠธ ํ•œ ๋Œ€๋ฅผ ํ‰๋‚ด๋‚ธ๋‹ค โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
HOST = {
    "name": "win-lab-07",
    "reported_modules": [                       # ํ”„๋กœ์„ธ์Šค๊ฐ€ '๋ณด๊ณ ํ•˜๋Š”' ๋ชจ๋“ˆ (์ •์ƒ ์—ด๊ฑฐ ๊ฒฐ๊ณผ)
        {"name": "kernel32.dll", "path": r"C:\Windows\System32\kernel32.dll"},
        {"name": "user32.dll",   "path": r"C:\Windows\System32\user32.dll"},
        {"name": "ws2_32.dll",   "path": r"C:\Windows\System32\ws2_32.dll"},
    ],
    "memory_regions": [                         # ์‹ค์ œ ๋ฉ”๋ชจ๋ฆฌ ์Šค์บ” ๊ฒฐ๊ณผ (๊ฐ€์ƒ ์ฃผ์†Œ ๊ธฐ์ค€)
        {"base": "0x400000", "size": 262144, "prot": "RX",  "backed_by_file": True,  "name": "app.exe"},
        {"base": "0x7A0000", "size": 131072, "prot": "RWX", "backed_by_file": False, "name": None},
        {"base": "0x9C0000", "size": 624640, "prot": "RWX", "backed_by_file": False, "name": None},
        {"base": "0xB00000", "size": 40960,  "prot": "RW",  "backed_by_file": True,  "name": "config.db"},
    ],
    "disk_artifacts": [                         # ๋””์Šคํฌ์— ๋‚จ์€ ํ”์ (ํŒŒ์ผ/๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅ˜)
        {"path": r"C:\Windows\system32\msvcrt10.dll", "type": "file", "note": "์„œ๋ช… ์—†์Œ"},
        {"path": r"HKLM\SYSTEM\CurrentControlSet\Services\mssndsrv", "type": "registry",
         "note": "๊ฐ€์งœ ์˜ค๋””์˜ค ๋“œ๋ผ์ด๋ฒ„ ์„œ๋น„์Šค(์ง€์†์„ฑ)"},
    ],
}

MIN_ANON_RWX = 64 * 1024                        # ์ต๋ช… RWX ์˜์—ญ ํฌ๊ธฐ ์ž„๊ณ„๊ฐ’


def audit(host, min_size=MIN_ANON_RWX):
    """์ •์ƒ ์—ด๊ฑฐ ๋ชฉ๋ก์— ์—†๋Š” '์ด๋ฆ„ ์—†๋Š” RWX ๋ฉ”๋ชจ๋ฆฌ' ๋ฅผ ์ฐพ์•„๋‚ธ๋‹ค."""
    findings = []
    listed = {m["path"].lower() for m in host["reported_modules"]}
    for r in host["memory_regions"]:
        anon = (r["name"] is None) and (not r["backed_by_file"])
        if anon and r["size"] >= min_size and "W" in r["prot"] and "X" in r["prot"]:
            findings.append({
                "host": host["name"], "rule": "anon_rwx_region",
                "severity": "high",
                "detail": "ํŒŒ์ผ๋กœ ์„ค๋ช…๋˜์ง€ ์•Š๋Š” %s ์˜์—ญ %d bytes @ %s"
                          % (r["prot"], r["size"], r["base"]),
            })
    for a in host["disk_artifacts"]:
        if a["type"] == "file" and "์„œ๋ช… ์—†์Œ" in a["note"]:
            findings.append({"host": host["name"], "rule": "unsigned_module_on_disk",
                             "severity": "medium", "detail": a["path"]})
        if a["type"] == "registry" and "์˜ค๋””์˜ค" in a["note"]:
            findings.append({"host": host["name"], "rule": "suspicious_persistence",
                             "severity": "medium",
                             "detail": "%s โ€” %s" % (a["path"], a["note"])})
    # ๋ชจ๋“ˆ ์ˆ˜ ์ž์ฒด์˜ ์ด์ƒ: ๋ฉ”๋ชจ๋ฆฌ ํ”์  ๋Œ€๋น„ ์—ด๊ฑฐ ๋ชฉ๋ก์ด ์ง€๋‚˜์น˜๊ฒŒ ์งง์Œ
    named = sum(1 for r in host["memory_regions"] if r["name"])
    if len(listed) < named:
        findings.append({"host": host["name"], "rule": "module_enum_mismatch",
                         "severity": "high",
                         "detail": "์—ด๊ฑฐ %d๊ฐœ < ๋ช…๋ช…๋œ ์˜์—ญ %d๊ฐœ" % (len(listed), named)})
    return findings


if __name__ == "__main__":
    res = audit(HOST)
    print("ํ˜ธ์ŠคํŠธ:", HOST["name"])
    print(json.dumps(res, ensure_ascii=False, indent=2))
    print("\nํƒ์ง€ ๊ฑด์ˆ˜:", len(res), "โ†’ '๋ชจ๋“ˆ ์—ด๊ฑฐ ๊ฒฐ๊ณผ'๋งŒ ๋ฏฟ์œผ๋ฉด 0๊ฑด, ๋ฉ”๋ชจ๋ฆฌ+RWX+์„œ๋ช… ๋Œ€์กฐ๋กœ %d๊ฑด" % len(res))

์‹คํ–‰ ๊ฒฐ๊ณผ(์ด๋ฒˆ ์‹คํ–‰ ์‹ค์ œ ์ถœ๋ ฅ, ์š”์•ฝ):

ํ˜ธ์ŠคํŠธ: win-lab-07
  [anon_rwx_region]        ํŒŒ์ผ๋กœ ์„ค๋ช…๋˜์ง€ ์•Š๋Š” RWX ์˜์—ญ 131072 bytes @ 0x7A0000   (high)
  [anon_rwx_region]        ํŒŒ์ผ๋กœ ์„ค๋ช…๋˜์ง€ ์•Š๋Š” RWX ์˜์—ญ 624640 bytes @ 0x9C0000   (high)
  [unsigned_module_on_disk] C:\Windows\system32\msvcrt10.dll                     (medium)
  [suspicious_persistence]  HKLM\SYSTEM\...\Services\mssndsrv โ€” ๊ฐ€์งœ ์˜ค๋””์˜ค ๋“œ๋ผ์ด๋ฒ„ (medium)

ํƒ์ง€ ๊ฑด์ˆ˜: 4 โ†’ '๋ชจ๋“ˆ ์—ด๊ฑฐ ๊ฒฐ๊ณผ'๋งŒ ๋ฏฟ์œผ๋ฉด 0๊ฑด, ๋ฉ”๋ชจ๋ฆฌ+RWX+์„œ๋ช… ๋Œ€์กฐ๋กœ 4๊ฑด

PoC โ‘ฃ WPAD/NBNS ์Šคํ‘ธํ•‘ + "๊ฐ€์งœ ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„" ์ „ํŒŒ (127.0.0.1 ํ•œ์ •)

Flame์˜ LAN ์ „ํŒŒ๋ฅผ ๋ฃจํ”„๋ฐฑ ์•ˆ์—์„œ๋งŒ ์žฌํ˜„ํ•œ๋‹ค. ๊ฐ€์งœ ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„(๋ชจ์˜ ๋งค๋‹ˆํŽ˜์ŠคํŠธ๋งŒ ์ œ๊ณต)๋ฅผ ๋„์šฐ๊ณ , WPAD ์ž๋™ ํ”„๋ก์‹œ ํƒ์ƒ‰์„ ํ‰๋‚ด ๋‚ธ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ทธ ์„œ๋ฒ„์—์„œ ๋งค๋‹ˆํŽ˜์ŠคํŠธ๋ฅผ ๋ฐ›๋Š”๋‹ค. ์ด์–ด์„œ ๋ฐฉ์–ด ์Šคํฌ๋ฆฝํŠธ๊ฐ€ "WPAD๊ฐ€ ์‚ฌ๋‚ด ์ ‘๋ฏธ์‚ฌ๋กœ ํ•ด์„๋˜์ง€ ์•Š์Œ" + "์—…๋ฐ์ดํŠธ๋ฅผ ์‚ฌ๋‚ด WSUS๊ฐ€ ์•„๋‹Œ ํ˜ธ์ŠคํŠธ์—์„œ ๋ฐ›์Œ" ๋‘ ์‹ ํ˜ธ๋กœ ํƒ์ง€ํ•œ๋‹ค. ์‹ค์ œ ํŽ˜์ด๋กœ๋“œ ๋ฐฐํฌยท์„ค์น˜๋Š” ํ•˜์ง€ ์•Š๋Š”๋‹ค.

#!/usr/bin/env python3
"""Flame(2012) ๊ต์œก์šฉ PoC #4 โ€” WPAD/NBNS ์Šคํ‘ธํ•‘ + '๊ฐ€์งœ ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„' LAN ์ „ํŒŒ(๋ชจ์˜).

Flame ์€ ๊ฐ™์€ LAN ์•ˆ์—์„œ WINS/NBNS ์ด๋ฆ„ ์กฐํšŒ์™€ WPAD(์ž๋™ ํ”„๋ก์‹œ ์„ค์ •)๋ฅผ ์•…์šฉํ•ด
์ž์‹ ์„ '์ •ํ’ˆ ๋งˆ์ดํฌ๋กœ์†Œํ”„ํŠธ ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„' ๋กœ ์œ„์žฅํ–ˆ๊ณ , ์œ„์กฐ ์ธ์ฆ์„œ๋กœ ์„œ๋ช…๋œ
๊ตฌ์„ฑ ์š”์†Œ๋ฅผ ๋ฐฐํฌํ•ด ๋‹ค๋ฅธ PC ๊ฐ€ ์Šค์Šค๋กœ ์„ค์น˜ํ•˜๊ฒŒ ๋งŒ๋“ค์—ˆ๋‹ค.

์ด PoC ๋Š” ๊ทธ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ **127.0.0.1 ์•ˆ์—์„œ๋งŒ** ์žฌํ˜„ํ•œ๋‹ค.
  1) 127.0.0.1:8412 ์— '๊ฐ€์งœ ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„' ๋ฅผ ๋„์šด๋‹ค (mock ๋งค๋‹ˆํŽ˜์ŠคํŠธ๋งŒ ์ œ๊ณต)
  2) ์ž๋™ ํ”„๋ก์‹œ(WPAD) ํƒ์ƒ‰์„ ํ‰๋‚ด ๋‚ธ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ทธ ์„œ๋ฒ„์—์„œ ๋งค๋‹ˆํŽ˜์ŠคํŠธ๋ฅผ ๋ฐ›๋Š”๋‹ค
  3) ๋ฐฉ์–ด ์Šคํฌ๋ฆฝํŠธ๊ฐ€ 'WPAD ์งˆ์˜ + ๋น„์ธ๊ฐ€ ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„ ์ ‘์†' ์„ ๋กœ๊ทธ์—์„œ ํƒ์ง€ํ•œ๋‹ค
์‹ค์ œ ์•„ํ‹ฐํŒฉํŠธ ๋ฐฐํฌยท์„ค์น˜ยท์‹คํ–‰์€ ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ์™ธ๋ถ€ ๋„คํŠธ์›Œํฌ๋กœ ๋‚˜๊ฐ€์ง€ ์•Š๋Š”๋‹ค.

์‹คํ–‰: python3 poc4_wpad_rogue_wsus.py   (๊ฒฉ๋ฆฌ ๋žฉ ์ „์šฉ)
"""
import http.server
import json
import socketserver
import threading
import urllib.request

ROGUE_PORT = 0                     # 0 = ์šด์˜์ฒด์ œ๊ฐ€ ๋นˆ ํฌํŠธ๋ฅผ ๋ฐฐ์ •(๋žฉ ํ™˜๊ฒฝ ์ถฉ๋Œ ๋ฐฉ์ง€)
CORP_WSUS = "10.20.30.11"          # ๊ฐ€์ƒ์˜ ์‚ฌ๋‚ด ์ •ํ’ˆ ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„
LOG = []                           # ํ•ฉ์„ฑ ๋กœ๊ทธ(ํƒ์ง€ ๋Œ€์ƒ)


def rogue_server():
    class H(http.server.BaseHTTPRequestHandler):
        def do_GET(self):                                    # noqa: N802
            # ํ”„๋ก์‹œ ๊ฒฝ์œ  ์š”์ฒญ์€ ์ ˆ๋Œ€ URI(http://wpad/...)๋กœ ๋“ค์–ด์˜จ๋‹ค
            if "/updates/manifest" in self.path:
                body = json.dumps({
                    "note": "๊ต์œก์šฉ ๋ชจ์˜ ๋งค๋‹ˆํŽ˜์ŠคํŠธ โ€” ์‹ค์ œ ํŽ˜์ด๋กœ๋“œ ์—†์Œ",
                    "server": "ROGUE-WSUS(127.0.0.1)",
                    "artifact": {"name": "mock-update.cab", "sha256": "0" * 64},
                }).encode()
                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.end_headers()
                self.wfile.write(body)
            else:
                self.send_response(404)
                self.end_headers()

        def log_message(self, *a):                           # ์ฝ˜์†” ์žก์Œ ์–ต์ œ
            pass

    socketserver.TCPServer.allow_reuse_address = True
    srv = socketserver.TCPServer(("127.0.0.1", ROGUE_PORT), H)
    threading.Thread(target=srv.serve_forever, daemon=True).start()
    return srv


def victim_client(victim, proxy_ip, proxy_port):
    """WPAD ์ž๋™ ํ”„๋ก์‹œ ํƒ์ƒ‰ โ†’ ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•ด ์—…๋ฐ์ดํŠธ ๋งค๋‹ˆํŽ˜์ŠคํŠธ ์š”์ฒญ."""
    LOG.append({"evt": "wpad_query", "host": victim, "answer": proxy_ip, "suffix": "local"})
    import urllib.error
    proxy = urllib.request.ProxyHandler({"http": "http://%s:%d" % (proxy_ip, proxy_port)})
    opener = urllib.request.build_opener(proxy)
    url = "http://wpad/updates/manifest.xml"          # ์ด๋ฆ„ ํ•ด์„์€ ํ”„๋ก์‹œ๊ฐ€ ๋Œ€์‹  ์ˆ˜ํ–‰
    try:
        with opener.open(url, timeout=5) as r:
            data = json.loads(r.read().decode())
    except Exception as e:                            # ํ”„๋ก์‹œ ๋ฏธ์‚ฌ์šฉ ์‹œ ์‹คํŒจ โ†’ ์ •์ƒ
        data = {"error": str(e)}
    LOG.append({"evt": "update_fetch", "host": victim,
                "server_ip": proxy_ip, "url": url,
                "artifact": data.get("artifact", {}).get("name", "none")})
    return data


def detect(log, corp_wsus=CORP_WSUS):
    """WPAD ์งˆ์˜๊ฐ€ ์‚ฌ๋‚ด ์ ‘๋ฏธ์‚ฌ๋กœ ํ•ด์„๋˜์ง€ ์•Š์•˜๊ฑฐ๋‚˜, ์—…๋ฐ์ดํŠธ๋ฅผ ์‚ฌ๋‚ด WSUS ๊ฐ€ ์•„๋‹Œ
    ํ˜ธ์ŠคํŠธ์—์„œ ๋ฐ›์€ ๊ฒฝ์šฐ๋ฅผ '๊ณ ์‹ ๋ขฐ' ์ด์ƒ์œผ๋กœ ๋ถ„๋ฅ˜ํ•œ๋‹ค."""
    alerts = []
    wpad_hosts = {e["host"] for e in log if e["evt"] == "wpad_query" and e["suffix"] != "corp.example.com"}
    for e in log:
        if e["evt"] == "wpad_query" and e["suffix"] != "corp.example.com":
            alerts.append({"rule": "wpad_resolved_outside_corp", "host": e["host"],
                           "detail": "wpad ์งˆ์˜ ์‘๋‹ต=%s (์‚ฌ๋‚ด ์ ‘๋ฏธ์‚ฌ ์•„๋‹˜)" % e["answer"]})
        if e["evt"] == "update_fetch" and e["server_ip"] != corp_wsus:
            alerts.append({"rule": "update_from_untrusted_host", "host": e["host"],
                           "detail": "์—…๋ฐ์ดํŠธ ์„œ๋ฒ„=%s (์‚ฌ๋‚ด WSUS=%s ์•„๋‹˜)" % (e["server_ip"], corp_wsus)})
    return alerts, wpad_hosts


if __name__ == "__main__":
    srv = rogue_server()
    port = srv.server_address[1]
    print("๊ฐ€์งœ ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„ ๊ธฐ๋™: 127.0.0.1:%d (๊ต์œก์šฉ ๋ชจ์˜)" % port)
    try:
        victim_client("pc-sales-04", "127.0.0.1", port)  # ๋กœ์ปฌ ๋ฃจํ”„๋ฐฑ = '๊ณต๊ฒฉ์ž ํ”„๋ก์‹œ' ์—ญํ• 
    finally:
        srv.shutdown()
        srv.server_close()

    print("โ”€โ”€ ํ•ฉ์„ฑ ๋กœ๊ทธ โ”€โ”€")
    for e in LOG:
        print(" ", e)
    alerts, hosts = detect(LOG)
    print("\nโ”€โ”€ ํƒ์ง€ ๊ฒฐ๊ณผ โ”€โ”€")
    for a in alerts:
        print(" [%s] %s โ€” %s" % (a["rule"], a["host"], a["detail"]))
    print("\nWPAD ๋ฅผ ์‚ฌ๋‚ด ๋ฐ–์œผ๋กœ ํ•ด์„ํ•œ ํ˜ธ์ŠคํŠธ:", ", ".join(sorted(hosts)) or "์—†์Œ")
    print("โ†’ ๊ตํ›ˆ: 'WPAD ์ž๋™ ํƒ์ƒ‰ ๋„๊ธฐ + NBNS ์ฐจ๋‹จ + ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„ ๊ณ ์ •(์ •์ฑ…)' ์ด")
    print("  ์ด ์ „ํŒŒ ๊ฒฝ๋กœ๋ฅผ ํ†ต์งธ๋กœ ๋‹ซ๋Š”๋‹ค (KB2718704 ๋Š” ์‹ ๋ขฐ ์‚ฌ์Šฌ์˜ ๋งˆ์ง€๋ง‰ ๊ตฌ๋ฉ์„ ๋ง‰์•˜๋‹ค).")

์‹คํ–‰ ๊ฒฐ๊ณผ(์ด๋ฒˆ ์‹คํ–‰ ์‹ค์ œ ์ถœ๋ ฅ):

๊ฐ€์งœ ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„ ๊ธฐ๋™: 127.0.0.1:41127 (๊ต์œก์šฉ ๋ชจ์˜)
โ”€โ”€ ํ•ฉ์„ฑ ๋กœ๊ทธ โ”€โ”€
  {'evt': 'wpad_query', 'host': 'pc-sales-04', 'answer': '127.0.0.1', 'suffix': 'local'}
  {'evt': 'update_fetch', 'host': 'pc-sales-04', 'server_ip': '127.0.0.1',
   'url': 'http://wpad/updates/manifest.xml', 'artifact': 'mock-update.cab'}

โ”€โ”€ ํƒ์ง€ ๊ฒฐ๊ณผ โ”€โ”€
 [wpad_resolved_outside_corp]  pc-sales-04 โ€” wpad ์งˆ์˜ ์‘๋‹ต=127.0.0.1 (์‚ฌ๋‚ด ์ ‘๋ฏธ์‚ฌ ์•„๋‹˜)
 [update_from_untrusted_host]  pc-sales-04 โ€” ์—…๋ฐ์ดํŠธ ์„œ๋ฒ„=127.0.0.1 (์‚ฌ๋‚ด WSUS=10.20.30.11 ์•„๋‹˜)

๋˜ ํ•˜๋‚˜์˜ ํŠน์ง• โ€” Flame์€ Lua๋กœ ๊ธฐ๋Šฅ์„ ํ™•์žฅํ–ˆ๋‹ค. ์•„๋ž˜๋Š” ๊ทธ ๊ตฌ์กฐ๋ฅผ ํ‰๋‚ด ๋‚ธ ๋ฌดํ•ดํ•œ ๋ชจ๋“ˆ ์˜ˆ์‹œ๋‹ค(๋กœ์ปฌ ๊ฐ์‚ฌ ๋กœ๊ทธ ํ•œ ์ค„์„ ๋‚จ๊ธธ ๋ฟ์ด๋ฉฐ, ์ˆ˜์ง‘ยท์ „์†ก ๊ธฐ๋Šฅ์€ ์—†๋‹ค).

-- flame_like_module.lua : ํ”Œ๋Ÿฌ๊ทธ์ธ ๊ตฌ์กฐ ์žฌํ˜„์šฉ (๊ต์œก์šฉยท๋ฌดํ•ด)
local M = {}

function M.tick(cfg)                    -- ๋กœ๋”๊ฐ€ ์ฃผ๊ธฐ์ ์œผ๋กœ ํ˜ธ์ถœํ•˜๋Š” ์ง„์ž…์ 
  local f = assert(io.open(cfg.audit_log, "a"))
  f:write("[audit] module tick ", os.date("%Y-%m-%d %H:%M:%S"), "\n")
  f:close()
  return true                           -- ์‹คํŒจํ•ด๋„ ๋ณธ์ฒด๋Š” ๊ณ„์† ๋™์ž‘(๋ชจ๋“ˆ ๊ฒฉ๋ฆฌ)
end

return M

7. ๋ฐฉ์–ด ๋ฐฉ๋ฒ•/๊ตํ›ˆ โ€” Flame์ด ๋‚จ๊ธด ์ฒดํฌ๋ฆฌ์ŠคํŠธ

8. ์ถœ์ฒ˜

โ€ป ๋ณธ๋ฌธ์˜ PoC ์ฝ”๋“œ๋Š” ๊ตฌ์กฐ ์ดํ•ด ๋ชฉ์ ์˜ ๊ต์œก์šฉ ๋ชจ์˜ ์ฝ”๋“œ์ด๋ฉฐ, ์‹ค์ œ Flame ์ƒ˜ํ”Œยท์‹ค์ œ MD5 ์ถฉ๋Œ์Œยท ์‹ค์ œ ์ต์Šคํ”Œ๋กœ์ž‡ยทC2 ์ฃผ์†Œยท์œ ํฌ ๊ธฐ๋Šฅ์€ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์‹ค์Šต์€ ์ธํ„ฐ๋„ท๊ณผ ๋ถ„๋ฆฌ๋œ ๊ฒฉ๋ฆฌ ๋žฉ VM์„ ์ „์ œ๋กœ ํ•ฉ๋‹ˆ๋‹ค.

ํ•จ๊ป˜ ์ฝ๊ธฐ: โš›๏ธ Stuxnet (2010) โ€” ์—์–ด๊ฐญ์„ ๋„˜์€ ์„ธ๊ณ„ ์ตœ์ดˆ์˜ ์‚ฌ์ด๋ฒ„ ๋ฌด๊ธฐ ยท ๐ŸŸฅ Code Red (2001) โ€” ํŒŒ์ผ๋ฆฌ์Šค ๋ฉ”๋ชจ๋ฆฌ ์ƒ์ฃผ ์›œ์˜ ์›ํ˜• ยท โšก Industroyer (2016) โ€” ํ”„๋กœํ† ์ฝœ์„ ๊ทธ๋Œ€๋กœ ๋งํ•œ ICS ์•…์„ฑ์ฝ”๋“œ ยท ๐Ÿฆ… SolarWinds SUNBURST (2020) โ€” ๊ณต๊ธ‰๋งยท์„œ๋ช… ์‹ ๋ขฐ์˜ ๋ถ•๊ดด ยท โ˜ ๏ธ NotPetya (2017) โ€” ์‹ ๋ขฐ๋ฐ›๋Š” ์—…๋ฐ์ดํŠธ ์ฑ„๋„์„ ๋ฌด๊ธฐ๋กœ ์“ด ์™€์ดํผ

๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ