Replace HexRaysCodeXplorer and HexraysInvertIf with HexRaysPyTools
This commit is contained in:
864
plugins/HexRaysPyTools/Core/TemporaryStructure.py
Normal file
864
plugins/HexRaysPyTools/Core/TemporaryStructure.py
Normal file
@@ -0,0 +1,864 @@
|
||||
import bisect
|
||||
import idc
|
||||
import idaapi
|
||||
import re
|
||||
import itertools
|
||||
# import PySide.QtCore as QtCore
|
||||
# import PySide.QtGui as QtGui
|
||||
import Cache
|
||||
from HexRaysPyTools.Cute import *
|
||||
import Const
|
||||
import Helper
|
||||
import VariableScanner
|
||||
import HexRaysPyTools.Api as Api
|
||||
from HexRaysPyTools.Forms import MyChoose
|
||||
|
||||
|
||||
SCORE_TABLE = dict((v, k) for k, v in enumerate(
|
||||
['unsigned __int8 *', 'unsigned __int8', '__int8 *', '__int8', '_BYTE', '_BYTE *', '_BYTE **', 'const char **',
|
||||
'signed __int16', 'unsigned __int16', '__int16', 'signed __int16 *', 'unsigned __int16 *', '__int16 *',
|
||||
'_WORD *', '_WORD **',
|
||||
'signed int*', 'signed int', 'unsigned int *', 'unsigned int', 'int **', 'char **', 'int *', 'void **',
|
||||
'int', '_DWORD *', 'char', '_DWORD', '_WORD', 'void *', 'char *']
|
||||
))
|
||||
|
||||
|
||||
def parse_vtable_name(address):
|
||||
name = idaapi.get_short_name(address)
|
||||
if idaapi.is_valid_typename(name):
|
||||
if name[0:3] == 'off':
|
||||
# off_XXXXXXXX case
|
||||
return "Vtable" + name[3:], False
|
||||
elif "table" in name:
|
||||
return name, True
|
||||
print "[Warning] Weird virtual table name -", name
|
||||
return "Vtable_" + name
|
||||
else:
|
||||
# Attempt to make nice and valid name from demangled RTTI name
|
||||
try:
|
||||
name = re.sub("^const ", "", name)
|
||||
sliced_names = name.split("::")
|
||||
name, for_part = "_for_".join(sliced_names[:-1]), sliced_names[-1]
|
||||
print name, for_part
|
||||
templates = re.search("<(.*)>", name)
|
||||
if templates:
|
||||
templates = templates.group(1)
|
||||
name = re.sub("<.*>", "", name)
|
||||
templates = re.sub("[^a-zA-Z0-9_*]", "_", templates)
|
||||
templates = re.sub("\*", "PTR", templates)
|
||||
name += '_' + templates
|
||||
|
||||
for_part = re.search("\{for `(.*)'\}", for_part)
|
||||
if for_part:
|
||||
for_part = for_part.group(1)
|
||||
name += '_' + for_part
|
||||
|
||||
return 'Vtable_' + name, True
|
||||
|
||||
except (AttributeError, IndexError):
|
||||
print "[Warning] Unable to parse virtual table name - "
|
||||
|
||||
return "Vtable_{0:X}".format(address), False
|
||||
|
||||
|
||||
class AbstractMember:
|
||||
def __init__(self, offset, scanned_variable, origin):
|
||||
"""
|
||||
Offset is the very very base of the structure
|
||||
Origin is from which offset of the base structure the variable have been scanned
|
||||
scanned_variable - information about context in which this variable was scanned. This is necessary for final
|
||||
applying type after packing or finalizing structure.
|
||||
|
||||
:param offset: int
|
||||
:param scanned_variable: ScannedVariable
|
||||
:param origin: int
|
||||
"""
|
||||
self.offset = offset
|
||||
self.origin = origin
|
||||
self.enabled = True
|
||||
self.is_array = False
|
||||
self.scanned_variables = {scanned_variable} if scanned_variable else set()
|
||||
self.tinfo = None
|
||||
|
||||
def type_equals_to(self, tinfo):
|
||||
return self.tinfo.equals_to(tinfo)
|
||||
|
||||
def switch_array_flag(self):
|
||||
self.is_array ^= True
|
||||
|
||||
def activate(self):
|
||||
pass
|
||||
|
||||
def set_enabled(self, enable):
|
||||
self.enabled = enable
|
||||
self.is_array = False
|
||||
|
||||
def has_collision(self, other):
|
||||
if self.offset <= other.offset:
|
||||
return self.offset + self.size > other.offset
|
||||
return other.offset + other.size >= self.offset
|
||||
|
||||
@property
|
||||
def score(self):
|
||||
""" More score of the member - it better suits as candidate for this offset """
|
||||
try:
|
||||
return SCORE_TABLE[self.type_name]
|
||||
except KeyError:
|
||||
if self.tinfo and self.tinfo.is_funcptr():
|
||||
return 0x1000 + len(self.tinfo.dstr())
|
||||
return 0xFFFF
|
||||
|
||||
@property
|
||||
def type_name(self):
|
||||
return self.tinfo.dstr()
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
size = self.tinfo.get_size()
|
||||
return size if size != idaapi.BADSIZE else 1
|
||||
|
||||
@property
|
||||
def font(self):
|
||||
return None
|
||||
|
||||
def __repr__(self):
|
||||
return hex(self.offset) + ' ' + self.type_name
|
||||
|
||||
def __eq__(self, other):
|
||||
""" I'm aware that it's dirty but have no time to refactor whole file to nice one """
|
||||
|
||||
if self.offset == other.offset and self.type_name == other.type_name:
|
||||
self.scanned_variables |= other.scanned_variables
|
||||
return True
|
||||
return False
|
||||
|
||||
__ne__ = lambda self, other: self.offset != other.offset or self.type_name != other.type_name
|
||||
__lt__ = lambda self, other: self.offset < other.offset or \
|
||||
(self.offset == other.offset and self.type_name < other.type_name)
|
||||
__le__ = lambda self, other: self.offset <= other.offset
|
||||
__gt__ = lambda self, other: self.offset > other.offset or \
|
||||
(self.offset == other.offset and self.type_name < other.type_name)
|
||||
__ge__ = lambda self, other: self.offset >= other.offset
|
||||
|
||||
|
||||
class VirtualFunction:
|
||||
def __init__(self, address, offset):
|
||||
self.address = address
|
||||
self.offset = offset
|
||||
self.visited = False
|
||||
|
||||
def get_ptr_tinfo(self):
|
||||
# print self.tinfo.dstr()
|
||||
ptr_tinfo = idaapi.tinfo_t()
|
||||
ptr_tinfo.create_ptr(self.tinfo)
|
||||
return ptr_tinfo
|
||||
|
||||
def get_udt_member(self):
|
||||
udt_member = idaapi.udt_member_t()
|
||||
udt_member.type = self.get_ptr_tinfo()
|
||||
udt_member.offset = self.offset
|
||||
udt_member.name = self.name
|
||||
udt_member.size = Const.EA_SIZE
|
||||
return udt_member
|
||||
|
||||
def get_information(self):
|
||||
return [Helper.to_hex(self.address), self.name, self.tinfo.dstr()]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
name = idaapi.get_short_name(self.address)
|
||||
name = name.split('(')[0]
|
||||
result = re.search(r"(\[thunk\]:)?([^`]*)(.*\{(\d+)}.*)?", name)
|
||||
name, adjuster = result.group(2), result.group(4)
|
||||
if adjuster:
|
||||
name += "_adj_" + adjuster
|
||||
name = name.translate(None, "`'").replace(':', '_').replace(' ', '_').replace(',', '_').replace('~', 'DESTR__')
|
||||
name = name.replace("==", "__eq__")
|
||||
name = name.replace("=", "__asg__")
|
||||
name = re.sub(r'[<>]', '_t_', name)
|
||||
return name
|
||||
|
||||
@property
|
||||
def tinfo(self):
|
||||
try:
|
||||
decompiled_function = idaapi.decompile(self.address)
|
||||
if decompiled_function:
|
||||
return idaapi.tinfo_t(decompiled_function.type)
|
||||
return Const.DUMMY_FUNC
|
||||
except idaapi.DecompilationFailure:
|
||||
pass
|
||||
print "[ERROR] Failed to decompile function at 0x{0:08X}".format(self.address)
|
||||
return Const.DUMMY_FUNC
|
||||
|
||||
def show_location(self):
|
||||
idaapi.open_pseudocode(self.address, 1)
|
||||
|
||||
|
||||
class ImportedVirtualFunction(VirtualFunction):
|
||||
def __init__(self, address, offset):
|
||||
VirtualFunction.__init__(self, address, offset)
|
||||
|
||||
@property
|
||||
def tinfo(self):
|
||||
print "[INFO] Ignoring import function at 0x{0:08X}".format(self.address)
|
||||
tinfo = idaapi.tinfo_t()
|
||||
if idaapi.guess_tinfo2(self.address, tinfo):
|
||||
return tinfo
|
||||
return Const.DUMMY_FUNC
|
||||
|
||||
def show_location(self):
|
||||
idaapi.jumpto(self.address)
|
||||
|
||||
|
||||
class VirtualTable(AbstractMember):
|
||||
class VirtualTableChoose(MyChoose):
|
||||
def __init__(self, items, temp_struct, virtual_table):
|
||||
MyChoose.__init__(
|
||||
self,
|
||||
items,
|
||||
"Select Virtual Function",
|
||||
[["Address", 10], ["Name", 15], ["Declaration", 45]],
|
||||
13
|
||||
)
|
||||
self.popup_names = ["Scan All", "-", "Scan", "-"]
|
||||
self.__temp_struct = temp_struct
|
||||
self.__virtual_table = virtual_table
|
||||
|
||||
def OnGetLineAttr(self, n):
|
||||
return [0xd9d9d9, 0x0] if self.__virtual_table.virtual_functions[n].visited else [0xffffff, 0x0]
|
||||
|
||||
def OnGetIcon(self, n):
|
||||
return 32 if self.__virtual_table.virtual_functions[n].visited else 160
|
||||
|
||||
def OnInsertLine(self):
|
||||
""" Scan All Functions menu """
|
||||
self.__virtual_table.scan_virtual_functions()
|
||||
|
||||
def OnEditLine(self, n):
|
||||
""" Scan menu """
|
||||
self.__virtual_table.scan_virtual_function(n)
|
||||
|
||||
def __init__(self, offset, address, scanned_variable=None, origin=0):
|
||||
AbstractMember.__init__(self, offset + origin, scanned_variable, origin)
|
||||
self.address = address
|
||||
self.virtual_functions = []
|
||||
self.name = "vtable" + ("_{0:X}".format(self.offset) if self.offset else '')
|
||||
self.vtable_name, self.have_nice_name = parse_vtable_name(address)
|
||||
self.populate()
|
||||
|
||||
def populate(self):
|
||||
address = self.address
|
||||
while True:
|
||||
if Const.EA64:
|
||||
func_address = idaapi.get_64bit(address)
|
||||
else:
|
||||
func_address = idaapi.get_32bit(address)
|
||||
|
||||
if Helper.is_code_ea(func_address):
|
||||
self.virtual_functions.append(VirtualFunction(func_address, address - self.address))
|
||||
elif Helper.is_imported_ea(func_address):
|
||||
self.virtual_functions.append(ImportedVirtualFunction(func_address, address - self.address))
|
||||
else:
|
||||
break
|
||||
address += Const.EA_SIZE
|
||||
|
||||
if idaapi.get_first_dref_to(address) != idaapi.BADADDR:
|
||||
break
|
||||
|
||||
def create_tinfo(self):
|
||||
# print "(Virtual table) at address: 0x{0:08X} name: {1}".format(self.address, self.name)
|
||||
udt_data = idaapi.udt_type_data_t()
|
||||
for function in self.virtual_functions:
|
||||
udt_data.push_back(function.get_udt_member())
|
||||
|
||||
for duplicates in Helper.search_duplicate_fields(udt_data):
|
||||
first_entry_idx = duplicates.pop(0)
|
||||
print "[Warning] Found duplicate virtual functions", udt_data[first_entry_idx].name
|
||||
for num, dup in enumerate(duplicates):
|
||||
udt_data[dup].name = "duplicate_{0}_{1}".format(first_entry_idx, num + 1)
|
||||
tinfo = idaapi.tinfo_t()
|
||||
tinfo.create_ptr(Const.DUMMY_FUNC)
|
||||
udt_data[dup].type = tinfo
|
||||
|
||||
final_tinfo = idaapi.tinfo_t()
|
||||
if final_tinfo.create_udt(udt_data, idaapi.BTF_STRUCT):
|
||||
# print "\n\t(Final structure)\n" + idaapi.print_tinfo('\t', 4, 5, idaapi.PRTYPE_MULTI | idaapi.PRTYPE_TYPE
|
||||
# | idaapi.PRTYPE_SEMI, final_tinfo, self.name, None)
|
||||
return final_tinfo
|
||||
print "[ERROR] Virtual table creation failed"
|
||||
|
||||
def import_to_structures(self, ask=False):
|
||||
"""
|
||||
Imports virtual tables and returns tid_t of new structure
|
||||
|
||||
:return: idaapi.tid_t
|
||||
"""
|
||||
cdecl_typedef = idaapi.print_tinfo(None, 4, 5, idaapi.PRTYPE_MULTI | idaapi.PRTYPE_TYPE | idaapi.PRTYPE_SEMI,
|
||||
self.create_tinfo(), self.vtable_name, None)
|
||||
if ask:
|
||||
cdecl_typedef = idaapi.asktext(0x10000, cdecl_typedef, "The following new type will be created")
|
||||
if not cdecl_typedef:
|
||||
return
|
||||
previous_ordinal = idaapi.get_type_ordinal(idaapi.cvar.idati, self.vtable_name)
|
||||
if previous_ordinal:
|
||||
idaapi.del_numbered_type(idaapi.cvar.idati, previous_ordinal)
|
||||
ordinal = idaapi.idc_set_local_type(previous_ordinal, cdecl_typedef, idaapi.PT_TYP)
|
||||
else:
|
||||
ordinal = idaapi.idc_set_local_type(-1, cdecl_typedef, idaapi.PT_TYP)
|
||||
|
||||
if ordinal:
|
||||
print "[Info] Virtual table " + self.vtable_name + " added to Local Types"
|
||||
return idaapi.import_type(idaapi.cvar.idati, -1, self.vtable_name)
|
||||
else:
|
||||
print "[Error] Failed to create virtual table " + self.vtable_name
|
||||
print "*" * 100
|
||||
print cdecl_typedef
|
||||
print "*" * 100
|
||||
|
||||
def show_virtual_functions(self):
|
||||
function_chooser = self.VirtualTableChoose(
|
||||
[function.get_information() for function in self.virtual_functions], Cache.temporary_structure, self)
|
||||
|
||||
idx = function_chooser.Show(True)
|
||||
if idx != -1:
|
||||
virtual_function = self.virtual_functions[idx]
|
||||
virtual_function.visited = True
|
||||
virtual_function.show_location()
|
||||
|
||||
def scan_virtual_function(self, index):
|
||||
if Helper.is_imported_ea(self.virtual_functions[index].address):
|
||||
print "[INFO] Ignoring import function at 0x{0:08X}".format(self.address)
|
||||
return
|
||||
try:
|
||||
function = idaapi.decompile(self.virtual_functions[index].address)
|
||||
except idaapi.DecompilationFailure:
|
||||
print "[ERROR] Failed to decompile function at 0x{0:08X}".format(self.address)
|
||||
return
|
||||
if Helper.FunctionTouchVisitor(function).process():
|
||||
function = idaapi.decompile(self.virtual_functions[index].address)
|
||||
if function.arguments and function.arguments[0].is_arg_var and Helper.is_legal_type(function.arguments[0].tif):
|
||||
print "[Info] Scanning virtual function at 0x{0:08X}".format(function.entry_ea)
|
||||
# TODO: Remove usage `temporary_structure' as global
|
||||
obj = Api.VariableObject(function.get_lvars()[0], 0)
|
||||
scanner = VariableScanner.NewDeepSearchVisitor(function, self.offset, obj, Cache.temporary_structure)
|
||||
scanner.process()
|
||||
else:
|
||||
print "[Warning] Bad type of first argument in virtual function at 0x{0:08X}".format(function.entry_ea)
|
||||
|
||||
def scan_virtual_functions(self):
|
||||
for idx in xrange(len(self.virtual_functions)):
|
||||
self.scan_virtual_function(idx)
|
||||
|
||||
def get_udt_member(self, offset=0):
|
||||
udt_member = idaapi.udt_member_t()
|
||||
tid = self.import_to_structures()
|
||||
if tid != idaapi.BADADDR:
|
||||
udt_member.name = self.name
|
||||
tmp_tinfo = idaapi.create_typedef(self.vtable_name)
|
||||
tmp_tinfo.create_ptr(tmp_tinfo)
|
||||
udt_member.type = tmp_tinfo
|
||||
udt_member.offset = self.offset - offset
|
||||
udt_member.size = Const.EA_SIZE
|
||||
return udt_member
|
||||
|
||||
def type_equals_to(self, tinfo):
|
||||
udt_data = idaapi.udt_type_data_t()
|
||||
if tinfo.is_ptr() and tinfo.get_pointed_object().get_udt_details(udt_data):
|
||||
if udt_data[0].type.is_funcptr():
|
||||
return True
|
||||
return False
|
||||
|
||||
def switch_array_flag(self):
|
||||
pass
|
||||
|
||||
def activate(self):
|
||||
self.show_virtual_functions()
|
||||
|
||||
@staticmethod
|
||||
def check_address(address):
|
||||
# Checks if given address contains virtual table. Returns True if more than 2 function pointers found
|
||||
# Also if table's addresses point to code in executable section, than tries to make functions at that addresses
|
||||
functions_count = 0
|
||||
while True:
|
||||
func_address = idaapi.get_64bit(address) if Const.EA64 else idaapi.get_32bit(address)
|
||||
# print "[INFO] Address 0x{0:08X}".format(func_address)
|
||||
if Helper.is_code_ea(func_address) or Helper.is_imported_ea(func_address):
|
||||
functions_count += 1
|
||||
address += Const.EA_SIZE
|
||||
else:
|
||||
segment = idaapi.getseg(func_address)
|
||||
if segment and segment.perm & idaapi.SEGPERM_EXEC:
|
||||
idc.MakeUnknown(func_address, 1, idaapi.DOUNK_SIMPLE)
|
||||
if idc.MakeFunction(func_address):
|
||||
functions_count += 1
|
||||
address += Const.EA_SIZE
|
||||
continue
|
||||
break
|
||||
idaapi.autoWait()
|
||||
return functions_count
|
||||
|
||||
@property
|
||||
def type_name(self):
|
||||
return self.vtable_name + " *"
|
||||
|
||||
@property
|
||||
def font(self):
|
||||
return QtGui.QFont("Consolas", 10, QtGui.QFont.Bold)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
return Const.EA_SIZE
|
||||
|
||||
|
||||
class Member(AbstractMember):
|
||||
def __init__(self, offset, tinfo, scanned_variable, origin=0):
|
||||
AbstractMember.__init__(self, offset + origin, scanned_variable, origin)
|
||||
self.tinfo = tinfo
|
||||
self.name = "field_{0:X}".format(self.offset)
|
||||
|
||||
def get_udt_member(self, array_size=0, offset=0):
|
||||
udt_member = idaapi.udt_member_t()
|
||||
udt_member.name = "field_{0:X}".format(self.offset - offset) if self.name[:6] == "field_" else self.name
|
||||
udt_member.type = self.tinfo
|
||||
if array_size:
|
||||
tmp = idaapi.tinfo_t(self.tinfo)
|
||||
tmp.create_array(self.tinfo, array_size)
|
||||
udt_member.type = tmp
|
||||
udt_member.offset = self.offset - offset
|
||||
udt_member.size = self.size
|
||||
return udt_member
|
||||
|
||||
def activate(self):
|
||||
new_type_declaration = idaapi.askstr(0x100, self.type_name, "Enter type:")
|
||||
if new_type_declaration is None:
|
||||
return
|
||||
|
||||
result = idc.ParseType(new_type_declaration, 0)
|
||||
if result is None:
|
||||
return
|
||||
_, tp, fld = result
|
||||
tinfo = idaapi.tinfo_t()
|
||||
tinfo.deserialize(idaapi.cvar.idati, tp, fld, None)
|
||||
self.tinfo = tinfo
|
||||
self.is_array = False
|
||||
|
||||
|
||||
class VoidMember(Member):
|
||||
def __init__(self, offset, scanned_variable, origin=0, char=False):
|
||||
tinfo = Const.CHAR_TINFO if char else Const.BYTE_TINFO
|
||||
Member.__init__(self, offset, tinfo, scanned_variable, origin)
|
||||
self.is_array = True
|
||||
|
||||
def type_equals_to(self, tinfo):
|
||||
return True
|
||||
|
||||
def switch_array_flag(self):
|
||||
pass
|
||||
|
||||
def set_enabled(self, enable):
|
||||
self.enabled = enable
|
||||
|
||||
@property
|
||||
def font(self):
|
||||
return QtGui.QFont("Consolas", 10, italic=True)
|
||||
|
||||
|
||||
class TemporaryStructureModel(QtCore.QAbstractTableModel):
|
||||
|
||||
def __init__(self, *args):
|
||||
"""
|
||||
Keeps information about currently found fields in possible structure
|
||||
main_offset - is the base from where variables scanned. Can be set to different value if some field is passed by
|
||||
reverence
|
||||
items - array of candidates to fields
|
||||
"""
|
||||
super(TemporaryStructureModel, self).__init__(*args)
|
||||
self.main_offset = 0
|
||||
self.headers = ["Offset", "Type", "Name"]
|
||||
self.items = []
|
||||
self.collisions = []
|
||||
self.structure_name = "CHANGE_MY_NAME"
|
||||
|
||||
# OVERLOADED METHODS #
|
||||
|
||||
def rowCount(self, *args):
|
||||
return len(self.items)
|
||||
|
||||
def columnCount(self, *args):
|
||||
return len(self.headers)
|
||||
|
||||
def data(self, index, role):
|
||||
row, col = index.row(), index.column()
|
||||
item = self.items[row]
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
if col == 0:
|
||||
return "0x{0:08X}".format(item.offset)
|
||||
elif col == 1:
|
||||
if item.is_array and item.size > 0:
|
||||
array_size = self.calculate_array_size(row)
|
||||
if array_size:
|
||||
return item.type_name + "[{}]".format(array_size)
|
||||
return item.type_name
|
||||
elif col == 2:
|
||||
return item.name
|
||||
elif role == QtCore.Qt.ToolTipRole:
|
||||
if col == 0:
|
||||
return self.items[row].offset
|
||||
elif col == 1:
|
||||
return self.items[row].size * (self.calculate_array_size(row) if self.items[row].is_array else 1)
|
||||
elif role == QtCore.Qt.EditRole:
|
||||
if col == 2:
|
||||
return self.items[row].name
|
||||
elif role == QtCore.Qt.FontRole:
|
||||
if col == 1:
|
||||
return item.font
|
||||
elif role == QtCore.Qt.BackgroundRole:
|
||||
if not item.enabled:
|
||||
return QtGui.QColor(QtCore.Qt.gray)
|
||||
if item.offset == self.main_offset:
|
||||
if col == 0:
|
||||
return QtGui.QBrush(QtGui.QColor("#ff8080"))
|
||||
if self.have_collision(row):
|
||||
return QtGui.QBrush(QtGui.QColor("#ffff99"))
|
||||
elif role == QtCore.Qt.ForegroundRole:
|
||||
if self.have_collision(row):
|
||||
return QtGui.QBrush(QtGui.QColor("#191919"))
|
||||
|
||||
def setData(self, index, value, role):
|
||||
row, col = index.row(), index.column()
|
||||
if role == QtCore.Qt.EditRole and idaapi.isident(str(value)):
|
||||
self.items[row].name = str(value)
|
||||
self.dataChanged.emit(index, index)
|
||||
return True
|
||||
return False
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
|
||||
return self.headers[section]
|
||||
|
||||
def flags(self, index):
|
||||
if index.column() == 2:
|
||||
return super(TemporaryStructureModel, self).flags(index) | QtGui.QAbstractItemView.DoubleClicked
|
||||
return super(TemporaryStructureModel, self).flags(index)
|
||||
|
||||
# HELPER METHODS #
|
||||
|
||||
def pack(self, start=0, stop=None):
|
||||
if self.collisions[start:stop].count(True):
|
||||
print "[Warning] Collisions detected"
|
||||
return
|
||||
|
||||
final_tinfo = idaapi.tinfo_t()
|
||||
udt_data = idaapi.udt_type_data_t()
|
||||
origin = self.items[start].offset if start else 0
|
||||
offset = origin
|
||||
|
||||
for item in filter(lambda x: x.enabled, self.items[start:stop]): # Filter disabled members
|
||||
gap_size = item.offset - offset
|
||||
if gap_size:
|
||||
udt_data.push_back(TemporaryStructureModel.get_padding_member(offset - origin, gap_size))
|
||||
if item.is_array:
|
||||
array_size = self.calculate_array_size(bisect.bisect_left(self.items, item))
|
||||
if array_size:
|
||||
udt_data.push_back(item.get_udt_member(array_size, offset=origin))
|
||||
offset = item.offset + item.size * array_size
|
||||
continue
|
||||
udt_data.push_back(item.get_udt_member(offset=origin))
|
||||
offset = item.offset + item.size
|
||||
|
||||
final_tinfo.create_udt(udt_data, idaapi.BTF_STRUCT)
|
||||
cdecl = idaapi.print_tinfo(None, 4, 5, idaapi.PRTYPE_MULTI | idaapi.PRTYPE_TYPE | idaapi.PRTYPE_SEMI,
|
||||
final_tinfo, self.structure_name, None)
|
||||
cdecl = idaapi.asktext(0x10000, '#pragma pack(push, 1)\n' + cdecl, "The following new type will be created")
|
||||
|
||||
if cdecl:
|
||||
structure_name = idaapi.idc_parse_decl(idaapi.cvar.idati, cdecl, idaapi.PT_TYP)[0]
|
||||
previous_ordinal = idaapi.get_type_ordinal(idaapi.cvar.idati, structure_name)
|
||||
|
||||
if previous_ordinal:
|
||||
reply = QtGui.QMessageBox.question(
|
||||
None,
|
||||
"HexRaysPyTools",
|
||||
"Structure already exist. Do you want to overwrite it?",
|
||||
QtGui.QMessageBox.Yes | QtGui.QMessageBox.No
|
||||
)
|
||||
if reply == QtGui.QMessageBox.Yes:
|
||||
idaapi.del_numbered_type(idaapi.cvar.idati, previous_ordinal)
|
||||
ordinal = idaapi.idc_set_local_type(previous_ordinal, cdecl, idaapi.PT_TYP)
|
||||
else:
|
||||
return
|
||||
else:
|
||||
ordinal = idaapi.idc_set_local_type(-1, cdecl, idaapi.PT_TYP)
|
||||
if ordinal:
|
||||
print "[Info] New type {0} was added to Local Types".format(structure_name)
|
||||
tid = idaapi.import_type(idaapi.cvar.idati, -1, structure_name)
|
||||
if tid:
|
||||
tinfo = idaapi.create_typedef(structure_name)
|
||||
ptr_tinfo = idaapi.tinfo_t()
|
||||
ptr_tinfo.create_ptr(tinfo)
|
||||
for scanned_var in self.get_unique_scanned_variables(origin):
|
||||
scanned_var.apply_type(ptr_tinfo)
|
||||
return tinfo
|
||||
else:
|
||||
print "[ERROR] Structure {0} probably already exist".format(structure_name)
|
||||
|
||||
def have_member(self, member):
|
||||
if self.items:
|
||||
idx = bisect.bisect_left(self.items, member)
|
||||
if idx < self.rowCount():
|
||||
return self.items[idx] == member
|
||||
return False
|
||||
|
||||
def have_collision(self, row):
|
||||
return self.collisions[row]
|
||||
|
||||
def refresh_collisions(self):
|
||||
self.collisions = [False for _ in xrange(len(self.items))]
|
||||
if (len(self.items)) > 1:
|
||||
curr = 0
|
||||
while curr < len(self.items):
|
||||
if self.items[curr].enabled:
|
||||
break
|
||||
curr += 1
|
||||
next = curr + 1
|
||||
while next < len(self.items):
|
||||
if self.items[next].enabled:
|
||||
if self.items[curr].offset + self.items[curr].size > self.items[next].offset:
|
||||
self.collisions[curr] = True
|
||||
self.collisions[next] = True
|
||||
if self.items[curr].offset + self.items[curr].size < self.items[next].offset + self.items[next].size:
|
||||
curr = next
|
||||
else:
|
||||
curr = next
|
||||
next += 1
|
||||
|
||||
def add_row(self, member):
|
||||
if not self.have_member(member):
|
||||
bisect.insort(self.items, member)
|
||||
self.refresh_collisions()
|
||||
self.modelReset.emit()
|
||||
|
||||
def get_unique_scanned_variables(self, origin=0):
|
||||
scan_objects = itertools.chain.from_iterable(
|
||||
[list(item.scanned_variables) for item in self.items if item.origin == origin])
|
||||
return dict(((item.function_name, item.name), item) for item in scan_objects).values()
|
||||
|
||||
def get_next_enabled(self, row):
|
||||
row += 1
|
||||
while row < self.rowCount():
|
||||
if self.items[row].enabled:
|
||||
return row
|
||||
row += 1
|
||||
return None
|
||||
|
||||
def calculate_array_size(self, row):
|
||||
next_row = self.get_next_enabled(row)
|
||||
if next_row:
|
||||
return (self.items[next_row].offset - self.items[row].offset) / self.items[row].size
|
||||
return 0
|
||||
|
||||
def get_recognized_shape(self, start=0, stop=-1):
|
||||
if not self.items:
|
||||
return None
|
||||
result = []
|
||||
if stop != -1:
|
||||
base = self.items[start].offset
|
||||
enabled_items = filter(lambda x: x.enabled, self.items[start:stop])
|
||||
else:
|
||||
base = 0
|
||||
enabled_items = filter(lambda x: x.enabled, self.items)
|
||||
offsets = set(map(lambda x: x.offset, enabled_items))
|
||||
if not enabled_items:
|
||||
return
|
||||
min_size = enabled_items[-1].offset + enabled_items[-1].size - base
|
||||
tinfo = idaapi.tinfo_t()
|
||||
for ordinal in xrange(1, idaapi.get_ordinal_qty(idaapi.cvar.idati)):
|
||||
tinfo.get_numbered_type(idaapi.cvar.idati, ordinal)
|
||||
if tinfo.is_udt() and tinfo.get_size() >= min_size:
|
||||
is_found = False
|
||||
for offset in offsets:
|
||||
is_found = False
|
||||
items = filter(lambda x: x.offset == offset, enabled_items)
|
||||
potential_members = Helper.get_fields_at_offset(tinfo, offset - base)
|
||||
for item in items:
|
||||
for potential_member in potential_members:
|
||||
if item.type_equals_to(potential_member):
|
||||
is_found = True
|
||||
break
|
||||
if is_found:
|
||||
break
|
||||
if not is_found:
|
||||
break
|
||||
if is_found:
|
||||
result.append((ordinal, idaapi.tinfo_t(tinfo)))
|
||||
chooser = MyChoose(
|
||||
[[str(x), "0x{0:08X}".format(y.get_size()), y.dstr()] for x, y in result],
|
||||
"Select Structure",
|
||||
[["Ordinal", 5], ["Size", 10], ["Structure name", 50]]
|
||||
)
|
||||
idx = chooser.Show(modal=True)
|
||||
if idx != -1:
|
||||
return result[idx][1]
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_padding_member(offset, size):
|
||||
udt_member = idaapi.udt_member_t()
|
||||
if size == 1:
|
||||
udt_member.name = "gap_{0:X}".format(offset)
|
||||
udt_member.type = Const.BYTE_TINFO
|
||||
udt_member.size = Const.BYTE_TINFO.get_size()
|
||||
udt_member.offset = offset
|
||||
return udt_member
|
||||
|
||||
array_data = idaapi.array_type_data_t()
|
||||
array_data.base = 0
|
||||
array_data.elem_type = Const.BYTE_TINFO
|
||||
array_data.nelems = size
|
||||
tmp_tinfo = idaapi.tinfo_t()
|
||||
tmp_tinfo.create_array(array_data)
|
||||
|
||||
udt_member.name = "gap_{0:X}".format(offset)
|
||||
udt_member.type = tmp_tinfo
|
||||
udt_member.size = size
|
||||
udt_member.offset = offset
|
||||
return udt_member
|
||||
|
||||
# SLOTS #
|
||||
|
||||
def finalize(self):
|
||||
if self.pack():
|
||||
self.clear()
|
||||
|
||||
def disable_rows(self, indices):
|
||||
for idx in indices:
|
||||
if self.items[idx.row()].enabled:
|
||||
self.items[idx.row()].set_enabled(False)
|
||||
self.refresh_collisions()
|
||||
self.modelReset.emit()
|
||||
|
||||
def enable_rows(self, indices):
|
||||
for idx in indices:
|
||||
if not self.items[idx.row()].enabled:
|
||||
self.items[idx.row()].enabled = True
|
||||
self.refresh_collisions()
|
||||
self.modelReset.emit()
|
||||
|
||||
def set_origin(self, indices):
|
||||
if indices:
|
||||
self.main_offset = self.items[indices[0].row()].offset
|
||||
self.modelReset.emit()
|
||||
|
||||
def make_array(self, indices):
|
||||
if indices:
|
||||
self.items[indices[0].row()].switch_array_flag()
|
||||
self.dataChanged.emit(indices[0], indices[0])
|
||||
|
||||
def pack_substructure(self, indices):
|
||||
if indices:
|
||||
indices = sorted(indices)
|
||||
self.dataChanged.emit(indices[0], indices[-1])
|
||||
start, stop = indices[0].row(), indices[-1].row() + 1
|
||||
tinfo = self.pack(start, stop)
|
||||
if tinfo:
|
||||
offset = self.items[start].offset
|
||||
self.items = self.items[0:start] + self.items[stop:]
|
||||
self.add_row(Member(offset, tinfo, None))
|
||||
|
||||
def unpack_substructure(self, indices):
|
||||
|
||||
if indices is None or len(indices) != 1:
|
||||
return
|
||||
|
||||
item = self.items[indices[0].row()]
|
||||
if item.tinfo is not None and item.tinfo.is_udt():
|
||||
|
||||
self.remove_items(indices)
|
||||
offset = item.offset
|
||||
udt_data = idaapi.udt_type_data_t()
|
||||
if item.tinfo.get_udt_details(udt_data):
|
||||
for udt_item in udt_data:
|
||||
member = Member(offset + udt_item.offset / 8, udt_item.type, None)
|
||||
member.name = udt_item.name
|
||||
self.add_row(member)
|
||||
|
||||
def resolve_types(self):
|
||||
current_item = None
|
||||
current_item_score = 0
|
||||
|
||||
for item in self.items:
|
||||
if not item.enabled:
|
||||
continue
|
||||
|
||||
if current_item is None:
|
||||
current_item = item
|
||||
current_item_score = current_item.score
|
||||
continue
|
||||
|
||||
item_score = item.score
|
||||
if current_item.has_collision(item):
|
||||
if item_score <= current_item_score:
|
||||
item.set_enabled(False)
|
||||
continue
|
||||
elif item_score > current_item_score:
|
||||
current_item.set_enabled(False)
|
||||
|
||||
current_item = item
|
||||
current_item_score = item_score
|
||||
|
||||
self.refresh_collisions()
|
||||
self.modelReset.emit()
|
||||
|
||||
def remove_items(self, indices):
|
||||
rows = map(lambda x: x.row(), indices)
|
||||
if rows:
|
||||
self.items = [item for item in self.items if self.items.index(item) not in rows]
|
||||
self.modelReset.emit()
|
||||
|
||||
def clear(self):
|
||||
self.items = []
|
||||
self.main_offset = 0
|
||||
self.modelReset.emit()
|
||||
|
||||
def recognize_shape(self, indices):
|
||||
min_idx = max_idx = None
|
||||
if indices:
|
||||
min_idx, max_idx = min(indices), max(indices, key=lambda x: (x.row(), x.column()))
|
||||
|
||||
if min_idx == max_idx:
|
||||
tinfo = self.get_recognized_shape()
|
||||
if tinfo:
|
||||
tinfo.create_ptr(tinfo)
|
||||
for scanned_var in self.get_unique_scanned_variables(origin=0):
|
||||
scanned_var.apply_type(tinfo)
|
||||
self.clear()
|
||||
else:
|
||||
# indices = sorted(indices)
|
||||
start, stop = min_idx.row(), max_idx.row() + 1
|
||||
base = self.items[start].offset
|
||||
tinfo = self.get_recognized_shape(start, stop)
|
||||
if tinfo:
|
||||
ptr_tinfo = idaapi.tinfo_t()
|
||||
ptr_tinfo.create_ptr(tinfo)
|
||||
for scanned_var in self.get_unique_scanned_variables(base):
|
||||
scanned_var.apply_type(ptr_tinfo)
|
||||
self.items = filter(lambda x: x.offset < base or x.offset >= base + tinfo.get_size(), self.items)
|
||||
self.add_row(Member(base, tinfo, None))
|
||||
|
||||
def activated(self, index):
|
||||
# Double click on offset, opens window with variables
|
||||
if index.column() == 0:
|
||||
item = self.items[index.row()]
|
||||
scanned_variables = list(item.scanned_variables)
|
||||
variable_chooser = MyChoose(
|
||||
map(lambda x: x.to_list(), scanned_variables),
|
||||
"Select Variable",
|
||||
[["Origin", 4], ["Function name", 25], ["Variable name", 25], ["Expression address", 10]]
|
||||
)
|
||||
row = variable_chooser.Show(modal=True)
|
||||
if row != -1:
|
||||
idaapi.open_pseudocode(scanned_variables[row].expression_address, 0)
|
||||
|
||||
# Double click on type. If type is virtual table than opens windows with virtual methods
|
||||
elif index.column() == 1:
|
||||
self.items[index.row()].activate()
|
||||
Reference in New Issue
Block a user