1. 배경/역사 — 언제, 누가, 왜 유명한가
NotPetya는 2017년 6월 27일 우크라이나에서 시작돼 수 시간 만에 전 세계로 번진, 역사상 가장 파괴적인 사이버 공격으로 평가되는 악성코드다. 겉으로는 "파일을 잠그고 300달러를 요구하는 랜섬웨어"처럼 보였지만, 실제로는 복구가 구조적으로 불가능한 '와이퍼(wiper)'였다. 백악관 평가 기준 총 피해액 100억 달러(약 13조 원) 이상을 남긴 이 공격은, 러시아 GRU 산하 Sandworm(샌드웜) 그룹의 소행으로 공식 지목됐다.
- 이름의 유래: 2016년 3월 발견된 Petya 랜섬웨어와 코드·동작이 일부 닮았지만 결정적 차이가 있어, Kaspersky 연구진이 "Petya가 아니다"는 뜻으로 NotPetya라고 명명했다. (이후 일부 매체는 ExPetr, GoldenEye, NotPetya 등으로 혼용)
- 발생 시점: 우크라이나 헌법의 날(6/28) 전야인 6월 27일 아침, 우크라이나 전역의 기업·정부·은행·에너지 시설에서 동시다발적으로 시스템이 마비되기 시작했다. 공격 시점 자체가 우크라이나 국경일을 노린 상징적 선택으로 해석된다.
- 왜 유명한가: ① 최초의 '랜섬웨어 위장 와이퍼' — 금전을 노리지 않고 순수하게 시스템을 파괴할 목적으로 설계됨 ② 공급망(소프트웨어 업데이트)을 무기화한 대표 사례 — 우크라이나의 사실상 표준 회계·세무 프로그램 M.E.Doc의 업데이트 서버가 침해돼 '정품 업데이트'로 위장해 배포됨 ③ NSA에서 유출된 EternalBlue 등 사이버 무기를 국가가 자체 공격에 재사용 ④ 우크라이나 경제의 1/3을 3일간 멈추게 하고, 머스크(Maersk)·머크(Merck)·페덱스(FedEx) 등 우크라이나와 무관한 글로벌 기업들에 수십억 달러의 피해를 입힘.
- 책임자 규명: 2017년 12월 영국·미국 정부가 러시아 GRU(군사정보총국)의 Sandworm 부대(Unit 74455) 소행으로 공식 지목했고, 2018년 2월 백악관이 러시아를 공개 비난했다. 2020년 10월 미국 법무부는 GRU 장교 6명을 NotPetya·올림픽 파괴 공격 등과 관련해 기소했다.
2. 감염·전파 경로 — 어떻게 퍼졌나
NotPetya의 전파는 "공급망을 통한 1차 침투 → 자격 증명 탈취 기반 내부 확산 + SMB 취약점을 통한 무차별 확산"의 3중 구조였다. 우크라이나에 국한된 1차 침투가 어떻게 수 시간 만에 전 세계 60여 개국을 강타하는 웜이 됐는지가 이 사건의 핵심이다.
- 1차 경로 — M.E.Doc 업데이트 서버 오염 (공급망): 우크라이나 기업의 약 80%가 사용하던 세무·회계 소프트웨어 M.E.Doc의 업데이트 서버가 공격자에게 장악됐다. ESET 분석 결과, 업데이트 시스템에 공격 최소 6주 전부터 백도어가 존재했으며 "철저히 계획되고 완벽히 실행된 작전"으로 평가됐다. 6월 27일 아침, 사용자 PC가 '정품 업데이트'를 받는 순간 NotPetya가 설치됐다. "신뢰하는 업데이트 채널"이 단일 실패 지점(single point of failure)이 된 순간이다.
- 2차 경로 — 자격 증명 탈취 후 원격 실행: 감염된 PC는 Mimikatz의 변형판으로 메모리(lsass)에서 로그인 자격 증명을 탈취하고, 이를 이용해 같은 네트워크의 다른 PC에 PsExec 또는 WMI로 원격 코드 실행을 시도했다. 즉 관리자 계정의 비밀번호가 하나 털리면 패치 여부와 무관하게 내부망 전체로 옆으로 이동(lateral movement)할 수 있었다.
- 3차 경로 — EternalBlue·EternalRomance (SMB 웜 확산): NSA가 개발하고 2017년 4월 유출된 EternalBlue(MS17-010, SMBv1 버퍼 오버플로우)와 EternalRomance를 이용해, 패치되지 않은 Windows를 인터넷/내부망에서 무인증 원격 점령했다. WannaCry(2017년 5월)와 같은 취약점·무기였기에, 패치가 밀린 글로벌 기업 네트워크에선 우크라이나발 감염이 순식간에 대륙을 건너 퍼졌다.
- 전파의 비대칭성: NotPetya는 우크라이나 기업(감염원)과 무관한 해외 기업에도 치명적이었다. 예: 덴마크 머스크, 미국 머크, 영국 WPP·DLA Piper, 프랑스 Saint-Gobain 등은 우크라이나와 직접 거래가 없었음에도, 우크라이나 법인/지사 또는 거래처의 감염 PC가 연결된 글로벌망을 통해 2·3차 경로로 감염됐다.
- 이메일 첨부는 없었다: 2016년 Petya는 이메일 첨부로 퍼졌지만, NotPetya는 첨부 파일 전파를 쓰지 않았다. (일부 매체의 "피싱 이메일 유포" 보도는 확인되지 않은 오보다.)
3. 동작 원리 상세 분석 — 코드/구조 레벨에서
NotPetya의 구조는 "전파(드로퍼) — 파괴(부트로더·MFT 암호화) — 위장(가짜 chkdsk·랜섬 메시지)" 3층으로 나뉜다. '랜섬웨어처럼 보이도록' 세심하게 위장했지만, 복구 경로를 의도적으로 전부 제거한 것이 기술적 핵심이다.
- 감염 후 1단계 — 예약 재부팅: 감염 PC는 페이로드를 %TEMP%에 풀고, 약 1시간 뒤 강제 재부팅을 예약한다. 재부팅을 기다리는 이유는 운영 중인 파일 잠금을 피하고, 다음 부팅의 아주 이른 단계(BootExecute)에서 암호화를 실행하기 위해서다. (일부 PC는 사용자가 재부팅하기 전에 이미 다음 단계가 실행되기도 했다.)
- 2단계 — MBR(마스터 부트 레코드) 덮어쓰기: 재부팅 시 악성코드는 디스크의 MBR을 자체 부트로더로 교체한다. 이 부트로더가 실제 암호화 모듈(ELF 바이너리)을 디스크에서 읽어 실행한다. 여기서부터는 OS가 아닌 악성코드가 제어하는 부팅 환경이다.
- 3단계 — NTFS MFT 암호화 = '디스크 전체 잠금': NotPetya는 개별 파일을 하나씩 암호화하는 대신, NTFS 파일 시스템의 MFT(Master File Table)를 Salsa20 스트림 암호로 암호화했다. MFT는 모든 파일·폴더의 위치와 메타데이터를 담는 '파일 시스템의 목차'이므로, MFT가 파괴되면 OS는 파일을 찾지 못해 사실상 디스크 전체에 접근 불가 상태가 된다. (파일 데이터 자체는 남아 있어도 복구가 극도로 어렵다. FAT 파일시스템에서는 MFT 개념이 없어 암호화가 생략되고 랜섬 메시지만 표시됐다 — 반어적으로 FAT 사용자는 쉽게 복구됐다.)
- 4단계 — 가짜 chkdsk 위장: 암호화가 진행되는 동안 화면에는 Windows의 디스크 검사 도구인 chkdsk가 실행되는 것처럼 위장한 화면을 띄운다. 사용자가 "디스크 검사 중"으로 착각해 끄지 않고 기다리게 만드는 심리적 장치다. (이 화면이 뜨는 즉시 전원을 끄면 암호화를 중단시킬 수 있다는 것이 초기 대응 요령이었다.)
- 5단계 — 랜섬 메시지 (위장): 암호화 완료 후 빨간 화면에 "파일이 암호화됐다. 300달러 상당의 비트코인을 지불하라"는 메시지를 표시한다. 그런데 여기에 세 가지 '이상한 점'이 있었다:
- 요구액이 300달러로 고정 (당시 실제 랜섬웨어는 피해자 규모에 따라 수백~수천 달러, 건당 고유 지갑을 생성)
- 단일 고정 비트코인 지갑을 모든 피해자가 공유 (피해자 식별·복구 키 매칭 구조가 아예 없음)
- 복구 문의용 이메일 주소는 Posteo가 약관 위반으로 이미 정지시켜 연락 불가 — 지불 확인 절차 자체가 성립 불가
- 전파·파괴의 분리 설계: 감염 직후 바로 파괴하지 않고 먼저 최대한 옆으로 퍼진 뒤, 예약된 재부팅 시점에 일제히 파괴하는 순서를 택했다. 한 대가 파괴되기 전에 수백 대로 증식할 시간을 벌어, 내부망 전체가 동시에 '터지는' 효과를 냈다. 이 설계 때문에 수동 대응(전원 차단·네트워크 분리)이 늦어지면 피해가 기하급수로 커졌다.
- 백신(퍼시스턴스 방지책) 존재: 연구자들은 Windows 디렉터리에
perfc또는perfc.dat·perfc.dll이라는 읽기 전용 파일을 만들어 두면 이 변종의 페이로드가 실행을 중단한다는 점을 발견해 '백신'으로 공유했다. (NotPetya는 WannaCry의 킬 스위치처럼 외부 도메인에 의존하지 않았고, 이 로컬 파일 존재 여부가 유일한 '꺼짐 스위치'였다.) (PoC 3에서 재현)
4. 피해 규모/영향 — 실제 사례 수치
- 총 피해액 — 100억 달러 이상: 백악관(당시 국토안보보좌관 Tom Bossert) 평가 기준 NotPetya의 전 세계 피해는 100억 달러(약 13조 원) 초과. WIRED는 NotPetya를 "역사상 가장 파괴적인 사이버 공격"으로 명명했다.
- 우크라이나 (1차 표적): 정부 기관·은행(PrivatBank 등)·에너지 기업(Kyivenergo)·지하철·공항(Boryspil)·통신이 마비됐고, 체르노빌 원전의 방사선 모니터링 시스템이 오프라인으로 전환돼 수동 계측으로 대체됐다. 우크라이나 경제의 약 1/3이 3일간 정지했고, 그 손실만 4억 달러 이상으로 추산됐다.
- 머스크 Maersk (덴마크 해운): 세계 최대 컨테이너 해운사. 전 세계 항만·물류 운영이 수일간 정지됐다. 복구에 4,000+대 서버, 45,000+대 PC를 전면 재설치했고, 피해액은 2억~3억 달러로 집계됐다. (유명한 일화: 가나의 한 오프라인 도메인 컨트롤러가 유일하게 살아남아 전 세계 재구축의 기반이 됐다.)
- FedEx(페덱스) — TNT Express: 유럽 자회사 TNT Express의 시스템이 타격을 입어, FedEx는 2019년 연례 보고서에서 NotPetya 영향으로 2018년 실적이 약 4억 달러 감소했다고 공시했다.
- 머크 Merck (미국 제약): 생산·연구 시설이 며칠간 중단되며 수억 달러의 손실을 입었다. (이후 머크는 보험사를 상대로 소송을 제기했고, 2022년 합의로 마무리됐다.)
- 그 외 글로벌 피해자: 영국 광고대기업 WPP, 글로벌 로펌 DLA Piper, 프랑스 건자재 Saint-Gobain, 영국 소비재 Reckitt Benckiser, 미국 식품 Mondelez(피해 1억 달러 이상 — 보험사 Zurich가 "전쟁 행위"를 이유로 지급을 거부해 유명해짐), 독일 DHL·Beiersdorf, 러시아 Rosneft(생산엔 차질 없음) 등. 러시아 국영석유 Rosneft까지 피해를 입은 점은 "우크라이나 공격의 오염 피해가 공격자 동맹까지 덮쳤다"는 아이러니로 자주 인용된다.
- 피해의 성격: 돈을 갈취한 것이 아니라 시스템을 영구 파괴했다는 점에서 피해액이 '몇 달러 랜섬 지불'이 아니라 전산망 재구축·물류 중단·생산 차질의 형태로 발생했다. 사이버 공격 피해의 패러다임을 "금전 갈취"에서 "국가 간 물리적 혼란 유발"로 바꾼 분수령이다.
5. PoC — 교육용 모의 코드로 동작 원리 재현
PoC 1 — 공급망(업데이트 서버) 오염 구조: "정품 업데이트 채널이 장악되면 어떤 일이 벌어지는가"를 서명 검증 유무에 따라 대조한다. M.E.Doc 사태의 본질 — 공급망의 신뢰성이 무너지면 수만 대 PC가 '정품 업데이트'로 동시 감염된다는 점 — 을 가상 서버 객체로 재현한다.
#!/usr/bin/env python3
"""
PoC 1 — NotPetya의 M.E.Doc 공급망 오염 구조 모의 (교육용)
2017년 6월 27일, 우크라이나 회계·세무 소프트웨어 M.E.Doc의 업데이트 서버가
침해당했고(ESET 분석 결과 백도어가 최소 6주 전부터 존재), 정품 업데이트로
위장한 트로이 목마가 수만 개 기업의 '신뢰된 갱신 경로'를 타고 동시에
배포됐다. 이른바 '신뢰할 수 있는 업데이트 채널'이 단일 실패 지점이 된 사례다.
아래 코드는 네트워크 호출 없이 '가상의 업데이트 서버/클라이언트' 객체로
그 구조만 재현한다:
1) (정상) 서명이 검증된 업데이트 -> 수락
2) (공격) 버전 번호만 높고 서명이 없는 악성 업데이트 -> 서명 미검증
클라이언트는 수락, 서명 검증 클라이언트는 거부
실제 M.E.Doc 소프트웨어·실제 서버를 대상으로 하지 않는다.
"""
# --- 가상의 업데이트 메타데이터 ---
def make_update(version: str, payload: str, signature: str | None):
"""payload: 'clean'이면 정상 패치, 'trojan'이면 악성 페이로드."""
return {"version": version, "payload": payload, "signature": signature}
REAL_SIG = "SIG-VERIFIED" # 정품 개발사만 가진 서명 키
class FakeUpdateServer:
"""공격자에게 장악당한 가상의 업데이트 서버."""
def __init__(self):
# 공격자는 서버를 장악했으므로 '광고 목록(매니페스트)'까지 조작할 수 있다
self.updates = {
"1.2.0": make_update("1.2.0", "clean", REAL_SIG),
"1.2.1": make_update("1.2.1", "trojan", None), # 서명 자체가 없음
"1.2.2": make_update("1.2.2", "trojan", "FORGED-SIG"), # 위조 서명
# 1.2.3(정상 패치)도 서버에 있지만, 공격자는 광고에서 빼버림
}
self.advertised = "1.2.2" # 공격자가 광고하는 '최신' 버전
def latest(self):
# 공격자가 조작한 매니페스트가 광고하는 버전
return self.advertised
class NaiveUpdateClient:
"""서명을 검증하지 않는 (2017년의 수많은 기업용 SW와 같은) 클라이언트."""
name = "서명 미검증 클라이언트 (취약)"
def __init__(self):
self.installed = "1.2.0"
def poll(self, server: FakeUpdateServer):
latest = server.latest()
print(f" 서버가 광고하는 최신 버전: {latest}")
if latest > self.installed:
upd = server.updates[latest]
print(f" 버전 {latest} 발견 -> 다운로드 -> INSTALL (payload={upd['payload']})")
self.installed = latest
return upd["payload"] == "trojan"
return False
class VerifyingUpdateClient:
"""업데이트 서명을 검증하는 클라이언트 (올바른 방어)."""
name = "서명 검증 클라이언트 (안전)"
def __init__(self):
self.installed = "1.2.0"
# 화이트리스트: 신뢰하는 서명 키
self.trusted_keys = {REAL_SIG}
def poll(self, server: FakeUpdateServer):
latest = server.latest()
print(f" 서버가 광고하는 최신 버전: {latest}")
if latest > self.installed:
upd = server.updates[latest]
if upd["signature"] and upd["signature"] in self.trusted_keys:
print(f" 버전 {latest} 서명 검증 OK -> INSTALL (payload={upd['payload']})")
self.installed = latest
return upd["payload"] == "trojan"
else:
print(f" 버전 {latest} 서명 검증 실패(sig={upd['signature']}) -> 거부")
return False
return False
if __name__ == "__main__":
server = FakeUpdateServer()
print("== 시나리오: 공급망 업데이트 서버가 장악된 경우 ==")
print()
print(f"[1] {NaiveUpdateClient.name}")
infected = NaiveUpdateClient().poll(server)
print(f" -> 트로이 목마 설치됨? {infected} (전 직원 PC가 '정품 업데이트'로 감염)")
print()
print(f"[2] {VerifyingUpdateClient.name}")
infected2 = VerifyingUpdateClient().poll(server)
print(f" -> 트로이 목마 설치됨? {infected2} (서명 검증으로 차단)")
print()
print("교훈: 공급망 공격에서 '서버를 믿는 것'은 충분하지 않다. "
"코드 서명 검증이 있으면 서버가 장악돼도 악성 업데이트는 설치되지 않는다.")
# 실행 결과
== 시나리오: 공급망 업데이트 서버가 장악된 경우 ==
[1] 서명 미검증 클라이언트 (취약)
서버가 광고하는 최신 버전: 1.2.2
버전 1.2.2 발견 -> 다운로드 -> INSTALL (payload=trojan)
-> 트로이 목마 설치됨? True (전 직원 PC가 '정품 업데이트'로 감염)
[2] 서명 검증 클라이언트 (안전)
서버가 광고하는 최신 버전: 1.2.2
버전 1.2.2 서명 검증 실패(sig=FORGED-SIG) -> 거부
-> 트로이 목마 설치됨? False (서명 검증으로 차단)
교훈: 공급망 공격에서 '서버를 믿는 것'은 충분하지 않다. 코드 서명 검증이 있으면 서버가 장악돼도 악성 업데이트는 설치되지 않는다.
PoC 2 — '자격 증명 탈취 + SMB 취약점' 이중 전파 축: NotPetya가 내부망을 가로지르는 두 갈래 전파 (① 패치 안 된 PC는 EternalBlue로 무인증 점령 ② 패치된 PC라도 Mimikatz로 훔친 자격 증명으로 점령)를 가상의 호스트 4대로 시뮬레이션한다. 실제 네트워크 통신은 없다.
#!/usr/bin/env python3
"""
PoC 2 — NotPetya의 '이중 전파 축' 구조 모의 (교육용)
NotPetya는 한 PC에 들어온 뒤 두 갈래로 옆으로 퍼졌다:
A축) EternalBlue(MS17-010, SMBv1) — 패치되지 않은 PC를 원격 점령
B축) Mimikatz로 lsass에서 훔친 자격 증명 + PsExec/WMIC — '패치된' PC도
관리자 계정을 탈취당하면 점령
아래 코드는 실제 SMB/네트워크 호출 없이 '가상의 내부망 호스트' 객체들에
대해 이 두 축의 조건부 로직만 재현한다. 실제 시스템은 일절 건드리지 않는다.
"""
from dataclasses import dataclass
@dataclass
class FakeHost:
"""실습용 가상 호스트 (실제 컴퓨터가 아님)."""
name: str
patched: bool # MS17-010 패치 여부
admin_pwd: str # 관리자 계정 비밀번호 (가상)
compromised: bool = False
# Mimikatz가 lsass 메모리에서 훔쳐낸 '것처럼' 만든 가상 자격 증명 풀
# (감염 PC에 로그인돼 있던 세션의 자격 증명이라고 가정)
STOLEN_PASSWORDS = {"P@ssw0rd", "Winter2026!"}
def axis_a_eternalblue(target: FakeHost) -> bool:
"""A축: 패치 전 호스트는 SMBv1 취약점으로 인증 없이 점령."""
if target.patched:
return False
target.compromised = True
return True
def axis_b_stolen_creds(target: FakeHost, creds) -> bool:
"""B축: 패치된 호스트라도 훔친 관리자 자격 증명이 통하면 점령."""
if target.admin_pwd in creds:
# 실제로는 원격 WMI/PsExec로 프로세스 실행 (여기서는 플래그만)
target.compromised = True
return True
return False
if __name__ == "__main__":
network = [
FakeHost("PC-FIN-01", patched=False, admin_pwd="P@ssw0rd"), # 패치 안 됨+흔한 암호
FakeHost("SRV-DB-02", patched=True, admin_pwd="P@ssw0rd"), # 패치됨+훔친 풀에 있는 암호
FakeHost("PC-RND-03", patched=True, admin_pwd="Xk9#mQ2!vL"), # 패치됨+고유 강한 암호
FakeHost("DC-AD-01", patched=True, admin_pwd="R7$tY4@nPqW"), # 도메인 컨트롤러(고유 강한 암호)
]
print("== 감염 PC(PC-FIN-01 진입 가정)의 내부망 전파 시뮬레이션 ==")
for host in network:
via_a = axis_a_eternalblue(host)
via_b = not via_a and axis_b_stolen_creds(host, STOLEN_PASSWORDS)
how = ("A축 EternalBlue" if via_a else
"B축 훔친 자격증명+PsExec" if via_b else "전파 실패")
print(f" {host.name}: patched={host.patched}, admin_pwd={host.admin_pwd} "
f"-> 점령={host.compromised} ({how})")
print()
print("결론: ① 패치는 A축을 막지만 ② 자격 증명 탈취(Mimikatz)는 패치를 "
"우회한다 -> '계정 보호 + 네트워크 세분화'가 함께 필요하다.")
# 실행 결과
== 감염 PC(PC-FIN-01 진입 가정)의 내부망 전파 시뮬레이션 ==
PC-FIN-01: patched=False, admin_pwd=P@ssw0rd -> 점령=True (A축 EternalBlue)
SRV-DB-02: patched=True, admin_pwd=P@ssw0rd -> 점령=True (B축 훔친 자격증명+PsExec)
PC-RND-03: patched=True, admin_pwd=Xk9#mQ2!vL -> 점령=False (전파 실패)
DC-AD-01: patched=True, admin_pwd=R7$tY4@nPqW -> 점령=False (전파 실패)
결론: ① 패치는 A축을 막지만 ② 자격 증명 탈취(Mimikatz)는 패치를 우회한다 -> '계정 보호 + 네트워크 세분화'가 함께 필요하다.
PoC 3 — '랜섬웨어 위장 와이퍼'의 파괴 구조: 진짜 랜섬웨어(복구 키 보관)와 NotPetya형 와이퍼(키 폐기)의 차이를
가상 MFT 객체로 대조하고, 실제로 보고된 perfc '백신' 파일의 동작 조건도 함께 보여준다. 실제 디스크는 건드리지 않는다.
#!/usr/bin/env python3
"""
PoC 3 — '랜섬웨어로 위장한 와이퍼' NotPetya의 암호화 구조 모의 (교육용)
NotPetya는 겉으로는 '파일을 잠그고 $300를 요구하는 랜섬웨어'처럼 보였지만,
암호화 루틴이 원복 불가능하게 설계되어 사실상 와이퍼였다:
1) 부팅 시 MBR 부트로더를 덮어써 강제 재부팅 유도
2) NTFS MFT(마스터 파일 테이블)를 Salsa20으로 암호화 -> 파일명 트리 소실
3) 'chkdsk'로 위장한 화면을 띄우며 암호화 진행
4) 단일 고정 BTC 지갑에 $300 요구 + 복구용 이메일은 이미 정지
-> '지불해도 복구 불가능' = 금전 목적이 아닌 파괴 목적
아래 코드는 가상의 MFT 레코드 목록을 대상으로 '진짜 랜섬웨어(복구 키 보관)'와
'NotPetya형 와이퍼(키 폐기)'의 차이만 재현한다. 실제 디스크·파일은 건드리지 않는다.
"""
class FakeMFT:
"""가상의 NTFS MFT 레코드 (파일명 -> 논리 블록). 실제 디스크와 무관."""
def __init__(self, files):
self.files = {f: i for i, f in enumerate(files)} # name -> record idx
class RealRansomware:
"""비교군: 진짜 랜섬웨어 (Petya 2016 등) — 복구 키를 공격자 C2에 저장."""
name = "진짜 랜섬웨어 (금전 목적)"
def encrypt(self, mft: FakeMFT):
key = "AES-KEY-SAVED" # 복구 키를 어딘가에 보관
print(f" MFT {len(mft.files)}개 레코드 암호화 (키: {key})")
return key # -> 지불하면 복구 가능
class NotPetyaWiper:
"""NotPetya형: 복구 키를 만들지 않거나 즉시 폐기."""
name = "NotPetya형 와이퍼 (파괴 목적)"
def encrypt(self, mft: FakeMFT):
# Salsa20 스트림 암호로 MFT를 덮어쓰지만, 복호화 키를 어디에도 저장하지 않음
for name in mft.files:
mft.files[name] = "0xDEADBEEF(encrypted, key discarded)"
print(f" MFT {len(mft.files)}개 레코드 암호화 후 키 즉시 폐기")
return None # 복구 수단 없음
if __name__ == "__main__":
print("== 대조: 같은 '암호화'라도 목적에 따라 결과가 다르다 ==")
for cls in (RealRansomware, NotPetyaWiper):
mft = FakeMFT(["report.xlsx", "db_backup.bak", "contract.pdf", "photo.jpg"])
print(f"[{cls.name}]")
key = cls().encrypt(mft)
if key:
print(f" -> 복구 키 보관됨: {key} — 피해자는 돈을 내면(이론상) 복구 가능")
else:
print(" -> 복구 키 없음 = '지불'은 사기극, 데이터는 영구 소실 (와이퍼)")
print()
# '백신(perfc)' 우회 가능 여부: NotPetya 페이로드는 Windows 폴더에
# perfc/perfc.dat/perfc.dll 파일이 있으면 실행을 중단한다는 분석이 있었다.
def vaccine_check(files_in_windows_dir):
vaccine = any(f in ("perfc", "perfc.dat", "perfc.dll")
for f in files_in_windows_dir)
return vaccine
print("== '백신' 파일 존재 여부에 따른 페이로드 실행 ==")
print(f" 백신 없음 -> 실행? {not vaccine_check(['winlogon.exe'])}")
print(f" perfc.dat 존재 -> 실행? {not vaccine_check(['perfc.dat'])}")
print(" (읽기 전용 perfc 파일 생성이 임시 방어책으로 제안된 사건이었다)")
# 실행 결과
== 대조: 같은 '암호화'라도 목적에 따라 결과가 다르다 ==
[진짜 랜섬웨어 (금전 목적)]
MFT 4개 레코드 암호화 (키: AES-KEY-SAVED)
-> 복구 키 보관됨: AES-KEY-SAVED — 피해자는 돈을 내면(이론상) 복구 가능
[NotPetya형 와이퍼 (파괴 목적)]
MFT 4개 레코드 암호화 후 키 즉시 폐기
-> 복구 키 없음 = '지불'은 사기극, 데이터는 영구 소실 (와이퍼)
== '백신' 파일 존재 여부에 따른 페이로드 실행 ==
백신 없음 -> 실행? True
perfc.dat 존재 -> 실행? False
(읽기 전용 perfc 파일 생성이 임시 방어책으로 제안된 사건이었다)
6. 방어 방법/교훈 — 어떻게 막았고, 무엇을 배우나
- 공급망 신뢰의 재설계: M.E.Doc 사태는 "업데이트 서버가 장악되면 백신·패치도 못 믿는다"는 현실을 보여줬다. 방어는 ① 코드 서명 + 서명 키의 하드웨어 보관 ② 업데이트 서버의 이중 인증·네트워크 분리·무결성 모니터링 ③ 업데이트 수신 측에서도 서명을 재검증하는 다층 검증으로 가능하다. (이 교훈은 2020년 SolarWinds 공급망 공격에서 또 반복됐다.)
- SMBv1 차단과 패치: EternalBlue(MS17-010)는 2017년 3월에 이미 패치가 나와 있었다. 그러나 SMBv1을 쓰는 레거시 환경은 패치 적용이 어려워 5월 WannaCry, 6월 NotPetya에 연달아 뚫렸다. SMBv1 비활성화(또는 445 포트 차단) + 패치 관리가 가장 기본적인 방어다.
- 자격 증명 탈취 차단 (Mimikatz 대응): ① LSASS 보호(Protected Process Light) 활성화로 메모리 덤프 차단 ② Windows Defender Credential Guard ③ 관리자 계정의 비밀번호 순환 + LAPS(로컬 관리자 암호를 PC마다 다르게) ④ 최소 권한 원칙(도메인 관리자 계정을 워크스테이션에 로그인 금지) ⑤ lsass 덤프·비정상 WMI/PsExec 실행을 EDR로 탐지. "패치를 다 해도 계정 하나 털리면 끝"이라는 점이 NotPetya의 가장 값진 교훈이다.
- 빠른 격리 절차: 감염 초기(재부팅 전)에는 전원 차단 + 네트워크 분리가 유일한 방어였다. 평소에 "이상 징후 시 누가 어떤 절차로 스위치를 내리는가"를 훈련해 두어야 한다. 가짜 chkdsk/랜섬 화면이 보이면 이미 늦었다고 보는 게 맞다.
- 복구 가능성의 전제 — 백업: 와이퍼형 공격은 '지불'로 해결되지 않는다. 오프라인(에어갭) 백업 + 복구 훈련만이 유일한 복구 경로다. 머스크가 가나의 오프라인 도메인 컨트롤러 덕에 재건된 일화는 "단 하나의 오프라인 백업이 회사를 살린다"는 교훈으로 회자된다.
- 위협 모델의 확장 — '누가 나를 노리나': NotPetya는 우크라이나를 노린 공격이 전 세계 기업을 무차별로 덮친 사건이다. "나는 표적이 아니다"라는 안이함이 통하지 않는 이유 — 지정학적 긴장이 곧 모든 기업의 사이버 위협이 되는 시대를 열었다. 국가 후원 공격자는 금전이 아니라 혼란·파괴·전략적 이익을 노리므로, 보험·법률·위기 대응 계획도 '전쟁 행위' 시나리오를 포함해야 한다 (Mondelez vs. Zurich '전쟁 면책' 소송이 그 예).
- 교훈 요약:
- 위장의 힘: "랜섬웨어처럼 보이게" 만든 것만으로도 초기 대응(지불 검토, 복구 도구 선택)이 지연되고 혼선이 빚어졌다. 악성코드를 판별할 때 '겉모습'이 아니라 '복구 가능성의 유무'를 봐야 한다.
- 공급망은 최전선이다: 2017년 M.E.Doc, 2020년 SolarWinds로 이어지는 공급망 공격의 원형이 NotPetya다. 업데이트·배포 파이프라인은 이제 1급 보호 자산이다.
- 파괴형 공격의 시대: NotPetya는 사이버 공격이 '돈'이 아니라 '실물 경제 마비'를 노릴 수 있음을 증명했다. 방어의 목표도 "지불하지 않기"에서 "멈추지 않는 비즈니스"로 바뀌어야 한다.
출처
- Wikipedia — NotPetya — https://en.wikipedia.org/wiki/NotPetya
- WIRED — The Untold Story of NotPetya, the Most Devastating Cyberattack in History (Andy Greenberg, 2018-08-22) — https://www.wired.com/story/notpetya-cyberattack-ukraine-russia-code-crashed-the-world/
- Microsoft MSRC — CVE-2017-0144 (EternalBlue / MS17-010) — https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2017-0144
- CISA — Alert (TA17-181A) Petya Ransomware — https://www.cisa.gov/news-events/alerts/2017/07/01/petya-ransomware
- U.S. DOJ — Six Russian GRU Officers Charged (2020-10-19) — https://www.justice.gov/opa/pr/six-russian-gru-officers-charged-connection-worldwide-deployment-destructive-malware-and
- BleepingComputer — Vaccine, Not Killswitch, Found for Petya (NotPetya) Ransomware Outbreak — https://www.bleepingcomputer.com/news/security/vaccine-not-killswitch-found-for-petya-notpetya-ransomware-outbreak/
함께 읽기: 💀 WannaCry (2017) — 같은 NSA 유출 무기 EternalBlue를 5월에 사용한 자가전파형 랜섬웨어 · ⚛️ Stuxnet (2010) — 국가가 만든 파괴형 사이버 무기의 원형 · 🪱 Conficker (2008) — SMB 취약점 웜의 고전