The DPRK has a long history of luring developers with fake job interviews and take-home coding tests. The ultimate goal is to convince the victim to load a remote-access tool (RAT) or infostealer onto their system through a trojanised project.
At Ossprey, we spend our time developing systems to detect open-source malware, including NPM and PyPi; the open-source ecosystems that millions of users and developers rely on every day.
This time, we don't want to talk about packages. Instead we'll explore how a pattern of second-stage loaders allowed us to discover the true extent of an ongoing DPRK operation, a continuation of what Ossprey previously covered.
Executive Summary
Ossprey's live npm monitoring flagged two malicious packages in late April. Both fetched second-stage payloads, that were immediately attributed to DPRK's Contagious Interview campaign.
We found 298 URLs serving active Contagious Interview malware. This post covers what was in them, how the infrastructure hangs together, a second tool the campaign is running alongside BeaverTail, and a live npm dropper we caught this afternoon.
Key Findings
Ossprey identified 298 active Contagious Interview payloads.
We mapped 26 distinct C2 servers across six infrastructure variants.
The campaign is running two separate tools: the well-documented BeaverTail infostealer on port 1224, and a distinct self-contained Node.js HTTP RAT.
BeaverTail targets 54 browser extensions by ID: 40 crypto wallets and 10 password managers, alongside full Chromium credential database extraction on Windows and macOS
A PE stage-2 payload endpoint (
216.126.225[.]104:5000/download-app) was still live and serving as of publication.
The Investigation
It all started with ozonex-sdk@1.0.1 [npm], which Ossprey detected in late April. Inside it was a URL pointing to a second-stage payload: https://jsonkeeper[.]com/b/UPCMV. This was our live threat-hunting platform's first sighting of a Contagious Interview package; further inspection led us to a BeaverTail JavaScript infostealer and subsequently to a Python RAT, known as InvisibleFerret.
The very next day we detected another package, metrica-chain@2.4.5 [npm]; just like before, this one also fetched a second-stage payload from https://jsonkeeper[.]com/b/BADC6.
Then another, and another.
We noticed that all of these payloads were being served by jsonkeeper with a short, non-sequential string identifying each entry. At this point we had collected enough samples that we had very strong rules for differentiating between DPRK samples and unrelated malware and code. So we started investigating.
After 10 days, we had checked ~60 million possible entries, yielding ~120k results. We ran these entries through our detection pipeline and produced a list of 298 URLs serving Contagious-Interview flavoured malware.
You can learn more about the Contagious-Interview campaign from these excellent articles.
How we found them in the investigation
Investigating the namespace was the easy part. The hard part is that most of what comes back is legitimate: JSONkeeper is a real service, and the investigation stored on the order of 120,000 JSON documents, the overwhelming majority of them harmless. We needed one signal that was cheap enough to run across the whole database and precise enough not to bury us in false positives.
That signal was the obfuscation. Every one of these samples is run through obfuscator[.]io, whose output has an unmistakable shape: a large array of encoded strings, then a self-invoking function that rotates that array into place with a while(!![]) loop before any real code runs. Instead of trying to recognise the malware we fingerprinted the obfuscator.
We applied it in two passes: a cheap SQL LIKE '%while(!![]%' to drop the ~120,000 rows down to a short candidate list, then a full JSON parse of each survivor, running the test against every string value inside it. We found payloads stashed under cookie, content, model, message and a dozen other key names. In total it matched 298 payloads across our data.
The signature is deliberately broad. obfuscator[.]io is off-the-shelf, so on its own a hit only means "someone packed this", not "DPRK", and narrowing from obfuscated to this actor is the job of the clustering and infrastructure work. But inside JSONkeeper's namespace the two lined up almost exactly: nearly every obfuscated payload we pulled clustered into this one campaign.
Through static analysis we were able to cluster the malware into the following groups.
Cluster | Samples | Role |
|---|---|---|
| 110 | Full BeaverTail stealer (socket.io / operator-config variants) |
| 36 | HTTP RAT dropper (port 1244), see below |
~130 smaller clusters | remainder | loader / stealer variants |
Thanks to existing reporting and the open-source malware community, we were able to attribute all of these samples to DPRK operations with extremely high confidence.
Every payload is obfuscated with obfuscator[.]io, an extremely popular off-the-shelf JavaScript obfuscator that is clearly favoured by this actor. The re-use of generator parameters (array size, rotation offset) also proved a great help in allowing us to cluster these samples.
Dealing with heavily obfuscated JavaScript is a daily occurrence for us, so we were able to run all 298 of these samples through our de-obfuscation pipeline.
Disrupting the campaign
Ossprey Security was able to collaborate with the operator of JSONkeeper to ensure that many of the verified URLs hosting these second stage payloads were deleted, effectively disabling the newest wave. Collaboration is on-going to ensure that detected samples are removed as fast as possible.
The shape of the network
We also mapped the campaign's infrastructure. Much of it was already public, which strengthened our attribution.
NVISO Labs had documented this exact campaign, including its pivot to JSON-storage dead-drops, back in November 2025, right down to a published list of C2 servers. Five of ours appear on it: 23.227.202[.]244, 146.70.253[.]107, 45.61.150[.]31, 88.218.0[.]78 and 5.253.43[.]122. Another dozen sit in the same /24 or /16 ranges as hosts NVISO named. And the two C2 ports (1224 and 1244) are associated by ESET with BeaverTail activity.
Across the 298 samples we pulled out 26 distinct command-and-control servers**.** These fall into a handful of architectural variants:
Variant | Servers | Description |
|---|---|---|
HTTP loader, port 1224 | 12 | Classic |
HTTP RAT, port 1244 | 6 | Dropper with |
socket.io + PE download, port 5000 | 2 |
|
socket.io + | 1 |
|
socket.io | 2 | short-event C2 |
WebSocket, embedded operator config | 3 | C2 octets split across JSON fields |
What the stealer takes
The bulk of the set is standard BeaverTail. It checks the credential databases of every Chromium-based browser it can find (Chrome, Edge, Brave, Opera, Yandex), decrypting the saved passwords with DPAPI on Windows and reaching for the login Keychain on macOS.
Then it goes after browser extensions, 54 of them, by ID. The real list is 40 crypto wallets (MetaMask, Phantom, OKX, Keplr and others), but also 10 password managers, including Bitwarden, 1Password, LastPass, NordPass, Norton, RoboForm and Proton Pass.
For persistence it drops a LaunchAgent on macOS or a Run/Startup entry on Windows, and several samples reach a /download-app endpoint that hands back an 8.4 MB Windows executable, the next stage. That endpoint, on 216.126.225[.]104:5000, was still live and still serving at the time of publication.
A second tool: the homebrew RAT
In a second, smaller cluster (the 36-sample array-size-109 group from the table above) we found a different tool entirely: a self-contained Node.js HTTP RAT with its own protocol:
The implant also won't run unless the server answers with the literal token ZT3 first, which cheaply screens out the sandboxes and scanners that poke a beacon without knowing the handshake.
The check-in POST carries process.argv[1], the path of the script that launched the malware, which tells the operator, and us, exactly which package or "interview project" the victim ran.
Methodology for attribution
This malware is not BeaverTail, it has a completely different protocol and structure. Our attribution of this to the same actor is through strong similarities in distribution, hosting and trade-craft.
The RAT is staged in the same JSONkeeper namespace as the confirmed BeaverTail samples, in the same window, built with the same obfuscator[.]io configuration, on the sibling port (1244 to BeaverTail's 1224), and its servers sit in the same provider /24s as NVISO's published Contagious-Interview C2: our 38.92.47.175 and .157 next door to NVISO's 38.92.47.85/.91/.151, and our 45.43.11.199 a single address from their .201.
For this to be someone else, they would have to be independently abusing the same storage service, with the same obfuscator config, on the same ports, renting C2s in the same /24s as DPRK, at the same time.
One caveat: those /24s belong to a budget VPS host (Tier.net), so on its own that proximity is suggestive rather than conclusive. It's the convergence of channel, toolchain, ports and hosting all pointing the same way that gets us to high confidence, not any single thread.
Caught in the act: a live npm dropper
While the investigation was still running, one of these packages was detected by Ossprey's real-time ecosystem scanner. On 30 June a package called buffer-util-internal was published by a throwaway account kendallmix1223. It masquerades as feross/buffer, and nearly all of it (lines 69 to 2128) is the real library copied byte for byte. The only thing added is a single block near the top:
That is the same dead-drop pattern as every sample in the set: fetch a JSONkeeper blob, eval its content field. What made this one useful is that the operator was still working on it. The package shipped twice in six minutes, 1.0.13 at 21:35 and 1.0.14 at 21:41 UTC, and the only change between the two was a commented-out toggle that swapped the active blob from PT0ON to a second one, CWOV9. Both were live and attacker-controlled, being rotated in real time.
We already had both blobs. PT0ON and CWOV9 were sitting in our enumerated set; the live package pushed us to take a closer look:
PT0ONis the port-1244 Node RAT again, the same/s/<id>beacon andZT3handshake from the previous section, pointing at a C2 we had not seen before:45.59.163.198.CWOV9is a one-shot downloader. It runsnpm install axios socket.io-clientin a temp directory, pulls a payload from216.126.225.83/api/service/<token>, writes it to0001.datand runs it withnode. That C2 sits in the same216.126.225.xblock as216.126.225.104, the live PE-serving host from earlier. The identical install command,0001.datdrop and/api/service/<token>URL also turned up in a separate DPRK npm chain we analysed, so this is a reused module rather than a one-off.
What this means for you
Ossprey Security's platform isn't just for enterprise and business users. Engineers, crypto users and developers are constantly being targeted for their access and cryptocurrencies.
You can use Ossprey now, for free, to scan npm and PyPI packages and GitHub repos.
Alongside only opening code and projects from sources you trust, this can help protect you from threats that we are currently seeing in the wild.
IOCs
Live / notable C2
HTTP loaders (port 1224)
HTTP RAT (port 1244), same operator, distinct tool
WebSocket operator-config cluster
Dead-drops / staging
Live npm dropper (buffer-util-internal, 30 Jun 2026)
Other
Typosquat / staging domains




