When "ollama.js" Isn't Ollama
- Threat Hunting
- Incident Response
Overview
A file named ollama.js came off a host that already had a working Ollama installation, which put both the file name and the application in line with ordinary setup activity. The one thing that did not fit was a winget command that installed Deno a few seconds earlier, since Ollama has no JavaScript runtime dependency and no supported install path that pulls Deno in alongside it.
Deno’s foreign function interface is what the rest of the chain depends on, because it lets a script named ollama.js load a Rust DLL directly into memory, decrypt and exfiltrate saved browser credentials, and install a browser extension that provides persistent remote access. The genuine Ollama install left behind by the same installer serves as cover, since the victim ends up with a working application and no reason to look at what ran next to it.
The technique matches the “Evil Deno” proof of concept that Taggart Tech published last year, though the DLL here was compiled on 2026-06-22, which puts it in active development rather than a replay of the original research.
Initial access
Delivery is a fake Ollama installer, a PowerShell script retrieved from ollama[.]lnk/install.ps1, promoted as far as I can tell through a YouTube video advertising fake ChatGPT and Claude tooling. The lure matches the fake AI installer campaigns that have been delivering Deno-based malware for the past several months, though there is no ClickFix prompt or terminal paste involved and the user is simply running an installer for software they intended to install.
From that one script the installer forks into two chains, one installing the genuine Ollama package so the host ends up with a functioning application, the other pulling Deno through winget and executing ollama.js. Running both from the same script is what makes the decoy hold, since the legitimate install is part of the delivery design rather than a side effect of it.
Loader
Obfuscation on the loader is a single 2.7 MB base64 string plus per-call reconstruction of API names from character codes, and stripping it leaves a short sequence of operations. The embedded blob is base64-decoded, raw-inflated into a PE32+ DLL, written to %APPDATA%, loaded through Deno’s foreign function interface, called once at a single exported function in-process, and deleted when the call returns.
The flow below has the runnable components removed.
| |
Comparable loaders reach native code through LoadLibrary or by shelling out to rundll32 or regsvr32, all of which leave process artifacts worth pivoting on, so executing the DLL inside a signed and generally trusted JavaScript runtime removes the separate loader process from the picture entirely. The file is also gone from disk before most scheduled scans would reach the directory.
What remains to detect on is deno.exe writing a PE file into a user-writable directory and then loading that file, a sequence with no legitimate use case.
Credential theft
The DLL is written in Rust under the internal project name app_edge, with a leaked PDB path identifying the build user as root, and its purpose is browser credential theft. It reads each browser’s Local State file, extracts the encrypted key, and recovers it by defeating App-Bound Encryption, falling back to DPAPI where the newer scheme is not present. Debug strings left in the binary document that process, including references to retrieving the Comet v10 key and to falling back after an empty v20 DPAPI key. Locked cookie and credential databases are reached by force-closing the browser with taskkill, after which the contents are decrypted with AES-GCM using the recovered keys.
Chrome, Edge, Brave, and Perplexity Comet are all targeted, and the Comet entry is the first case I have handled where an AI browser is treated as a first-class credential target with its own dedicated key recovery path. I expect that to become normal in stealer development.
Browser extension and native messaging host
Alongside the credential theft, the DLL unpacks an MV3 browser extension into %APPDATA%\ext\ and registers a native messaging host, which bridges the extension to PowerShell through a run.bat that launches an obfuscated script of roughly 2 MB. That bridge is what allows commands issued from the extension to execute in a real shell on the host.
The extension maintains a WebSocket connection to its C2, reconnects every five seconds, and pulls per-victim configuration and DPAPI keys down at runtime. Decoding the background script gives the following command set.
- Cookie theft, exported in Netscape format
- Interval screenshots through the browser capture API
- Web injects, form grabbing, and payment card capture
- Remote shell through the native messaging host
- Targeted file exfiltration
- Host reconnaissance
- Self-update
Persistent remote access inside the browser is a different problem from a stealer that runs once and leaves, since cookie and session theft from that position walks past MFA on any session that is already authenticated.
PowerShell backend
Most of the collection turned out to be implemented in the PowerShell component rather than in the extension. The native messaging host executes zqpbm.ps1, which took two passes to read: stripping the <#...#> comment injection produces roughly 640 KB of arithmetic character code assembly, that decodes to a second array, and the second array yields roughly 26 KB of readable PowerShell.
Reading the cleartext corrected my initial assessment of the C2 layout, since the WebSocket at qweuyquigwebq[.]com carries commands only and stolen data goes over plain HTTP to adyitegqbk[.]com instead. /gate receives browser database ZIPs and extension data, /filegrabber receives targeted file grabs, and /api/config/extensions returns a server-controlled list of extension IDs to collect. A reference to bestgrillde31[.]com remains as a commented-out earlier backend the developer never cleaned up, and every POST carries the host MachineGuid, read from HKLM\SOFTWARE\Microsoft\Cryptography\MachineGuid, in an id header, which is how the operator keys victims apart.
The script copies the Web Data and Login Data SQLite files from every Chrome, Edge, and Brave profile under every user account on the host, opening them with FileShare read-write so locked files do not interrupt collection, then compresses and uploads them, which is already broader than the extension command set suggested. It also retrieves the server-configured extension list and collects Local Extension Settings and IndexedDB for each ID, a pattern built for draining cryptocurrency wallets. The file grabber supports recursive globbing and uploads matches per configured path, and a hidden interactive shell, either PowerShell or cmd, runs with an async stderr reader to keep the pipe from deadlocking. Taken together it reads as a maintained backend rather than something assembled for one campaign.
The cleartext also retains Russian comments, #читать из экста + писать в экст (“read from ext + write to ext”) and #сепаратор (“separator”), pointing to a Russian-speaking developer.
No public reporting was identified for these domains or this build at the time of writing, so the indicators below are assessed as first-seen. All four domains are recent, and both HTTP backends have gone on the block list alongside the WebSocket C2.
Detection
The mutexes and PDB string survive rebuilds better than the domains do, so a YARA rule keyed on those plus the known hash catches re-packaged copies of the same DLL:
| |
The 2-of-6 string threshold is there so a single coincidental match (com.abcd alone, say) doesn’t fire; the mutex names and the PDB path together are specific enough that a rebuild would need to change all of them to evade this.
Indicators of compromise
Network
| |
Files
| |
Registry (native messaging host)
| |
Host artifacts
| |
Hashes
| |
Resources
- Evil Deno: Abusing the Nicest JavaScript Runtime (Taggart Tech, May 2025): The proof of concept this sample weaponizes, including the FFI and native-DLL load tricks.
- Fake ChatGPT and Claude installers are dropping Deno RAT malware (Help Net Security, May 2026): Documents the same trojanized-AI-installer playbook, including the YouTube channels driving traffic and the Deno-based RAT it delivers.
- How Infostealer Malware Bypasses Chrome’s App-Bound Encryption (SpyCloud, February 2025): Background on why the ABE-to-DPAPI fallback in the Rust stealer still works.