Skip to main content

New: Malicious MCP server caught in the wild!

research findings Coding agents, assistants, and MCP security AgentMesh

The Duck Song: Malicious Solidity Extensions Hide a Cross-Platform Payload in an MP4

By:
Tamir Isaschar Tamir Isaschar
11 min read

A Solidity extension should help you write smart contracts. These extensions brought something extra: a music video carrying an installer and four native executables.

Knostic’s AgentMesh identified five malicious editor extensions presented as Solidity or Hardhat tooling. In four of them, a file named The Duck Song.mp4 contains a shared payload for Windows, macOS and Linux. The video data remains intact; the loader retrieves the payload from a separate section of the MP4 container.

On Windows, the chain attempts to install ScreenConnect, a legitimate remote-access product configured to connect to an external relay. If installation and connection succeed, that client can give the operator remote control of the developer’s machine. On macOS and Linux, the loader writes a native Go binary, attempts to start it, and registers a per-user LaunchAgent or systemd unit to start it again. What those binaries do once running is only partially characterized.

The lure matters. A smart-contract developer’s workstation may hold wallet keys, deployment credentials, repository access and RPC tokens. Remote control of that workstation could expose those assets. We found no evidence of stolen funds or confirmed victim infections.

1. TL;DR

  • Five malicious extensions, four publisher accounts. The packages present themselves as Solidity or Hardhat tooling; some impersonate established developers or teams.
  • One payload in four packages. All four MP4-bearing extensions decode to a byte-identical seven-file archive. There are two differently encoded video carriers, each shipped in two of the packages.
  • Windows remote access. The loader attempts elevation, stages a ScreenConnect MSI, and attempts a temporary Microsoft Defender exclusion around installation.
  • macOS/Linux persistence. The loader installs platform-specific Go executables under ~/.solidity, with a LaunchAgent or systemd user unit.
  • Limited measured exposure. AgentMesh values (API and web view) retrieved on October 5, 2026 reported one install for the earlier Windows-only extension and zero for each of the four MP4-bearing packages. These counters do not establish successful deployments or victims.

Bottom line: installing one of these “Solidity” extensions and restarting the editor is enough to start an installer chain. On Windows it attempts to install remote-access software; on macOS and Linux it plants a persistent native binary whose full behavior is still unknown.

Our analysis was static: we inspected package contents, decoded the embedded archive, and traced the JavaScript and PowerShell installation chain. We did not execute the extensions or payloads, contact their relays, or verify installation success on a victim machine.

2. The lure: familiar names, unexpected behavior

The packages borrow credibility from the Solidity ecosystem. juanfranblanco.solidity-vscode resembles the identity of Solidity-extension author Juan Blanco. Nomic-ETH.hardhat-solidity invokes the Hardhat ecosystem; both that package and alejandroaranda.solidity-languini describe themselves as “Solidity and Hardhat support by the Hardhat team.” These references identify the impersonated parties, not parties implicated in the malicious behavior.

The earlier JuanFra.sol-prac package adds zero-width characters to its display name. It looks like “Solidity For Ethereum,” but the underlying string differs, which can defeat unnormalized exact-string comparisons. Its repository field points to github[.]com/ethereum, the legitimate Ethereum organization; a manifest can name any repository, so the field does not establish provenance.

All five manifests (extension/package.json in each package) declare startup activation:

"activationEvents": ["onStartupFinished"]

Opening the editor is enough to invoke the extension. Successful payload deployment is a separate step, subject to platform checks, existing-install checks and, on Windows, elevation.

Analyzed packages

Extension IDVersionRegistry recorded in the researchAgentMesh analysis
JuanFra.sol-prac0.0.2VS Code MarketplaceView
alejandroaranda.solidity-langsupport0.0.6OpenVSXView
alejandroaranda.solidity-languini0.9.2OpenVSXView
juanfranblanco.solidity-vscode0.0.191OpenVSXView
Nomic-ETH.hardhat-solidity0.9.3OpenVSXView

These identifiers refer to the specific analyzed packages and versions. This table does not assert their current availability or removal from either registry. AgentMesh pages show automated scan summaries; where their wording is broader (for example, describing Defender as “disabled”), the code-level account in this post is the more precise one.

3. From editor startup to payload deployment

Flowchart of the attack chain from editor startup: the earlier JuanFra.sol-prac package renames an MSI and installs a ScreenConnect client; the four MP4 packages launch a detached runner, extract the kit from The Duck Song.mp4, then either install ScreenConnect on Windows or write a persistent Go binary on macOS and Linux.

Figure 1. Installation paths recovered from the packages. ScreenConnect remote access is conditional on successful installation and connection. Native-implant behavior is not fully established.

The earlier Windows-only package: an MSI wearing a .node extension

JuanFra.sol-prac ships libraries/nodejs.node. The filename suggests a compiled Node add-on, but the contents are a Windows Installer package.

Behind a Windows-only guard, the extension renames the bundled file and launches PowerShell to invoke msiexec with /qn and /norestart, using Start-Process -Verb RunAs. The MSI installation is quiet, but RunAs requests elevation and can display a UAC prompt.

The installer is a ScreenConnect client configured for an unattended Access session through connect[.]cs3-wcdn[.]com:8041, with the embedded client instance name ScreenConnect Client (7eb9ed4222a80aa8). This is a remote-access installation path, not a Solidity language feature.

The four MP4 packages: the video is the container

The other four extensions invoke attemptUpdate() from their activation function. Before deploying anything, the shared autoUpdate.js checks whether installation appears complete.

On Windows, it searches folder names under Program Files, Program Files (x86), %LOCALAPPDATA% and %APPDATA%, then process and service names, for “ScreenConnect” (hex-encoded in the code); the process/service probe is a PowerShell child of the editor. This is name-based detection: even a legitimate ScreenConnect installation can make the loader skip deployment. On macOS and Linux, it checks for its own binaries and runner files under ~/.solidity.

If deployment proceeds, the extension launches lang-resource-sync.js as a detached process. On Windows it prefers an installed Node executable and falls back to the editor’s executable with ELECTRON_RUN_AS_NODE=1. On macOS/Linux it takes that fallback route directly.

The runner then reads The Duck Song.mp4. The archive is stored in an MP4 free box placed after the mdat (media data) box; it is not encoded into the frames or audio samples. The video portion is about 3.8 MB in both variants, while the free box adds about 201 MB or 50 MB depending on the variant.

The decoding layers are:

LayerWhat the loader reads
Container markerNYANSTEG inside the MP4 free box
Integrity checkA 32-byte SHA-256 digest for the following encoded payload
Substitution encodingNYANB64X: a token table substitutes for the Base64 alphabet, including padding
Decoded archiveNYANARCH: file names, lengths, SHA-256 digests and file contents

The relevant readArchiveFromMp4() code in lang-resource-sync.js explicitly checks the container box type. The excerpt below is from alejandroaranda.solidity-langsupport 0.0.6 (lines 130–133); the same lines are present in all four runners:

const boxStart = idx - 8;
const boxSize = data.readUInt32BE(boxStart);
const boxType = data.subarray(boxStart + 4, boxStart + 8).toString('ascii');
if (boxType !== 'free') {
  throw new Error("found magic but not inside a 'free' box");
}

Two carrier variants use different token tables and lengths. Both produce the same archive. Its seven entries are app.msi, invoke.ps1, four macOS/Linux executables, and a duck-art note.txt. Two additional DLLs are embedded inside the PowerShell script, rather than stored as separate archive entries.

Windows: elevation, a staged MSI and cleanup

The decoded invoke.ps1 contains a compressed inner script. Its elevation logic branches according to the current token and local-administrator membership:

  • Already elevated: launch the command directly.
  • Not a local administrator: request elevation with RunAs.
  • A local administrator with an unelevated token: attempt a .NET-profiler UAC bypass. The script falls back to the RunAs loop if the attempt reports no success, or if it throws an error that Test-ScriptScanBlock recognizes as an antivirus script-scan block. Any other exception is rethrown, so the fallback is not guaranteed.

The bypass attempt registers embedded profiler DLLs through COR_ENABLE_PROFILING, COR_PROFILER, COR_PROFILER_PATH and an HKCU CLSID entry, then launches mmc.exe with .msc targets. The fallback re-issues the elevation prompt whenever cancellation is detected, with no retry limit in the code, so a user who cancels can be prompted repeatedly. We traced these code paths; we did not establish that the bypass succeeds on any Windows build.

Before elevation, the runner writes the MSI and helper scripts to %TEMP% (.stuffed-<timestamp>.msi, .stuffed-launch-<timestamp>.vbs, .stuffed-install-<timestamp>.ps1, .stuffed-invoke-<timestamp>.ps1). The command passed for elevation runs wscript.exe with the VBS launcher, which starts a hidden PowerShell post-elevation script. That script creates a hidden, system-attributed directory under %LOCALAPPDATA%, attempts to exclude it from Defender scanning, and installs the MSI with msiexec /q /i … ALLUSERS=1. A finally block attempts to remove the exclusion and staging files. The intended exclusion is temporary; neither addition nor cleanup is guaranteed to succeed.

macOS/Linux: binaries made persistent

The runner extracts the binary matching the operating system and CPU architecture, writes it under ~/.solidity, and creates a small wrapper that starts it: .run.py on Linux, and on macOS .run.cjs when Node is found or .run.py otherwise. On first deployment it starts the wrapper in the background. It then writes and enables a systemd user unit on Linux (Restart=on-failure) or a RunAtLoad LaunchAgent on macOS. On macOS it also runs xattr -c on the binary, which clears its extended attributes, including a quarantine attribute if one is present. None of these steps requires elevation.

Two packages—juanfranblanco.solidity-vscode and Nomic-ETH.hardhat-solidity—also exit before reading the MP4 if they detect a debugger (inspector flags, an attached Node inspector, or on Windows a list of analysis tools) or VM indicators (DMI product names and the CPU hypervisor flag on Linux, hw.model/ioreg on macOS, CIM model/BIOS strings on Windows). The two alejandroaranda packages lack those runner checks. This is a difference between samples, not evidence of a verified development sequence.

4. What happens to the developer’s machine?

On Windows, successful installation and relay connection can expose the workstation to interactive remote access. That access could allow an operator to inspect files, run commands and reach credentials or development resources available from the machine. We did not observe credential theft, wallet transactions or lateral movement.

On macOS/Linux, the installation code we traced writes a native binary, attempts to run it, and installs persistence intended to launch it again. We did not observe it running, and its runtime capabilities remain unresolved.

In the inspected linux_amd64 binary, embedded strings reference public Ethereum RPC endpoints and the eth_call method, and recovered Go function names (for example ethCall, ReadParam1WithFallback, hubclient.Run) suggest contract reads and a “hub” client. We read names and strings only, not instruction-level logic. Names such as startShellLocked and runCommand suggest shell or command-execution functionality, but names alone do not prove its implementation. We have not traced whether an Ethereum response supplies a control-server address. Blockchain-based C2 retrieval and wallet targeting are not established findings.

The lure selects a valuable development context, but potential impact should not be confused with observed harm. Low counters also do not tell us whether any of the recorded installs completed this chain.

All four MP4 packages share byte-identical extension.js, autoUpdate.js and winDebug.js files. All four carriers decode to the same archive:

118f0fb16088f94ae70c22299d886929b71c62d64f1e57404bd91424870aff5c

That relationship is strong: the four MP4 packages are clearly related samples.

The link to the earlier JuanFra.sol-prac package is weaker. Its MSI and the MP4-carried MSI share ProductCode {B292C5EA-BF5F-4280-B056-1670FB10BB1D}, the e=Access and y=Guest session parameters, and relay port 8041. But they differ in relay host, in the embedded ScreenConnect server public key (k=), and in client instance name (7eb9ed4222a80aa8 versus 5cbe79c9c25524df): the two installers are configured for different ScreenConnect servers. We have not established how specific these shared values are: the ProductCode, e=Access, y=Guest and port 8041 could be unique to this activity or common across ScreenConnect builds and deployments. The MSI overlap is therefore weak evidence on its own.

We assess the earlier package as probably part of the same activity—same lure, impersonation style, startup activation and ScreenConnect delivery—but that link is not proven. None of these overlaps proves that all publisher accounts were operated by one individual.

6. Detection lessons

Follow the startup path. A language-tooling manifest that leads directly to installer execution deserves scrutiny. Analyze reachable behavior from the declared entry point, not just package descriptions or suspicious strings.

Inspect content, not extensions. Here, .node contains an MSI and .mp4 contains an executable archive. JavaScript inspection exposes the extraction chain; examining the bundled data identifies what is deployed.

Correlate editor processes with remote-access installation. An editor (or its detached Node/Electron child) launching PowerShell, then wscript.exe, then msiexec to install ScreenConnect is a useful behavioral chain. The .NET profiling environment changes and temporary Defender exclusion add context; isolated signals can also have legitimate explanations.

Check multiple registries. Four analyzed packages were recorded in OpenVSX and one in the VS Code Marketplace. Review extension provenance and behavior across the registries your development environment uses.

7. IOCs and investigation artifacts

Network indicators below are defanged. Hashes, extension IDs, filenames, paths and GUIDs retain their exact values for matching. Public RPC services and metadata references are contextual artifacts, not malicious infrastructure on their own. AgentMesh analysis links above are ordinary links to the defender’s service.

Network

IndicatorContext
connect[.]cs3-wcdn[.]com:8041Earlier ScreenConnect relay configuration
185[.]232[.]84[.]169:8041MP4-carried ScreenConnect relay configuration

Extension packages: SHA-256

Extension and versionVSIX SHA-256
JuanFra.sol-prac 0.0.207a91a0405829acb22c153190fc23d3f58c0dd023a49b28a34cda56de1241f44
alejandroaranda.solidity-langsupport 0.0.6e9ccb04446d570d784d05c675898164a48eb15c4b7a9ae3266a3f43ee809c788
alejandroaranda.solidity-languini 0.9.266c1f6eb386ecb9bddf54a05aa48d11b3b1424e8a0bc109300e5b30f3d41d395
juanfranblanco.solidity-vscode 0.0.191ed6323c5023e466eaeeaf37651a9f8b803e13837e3783e87f93fdde53e384ba8
Nomic-ETH.hardhat-solidity 0.9.3f7f85f432945500441700f6d95dd22f9659de21d7488a9bb43789bbce882a20a

Extracted content: SHA-256

ContentSHA-256
Shared decoded archive118f0fb16088f94ae70c22299d886929b71c62d64f1e57404bd91424870aff5c
Earlier MSI (nodejs.node)bdd8df3c2ddd43c03022717bcb0f4905256701b6f553c53b695148f4f5d92022
MP4-carried app.msi7527e86d70e7c9493db4356f1c3775380431b310000f2602c1e993cf435d01da
darwin_amd6480d2672e2599732d3c0ae2a4cd0d1e3fe4d555a60273ce33feb99db3f34d250f
darwin_arm64fae61f31f00988fdc5cc9e7272b51e08af2e245d81003237875415930fd27358
linux_amd649b73e7cd4e1425e770392549d8df46c706139ef476f4f7b9ac405165dc8d9696
linux_arm64b601776817363b96295119f7221338a122d5247a35a77a64b04f286e1bbcc565
invoke.ps137d14bff30762ca701e9c1f8bc6c57c065bb20ae3a2208cb7411a6530610f08e
Embedded profiler DLL, x64ef5ede5a20619af9467edb80c2a49714d512f836ddd0797ce0f8f9a4ba1a7ea8
Embedded profiler DLL, x86d7af711d435eb106cc81be226449e902872196c4c81092ebae13f60d5982cbac

Host and package artifacts

ArtifactContext
{B292C5EA-BF5F-4280-B056-1670FB10BB1D}ScreenConnect MSI ProductCode in both delivery variants; low specificity, may be shared by a ScreenConnect build
ScreenConnect Client (7eb9ed4222a80aa8)Client instance name embedded in the earlier MSI (nodejs.node)
ScreenConnect Client (5cbe79c9c25524df)Client instance name embedded in the MP4-carried app.msi
The Duck Song.mp4 containing NYANSTEG and NYANB64X in a free boxPayload carrier; NYANARCH appears after decoding
~/.solidity/linux_amd64, ~/.solidity/linux_arm64Linux payload destinations
~/.solidity/darwin_amd64, ~/.solidity/darwin_arm64macOS payload destinations
~/.solidity/.run.py, ~/.solidity/.run.cjsPlatform-dependent runner scripts
~/.config/systemd/user/solidity-langsupport-runner.serviceLinux persistence unit (Description=Solidity language tooling helper)
~/Library/LaunchAgents/com.solidity.langsupport.runner.plistmacOS persistence plist (label com.solidity.langsupport.runner)
%TEMP%\solidity-langsupport-debug.logWindows loader debug log (MP4 packages)
%TEMP%\.stuffed-<timestamp>.msi, .stuffed-launch-<timestamp>.vbs, .stuffed-install-<timestamp>.ps1, .stuffed-invoke-<timestamp>.ps1Transient Windows staging files; the code attempts to delete them
%LOCALAPPDATA%\.<32-hex GUID> (hidden, system)Transient Windows staging directory and attempted Defender exclusion path

These artifacts vary in specificity. A filename, a public RPC endpoint or a ScreenConnect installation alone is not proof of compromise.

Context from the inspected Linux binary

ValueInterpretation
hxxps://1rpc[.]io/ethPublic RPC endpoint string; not an attacker-owned IOC
hxxps://eth[.]drpc[.]orgPublic RPC endpoint string; not an attacker-owned IOC
0xf8a900Db50b3331be6B768bA460BB59f3E40C344Contract-shaped literal; role and ownership unresolved
evjawbreaker/internal/eth, internal/hubclient, internal/antivmRecovered Go module/function-name context

8. If you installed an analyzed package

Disable the extension and investigate the host, using the package hashes and persistence paths above. Look for an unexpected ScreenConnect service (including the client instance names above), installer activity and Defender configuration changes on Windows, or the listed binaries and persistence files on macOS/Linux. Absence of a current Defender exclusion does not rule out the installation path: the code attempts to remove it after staging.

Treat credentials accessible from a potentially compromised host as exposed pending investigation. Revoke and replace relevant API tokens and deployment credentials from a clean device. If a wallet’s private key or recovery material was accessible to the host, use trusted wallet software on a clean device to move assets to a newly generated wallet; replacing an API token does not secure an exposed wallet key.

9. Conclusion

The most revealing part of this campaign is the gap between the advertised tool and its startup behavior. The packages promise Solidity support; their installation chain extracts executables, requests elevated execution, installs remote-access software and sets up persistence.

The duck video makes the samples memorable. The security finding comes from following the code that opens it: treat a startup-activated extension that ships media or binary blobs as code to be executed, and inspect what its entry point does with them.