Repository: iphelix/ida-sploiter
Branch: master
Commit: 845f0946d3d8
Files: 4
Total size: 181.7 KB
Directory structure:
gitextract_x8ih4kwe/
├── README.txt
├── idasploiter.py
├── idasploiter_ppc.py
└── idasploiter_x86.py
================================================
FILE CONTENTS
================================================
================================================
FILE: README.txt
================================================
Welcome to IDA Sploiter, an exploit development and vulnerability research
plugin for Hex-Ray's IDA Pro disassembler.
To install IDA Sploiter simply copy all the python files to IDA's plugins folder.
The plugin will be automatically loaded the next time you start IDA Pro.
IDA Sploiter currently supports the following architectures:
- x86/amd64
- PowerPC
You can find the latest IDA Sploiter version and documentation here:
http://thesprawl.org/projects/ida-sploiter/
Happy sploiting!
-Peter Kacherginsky <iphelix@thesprawl.org>
================================================
FILE: idasploiter.py
================================================
#!/usr/bin/env python
#
# IDA Sploiter is an exploit development and vulnerability research environment
# implemented as a plugin for Hex-Ray's IDA Pro disassembler.
IDASPLOITER_VERSION = "1.1"
# Copyright (C) 2014 Peter Kacherginsky
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Performance profiling
import cProfile
import pstats
# IDA libraries
import idaapi
import idautils
import idc
from idaapi import Form, Choose2, plugin_t
# Python libraries
import os
import binascii
import string
import textwrap
import copy
import csv
import itertools
import struct
from struct import pack, unpack
from ctypes import *
###############################################################################
# Data Tables and Structures
###############################################################################
# Initialize the list of supported processors.
SPLOITER_SUPPORTED_ARCHES = [
idaapi.PLFM_386,
idaapi.PLFM_PPC
]
###############################################################################
# 00-FF Single byte to unicode transforms
# NOTE: Some unicode characters can have two bytes
# http://www.phenoelit.org/stuff/Phenoelit20c3.pdf
# Ascii_to_Unicode_transforms
ASCII_TO_UNICODE_GENERAL = [
"\x00\x00", "\x00\x01", "\x00\x02", "\x00\x03", "\x00\x04", "\x00\x05",
"\x00\x06", "\x00\x07", "\x00\x08", "\x00\x09", "\x00\x0a", "\x00\x0b",
"\x00\x0c", "\x00\x0d", "\x00\x0e", "\x00\x0f", "\x00\x10", "\x00\x11",
"\x00\x12", "\x00\x13", "\x00\x14", "\x00\x15", "\x00\x16", "\x00\x17",
"\x00\x18", "\x00\x19", "\x00\x1a", "\x00\x1b", "\x00\x1c", "\x00\x1d",
"\x00\x1e", "\x00\x1f", "\x00\x20", "\x00\x21", "\x00\x22", "\x00\x23",
"\x00\x24", "\x00\x25", "\x00\x26", "\x00\x27", "\x00\x28", "\x00\x29",
"\x00\x2a", "\x00\x2b", "\x00\x2c", "\x00\x2d", "\x00\x2e", "\x00\x2f",
"\x00\x30", "\x00\x31", "\x00\x32", "\x00\x33", "\x00\x34", "\x00\x35",
"\x00\x36", "\x00\x37", "\x00\x38", "\x00\x39", "\x00\x3a", "\x00\x3b",
"\x00\x3c", "\x00\x3d", "\x00\x3e", "\x00\x3f", "\x00\x40", "\x00\x41",
"\x00\x42", "\x00\x43", "\x00\x44", "\x00\x45", "\x00\x46", "\x00\x47",
"\x00\x48", "\x00\x49", "\x00\x4a", "\x00\x4b", "\x00\x4c", "\x00\x4d",
"\x00\x4e", "\x00\x4f", "\x00\x50", "\x00\x51", "\x00\x52", "\x00\x53",
"\x00\x54", "\x00\x55", "\x00\x56", "\x00\x57", "\x00\x58", "\x00\x59",
"\x00\x5a", "\x00\x5b", "\x00\x5c", "\x00\x5d", "\x00\x5e", "\x00\x5f",
"\x00\x60", "\x00\x61", "\x00\x62", "\x00\x63", "\x00\x64", "\x00\x65",
"\x00\x66", "\x00\x67", "\x00\x68", "\x00\x69", "\x00\x6a", "\x00\x6b",
"\x00\x6c", "\x00\x6d", "\x00\x6e", "\x00\x6f", "\x00\x70", "\x00\x71",
"\x00\x72", "\x00\x73", "\x00\x74", "\x00\x75", "\x00\x76", "\x00\x77",
"\x00\x78", "\x00\x79", "\x00\x7a", "\x00\x7b", "\x00\x7c", "\x00\x7d",
"\x00\x7e", "\x00\x7f"
]
ASCII_TO_UNICODE_ANSI = ASCII_TO_UNICODE_GENERAL + [
"\x20\xac", "\x00\x81", "\x20\x1a", "\x01\x92", "\x20\x1e", "\x20\x26",
"\x20\x20", "\x20\x21", "\x02\xc6", "\x20\x30", "\x01\x60", "\x20\x39",
"\x01\x52", "\x00\x8d", "\x01\x7d", "\x00\x8f", "\x90\x00", "\x20\x18",
"\x20\x19", "\x20\x1c", "\x20\x1d", "\x20\x22", "\x20\x13", "\x20\x14",
"\x02\xdc", "\x21\x22", "\x01\x61", "\x3a\x20", "\x01\x53", "\x00\x9d",
"\x01\x7e", "\x01\x78", "\x00\xa0", "\x00\xa1", "\x00\xa2", "\x00\xa3",
"\x00\xa4", "\x00\xa5", "\x00\xa6", "\x00\xa7", "\x00\xa8", "\x00\xa9",
"\x00\xaa", "\x00\xab", "\x00\xac", "\x00\xad", "\x00\xae", "\x00\xaf",
"\x00\xb0", "\x00\xb1", "\x00\xb2", "\x00\xb3", "\x00\xb4", "\x00\xb5",
"\x00\xb6", "\x00\xb7", "\x00\xb8", "\x00\xb9", "\x00\xba", "\x00\xbb",
"\x00\xbc", "\x00\xbd", "\x00\xbe", "\x00\xbf", "\x00\xc0", "\x00\xc1",
"\x00\xc2", "\x00\xc3", "\x00\xc4", "\x00\xc5", "\x00\xc6", "\x00\xc7",
"\x00\xc8", "\x00\xc9", "\x00\xca", "\x00\xcb", "\x00\xcc", "\x00\xcd",
"\x00\xce", "\x00\xcf", "\x00\xd0", "\x00\xd1", "\x00\xd2", "\x00\xd3",
"\x00\xd4", "\x00\xd5", "\x00\xd6", "\x00\xd7", "\x00\xd8", "\x00\xd9",
"\x00\xda", "\x00\xdb", "\x00\xdc", "\x00\xdd", "\x00\xde", "\x00\xdf",
"\x00\xe0", "\x00\xe1", "\x00\xe2", "\x00\xe3", "\x00\xe4", "\x00\xe5",
"\x00\xe6", "\x00\xe7", "\x00\xe8", "\x00\xe9", "\x00\xea", "\x00\xeb",
"\x00\xec", "\x00\xed", "\x00\xee", "\x00\xef", "\x00\xf0", "\x00\xf1",
"\x00\xf2", "\x00\xf3", "\x00\xf4", "\x00\xf5", "\x00\xf6", "\x00\xf7",
"\x00\xf8", "\x00\xf9", "\x00\xfa", "\x00\xfb", "\x00\xfc", "\x00\xfd",
"\x00\xfe", "\x00\xff"
]
ASCII_TO_UNICODE_OEM = ASCII_TO_UNICODE_GENERAL + [
"\x00\xc7", "\x00\xfc", "\x00\xe9", "\x00\xe2", "\x00\xe4", "\x00\xe0",
"\x00\xe5", "\x00\xe7", "\x00\xea", "\x00\xeb", "\x00\xe8", "\x00\xef",
"\x00\xee", "\x00\xec", "\x00\xc4", "\x00\xc5", "\x00\xc9", "\x00\xe6",
"\x00\xc6", "\x00\xf4", "\x00\xf6", "\x00\xf2", "\x00\xfb", "\x00\xf9",
"\x00\xff", "\x00\xd6", "\x00\xdc", "\x00\xf8", "\x00\xa3", "\x00\xd8",
"\x00\xd7", "\x01\x92", "\x00\xe1", "\x00\xed", "\x00\xf3", "\x00\xfa",
"\x00\xf1", "\x00\xd1", "\x00\xaa", "\x00\xba", "\x00\xbf", "\x00\xae",
"\x00\xac", "\x00\xbd", "\x00\xbc", "\x00\xa1", "\x00\xab", "\x00\xbb",
"\x25\x91", "\x25\x92", "\x25\x93", "\x25\x02", "\x25\x24", "\x00\xc1",
"\x00\xc2", "\x00\xc0", "\x00\xa9", "\x25\x63", "\x25\x51", "\x25\x57",
"\x25\x5d", "\x00\xa2", "\x00\xa5", "\x25\x10", "\x25\x14", "\x25\x34",
"\x25\x2c", "\x25\x1c", "\x25\x00", "\x25\x3c", "\x00\xe3", "\x00\xc3",
"\x25\x5a", "\x25\x54", "\x25\x69", "\x25\x66", "\x25\x60", "\x25\x50",
"\x25\x6c", "\x00\xa4", "\x00\xf0", "\x00\xd0", "\x00\xca", "\x00\xcb",
"\x00\xc8", "\x01\x31", "\x00\xcd", "\x00\xce", "\x00\xcf", "\x25\x18",
"\x25\x0c", "\x25\x88", "\x25\x84", "\x00\xa6", "\x00\xcc", "\x25\x80",
"\x00\xd3", "\x00\xdf", "\x00\xd4", "\x00\xd2", "\x00\xf5", "\x00\xd5",
"\x00\xb5", "\x00\xfe", "\x00\xde", "\x00\xda", "\x00\xdb", "\x00\xd9",
"\x00\xfd", "\x00\xdd", "\x00\xaf", "\x00\xb4", "\x00\xad", "\x00\xb1",
"\x20\x17", "\x00\xbe", "\x00\xb6", "\x00\xa7", "\x00\xf7", "\x00\xb8",
"\x00\xb0", "\x00\xa8", "\x00\xb7", "\x00\xb9", "\x00\xb3", "\x00\xb2",
"\x25\xa0", "\x00\xa0"
]
ASCII_TO_UNICODE_UTF7 = ASCII_TO_UNICODE_GENERAL + [
"\xff\x80", "\xff\x81", "\xff\x82", "\xff\x83", "\xff\x84", "\xff\x85",
"\xff\x86", "\xff\x87", "\xff\x88", "\xff\x89", "\xff\x8a", "\xff\x8b",
"\xff\x8c", "\xff\x8d", "\xff\x8e", "\xff\x8f", "\xff\x90", "\xff\x91",
"\xff\x92", "\xff\x93", "\xff\x94", "\xff\x95", "\xff\x96", "\xff\x97",
"\xff\x98", "\xff\x99", "\xff\x9a", "\xff\x9b", "\xff\x9c", "\xff\x9d",
"\xff\x9e", "\xff\x9f", "\xff\xa0", "\xff\xa1", "\xff\xa2", "\xff\xa3",
"\xff\xa4", "\xff\xa5", "\xff\xa6", "\xff\xa7", "\xff\xa8", "\xff\xa9",
"\xff\xaa", "\xff\xab", "\xff\xac", "\xff\xad", "\xff\xae", "\xff\xaf",
"\xff\xb0", "\xff\xb1", "\xff\xb2", "\xff\xb3", "\xff\xb4", "\xff\xb5",
"\xff\xb6", "\xff\xb7", "\xff\xb8", "\xff\xb9", "\xff\xba", "\xff\xbb",
"\xff\xbc", "\xff\xbd", "\xff\xbe", "\xff\xbf", "\xff\xc0", "\xff\xc1",
"\xff\xc2", "\xff\xc3", "\xff\xc4", "\xff\xc5", "\xff\xc6", "\xff\xc7",
"\xff\xc8", "\xff\xc9", "\xff\xca", "\xff\xcb", "\xff\xcc", "\xff\xcd",
"\xff\xce", "\xff\xcf", "\xff\xd0", "\xff\xd1", "\xff\xd2", "\xff\xd3",
"\xff\xd4", "\xff\xd5", "\xff\xd6", "\xff\xd7", "\xff\xd8", "\xff\xd9",
"\xff\xda", "\xff\xdb", "\xff\xdc", "\xff\xdd", "\xff\xde", "\xff\xdf",
"\xff\xe0", "\xff\xe1", "\xff\xe2", "\xff\xe3", "\xff\xe4", "\xff\xe5",
"\xff\xe6", "\xff\xe7", "\xff\xe8", "\xff\xe9", "\xff\xea", "\xff\xeb",
"\xff\xec", "\xff\xed", "\xff\xee", "\xff\xef", "\xff\xf0", "\xff\xf1",
"\xff\xf2", "\xff\xf3", "\xff\xf4", "\xff\xf5", "\xff\xf6", "\xff\xf7",
"\xff\xf8", "\xff\xf9", "\xff\xfa", "\xff\xfb", "\xff\xfc", "\xff\xfd",
"\xff\xfe", "\xff\xff"
]
ASCII_TO_UNICODE_UTF8 = ASCII_TO_UNICODE_GENERAL + [
"\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", "\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\x01",
"\x00\x02", "\x00\x03", "\x00\x04", "\x00\x05", "\x00\x06", "\x00\x07",
"\x00\x08", "\x00\x09", "\x00\x0a", "\x00\x0b", "\x00\x0c", "\x00\x0d",
"\x00\x0e", "\x00\x0f", "\x00\x10", "\x00\x11", "\x00\x12", "\x00\x13",
"\x00\x14", "\x00\x15", "\x00\x16", "\x00\x17", "\x00\x18", "\x00\x19",
"\x00\x1a", "\x00\x1b", "\x00\x1c", "\x00\x1d", "\x00\x1e", "\x00\x1f",
"\x00\x00", "\x00\x01", "\x00\x02", "\x00\x03", "\x00\x04", "\x00\x05",
"\x00\x06", "\x00\x07", "\x00\x08", "\x00\x09", "\x00\x0a", "\x00\x0b",
"\x00\x0c", "\x00\x0d", "\x00\x0e", "\x00\x0f", "\x00\x00", "\x00\x01",
"\x00\x02", "\x00\x03", "\x00\x04", "\x00\x05", "\x00\x06", "\x00\x07",
"\x00\x08", "\x00\x09", "\x00\x0a", "\x00\x0b", "\x00\x0c", "\x00\x0d",
"\x00\x0e", "\x00\x0f"
]
ASCII_TO_UNICODE = [ASCII_TO_UNICODE_ANSI,
ASCII_TO_UNICODE_OEM,
ASCII_TO_UNICODE_UTF7,
ASCII_TO_UNICODE_UTF8]
###############################################################################
# Microsoft Portable Executable and Common Object File Format Specification
# Revision 8.3 - February 6, 2013
class IMAGE_DOS_HEADER(Structure):
_fields_ = [
("signature" , c_char * 2),
("lastsize" , c_short),
("nblocks" , c_short),
("nreloc" , c_short),
("hdrsize" , c_short),
("minalloc" , c_short),
("maxalloc" , c_short),
("ss" , c_short),
("sp" , c_short),
("checksum" , c_short),
("ip" , c_short),
("cs" , c_short),
("relocpos" , c_short),
("noverlay" , c_short),
("reserved1" , c_short * 4),
("oem_id" , c_short),
("reserved2" , c_short * 10),
("e_lfanew" , c_long)
]
class IMAGE_FILE_HEADER(Structure):
_fields_ = [
("Machine" , c_short),
("NumberOfSections" , c_short),
("TimeDateStamp" , c_long),
("PointerToSymbolTable" , c_long),
("NumberOfSymbols" , c_long),
("SizeOfOptionalHeader" , c_short),
("Characteristics" , c_short)
]
class IMAGE_DATA_DIRECTORY(Structure):
_fields_ = [
("VirtualAddress", c_long),
("Size", c_long)
]
class IMAGE_OPTIONAL_HEADER(Structure):
_fields_ = [
("signature" , c_short),
("MajorLinkerVersion" , c_byte),
("MinorLinkerVersion" , c_byte),
("SizeOfCode" , c_long),
("SizeOfInitializedData" , c_long),
("SizeOfUninitializedData" , c_long),
("AddressOfEntryPoint" , c_long),
("BaseOfCode" , c_long),
("BaseOfData" , c_long),
("ImageBase" , c_long),
("SectionAlignment" , c_long),
("FileAlignment" , c_long),
("MajorOSVersion" , c_short),
("MinorOSVersion" , c_short),
("MajorImageVersion" , c_short),
("MinorImageVersion" , c_short),
("MajorSubsystemVersion" , c_short),
("MinorSubsystemVersion" , c_short),
("Reserved" , c_long),
("SizeOfImage" , c_long),
("SizeOfHeaders" , c_long),
("Checksum" , c_long),
("Subsystem" , c_short),
("DLLCharacteristics" , c_short),
("SizeOfStackReserve" , c_long),
("SizeOfStackCommit" , c_long),
("SizeOfHeapReserve" , c_long),
("SizeOfHeapCommit" , c_long),
("LoaderFlags" , c_long),
("NumberOfRvaAndSizes" , c_long),
("DataDirectory" , IMAGE_DATA_DIRECTORY * 16)
]
class IMAGE_OPTIONAL_HEADER64(Structure):
_fields_ = [
("signature" , c_short),
("MajorLinkerVersion" , c_byte),
("MinorLinkerVersion" , c_byte),
("SizeOfCode" , c_long),
("SizeOfInitializedData" , c_long),
("SizeOfUninitializedData" , c_long),
("AddressOfEntryPoint" , c_long),
("BaseOfCode" , c_long),
("ImageBase" , c_longlong),
("SectionAlignment" , c_long),
("FileAlignment" , c_long),
("MajorOSVersion" , c_short),
("MinorOSVersion" , c_short),
("MajorImageVersion" , c_short),
("MinorImageVersion" , c_short),
("MajorSubsystemVersion" , c_short),
("MinorSubsystemVersion" , c_short),
("Reserved" , c_long),
("SizeOfImage" , c_long),
("SizeOfHeaders" , c_long),
("Checksum" , c_long),
("Subsystem" , c_short),
("DLLCharacteristics" , c_short),
("SizeOfStackReserve" , c_longlong),
("SizeOfStackCommit" , c_longlong),
("SizeOfHeapReserve" , c_longlong),
("SizeOfHeapCommit" , c_longlong),
("LoaderFlags" , c_long),
("NumberOfRvaAndSizes" , c_long),
("DataDirectory" , IMAGE_DATA_DIRECTORY * 16)
]
class IMAGE_LOAD_CONFIG_DIRECTORY(Structure):
_fields_ = [
("Size" , c_long),
("TimeDateStamp" , c_long),
("MajorVersion" , c_short),
("MinorVersion" , c_short),
("GlobalFlagsClear" , c_long),
("GlobalFlagsSet" , c_long),
("CriticalSectionDefaultTimeout" , c_long),
("DeCommitFreeBlockThreshold" , c_long),
("DeCommitTotalFreeThreshold" , c_long),
("LockPrefixTable" , c_long),
("MaximumAllocationSize" , c_long),
("VirtualMemoryThreshold" , c_long),
("ProcessHeapFlags" , c_long),
("ProcessAffinityMask" , c_long),
("CSDVersion" , c_short),
("Reserved1" , c_short),
("EditList" , c_long),
("SecurityCookie" , c_long),
("SEHandlerTable" , c_long),
("SEHandlerCount" , c_long)
]
class IMAGE_LOAD_CONFIG_DIRECTORY64(Structure):
_fields_ = [
("Size" , c_long),
("TimeDateStamp" , c_long),
("MajorVersion" , c_short),
("MinorVersion" , c_short),
("GlobalFlagsClear" , c_long),
("GlobalFlagsSet" , c_long),
("CriticalSectionDefaultTimeout" , c_long),
("DeCommitFreeBlockThreshold" , c_longlong),
("DeCommitTotalFreeThreshold" , c_longlong),
("LockPrefixTable" , c_longlong),
("MaximumAllocationSize" , c_longlong),
("VirtualMemoryThreshold" , c_longlong),
("ProcessHeapFlags" , c_long),
("ProcessAffinityMask" , c_long),
("CSDVersion" , c_short),
("Reserved1" , c_short),
("EditList" , c_longlong),
("SecurityCookie" , c_longlong),
("SEHandlerTable" , c_longlong),
("SEHandlerCount" , c_longlong),
]
def is_processor_supported():
# Check if the current processor is supported.
if idaapi.ph.id not in SPLOITER_SUPPORTED_ARCHES:
return False
else:
return True
def read_module_memory(addr, size):
# Determine if the debugger is running and loaded.
if idaapi.dbg_can_query() and idaapi.get_process_state() < 0:
return idaapi.dbg_read_memory(addr, size)
else:
return idaapi.get_many_bytes(addr, size)
###############################################################################
# Module Class - Manages module characteristics
class Module():
def __init__(self, name, size, base, rebase_to):
self.addr = base
# BUG: IDA's API does not always zero out the SWIG buffer.
# BUG: ntdll does not have path information.
if "\x00" in name:
name,junk = name.split("\x00",1)
self.file = os.path.basename(name)
self.path = os.path.dirname(name)
self.size = size
# Parse the module as a PE file
if idaapi.dbg_can_query() and idaapi.get_process_state() < 0:
pe = PE(self.addr)
else:
pe = None
self.NXCompat = "No"
self.ASLR = "No"
self.SafeSEH = "N/A"
self.GS = "Null"
if pe:
self.NXCompat = pe.isNXCompat()
self.ASLR = pe.isDynamicBase()
self.SafeSEH = pe.isSafeSEH()
self.GS = pe.isGS()
###############################################################################
# PE Module Parser
class PE():
# PE flags
IMAGE_FILE_MACHINE_I386 = 0x14c
MAGIC_PE32 = 0x10b
MAGIC_PE32_PLUS = 0x20b
IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040
IMAGE_DLL_CHARACTERISTICS_NX_COMPAT = 0x0100
IMAGE_DLLCHARACTERISTICS_NO_SEH = 0x0400
def __init__(self, base):
# PE headers
self.dos_header = None
self.file_header = None
self.optional_header = None
self.load_config_directory = None
self.arch64 = False
# Verify DOS header magic
magic_dos = read_module_memory( base, 0x2)
if magic_dos != "MZ":
print "[idasploiter] Invalid DOS header magic."
return None
# Parse the DOS header
self.dos_header = IMAGE_DOS_HEADER.from_buffer_copy(
read_module_memory(base, 0x40) )
# Get offset to PE header
base_pe = base + self.dos_header.e_lfanew
# Verify PE header magic
magic_pe = read_module_memory( base_pe, 0x4 )
if magic_pe != "PE\x00\x00":
print "[idasploiter] Invalid PE header magic: %x" % unpack("I",magic_pe)[0]
return None
# Parse the FILE header
# NOTE: IMAGE_FILE_HEADER size is 0x14
self.file_header = IMAGE_FILE_HEADER.from_buffer_copy(
read_module_memory( base_pe + 0x4, sizeof(IMAGE_FILE_HEADER) ) )
# Parse the OPTIONAL header
base_pe_optional = base_pe + 0x4 + 0x14
magic_pe_opt = read_module_memory ( base_pe_optional, 0x2)
magic_pe_opt = unpack("H", magic_pe_opt)[0]
# PE32 Optional Header
if magic_pe_opt == self.MAGIC_PE32:
self.arch64 = False
self.optional_header = IMAGE_OPTIONAL_HEADER.from_buffer_copy(
read_module_memory( base_pe_optional, self.file_header.SizeOfOptionalHeader ))
# PE32+ Optional Header
elif magic_pe_opt == self.MAGIC_PE32_PLUS:
self.arch64 = True
self.optional_header = IMAGE_OPTIONAL_HEADER64.from_buffer_copy(
read_module_memory( base_pe_optional, self.file_header.SizeOfOptionalHeader ))
# Invalid PE Header
else:
print "[idasploiter] Invalid IMAGE_OPTIONAL_HEADER magic: %x" % unpack("H",magic_pe_opt)[0]
return None
# Load Configuration Table
load_config_directory_rva = self.optional_header.DataDirectory[10].VirtualAddress
# Parse Load Configuration Table if present
if load_config_directory_rva:
# Read LOAD CONFIG DIRECTORY size before parsing it
load_config_directory_size = read_module_memory( base + load_config_directory_rva, 0x4)
load_config_directory_size = unpack("I",load_config_directory_size)[0]
# Parse LOAD CONFIG DIRECTORY based on PE32 format
if self.optional_header.signature == self.MAGIC_PE32:
self.load_config_directory = IMAGE_LOAD_CONFIG_DIRECTORY.from_buffer_copy( read_module_memory( base + load_config_directory_rva, load_config_directory_size ))
# Parse LOAD CONFIG DIRECTORY based on PE32+ format
elif self.optional_header.signature == self.MAGIC_PE32_PLUS:
self.load_config_directory = IMAGE_LOAD_CONFIG_DIRECTORY64.from_buffer_copy( read_module_memory( base + load_config_directory_rva, load_config_directory_size ))
# Invalid PE header
else:
print "[idasploiter] Invalid IMAGE_OPTIONAL_HEADER magic: %x" % unpack("H",magic)[0]
return None
def isDynamicBase(self):
if self.optional_header:
if self.optional_header.DLLCharacteristics & self.IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE:
return "Yes"
else:
return "No"
else:
return "Unk"
def isNXCompat(self):
if self.optional_header:
if self.optional_header.DLLCharacteristics & self.IMAGE_DLL_CHARACTERISTICS_NX_COMPAT:
return "Yes"
else:
return "No"
else:
return "Unk"
def isSafeSEH(self):
if self.file_header and self.optional_header:
# SafeSEH is only applicable to 32-bit Windows.
if self.file_header.Machine != self.IMAGE_FILE_MACHINE_I386:
return "N/A"
# NO_SEH flag indicates that the module does not use SEH and no handlers can be called in this image.
elif self.optional_header.DLLCharacteristics & self.IMAGE_DLLCHARACTERISTICS_NO_SEH:
return "No SEH"
# SafeSEH is enabled when there is SEHandlerTable entry in the LOAD_CONFIG_DIRECTORY
elif self.load_config_directory and self.load_config_directory.SEHandlerTable != 0:
return "Yes"
# Otherwise SafeSEH is not used
else:
return "No"
else:
return "Unk"
def isGS(self):
if self.file_header and self.optional_header:
# NOTE: PE32+ does not necessarily provide a pointer to the Security Cookie in the
# LoadConfigDirectory. The address appears to be still present in the binary.
# TODO: Need to do additional research into reliably detecting /GS in PE32+ files.
# PE32+ binaries with missing LoadConfigDirectory may still used security cookies.
if self.arch64 and not self.load_config_directory:
return "Unk"
if self.load_config_directory and self.load_config_directory.SecurityCookie != 0:
# Attempt to read the security cookie
# NOTE: The extra exception handler was added to account for cookie being stored in memory locations
# that are not accessible by the debugger (e.g. manually loading win32k.sys, security cookie
# address will point to kernel space which we can't read)
try:
# Confirm the Security Cookie is not zero
if self.arch64:
cookie = read_module_memory(self.load_config_directory.SecurityCookie, 8)
cookie = struct.unpack("<Q", cookie)[0]
else:
cookie = read_module_memory(self.load_config_directory.SecurityCookie, 4)
cookie = struct.unpack("<I", cookie)[0]
if cookie != 0:
return "Yes"
else:
return "Null"
except Exception, e:
return "Inv"
else:
return "No"
else:
return "Unk"
###############################################################################
# Pointer Class - Manager writable pointer characteristics
class Ptr():
def __init__(self, module, ptr_ea, ptr_offset, ptr_charset, call_ea, insn_disas):
self.module = module
self.ptr_ea = ptr_ea
self.ptr_offset = ptr_offset
self.ptr_charset = ptr_charset
self.call_ea = call_ea
self.insn_disas = insn_disas
self.name = idaapi.get_name(self.call_ea + self.ptr_offset, self.ptr_ea) or ""
self.call_name = idaapi.get_func_name(self.call_ea) or ""
self.p2p_ea = None
self.p2p_offset = None
self.p2p_charset = None
###############################################################################
# Function Pointer Search Engine
class FuncPtr():
def __init__(self,sploiter):
self.sploiter = sploiter
self.ptr_calls = list()
self.ptrs = list()
def search_pointers(self):
# To be defined by parent classes.
pass
###############################################################################
# Gadget Class - manages ROP gadget characteristics
class Gadget():
def __init__(self, instructions, pivot, operations, chg_registers, use_registers):
self.address = 0x0
self.module = ""
self.instructions = instructions
self.size = len(instructions)
self.pivot = pivot
self.operations = operations
self.chg_registers = chg_registers
self.use_registers = use_registers
self.ptr_charset = []
###############################################################################
# ROP Search Engine
class Rop():
def __init__(self, sploiter):
self.maxRopOffset = 40 # Maximum offset from the return instruction to look for gadgets. default: 40
self.maxRopSize = 6 # Maximum number of instructions to look for gadgets. default: 6
self.maxRetnImm = 64 # Maximum imm16 value in retn. default: 64
self.maxJopImm = 255 # Maximum jop [reg + IMM] value. default: 64
self.maxRops = 0 # Maximum number of ROP chains to find. default: 0 (unlimited)
self.debug = False
self.regnames = idaapi.ph_get_regnames()
self.sploiter = sploiter
self.retns = list()
self.gadgets = list()
# Decoded instruction cache
self.insn_cache = dict()
def get_o_reg_name(self, insn, n):
# To be defined by parent classes.
return None
def search_retns(self):
# To be defined by parent classes.
pass
def search_gadgets(self):
# To be defined by parent classes.
pass
# Attempt to build a gadget at the provided start address
# by verifying it properly terminates at the expected RETN.
def build_gadget(self, ea, ea_end):
# To be defined by parent classes.
return None
###############################################################
# Decode instruction
def decode_instruction(self, insn, ea, ea_end):
# To be defined by parent classes.
pass
###############################################################################
# Sploiter Engine
class Sploiter():
def __init__(self):
# Process modules list
self.modules = list()
self.rop = None
self.funcptr = None
# Default patterns
self.c4_list = string.punctuation
self.c3_list = string.uppercase
self.c2_list = string.lowercase
self.c1_list = string.digits
# Check if processor supports 64-bit addressing
if idaapi.ph.flag & idaapi.PR_USE64:
self.addr64 = True
self.addr_format = "%016X"
self.pack_format_be = ">Q"
self.pack_format_le = "<Q"
else:
self.addr64 = False
self.addr_format = "%08X"
self.pack_format_be = ">I"
self.pack_format_le = "<I"
def get_func_ptr_instance(self):
# To be implemented by the plugin.
pass
def get_rop_instance(self):
# To be implemented by the plugin.
pass
def get_ptr_charset(self, ea):
ptr_charset = []
# Flags. True until proven otherwise.
nonull = True
unicode = True
ascii = True
asciiprint = True
alphanum = True
alpha = True
numeric = True
ptr_bytes = pack(self.pack_format_be, ea)
for i,b in enumerate( ptr_bytes ):
b_int = ord(b)
if b in self.ptrBadChars:
return None
# Locate any null bytes
if nonull and b == "\x00":
nonull = False
# Unicode compatible address must have
# null bytes at even points in the address
if unicode and not i % 2:
if not ptr_bytes[i:i+2] in ASCII_TO_UNICODE[ self.unicodeTable ]:
unicode = False
# Find any non-ascii characters
if ascii and not b_int > 127:
# Find any non-ascii printable characters
if asciiprint and not (b_int < 32 or b_int > 126):
# Find any non-alphanumeric characters
if alphanum and (b in string.ascii_letters or b in string.digits):
# Find any non-numeric characters
if numeric and not b in string.digits:
numeric = False
# Find any non-letter characters
if alpha and not b in string.ascii_letters:
alpha = False
else:
alphanum = False
numeric = False
alpha = False
else:
asciiprint = False
alphanum = False
numeric = False
alpha = False
else:
ascii = False
asciiprint = False
alphanum = False
numeric = False
alpha = False
# NOTE: You can continue to filter for upper/lower here if necessary
if nonull: ptr_charset.append("nonull")
if unicode: ptr_charset.append("unicode")
if ascii:
ptr_charset.append("ascii")
if asciiprint:
ptr_charset.append("asciiprint")
if alphanum:
ptr_charset.append("alphanum")
if numeric: ptr_charset.append("numeric")
if alpha: ptr_charset.append("alpha")
return ptr_charset
def process_modules(self):
# Reset modules list
self.modules = list()
# Check if the debugger is current active, if not add one module entry for the main executable.
if idaapi.dbg_can_query() and idaapi.get_process_state() < 0:
for m in idautils.Modules():
module = Module(m.name, m.size, m.base, m.rebase_to)
self.modules.append(module)
else:
# Get the IDA info struct.
info = idaapi.get_inf_structure()
# NOTE: There is no universal way in IDA SDK to get the base address/size of a loaded file because
# some of the loaders do not fill out the module information structure when loading the input file.
# To get around this I hacked in information that is both dynamically pulled from the database and
# "good enough", by using the maxEA and minEA of the loaded file. Since we are only searching the
# segments of the input file and they will all fall within this range this should be sufficient even
# though the size and base address fields are not accurate.
#
# I will buy beer for anyone who can come up with a universal way to get the base address and size
# of the loaded input file regardless of format or loader module used to load it. You also can't reparse
# the original file.
# Fake the module information.
module = Module(idaapi.get_root_filename(), info.maxEA - info.minEA, info.minEA, 0) # name, size, base, rebase_to
self.modules.append(module)
def show_modules_view(self):
mod = ModuleView(self)
mod.show()
def process_rop(self, select_list = None):
# Initialize ROP gadget search engine
self.rop = self.get_rop_instance()
if self.rop is None:
return
# Prompt user for ROP search settings
f = RopForm(self, select_list)
ok = f.Execute()
if ok == 1:
# Configure ROP gadget search engine
# Get selected modules
self.rop.modules = [self.modules[i] for i in f.mod.GetEmbSelection()]
if len(self.rop.modules) > 0:
# Pointer filters
self.rop.ptrNonull = f.cPtrNonull.checked
self.rop.ptrUnicode = f.cPtrUnicode.checked
self.rop.ptrAscii = f.cPtrAscii.checked
self.rop.ptrAsciiPrint = f.cPtrAsciiPrint.checked
self.rop.ptrAlphaNum = f.cPtrAlphaNum.checked
self.rop.ptrAlpha = f.cPtrAlpha.checked
self.rop.ptrNum = f.cPtrNum.checked
# Filter bad characters
buf = f.strBadChars.value
buf = buf.replace(' ','') # remove spaces
buf = buf.replace('\\x','') # remove '\x' prefixes
buf = buf.replace('0x','') # remove '0x' prefixes
try:
buf = binascii.unhexlify(buf) # convert to bytes
self.ptrBadChars = buf
except Exception, e:
idaapi.warning("Invalid input: %s" % e)
self.ptrBadChars = ""
# Ascii_to_Unicode_transformation table
# BUG: DropdownControl does not work on IDA 6.5
self.unicodeTable = f.radUnicode.value
# ROP instruction filters
self.rop.ropBadMnems = [mnem.strip().lower() for mnem in f.strBadMnems.value.split(',')]
self.rop.ropAllowJcc = f.cRopAllowJcc.checked
self.rop.ropNoBadBytes = f.cRopNoBadBytes.checked
# Get ROP engine settings
self.rop.maxRopSize = f.intMaxRopSize.value
self.rop.maxRopOffset = f.intMaxRopOffset.value
self.rop.maxRops = f.intMaxRops.value
self.rop.maxRetnImm = f.intMaxRetnImm.value
# Gadget search values
self.rop.searchRop = f.cRopSearch.checked
self.rop.searchJop = f.cJopSearch.checked
# Search for returns and ROP gadgets
self.rop.search_retns()
self.rop.search_gadgets()
# Show the ROP gadgets view
ropView = RopView(self)
ropView.show()
else:
idaapi.warning("No modules selected.")
f.Free()
def process_funcptr(self, select_list = None):
# Prompt user for function pointer search settings
f = PtrForm(self, select_list)
ok = f.Execute()
if ok == 1:
# Initialize Function Pointer search engine
self.funcptr = self.get_func_ptr_instance()
if self.funcptr is None:
return
# Configure Function Pointer search engine
# Get selected modules
self.funcptr.modules = [self.modules[i] for i in f.mod.GetEmbSelection()]
if len(self.funcptr.modules) > 0:
# Pointer filters
self.funcptr.ptrNonull = f.cPtrNonull.checked
self.funcptr.ptrUnicode = f.cPtrUnicode.checked
self.funcptr.ptrAscii = f.cPtrAscii.checked
self.funcptr.ptrAsciiPrint = f.cPtrAsciiPrint.checked
self.funcptr.ptrAlphaNum = f.cPtrAlphaNum.checked
self.funcptr.ptrAlpha = f.cPtrAlpha.checked
self.funcptr.ptrNum = f.cPtrNum.checked
# Apply pointer filters to the funcptr or ptr-to-ptr
if f.rFilterP2P.selected:
self.funcptr.filterP2P = True
else:
self.funcptr.filterP2P = False
# Ascii_to_Unicode_transformation table
# BUG: DropdownControl does not work on IDA 6.5
self.unicodeTable = f.radUnicode.value
# Filter bad characters
buf = f.strBadChars.value
buf = buf.replace(' ','') # remove spaces
buf = buf.replace('\\x','') # remove '\x' prefixes
buf = buf.replace('0x','') # remove '0x' prefixes
try:
buf = binascii.unhexlify(buf) # convert to bytes
self.ptrBadChars = buf
except Exception, e:
idaapi.warning("Invalid input: %s" % e)
self.ptrBadChars = ""
# Offsets
self.funcptr.ptrOffset = int(f.intPtrOffset.value, 16)
self.funcptr.p2pOffset = int(f.intP2POffset.value, 16)
# Pointer search engine settings
self.funcptr.searchP2P = not f.rSearchPtrOnly.checked
self.funcptr.maxPtrs = f.intMaxPtrs.value
# Search for writable pointers
self.funcptr.search_pointers()
# Show the writable pointers view
ptrView = PtrView(self)
ptrView.show()
else:
idaapi.warning("No modules selected.")
f.Free()
def pattern_create(self):
f = PatternCreateForm(self)
ok = f.Execute()
if ok == 1:
pass
f.Free()
def pattern_detect(self, debugger=True):
f = PatternDetectForm(self, debugger)
ok = f.Execute()
if ok == 1:
pass
f.Free()
def process_compare(self, debugger=True):
f = CompareForm(self, debugger)
ok = f.Execute()
if ok == 1:
pass
f.Free()
###############################################################################
# Sploiter UI
###############################################################################
###############################################################################
# Module UI
###############################################################################
class ModuleView(Choose2):
"""
Chooser class to display security characteristics of loaded modules.
"""
def __init__(self, sploiter, embedded = False):
self.sploiter = sploiter
Choose2.__init__(self,
"Modules",
[ ["Address", 13 | Choose2.CHCOL_HEX],
["Name", 10 | Choose2.CHCOL_PLAIN],
["Size", 10 | Choose2.CHCOL_HEX],
["SafeSEH", 6 | Choose2.CHCOL_PLAIN],
["ASLR", 6 | Choose2.CHCOL_PLAIN],
["DEP", 6 | Choose2.CHCOL_PLAIN],
["Canary", 6 | Choose2.CHCOL_PLAIN],
["Path", 40 | Choose2.CHCOL_PLAIN],
],
flags = Choose2.CH_MULTI, # Select multiple modules
embedded=embedded)
self.icon = 150
# Items for display
self.items = list()
# Initialize/Refresh the view
self.refreshitems()
# Selected items
self.select_list = list()
# Command callbacks
self.cmd_load_module = None
self.cmd_search_gadgets = None
self.cmd_search_pointers = None
def show(self):
# Attempt to open the view
if self.Show() < 0: return False
# Add extra context menu commands
# NOTE: Make sure you check for duplicates
if self.cmd_load_module == None:
self.cmd_load_module = self.AddCommand("Load module...", flags = idaapi.CHOOSER_POPUP_MENU, icon=135)
if self.cmd_search_gadgets == None:
self.cmd_search_gadgets = self.AddCommand("Search gadgets...", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=182 )
if self.cmd_search_pointers == None and self.sploiter.is_func_ptr_supported() == True:
self.cmd_search_pointers = self.AddCommand("Search function pointers...", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=143 )
return True
def refreshitems(self):
self.items = list()
for m in self.sploiter.modules:
self.items.append([ self.sploiter.addr_format % m.addr,
m.file,
"%08X" % m.size,
m.SafeSEH, m.ASLR,
m.NXCompat,
m.GS,
m.path])
def OnCommand(self, n, cmd_id):
# Search ROP gadgets
if cmd_id == self.cmd_search_gadgets:
# Empty selection
if n == -1:
# Initialize ROP gadget form with empty selection
self.sploiter.process_rop(select_list = self.select_list)
# Selection start
elif n == -2:
self.select_list = list()
# Selection end
elif n == -3:
# Initialize ROP gadget form with user selection
self.sploiter.process_rop(select_list = self.select_list)
self.select_list = list()
# Selection number
else:
self.select_list.append(n)
# Search function pointers
elif cmd_id == self.cmd_search_pointers:
# Empty selection
if n == -1:
# Initialize function pointers form with empty selection
self.sploiter.process_funcptr(select_list = self.select_list)
# Selection start
elif n == -2:
self.select_list = list()
# Selection end
elif n == -3:
# Initialize function pointers form with user selection
self.sploiter.process_funcptr(select_list = self.select_list)
self.select_list = list()
# Selection number
else:
self.select_list.append(n)
elif cmd_id == self.cmd_load_module:
module_name = idaapi.askfile_c(0, "*.*", "Please select module to load")
if module_name:
print "[idasploiter] Loading module: %s" % module_name
loadlib = idaapi.Appcall.proto("kernel32_LoadLibraryA", "int __stdcall loadlib(const char *fn);")
hmod = loadlib(module_name)
if hmod:
print "[idasploiter] Finished loading module: %s" % module_name
else:
print "[idasploiter] Could not load: %s" % module_name
self.refreshitems()
return 1
def OnSelectLine(self, n):
pass
def OnGetLine(self, n):
return self.items[n]
def OnGetIcon(self, n):
if not len(self.items) > 0:
return -1
m = self.sploiter.modules[n]
if m.SafeSEH != "No" and m.ASLR == "Yes" and m.NXCompat == "Yes" and m.GS != "No":
return 61
elif m.ASLR == "Yes":
return 60
else:
return 59
def OnClose(self):
self.cmd_search_gadgets = None
self.cmd_search_pointers = None
def OnGetSize(self):
return len(self.items)
def OnRefresh(self, n):
self.refreshitems()
return n
def OnActivate(self):
self.refreshitems()
###############################################################################
# ROP UI
###############################################################################
###############################################################################
# ROP/JOP/COP Form
class RopForm(Form):
def __init__(self, sploiter, select_list = None):
self.sploiter = sploiter
self.select_list = select_list
self.mod = ModuleView(self.sploiter, embedded=True)
Form.__init__(self,
r"""BUTTON YES* Search
Search ROP gadgets
{FormChangeCb}<Modules:{cEChooser}>
Pointer Charset: Search Settings:
<nonull:{cPtrNonull}> <Bad Chars :{strBadChars}>
<unicode:{cPtrUnicode}> Unicode Table <ANSI:{rUnicodeANSI}><OEM:{rUnicodeOEM}><UTF7:{rUnicodeUTF7}><UTF8:{rUnicodeUTF8}>{radUnicode}>
<ascii:{cPtrAscii}> <Bad Instructions :{strBadMnems}>
<asciiprint:{cPtrAsciiPrint}> <Max gadget size :{intMaxRopSize}>
<alphanum:{cPtrAlphaNum}> <Max gadget offset:{intMaxRopOffset}>
<alpha:{cPtrAlpha}> <Max RETN imm16 :{intMaxRetnImm}>
<numeric:{cPtrNum}>{ptrGroup}> <Max JOP imm8/32 :{intMaxJopImm}>
<Max gadgets :{intMaxRops}>
Other settings <Allow conditional jumps:{cRopAllowJcc}>
<Do not allow bad bytes:{cRopNoBadBytes}>
<Search for ROP gadgets:{cRopSearch}>
<Search for JOP gadgets:{cJopSearch}>{ropGroup}>
""", {
'cEChooser' : Form.EmbeddedChooserControl(self.mod, swidth=131),
'ptrGroup' : Form.ChkGroupControl(("cPtrNonull", "cPtrAscii", "cPtrAsciiPrint", "cPtrUnicode",'cPtrAlphaNum','cPtrAlpha','cPtrNum')),
'ropGroup' : Form.ChkGroupControl(('cRopAllowJcc','cRopNoBadBytes','cRopSearch','cJopSearch')),
'intMaxRopSize' : Form.NumericInput(swidth=4,tp=Form.FT_DEC,value=self.sploiter.rop.maxRopSize),
'intMaxRopOffset' : Form.NumericInput(swidth=4,tp=Form.FT_DEC,value=self.sploiter.rop.maxRopOffset),
'intMaxRops' : Form.NumericInput(swidth=4,tp=Form.FT_DEC,value=self.sploiter.rop.maxRops),
'intMaxRetnImm' : Form.NumericInput(swidth=4,tp=Form.FT_HEX,value=self.sploiter.rop.maxRetnImm),
'intMaxJopImm' : Form.NumericInput(swidth=4,tp=Form.FT_HEX,value=self.sploiter.rop.maxJopImm),
'strBadChars' : Form.StringInput(swidth=70,tp=Form.FT_ASCII),
'radUnicode' : Form.RadGroupControl(("rUnicodeANSI","rUnicodeOEM","rUnicodeUTF7","rUnicodeUTF8")),
'strBadMnems' : Form.StringInput(swidth=80,tp=Form.FT_ASCII,value=self.sploiter.bad_instructions),
'FormChangeCb' : Form.FormChangeCb(self.OnFormChange),
})
self.Compile()
def OnFormChange(self, fid):
# Form initialization
if fid == -1:
self.SetFocusedField(self.cEChooser)
# Preselect non-ASLR modules on startup if none were already specified
if self.select_list == None:
self.select_list = list()
for i, m in enumerate(self.sploiter.modules):
if m.ASLR == "No":
self.select_list.append(i)
self.SetControlValue(self.cEChooser, self.select_list)
# Enable both ROP and JOP search by default
self.SetControlValue(self.cRopSearch, True)
self.SetControlValue(self.cJopSearch, True)
# Skip bad instructions by default
self.SetControlValue(self.cRopNoBadBytes, True)
# Form OK pressed
elif fid == -2:
pass
return 1
###############################################################################
# ROP Viewer
class RopView(Choose2):
"""
Chooser class to display security characteristics of loaded modules.
"""
def __init__(self, sploiter):
self.sploiter = sploiter
Choose2.__init__(self,
"ROP gadgets",
[ ["Address", 13 | Choose2.CHCOL_HEX],
["Gadget", 30 | Choose2.CHCOL_PLAIN],
["Module", 10 | Choose2.CHCOL_PLAIN],
["Size", 3 | Choose2.CHCOL_DEC],
["Pivot", 4 | Choose2.CHCOL_DEC],
["Operations", 12 | Choose2.CHCOL_PLAIN],
["Changed Registers", 12 | Choose2.CHCOL_PLAIN],
["Used Registers", 12 | Choose2.CHCOL_PLAIN],
["Charset", 12 | Choose2.CHCOL_PLAIN],
],
flags = Choose2.CH_MULTI)
self.icon = 182
# Items for display
self.items = []
# Initialize/Refresh the view
self.refreshitems()
# Selected items
self.gadget_chain = list()
# Command callbacks
self.cmd_chain_add = None
self.cmd_chain_build = None
self.cmd_chain_clear = None
self.cmd_export_csv = None
def show(self):
# Attempt to open the view
if self.Show() < 0: return False
if self.cmd_chain_add == None:
self.cmd_chain_add = self.AddCommand("Add to chain", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=50)
if self.cmd_chain_build == None:
self.cmd_chain_build = self.AddCommand("Build chain", flags = idaapi.CHOOSER_POPUP_MENU, icon=156)
if self.cmd_chain_clear == None:
self.cmd_chain_clear = self.AddCommand("Clear chain", flags = idaapi.CHOOSER_POPUP_MENU, icon=32)
if self.cmd_export_csv == None:
self.cmd_export_csv = self.AddCommand("Export as csv...", flags = idaapi.CHOOSER_POPUP_MENU, icon=40)
return True
def refreshitems(self):
self.items = []
for g in self.sploiter.rop.gadgets:
self.items.append([ self.sploiter.addr_format % g.address,
" # ".join(g.instructions),
g.module,
"%d" % g.size,
"%d" % g.pivot,
", ".join(g.operations),
", ".join(g.chg_registers),
", ".join(g.use_registers),
", ".join(g.ptr_charset)
])
def OnCommand(self, n, cmd_id):
# Build ROP chain
if cmd_id == self.cmd_chain_build:
# Display ROP chain builder form
f = RopChainForm(self)
ok = f.Execute()
if ok == 1:
pass
f.Free()
# Clear ROP chain
elif cmd_id == self.cmd_chain_clear:
self.gadget_chain = list()
# Add gadget to ROP chain
elif cmd_id == self.cmd_chain_add:
if n >= 0:
self.gadget_chain.append( self.sploiter.rop.gadgets[n] )
# Export CSV
elif cmd_id == self.cmd_export_csv:
file_name = idaapi.askfile_c(1, "*.csv", "Please enter CSV file name")
if file_name:
print "[idasploiter] Exporting gadgets to %s" % file_name
with open(file_name, 'wb') as csvfile:
csvwriter = csv.writer(csvfile, delimiter=',',
quotechar='"', quoting=csv.QUOTE_MINIMAL)
csvwriter.writerow(["Address","Gadget","Module","Size","Pivot","Operations","Changed Registers","Used Registers","Charset"])
for item in self.items:
csvwriter.writerow(item)
return 1
def OnSelectLine(self, n):
idaapi.jumpto( self.sploiter.rop.gadgets[n].address )
def OnGetLine(self, n):
return self.items[n]
def OnClose(self):
self.cmd_chain_add = None
self.cmd_chain_build = None
self.cmd_chain_clear = None
self.cmd_export_csv = None
def OnGetSize(self):
return len(self.items)
def OnRefresh(self, n):
self.refreshitems()
return n
def OnActivate(self):
self.refreshitems()
###############################################################################
# ROP Chain Builder UI
###############################################################################
###############################################################################
# ROP Chain Viewer
class RopChainView(Choose2):
"""
Chooser class to display security characteristics of loaded modules.
"""
def __init__(self, ropview):
self.ropview = ropview
Choose2.__init__(self,
"ROP gadgets",
[ ["Address", 13 | Choose2.CHCOL_HEX],
["Gadget", 30 | Choose2.CHCOL_PLAIN],
["Module", 10 | Choose2.CHCOL_PLAIN],
["Size", 3 | Choose2.CHCOL_DEC],
["Pivot", 4 | Choose2.CHCOL_DEC],
["Operations", 12 | Choose2.CHCOL_PLAIN],
["Changed Registers", 12 | Choose2.CHCOL_PLAIN],
["Used Registers", 12 | Choose2.CHCOL_PLAIN],
["Charset", 12 | Choose2.CHCOL_PLAIN],
],
flags = Choose2.CH_MULTI,
embedded = True)
self.icon = 182
# Items for display and corresponding data
self.items = []
# Selected items
self.select_list = list()
# Initialize/Refresh the view
self.refreshitems()
def refreshitems(self):
self.items = []
for g in self.ropview.gadget_chain:
self.items.append([ self.ropview.sploiter.addr_format % g.address,
" # ".join(g.instructions),
g.module,
"%d" % g.size if g.size else "",
"%d" % g.pivot if g.pivot else "",
", ".join(g.operations),
", ".join(g.chg_registers),
", ".join(g.use_registers),
", ".join(g.ptr_charset)
])
return
def OnCommand(self, n, cmd_id):
if cmd_id == self.cmd_move_up:
if n > 0:
self.ropview.gadget_chain.insert(n-1, self.ropview.gadget_chain.pop(n))
# NOTE: Moving items down should be done in one go in reverse
# to avoid self.items from desyncing from gadget_chain list.
elif cmd_id == self.cmd_move_down:
# Empty selection
if n == -1:
pass
# Selection start
elif n == -2:
self.select_list = list()
# Selection end
elif n == -3:
for i in sorted(self.select_list, reverse=True):
self.ropview.gadget_chain.insert(i+1, self.ropview.gadget_chain.pop(i))
self.select_list = list()
else:
self.select_list.append(n)
# NOTE: Duplicating items should be done in one go in reverse
# to avoid self.items desyncing from gadget_chain list.
elif cmd_id == self.cmd_duplicate:
# Empty selection
if n == -1:
pass
# Selection start
elif n == -2:
self.select_list = list()
# Selection end
elif n == -3:
# Duplicate selection after the last index
last_i = self.select_list[-1]
# Duplicate selected items in reverse
for i in sorted(self.select_list, reverse=True):
if last_i == len(self.ropview.gadget_chain) - 1:
self.ropview.gadget_chain.append(self.ropview.gadget_chain[i])
else:
self.ropview.gadget_chain.insert(last_i + 1, self.ropview.gadget_chain[i])
self.select_list = list()
else:
self.select_list.append(n)
# NOTE: Deleting items should be done in one go in reverse
# to avoid self.items desyncing from gadget_chain list.
elif cmd_id == self.cmd_delete:
# Empty selection
if n == -1:
pass
# Selection start
elif n == -2:
self.select_list = list()
# Selection end
elif n == -3:
for i in sorted(self.select_list, reverse=True):
del self.ropview.gadget_chain[i]
self.select_list = list()
else:
self.select_list.append(n)
return 1
def OnSelectLine(self, n):
pass
def OnGetLine(self, n):
return self.items[n]
def OnClose(self):
pass
def OnGetSize(self):
return len(self.items)
def OnRefresh(self, n):
self.refreshitems()
return n
def OnRefreshed(self):
self.refreshitems()
return len(self.items)
def OnActivate(self):
self.refreshitems()
###############################################################################
# ROPChain Form
class RopChainForm(Form):
def __init__(self, ropview):
self.ropview = ropview
self.ropchainview = RopChainView(self.ropview)
Form.__init__(self,
r"""BUTTON YES* NONE
BUTTON CANCEL NONE
ROP Chain Builder
{FormChangeCb}<ROP gadgets:{cEChooser}>
<:{strAddr}><:{strComment}><Insert:{iButtonInsert}>
<:{strRopChain}>
Formats<Plain:{rFmtPlain}><Python:{rFmtPython}><Ruby:{rFmtRuby}><Perl:{rFmtPerl}><JavaScript:{rFmtJS}>{rGroup}>
{spacer}<Generate:{iButtonGenerate}>
""", {
'spacer' : Form.StringLabel(" "),
'cEChooser' : Form.EmbeddedChooserControl(self.ropchainview, swidth=125),
'strAddr' : Form.StringInput(value="0x4141414141414141" if self.ropview.sploiter.addr64 else "0x41414141", swidth=10),
'strComment' : Form.StringInput(value="padding", swidth=20),
'iButtonInsert' : Form.ButtonInput(self.OnButtonInsert, swidth=5),
'strRopChain' : Form.MultiLineTextControl(text="", swidth=125, flags = Form.MultiLineTextControl.TXTF_FIXEDFONT),
'rGroup' : Form.RadGroupControl(("rFmtPlain","rFmtPython", "rFmtRuby", "rFmtPerl", "rFmtJS") ),
'iButtonGenerate' : Form.ButtonInput(self.OnButtonGenerate, swidth=5),
'FormChangeCb' : Form.FormChangeCb(self.OnFormChange),
})
self.Compile()
def OnFormChange(self, fid):
# Form initialization
if fid == -1:
self.SetFocusedField(self.cEChooser)
# Add commands to embedded view
self.ropchainview.cmd_move_up = self.cEChooser.AddCommand("Move Up", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION | idaapi.CHOOSER_HOTKEY, icon=86)
self.ropchainview.cmd_move_down = self.cEChooser.AddCommand("Move Down", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=85)
self.ropchainview.cmd_duplicate = self.cEChooser.AddCommand("Duplicate", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=111)
self.ropchainview.cmd_delete = self.cEChooser.AddCommand("Delete", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=112)
# Form OK pressed
elif fid == -2:
pass
return 1
def OnButtonInsert(self, code=0):
# Get user input
address = self.GetControlValue(self.strAddr)
address = int(address,16)
comment = self.GetControlValue(self.strComment)
# Create a dummy gadget
gadget = Gadget(instructions = [comment,], pivot = 0, operations = [], chg_registers = [], use_registers = [])
gadget.address = address
gadget.size = None
gadget.pivot = None
# Insert dummy gadget at selected positions
select_list = self.GetControlValue(self.cEChooser)
if select_list:
for i in sorted(select_list,reverse=True):
self.ropview.gadget_chain.insert(i + 1, gadget)
else:
self.ropview.gadget_chain.append(gadget)
# Update the view
self.RefreshField(self.cEChooser)
# TODO: Generate relative addresses to a dynamically determined module base
# (e.g. ROP chain built based on a leaked or calculated base address)
def OnButtonGenerate(self, code=0):
ropchain = ""
if self.ropview.sploiter.addr64:
addr_format = "%016X"
pack_format_python = "<Q"
pack_format_ruby = "Q<"
pack_format_perl = "Q<"
pack_words_js = 4
pack_bytes_c = 8
else:
addr_format = "%08X"
pack_format_python = "<I"
pack_format_ruby = "V"
pack_format_perl = "V"
pack_words_js = 2
pack_bytes_c = 4
# Plain output
if self.GetControlValue(self.rGroup) == 0:
ropchain += "// Generated by IDA Sploiter\n"
for g in self.ropview.gadget_chain:
bytes = ["\\x%02x" % b for b in struct.unpack("B"*pack_bytes_c, struct.pack(pack_format_python, g.address))]
ropchain += "\"%s\" // %s # %s\n" % (
''.join(bytes),
addr_format % g.address,
" # ".join(g.instructions))
# Python
elif self.GetControlValue(self.rGroup) == 1:
ropchain += "# Generated by IDA Sploiter\n"
for i, g in enumerate(self.ropview.gadget_chain):
ropchain += "rop_chain %s= struct.pack('%s', 0x%s) # %s\n" % (
'+' if i > 0 else ' ',
pack_format_python,
addr_format % g.address,
" # ".join(g.instructions) )
# Ruby
elif self.GetControlValue(self.rGroup) == 2:
ropchain += "# Generated by IDA Sploiter\n"
for i, g in enumerate(self.ropview.gadget_chain):
ropchain += "rop_chain %s [0x%s].pack('%s') # %s\n" % (
'<<' if i > 0 else ' =',
addr_format % g.address,
pack_format_ruby,
" # ".join(g.instructions) )
# Perl
elif self.GetControlValue(self.rGroup) == 3:
ropchain += "# Generated by IDA Sploiter\n"
for i, g in enumerate(self.ropview.gadget_chain):
ropchain += "%s$rop_chain %s= pack('%s', 0x%s); # %s\n" % (
'my ' if i == 0 else '',
'.' if i > 0 else ' ',
pack_format_perl,
addr_format % g.address,
" # ".join(g.instructions) )
# JavaScript
elif self.GetControlValue(self.rGroup) == 4:
ropchain += "// Generated by IDA Sploiter\n"
for i, g in enumerate(self.ropview.gadget_chain):
words_js = ["%%u%04x" % w for w in struct.unpack("H"*pack_words_js, struct.pack(pack_format_python, g.address))]
ropchain += "rop_chain %s= unescape(\"%s\"); // %s # %s\n" % (
'+' if i > 0 else ' ',
''.join(words_js),
addr_format % g.address,
" # ".join(g.instructions) )
strRopChain = idaapi.textctrl_info_t(text=ropchain, flags = Form.MultiLineTextControl.TXTF_FIXEDFONT)
self.SetControlValue(self.strRopChain, strRopChain)
###############################################################################
# Function Pointer UI
###############################################################################
###############################################################################
# Function Pointer Form
class PtrForm(Form):
def __init__(self, sploiter, select_list = None):
self.sploiter = sploiter
self.select_list = select_list
self.mod = ModuleView(self.sploiter, embedded=True)
Form.__init__(self,
r"""BUTTON YES* Search
Search writable function pointers
{FormChangeCb}<Modules:{cEChooser}>
Pointer Charset: Search Settings:
<nonull:{cPtrNonull}> <Bad chars :{strBadChars}>
<unicode:{cPtrUnicode}> Unicode Table <ANSI:{rUnicodeANSI}><OEM:{rUnicodeOEM}><UTF7:{rUnicodeUTF7}><UTF8:{rUnicodeUTF8}>{radUnicode}>
<ascii:{cPtrAscii}> <PTR Offset :{intPtrOffset}>
<asciiprint:{cPtrAsciiPrint}> <P2P Offset :{intP2POffset}>
<alphanum:{cPtrAlphaNum}> <Max Pointers :{intMaxPtrs}>
<alpha:{cPtrAlpha}> Filter <Function Pointers:{rFilterPtr}><Pointers-to-Pointers:{rFilterP2P}>{rGroup}>
<numeric:{cPtrNum}>{ptrGroup}> Other settings<Do not search for pointers-to-pointers:{rSearchPtrOnly}>{rSearchGroup}>
""", {
'cEChooser' : Form.EmbeddedChooserControl(self.mod, swidth=131),
'ptrGroup' : Form.ChkGroupControl(("cPtrNonull", "cPtrAscii", "cPtrAsciiPrint", "cPtrUnicode",'cPtrAlphaNum','cPtrAlpha','cPtrNum')),
'rGroup' : Form.RadGroupControl(("rFilterPtr","rFilterP2P") ),
'rSearchGroup' : Form.ChkGroupControl(("rSearchPtrOnly",) ),
'strBadChars' : Form.StringInput(swidth=70,tp=Form.FT_ASCII),
'radUnicode' : Form.RadGroupControl(("rUnicodeANSI","rUnicodeOEM","rUnicodeUTF7","rUnicodeUTF8")),
'intPtrOffset' : Form.StringInput(tp=Form.FT_ASCII,value="0x0", swidth=10),
'intP2POffset' : Form.StringInput(tp=Form.FT_ASCII,value="0x0", swidth=10),
'intMaxPtrs' : Form.NumericInput(swidth=4,tp=Form.FT_DEC,value=0),
'FormChangeCb' : Form.FormChangeCb(self.OnFormChange),
})
self.Compile()
def OnFormChange(self, fid):
# Form initialization
if fid == -1:
self.SetFocusedField(self.cEChooser)
# Preselect non-ASLR modules on startup if none were already specified
if self.select_list == None:
self.select_list = list()
for i, m in enumerate(self.sploiter.modules):
if m.ASLR == "No":
self.select_list.append(i)
self.SetControlValue(self.cEChooser, self.select_list)
# Form OK pressed
elif fid == -2:
pass
return 1
###############################################################################
# Function Pointer Viewer
class PtrView(Choose2):
"""
Chooser class to display writable function pointers.
"""
def __init__(self, sploiter):
self.sploiter = sploiter
Choose2.__init__(self,
"Writeable function pointers",
[ ["Pointer", 13 | Choose2.CHCOL_HEX],
["Offset", 4 | Choose2.CHCOL_HEX],
["Name", 10 | Choose2.CHCOL_PLAIN],
["Module", 10 | Choose2.CHCOL_PLAIN],
["Charset", 12 | Choose2.CHCOL_PLAIN],
["Caller Address", 13 | Choose2.CHCOL_HEX],
["Caller Name", 10 | Choose2.CHCOL_PLAIN],
["Instruction", 15 | Choose2.CHCOL_PLAIN],
["Ptr-to-Ptr", 13 | Choose2.CHCOL_PLAIN],
["Ptr-to-Ptr Offset", 4 | Choose2.CHCOL_HEX],
["Ptr-to-Ptr Charset", 12 | Choose2.CHCOL_PLAIN],
],
flags = Choose2.CH_MULTI_EDIT)
self.icon = 143
# Items for display and corresponding data
# NOTE: Could become desynchronized, so to avoid this
# refresh the view after each change.
self.items = []
# Initialize/Refresh the view
self.refreshitems()
# Selected items
self.select_caller_breakpoint_list = list()
# Command callbacks
self.cmd_select_caller_breakpoint = None
self.cmd_jump_ptr = None
self.cmd_jump_p2p = None
self.cmd_jump_caller = None
self.cmd_export_csv = None
def show(self):
# Attempt to open the view
if self.Show() < 0: return False
# Add extra context menu commands
# NOTE: Make sure you check for duplicates
if self.cmd_select_caller_breakpoint == None:
self.cmd_select_caller_breakpoint = self.AddCommand("Add breakpoint", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=120)
if self.cmd_jump_ptr == None:
self.cmd_jump_ptr = self.AddCommand("Jump to pointer", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=141)
if self.cmd_jump_p2p == None:
self.cmd_jump_p2p = self.AddCommand("Jump to pointer-to-pointer", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=142)
if self.cmd_jump_caller == None:
self.cmd_jump_caller = self.AddCommand("Jump to caller", flags = idaapi.CHOOSER_POPUP_MENU | idaapi.CHOOSER_MULTI_SELECTION, icon=143)
if self.cmd_export_csv == None:
self.cmd_export_csv = self.AddCommand("Export as csv...", flags = idaapi.CHOOSER_POPUP_MENU, icon=40)
return True
def refreshitems(self):
self.items = []
for ptr in self.sploiter.funcptr.ptrs:
self.items.append([ self.sploiter.addr_format % ptr.ptr_ea,
hex(ptr.ptr_offset),
ptr.name,
ptr.module,
", ".join(ptr.ptr_charset),
self.sploiter.addr_format % ptr.call_ea,
ptr.call_name,
ptr.insn_disas,
self.sploiter.addr_format % ptr.p2p_ea if ptr.p2p_ea else "",
hex(ptr.p2p_offset) if ptr.p2p_ea else "",
", ".join(ptr.p2p_charset) if ptr.p2p_ea else "",
])
def OnCommand(self, n, cmd_id):
if cmd_id == self.cmd_select_caller_breakpoint:
# Empty selection
if n == -1:
pass
# Selection start
elif n == -2:
self.select_caller_breakpoint_list = list()
# Selection end
elif n == -3:
# Unique set of caller addresses
eas = set()
for i in self.select_caller_breakpoint_list:
eas.add( self.sploiter.funcptr.ptrs[i].call_ea )
print "[idasploiter] Settings %d breakpoints on selected caller addresses." % len(eas)
for ea in eas:
# Set default software breakpoint
idaapi.add_bpt(ea, 0, idaapi.BPT_DEFAULT)
self.select_caller_breakpoint_list = list()
# Selection number
else:
self.select_caller_breakpoint_list.append(n)
# Jump to Pointer address
elif cmd_id == self.cmd_jump_ptr:
if n >= 0 and self.sploiter.funcptr.ptrs[n].ptr_ea:
idaapi.jumpto( self.sploiter.funcptr.ptrs[n].ptr_ea )
# Jump to Pointer-to-Pointer address
elif cmd_id == self.cmd_jump_p2p:
if n >= 0 and self.sploiter.funcptr.ptrs[n].p2p_ea:
idaapi.jumpto( self.sploiter.funcptr.ptrs[n].p2p_ea )
# Jump to Caller address
elif cmd_id == self.cmd_jump_caller:
if n >= 0 and self.sploiter.funcptr.ptrs[n].call_ea:
idaapi.jumpto( self.sploiter.funcptr.ptrs[n].call_ea )
# Export CSV
elif cmd_id == self.cmd_export_csv:
file_name = idaapi.askfile_c(1, "*.csv", "Please enter CSV file name")
if file_name:
print "[idasploiter] Exporting function pointers to %s" % file_name
with open(file_name, 'wb') as csvfile:
csvwriter = csv.writer(csvfile, delimiter=',',
quotechar='"', quoting=csv.QUOTE_MINIMAL)
csvwriter.writerow(["Pointer","Offset","Name","Module","Charset","Caller Address","Caller Name","Instruction","Ptr-to-Ptr","Ptr-to-Ptr Offset","Ptr-to-Ptr Charset"])
for item in self.items:
csvwriter.writerow(item)
return 1
def OnSelectLine(self, n):
idaapi.jumpto( int(self.items[n][0],16) )
def OnGetLine(self, n):
return self.items[n]
def OnClose(self):
self.cmd_select_caller_breakpoint = None
self.cmd_jump_ptr = None
self.cmd_jump_p2p = None
self.cmd_jump_caller = None
self.cmd_export_csv = None
def OnGetSize(self):
return len(self.items)
def OnRefresh(self, n):
self.refreshitems()
return n
def OnActivate(self):
self.refreshitems()
###############################################################################
# Pattern UI
###############################################################################
###############################################################################
# Pattern Create Form
class PatternCreateForm(Form):
def __init__(self, sploiter):
self.sploiter = sploiter
self.pattern_complete = ""
Form.__init__(self,
r"""BUTTON YES NONE
BUTTON CANCEL NONE
Create pattern
{FormChangeCb}<:{strPattern}>
<Size :{intSize}>
Formats<Regular:{rFmtReg}><Hex:{rFmtHex}><JavaScript:{rFmtJS}>{rGroup}>
Charset<C4:{cC4}><##:{strC4}>
<C3:{cC3}><##:{strC3}>
<C2:{cC2}><##:{strC2}>
<C1:{cC1}>{cGroup}><##:{strC1}>
{spacer}<Generate:{iButtonGenerate}>
""", {
'spacer' : Form.StringLabel(" "),
'intSize' : Form.NumericInput(swidth=30,tp=Form.FT_DEC,value=1000),
'iButtonGenerate' : Form.ButtonInput(self.OnButtonGenerate,swidth=5),
'rGroup' : Form.RadGroupControl(("rFmtReg","rFmtHex", "rFmtJS") ),
'cGroup' : Form.ChkGroupControl(("cC4","cC3","cC2","cC1")),
'strC4' : Form.StringInput(swidth=38,tp=Form.FT_ASCII),
'strC3' : Form.StringInput(swidth=38,tp=Form.FT_ASCII),
'strC2' : Form.StringInput(swidth=38,tp=Form.FT_ASCII),
'strC1' : Form.StringInput(swidth=38,tp=Form.FT_ASCII),
'strPattern' : Form.MultiLineTextControl(text="", width=400, flags = Form.MultiLineTextControl.TXTF_FIXEDFONT | Form.MultiLineTextControl.TXTF_SELECTED | Form.MultiLineTextControl.TXTF_READONLY),
'FormChangeCb' : Form.FormChangeCb(self.OnFormChange),
})
self.Compile()
def OnFormChange(self, fid):
# Form initialization
if fid == -1:
# Set initial checkboxes for the classic metasploit 3-byte pattern
self.SetControlValue(self.cC4, False)
self.SetControlValue(self.cC3, True)
self.SetControlValue(self.cC2, True)
self.SetControlValue(self.cC1, True)
# Set initial charsets values
self.SetControlValue(self.strC4, self.sploiter.c4_list)
self.SetControlValue(self.strC3, self.sploiter.c3_list)
self.SetControlValue(self.strC2, self.sploiter.c2_list)
self.SetControlValue(self.strC1, self.sploiter.c1_list)
# Form OK pressed
elif fid == -2:
pass
return 1
def OnButtonGenerate(self, code=0):
size = self.GetControlValue(self.intSize)
print "[idasploiter] Creating a pattern of size %d." % size
# Update global charset lists
self.sploiter.c4_list = self.GetControlValue(self.strC4)
self.sploiter.c3_list = self.GetControlValue(self.strC3)
self.sploiter.c2_list = self.GetControlValue(self.strC2)
self.sploiter.c1_list = self.GetControlValue(self.strC1)
# Generate pattern only using selected lists
self.update_complete_pattern(self.GetControlValue(self.cC4), self.GetControlValue(self.cC3), self.GetControlValue(self.cC2), self.GetControlValue(self.cC1))
pattern = self.pattern_create(size)
if pattern:
# Hex output
if self.GetControlValue(self.rGroup) == 1:
pattern = binascii.hexlify(pattern)
# JavaScript Unicode output
elif self.GetControlValue(self.rGroup) == 2:
pattern_js = ""
# Number of WORDs in the pattern
num_words = len(pattern) / 2
# Chop off extra characters
num_extra = len(pattern) % 2
if num_extra:
pattern = pattern[:-num_extra]
# Unpack individual WORDs from the pattern
words = struct.unpack("H"*num_words, pattern)
for word in words:
pattern_js += "%%u%s" % hex(word)
pattern = pattern_js
strPattern = idaapi.textctrl_info_t(text=pattern, flags = Form.MultiLineTextControl.TXTF_FIXEDFONT | Form.MultiLineTextControl.TXTF_SELECTED | Form.MultiLineTextControl.TXTF_READONLY)
self.SetControlValue(self.strPattern, strPattern )
def pattern_create(self, size):
# Make sure at least one custom charset was specified
if not len(self.pattern_complete):
idaapi.warning("Cannot generate an empty pattern.")
return False
# Requested pattern size exceeds maximum allowed pattern size for the selected characterset
elif size > len(self.pattern_complete):
idaapi.warning("Requested size %d exceeds maximum unique pattern size %d." % (size, len(self.pattern_complete)))
return False
# Get a size substring from the complete pattern
else:
return self.pattern_complete[:size]
def update_complete_pattern(self, c4, c3, c2, c1):
c4_list = self.sploiter.c4_list if c4 else ["",]
c3_list = self.sploiter.c3_list if c3 else ["",]
c2_list = self.sploiter.c2_list if c2 else ["",]
c1_list = self.sploiter.c1_list if c1 else ["",]
# Generate complete pattern
# NOTE: Not as pretty as calculating offset based on positions, but
# allows custom charactersets with varying positions
self.pattern_complete = ""
# Produce a cartesian product of all character lists and make a single string by flattening the list of lists
self.pattern_complete = ''.join([i for j in itertools.product(c4_list, c3_list, c2_list, c1_list) for i in j])
###############################################################################
# Pattern Offset Form
class PatternDetectForm(Form):
def __init__(self, sploiter, debugger = True):
self.sploiter = sploiter
self.debugger = debugger
self.pattern_complete = ""
form_dict = {
'spacer' : Form.StringLabel(" "),
'strPattern' : Form.StringInput(tp=Form.FT_ASCII, swidth=17),
'strOffset' : Form.StringInput(tp=Form.FT_ASCII, swidth=13),
'iButtonDetect' : Form.ButtonInput(self.OnButtonDetect, swidth=5),
'rGroup' : Form.RadGroupControl(('rFmtAuto', 'rFmtStr', 'rFmtAddr')),
'cGroup' : Form.ChkGroupControl(("cC4","cC3","cC2","cC1")),
'strC4' : Form.StringInput(swidth=38,tp=Form.FT_ASCII),
'strC3' : Form.StringInput(swidth=38,tp=Form.FT_ASCII),
'strC2' : Form.StringInput(swidth=38,tp=Form.FT_ASCII),
'strC1' : Form.StringInput(swidth=38,tp=Form.FT_ASCII),
'FormChangeCb' : Form.FormChangeCb(self.OnFormChange),
}
reg_form = ""
self.reg_ctrls = list()
# Display register values when executed with a debugger running
if debugger:
# Populate register values and store form controls in a list
for reg_name in self.sploiter.reg_list:
reg_value = idc.GetRegValue(reg_name)
reg_value = self.sploiter.addr_format % reg_value
ctrlRegValue = Form.StringInput(tp=Form.FT_ASCII, value=reg_value,swidth=17)
form_dict["str%s" % reg_name] = ctrlRegValue
ctrlRegOffset = Form.StringInput(tp=Form.FT_ASCII, swidth=13)
form_dict["str%sOffset" % reg_name] = ctrlRegOffset
reg_form += " <%(reg)03s :{str%(reg)s}><Offset:{str%(reg)sOffset}>\n" % {'reg': reg_name}
self.reg_ctrls.append( (ctrlRegValue, ctrlRegOffset) )
Form.__init__(self,
r"""BUTTON YES NONE
BUTTON CANCEL NONE
Detect pattern
{FormChangeCb}%s<Pattern:{strPattern}><Offset:{strOffset}>
Formats<Auto:{rFmtAuto}><Address:{rFmtAddr}><String:{rFmtStr}>{rGroup}>
Charset<C4:{cC4}><:{strC4}>
<C3:{cC3}><:{strC3}>
<C2:{cC2}><:{strC2}>
<C1:{cC1}>{cGroup}><:{strC1}>
{spacer}<Detect:{iButtonDetect}>
""" % reg_form, form_dict)
self.Compile()
def OnFormChange(self, fid):
# Form initialization
if fid == -1:
# Set initial checkboxes for the classic metasploit 3-byte pattern
self.SetControlValue(self.cC4, False)
self.SetControlValue(self.cC3, True)
self.SetControlValue(self.cC2, True)
self.SetControlValue(self.cC1, True)
# Set initial charsets values
self.SetControlValue(self.strC4, self.sploiter.c4_list)
self.SetControlValue(self.strC3, self.sploiter.c3_list)
self.SetControlValue(self.strC2, self.sploiter.c2_list)
self.SetControlValue(self.strC1, self.sploiter.c1_list)
# NOTE: Trying to detect offset for registry values during
# form initialization appears to be very slow and does
# not provide an option for a user to specify a custom
# characterset.
# Form OK pressed
elif fid == -2:
pass
return 1
def OnButtonDetect(self, code=0):
# Update global charset lists
self.sploiter.c4_list = self.GetControlValue(self.strC4)
self.sploiter.c3_list = self.GetControlValue(self.strC3)
self.sploiter.c2_list = self.GetControlValue(self.strC2)
self.sploiter.c1_list = self.GetControlValue(self.strC1)
# Generate pattern only using selected lists
self.update_complete_pattern(self.GetControlValue(self.cC4), self.GetControlValue(self.cC3), self.GetControlValue(self.cC2), self.GetControlValue(self.cC1))
for ctrlRegValue, ctrlRegOffset in self.reg_ctrls:
reg_value = self.GetControlValue(ctrlRegValue)
self.SetControlValue(ctrlRegOffset, self.verbalize_pattern(reg_value, address=True) )
pattern = self.GetControlValue(self.strPattern)
# Force address format
if self.GetControlValue(self.rFmtAddr):
self.SetControlValue(self.strOffset, self.verbalize_pattern(pattern, address=True) )
# Force string format
elif self.GetControlValue(self.rFmtStr):
self.SetControlValue(self.strOffset, self.verbalize_pattern(pattern, string=True) )
# Auto format
else:
self.SetControlValue(self.strOffset, self.verbalize_pattern(pattern) )
def verbalize_pattern(self, pattern, address=False, string=False):
# Forced address format
if address:
# Cleanup the pattern
pattern = pattern.replace(' ','') # remove spaces
pattern = pattern.replace('\\x','') # remove '\x' prefixes
pattern = pattern.replace('0x','') # remove '0x' prefixes
if self.sploiter.addr64 and len(pattern) == 8*2:
try:
pattern = struct.pack("Q", int(pattern, 16))
except Exception, e:
return "Invalid address"
elif not self.sploiter.addr64 and len(pattern) == 4*2:
try:
pattern = struct.pack("I", int(pattern, 16))
except Exception, e:
return "Invalid address"
else:
return "Invalid address"
# Forced string format
elif string:
pass
# Autodetect format
else:
# Cleanup the pattern
pattern = pattern.replace(' ','') # remove spaces
pattern = pattern.replace('\\x','') # remove '\x' prefixes
pattern = pattern.replace('0x','') # remove '0x' prefixes
# Attempt to detect and unpack pattern
if self.sploiter.addr64 and len(pattern) == 8*2:
try:
pattern = struct.pack("Q", int(pattern, 16))
except Exception, e:
pass
elif not self.sploiter.addr64 and len(pattern) == 4*2:
try:
pattern = struct.pack("I", int(pattern, 16))
except Exception, e:
pass
if len(pattern) > 0:
offset = self.pattern_offset(pattern)
offset_reverse = self.pattern_offset(pattern[::-1])
else:
return "Blank pattern"
if offset >= 0:
return "%d" % offset
elif offset_reverse >= 0:
return "%d (reverse)" % offset_reverse
else:
return "Not detected"
def pattern_offset(self, pattern):
offset = -1
# Make sure at least one custom charset was specified
if not len(self.pattern_complete):
idaapi.warning("Cannot detect an empty pattern.")
return offset
# Locate the pattern
if pattern in self.pattern_complete:
offset = self.pattern_complete.index(pattern)
return offset
def update_complete_pattern(self, c4, c3, c2, c1):
c4_list = self.sploiter.c4_list if c4 else ["",]
c3_list = self.sploiter.c3_list if c3 else ["",]
c2_list = self.sploiter.c2_list if c2 else ["",]
c1_list = self.sploiter.c1_list if c1 else ["",]
# Generate complete pattern
# NOTE: Not as pretty as calculating offset based on positions, but
# allows custom character sets with varying positions
self.pattern_complete = ""
# Produce a cartesian product of all character lists and make a single string by flattening the list of lists
self.pattern_complete = ''.join([i for j in itertools.product(c4_list, c3_list, c2_list, c1_list) for i in j])
###############################################################################
# Compare UI
###############################################################################
###############################################################################
# Compare Form
class CompareForm(Form):
def __init__(self, sploiter, debugger = True):
self.sploiter = sploiter
self.debugger = debugger
Form.__init__(self,
r"""BUTTON YES NONE
BUTTON CANCEL NONE
Compare file to memory
{FormChangeCb}<File :{impFile}>
<Memory :{intAddr}>
<:{strCompare}>
<Bad Chars:{strBadChars}>
<Mem Holes:{strMemHoles}>
{spacer}<Compare:{iButtonCompare}>
""", {
'spacer' : Form.StringLabel(" "),
'impFile' : Form.FileInput(swidth=62, open=True),
'intAddr' : Form.NumericInput(swidth=62, tp=Form.FT_ADDR, value=idaapi.get_screen_ea()),
'strCompare' : Form.MultiLineTextControl(text="", swidth=72, flags = Form.MultiLineTextControl.TXTF_FIXEDFONT | Form.MultiLineTextControl.TXTF_READONLY),
'strBadChars' : Form.StringInput(swidth=62,tp=Form.FT_ASCII),
'strMemHoles' : Form.StringInput(swidth=62,tp=Form.FT_ASCII),
'iButtonCompare' : Form.ButtonInput(self.OnButtonCompare,),
'FormChangeCb' : Form.FormChangeCb(self.OnFormChange),
})
self.Compile()
def OnFormChange(self, fid):
# Form initialization
if fid == -1:
pass
# Form OK pressed
elif fid == -2:
pass
return 1
def OnButtonCompare(self, code=0):
#######################################################################
# Read file into buffer
file_name = self.GetControlValue(self.impFile)
# Verify file name was provided
if not len(file_name):
idaapi.warning("Please provide file name.")
return
# Attempt to read the file
print "[idasploiter] Reading file %s" % file_name
try:
f = open(file_name,'rb')
except Exception, e:
idaapi.warning("File I/O error({0}): {1}.".format(e.errno, e.strerror) )
return
else:
file_buf = f.read()
f.close()
# Check file content
if not len(file_buf) > 0:
idaapi.warning("Provided file was blank.")
return
# 64k ought to be enough for anybody =)
elif len(file_buf) > 0xffff:
idaapi.warning("File is too big.")
return
# Covert file bytes into printable hex view
file_buf_str = " ".join(["%02x" % ord(b) for b in file_buf])
file_buf_list = textwrap.wrap(file_buf_str, 48)
#######################################################################
# Read memory into buffer and mark non-matching bytes
address = self.GetControlValue(self.intAddr)
print "[idasploiter] Reading %d bytes from %s." % ( len(file_buf), self.sploiter.addr_format % address)
if self.debugger:
mem_buf = read_module_memory(address, len(file_buf))
else:
mem_buf = idaapi.get_many_bytes(address, len(file_buf))
if not mem_buf:
idaapi.warning("Could not read memory.")
return
# Convert memory bytes into printable hex view
mem_buf_str = ""
# Badchars tracking
badchars = list()
badchars_nonseq = set()
badchars_hole = list()
# Locate all of the non-sequential badchars. The assumption is that
# after the first badchar (e.g. 0x0A) the rest may be corrupted.
for i, b in enumerate(file_buf):
if file_buf[i] == mem_buf[i]:
mem_buf_str+= ".. "
else:
mem_buf_str+= "%02x " % ord( mem_buf[i])
# Do differential processing if the two bytes are not the same or
# if the matching byte is in the bad characters list
if file_buf[i] != mem_buf[i] or "%02x" % ord(file_buf[i]) in badchars_nonseq:
###############################################################
# Memory holes
# Check if the badchar appears anywhere else in memory to
# detect positional holes. If this is the first discrepancy
# then it is likely it is a badchar (e.g. 0x00, 0x0a, 0x0d)
if len(badchars_hole):
hole_i, hole_size = badchars_hole[-1]
# Continuation of a previously discovered hole
if i == hole_i + hole_size:
badchars_hole[-1] = (hole_i, hole_size + 1)
# New hole discovered
else:
badchars_hole.append( (i,1) )
# New hole discovered
else:
badchars_hole.append( (i,1) )
###############################################################
# Bad characters
# Check if previous byte in the file is already a badchar, so
# this new discrepancy doesn't matter:
if not "%02x" % ord(file_buf[i-1]) in badchars_nonseq:
# Skip sequential corruption
if len(badchars_nonseq):
bad_i, bad_b = badchars[-1]
# Check if the previous byte was also corrupted
# indicating a sequential corruption
if i - 1 != bad_i:
badchars_nonseq.add( "%02x" % ord(b) )
else:
badchars_nonseq.add( "%02x" % ord(b) )
# Store badchars in a list
badchars.append( (i,b) )
mem_buf_list = textwrap.wrap(mem_buf_str, 48)
if len(badchars) == 0:
self.SetControlValue(self.strBadChars, "Identical.")
elif len(badchars) == len(file_buf):
self.SetControlValue(self.strBadChars, "Completely different.")
else:
self.SetControlValue(self.strBadChars, " ".join( sorted(badchars_nonseq) ) )
self.SetControlValue(self.strMemHoles, ", ".join( [ "0x%02X: %d byte(s)" % h for h in badchars_hole ] ) )
#######################################################################
# Populate the text box
buf_formatted = " 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F\n\n"
for i in range( len(file_buf_list) ):
file_line = file_buf_list[i].ljust(48)
file_line = file_line[:23]+" "+file_line[23:]
mem_line = mem_buf_list[i].ljust(48)
mem_line = mem_line[:23]+" "+mem_line[23:]
buf_formatted += "%04X %s F\n %s M\n\n" % (i*0x10, file_line, mem_line)
strCompare = idaapi.textctrl_info_t(text=buf_formatted, flags = Form.MultiLineTextControl.TXTF_FIXEDFONT | Form.MultiLineTextControl.TXTF_READONLY)
self.SetControlValue(self.strCompare, strCompare)
###############################################################################
# Plugin Manager
###############################################################################
class SploitManager():
""" Class that manages GUI forms and exploitation methods of the plugin. """
def __init__(self):
# Import plugins in function scope to avoid circular dependencies from importing in global scope.
from idasploiter_x86 import x86_Sploiter
from idasploiter_ppc import ppc_Sploiter
self.addmenu_item_ctxs = list()
# Initialize sploiter class based on architecture type.
if idaapi.ph.id == idaapi.PLFM_386:
self.sploiter = x86_Sploiter()
elif idaapi.ph.id == idaapi.PLFM_PPC:
self.sploiter = ppc_Sploiter()
else:
self.sploiter = None
print("Current processor type is not supported!")
def is_architecture_supported(self):
return False if self.sploiter is None else True
###########################################################################
# Menu Items
def add_menu_item_helper(self, menupath, name, hotkey, flags, pyfunc, args):
# add menu item and report on errors
addmenu_item_ctx = idaapi.add_menu_item(menupath, name, hotkey, flags, pyfunc, args)
if addmenu_item_ctx is None:
return 1
else:
self.addmenu_item_ctxs.append(addmenu_item_ctx)
return 0
def add_menu_items(self):
if self.add_menu_item_helper("View/Open subviews/Segments", "Modules", "Shift+F6", 0, self.show_modules_view, None): return 1
# Check if this feature is supported or not.
if self.sploiter.is_func_ptr_supported():
if self.add_menu_item_helper("Search/all error operands", "function pointers...", "Alt+f", 1, self.show_funcptr_view, None): return 1
if self.add_menu_item_helper("Search/all error operands", "gadgets...", "Alt+r", 1, self.show_rop_view, None): return 1
if self.add_menu_item_helper("Edit/Begin selection", "Create pattern...", "Shift+c", 0, self.show_pattern_create, None): return 1
if self.add_menu_item_helper("Edit/Begin selection", "Detect pattern...", "Shift+d", 0, self.show_pattern_detect, None): return 1
if self.add_menu_item_helper("Edit/Begin selection", "Compare file to memory...", "Shift+f", 0, self.show_compare, None): return 1
return 0
def del_menu_items(self):
for addmenu_item_ctx in self.addmenu_item_ctxs:
result = idaapi.del_menu_item(addmenu_item_ctx)
###########################################################################
# Utility Functions
# Check debugger is ready for querying
def check_debugger(self):
if idaapi.dbg_can_query() and idaapi.get_process_state() < 0:
return True
else:
return False
###########################################################################
# View Callbacks
# Modules View
def show_modules_view(self):
self.sploiter.process_modules()
self.sploiter.show_modules_view()
# ROP View
def show_rop_view(self):
self.sploiter.process_modules()
self.sploiter.process_rop()
# Function Pointer View
def show_funcptr_view(self):
self.sploiter.process_modules()
self.sploiter.process_funcptr()
# Create Pattern Form
def show_pattern_create(self):
self.sploiter.pattern_create()
# Detect Pattern Form
def show_pattern_detect(self):
# Debugger Version
if self.check_debugger():
self.sploiter.pattern_detect()
# Static Version
else:
self.sploiter.pattern_detect(debugger=False)
# Compare File to Memory Form
def show_compare(self):
# Debugger Version
if self.check_debugger():
self.sploiter.process_compare()
# Static Version
else:
self.sploiter.process_compare(debugger=False)
###############################################################################
class idasploiter_t(plugin_t):
flags = idaapi.PLUGIN_PROC
comment = "Exploit development and vulnerability research toolkit."
help = "Exploit development and vulnerability research toolkit."
wanted_name = "IDA Sploiter"
wanted_hotkey = ""
def init(self):
global idasploiter_manager
if 'idasploiter_manager' in globals().keys():
idasploiter_manager.del_menu_items()
del idasploiter_manager
# Initialize the sploit manager.
idasploiter_manager = SploitManager()
if idasploiter_manager.is_architecture_supported() is False:
return idaapi.PLUGIN_SKIP
# Add menu items.
if idasploiter_manager.add_menu_items():
print "Failed to initialize IDA Sploiter."
idasploiter_manager.del_menu_items()
del idasploiter_manager
return idaapi.PLUGIN_SKIP
else:
print("Initialized IDA Sploiter v%s (c) Peter Kacherginsky <iphelix@thesprawl.org>" % IDASPLOITER_VERSION)
return idaapi.PLUGIN_KEEP
def run(self, arg):
pass
def term(self):
global idasploiter_manager
if 'idasploiter_manager' in globals().keys():
idasploiter_manager.del_menu_items()
del idasploiter_manager
def PLUGIN_ENTRY():
return idasploiter_t()
###############################################################################
# Script / Testing
###############################################################################
def idasploiter_main():
global idasploiter_manager
if 'idasploiter_manager' in globals():
idasploiter_manager.del_menu_items()
del idasploiter_manager
idasploiter_manager = SploitManager()
idasploiter_manager.add_menu_items()
sploiter = idasploiter_manager.sploiter
if __name__ == '__main__':
#idasploiter_main()
pass
================================================
FILE: idasploiter_ppc.py
================================================
#!/usr/bin/env python
#
# IDA Sploiter is an exploit development and vulnerability research environment
# implemented as a plugin for Hex-Ray's IDA Pro disassembler.
# Copyright (C) 2014 Peter Kacherginsky
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Performance profiling
import cProfile
import pstats
# IDA Sploiter
from idasploiter import FuncPtr, Gadget, Ptr, Rop, read_module_memory, Sploiter
# IDA libraries
import idaapi
import idautils
import idc
from idaapi import Form, Choose2, plugin_t
# Python libraries
import os
import binascii
import string
import textwrap
import copy
import csv
import itertools
import struct
from struct import pack, unpack
from ctypes import *
###############################################################################
# ROP Search Engine
class ppc_Rop(Rop):
def __init__(self, sploiter):
Rop.__init__(self, sploiter)
# Extra bytes to read to ensure correct decoding of
# BLR instructions.
self.dbg_read_extra = 4
self.insn_arithmetic_ops = ["neg", "add","addc","adde","addi","addic","addic.","addis","addme","addze","divw","divw.","divwo","divwo.","divwu","divwu.","divwuo","divwuo.","mulhw","mulhw.","mulhwu","mulhwu.","mulli","mullw","mullw.","mullwo","mullwo.",
"subf","subf.","subfo","subfo.","sub","subfc","subfc.","subfco","subfco.","subc","subfe","subfe.","subfeo","subfeo.","subfic","subfme","subfme.","subfmeo","subfmeo.","subfzo","subfze.","subfzeo","subfzeo."]
self.insn_bit_ops = ["neg","and","andc","andi","andis","or","orc","ori","oris","nand","nor","xor","xori","xoris","slw","srw","sraw","srawi","rlwimi","rlwinm","rlwnm","rldicr"]
def get_o_reg_name(self, insn, n):
reg_num = insn.Operands[n].reg
reg_name = self.regnames[reg_num]
return reg_name
def search_retns(self):
if not self.debug: print("found %d modules" % len(self.modules))
for m in self.modules:
# Iterate over segments in the module
# BUG: Iterating over all loaded segments is more stable than looking up by address
if not self.debug: print("found %d segments" % idaapi.get_segm_qty())
for n in xrange(idaapi.get_segm_qty()):
seg = idaapi.getnseg(n)
# Locate executable segments in a selected modules
# NOTE: Each module may have multiple executable segments
if seg and seg.startEA >= m.addr and seg.endEA <= (m.addr + m.size):
# If the debugger is attached then we can check if the segment is executable, else
# just check if it is code or not.
if idaapi.dbg_can_query() and idaapi.get_process_state() < 0:
if seg.perm & idaapi.SEGPERM_EXEC == 0:
continue
elif seg.type & idaapi.SEG_CODE == 0:
continue
#######################################################
# Search for ROP gadgets
if self.searchRop:
#Search all instances of BLR
ea = seg.startEA
while True:
ea = idaapi.find_binary(ea + 1, seg.endEA, "4E 80 00 20", 16, idaapi.SEARCH_DOWN)
if ea == idaapi.BADADDR: break
self.retns.append((ea, m.file))
# Search all instances of BTCTR
ea = seg.startEA
while True:
ea = idaapi.find_binary(ea + 1, seg.endEA, "4E 80 04 20", 16, idaapi.SEARCH_DOWN)
if ea == idaapi.BADADDR: break
self.retns.append((ea, m.file))
# Search all instances of BTCTRL
ea = seg.startEA
while True:
ea = idaapi.find_binary(ea + 1, seg.endEA, "4E 80 04 21", 16, idaapi.SEARCH_DOWN)
if ea == idaapi.BADADDR: break
self.retns.append((ea, m.file))
def search_gadgets(self):
count_total = len(self.retns)
count_notify = 0
count_curr = 0
# BUG: A separate flag is used to track user canceling the search,
# because multiple calls to idaapi.wasBreak() do not properly
# detect cancellations.
breakFlag = False
# Show wait dialog
if not self.debug: idaapi.show_wait_box("Searching gadgets: 00%%%%")
print("found %s retns" % len(self.retns))
for (ea_end, module) in self.retns:
# Flush the gadgets cache for each new retn pointer
self.gadgets_cache = dict()
# Flush memory cache for each new retn pointer
self.dbg_mem_cache = None
# CACHE: It is faster to read as much memory in one blob than to make incremental reads backwards.
# Try to read and cache self.maxRopSize instructions back. In cases where it is not possible,
# then simply try to read the largest chunk.
# NOTE: Read a bit extra to cover correct decoding of RETN and RETN imm16 instructions.
self.dbg_mem_cache = read_module_memory(ea_end - (self.maxRopSize * 4), (self.maxRopSize * 4) + self.dbg_read_extra)
# Check to make sure we have actual data to work with.
if self.dbg_mem_cache == None:
continue
# Search all possible gadgets up to maxoffset bytes back
# NOTE: try all byte combinations to capture longer/more instructions
# even with bad bytes in the middle.
for i in range(1, self.maxRopSize + 1):
ea = ea_end - (i * 4)
# Get pointer charset
ptr_charset = self.sploiter.get_ptr_charset(ea)
# Filter the pointer
if ptr_charset == None: continue
if self.ptrNonull and not "nonull" in ptr_charset: continue
if self.ptrUnicode and not "unicode" in ptr_charset: continue
if self.ptrAscii and not "ascii" in ptr_charset: continue
if self.ptrAsciiPrint and not "asciiprint" in ptr_charset: continue
if self.ptrAlphaNum and not "alphanum" in ptr_charset: continue
if self.ptrNum and not "numeric" in ptr_charset: continue
if self.ptrAlpha and not "alpha" in ptr_charset: continue
# Try to build a gadget at the pointer
gadget = self.build_gadget(ea, ea_end)
# Successfully built the gadget
if gadget:
# Populate gadget object with more data
gadget.address = ea
gadget.module = module
gadget.ptr_charset = ptr_charset
# Filter gadgets with too many instruction
if gadget.size > self.maxRopSize:
break
# Append newly built gadget
self.gadgets.append(gadget)
self.gadgets_cache[ea] = gadget
# Exceeded maximum number of gadgets
if self.maxRops and len(self.gadgets) >= self.maxRops:
breakFlag = True
print "[idasploiter] Maximum number of gadgets exceeded."
break
else:
self.gadgets_cache[ea] = None
if breakFlag or idaapi.wasBreak():
breakFlag = True
break
# Canceled
# NOTE: Only works when started from GUI not script.
if breakFlag or idaapi.wasBreak():
breakFlag = True
print "[idasploiter] Canceled."
break
# Progress report
if not self.debug and count_curr >= count_notify:
# NOTE: Need to use %%%% to escape both Python and IDA's format strings
idaapi.replace_wait_box("Searching gadgets: %02d%%%%" % (count_curr * 100/count_total))
count_notify += 0.10 * count_total
count_curr += 1
print "[idasploiter] Found %d gadgets." % len(self.gadgets)
if not self.debug: idaapi.hide_wait_box()
# Attempt to build a gadget at the provided start address
# by verifying it properly terminates at the expected RETN.
def build_gadget(self, ea, ea_end):
instructions = list()
chg_registers = set()
use_registers = set()
operations = set()
pivot = 0
# Process each instruction in the gadget
while ea <= ea_end:
###################################################################
# Gadget Level Cache:
#
# Locate a gadget (failed or built) starting at this address.
# If one is located, then we don't need to process any further
# instructions and just get necessary data from the cached
# gadget to never have to process the same address twice.
if ea in self.gadgets_cache:
# Check if the gadget was build successfully
gadget_cache = self.gadgets_cache[ea]
# Build the reset of the gadget from cache
if gadget_cache:
for insn in gadget_cache.instructions:
instructions.append(insn)
for reg in gadget_cache.chg_registers:
chg_registers.add(reg)
for reg in gadget_cache.use_registers:
use_registers.add(reg)
for op in gadget_cache.operations:
operations.add(op)
pivot += gadget_cache.pivot
gadget = Gadget(instructions, pivot, operations, chg_registers, use_registers)
return gadget
# Previous attempt to build gadget at this address failed
else:
return None
# Process new instruction
else:
# Instruction length
# NOTE: decode_insn also sets global idaapi.cmd
# which contains insn_t structure
insn_size = idaapi.decode_insn(ea)
# Check successful decoding of the instruction
if insn_size:
###############################################################
# Instruction Level Cache
#
# Most instructions are repetitive so we can just cache
# unique byte combinations to avoid costly decoding more
# than once
# Read instruction from memory cache
dbg_mem_offset = ea - (ea_end - (len(self.dbg_mem_cache) - self.dbg_read_extra))
dbg_mem = self.dbg_mem_cache[dbg_mem_offset:dbg_mem_offset + insn_size]
# Create instruction cache if it doesn't already exist
if not dbg_mem in self.insn_cache:
###########################################################
# Decode instruction
###########################################################
# Get global insn_t structure describing the instruction
# NOTE: copy() is expensive, so we keep this single-threaded
insn = idaapi.cmd
#######################################################
# Decode and Cache instruction characteristics
self.insn_cache[dbg_mem] = self.decode_instruction(insn, ea, ea_end)
##################################################################
# Retrieve cached instruction and apply it to the gadget
# Check that cached instruction contains valid data
if self.insn_cache[dbg_mem]:
# Retrieve basic instruction characteristics
insn_mnem = self.insn_cache[dbg_mem]["insn_mnem"]
insn_disas = self.insn_cache[dbg_mem]["insn_disas"]
instructions.append(insn_disas)
# Check if we found an instruction that would change the instruction pointer
if insn_mnem == "blr" or insn_mnem == "bctr" or insn_mnem == "bctrl":
# RETN at the expected address
if ea == ea_end:
gadget = Gadget(instructions, pivot, operations, chg_registers, use_registers)
return gadget
# RETN at an unexpected address
else:
return None
#######################################################
# Add instruction instruction characteristics to the gadget
else:
for reg in self.insn_cache[dbg_mem]["insn_chg_registers"]:
chg_registers.add(reg)
for reg in self.insn_cache[dbg_mem]["insn_use_registers"]:
use_registers.add(reg)
for op in self.insn_cache[dbg_mem]["insn_operations"]:
operations.add(op)
pivot += self.insn_cache[dbg_mem]["insn_pivot"]
# Previous attempt to decode the instruction invalidated the gadget
else:
return None
###############################################################
# Next instruction
# NOTE: This is outside cache
ea += insn_size
###################################################################
# Failed decoding of the instruction
# NOTE: Gadgets may have bad instructions in the middle which
# can be tolerated as long as we can find a useful instruction
# further out.
else:
# Invalidate the gadget
return None
# Failed to build a gadget, because RETN instruction was not found
return None
###############################################################
# Decode instruction
def decode_instruction(self, insn, ea, ea_end):
# Instruction specific characteristics
insn_chg_registers = set()
insn_use_registers = set()
insn_operations = set()
insn_pivot = 0
# Instruction feature
#
# instruc_t.feature
#
# CF_STOP = 0x00001 # Instruction doesn't pass execution to the next instruction
# CF_CALL = 0x00002 # CALL instruction (should make a procedure here)
# CF_CHG1 = 0x00004 # The instruction modifies the first operand
# CF_CHG2 = 0x00008 # The instruction modifies the second operand
# CF_CHG3 = 0x00010 # The instruction modifies the third operand
# CF_CHG4 = 0x00020 # The instruction modifies 4 operand
# CF_CHG5 = 0x00040 # The instruction modifies 5 operand
# CF_CHG6 = 0x00080 # The instruction modifies 6 operand
# CF_USE1 = 0x00100 # The instruction uses value of the first operand
# CF_USE2 = 0x00200 # The instruction uses value of the second operand
# CF_USE3 = 0x00400 # The instruction uses value of the third operand
# CF_USE4 = 0x00800 # The instruction uses value of the 4 operand
# CF_USE5 = 0x01000 # The instruction uses value of the 5 operand
# CF_USE6 = 0x02000 # The instruction uses value of the 6 operand
# CF_JUMP = 0x04000 # The instruction passes execution using indirect jump or call (thus needs additional analysis)
# CF_SHFT = 0x08000 # Bit-shift instruction (shl,shr...)
# CF_HLL = 0x10000 # Instruction may be present in a high level language function.
insn_feature = insn.get_canon_feature()
if insn_feature == 0:
return None
# Instruction mnemonic name
insn_mnem = insn.get_canon_mnem()
# BUGBUG: The IDA PowerPC processor module has a bug that causes 'b', 'blr', 'bctr', and 'bctrl' instructions to decode
# identically. Manually check the opcode bytes to determine for real what instruction this is.
insn_opcode = idc.Dword(ea)
if insn_opcode == 0x4E800020:
insn_mnem = "blr"
elif insn_opcode == 0x4E800420:
insn_mnem = "bctr"
elif insn_opcode == 0x4E800421:
insn_mnem = "bctrl"
#if insn_mnem in self.mnems: self.mnems[insn_mnem] += 1
#else: self.mnems[insn_mnem] = 1
# Get instruction operand types
#
# op_t.type
# Description Data field
# o_void = 0 # No Operand ----------
# o_reg = 1 # General Register (al,ax,es,ds...) reg
# o_mem = 2 # Direct Memory Reference (DATA) addr
# o_phrase = 3 # Memory Ref [Base Reg + Index Reg] phrase
# o_displ = 4 # Memory Reg [Base Reg + Index Reg + Displacement] phrase+addr
# o_imm = 5 # Immediate Value value
# o_far = 6 # Immediate Far Address (CODE) addr
# o_near = 7 # Immediate Near Address (CODE) addr
insn_op1 = insn.Operands[0].type
insn_op2 = insn.Operands[1].type
insn_op3 = insn.Operands[2].type
###############################################################
# Filter gadget
###############################################################
# Filter gadgets with instructions that don't forward execution to the next address
if insn_feature & idaapi.CF_STOP:
if insn_mnem != "blr" and insn_mnem != "bctrl":
return None
# Filter gadgets with instructions in a bad list
elif insn_mnem in self.ropBadMnems:
return None
# Filter gadgets with branch instructions
# Note: conditional branches may still be useful if we can
# set flags prior to calling them.
elif not self.ropAllowJcc and insn_mnem == "b":
return None
###############################################################
# Get disassembly
###############################################################
# NOTE: GENDSM_FORCE_CODE ensures correct decoding
# of split instructions.
insn_disas = idc.GetDisasmEx(ea, idaapi.GENDSM_FORCE_CODE)
insn_disas = insn_disas.partition(';')[0] # Remove comments from disassembly
insn_disas = ' '.join(insn_disas.split()) # Remove extraneous space from disassembly
###############################################################
# Analyze instruction
###############################################################
# Check for arithmetic operations
if insn_mnem in self.insn_arithmetic_ops:
insn_operations.add("math")
# Check for bit-wise operations
if insn_mnem in self.insn_bit_ops:
insn_operations.add("bit")
# Check if the operands are registers and record how it is used.
if insn_op1 == idaapi.o_reg:
reg1_name = self.get_o_reg_name(insn, 0)
# Check if the register is used or modified or both.
if insn_feature & idaapi.CF_USE1:
insn_use_registers.add(reg1_name)
if insn_feature & idaapi.CF_CHG1:
insn_chg_registers.add(reg1_name)
# Check for stack operation
if reg1_name == "r1":
insn_operations.add("stack")
# Determine stack pivot distance
if insn_op3 == idaapi.o_imm:
# NOTE: adb and sbb may also be useful, but let the user
# determine their use by locating the operations "stack"
if insn_mnem == "add" or insn_mnem == "addi":
insn_pivot += insn.Operands[2].value
elif insn_mnem == "sub" or insn_mnem == "subi":
insn_pivot -= insn.Operands[2].value
# Check for operations
if insn_op3 == idaapi.o_reg:
insn_operations.add("reg-to-reg")
elif insn_op3 == idaapi.o_phrase or insn_op3 == idaapi.o_displ:
insn_operations.add("mem-to-reg")
# Check second operand.
if insn_op2 == idaapi.o_reg or insn_op2 == idaapi.o_displ:
reg2_name = self.get_o_reg_name(insn, 1)
insn_use_registers.add(reg2_name)
# Check if the register is used or modified or both.
if insn_feature & idaapi.CF_CHG2:
insn_chg_registers.add(reg2_name)
# Check if the third operand is used and if not determine the instruction type.
if insn_feature & idaapi.CF_USE3 == 0:
# Two operand register
if insn_op2 == idaapi.o_reg:
insn_operations.add("reg-to-reg")
# Two operand immediate
elif insn_op2 == idaapi.o_imm:
insn_operations.add("imm-reg")
# Two operand reference
elif insn_op2 == idaapi.o_phrase or insn_op2 == idaapi.o_displ:
# Hack to try and determine if the instruction is a load or store instruction.
if insn_mnem[0] == "l":
insn_operations.add("mem-reg")
else:
insn_operations.add("reg-mem")
# Check third operand.
if insn_op3 == idaapi.o_reg:
reg3_name = self.get_o_reg_name(insn, 2)
# Check if the register is used or modified or both.
if insn_feature & idaapi.CF_USE3:
insn_use_registers.add(reg3_name)
if insn_feature & idaapi.CF_CHG3:
insn_chg_registers.add(reg3_name)
# Build instruction dictionary
insn = dict()
insn["insn_mnem"] = insn_mnem
insn["insn_disas"] = insn_disas
insn["insn_operations"] = insn_operations
insn["insn_chg_registers"] = insn_chg_registers
insn["insn_use_registers"] = insn_use_registers
insn["insn_pivot"] = insn_pivot
return insn
###############################################################################
# Sploiter Engine
class ppc_Sploiter(Sploiter):
def __init__(self):
Sploiter.__init__(self)
# Initialize fields specific to this architecture.
self.bad_instructions = "sc, rfid, twi"
self.reg_list = [ "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10",
"r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20",
"r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31" ]
def is_func_ptr_supported(self):
return False
def get_func_ptr_instance(self):
# Currently not supported.
return None
def get_rop_instance(self):
return ppc_Rop(self)
class idasploiter_ppc_t(plugin_t):
flags = idaapi.PLUGIN_UNL
comment = ""
help = ""
wanted_name = ""
wanted_hotkey = ""
def init(self):
return idaapi.PLUGIN_SKIP
def run(self, arg):
pass
def term(self):
pass
def PLUGIN_ENTRY():
return idasploiter_ppc_t()
================================================
FILE: idasploiter_x86.py
================================================
#!/usr/bin/env python
#
# IDA Sploiter is an exploit development and vulnerability research environment
# implemented as a plugin for Hex-Ray's IDA Pro disassembler.
# Copyright (C) 2014 Peter Kacherginsky
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its contributors
# may be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Performance profiling
import cProfile
import pstats
# IDA Sploiter
from idasploiter import FuncPtr, Gadget, Ptr, Rop, read_module_memory, Sploiter
# IDA libraries
import idaapi
import idautils
import idc
from idaapi import Form, Choose2, plugin_t
# Python libraries
import os
import binascii
import string
import textwrap
import copy
import csv
import itertools
import struct
from struct import pack, unpack
from ctypes import *
###############################################################################
# Function Pointer Search Engine
class x86_FuncPtr(FuncPtr):
def __init__(self, sploiter):
FuncPtr.__init__(self, sploiter)
# NOTE: Pointer Offsets are interpreted as follows:
# Example 1: Consider we have loaded fptr with a user controlled value
#
# mov [fptr + offset], value
#
# The fptr value we need is (fptr - offset) to overwrite
# the actual fptr location. Do this early so that applied address
# filters consider fptr bytes before offset adjustment.
#
# Specify this positive offset in the Function Pointer form
# so that it would be applied to all discovered function pointers.
#
# Example 2: Consider we have a pointer to a pointer write condition:
#
# mov fptr, [p2p + offset2]
# mov [fptr + offset1], value
#
# The value (p2p - offset2) must point to the fptr location,
# but since we have another offset the target of (p2p - offset2)
# must be actually (fptr - offset1). Also do this early so that
# applied address filters consider p2p before offset adjustment.
#
# Specify this positive p2p offset2 so it gets applied to the listing
# of pointers-to-pointers. All pointer-to-pointer(s) will also
# be calculated relative to the ptr - offset1
#
# NOTE: Only search for pointers to pointers within the same module. This
# was done purely for performance considerations since any readable
# pointer to pointer may be used.
def search_pointers(self):
# HACK: A separate flag is used to track user canceling the search,
# because multiple calls to idaapi.wasBreak() do not properly
# detect cancellations.
breakFlag = False
# Show wait dialog
idaapi.show_wait_box("Searching writable function pointers...")
for m in self.modules:
###################################################################
# Locate all of the CALL and JMP instructions in the current module
# which use an immediate operand.
# List of call/jmp pointer calls in a given module
ptr_calls = list()
# Iterate over segments in the module
# BUG: Iterating over all loaded segments is more stable than looking up by address
for n in xrange(idaapi.get_segm_qty()):
seg = idaapi.getnseg(n)
# Segment in a selected modules
if seg and seg.startEA >= m.addr and seg.endEA <= (m.addr + m.size):
# Locate executable segments
# NOTE: Each module may have multiple executable segments
# TODO: Search for "MOV REG, PTR # CALL REG"
if seg.perm & idaapi.SEGPERM_EXEC:
# Search all instances of CALL /2 imm32/64 - FF 15
# TODO: Alternative pointer calls using SIB: FF 14 E5 11 22 33 44 - call dword/qword ptr [0x44332211]
# FF 14 65 11 22 33 44
# FF 14 25 11 22 33 44
call_ea = seg.startEA
while True:
call_ea = idaapi.find_binary(call_ea + 1, seg.endEA, "FF 15", 16, idaapi.SEARCH_DOWN)
if call_ea == idaapi.BADADDR: break
ptr_calls.append(call_ea)
# Search all instances of JMP /2 imm32/64 - FF 25
# TODO: Alternative pointer calls using SIB: FF 24 E5 11 22 33 44 - jmp dword/qword ptr [0x44332211]
# FF 24 65 11 22 33 44
# FF 24 25 11 22 33 44
call_ea = seg.startEA
while True:
call_ea = idaapi.find_binary(call_ea + 1, seg.endEA, "FF 25", 16, idaapi.SEARCH_DOWN)
if call_ea == idaapi.BADADDR: break
ptr_calls.append(call_ea)
###################################################################
# Extract all of the function pointers and make sure they are
# are writable.
# List of writable function pointer objects in a given module
ptrs = list()
for call_ea in ptr_calls:
# Decode CALL/JMP instruction
# NOTE: May result in invalid disassembly of split instructions
insn_size = idaapi.decode_insn(call_ea)
if insn_size:
insn = idaapi.cmd
insn_op1 = insn.Operands[0].type
# Verify first operand is a direct memory reference
if insn.Operands[0].type == idaapi.o_mem:
# Get operand address
ptr_ea = insn.Operands[0].addr
# Apply pointer offset
ptr_ea -= self.ptrOffset
# Locate segment where the pointer is located
ptr_seg = idaapi.getseg(ptr_ea)
# Make sure a valid segment writeable segment was found
if ptr_seg and ptr_seg.perm & idaapi.SEGPERM_WRITE:
# Get pointer charset
ptr_charset = self.sploiter.get_ptr_charset(ptr_ea)
# Filter the pointer
if not self.filterP2P:
if ptr_charset == None: continue
if self.ptrNonull and not "nonull" in ptr_charset: continue
if self.ptrUnicode and not "unicode" in ptr_charset: continue
if self.ptrAscii and not "ascii" in ptr_charset: continue
if self.ptrAsciiPrint and not "asciiprint" in ptr_charset: continue
if self.ptrAlphaNum and not "alphanum" in ptr_charset: continue
if self.ptrNum and not "numeric" in ptr_charset: continue
if self.ptrAlpha and not "alpha" in ptr_charset: continue
# Increment the fptr counter
# Get pointer disassembly
insn_disas = idc.GetDisasmEx(call_ea, idaapi.GENDSM_FORCE_CODE)
# Add pointer to the list
ptr = Ptr(m.file, ptr_ea, self.ptrOffset, ptr_charset, call_ea, insn_disas)
ptrs.append(ptr)
###################################################################
# Cache Pointers to Pointers
ptr_ea_prefix_cache = dict()
if self.searchP2P:
# CACHE: Running repeated searches over the entire memory space is
# very expensive. Let's cache all of the addresses containing
# bytes corresponding to discovered function pointers in a
# single search and simply reference this cache for each
# function pointer. Specifically running idaapi.find_binary()
# is much more expensive than idaapi.dbg_read_memory().
#
# NOTE: For performance considerations, the cache works on a per
# module basis, but could be expanded for the entire memory
# space.
#
# prefix_offset - how many bytes of discovered function
# pointers to cache.
#
# Example: For function pointers 0x00401234, 0x00404321, 0x000405678
# we are going to use prefix_offset 2, so we will cache all of the
# values located at addresses 0x0040XXXX
if self.sploiter.addr64:
pack_format = "<Q"
addr_bytes = 8
prefix_offset = 6
else:
pack_format = "<I"
addr_bytes = 4
prefix_offset = 2
# Set of unique N-byte address prefixes to search in memory
ea_prefix_set = set()
for ptr in ptrs:
ptr_ea = ptr.ptr_ea
ptr_bytes = struct.pack(pack_format, ptr_ea)
ptr_bytes = ptr_bytes[-prefix_offset:]
ea_prefix_set.add(ptr_bytes)
# Search the module for all bytes corresponding to the prefix
# and use them as candidates for pointers-to-pointers
for ea_prefix in ea_prefix_set:
# NOTE: Make sure you search using 44 33 22 11 format and not 11223344
ea_prefix_str = " ".join(["%02x" % ord(b) for b in ea_prefix])
# Initialize search parameters for a given module
ea = m.addr
maxea = m.addr + m.size
while True:
ea = idaapi.find_binary(ea + 1, maxea, ea_prefix_str, 16, idaapi.SEARCH_DOWN)
if ea == idaapi.BADADDR: break
p2p_ea = ea - (addr_bytes - prefix_offset)
dbg_mem = read_module_memory(p2p_ea, addr_bytes)
ptr_ea_prefix = unpack(pack_format, dbg_mem)[0]
if ptr_ea_prefix in ptr_ea_prefix_cache:
ptr_ea_prefix_cache[ptr_ea_prefix].add(p2p_ea)
else:
ptr_ea_prefix_cache[ptr_ea_prefix] = set([p2p_ea, ])
# Detect search cancellation, but allow the loop below
# to run to create already cached/found function pointers
# Canceled
if breakFlag or idaapi.wasBreak():
breakFlag = True
break
# Canceled
if breakFlag or idaapi.wasBreak():
breakFlag = True
break
###################################################################
# Locate Pointer to Pointers
for ptr in ptrs:
ptr_ea = ptr.ptr_ea
# Locate pointers-to-pointers for a given function pointer in the cache
if self.searchP2P and ptr_ea in ptr_ea_prefix_cache:
for p2p_ea in ptr_ea_prefix_cache[ptr_ea]:
# Apply pointer-to-pointer offset
p2p_ea -= self.p2pOffset
p2p_charset = self.sploiter.get_ptr_charset(p2p_ea)
# Filter the pointer
if self.filterP2P:
if p2p_charset == None: continue
if self.ptrNonull and not "nonull" in p2p_charset: continue
if self.ptrUnicode and not "unicode" in p2p_charset: continue
if self.ptrAscii and not "ascii" in p2p_charset: continue
if self.ptrAsciiPrint and not "asciiprint" in p2p_charset: continue
if self.ptrAlphaNum and not "alphanum" in p2p_charset: continue
if self.ptrNum and not "numeric" in p2p_charset: continue
if self.ptrAlpha and not "alpha" in p2p_charset: continue
# Copy existing pointer object to modify it for the particular p
p2p = copy.copy(ptr)
p2p.p2p_ea = p2p_ea
p2p.p2p_offset = self.p2pOffset
p2p.p2p_charset = p2p_charset
# Apppend p2p specific pointer object to the global list
self.ptrs.append(p2p)
# Exceeded maximum number of pointers
if self.maxPtrs and len(self.ptrs) >= self.maxPtrs:
breakFlag = True
print "[idasploiter] Maximum number of pointers exceeded."
break
# Simply append pointer object to the global list
else:
self.ptrs.append(ptr)
# Exceeded maximum number of pointers
if self.maxPtrs and len(self.ptrs) >= self.maxPtrs:
breakFlag = True
print "[idasploiter] Maximum number of pointers exceeded."
break
if breakFlag or idaapi.wasBreak():
breakFlag = True
break
# Canceled
# NOTE: Only works when started from GUI not script.
if breakFlag or idaapi.wasBreak():
breakFlag = True
print "[idasploiter] Canceled."
break
print "[idasploiter] Found %d total pointers." % len(self.ptrs)
idaapi.hide_wait_box()
###############################################################################
# ROP Search Engine
class x86_Rop(Rop):
def __init__(self, sploiter):
Rop.__init__(self, sploiter)
# Extra bytes to read to ensure correct decoding of
# RETN, RETN imm16, CALL /2, and JMP /4 instructions.
self.dbg_read_extra = 6 # FF + ModR/M + SIB + disp32
self.insn_arithmetic_ops = ["inc", "dec", "neg", "add", "sub", "mul", "imul", "div", "idiv", "adc", "sbb",
"lea"]
self.insn_bit_ops = ["not", "and", "or", "xor", "shr", "shl", "sar", "sal", "shld", "shrd", "ror", "rcr", "rcl"]
def get_o_reg_name(self, insn, n):
reg_num = insn.Operands[n].reg
reg_name = self.regnames[reg_num]
# NOTE: IDA's x86/x86-64 regname array contains only register root names
# (e.g ax,cx,dx,etc.). However we can still figure out exact register
# size by looking at the operand 'dtyp' property.
if reg_num < 8:
# 32-bit register
if insn.Operands[n].dtyp == idaapi.dt_dword:
reg_name = 'e' + reg_name
# 64-bit register
elif insn.Operands[n].dtyp == idaapi.dt_qword:
reg_name = 'r' + reg_name
# 16-bit register otherwise
return reg_name
def search_retns(self):
if not self.debug: print("found %d modules" % len(self.modules))
for m in self.modules:
# Iterate over segments in the module
# BUG: Iterating over all loaded segments is more stable than looking up by address
if not self.debug: print("found %d segments" % idaapi.get_segm_qty())
for n in xrange(idaapi.get_segm_qty()):
seg = idaapi.getnseg(n)
# Locate executable segments in a selected modules
# NOTE: Each module may have multiple executable segments
if seg and seg.startEA >= m.addr and seg.endEA <= (m.addr + m.size):
# If the debugger is attached then we can check if the segment is executable, else
# just check if it is code or not.
if idaapi.dbg_can_query() and idaapi.get_process_state() < 0:
if seg.perm & idaapi.SEGPERM_EXEC == 0:
continue
elif seg.type & idaapi.SEG_CODE == 0:
continue
#######################################################
# Search for ROP gadgets
if self.searchRop:
# Search all instances of RETN
ea = seg.startEA
while True:
ea = idaapi.find_binary(ea + 1, seg.endEA, "C3", 16, idaapi.SEARCH_DOWN)
if ea == idaapi.BADADDR: break
self.retns.append((ea, m.file))
# Search all instances of RETN imm16
ea = seg.startEA
while True:
ea = idaapi.find_binary(ea + 1, seg.endEA, "C2", 16, idaapi.SEARCH_DOWN)
if ea == idaapi.BADADDR: break
# Read imm16 value and filter large values
retn_imm16 = read_module_memory(ea + 1, 0x2)
retn_imm16 = unpack("<H", retn_imm16)[0]
if retn_imm16 <= self.maxRetnImm:
self.retns.append((ea, m.file))
#######################################################
# Search for JOP gadgets
if self.searchJop:
# Search all instances of JMP reg (FF /4) and CALL reg (FF /2)
ea = seg.startEA
while True:
ea = idaapi.find_binary(ea + 1, seg.endEA, "FF", 16, idaapi.SEARCH_DOWN)
if ea == idaapi.BADADDR: break
# Read possible ModR/M, SIB, and IMM8/IMM32 bytes
jop = read_module_memory(ea + 1, 0x6)
if jop == None or len(jop) == 0:
continue
###################################################
# JMP/CALL reg
if jop[0] in ["\xe0", "\xe1", "\xe2", "\xe3", "\xe4", "\xe5", "\xe6", "\xe7",
"\xd0", "\xd1", "\xd2", "\xd3", "\xd4", "\xd5", "\xd6", "\xd7"]:
self.retns.append((ea, m.file))
###################################################
# JMP/CALL [reg] no SIB
# NOTE: Do not include pure [disp] instruction.
# JMP/CALL [reg] no *SP,*BP
elif jop[0] in ["\x20", "\x21", "\x22", "\x23", "\x26", "\x27",
"\x10", "\x11", "\x12", "\x13", "\x16", "\x17"]:
self.retns.append((ea, m.file))
# JMP/CALL [reg + imm8] no *SP
elif jop[0] in ["\x60", "\x61", "\x62", "\x63", "\x65", "\x66", "\x67",
"\x50", "\x51", "\x52", "\x53", "\x55", "\x56", "\x57"]:
jop_imm8 = jop[1]
jop_imm8 = unpack("b", jop_imm8)[0] # signed
if jop_imm8 <= self.maxJopImm:
self.retns.append((ea, m.file))
# JMP/CALL [reg + imm32] no *SP
elif jop[0] in ["\xa0", "\xa1", "\xa2", "\xa3", "\xa5", "\xa6", "\xa7",
"\x90", "\x91", "\x92", "\x93", "\x95", "\x96", "\x97"]:
jop_imm32 = jop[1:5]
jop_imm32 = unpack("<i", jop_imm32)[0] # signed
if jop_imm32 <= self.maxJopImm:
self.retns.append((ea, m.file))
###################################################
# JMP/CALL [reg] with SIB
# NOTE: Do no include pure [disp] instructions in SIB ([*] - none)
elif (jop[0] in ["\x24", "\x64", "\xa4"] and not jop[1] in ["\x25", "\x65", "\xad",
"\xe5"]) or \
(jop[0] in ["\x14", "\x54", "\x94"] and not jop[1] in ["\x25", "\x65", "\xad",
"\xe5"]):
# Check for displacement
if jop[0] in ["\x64", "\x54"]:
jop_imm8 = jop[2]
jop_imm8 = unpack("b", jop_imm8)[0] # signed
if jop_imm8 <= self.maxJopImm:
self.retns.append((ea, m.file))
elif jop[0] in ["\xa4", "\x94"]:
jop_imm32 = jop[2:6]
jop_imm32 = unpack("<i", jop_imm32)[0] # signed
if jop_imm32 <= self.maxJopImm:
self.retns.append((ea, m.file))
else:
self.retns.append((ea, m.file))
print "[idasploiter] Found %d returns" % len(self.retns)
def search_gadgets(self):
count_total = len(self.retns)
count_notify = 0
count_curr = 0
# BUG: A separate flag is used to track user canceling the search,
# because multiple calls to idaapi.wasBreak() do not properly
# detect cancellations.
breakFlag = False
# Show wait dialog
if not self.debug: idaapi.show_wait_box("Searching gadgets: 00%%%%")
for (ea_end, module) in self.retns:
# Flush the gadgets cache for each new retn pointer
self.gadgets_cache = dict()
# Flush memory cache for each new retn pointer
self.dbg_mem_cache = None
# CACHE: It is faster to read as much memory in one blob than to make incremental reads backwards.
# Try to read and cache self.maxRopOffset bytes back. In cases where it is not possible,
# then simply try to read the largest chunk.
# NOTE: Read a bit extra to cover correct decoding of RETN, RETN imm16, CALL /2, and JMP /4 instructions.
for i in range(self.maxRopOffset):
self.dbg_mem_cache = read_module_memory(ea_end - self.maxRopOffset + i,
self.maxRopOffset - i + self.dbg_read_extra)
if self.dbg_mem_cache != None:
break
# Check to make sure we have actual data to work with.
if self.dbg_mem_cache == None:
continue
# Search all possible gadgets up to maxoffset bytes back
# NOTE: Try all byte combinations to capture longer/more instructions
# even with bad bytes in the middle.
for i in range(1, len(self.dbg_mem_cache) - self.dbg_read_extra):
ea = ea_end - i
# Get pointer charset
ptr_charset = self.sploiter.get_ptr_charset(ea)
# Filter the pointer
if ptr_charset == None: continue
if self.ptrNonull and not "nonull" in ptr_charset: continue
if self.ptrUnicode and not "unicode" in ptr_charset: continue
if self.ptrAscii and not "ascii" in ptr_charset: continue
if self.ptrAsciiPrint and not "asciiprint" in ptr_charset: continue
if self.ptrAlphaNum and not "alphanum" in ptr_charset: continue
if self.ptrNum and not "numeric" in ptr_charset: continue
if self.ptrAlpha and not "alpha" in ptr_charset: continue
# Try to build a gadget at the pointer
gadget = self.build_gadget(ea, ea_end)
# Successfully built the gadget
if gadget:
# Populate gadget object with more data
gadget.address = ea
gadget.module = module
gadget.ptr_charset = ptr_charset
# Filter gadgets with too many instruction
if gadget.size > self.maxRopSize:
break
# Append newly built gadget
self.gadgets.append(gadget)
self.gadgets_cache[ea] = gadget
# Exceeded maximum number of gadgets
if self.maxRops and len(self.gadgets) >= self.maxRops:
breakFlag = True
print "[idasploiter] Maximum number of gadgets exceeded."
break
else:
self.gadgets_cache[ea] = None
if breakFlag or idaapi.wasBreak():
breakFlag = True
break
# Canceled
# NOTE: Only works when started from GUI not script.
if breakFlag or idaapi.wasBreak():
breakFlag = True
print "[idasploiter] Canceled."
break
# Progress report
if not self.debug and count_curr >= count_notify:
# NOTE: Need to use %%%% to escape both Python and IDA's format strings
idaapi.replace_wait_box("Searching gadgets: %02d%%%%" % (count_curr * 100 / count_total))
count_notify += 0.10 * count_total
count_curr += 1
print "[idasploiter] Found %d gadgets." % len(self.gadgets)
if not self.debug: idaapi.hide_wait_box()
# Attempt to build a gadget at the provided start address
# by verifying it properly terminates at the expected RETN.
def build_gadget(self, ea, ea_end):
instructions = list()
chg_registers = set()
use_registers = set()
operations = set()
pivot = 0
# Process each instruction in the gadget
while ea <= ea_end:
###################################################################
# Gadget Level Cache:
#
# Locate a gadget (failed or built) starting at this address.
# If one is located, then we don't need to process any further
# instructions and just get necessary data from the cached
# gadget to never have to process the same address twice.
if ea in self.gadgets_cache:
# Check if the gadget was build successfully
gadget_cache = self.gadgets_cache[ea]
# Build the reset of the gadget from cache
if gadget_cache:
for insn in gadget_cache.instructions:
instructions.append(insn)
for reg in gadget_cache.chg_registers:
chg_registers.add(reg)
for reg in gadget_cache.use_registers:
use_registers.add(reg)
for op in gadget_cache.operations:
operations.add(op)
pivot += gadget_cache.pivot
gadget = Gadget(instructions, pivot, operations, chg_registers, use_registers)
return gadget
# Previous attempt to build gadget at this address failed
else:
return None
# Process new instruction
else:
# Instruction length
# NOTE: decode_insn also sets global idaapi.cmd
# which contains insn_t structure
insn_size = idaapi.decode_insn(ea)
# Check successful decoding of the instruction
if insn_size:
# Decoded instruction is too big to be a RETN or RETN imm16
if ea + insn_size > ea_end + self.dbg_read_extra:
return None
###############################################################
# Instruction Level Cache
#
# Most instructions are repetitive so we can just cache
# unique byte combinations to avoid costly decoding more
# than once
# Read instruction from memory cache
dbg_mem_offset = ea - (ea_end - (len(self.dbg_mem_cache) - self.dbg_read_extra))
dbg_mem = self.dbg_mem_cache[dbg_mem_offset:dbg_mem_offset + insn_size]
# Create instruction cache if it doesn't already exist
if not dbg_mem in self.insn_cache:
###########################################################
# Decode instruction
###########################################################
# Get global insn_t structure describing the instruction
# NOTE: copy() is expensive, so we keep this single-threaded
insn = idaapi.cmd
#######################################################
# Decode and Cache instruction characteristics
self.insn_cache[dbg_mem] = self.decode_instruction(insn, ea, ea_end)
##################################################################
# Retrieve cached instruction and apply it to the gadget
# Check that cached instruction contains valid data
if self.insn_cache[dbg_mem]:
# Retrieve basic instruction characteristics
insn_mnem = self.insn_cache[dbg_mem]["insn_mnem"]
insn_disas = self.insn_cache[dbg_mem]["insn_disas"]
instructions.append(insn_disas)
#######################################################
# Expected ending instruction of the gadget
if ea == ea_end:
gadget = Gadget(instructions, pivot, operations, chg_registers, use_registers)
return gadget
#######################################################
# Filter out of place ROP/JOP/COP terminators
# NOTE: retn/jmp/call are allowed, but only in the last position
# Unexpected return instruction
elif insn_mnem == "retn":
return None
# Unexpected call/jmp instruction
elif insn_mnem in ["jmp", "call"]:
return None
#######################################################
# Add instruction instruction characteristics to the gadget
else:
for reg in self.insn_cache[dbg_mem]["insn_chg_registers"]:
chg_registers.add(reg)
for reg in self.insn_cache[dbg_mem]["insn_use_registers"]:
use_registers.add(reg)
for op in self.insn_cache[dbg_mem]["insn_operations"]:
operations.add(op)
pivot += self.insn_cache[dbg_mem]["insn_pivot"]
# Previous attempt to decode the instruction invalidated the gadget
else:
return None
###############################################################
# Next instruction
# NOTE: This is outside cache
ea += insn_size
###################################################################
# Failed decoding of the instruction
# NOTE: Gadgets may have bad instructions in the middle which
# can be tolerated as long as we can find a useful instruction
# further out.
else:
# HACK: IDA does not disassemble "\x00\x00" unless you enable
# "Disassemble zero opcode instructions" in Processor Options.
# Since this option is normally disabled, I will attempt
# to get this instruction manually.
# Read two bytes from memory cache at current instruction candidate
dbg_mem_offset = ea - (ea_end - self.maxRopOffset)
dbg_mem = self.dbg_mem_cache[dbg_mem_offset:dbg_mem_offset + 2]
# BUGFIX: For some reason the length of dbg_mem may be 0 (perhaps we ran out of cache?), so
# verify the size is valid before using the buffer.
if len(dbg_mem) != 2:
return None
# Compare to two zero bytes
if dbg_mem[:2] == "\x00\x00":
if self.sploiter.addr64:
instructions.append("add [rax],al")
else:
instructions.append("add [eax],al")
use_registers.add("al")
operations.add("reg-to-mem")
ea += 2
# "MOV Sreg, r/m16" instructions will result in illegal instruction exception: c000001d
# or the memory couldn't be read exception: c0000005 which we don't want in our gadgets.
elif dbg_mem[0] == "\x8E":
return None
# Record a "bad byte" if allowed
elif dbg_mem and not self.ropNoBadBytes:
byte = dbg_mem[0]
instructions.append("db %sh" % binascii.hexlify(byte))
ea += 1
# Invalidate the gadget
else:
return None
# Failed to build a gadget, because RETN instruction was not found
else:
return None
###############################################################
# Decode instruction
def decode_instruction(self, insn, ea, ea_end):
# Instruction specific characteristics
insn_chg_registers = set()
insn_use_registers = set()
insn_operations = set()
insn_pivot = 0
# Instruction feature
#
# instruc_t.feature
#
# CF_STOP = 0x00001 # Instruction doesn't pass execution to the next instruction
# CF_CALL = 0x00002 # CALL instruction (should make a procedure here)
# CF_CHG1 = 0x00004 # The instruction modifies the first operand
# CF_CHG2 = 0x00008 # The instruction modifies the second operand
# CF_CHG3 = 0x00010 # The instruction modifies the third operand
# CF_CHG4 = 0x00020 # The instruction modifies 4 operand
# CF_CHG5 = 0x00040 # The instruction modifies 5 operand
# CF_CHG6 = 0x00080 # The instruction modifies 6 operand
# CF_USE1 = 0x00100 # The instruction uses value of the first operand
# CF_USE2 = 0x00200 # The instruction uses value of the second operand
# CF_USE3 = 0x00400 # The instruction uses value of the third operand
# CF_USE4 = 0x00800 # The instruction uses value of the 4 operand
# CF_USE5 = 0x01000 # The instruction uses value of the 5 operand
# CF_USE6 = 0x02000 # The instruction uses value of the 6 operand
# CF_JUMP = 0x04000 # The instruction passes execution using indirect jump or call (thus needs additional analysis)
# CF_SHFT = 0x08000 # Bit-shift instruction (shl,shr...)
# CF_HLL = 0x10000 # Instruction may be present in a high level language function.
insn_feature = insn.get_canon_feature()
# Instruction mnemonic name
insn_mnem = insn.get_canon_mnem()
# if insn_mnem in self.mnems: self.mnems[insn_mnem] += 1
# else: self.mnems[insn_mnem] = 1
# Get instruction operand types
#
# op_t.type
# Description Data field
# o_void = 0 # No Operand ----------
# o_reg = 1 # General Register (al,ax,es,ds...) reg
# o_mem = 2 # Direct Memory Reference (DATA) addr
# o_phrase = 3 # Memory Ref [Base Reg + Index Reg] phrase
# o_displ = 4 # Memory Reg [Base Reg + Index Reg + Displacement] phrase+addr
# o_imm = 5 # Immediate Value value
# o_far = 6 # Immediate Far Address (CODE) addr
# o_near = 7 # Immediate Near Address (CODE) addr
insn_op1 = insn.Operands[0].type
insn_op2 = insn.Operands[1].type
###############################################################
# Filter gadget
###############################################################
# Do not filter ROP, JOP, COP, always decode them
# NOTE: A separate check must be done to check if they are out of place.
if not insn_mnem in ["retn", "jmp", "call"]:
# Filter gadgets with instructions that don't forward execution to the next address
if insn_feature & idaapi.CF_STOP:
return None
# Filter gadgets with instructions in a bad list
elif insn_mnem in self.ropBadMnems:
return None
# Filter gadgets with jump instructions
# Note: conditional jumps may still be useful if we can
# set flags prior to calling them.
elif not self.ropAllowJcc and insn_mnem[0] == "j":
return None
###############################################################
# Get disassembly
###############################################################
# NOTE: GENDSM_FORCE_CODE ensures correct decoding
# of split instructions.
insn_disas = idc.GetDisasmEx(ea, idaapi.GENDSM_FORCE_CODE)
insn_disas = insn_disas.partition(';')[0] # Remove comments from disassembly
insn_disas = ' '.join(insn_disas.split()) # Remove extraneous space from disassembly
###############################################################
# Analyze instruction
###############################################################
# Standalone instruction
if insn_op1 == idaapi.o_void:
# TODO: Determine and test how these instructions affect the stack
# in 32-bit and 64-bit modes.
if insn_mnem in ["pusha", "pushad", "popa", "popad", "pushf", "pushfd", "pushfq", "popf", "popfd", "popfq"]:
insn_operations.add("stack")
if insn_mnem in ["popa", "popad"]:
insn_pivot += 7 * 4
elif insn_mnem in ["pusha", "pushad"]:
insn_pivot -= 8 * 4
elif insn_mnem in ["popf", "popfd"]:
insn_pivot += 4
elif insn_mnem in ["pushf", "pushfd"]:
insn_pivot -= 4
elif insn_mnem == "popfq": # TODO: Needs testing
insn_pivot += 8
elif insn_mnem == "pushfq": # TODO: Needs testing
insn_pivot -= 8
# Single operand instruction
elif insn_op2 == idaapi.o_void:
# Single operand register
if insn_op1 == idaapi.o_reg:
insn_operations.add("one-reg")
if insn_feature & idaapi.CF_CHG1:
reg_name = self.get_o_reg_name(insn, 0)
insn_chg_registers.add(reg_name)
# Check for stack operation
if reg_name[1:] == "sp":
insn_operations.add("stack")
if insn_mnem == "inc":
insn_pivot += 1
elif insn_mnem == "dec":
insn_pivot -= 1
elif insn_feature & idaapi.CF_USE1:
reg_name = self.get_o_reg_name(insn, 0)
insn_use_registers.add(reg_name)
# Single operand immediate
elif insn_op1 == idaapi.o_imm:
insn_operations.add("one-imm")
# Single operand reference
# TODO: determine the [reg + ...] value if present
elif insn_op1 == idaapi.o_phrase or insn_op1 == idaapi.o_displ:
insn_operations.add("one-mem")
# PUSH/POP mnemonic with a any operand type
if insn_mnem in ["push", "pop"]:
insn_operations.add("stack")
# Adjust pivot based on operand size (32bit vs 64bit)
if insn_mnem == "pop":
if insn.Operands[0].dtyp == idaapi.dt_dword:
insn_pivot += 4
elif insn.Operands[0].dtyp == idaapi.dt_qword:
insn_pivot += 8
elif insn_mnem == "push":
if insn.Operands[0].dtyp == idaapi.dt_dword:
insn_pivot -= 4
elif insn.Operands[0].dtyp == idaapi.dt_qword:
insn_pivot -= 8
# Check for arithmetic operation:
if insn_mnem in self.insn_arithmetic_ops:
insn_operations.add("math")
# Check for bit-wise operations:
if insn_mnem in self.insn_bit_ops:
insn_operations.add("bit")
# Two operand instruction
else:
# Check for arithmetic operations
if insn_mnem in self.insn_arithmetic_ops:
insn_operations.add("math")
# Check for bit-wise operations
if insn_mnem in self.insn_bit_ops:
insn_operations.add("bit")
# Two operand instruction with the first operand a register
if insn_op1 == idaapi.o_reg:
reg_name = self.get_o_reg_name(insn, 0)
if insn_feature & idaapi.CF_CHG1:
insn_chg_registers.add(reg_name)
# Check for stack operation
if reg_name[1:] == "sp":
insn_operations.add("stack")
# Determine stack pivot distance
if insn_op2 == idaapi.o_imm:
# NOTE: adb and sbb may also be useful, but let the user
# determine their use by locating the operations "stack"
if insn_mnem == "add":
insn_pivot += insn.Operands[1].value
elif insn_mnem == "sub":
insn_pivot -= insn.Operands[1].value
# Check for operations
if insn_op2 == idaapi.o_reg:
insn_operations.add("reg-to-reg")
elif insn_op2 == idaapi.o_imm:
insn_operations.add("imm-to-reg")
# TODO: determine the [reg + ...] value if present
elif insn_op2 == idaapi.o_phrase or insn_op2 == idaapi.o_displ:
insn_operations.add("mem-to-reg")
if insn_feature & idaapi.CF_USE1:
insn_use_registers.add(reg_name)
# Two operand instruction with the second operand a register
if insn_op2 == idaapi.o_reg:
reg_name = self.get_o_reg_name(insn, 1)
if insn_feature & idaapi.CF_CHG2:
insn_chg_registers.add(reg_name)
# Check for stack operation
if reg_name[1:] == "sp":
insn_operations.add("stack")
if insn_feature & idaapi.CF_USE2:
insn_use_registers.add(reg_name)
# Check for operations
# TODO: determine the [reg + ...] value if present
if insn_op1 == idaapi.o_phrase or insn_op1 == idaapi.o_displ:
insn_operations.add("reg-to-mem")
# Build instruction dictionary
insn = dict()
insn["insn_mnem"] = insn_mnem
insn["insn_disas"] = insn_disas
insn["insn_operations"] = insn_operations
insn["insn_chg_registers"] = insn_chg_registers
insn["insn_use_registers"] = insn_use_registers
insn["insn_pivot"] = insn_pivot
return insn
###############################################################################
# Sploiter Engine
class x86_Sploiter(Sploiter):
def __init__(self):
Sploiter.__init__(self)
# Initialize fields specific to this architecture.
self.bad_instructions = "leave, int, into, enter, syscall, sysenter, sysexit, sysret, in, out, loop, loope, loopne, lock, rep, repe, repz, repne, repnz"
# Select general purpose registers for a given architecture
if self.addr64:
self.reg_list = ["RAX", "RBX", "RCX", "RDX", "RSP", "RBP", "RSI", "RDI", "RIP", "R8", "R9", "R10", "R11",
"R12", "R13", "R14", "R15"]
else:
self.reg_list = ["EAX", "EBX", "ECX", "EDX", "ESP", "EBP", "ESI", "EDI", "EIP"]
def is_func_ptr_supported(self):
return True
def get_func_ptr_instance(self):
# Check processor type and create the function pointer class.
if idaapi.ph.id == idaapi.PLFM_386:
return x86_FuncPtr(self)
else:
return None
def get_rop_instance(self):
# Check the processor type and create the rop class.
if idaapi.ph.id == idaapi.PLFM_386:
return x86_Rop(self)
else:
return None
class idasploiter_x86_t(plugin_t):
flags = idaapi.PLUGIN_UNL
comment = ""
help = ""
wanted_name = ""
wanted_hotkey = ""
def init(self):
return idaapi.PLUGIN_SKIP
def run(self, arg):
pass
def term(self):
pass
def PLUGIN_ENTRY():
return idasploiter_x86_t()
gitextract_x8ih4kwe/ ├── README.txt ├── idasploiter.py ├── idasploiter_ppc.py └── idasploiter_x86.py
SYMBOL INDEX (172 symbols across 3 files)
FILE: idasploiter.py
class IMAGE_DOS_HEADER (line 207) | class IMAGE_DOS_HEADER(Structure):
class IMAGE_FILE_HEADER (line 229) | class IMAGE_FILE_HEADER(Structure):
class IMAGE_DATA_DIRECTORY (line 240) | class IMAGE_DATA_DIRECTORY(Structure):
class IMAGE_OPTIONAL_HEADER (line 246) | class IMAGE_OPTIONAL_HEADER(Structure):
class IMAGE_OPTIONAL_HEADER64 (line 281) | class IMAGE_OPTIONAL_HEADER64(Structure):
class IMAGE_LOAD_CONFIG_DIRECTORY (line 315) | class IMAGE_LOAD_CONFIG_DIRECTORY(Structure):
class IMAGE_LOAD_CONFIG_DIRECTORY64 (line 339) | class IMAGE_LOAD_CONFIG_DIRECTORY64(Structure):
function is_processor_supported (line 364) | def is_processor_supported():
function read_module_memory (line 371) | def read_module_memory(addr, size):
class Module (line 381) | class Module():
method __init__ (line 383) | def __init__(self, name, size, base, rebase_to):
class PE (line 417) | class PE():
method __init__ (line 429) | def __init__(self, base):
method isDynamicBase (line 507) | def isDynamicBase(self):
method isNXCompat (line 516) | def isNXCompat(self):
method isSafeSEH (line 525) | def isSafeSEH(self):
method isGS (line 548) | def isGS(self):
class Ptr (line 593) | class Ptr():
method __init__ (line 595) | def __init__(self, module, ptr_ea, ptr_offset, ptr_charset, call_ea, i...
class FuncPtr (line 615) | class FuncPtr():
method __init__ (line 617) | def __init__(self,sploiter):
method search_pointers (line 624) | def search_pointers(self):
class Gadget (line 631) | class Gadget():
method __init__ (line 633) | def __init__(self, instructions, pivot, operations, chg_registers, use...
class Rop (line 653) | class Rop():
method __init__ (line 655) | def __init__(self, sploiter):
method get_o_reg_name (line 674) | def get_o_reg_name(self, insn, n):
method search_retns (line 679) | def search_retns(self):
method search_gadgets (line 684) | def search_gadgets(self):
method build_gadget (line 691) | def build_gadget(self, ea, ea_end):
method decode_instruction (line 699) | def decode_instruction(self, insn, ea, ea_end):
class Sploiter (line 707) | class Sploiter():
method __init__ (line 709) | def __init__(self):
method get_func_ptr_instance (line 736) | def get_func_ptr_instance(self):
method get_rop_instance (line 741) | def get_rop_instance(self):
method get_ptr_charset (line 746) | def get_ptr_charset(self, ea):
method process_modules (line 831) | def process_modules(self):
method show_modules_view (line 861) | def show_modules_view(self):
method process_rop (line 865) | def process_rop(self, select_list = None):
method process_funcptr (line 937) | def process_funcptr(self, select_list = None):
method pattern_create (line 1007) | def pattern_create(self):
method pattern_detect (line 1016) | def pattern_detect(self, debugger=True):
method process_compare (line 1025) | def process_compare(self, debugger=True):
class ModuleView (line 1043) | class ModuleView(Choose2):
method __init__ (line 1047) | def __init__(self, sploiter, embedded = False):
method show (line 1081) | def show(self):
method refreshitems (line 1097) | def refreshitems(self):
method OnCommand (line 1109) | def OnCommand(self, n, cmd_id):
method OnSelectLine (line 1175) | def OnSelectLine(self, n):
method OnGetLine (line 1178) | def OnGetLine(self, n):
method OnGetIcon (line 1181) | def OnGetIcon(self, n):
method OnClose (line 1194) | def OnClose(self):
method OnGetSize (line 1198) | def OnGetSize(self):
method OnRefresh (line 1201) | def OnRefresh(self, n):
method OnActivate (line 1205) | def OnActivate(self):
class RopForm (line 1215) | class RopForm(Form):
method __init__ (line 1217) | def __init__(self, sploiter, select_list = None):
method OnFormChange (line 1262) | def OnFormChange(self, fid):
class RopView (line 1294) | class RopView(Choose2):
method __init__ (line 1298) | def __init__(self, sploiter):
method show (line 1334) | def show(self):
method refreshitems (line 1349) | def refreshitems(self):
method OnCommand (line 1365) | def OnCommand(self, n, cmd_id):
method OnSelectLine (line 1401) | def OnSelectLine(self, n):
method OnGetLine (line 1404) | def OnGetLine(self, n):
method OnClose (line 1407) | def OnClose(self):
method OnGetSize (line 1413) | def OnGetSize(self):
method OnRefresh (line 1416) | def OnRefresh(self, n):
method OnActivate (line 1420) | def OnActivate(self):
class RopChainView (line 1430) | class RopChainView(Choose2):
method __init__ (line 1434) | def __init__(self, ropview):
method refreshitems (line 1464) | def refreshitems(self):
method OnCommand (line 1482) | def OnCommand(self, n, cmd_id):
method OnSelectLine (line 1565) | def OnSelectLine(self, n):
method OnGetLine (line 1568) | def OnGetLine(self, n):
method OnClose (line 1571) | def OnClose(self):
method OnGetSize (line 1574) | def OnGetSize(self):
method OnRefresh (line 1577) | def OnRefresh(self, n):
method OnRefreshed (line 1581) | def OnRefreshed(self):
method OnActivate (line 1585) | def OnActivate(self):
class RopChainForm (line 1592) | class RopChainForm(Form):
method __init__ (line 1594) | def __init__(self, ropview):
method OnFormChange (line 1624) | def OnFormChange(self, fid):
method OnButtonInsert (line 1642) | def OnButtonInsert(self, code=0):
method OnButtonGenerate (line 1668) | def OnButtonGenerate(self, code=0):
class PtrForm (line 1767) | class PtrForm(Form):
method __init__ (line 1769) | def __init__(self, sploiter, select_list = None):
method OnFormChange (line 1806) | def OnFormChange(self, fid):
class PtrView (line 1831) | class PtrView(Choose2):
method __init__ (line 1835) | def __init__(self, sploiter):
method show (line 1875) | def show(self):
method refreshitems (line 1898) | def refreshitems(self):
method OnCommand (line 1916) | def OnCommand(self, n, cmd_id):
method OnSelectLine (line 1979) | def OnSelectLine(self, n):
method OnGetLine (line 1982) | def OnGetLine(self, n):
method OnClose (line 1985) | def OnClose(self):
method OnGetSize (line 1992) | def OnGetSize(self):
method OnRefresh (line 1995) | def OnRefresh(self, n):
method OnActivate (line 1999) | def OnActivate(self):
class PatternCreateForm (line 2009) | class PatternCreateForm(Form):
method __init__ (line 2011) | def __init__(self, sploiter):
method OnFormChange (line 2045) | def OnFormChange(self, fid):
method OnButtonGenerate (line 2068) | def OnButtonGenerate(self, code=0):
method pattern_create (line 2114) | def pattern_create(self, size):
method update_complete_pattern (line 2130) | def update_complete_pattern(self, c4, c3, c2, c1):
class PatternDetectForm (line 2149) | class PatternDetectForm(Form):
method __init__ (line 2151) | def __init__(self, sploiter, debugger = True):
method OnFormChange (line 2210) | def OnFormChange(self, fid):
method OnButtonDetect (line 2238) | def OnButtonDetect(self, code=0):
method verbalize_pattern (line 2267) | def verbalize_pattern(self, pattern, address=False, string=False):
method pattern_offset (line 2330) | def pattern_offset(self, pattern):
method update_complete_pattern (line 2345) | def update_complete_pattern(self, c4, c3, c2, c1):
class CompareForm (line 2367) | class CompareForm(Form):
method __init__ (line 2369) | def __init__(self, sploiter, debugger = True):
method OnFormChange (line 2400) | def OnFormChange(self, fid):
method OnButtonCompare (line 2412) | def OnButtonCompare(self, code=0):
class SploitManager (line 2563) | class SploitManager():
method __init__ (line 2566) | def __init__(self):
method is_architecture_supported (line 2584) | def is_architecture_supported(self):
method add_menu_item_helper (line 2590) | def add_menu_item_helper(self, menupath, name, hotkey, flags, pyfunc, ...
method add_menu_items (line 2600) | def add_menu_items(self):
method del_menu_items (line 2615) | def del_menu_items(self):
method check_debugger (line 2623) | def check_debugger(self):
method show_modules_view (line 2633) | def show_modules_view(self):
method show_rop_view (line 2639) | def show_rop_view(self):
method show_funcptr_view (line 2645) | def show_funcptr_view(self):
method show_pattern_create (line 2650) | def show_pattern_create(self):
method show_pattern_detect (line 2654) | def show_pattern_detect(self):
method show_compare (line 2665) | def show_compare(self):
class idasploiter_t (line 2678) | class idasploiter_t(plugin_t):
method init (line 2686) | def init(self):
method run (line 2712) | def run(self, arg):
method term (line 2715) | def term(self):
function PLUGIN_ENTRY (line 2723) | def PLUGIN_ENTRY():
function idasploiter_main (line 2730) | def idasploiter_main():
FILE: idasploiter_ppc.py
class ppc_Rop (line 62) | class ppc_Rop(Rop):
method __init__ (line 64) | def __init__(self, sploiter):
method get_o_reg_name (line 76) | def get_o_reg_name(self, insn, n):
method search_retns (line 83) | def search_retns(self):
method search_gadgets (line 133) | def search_gadgets(self):
method build_gadget (line 241) | def build_gadget(self, ea, ea_end):
method decode_instruction (line 387) | def decode_instruction(self, insn, ea, ea_end):
class ppc_Sploiter (line 581) | class ppc_Sploiter(Sploiter):
method __init__ (line 582) | def __init__(self):
method is_func_ptr_supported (line 593) | def is_func_ptr_supported(self):
method get_func_ptr_instance (line 596) | def get_func_ptr_instance(self):
method get_rop_instance (line 601) | def get_rop_instance(self):
class idasploiter_ppc_t (line 606) | class idasploiter_ppc_t(plugin_t):
method init (line 614) | def init(self):
method run (line 617) | def run(self, arg):
method term (line 620) | def term(self):
function PLUGIN_ENTRY (line 623) | def PLUGIN_ENTRY():
FILE: idasploiter_x86.py
class x86_FuncPtr (line 62) | class x86_FuncPtr(FuncPtr):
method __init__ (line 63) | def __init__(self, sploiter):
method search_pointers (line 98) | def search_pointers(self):
class x86_Rop (line 361) | class x86_Rop(Rop):
method __init__ (line 362) | def __init__(self, sploiter):
method get_o_reg_name (line 374) | def get_o_reg_name(self, insn, n):
method search_retns (line 396) | def search_retns(self):
method search_gadgets (line 519) | def search_gadgets(self):
method build_gadget (line 629) | def build_gadget(self, ea, ea_end):
method decode_instruction (line 827) | def decode_instruction(self, insn, ea, ea_end):
class x86_Sploiter (line 1075) | class x86_Sploiter(Sploiter):
method __init__ (line 1076) | def __init__(self):
method is_func_ptr_supported (line 1090) | def is_func_ptr_supported(self):
method get_func_ptr_instance (line 1093) | def get_func_ptr_instance(self):
method get_rop_instance (line 1101) | def get_rop_instance(self):
class idasploiter_x86_t (line 1110) | class idasploiter_x86_t(plugin_t):
method init (line 1118) | def init(self):
method run (line 1121) | def run(self, arg):
method term (line 1124) | def term(self):
function PLUGIN_ENTRY (line 1127) | def PLUGIN_ENTRY():
Condensed preview — 4 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (200K chars).
[
{
"path": "README.txt",
"chars": 546,
"preview": "Welcome to IDA Sploiter, an exploit development and vulnerability research\nplugin for Hex-Ray's IDA Pro disassembler.\n\nT"
},
{
"path": "idasploiter.py",
"chars": 108758,
"preview": "#!/usr/bin/env python\r\n#\r\n# IDA Sploiter is an exploit development and vulnerability research environment\r\n# implemented"
},
{
"path": "idasploiter_ppc.py",
"chars": 26500,
"preview": "#!/usr/bin/env python\r\n#\r\n# IDA Sploiter is an exploit development and vulnerability research environment\r\n# implemented"
},
{
"path": "idasploiter_x86.py",
"chars": 50222,
"preview": "#!/usr/bin/env python\r\n#\r\n# IDA Sploiter is an exploit development and vulnerability research environment\r\n# implemented"
}
]
About this extraction
This page contains the full source code of the iphelix/ida-sploiter GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 4 files (181.7 KB), approximately 41.7k tokens, and a symbol index with 172 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.