Post

sw-cache-forge: Polymorphic Payload Evolution on Top of GAP

sw-cache-forge: Polymorphic Payload Evolution on Top of GAP

sw-cache-forge is a small standalone tool that rewrites the cached JavaScript of a Chromium service worker. It has zero dependency on GAP and works on any SW cache entry you can write to, but it’s the missing second half of the GAP chain.

Persistence, then evolution

GAP gives you persistence: a service worker that keeps executing even after its extension folder is deleted. The catch is that the payload you injected at install time is frozen. sw-cache-forge lets you change that payload later, directly on disk, by rewriting the cache - swap the C2, add modules, pivot from a collector to an RCE agent - without redeploying and without dropping a new malicious artifact.

1
2
3
4
5
GAP (persistence)              sw-cache-forge (evolution)
────────────────────           ──────────────────────────
secure prefs + SW DB           rewrite _0 stream1 + CRC32
        β”‚                              β”‚
        └────► persistent SW β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β†’ new payload, recompiled by V8

The kill-chain

  1. Install a minimal extension with GAP (or anchor an existing one) β†’ persistent SW.
  2. Evolve later, from a foothold: locate the SW’s _0 cache entry and patch it.
  3. V8 sees the stale source_hash, drops the code cache, and recompiles your new source. No re-signing, no new file.

Why it’s that easy

A SW script is a SimpleCache _0 entry:

1
header | key | stream1 (JS) | EOF1 | stream0 (headers) | ...

sw-cache-forge rewrites only stream1 and recomputes the EOF1 CRC32. Two Chromium details do the rest: content_length is recomputed from the actual stream size (there’s no stored size to mismatch), and the V8 code cache self-invalidates on a source_hash mismatch.

1
2
3
4
5
6
7
8
# find where your payload ends up
python3 sw_cache_forge.py dump <hash>_0 --find "AGENTID"

# append a payload (the reliable pattern β€” idempotent)
python3 sw_cache_forge.py patch <hash>_0 --append-file agent.js --output <hash>_0.patched

# confirm CRCs + marker
python3 sw_cache_forge.py verify <hash>_0.patched --contains "AGENTID"

Demo

You can also use this tool to inject unexpected code into legitimate browser extension.


One pitfall: the source must be complete, valid JS. Append a self-contained agent to the end; a fragment cut mid-bundle won’t compile and the worker shows up empty.

For authorized security testing and research only.

This post is licensed under CC BY 4.0 by the author.