document.write() Survival — Keeping Detection Alive After DOM Nukes
A C2 loader fetched a ClickFix payload from a remote server, then called document.open() followed by document.write() to replace the entire page. This destroyed ClickArmor's MutationObserver, all injected UI, every event listener, and all clipboard hooks — in one call.
The Technique
The attack chain started with an obfuscated inline script on a compromised WordPress site. The script fetched a remote payload via XHR, then executed:
// From the C2 response handler:
var doc = d.open("text/html");
doc.write(xhr.response);
doc.close();
document.open() replaces the entire document — not just DOM elements. It destroys the documentElement that the MutationObserver was attached to, removes all injected banner and overlay elements, kills all document.addEventListener handlers (including the copy event listener), and destroys all page-world clipboard hooks that were injected via <script> tags.
The content script's isolated world JavaScript context survives — the variables, functions, and closures are still alive in memory. But every DOM reference is orphaned. The content script is running but has no eyes, no ears, and no way to know the page was just replaced.
How We Added Detection
A heartbeat monitor was added that runs every 800ms. On each tick, it checks whether a hidden <meta> marker element still exists in the DOM. If the marker is gone, the document was replaced. The recovery sequence:
1. Reset all UI state flags — bannerShown, overlayShown, bannerDismissed — so the warning system can re-inject.
2. Place a new heartbeat marker in the new document.
3. Re-attach the MutationObserver to the new document.documentElement.
4. Re-attach the copy event listener (extracted to a named function for re-registration).
5. Attempt to re-inject page-world clipboard hooks (may be blocked by CSP, but the copy listener covers that path).
6. Immediately re-scan the new page. The existing lure detection layers score the freshly injected ClickFix content and the warning banner re-appears.
The heartbeat mechanism detects DOM replacement regardless of how it happens — document.write(), innerHTML replacement on the root, or any other full-page substitution technique. The recovery is fully automated and fires within 800ms of the nuke.