GAP - Ghost Anchor Persistence: Fileless Extension Persistence in Chromium Browsers
Browser extensions are an underestimated attack surface. They run natively in the browser, are trusted by design, have access to cookies, network requests, and every page the user visits - and most EDRs don’t even look at them. In a post-exploitation context, a malicious extension operating as an agent is practically invisible to standard endpoint security tooling.
In this post, I’m releasing GAP (Ghost Anchor Persistence), a fileless persistence technique I discovered during personal research. GAP exploits an architectural quirk in Chromium-based browsers to achieve persistent, arbitrary JavaScript execution within the browser - with no malicious artifact remaining on the filesystem after the attack.
The full research paper is available here.
Prior Work - Building on stomp
If you haven’t read the previous post on stomp, here’s the short version: it’s possible to force-install a browser extension into a Chromium-based browser by manipulating the Secure Preferences file with a correctly recomputed HMAC. This works even in GPO-restricted environments by spoofing the ID of a whitelisted extension.
That technique is effective, but it has one obvious limitation: the malicious extension folder sits on disk, readable by any process running under the target user. A SOC analyst or an EDR with filesystem monitoring will find it.
GAP eliminates that artifact entirely.
The Three Components at Play
To understand GAP, you need to know how Chromium manages extensions internally. Three independent components are involved:
Secure Preferences - a JSON file signed with an HMAC that stores the list of installed extensions, their paths on disk, and associated metadata.
1
C:\Users\<user>\AppData\Local\Microsoft\Edge\User Data\Default\Secure Preferences
Service Worker Registration database - a LevelDB database that persists service worker registrations. When a browser extension with a background service worker is first loaded, Chromium writes a registration entry mapping the extension scope to its script URL and an internal version_id. This database is architecturally independent from the extension folder on disk.
1
Service Worker\Database\
ScriptCache - a directory where Chromium stores compiled service worker scripts, indexed by a hash derived from the script URL. ScriptCache is only invalidated under three specific conditions: an HTTP cache-control signal, an explicit unregister() call, or a version bump through the official extension update mechanism. Replacing the extension folder on disk does not trigger invalidation.
1
Service Worker\ScriptCache\
The key insight: when the browser starts up, it checks that the extension folder exists on disk, but it does not verify that the cached script in ScriptCache matches the file currently present in that folder.
The Attack Primitive
GAP requires two extensions sharing the same ID - generated by stomp’s --spoof option:
- Extension A: malicious extension with a functional background service worker
- Extension B: benign extension with identical filenames but empty script content
The infection chain is four steps:
1. Deploy A : inject extension A using stomp. Edge registers A’s ID in Secure Preferences and resolves its folder path.
2. Trigger Registration : open and close the browser. This is mandatory: it causes Chromium to register A’s service worker in the Service Worker\Database\ and cache its compiled background.js in ScriptCache. Skipping this step breaks the chain.
3. Stomp with B : run B’s inject.bat. Secure Preferences is overwritten: the extension ID now resolves to B’s benign folder. The Service Worker Registration database and ScriptCache are untouched.
4. Remove A : delete A’s folder from disk. Only B’s benign folder remains. No malicious artifact on the filesystem.
At next browser launch: Edge resolves the extension ID to B’s folder, finds an existing Registration for this scope, confirms the folder exists, and loads A’s malicious script from ScriptCache. The Ghost Anchor Persistence is established.
1
2
3
4
5
6
7
8
9
Secure Preferences Extension ID → folder B (benign, on disk)
|
existence check only
|
Service Worker DB Registration of A (version_id unchanged)
|
cache key lookup
|
ScriptCache Compiled background.js of A [EXECUTING]
Why ScriptCache Is Never Invalidated
The chrome-extension:// scheme carries no HTTP headers, so cache-control validation doesn’t apply. No unregister() is called during the stomp operation, so the Registration persists. And since extension A and B share the same ID, no version bump is detected by the update mechanism.
The cache key - chrome-extension://<ID>/background.js — remains valid indefinitely. Edge serves A’s malicious compiled script on every launch, forever.
Red Team Value
From a post-exploitation perspective, GAP is particularly useful because:
- No malicious file on disk at any point after the stomp. The only artifact is B’s benign folder with empty scripts and a manifest. Static analysis yields nothing.
- No new process. The service worker executes inside Edge’s existing renderer process infrastructure, indistinguishable from legitimate extension activity.
- C2 over standard HTTPS. Traffic is attributed to
msedge.exeand blends with normal browser activity. - Survives everything. Browser restarts, system reboots, browser updates — validated across all of them.
- Works in AD + GPO environments. Combined with stomp’s
--spoofoption, GAP bypasses extension allowlist policies enforced via Group Policy. - No admin privileges required. Standard user rights are sufficient at every stage of the attack chain.
The agent running in the background service worker has access to: cookie exfiltration (chrome.cookies.getAll()), keylogging, screenshot capture (tabs.captureVisibleTab()), credential theft, network proxying, and dynamic content script injection via scripting.executeScript() — which is how page-level payloads are delivered without any static file dependency.
Responsible Disclosure
A vulnerability report was submitted to Microsoft MSRC on April 2, 2026 (Case #112111). Microsoft acknowledged and investigated. Their assessment: the technique doesn’t meet their bar for a security update, citing the local access prerequisite.
Fair enough on the prerequisite - but a fileless, EDR-evading persistence primitive operating within a trusted browser process is a meaningful post-exploitation risk in enterprise environments. The fix would be straightforward: implement content integrity validation between ScriptCache entries and their corresponding filesystem artifacts.