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 ID | Version | Registry recorded in the research | AgentMesh analysis |
|---|---|---|---|
JuanFra.sol-prac | 0.0.2 | VS Code Marketplace | View |
alejandroaranda.solidity-langsupport | 0.0.6 | OpenVSX | View |
alejandroaranda.solidity-languini | 0.9.2 | OpenVSX | View |
juanfranblanco.solidity-vscode | 0.0.191 | OpenVSX | View |
Nomic-ETH.hardhat-solidity | 0.9.3 | OpenVSX | View |
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
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:
| Layer | What the loader reads |
|---|---|
| Container marker | NYANSTEG inside the MP4 free box |
| Integrity check | A 32-byte SHA-256 digest for the following encoded payload |
| Substitution encoding | NYANB64X: a token table substitutes for the Base64 alphabet, including padding |
| Decoded archive | NYANARCH: 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
RunAsloop if the attempt reports no success, or if it throws an error thatTest-ScriptScanBlockrecognizes 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.
5. Why we link the samples
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
| Indicator | Context |
|---|---|
connect[.]cs3-wcdn[.]com:8041 | Earlier ScreenConnect relay configuration |
185[.]232[.]84[.]169:8041 | MP4-carried ScreenConnect relay configuration |
Extension packages: SHA-256
| Extension and version | VSIX SHA-256 |
|---|---|
JuanFra.sol-prac 0.0.2 | 07a91a0405829acb22c153190fc23d3f58c0dd023a49b28a34cda56de1241f44 |
alejandroaranda.solidity-langsupport 0.0.6 | e9ccb04446d570d784d05c675898164a48eb15c4b7a9ae3266a3f43ee809c788 |
alejandroaranda.solidity-languini 0.9.2 | 66c1f6eb386ecb9bddf54a05aa48d11b3b1424e8a0bc109300e5b30f3d41d395 |
juanfranblanco.solidity-vscode 0.0.191 | ed6323c5023e466eaeeaf37651a9f8b803e13837e3783e87f93fdde53e384ba8 |
Nomic-ETH.hardhat-solidity 0.9.3 | f7f85f432945500441700f6d95dd22f9659de21d7488a9bb43789bbce882a20a |
Extracted content: SHA-256
| Content | SHA-256 |
|---|---|
| Shared decoded archive | 118f0fb16088f94ae70c22299d886929b71c62d64f1e57404bd91424870aff5c |
Earlier MSI (nodejs.node) | bdd8df3c2ddd43c03022717bcb0f4905256701b6f553c53b695148f4f5d92022 |
MP4-carried app.msi | 7527e86d70e7c9493db4356f1c3775380431b310000f2602c1e993cf435d01da |
darwin_amd64 | 80d2672e2599732d3c0ae2a4cd0d1e3fe4d555a60273ce33feb99db3f34d250f |
darwin_arm64 | fae61f31f00988fdc5cc9e7272b51e08af2e245d81003237875415930fd27358 |
linux_amd64 | 9b73e7cd4e1425e770392549d8df46c706139ef476f4f7b9ac405165dc8d9696 |
linux_arm64 | b601776817363b96295119f7221338a122d5247a35a77a64b04f286e1bbcc565 |
invoke.ps1 | 37d14bff30762ca701e9c1f8bc6c57c065bb20ae3a2208cb7411a6530610f08e |
| Embedded profiler DLL, x64 | ef5ede5a20619af9467edb80c2a49714d512f836ddd0797ce0f8f9a4ba1a7ea8 |
| Embedded profiler DLL, x86 | d7af711d435eb106cc81be226449e902872196c4c81092ebae13f60d5982cbac |
Host and package artifacts
| Artifact | Context |
|---|---|
{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 box | Payload carrier; NYANARCH appears after decoding |
~/.solidity/linux_amd64, ~/.solidity/linux_arm64 | Linux payload destinations |
~/.solidity/darwin_amd64, ~/.solidity/darwin_arm64 | macOS payload destinations |
~/.solidity/.run.py, ~/.solidity/.run.cjs | Platform-dependent runner scripts |
~/.config/systemd/user/solidity-langsupport-runner.service | Linux persistence unit (Description=Solidity language tooling helper) |
~/Library/LaunchAgents/com.solidity.langsupport.runner.plist | macOS persistence plist (label com.solidity.langsupport.runner) |
%TEMP%\solidity-langsupport-debug.log | Windows loader debug log (MP4 packages) |
%TEMP%\.stuffed-<timestamp>.msi, .stuffed-launch-<timestamp>.vbs, .stuffed-install-<timestamp>.ps1, .stuffed-invoke-<timestamp>.ps1 | Transient 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
| Value | Interpretation |
|---|---|
hxxps://1rpc[.]io/eth | Public RPC endpoint string; not an attacker-owned IOC |
hxxps://eth[.]drpc[.]org | Public RPC endpoint string; not an attacker-owned IOC |
0xf8a900Db50b3331be6B768bA460BB59f3E40C344 | Contract-shaped literal; role and ownership unresolved |
evjawbreaker/internal/eth, internal/hubclient, internal/antivm | Recovered 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.