Files
ida-scripts/plugins/HexRaysPyTools/Core/TemporaryStructure.py

865 lines
33 KiB
Python

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