Defender가 있는데도 5일간 채굴당했다 — 개발기라는 무른 표적

안티바이러스가 켜져 있다는 사실은 안전의 증거인가, 아니면 아무도 그 경보를 읽지 않는다는 것을 가리는 알리바이인가? 한 일본 엔지니어의 개발용 PC는 Microsoft Defender가 정상 작동하는 상태에서 닷새 동안 암호화폐 채굴에 쓰였다. 방어가 무너진 것이 아니라 방어가 보낸 신호를 아무도 읽지 않았다는 이 사건은, 개발기(dev machine)라는 표적이 왜 그렇게 무른지를 되묻는다.

도입

2026년 8월, 일본 엔지니어 커뮤니티 Qiita에 인시던트 보고 하나가 올라왔다. 제목은 「Defender가 유효한데도, 개발기에서 5일간 채굴당하고 있었다」. 보고자는 자택의 Windows 11 개발기 — DTM(음악 제작)과 프로그래밍을 겸하는 20코어 머신 — 를 쓰며, Discord에서 Claude Code에 지시를 내리는 방식으로 AI 보조 개발을 한다고 밝혔다. 발단은 사소했다. Discord 음성 읽기 봇이 불안정해졌고, 보고자는 처음에 “AI가 코드를 망가뜨렸다”고 생각했다. 그러나 진짜 원인은 코드가 아니었다.

보고자에 따르면, 8월 10일 18시 21분(감염 파일의 생성 타임스탬프 기준)에 심어진 채굴기 XMRig 6.21.3이, 발각된 8월 15일 밤까지 닷새 동안 20개 논리 코어 중 15개를 100%로 태우고 있었다. 그동안 Defender는 꺼져 있지 않았다. 오히려 감염 당일 저녁, 권한 상승에 쓰이는 커널 드라이버 하나를 탐지해 격리까지 했다. 문제는 그 알림을 아무도 보지 않았다는 것이다. 보고자는 이 사건의 성격을 한 문장으로 요약한다. “Defender는 제 일을 하고 있었다. 알아채지 못한 것은 사람 쪽이었다.” 이 글은 보고자가 실제로 관측한 사실과, 엔드포인트 보안이 개발기 앞에서 왜 무력해지는가에 대한 나의 구조적 분석을 나눠 정리한다. 사실에는 출처를 붙이고, 일반화된 진단은 나의 논증으로 표시한다.

무엇이 일어났나 — Defender는 일했고, 아무도 알림을 보지 않았다

먼저 시간축을 사실대로 옮긴다. 보고자의 재구성에 따르면, 침입은 8월 10일 18시 21분에 시작됐다. 그리고 불과 한 시간 뒤인 19시 46분, Defender는 C:\Windows\SystemTemp 아래의 임시 파일에서 WinRing0x64.sys라는 14.5KB 크기의 커널 드라이버를 탐지하고 격리했다. 이 드라이버는 하드웨어 레지스터에 직접 접근해 권한을 끌어올리는, 채굴기가 흔히 끼고 다니는 부품이다. 즉 방어의 첫 단추는 제대로 채워졌다. 탐지도 됐고 차단도 됐다. 다만 그 결과가 데스크톱 한구석의 토스트 알림으로 한 번 떴다가 사라졌을 뿐, 아무도 그것을 읽지 않았다.

같은 시점에 공격자는 방어의 뒷문을 열어 두었다. 보고자에 따르면 악성코드는 Defender의 예외(제외) 목록에 두 개의 경로를 추가했다 — C:\Users\[user]\AppData\Local\Microsoft\Edge\SystemC:\Users\[user]\AppData\Roaming\DriversUpdate다. 둘 다 정상 구성요소의 이름을 흉내 낸 위치다. 예외 목록을 고치려면 관리자 권한이 필요하므로, 침입은 배치 시점에 이미 관리자 권한을 확보한 상태였음을 뜻한다. 방어 엔진은 살아 있었지만, 채굴기가 상주하는 폴더만은 그 엔진의 시야에서 도려내진 셈이다.

닷새 동안 몸이 보낸 신호는 있었다. 기계는 평소보다 뜨거웠고, 키보드 입력이 이따금 먹통이 됐다. 그러나 각각에는 그럴듯한 일상적 해석이 붙어 있었다 — 여름이라 덥고, 부하가 걸려서 그렇고, 프로그램이 무거워서 그렇다. 결정적 신호는 8월 15일 밤에 왔다. Discord 봇의 하트비트가 28초간 막혔고, DNS 해석이 반복 실패하고 웹소켓이 지연됐다. 보고자는 처음엔 이것을 최근의 코드 변경 탓으로 돌렸다. CPU가 고갈된 전형적 증상이었지만, 개발기에서 CPU가 치솟는 것은 너무나 흔한 일이라 아무도 그것을 침해의 신호로 읽지 않았다.

전환점은 이름이 아니라 측정이었다. 보고자에 따르면, 조사 과정에서 Claude가 프로세스 텔레메트리를 스냅숏으로 비교했다. 5초짜리 관측 창에서 updater.exe라는 프로세스가 75.55 CPU-초를 소비했다. 5초 동안 75초어치를 태웠다는 것은 약 15개 코어를 동시에 완전히 점유했다는 뜻이다. 창 제목에는 XMRig 6.21.3이 그대로 노출돼 있었고, 네트워크를 들여다보니 15.235.234.199:3333으로 향하는 TCP 연결이 살아 있었다. 3333은 채굴 풀이 흔히 쓰는 포트다. 탐지 시점까지 이 프로세스가 누적한 가동 시간은 9시간 54분이었다. 여기서 핵심 원리가 드러난다. 이름은 얼마든지 위장할 수 있어도, 실제로 소비한 CPU 초는 위장할 수 없다. updater.exe라는 정상적인 이름과, 15개 코어를 지속적으로 태우는 거동 사이의 모순이 정체를 폭로했다.

대응은 순서가 중요했다. 보고자에 따르면 조치는 다음 순으로 진행됐다. 첫째, 정상 Windows 이름을 흉내 낸 다섯 개의 예약 작업(\Microsoft\Windows\Shell\FamilySafetyRefreshingTask, \Microsoft\Windows\Multimedia\SystemRecordService 등, 모두 최고 권한)을 삭제했다. 둘째, 프로세스를 이름이 아니라 실행 파일 경로로 매칭해 종료했다 — 정상 RuntimeBroker.exe를 오인 종료하지 않기 위해서였다. 셋째, 사용자가 직접 등록한 예외는 보존하고 악성코드가 추가한 두 예외만 제거했다. 넷째, XMRig 부품과 페이로드 DLL, 그리고 SQLite.Interop.dll(약 2MB)이 들어 있던 세 개의 디렉터리를 삭제했다. 다섯째, 전체 스캔으로 검증했다. 프로세스를 종료하자 CPU 점유율은 100%에서 3%로 즉시 떨어졌다.

여기서 보고자가 무겁게 다룬 대목이 신용정보의 유출 가능성이다. 채굴기 곁에 SQLite.Interop.dll이 있었다는 사실은, 쿠키·비밀번호 같은 브라우저 데이터베이스에 손을 댔을 가능성을 시사한다. 그래서 대응은 비밀번호 변경보다 먼저 모든 기기에서의 강제 로그아웃(세션 하이재킹 차단)과 2단계 인증 도입, 토큰 재발급을 우선했다. 데이터가 실제로 빠져나갔다는 증거는 없었지만, 없다는 증거도 없었기에 최악을 가정하고 움직인 것이다.

왜 개발기는 무른 표적인가 — 관리자 권한·예외·성능 우선

이 사건의 표면적 교훈은 “알림을 놓쳤다”이지만, 더 깊은 질문은 따로 있다. Defender 같은 현대 엔드포인트 방어가 다섯 시간도 아니고 닷새 동안 채굴기를 방치한 구조는 무엇인가. 여기서부터는 나의 분석이다.

첫째, 이름 기반 신뢰의 붕괴다. 이 악성코드는 자신을 updater.exe라 불렀고, 예약 작업은 FamilySafetyRefreshingTask처럼 진짜 Windows 작업의 이름을 뒤집어썼으며, 상주 폴더는 Edge와 Microsoft의 이름을 흉내 냈다. 평판 기반·시그니처 기반 판정은 “이 이름이 알려진 악성 목록에 있는가”를 묻는데, 처음 보는 이름과 정상처럼 보이는 이름 앞에서는 둘 다 조용하다. 보고자가 정리한 교훈도 같다 — 탐지는 이름이 아니라 실제 실행 파일 경로를 System32Program Files의 정상 위치와 대조해야 성립한다. 정상적인 이름을 단 채 비정상적인 위치에서 도는 프로세스가 바로 위장의 본질이다.

둘째, 개발기에서는 유일한 거동 신호가 잡음에 묻힌다. 채굴기를 행위로 탐지할 수 있는 가장 강력한 단서는 “CPU가 오래 100%에 붙어 있다”이다. 그런데 개발기·DTM 머신에서 CPU 100%는 이상이 아니라 일상이다. 컴파일, 빌드, 오디오 렌더링, 학습 잡, 로컬 추론 — 코어를 오래 태우는 작업이 상시 돌아간다. 안티바이러스 벤더가 “지속적 고부하”를 위협으로 올리지 않는 이유가 여기 있다. 그렇게 하면 오탐이 폭발하기 때문이다. 결국 채굴기를 드러낼 유일한 행위 신호가, 개발기에서는 정상 워크로드와 구별되지 않는다. 무른 표적의 첫 번째 정의는 이것이다 — 방어가 의존하는 신호를, 그 환경의 정상 상태가 이미 소진해 버린 곳.

셋째, 예외와 관리자 권한의 문화다. 악성코드가 Defender 예외를 추가하려면 관리자 권한이 필요했고, 그것을 이미 갖고 있었다. 개발자에게 관리자 권한과 안티바이러스 예외는 예외적 사건이 아니라 작업 조건이다. node_modules를 실시간 검사하면 빌드가 기어가므로 프로젝트 폴더를 예외에 넣고, 도구 설치마다 권한 프롬프트를 반사적으로 통과시킨다. 보고자 자신도 “권한 프롬프트를 반사로 통과시키는 자각이 있는 사람”이라는 표현으로 이 습관을 인정했다. 그러니 예외 목록에 낯선 항목이 두 개 더 늘어도, 그것은 눈에 띄는 이물이 아니라 배경 소음이 된다.

넷째, 어질러진 환경이 은신처가 된다. 보고자의 관찰은 날카롭다 — “AppData 아래에 실행 파일이 늘어나는 것이 ‘보통’이 된다”, 그리고 “환경이 어질러져 있을수록 이물은 눈에 띄지 않는다”. 여기에 나의 일반화를 덧붙이면, AI 보조 개발과 현대 패키지 생태계는 이 어질러짐을 가속한다. npm, pip, cargo, 언어 서버, 에디터 확장, 각종 CLI가 사용자 프로필 아래에 수많은 바이너리를 흩뿌린다. “이건 무슨 프로세스인지 설명 못 하겠다”가 쌓인 상태 자체가 위험이라는 보고자의 결론은, 정확히 이 축적을 겨눈다.

이 지점에서 최근의 공급망 공격 흐름과 연결하고 싶다 — 이것은 명시적으로 나의 분석이다. 보고자 스스로는 감염 경로를 특정하지 못했다(“감염 경로는 특정할 수 없었다”고 명시한다). 로그가 8월 12일 이전에 순환 삭제됐고 작업 스케줄러 등록 이벤트도 남지 않았기 때문이다. 보고자가 정황상 의심한 것은 DTM 생태계였다 — 출처 검증이 느슨하고 관리자 권한을 요구하는 무료 음원·플러그인·샘플팩이 인스톨러 형태로 배포된다는 점을 짚었다. 여기서 내가 일반화하려는 논지는, 그 의심 경로를 npm/pip 같은 개발 의존성으로 바꿔도 이야기가 그대로 성립한다는 것이다. 최근 몇 년의 npm 공급망 침해들 — 인기 패키지가 탈취돼 설치 스크립트에 채굴기나 토큰 탈취기를 끼워 넣은 사례들 — 은 개발기가 서드파티 바이너리의 광대한 표면을 상시 신뢰한다는 같은 전제를 노린다. 감염 경로가 오디오 플러그인이든 오염된 npm 패키지든, 무른 표적의 조건은 동일하다. 관리자 권한, 관대한 예외, 성능 우선 문화, 그리고 정체를 설명할 수 없는 바이너리로 가득한 홈 디렉터리.

마지막으로 침묵의 문제다. Defender는 커널 드라이버를 잡았지만, 그 성공은 아무도 읽지 않는 알림으로 흩어졌다. 발화하되 아무도 듣지 않는 경보는 통제가 아니다. 상시 바쁜 개발기에서 토스트 알림은 방해로 취급돼 가장 먼저 무시되는 종류의 신호다. 방어의 성공이 조용히 사라지도록 설계된 곳에서는, 방어의 존재 자체가 안전의 환상을 제공할 뿐이다.

탐지의 재설계 — 이름이 아니라 거동으로, 침묵이 아니라 알림으로

그렇다면 개발기를 덜 무른 표적으로 만드는 실무적 방향은 무엇인가. 사건이 가리키는 축은 세 가지다.

첫째, 조용한 성공을 시끄럽게 만들라. 보고자의 첫 번째 권고는 Defender의 탐지를 사람이 실제로 읽는 곳으로 흘려보내는 것이다 — WMI 이벤트 구독으로 위협 탐지를 채팅이나 이메일로 라우팅하는 자동화다. 이 사건의 5일은 탐지의 실패가 아니라 통지의 실패였다. 무인에 가깝게 돌아가는 기계일수록, 경보는 그 기계 앞이 아니라 사람이 반드시 보는 채널로 나가야 한다. 실패는 조용히 삼켜지지 말고 드러나야 한다.

둘째, 정체를 이름이 아니라 경로와 거동으로 확인하라. 보고자는 관리자 PowerShell로 돌릴 여섯 가지 점검을 남겼다. Defender 예외 목록, 위협 탐지 이력, 사용자 컨텍스트의 예약 작업(경로가 AppData·Temp·Public을 포함하는 것으로 필터), 비표준 경로에서 도는 프로세스, WMI 이벤트 소비자(비어 있어야 정상 — 파일리스 지속성의 흔적), 그리고 키보드 필터 드라이버(kbdclass만 있어야 정상). 공통 원리는 하나다. “이름이 정상인가”가 아니라 “정상 이름이 정상 위치에서, 정상 자원으로 도는가”를 묻는 것이다. 측정이 위장을 이긴다.

셋째, 자격증명 탈취를 기본값으로 가정하라. 채굴이 눈에 보이는 증상이라면, 그 곁의 SQLite.Interop.dll 같은 부품은 눈에 보이지 않는 손실 — 쿠키·토큰·비밀번호 — 을 시사한다. 보고자가 비밀번호 변경보다 세션 무효화와 토큰 재발급을 먼저 한 판단은 옳다. 살아 있는 세션 토큰은 새 비밀번호를 우회하기 때문이다. 채굴기를 발견하면 “CPU를 훔쳤다”에서 멈추지 말고 “무엇을 더 읽어 갔을 수 있는가”까지 대응 범위를 넓혀야 한다.

한 가지 더, 이 사건에서 내가 인상 깊게 본 구조는 조사의 역할 분담이다. 보고자에 따르면 실제 손을 움직인 것은 Claude였고, 사람은 관리자 자격증명과 최종 실행만 맡았다. 흥미로운 것은, 증상을 “여름이라 덥고 코드가 문제겠지”로 설명해 치우던 것은 사람 쪽이었고, 그 일상 서사에 아무 지분이 없던 도구가 프로세스를 냉정하게 측정해 모순을 집어냈다는 점이다. 이것은 나의 해석이지만, 보안에서 가장 값진 것은 때로 “당연히 괜찮을 것”이라는 편향의 부재일지 모른다. 보고자의 마무리도 이 결을 향한다 — “네 탓이겠지”라는 말을 곧이곧대로 삼키지 않고 현물을 직접 확인한 것이 발견의 입구였다고.

전망을 세 가지 시나리오로 정리한다. 낙관적으로는, 개발기에 대한 이런 인시던트 공유가 늘면서 “홈 디렉터리 바이너리 목록을 설명할 수 있는가”가 개발자 위생의 기본 항목이 될 것이다. 비관적으로는, 성능 우선 문화와 예외 남발이 그대로인 채 공급망 침해만 정교해져, 채굴처럼 시끄러운 페이로드 대신 조용히 토큰만 빼 가는 조용한 침입이 표준이 될 것이다. 현실적으로는 그 사이 어딘가다 — 대부분의 개발기는 여전히 관대한 예외와 반사적 관리자 승인을 유지하되, 이번처럼 우연한 성능 이상이 조사로 이어질 때에만 뒤늦게 정체가 드러날 것이다.

결론

리드의 질문으로 돌아가자. Defender가 켜져 있다는 사실은 안전의 증거인가, 아무도 경보를 읽지 않는다는 것을 가리는 알리바이인가. 이 사건은 후자에 가깝다. 방어 엔진은 감염 한 시간 만에 커널 드라이버를 잡았지만, 그 성공은 읽히지 않은 알림으로 사라졌고, 정작 채굴기는 정상적인 이름을 걸친 채 개발기의 일상적 고부하 속에 닷새를 숨었다. 실패한 것은 엔진이 아니라 읽기였다.

개발기가 무른 표적인 이유는 게으름이 아니라 문화의 구조다. 성능을 위해 예외를 넓히고, 도구를 위해 관리자 권한을 상시 쥐고, 생산성을 위해 홈 디렉터리를 바이너리로 채우는 습관은 그 자체로 합리적이다. 다만 그 합리성이 방어가 의지하는 신호들 — 낯선 이름, 새 실행 파일, 치솟는 CPU — 을 하나씩 정상 상태로 흡수해 버린다. 무른 표적은 방어가 없는 곳이 아니라, 방어의 경보가 배경 소음과 구별되지 않게 된 곳이다.

그러니 남길 질문은 특정 도구의 성능이 아니라 하나의 점검 습관이다. 지금 당신의 개발기에서 돌고 있는 프로세스 가운데, 이름이 아니라 경로와 자원으로 정체를 설명할 수 없는 것이 몇 개인가. Defender가 마지막으로 무언가를 격리했을 때, 그 알림은 어디로 갔는가 — 당신이 반드시 보는 곳인가, 아니면 아무도 없는 데스크톱 구석인가. 이번엔 CPU가 시끄럽게 울어 발각됐다. 다음 침입이 조용히 토큰만 읽어 갈 때, 당신을 깨울 신호는 무엇으로 남아 있는가.


출처: