Skip to content
Security Article

Beyond Code: How StegoAd Hid Malware in Fonts and Images

Microsoft's removal of 119 Edge extensions exposes how threat actors bypass static analysis by weaponizing ordinary asset files.

Emeka Okafor
Emeka Okafor
Security Editor · Jun 29, 2026 · 6 min read
Beyond Code: How StegoAd Hid Malware in Fonts and Images

The discovery and subsequent removal of 119 malicious extensions from the Microsoft Edge Add-ons store is more than a routine cleanup. It is a highly instructive case study in asset-based evasion. The campaign, dubbed StegoAd by Microsoft, managed to rack up a combined install base of up to 2.6 million users by hiding its malicious payloads inside ordinary image and font files.

Active since at least 2021, the threat actor behind StegoAd successfully bypassed automated store scanners for years. They did this not by writing complex, obfuscated JavaScript files that would trigger static analysis flags, but by keeping their executable code completely out of the code files. Instead, they weaponized the very assets that developers and security tools usually trust by default: PNGs, WebP images, and WOFF2 web fonts.

For developers building browser extensions or maintaining web applications, StegoAd is a stark reminder that security boundaries are only as strong as the parser processing your assets.

The Anatomy of Asset-Based Evasion

Steganography, the practice of concealing messages or code within non-secret data, is rarely seen at this scale in the browser extension ecosystem. The StegoAd actors evolved their techniques over several years to stay ahead of detection engines.

In the earliest variants, the actors appended malicious JavaScript directly after the IEND marker of a PNG icon. Because image renderers stop reading a PNG once they hit the IEND chunk, the icon rendered perfectly in the browser. However, a simple file-reading script within the extension could read the entire file buffer, locate the bytes following the marker, and extract the payload.

As static scanners began flagging anomalous data trailing after image footers, the actors adapted. They migrated to WebP images and eventually to WOFF2 font files. In the WOFF2 variants, the payload was tucked away inside unused glyph ranges that read as Asian text, or buried within font metadata tables. Because fonts are highly structured and compressed using Brotli, parsing them for anomalies requires specialized tools that standard extension linter pipelines simply do not run.

flowchart TD
    A[User Installs Extension] --> B{Evasion Checks Passed?}
    B -- No --> C[Dormant State / Decoy Behavior]
    B -- Yes --> D[Fetch Asset from C2 / Local Package]
    D --> E[Decode Payload via Case/Digit Swaps & Base64]
    E --> F[Verify Signature]
    F --> G[Execute Malicious JS / Steal Credentials]

To make detection even harder, the extensions employed strict execution gates. The malicious payload did not run immediately. It remained dormant for days, checked if browser DevTools were open to avoid analyst detection, and used a 10% execution gate on certain variants to limit the blast radius.

When the extension finally decided to wake up, it did not always load the payload locally. Some variants fetched a clean-looking image from a command-and-control (C2) server. The extension then decoded the payload through a sequence of case swaps, digit swaps, Base64 decoding, and XOR operations, verifying the payload against a cryptographic signature before execution. The C2 servers, which utilized Cloudflare Workers for proxying and GitHub Pages for beacons, only served the real payload to requests that passed specific fingerprinting and User-Agent checks. Anyone probing the server directly received a harmless decoy.

The Payload: From Ad Fraud to Credential Theft

While the visible symptom of the infection was ad fraud (hijacking affiliate commissions on sites like Amazon, eBay, and AliExpress, or injecting ads into search results) the underlying capabilities of the retrieved payloads were far more severe.

Once executed via a remote code execution backdoor, the payload harvested Google credentials and session cookies, intercepted second-factor codes at sign-in, and targeted WordPress administrator logins. The infrastructure was highly organized, using more than ten C2 domains with automatic failover and abusing seven Google Analytics tracking IDs to provide the operators with real-time telemetry dashboards.

Security researchers have linked the exfiltration domain mitarchive.info to DarkSpectre, a Chinese threat group previously associated with the ShadyPanda and GhostPoster extension campaigns. The tactical overlap is clear: GhostPoster also hid code inside extension icons, and both campaigns shared identical extension names, such as Ads Block Ultimate.

The Developer Angle: Defending the Asset Pipeline

For developers, the StegoAd campaign highlights a critical vulnerability in how we handle static assets. If your build pipeline treats images, fonts, and media files as passive data that does not need validation, you are leaving a door open.

To defend your extensions and web applications against asset-based steganography, you must implement strict asset validation and runtime security controls.

1. Sanitize Assets in the Build Pipeline

Never ship raw, unverified third-party assets. Your CI/CD pipeline should actively strip metadata and rebuild images and fonts. For example, you can use image processing libraries to re-encode images, which strips out any appended data or non-standard chunks.

Here is a simple Python script using the Pillow library to sanitize PNGs by stripping trailing data and non-standard chunks during a build:

from PIL import Image
import io

def sanitize_png(input_path, output_path):
    # Opening the image and saving it to a byte buffer forces re-encoding,
    # which naturally discards any data appended after the IEND chunk.
    with Image.open(input_path) as img:
        clean_buffer = io.BytesIO()
        img.save(clean_buffer, format="PNG")
        
        # Write the clean bytes to the output path
        with open(output_path, "wb") as f:
            f.write(clean_buffer.getvalue())

# Example usage in a build step
sanitize_png("src/assets/icon.png", "dist/assets/icon.png")

For fonts, use tools like pyftsubset (part of the fonttools suite) to rebuild WOFF2 files, ensuring only the required glyphs and standard tables are preserved, effectively stripping out malicious payloads hidden in custom metadata tables.

2. Enforce Strict Content Security Policies (CSP)

Manifest V3 was designed to mitigate remote code execution by banning the execution of external code (such as unsafe-eval or loading external scripts). However, if your extension's local background service worker contains a parser that decodes string data from an image and passes it to a dynamic execution context, you have bypassed the platform's protections.

Ensure your extension's CSP explicitly blocks dynamic code execution. Avoid using eval(), new Function(), or setTimeout with string arguments. If you must parse data from assets, use strict, schema-validated JSON parsers rather than executing raw strings.

3. Implement Subresource Integrity (SRI)

If your web application loads assets or fonts from a CDN, always use Subresource Integrity (SRI) hashes. This ensures that if a threat actor compromises the CDN or the C2 server and swaps a clean font or image with a payload-carrying version, the browser will refuse to load it.

<link rel="stylesheet" href="https://cdn.example.com/style.css" 
      integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8mC" 
      crossorigin="anonymous">

The Manifest V3 Illusion

The StegoAd campaign successfully migrated from Manifest V2 to Manifest V3, proving that platform-level migrations are not a silver bullet. While Manifest V3 makes it harder to fetch and run raw JavaScript files from external servers, it cannot easily stop an extension from fetching a WebP image, decoding a string locally, and using native extension APIs to manipulate tabs or exfiltrate data.

Security is not a state you achieve by upgrading your manifest version; it is an ongoing process of verifying every input, including the ones that look like pictures.

Sources & further reading

  1. Microsoft Removes 119 Edge Extensions That Hid Malware in Images and Fonts — thehackernews.com
Emeka Okafor
Written by
Emeka Okafor · Security Editor

Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.

Discussion 2

Join the discussion

Sign in or create an account to comment and vote.

Nina Petrova @night_owl_nina · 3 hours ago

steganography in fonts, that's a new one

Dmitri Sokolov @ai_doomer_dmitri · 5 hours ago

i'm concerned about the second-order effects here - if threat actors can hide malware in seemingly innocuous files like fonts and images, what's to stop them from targeting other types of files that are commonly used in development, like open-source libraries or frameworks?

Related Reading