Inside the DPRK-Linked Backdoor Loitering in the VS Code Marketplace
The VS Code Marketplace is supposed to be safe. Microsoft reviews extensions. Millions of developers install them without a second thought. But one extension—jupyter-powerdev—passed every check while hiding a fully functional backdoor connected to DPRK intelligence operations.
Executive Summary
It takes exactly one click to compromise an entire enterprise development pipeline. On June 8, 2026, Argus flagged ByteBinTools.jupyter-powerdev-2026.6.8.vsix from the VS Code marketplace. Yeeth Security researchers have unmasked the extension, a seemingly innocent Jupyter Notebook productivity tool, as a highly sophisticated, multi-stage backdoor meticulously engineered to bypass modern endpoint defenses. This malware is a masterclass in architectural evasion that heavily shadows elite North Korean (DPRK) state-sponsored tradecraft. The digital supply chain is no longer just a risk factor—it is the new frontline.
The extension contains the following components that overlap with techniques used by the Lazarus Group:
- A JavaScript layer that handles all Command-and-Control (C2) communication via Microsoft Graph API and SharePoint
- Two platform-specific agents — a compiled
.exeon Windows and a Python script for Linux and macOS — that perform code execution - A SharePoint site functioning as a command queue, victim registry, and exfiltration channel, accessed through Azure-hosted proxy brokers disguised as financial APIs
- Arbitrary file read, write, and exfiltration capabilities as well as arbitrary code execution capabilities
The compiled binary component in the extension scores a 3/71 on VirusTotal. All IPC and C2 traffic is AES-256-CBC encrypted. The PE compilation timestamp is forged to 2040.
Two architectural patterns within the implant overlap with documented Lazarus Group tradecraft: the cross-platform JavaScript+Python developer-tooling split from Contagious Interview, and the Graph API token broker to SharePoint C2 abstraction from DreamLoader. What the overlaps mean is in DPRK TTP Overlaps.
Details
The campaign spans three extensions published under three different publisher names:
| VSIX | Publisher | Display Name | Size | Windows Agent | Linux/Mac Agent |
|---|---|---|---|---|---|
jupyter-powerdev-2026.6.8.vsix |
ByteBinTools | Jupyter Notebook PowerDev Tools | 7.4 MB | monitor-agent.exe |
handler-agent.py |
jupyter-powertools-3.21.0.vsix |
ToolCraft | Jupyter Notebook PowerTools | 7.4 MB | job-agent.exe |
monitor-agent.py |
markdown-mode-devtools-2.1.0.vsix |
OLDev | Markdown Mode Dev Tools | 18 KB | processor-service.exe (not bundled) |
processor-agent.py |
While no direct IoC matches exist in public repositories, the architecture mirrors documented North Korean tradecraft. Specifically, it borrows the cross-platform JavaScript/Python split seen in Contagious Interview and the Graph API-to-SharePoint abstraction pattern from DreamLoader. Deploying this hybrid blueprint inside a legitimate marketplace extension, rather than a malicious repo, marks a distinct evolution in their persistence strategy.
Furthermore, two constants shared across all three extensions in this campaign confirm a single operator:
- SharePoint site GUID:
e6bf72be-e8e2-4785-8814-5f874341d11f— the same C2 backend in all three, hardcoded after string decryption - C2 channel AES key:
3e1c9a72f5b84d06e2a97c5310fb8e4d— the same key encrypts all traffic toAppComms_CommandsandAppComms_Responsesacross all three extensions
Additionally, three OPSEC patterns are visible across the releases:
-
Binary names rotate per extension.
monitor-agent.exe→job-agent.exe→processor-service.exeon Windows;handler-agent.py→monitor-agent.py→processor-agent.pyon Linux/macOS. Detection rules anchored on the ByteBinTools binary name miss both later variants. The YARA rule for this family anchors on code shape, not filenames. -
String encryption keys are unique per extension. Each VSIX uses a different 32-byte AES-256-CBC key for its string obfuscation layer. Cross-extension key-reuse detection fails; each must be independently analyzed.
-
OLDev uses a staged, ultra-lightweight delivery model. Unlike the 7.4 MB packages, OLDev ships at just 18 KB, completely omitting the Windows binary (
processor-service.exe). It establishes an initial foothold via the Python agent, then uses itsupload_largecommand to pull the Windows executable from an Azure Blob URL later. Once dropped intodist/, the Roslyn REPL activates silently on the next C2 poll. OLDev also features unique auth brokers (ppe-margin-auth[.]azurewebsites[.]netandppe-finance-api[.]azurewebsites[.]net) and unique API keys, whereas ToolCraft simply reused theByteBinToolsinfrastructure.
Embedded Implants
The VSIX package contains extension.js, the two worker agents under dist/, and standard packaging metadata. The JavaScript source has all strings AES-256-CBC encrypted at rest and decrypted at runtime using a key assembled from four byte arrays split across the source. The extension registers three VSCode commands under the display name “Jupyter Notebook PowerDev Tools”: a .start command, a .stop command, and a .showLog command. On activation, it silently starts the C2 polling loop. There is no user-facing output, no visible indicator in the VS Code status bar, and nothing in the extension’s behavior that a user installing it for Jupyter productivity work would find unusual. The extension activates immediately on install with no additional user action required.
These two embedded implants are not independent features. They are the same feature compiled for two different operating systems.
When the C2 delivers a command, the JavaScript layer routes it to the appropriate handler. Commands of type cexec and script go to a local worker process. Which process runs depends on the OS:
function Ie() {
let s = N.dirname(__dirname);
if (ce) return {
cmd: N.join(s, "dist", "monitor-agent.exe"),
args: []
};
let n = N.join(s, "dist", "handler-agent.py");
return {
cmd: "python3",
args: [n]
}
}
On Windows, monitor-agent.exe is spawned. On Linux and macOS, python3 launches handler-agent.py. Both serve the same role: receive a code payload from the JS extension via stdin, execute it, and return the result via stdout. The JavaScript extension spawns the worker, waits for the ---READY--- sentinel on stdout, then writes the code payload to stdin and reads the result on process exit. Additionally, the inclusion of Python strings (handler-agent.py, python3) inside the Windows extension is merely a byproduct of shared platform-detection logic. The runtime divergence is pragmatic: a compiled, Costura.Fody-bundled .NET binary grants the actors superior AV evasion on Windows targets, while a native Python script sidesteps binary compatibility headaches across fragmented Linux and macOS environments.
A third command type, host_action, bypasses the worker entirely and runs inside the JavaScript process itself — filesystem operations like pwd, ls, cd, cat, file upload from base64, and file read-and-return. These work on every platform without needing the worker agents.
The Executable - A Runtime Code Engine
The samples with the name monitor-agent.exe and the SHA256 aa4f26b16a28d28bb18d48b5be6a94438486df610e7b8e3eb0908b91a17ea329 scores 3/71 on VirusTotal. VirusTotal’s own analysis provides a clear explanation for why the detection rate is low and why the user’s description — “reflectively loads .NET functionality” — is accurate.
DetectItEasy identifies the binary as a PE32 .NET assembly using Costura.Fody - a build-time tool that compresses all referenced assemblies into the PE’s managed resource section as embedded byte arrays. The tool then hooks AppDomain.AssemblyResolve at startup to decompress and load them into memory on demand. No dependent DLLs are written to disk. The PE’s import table shows only mscoree.dll — the minimal .NET runtime bootstrap — because every other dependency is bundled inside the binary and resolved in-memory.
The critical dependency bundled this way is Microsoft.CodeAnalysis.CSharp.Scripting v4.12.0.0 — the Roslyn scripting API. Roslyn scripting allows a running .NET process to accept a string of C# source code, compile it in-memory using the full C# compiler, and execute the resulting IL within the same process. This is the actual execution mechanism: the JavaScript extension writes a C# code string to the exe’s stdin, the exe’s Roslyn runtime compiles it, runs it, and writes the output back to stdout.
The upshot is that monitor-agent.exe functions as an in-process, on-demand C# REPL for arbitrary attacker payloads. No child process is spawned per command. No intermediate DLL files touch disk. The attacker can deliver any .NET capability — credential harvesting, lateral movement, additional stage drops — as a string of C# code through the SharePoint command queue, and it executes without leaving a file-based artifact beyond the initial exe and VSIX.
VT also tags the binary with detect-debug-environment, indicating anti-analysis behavior. The PE compilation timestamp is set to 2040-12-28 07:41:53 UTC — a far-future date that defeats timestamp-based triage heuristics and indicates deliberate anti-forensics during the build process.
The SharePoint C2
The C2 infrastructure uses two layers of abstraction to avoid fingerprinting.
Layer One — Auth Brokers — Two Azure-hosted endpoints masquerade as financial portfolio APIs:
investment-gateway[.]azurewebsites[.]net/api/v2/portfolio/positionsequity-core[.]azurewebsites[.]net/api/v1/accounts/summary
The extension POSTs its client GUID to these endpoints with API keys hardcoded in the source (R5mQ8vX2nT9pZ7cL3kF and Qm8tV3x9NpL7cR6zFh). The broker returns a Microsoft Graph API accessToken and expiresOn. This decouples the actual C2 backend from the extension’s source — the Graph token can be rotated by changing what the broker returns, without modifying the extension.
Layer Two — SharePoint Command Queues — Using the Graph token, the implant communicates with a SharePoint site (site ID e6bf72be-e8e2-4785-8814-5f874341d11f) via three SharePoint lists:
AppComms_Clients— Victim registration. On first activation, the implant writesHostname,Username,FirstSeenUTC, andLastHeartbeatUTCto this list and receives a per-victim item ID used in subsequent calls.AppComms_Commands— Task queue. The implant polls every 10 seconds for pending commands whoseTargetClientGUIDmatches its own UUID. Matched commands are claimed by updating theirStatusfield toProcessing.AppComms_Responses— Exfiltration channel. Command results are posted here, AES-encrypted, withStatus,CompletedUTC, and the response payload.
Heartbeat updates fire every 30 seconds. The result is a persistent, beacon-style implant whose traffic is indistinguishable from normal Graph API use by any Microsoft 365 application, and whose command channel has no direct network connection between victim and attacker infrastructure — all traffic flows through Microsoft’s own CDN.
The investment-gateway[.]azurewebsites[.]net domain was first observed on June 8, 2026 — consistent with same-day infrastructure standup for this campaign. At scan time it resolved to 51.137.163.33 (Microsoft Azure, Cardiff, United Kingdom, AS8075).
Exfiltration and File Transfer
Beyond command-and-control, the implant supports two large-file transfer modes, both delivered as python_action command types despite the name applying equally on all platforms.
Outbound exfiltration (download_large) — A local file is read, XOR-encoded against the victim’s hostname:username string, deflated, base64-encoded, and the base64 reversed before transmission. The encoded file is then uploaded to an attacker-controlled URL via multipart POST with attacker-specified cookie headers — giving the attacker a way to deliver session-authenticated uploads to any web endpoint they control, including cloud storage and file-drop services. The per-victim XOR encoding means each victim’s exfiltrated data looks different for identical files, complicating bulk detection.
Inbound staging (upload_large) — A file is downloaded from an Azure Blob Storage URL using the x-ms-version: 2023-11-03 header, decrypted using the reverse of the exfil encoding scheme, and written to a caller-specified path on the victim’s filesystem. This is the payload staging mechanism: the attacker can drop additional tools, configuration, or the next stage directly to disk without the download appearing in browser history or requiring the victim to take any action.
The host_action command type additionally exposes upload and download commands for small files as base64-encoded payloads delivered directly through the SharePoint command queue.
Encryption Architecture
The implant uses three distinct encryption layers:
-
String obfuscation — All strings in
extension.jsare AES-256-CBC encrypted. The key is split across four byte arrays in the source and assembled at runtime. -
C2 channel encryption — All content written to
AppComms_CommandsandAppComms_Responsesis AES-256-CBC encrypted using a hardcoded 32-byte key (3e1c9a72f5b84d06e2a97c5310fb8e4d) with a random 16-byte IV prepended to each message. The message is also GZIP compressed before encryption. -
Exfil encoding — File content is XOR’d with the victim’s
hostname:usernamestring (repeated to match the file length), then deflated, then base64-encoded, then byte-reversed. This is not strong cryptography — the XOR key is recoverable from the SharePoint client registration record, which the attacker controls. The encoding’s primary purpose is defeating content-inspection rules on network egress appliances that look for known file signatures.
DPRK TTP Overlaps
No direct IoC overlap exists between this sample and any publicly documented North Korean campaign — the broker domains, file hashes, and .NET module GUID return zero results across every indexed threat intelligence corpus we checked. What does exist are two meaningful architectural overlaps with documented Lazarus-nexus tradecraft, and one thematic match.
Contagious Interview / BeaverTail + InvisibleFerret: The cross-platform JS+Python architecture here is the most direct parallel. Contagious Interview — a campaign attributed to the Famous Chollima cluster (Lazarus nexus) and well-documented by Unit42, SentinelOne, and Google TAG — delivers BeaverTail (Node.js agent) alongside InvisibleFerret (Python backdoor) using the same platform split: a compiled binary or JS layer on Windows, a Python script on Linux and macOS. InvisibleFerret’s C2 protocol uses XOR and DEFLATE compression in its response encoding — the same primitives present in this sample’s exfil chain, though the specific combination (XOR keyed with hostname:username followed by byte-reversal) is not documented in any published InvisibleFerret analysis.
Lazarus DreamLoader: Lab52 published analysis of HideFirstLetter.dll in October 2025, attributing it to Lazarus Group. DreamLoader authenticates to Microsoft’s Graph API using an access token embedded in the binary, then uses the resulting session to retrieve its C2 infrastructure from a compromised SharePoint server. The authentication architecture in this sample — two Azure-hosted brokers that return Graph API Bearer tokens, which are then used for all SharePoint communication — is functionally the same pattern: a token broker separates the extension source from the actual C2 backend, and all C2 traffic flows through Microsoft infrastructure. The specific mechanism differs (DreamLoader embeds the token directly; this sample retrieves it dynamically from the broker), and no public DreamLoader report documents the list-queue structure (AppComms_Commands, AppComms_Clients, AppComms_Responses) seen here.
Financial infrastructure naming. The broker domains (investment-gateway, equity-core, path components /portfolio/positions, /accounts/summary) align with the targeting profile of TraderTraitor / Slow Pisces (Jade Sleet / UNC4899), DPRK actors who compromise cryptocurrency firms and financial services organizations and routinely use financial-sector personas and lure themes in their infrastructure. The domains themselves are not in any attributed DPRK infrastructure report; the naming is consistent with that cluster’s pattern without constituting evidence of it.
The Attribution Caveat: While these architectural components strongly mirror known DPRK playbooks, neither technique is exclusive to them. Frameworks like Havoc and campaigns like VEILDrive have similarly co-opted Graph API and SharePoint C2 infrastructure. Furthermore, the specific combination discovered here—marketplace persistence, SharePoint list-queues, and dynamic Roslyn compilation—presents a novel blueprint unmapped to any known threat cluster. TTP overlap provides a compelling lead, but definitive attribution requires further non-public indicators.
For organizations with access to non-public threat intelligence: the Contagious Interview fingerprint (JS+Python developer tooling, deflate+XOR exfil encoding) and the DreamLoader Graph API pattern are worth running against your private corpus.
Detection and Defense
The VS Code extension activation event fires on every IDE start. The implant does not require user interaction beyond installation. Any extension that registers activate and immediately starts a network polling loop without user-visible behavior is worth scrutiny.
Furthermore, Graph API traffic from developer workstations to SharePoint list endpoints is not inherently suspicious. The specific list names (AppComms_Clients, AppComms_Commands, AppComms_Responses) are IoCs in Graph API audit logs for any organization with Microsoft 365 logging enabled. The SharePoint site GUID e6bf72be-e8e2-4785-8814-5f874341d11f is a stable identifier for the command infrastructure.
Additionally, the broker domains — investment-gateway[.]azurewebsites[.]net and equity-core[.]azurewebsites[.]net — resolve to Azure infrastructure and will not be blocked by blanket *.azurewebsites.net rules without also blocking legitimate Azure-hosted applications. DNS-based blocking on the specific FQDNs is the appropriate control. monitor-agent.exe imports only mscoree.dll, has a PE section entropy of 8 (the .text section contains the Costura-compressed assembly payload), carries a forged 2040 compilation timestamp, and is unsigned. Those properties in combination are a reliable behavioral signature independent of hash-based detection, which the 3/71 VT score demonstrates is insufficient alone.
Indicators of Compromise
Extensions
ByteBinTools.jupyter-powerdev
- Publisher:
ByteBinTools· Display name: Jupyter Notebook PowerDev Tools - VSIX:
ByteBinTools.jupyter-powerdev-2026.6.8.vsix - VSIX SHA256:
(sample submitted via Argus Test Account 2)
ToolCraft.jupyter-powertools
- Publisher:
ToolCraft· Display name: Jupyter Notebook PowerTools - VSIX:
ToolCraft.jupyter-powertools-3.21.0.vsix - VSIX SHA256:
c1dcef7c51ebd4dd8c10b7a6c8e24f3084fd071b55d8c8f4eff9c4152c70935c
OLDev.markdown-mode-devtools
- Publisher:
OLDev· Display name: Markdown Mode Dev Tools - VSIX:
OLDev.markdown-mode-devtools-2.1.0.vsix - VSIX SHA256:
2b4d21506897c48932fe9d36e06b04b18f706dabf9683b3c2ad778db142eed7c
Network (Defanged)
Shared C2 backend (all three extensions)
- SharePoint site GUID:
e6bf72be-e8e2-4785-8814-5f874341d11f - C2 backend:
graph[.]microsoft[.]com/v1.0/sites/e6bf72be-e8e2-4785-8814-5f874341d11f/lists/AppComms_*
ByteBinTools + ToolCraft brokers
investment-gateway[.]azurewebsites[.]net/api/v2/portfolio/positions→51.137.163.33(AS8075, Azure UK)equity-core[.]azurewebsites[.]net/api/v1/accounts/summary- API keys:
R5mQ8vX2nT9pZ7cL3kF/Qm8tV3x9NpL7cR6zFh - User-Agent:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.3485.81
OLDev brokers (rotated infrastructure)
ppe-margin-auth[.]azurewebsites[.]net/api/v1/auth/refreshppe-finance-api[.]azurewebsites[.]net/api/v1/auth/login- API keys:
8pT3vQ9xN2mR7cL5zK1/9fA3kLm8QxP2vNz7Tg
Files
monitor-agent.exe (ByteBinTools)
- SHA256:
aa4f26b16a28d28bb18d48b5be6a94438486df610e7b8e3eb0908b91a17ea329 - SHA1:
db82b9009b862a92c50f166d3d6c3ecbb365e137 - MD5:
e0d58c1a0f2db5fcdae958fb42d9d0ef - SSDEEP:
196608:Q7TJJj2HwValVh0M5FqobMrtSeFhTh3j+d:QfqHwVaDwoiSePVT - TLSH:
T1BC7633BB8AF393B5F836C377F9A474A8D2BC8E9189517584C24D4149CC2BD2E145B3B2 - Size: 7,383,040 bytes · VT: 3/71 as of 2026-06-08
job-agent.exe (ToolCraft)
- SHA256:
1afe9b45f387772869dde622224c7518644413f922a2fa108fe66ec6cdc3e2db - Size: 7,383,040 bytes · Same Costura.Fody+Roslyn architecture
processor-agent.py (OLDev — Linux/macOS worker, identical ---READY--- protocol)
- SHA256:
3deae9a8c64028a4681566952fca8fef056dab294be6701d0caf9b09774c0fe7 - Size: 585 bytes
extension.js hashes
- ByteBinTools:
(see VSIX above) - ToolCraft:
d72c23dadcac89573252551b897d4d854f51b2f0fa6115560b8ce4b857427cc2 - OLDev:
414c04bbfc96accee2d1f13b76559dfd3e616b1d04289b0e7ebe67801720a18b
Cryptographic Artifacts
Shared across all three extensions
- C2 channel AES key (hardcoded):
3e1c9a72f5b84d06e2a97c5310fb8e4d
Per-extension string encryption keys (AES-256-CBC)
- ByteBinTools key:
5529a2c14bec074599384ee36686ee973ca681f94bc192d9eda572faf7d225af· IV:eb3798ac8759731755fb50c1ef56eabc - ToolCraft key:
d56ed25a914b6de8562f2c019e96b3a6f97a7402562537eb6f3f3be873603b2a· IV:200516f1cebd7077bdb8b52ef8c9f95c - OLDev key:
5d76b8a31a0b0ebd ed88dfe2cc7de6b3b3edcf0942f26cf8d84c4508d74d697c· IV:4eff ec22 26f1 44f8 052f 9102 936e 78d3
Behavioral (SIEM / EDR)
- Process:
nodeorelectronspawning any ofmonitor-agent.exe,job-agent.exe,processor-service.exe, orpython3 <*-agent.py>from a VSCode extensiondist/path - Network: HTTPS POST to
*.azurewebsites.netwith body{"clientId": "<UUID>"}andX-API-Keyheader - Network: Graph API PATCH to
/sites/*/lists/AppComms_Clients/items/*/fieldsat 30-second intervals - Network: Graph API GET to
/sites/*/lists/AppComms_Commands/itemswith filterTargetClientGUID eq '<UUID>'at 10-second intervals - PE:
.NETbinary, single importmscoree.dll,.textentropy ≈ 8, unsigned, PE timestamp 2040-12-28
Yeeth Security operates the Argus scanning service for Open VSX and publishes threat intelligence on malicious extensions. This sample was flagged by Argus on June 8, 2026. If you have additional samples or infrastructure related to this campaign, reach out at contact@yeethsecurity.com.