๐Ÿ› Morris Worm โ€” ์ธํ„ฐ๋„ท์„ ์ฒ˜์Œ ๋ฉˆ์ถ”๊ฒŒ ํ•œ ์ตœ์ดˆ์˜ ์ž๊ธฐ๋ณต์ œ ์›œ, 6,000๋Œ€ ๊ฐ์—ผ๊ณผ ์ตœ์ดˆ์˜ ์œ ์ฃ„ ํŒ๊ฒฐ (1988)

2026-09-10 ยท Malware Deep-Dive

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

๋ชจ๋ฆฌ์Šค ์›œ(Morris Worm)์€ 1988๋…„ 11์›” 2์ผ, ๋ฏธ๊ตญ ์ฝ”๋„ฌ๋Œ€ ๋Œ€ํ•™์›์ƒ ๋กœ๋ฒ„ํŠธ ํƒœํŽ€ ๋ชจ๋ฆฌ์Šค(Robert Tappan Morris, ๋‹น์‹œ 24์„ธ)๊ฐ€ ์ธํ„ฐ๋„ท์— ํ’€์–ด๋†“์€, ์—ญ์‚ฌ์ƒ ์ฒ˜์Œ์œผ๋กœ ๋Œ€๊ทœ๋ชจ๋กœ ํผ์ง„ ์ž๊ธฐ๋ณต์ œํ˜• ์ปดํ“จํ„ฐ ์›œ์ด๋‹ค. '์›œ(worm)'์ด๋ผ๋Š” ๊ฐœ๋…์€ 1982๋…„ ์ œ๋ก์Šค PARC์˜ ์กด ์Šˆํฌ(John Shoch)์™€ ์กด ํ•˜ํผ(Jon Hupp)๊ฐ€ ์ •๋ฆฌํ–ˆ์ง€๋งŒ, ๊ทธ๊ฒƒ์„ ์‹ค์ œ ์ธํ„ฐ๋„ท ์ „์ฒด ๊ทœ๋ชจ์—์„œ ์ฒ˜์Œ์œผ๋กœ ๋Œ€์žฌ์•™์œผ๋กœ ๋งŒ๋“  ์‚ฌ๊ฑด์ด ๋ฐ”๋กœ ์ด ์›œ์ด์—ˆ๋‹ค. ์ œ์ž‘์ž๋Š” ์ถœ์ฒ˜๋ฅผ ์ˆจ๊ธฐ๋ ค ์ฝ”๋„ฌ์ด ์•„๋‹Œ MIT์—์„œ ์›œ์„ ์‹คํ–‰ํ–ˆ์ง€๋งŒ, ํŒŒ๊ธ‰์€ ์ธํ„ฐ๋„ท์„ ๋ฉฐ์น ๊ฐ„ ์‚ฌ์‹ค์ƒ ๋งˆ๋น„์‹œ์ผฐ๋‹ค.

2. ๊ฐ์—ผยท์ „ํŒŒ ๊ฒฝ๋กœ โ€” ์–ด๋–ป๊ฒŒ ํผ์กŒ๋‚˜

๋ชจ๋ฆฌ์Šค ์›œ์˜ ํ•ต์‹ฌ์€ "ํ•˜๋‚˜์˜ ์ทจ์•ฝ์ ์„ ๋…ธ๋ฆฐ ์›œ"์ด ์•„๋‹ˆ๋ผ, ์„œ๋กœ ๋‹ค๋ฅธ ์„ธ ๊ฐ€์ง€ ์นจํˆฌ ๊ฒฝ๋กœ๋ฅผ ๋™์‹œ์— ์‹œ๋„ํ•˜๋Š” ๋ฉ€ํ‹ฐ๋ฒกํ„ฐ ์›œ"์ด์—ˆ๋‹ค๋Š” ์ ์ด๋‹ค. ์„ธ ๊ฒฝ๋กœ๋Š” ๋ชจ๋‘ ๋‹น์‹œ ๋„๋ฆฌ ์“ฐ์ด๋˜ ์ธํ„ฐ๋„ท ์„œ๋น„์Šค(๋ฉ”์ผยทfingerยท์›๊ฒฉ ์…ธ)์˜ ํ—ˆ์ ์„ ์ฐ”๋ €๊ณ , ํ•˜๋‚˜๋ผ๋„ ์—ด๋ ค ์žˆ์œผ๋ฉด ๊ฐ์—ผ๋๋‹ค.

3. ๋™์ž‘ ์›๋ฆฌ ์ƒ์„ธ ๋ถ„์„ โ€” ์ฝ”๋“œ/๊ตฌ์กฐ ๋ ˆ๋ฒจ์—์„œ

๋ชจ๋ฆฌ์Šค ์›œ์€ ํฌ๊ฒŒ โ‘  ๊ฐˆ๊ณ ๋ฆฌ(bootstrap) โ‘ก ๋ณธ์ฒด(main worm) ๋กœ ๋‚˜๋‰œ 2๋‹จ ๊ตฌ์กฐ์˜€๋‹ค. ์ด์‹์„ฑ๊ณผ ์€๋ฐ€์„ฑ์„ ๋™์‹œ์— ๋…ธ๋ฆฐ ์„ค๊ณ„์˜€์ง€๋งŒ, '์žฌ๊ฐ์—ผ ํ™•๋ฅ ' ๋กœ์ง ํ•˜๋‚˜๊ฐ€ ์›œ์„ ์ž๋ฉธ๋กœ ๋ชฐ๊ณ  ๊ฐ„ ๊ฒƒ์ด ๊ฐ€์žฅ ์œ ๋ช…ํ•œ ๊ธฐ์ˆ ์  ๊ตํ›ˆ์ด๋‹ค.

4. ํ”ผํ•ด ๊ทœ๋ชจ/์˜ํ–ฅ โ€” ์‹ค์ œ ์‚ฌ๋ก€ ์ˆ˜์น˜

5. PoC โ€” ๊ต์œก์šฉ ๋ชจ์˜ ์ฝ”๋“œ๋กœ ๋™์ž‘ ์›๋ฆฌ ์žฌํ˜„

โš ๏ธ ์•ˆ์ „ ๊ทœ์น™: ์•„๋ž˜ ์ฝ”๋“œ๋Š” ์‹ค์ œ ๋ชจ๋ฆฌ์Šค ์›œ ๋ฐ”์ด๋„ˆ๋ฆฌ๋‚˜ ์ต์Šคํ”Œ๋กœ์ž‡์ด ์•„๋‹ˆ๋ผ "๋™์ž‘ ์›๋ฆฌ๋งŒ ์žฌํ˜„ํ•œ ๊ต์œก์šฉ ๋ชจ์˜ ์ฝ”๋“œ"๋‹ค. ์‹ค์ œ sendmail ๋ช…๋ น ์ฃผ์ž…, ์‹ค์ œ fingerd ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ, ์‹ค์ œ ๋น„๋ฐ€๋ฒˆํ˜ธ ํŒŒ์ผ ํƒˆ์ทจยทํฌ๋ž˜ํ‚น์€ ์ผ์ ˆ ํฌํ•จํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๊ณต๊ฒฉ ๋Œ€์ƒ์€ ๊ฐ€์ƒ์˜ ๋กœ์ปฌ ์‹ค์Šต ํ™˜๊ฒฝ(๋ฉ”๋ชจ๋ฆฌ ์† ๊ฐ€์ƒ ํ˜ธ์ŠคํŠธ, ๊ฐ€์ƒ ๋„คํŠธ์›Œํฌ ๊ทธ๋ž˜ํ”„, ๊ฐ€์ƒ passwd ๋ ˆ์ฝ”๋“œ)์„ ์ „์ œ๋กœ ํ•œ๋‹ค. ์•„๋ž˜ ์ฝ”๋“œ๋Š” ๋ชจ๋‘ ์‹ค์ œ ์‹คํ–‰ํ•ด ์ถœ๋ ฅ์„ ํ™•์ธํ–ˆ๋‹ค.

PoC 1 โ€” '3๋Œ€ ์ „ํŒŒ ๋ฒกํ„ฐ' ๊ตฌ์กฐ: ํ•˜๋‚˜์˜ ์›œ์ด ์„œ๋กœ ๋‹ค๋ฅธ ์„ธ ํ—ˆ์ (sendmail DEBUG, fingerd gets() ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ, rsh+ํฌ๋ž˜ํ‚น๋œ ์•”ํ˜ธ)์„ ๋™์‹œ์— ๋…ธ๋ ค "์–ด๋А ํ•˜๋‚˜๋งŒ ์—ด๋ ค ์žˆ์–ด๋„ ๊ฐ์—ผ"๋˜๋Š” ๊ตฌ์กฐ๋ฅผ ์žฌํ˜„ํ•œ๋‹ค. ๋‹จ์ผ ํŒจ์น˜๋กœ ์›œ์„ ๋ฉˆ์ถœ ์ˆ˜ ์—†๋Š” ์ด์œ ๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.

#!/usr/bin/env python3
"""
PoC 1 โ€” ๋ชจ๋ฆฌ์Šค ์›œ์˜ '3๋Œ€ ์ „ํŒŒ ๋ฒกํ„ฐ' ๊ตฌ์กฐ ๋ชจ์˜ (๊ต์œก์šฉ)

1988๋…„ 11์›” 2์ผ, ๋ชจ๋ฆฌ์Šค ์›œ์€ ์„œ๋กœ ๋‹ค๋ฅธ ์„ธ ์ทจ์•ฝ์ ์„ ์กฐํ•ฉํ•ด ํผ์กŒ๋‹ค:
  A) sendmail DEBUG ๋ช…๋ น ์ฃผ์ž… โ€” ๋””๋ฒ„๊ทธ ๋ชจ๋“œ๋กœ ๋–  ์žˆ๋˜ sendmail์—
     'DEBUG' ๋ช…๋ น์„ ๋ณด๋‚ด ์ž„์˜ ๋ช…๋ น์„ ์‹คํ–‰์‹œํ‚ค๊ณ  ์ฝ”๋“œ๋ฅผ ์ „์†ก
  B) fingerd ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ โ€” gets()๊ฐ€ ์ž…๋ ฅ ๊ธธ์ด๋ฅผ ๊ฒ€์‚ฌํ•˜์ง€ ์•Š๋Š”
     ๊ณ ์ „์  ๋ฒ„๊ทธ๋ฅผ ์ด์šฉํ•ด finger ์„œ๋น„์Šค์— ์…ธ์ฝ”๋“œ๋ฅผ ์ฃผ์ž…
  C) rsh/rexec + ๋น„๋ฐ€๋ฒˆํ˜ธ ํฌ๋ž˜ํ‚น โ€” /etc/passwd(๋‹น์‹œ์—” ์ „์› ์ฝ๊ธฐ ๊ฐ€๋Šฅ)๋ฅผ
     ํ›”์ณ ์•ฝํ•œ ์•”ํ˜ธ๋ฅผ ์˜คํ”„๋ผ์ธ ํฌ๋ž˜ํ‚นํ•œ ๋’ค rsh/rexec๋กœ ์ด๋™

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์‹ค์ œ ๋„คํŠธ์›Œํฌ/์„œ๋น„์Šค ํ˜ธ์ถœ ์—†์ด '๊ฐ€์ƒ์˜ ํ˜ธ์ŠคํŠธ' ๊ฐ์ฒด์— ๋Œ€ํ•ด
๊ฐ ๋ฒกํ„ฐ์˜ ์„ฑ๋ฆฝ ์กฐ๊ฑด๋งŒ ์žฌํ˜„ํ•œ๋‹ค. ์‹ค์ œ ์‹œ์Šคํ…œ์€ ์ „ํ˜€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.
"""
from dataclasses import dataclass


@dataclass
class FakeHost:
    """์‹ค์Šต์šฉ ๊ฐ€์ƒ ํ˜ธ์ŠคํŠธ(์‹ค์ œ ์ปดํ“จํ„ฐ๊ฐ€ ์•„๋‹˜)."""
    name: str
    sendmail_debug: bool   # sendmail์ด DEBUG ๋ชจ๋“œ๋กœ ์‹คํ–‰ ์ค‘์ธ๊ฐ€
    fingerd_gets: bool     # fingerd๊ฐ€ ๊ฒฝ๊ณ„๊ฒ€์‚ฌ ์—†๋Š” gets()๋ฅผ ์“ฐ๋Š”๊ฐ€
    rsh_open: bool         # rsh/rexec ์‹ ๋ขฐ ๊ด€๊ณ„๊ฐ€ ์—ด๋ ค ์žˆ๋Š”๊ฐ€
    weak_password: bool    # ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์‚ฌ์ „ ๊ณต๊ฒฉ์— ๋šซ๋ฆฌ๋Š”๊ฐ€
    infected: bool = False


def vector_sendmail(h: FakeHost) -> bool:
    """A) sendmail DEBUG ๋ชจ๋“œ๋กœ ํ•œ ๋ฒˆ์˜ SMTP ์„ธ์…˜์—์„œ ์›๊ฒฉ ์‹คํ–‰."""
    return h.sendmail_debug


def vector_fingerd(h: FakeHost) -> bool:
    """B) fingerd gets() ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ๋กœ ์…ธ ํš๋“(512๋ฐ”์ดํŠธ ์ดˆ๊ณผ ์ž…๋ ฅ)."""
    return h.fingerd_gets


def vector_rsh(h: FakeHost) -> bool:
    """C) rsh/rexec + ํฌ๋ž˜ํ‚น๋œ ์•”ํ˜ธ๋กœ ๋ฌด์ธ์ฆ/์•”ํ˜ธ ๊ธฐ๋ฐ˜ ์‹คํ–‰."""
    return h.rsh_open and h.weak_password


if __name__ == "__main__":
    hosts = [
        FakeHost("smtp-gw",   sendmail_debug=True,  fingerd_gets=False, rsh_open=False, weak_password=False),
        FakeHost("cs-lab-3",  sendmail_debug=False, fingerd_gets=True,  rsh_open=True,  weak_password=True),
        FakeHost("db-old",    sendmail_debug=False, fingerd_gets=False, rsh_open=True,  weak_password=True),
        FakeHost("bastion",   sendmail_debug=True,  fingerd_gets=True,  rsh_open=True,  weak_password=False),
        FakeHost("hardened",  sendmail_debug=False, fingerd_gets=False, rsh_open=False, weak_password=False),
    ]
    print("== ๋ชจ๋ฆฌ์Šค ์›œ: ํ˜ธ์ŠคํŠธ๋ณ„ ์„ฑ๋ฆฝ ๊ฐ€๋Šฅํ•œ ์ „ํŒŒ ๋ฒกํ„ฐ ==")
    total = 0
    for h in hosts:
        paths = []
        if vector_sendmail(h):
            paths.append("A: sendmail DEBUG")
        if vector_fingerd(h):
            paths.append("B: fingerd gets() ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ")
        if vector_rsh(h):
            paths.append("C: rsh + ํฌ๋ž˜ํ‚น๋œ ์•”ํ˜ธ")
        h.infected = bool(paths)
        total += h.infected
        tag = ", ".join(paths) if paths else "์ „ํŒŒ ๋ถˆ๊ฐ€"
        print(f"  {h.name:9s} -> ๊ฐ์—ผ={h.infected} ({tag})")
    print()
    print(f"๊ฒฐ๋ก : 5๋Œ€ ์ค‘ {total}๋Œ€๊ฐ€ '์„œ๋กœ ๋‹ค๋ฅธ ์ด์œ '๋กœ ๊ฐ์—ผ๋จ "
          "-> ๋‹จ์ผ ์ทจ์•ฝ์  ํŒจ์น˜๋กœ๋Š” ์›œ์„ ๋ฉˆ์ถœ ์ˆ˜ ์—†๋‹ค.")
# ์‹คํ–‰ ๊ฒฐ๊ณผ
== ๋ชจ๋ฆฌ์Šค ์›œ: ํ˜ธ์ŠคํŠธ๋ณ„ ์„ฑ๋ฆฝ ๊ฐ€๋Šฅํ•œ ์ „ํŒŒ ๋ฒกํ„ฐ ==
  smtp-gw   -> ๊ฐ์—ผ=True (A: sendmail DEBUG)
  cs-lab-3  -> ๊ฐ์—ผ=True (B: fingerd gets() ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ, C: rsh + ํฌ๋ž˜ํ‚น๋œ ์•”ํ˜ธ)
  db-old    -> ๊ฐ์—ผ=True (C: rsh + ํฌ๋ž˜ํ‚น๋œ ์•”ํ˜ธ)
  bastion   -> ๊ฐ์—ผ=True (A: sendmail DEBUG, B: fingerd gets() ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ)
  hardened  -> ๊ฐ์—ผ=False (์ „ํŒŒ ๋ถˆ๊ฐ€)

๊ฒฐ๋ก : 5๋Œ€ ์ค‘ 4๋Œ€๊ฐ€ '์„œ๋กœ ๋‹ค๋ฅธ ์ด์œ '๋กœ ๊ฐ์—ผ๋จ -> ๋‹จ์ผ ์ทจ์•ฝ์  ํŒจ์น˜๋กœ๋Š” ์›œ์„ ๋ฉˆ์ถœ ์ˆ˜ ์—†๋‹ค.

PoC 2 โ€” '์žฌ๊ฐ์—ผ ํ™•๋ฅ '๊ณผ ๊ณผ์ž‰ ๋ณต์ œ: ๋ชจ๋ฆฌ์Šค ์›œ์„ ์ž๋ฉธ์‹œํ‚จ 1/7 ์žฌ๊ฐ์—ผ ๋กœ์ง์„ ๊ฐ€์ƒ์˜ ๋„คํŠธ์›Œํฌ ๊ทธ๋ž˜ํ”„์—์„œ '์žฌ๊ฐ์—ผ ์—†์Œ'๊ณผ ๋Œ€์กฐํ•œ๋‹ค. ๊ฐ™์€ '๊ฐ์—ผ ๋Œ€์ˆ˜'๋ผ๋„ ๋ณต์ œ๋ณธ ์ด๋Ÿ‰ยทํ˜ธ์ŠคํŠธ๋‹น ๋ถ€ํ•˜๊ฐ€ ์–ผ๋งˆ๋‚˜ ํญ์ฆํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

#!/usr/bin/env python3
"""
PoC 2 โ€” '์ž๊ฐ€ ๋ณต์ œ + ์žฌ๊ฐ์—ผ' ์ „ํŒŒ ๋™์—ญํ•™ ๋ชจ์˜ (๊ต์œก์šฉ)

๋ชจ๋ฆฌ์Šค ์›œ์˜ ์น˜๋ช…์  ๊ตฌํ˜„ ์‹ค์ˆ˜๋Š” '์ด๋ฏธ ๊ฐ์—ผ๋œ ํ˜ธ์ŠคํŠธ๋„ ํ™•๋ฅ ์ ์œผ๋กœ ๋‹ค์‹œ
๊ฐ์—ผ์‹œํ‚ค๋Š”' ๋กœ์ง์ด์—ˆ๋‹ค. ์„ค๊ณ„์ž๋Š” "๊ฐ€๋” ๋‹ค์‹œ ์‹ฌ์–ด์•ผ ๋ฐฑ์‹ ์„ ์šฐํšŒํ•œ๋‹ค"๊ณ 
ํŒ๋‹จํ•ด 1/7 ํ™•๋ฅ ๋กœ ์žฌ๊ฐ์—ผ์„ ์‹œ๋„ํ–ˆ๋Š”๋ฐ, ์ด ๋•Œ๋ฌธ์— ํ•œ ํ˜ธ์ŠคํŠธ๊ฐ€ ์ˆ˜์‹ญ~์ˆ˜๋ฐฑ
๊ฐœ์˜ ์›œ ํ”„๋กœ์„ธ์Šค๋ฅผ ๋– ์•ˆ์•„ ๋ฉ”๋ชจ๋ฆฌยทCPU๊ฐ€ ๊ณ ๊ฐˆ๋˜๊ณ  ์‹œ์Šคํ…œ์ด ๋‹ค์šด๋๋‹ค.
(์›œ ์ž์‹ ์ด ๋งŒ๋“  '๋ถ€ํ•˜'๊ฐ€ ์›œ์˜ ๊ฐ€์žฅ ํฐ ํ”ผํ•ด์˜€๋‹ค๋Š” ์ ์ด ์•„์ด๋Ÿฌ๋‹ˆ.)

์•„๋ž˜ ์ฝ”๋“œ๋Š” '๊ฐ€์ƒ์˜ ๋„คํŠธ์›Œํฌ ๊ทธ๋ž˜ํ”„'์—์„œ
  (a) ์žฌ๊ฐ์—ผ ์—†์Œ (์˜ฌ๋ฐ”๋ฅธ ์„ค๊ณ„)
  (b) 1/7 ํ™•๋ฅ  ์žฌ๊ฐ์—ผ (๋ชจ๋ฆฌ์Šค ์›œ)
์„ ๋Œ€์กฐํ•ด, ๊ฐ™์€ '๊ฐ์—ผ ํ˜ธ์ŠคํŠธ ์ˆ˜'๋ผ๋„ '๋ณต์ œ๋ณธ ์ด๋Ÿ‰'์ด ์–ผ๋งˆ๋‚˜ ๋‹ฌ๋ผ์ง€๋Š”์ง€ ๋ณธ๋‹ค.
"""
import random

# ๊ฐ€์ƒ์˜ ๋„คํŠธ์›Œํฌ: ๋…ธ๋“œ -> ์ด์›ƒ ๋ชฉ๋ก (์‹ค์ œ ์ปดํ“จํ„ฐ ์•„๋‹˜)
NET = {
    "host-01": ["host-02", "host-03", "host-04"],
    "host-02": ["host-01", "host-03", "host-05"],
    "host-03": ["host-02", "host-04", "host-05"],
    "host-04": ["host-01", "host-03", "host-05"],
    "host-05": ["host-02", "host-03", "host-04", "host-06"],
    "host-06": ["host-05", "host-07"],
    "host-07": ["host-05", "host-06"],
}


def simulate(reinfect_prob, steps=10, seed=42):
    rng = random.Random(seed)
    copies = {h: 0 for h in NET}   # ํ˜ธ์ŠคํŠธ๋ณ„ ์›œ ๋ณต์ œ๋ณธ ์ˆ˜
    copies["host-01"] = 1          # ์ตœ์ดˆ ๊ฐ์—ผ ์ง€์ 
    attempts = 0
    for _ in range(steps):
        for h in list(NET):
            if copies[h] == 0:
                continue
            for nb in NET[h]:
                attempts += 1
                if copies[nb] == 0 or rng.random() < reinfect_prob:
                    copies[nb] += 1
    infected = sum(1 for v in copies.values() if v > 0)
    return copies, infected, attempts


if __name__ == "__main__":
    for label, prob in (("์žฌ๊ฐ์—ผ ์—†์Œ (์˜ฌ๋ฐ”๋ฅธ ์„ค๊ณ„)", 0.0),
                        ("1/7 ํ™•๋ฅ  ์žฌ๊ฐ์—ผ (๋ชจ๋ฆฌ์Šค ์›œ)", 1 / 7)):
        copies, infected, attempts = simulate(prob)
        total = sum(copies.values())
        print(f"[{label}]  {10}์Šคํ…")
        print(f"  ๊ฐ์—ผ ํ˜ธ์ŠคํŠธ: {infected}/{len(NET)}  |  ์ด ๊ฐ์—ผ ์‹œ๋„: {attempts}")
        print(f"  ์›œ ๋ณต์ œ๋ณธ ์ด๋Ÿ‰: {total}")
        heaviest = max(copies.items(), key=lambda kv: kv[1])
        print(f"  ์ตœ๋‹ค ์ค‘๋ณต ํ˜ธ์ŠคํŠธ: {heaviest[0]} ({heaviest[1]}๊ฐœ ๋ณต์ œ๋ณธ)")
        print(f"  -> ํ˜ธ์ŠคํŠธ๋‹น ํ‰๊ท  ๋ถ€ํ•˜: {total / max(infected, 1):.1f}")
        print()
    print("๊ฒฐ๋ก : ์žฌ๊ฐ์—ผ ํ™•๋ฅ  ๋กœ์ง ํ•˜๋‚˜๊ฐ€ '๊ฐ์—ผ ๋Œ€์ˆ˜'๋Š” ๋น„์Šทํ•ด๋„")
    print("      '๋ณต์ œ๋ณธ ์ด๋Ÿ‰'๊ณผ 'ํ˜ธ์ŠคํŠธ๋‹น ๋ถ€ํ•˜'๋ฅผ ํญ์ฆ์‹œํ‚จ๋‹ค. ์›œ์€")
    print("      ํผ์ง€๋Š” ๊ฒƒ๋ณด๋‹ค '๊ณผ์ž‰ ๋ณต์ œ'๋กœ ์‹œ์Šคํ…œ์„ ๋จผ์ € ์ฃฝ์˜€๋‹ค.")
# ์‹คํ–‰ ๊ฒฐ๊ณผ
[์žฌ๊ฐ์—ผ ์—†์Œ (์˜ฌ๋ฐ”๋ฅธ ์„ค๊ณ„)]  10์Šคํ…
  ๊ฐ์—ผ ํ˜ธ์ŠคํŠธ: 7/7  |  ์ด ๊ฐ์—ผ ์‹œ๋„: 200
  ์›œ ๋ณต์ œ๋ณธ ์ด๋Ÿ‰: 7
  ์ตœ๋‹ค ์ค‘๋ณต ํ˜ธ์ŠคํŠธ: host-01 (1๊ฐœ ๋ณต์ œ๋ณธ)
  -> ํ˜ธ์ŠคํŠธ๋‹น ํ‰๊ท  ๋ถ€ํ•˜: 1.0

[1/7 ํ™•๋ฅ  ์žฌ๊ฐ์—ผ (๋ชจ๋ฆฌ์Šค ์›œ)]  10์Šคํ…
  ๊ฐ์—ผ ํ˜ธ์ŠคํŠธ: 7/7  |  ์ด ๊ฐ์—ผ ์‹œ๋„: 200
  ์›œ ๋ณต์ œ๋ณธ ์ด๋Ÿ‰: 36
  ์ตœ๋‹ค ์ค‘๋ณต ํ˜ธ์ŠคํŠธ: host-02 (10๊ฐœ ๋ณต์ œ๋ณธ)
  -> ํ˜ธ์ŠคํŠธ๋‹น ํ‰๊ท  ๋ถ€ํ•˜: 5.1

๊ฒฐ๋ก : ์žฌ๊ฐ์—ผ ํ™•๋ฅ  ๋กœ์ง ํ•˜๋‚˜๊ฐ€ '๊ฐ์—ผ ๋Œ€์ˆ˜'๋Š” ๋น„์Šทํ•ด๋„
      '๋ณต์ œ๋ณธ ์ด๋Ÿ‰'๊ณผ 'ํ˜ธ์ŠคํŠธ๋‹น ๋ถ€ํ•˜'๋ฅผ ํญ์ฆ์‹œํ‚จ๋‹ค. ์›œ์€
      ํผ์ง€๋Š” ๊ฒƒ๋ณด๋‹ค '๊ณผ์ž‰ ๋ณต์ œ'๋กœ ์‹œ์Šคํ…œ์„ ๋จผ์ € ์ฃฝ์˜€๋‹ค.

PoC 3 โ€” ์•ฝํ•œ ์•”ํ˜ธ ์˜คํ”„๋ผ์ธ ํฌ๋ž˜ํ‚น: ์›œ์ด /etc/passwd๋ฅผ ํ›”์ณ ํ”ํ•œ ๋‹จ์–ด์™€ ์‚ฌ์šฉ์ž ์ด๋ฆ„๋งŒ์œผ๋กœ ๊ณ„์ •์„ ๋šซ๊ณ  ๋‹ค์Œ ํ˜ธ์ŠคํŠธ๋กœ ์ด๋™ํ•˜๋˜ ๊ตฌ์กฐ๋ฅผ, ๊ฐ€์ƒ passwd ๋ ˆ์ฝ”๋“œ์— ๋Œ€ํ•œ ์‚ฌ์ „ ๊ณต๊ฒฉ์œผ๋กœ ์žฌํ˜„ํ•œ๋‹ค(์‹ค์ œ crypt ๋Œ€์‹  ์žฌํ˜„์šฉ ํ•ด์‹œ ์‚ฌ์šฉ).

#!/usr/bin/env python3
"""
PoC 3 โ€” '์•ฝํ•œ ์•”ํ˜ธ ์˜คํ”„๋ผ์ธ ํฌ๋ž˜ํ‚น' ์ „ํŒŒ ๋ฒกํ„ฐ ๋ชจ์˜ (๊ต์œก์šฉ)

๋ชจ๋ฆฌ์Šค ์›œ์€ ๊ฐ์—ผ๋œ ํ˜ธ์ŠคํŠธ์—์„œ /etc/passwd๋ฅผ ์ฝ์–ด(๋‹น์‹œ ์•”ํ˜ธ ํ•ด์‹œ๊ฐ€ ์ „์›
์ฝ๊ธฐ ๊ฐ€๋Šฅํ•œ ํŒŒ์ผ์— ์žˆ์—ˆ๋‹ค) ํ”ํ•œ ๋‹จ์–ดยท์‚ฌ์šฉ์ž ์ด๋ฆ„ ๊ธฐ๋ฐ˜ ํ›„๋ณด๋กœ '์˜คํ”„๋ผ์ธ
์‚ฌ์ „ ๊ณต๊ฒฉ'์„ ์ˆ˜ํ–‰ํ–ˆ๊ณ , ๋šซ๋ฆฐ ๊ณ„์ •์œผ๋กœ rsh/rexec๋ฅผ ํƒ€๊ณ  ๋‹ค๋ฅธ ํ˜ธ์ŠคํŠธ๋กœ
์ด๋™ํ–ˆ๋‹ค. ์ฆ‰ ์•ฝํ•œ ์•”ํ˜ธ ํ•˜๋‚˜๊ฐ€ '๋‹ค์Œ ํ˜ธ์ŠคํŠธ๋กœ ๊ฐ€๋Š” ์—ด์‡ '์˜€๋‹ค.

์•„๋ž˜ ์ฝ”๋“œ๋Š” ์‹ค์ œ ์‹œ์Šคํ…œ ์•”ํ˜ธ๊ฐ€ ์•„๋‹ˆ๋ผ, ๊ฐ€์ƒ์˜ passwd ๋ ˆ์ฝ”๋“œ์— ๋Œ€ํ•ด
'์†”ํŠธ + ์•ฝํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ'๊ฐ€ ์–ด๋–ป๊ฒŒ ์‚ฌ์ „ ๊ณต๊ฒฉ์— ๋ฌด๋„ˆ์ง€๋Š”์ง€ ์žฌํ˜„ํ•œ๋‹ค.
์‹ค์ œ crypt(3) ๋Œ€์‹  ์žฌํ˜„์šฉ ๋‹จ์ˆœ ํ•ด์‹œ๋ฅผ ์“ด๋‹ค(๊ต์œก ๋ชฉ์ ).
"""
import hashlib

def mock_crypt(password: str, salt: str) -> str:
    """์‹ค์ œ crypt(3) ๋Œ€์‹  ์“ฐ๋Š” ์žฌํ˜„์šฉ ๋‹จ์ˆœ ํ•ด์‹œ(๊ต์œก ๋ชฉ์ )."""
    return hashlib.sha256((salt + password).encode()).hexdigest()[:16]

# ์‹ค์ œ ๋“ฑ๋ก๋œ (๊ฐ€์ƒ์˜) ๋น„๋ฐ€๋ฒˆํ˜ธ
REAL_PW = {
    "root":  "password",
    "alice": "qwerty",
    "bob":   "Zx9!pLm2#vQ",
    "carol": "carol",
}
SALTS = {"root": "aa", "alice": "bb", "bob": "cc", "carol": "dd"}

# ์›œ์ด ์“ฐ๋˜ ๊ฒƒ์œผ๋กœ ์•Œ๋ ค์ง„ ํ›„๋ณด๊ตฐ: ํ”ํ•œ ๋‹จ์–ด + ์‚ฌ์šฉ์ž ์ด๋ฆ„(+๋ณ€ํ˜•)
WORDLIST = ["password", "admin", "letmein", "dragon", "monkey",
            "secret", "123456", "qwerty"]

def crack(username, salt, target):
    candidates = WORDLIST + [username, username + "123", username.capitalize()]
    for c in candidates:
        if mock_crypt(c, salt) == target:
            return c
    return None

if __name__ == "__main__":
    print("== ๊ฐ€์ƒ /etc/passwd ๋Œ€์ƒ ์˜คํ”„๋ผ์ธ ์‚ฌ์ „ ๊ณต๊ฒฉ ==")
    print(f"{'user':6s} {'salt':4s} {'ํฌ๋ž˜ํ‚น ๊ฒฐ๊ณผ':14s} ์ด๋™ ๊ฐ€๋Šฅ?")
    cracked = 0
    for user, salt in SALTS.items():
        target = mock_crypt(REAL_PW[user], salt)
        pw = crack(user, salt, target)
        if pw:
            cracked += 1
        print(f"{user:6s} {salt:4s} {str(pw):14s} {'์˜ˆ' if pw else '์•„๋‹ˆ์˜ค'}")
    print()
    print(f"๊ฒฐ๊ณผ: 4๊ณ„์ • ์ค‘ {cracked}๊ฐœ๊ฐ€ ํ”ํ•œ ๋‹จ์–ด/์ด๋ฆ„๋งŒ์œผ๋กœ ํฌ๋ž˜ํ‚น๋จ")
    print("๊ฒฐ๋ก : 'ํ•ด์‹œ ํŒŒ์ผ์€ ๋น„๋ฐ€์ด ์•„๋‹ˆ๋‹ค' + '์•ฝํ•œ ์•”ํ˜ธ๋Š” ๊ณง ์›๊ฒฉ ์‹คํ–‰'์ž„์„")
    print("      ๋ณด์—ฌ์ค€๋‹ค. ์†”ํŠธ๊ฐ€ ์žˆ์–ด๋„ ์‚ฌ์ „ ๊ณต๊ฒฉ์—๋Š” ๋ฌด๋ ฅํ–ˆ๋‹ค.")
# ์‹คํ–‰ ๊ฒฐ๊ณผ
== ๊ฐ€์ƒ /etc/passwd ๋Œ€์ƒ ์˜คํ”„๋ผ์ธ ์‚ฌ์ „ ๊ณต๊ฒฉ ==
user   salt ํฌ๋ž˜ํ‚น ๊ฒฐ๊ณผ         ์ด๋™ ๊ฐ€๋Šฅ?
root   aa   password       ์˜ˆ
alice  bb   qwerty         ์˜ˆ
bob    cc   None           ์•„๋‹ˆ์˜ค
carol  dd   carol          ์˜ˆ

๊ฒฐ๊ณผ: 4๊ณ„์ • ์ค‘ 3๊ฐœ๊ฐ€ ํ”ํ•œ ๋‹จ์–ด/์ด๋ฆ„๋งŒ์œผ๋กœ ํฌ๋ž˜ํ‚น๋จ
๊ฒฐ๋ก : 'ํ•ด์‹œ ํŒŒ์ผ์€ ๋น„๋ฐ€์ด ์•„๋‹ˆ๋‹ค' + '์•ฝํ•œ ์•”ํ˜ธ๋Š” ๊ณง ์›๊ฒฉ ์‹คํ–‰'์ž„์„
      ๋ณด์—ฌ์ค€๋‹ค. ์†”ํŠธ๊ฐ€ ์žˆ์–ด๋„ ์‚ฌ์ „ ๊ณต๊ฒฉ์—๋Š” ๋ฌด๋ ฅํ–ˆ๋‹ค.

6. ๋ฐฉ์–ด ๋ฐฉ๋ฒ•/๊ตํ›ˆ โ€” ์–ด๋–ป๊ฒŒ ๋ง‰์•˜๊ณ , ๋ฌด์—‡์„ ๋ฐฐ์šฐ๋‚˜

์ถœ์ฒ˜

ํ•จ๊ป˜ ์ฝ๊ธฐ: ๐Ÿชฑ Conficker (2008) โ€” SMB ์ทจ์•ฝ์  ์›œ์˜ ๊ณ ์ „ ยท ๐Ÿฆ  Mirai (2016) โ€” IoT๋ฅผ ๋ฌด๊ธฐ๋กœ ์‚ผ์€ ๋ด‡๋„ท ยท ๐Ÿ’€ WannaCry (2017) โ€” EternalBlue๋กœ ์ „ ์„ธ๊ณ„๋ฅผ ๊ฐ•ํƒ€ํ•œ ์ž๊ฐ€์ „ํŒŒํ˜• ์›œ

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