Replace HexRaysCodeXplorer and HexraysInvertIf with HexRaysPyTools
This commit is contained in:
536
plugins/HexRaysPyTools/Core/Classes.py
Normal file
536
plugins/HexRaysPyTools/Core/Classes.py
Normal file
@@ -0,0 +1,536 @@
|
||||
import idc
|
||||
import idaapi
|
||||
import HexRaysPyTools.Forms
|
||||
import Helper
|
||||
|
||||
# from PySide import QtGui, QtCore
|
||||
from HexRaysPyTools.Cute import *
|
||||
|
||||
all_virtual_functions = {} # name -> VirtualMethod
|
||||
all_virtual_tables = {} # ordinal -> VirtualTable
|
||||
|
||||
|
||||
class VirtualMethod(object):
|
||||
def __init__(self, tinfo, name, parent):
|
||||
self.tinfo = tinfo
|
||||
self.tinfo_modified = False
|
||||
self.name = name
|
||||
self.class_name = None
|
||||
self.name_modified = False
|
||||
self.parents = [parent]
|
||||
self.base_address = Helper.get_virtual_func_address(name)
|
||||
if self.base_address:
|
||||
self.base_address -= idaapi.get_imagebase()
|
||||
|
||||
self.rowcount = 0
|
||||
self.children = []
|
||||
|
||||
@staticmethod
|
||||
def create(tinfo, name, parent):
|
||||
result = all_virtual_functions.get(name)
|
||||
if result:
|
||||
result.parents.append(parent)
|
||||
return result
|
||||
result = VirtualMethod(tinfo, name, parent)
|
||||
all_virtual_functions[name] = result
|
||||
return result
|
||||
|
||||
def update(self, name, tinfo):
|
||||
self.name = name
|
||||
self.tinfo = tinfo
|
||||
self.name_modified = False
|
||||
self.tinfo_modified = False
|
||||
|
||||
self.base_address = idc.LocByName(self.name)
|
||||
if self.base_address != idaapi.BADADDR:
|
||||
self.base_address -= idaapi.get_imagebase()
|
||||
|
||||
def data(self, column):
|
||||
if column == 0:
|
||||
return self.name
|
||||
elif column == 1:
|
||||
return self.tinfo.get_pointed_object().dstr()
|
||||
elif column == 2:
|
||||
return "0x{0:08X}".format(self.address) if self.address else None
|
||||
|
||||
def setData(self, column, value):
|
||||
if column == 0:
|
||||
if idaapi.isident(value) and self.name != value:
|
||||
self.name = value
|
||||
self.name_modified = True
|
||||
for parent in self.parents:
|
||||
parent.modified = True
|
||||
return True
|
||||
elif column == 1:
|
||||
tinfo = idaapi.tinfo_t()
|
||||
split = value.split('(')
|
||||
if len(split) == 2:
|
||||
value = split[0] + ' ' + self.name + '(' + split[1] + ';'
|
||||
if idaapi.parse_decl2(idaapi.cvar.idati, value, '', tinfo, idaapi.PT_TYP):
|
||||
if tinfo.is_func():
|
||||
tinfo.create_ptr(tinfo)
|
||||
if tinfo.dstr() != self.tinfo.dstr():
|
||||
self.tinfo = tinfo
|
||||
self.tinfo_modified = True
|
||||
for parent in self.parents:
|
||||
parent.modified = True
|
||||
return True
|
||||
return False
|
||||
|
||||
def font(self, column):
|
||||
if column == 0 and self.name_modified:
|
||||
return QtGui.QFont("Consolas", 10, italic=True)
|
||||
elif column == 1 and self.tinfo_modified:
|
||||
return QtGui.QFont("Consolas", 10, italic=True)
|
||||
return QtGui.QFont("Consolas", 10, 0)
|
||||
|
||||
def flags(self, column):
|
||||
if column != 2:
|
||||
if self.address:
|
||||
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsEditable
|
||||
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
|
||||
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
# return QtGui.QBrush(QtGui.QColor("#ffffb3"))
|
||||
return QtGui.QColor("#fefbd8")
|
||||
|
||||
@property
|
||||
def tooltip(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
return self.base_address + idaapi.get_imagebase() if self.base_address else None
|
||||
|
||||
def set_first_argument_type(self, name):
|
||||
func_data = idaapi.func_type_data_t()
|
||||
func_tinfo = self.tinfo.get_pointed_object()
|
||||
class_tinfo = idaapi.tinfo_t()
|
||||
if func_tinfo.get_func_details(func_data) and func_tinfo.get_nargs() and \
|
||||
class_tinfo.get_named_type(idaapi.cvar.idati, name):
|
||||
class_tinfo.create_ptr(class_tinfo)
|
||||
first_arg_tinfo = func_data[0].type
|
||||
if (first_arg_tinfo.is_ptr() and first_arg_tinfo.get_pointed_object().is_udt()) or \
|
||||
Helper.is_legal_type(func_data[0].type):
|
||||
func_data[0].type = class_tinfo
|
||||
func_data[0].name = "this"
|
||||
func_tinfo.create_func(func_data)
|
||||
func_tinfo.create_ptr(func_tinfo)
|
||||
if func_tinfo.dstr() != self.tinfo.dstr():
|
||||
self.tinfo = func_tinfo
|
||||
self.tinfo_modified = True
|
||||
for parent in self.parents:
|
||||
parent.modified = True
|
||||
else:
|
||||
print "[Warning] function {0} probably have wrong type".format(self.name)
|
||||
|
||||
def open_function(self):
|
||||
if self.address:
|
||||
if idaapi.decompile(self.address):
|
||||
idaapi.open_pseudocode(self.address, 0)
|
||||
else:
|
||||
idaapi.jumpto(self.address)
|
||||
|
||||
def commit(self):
|
||||
if self.name_modified:
|
||||
self.name_modified = False
|
||||
if self.address:
|
||||
idaapi.set_name(self.address, self.name)
|
||||
if self.tinfo_modified:
|
||||
self.tinfo_modified = False
|
||||
if self.address:
|
||||
idaapi.apply_tinfo2(self.address, self.tinfo.get_pointed_object(), idaapi.TINFO_DEFINITE)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.address == other.address
|
||||
|
||||
def __repr__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class VirtualTable(object):
|
||||
def __init__(self, ordinal, tinfo, class_):
|
||||
self.ordinal = ordinal
|
||||
self.tinfo = tinfo
|
||||
self.class_ = [class_]
|
||||
self.class_name = None
|
||||
self.virtual_functions = []
|
||||
self.name = self.tinfo.dstr()
|
||||
self._modified = False
|
||||
|
||||
def update(self):
|
||||
if self.modified:
|
||||
vtable_tinfo = idaapi.tinfo_t()
|
||||
udt_data = idaapi.udt_type_data_t()
|
||||
vtable_tinfo.get_numbered_type(idaapi.cvar.idati, self.ordinal)
|
||||
vtable_tinfo.get_udt_details(udt_data)
|
||||
self.tinfo = vtable_tinfo
|
||||
self.name = vtable_tinfo.dstr()
|
||||
self.modified = False
|
||||
if len(self.virtual_functions) == len(udt_data):
|
||||
for current_function, other_function in zip(self.virtual_functions, udt_data):
|
||||
current_function.update(other_function.name, other_function.type)
|
||||
else:
|
||||
print "[ERROR] Something have been modified in Local types. Please refresh this view"
|
||||
|
||||
def update_local_type(self):
|
||||
if self.modified:
|
||||
final_tinfo = idaapi.tinfo_t()
|
||||
udt_data = idaapi.udt_type_data_t()
|
||||
self.tinfo.get_udt_details(udt_data)
|
||||
if len(udt_data) == len(self.virtual_functions):
|
||||
for udt_member, virtual_function in zip(udt_data, self.virtual_functions):
|
||||
udt_member.name = virtual_function.name
|
||||
udt_member.type = virtual_function.tinfo
|
||||
virtual_function.commit()
|
||||
final_tinfo.create_udt(udt_data, idaapi.BTF_STRUCT)
|
||||
final_tinfo.set_numbered_type(idaapi.cvar.idati, self.ordinal, idaapi.NTF_REPLACE, self.name)
|
||||
self.modified = False
|
||||
else:
|
||||
print "[ERROR] Something have been modified in Local types. Please refresh this view"
|
||||
|
||||
def set_first_argument_type(self, class_name):
|
||||
for function in self.virtual_functions:
|
||||
function.set_first_argument_type(class_name)
|
||||
|
||||
@property
|
||||
def modified(self):
|
||||
return self._modified
|
||||
|
||||
@modified.setter
|
||||
def modified(self, value):
|
||||
self._modified = value
|
||||
if value:
|
||||
for class_ in self.class_:
|
||||
class_.modified = True
|
||||
|
||||
@staticmethod
|
||||
def create(tinfo, class_):
|
||||
ordinal = idaapi.get_type_ordinal(idaapi.cvar.idati, tinfo.dstr())
|
||||
result = all_virtual_tables.get(ordinal)
|
||||
if result:
|
||||
result.class_.append(class_)
|
||||
else:
|
||||
udt_data = idaapi.udt_type_data_t()
|
||||
tinfo.get_udt_details(udt_data)
|
||||
result = VirtualTable(ordinal, tinfo, class_)
|
||||
virtual_functions = [VirtualMethod.create(func.type, func.name, result) for func in udt_data]
|
||||
result.virtual_functions = virtual_functions
|
||||
all_virtual_functions[ordinal] = result
|
||||
return result
|
||||
|
||||
def get_class_tinfo(self):
|
||||
if len(self.class_) == 1:
|
||||
return self.class_.tinfo
|
||||
|
||||
def setData(self, column, value):
|
||||
if column == 0:
|
||||
if idaapi.isident(value) and self.name != value:
|
||||
self.name = value
|
||||
self.modified = True
|
||||
return True
|
||||
return False
|
||||
|
||||
def data(self, column):
|
||||
if column == 0:
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
return QtGui.QColor("#d5f4e6")
|
||||
|
||||
@property
|
||||
def tooltip(self):
|
||||
pass
|
||||
|
||||
def font(self, column):
|
||||
if self.modified:
|
||||
return QtGui.QFont("Consolas", 12, italic=True)
|
||||
return QtGui.QFont("Consolas", 12)
|
||||
|
||||
def flags(self, column):
|
||||
if column == 0:
|
||||
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
|
||||
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
return self.virtual_functions
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.virtual_functions)
|
||||
|
||||
|
||||
class Class(object):
|
||||
def __init__(self, name, tinfo, ordinal):
|
||||
self.name = name
|
||||
self.class_name = name
|
||||
self.ordinal = ordinal
|
||||
self.parent = None
|
||||
self.vtables = {}
|
||||
self.modified = False
|
||||
|
||||
@staticmethod
|
||||
def create_class(ordinal):
|
||||
tinfo = idaapi.tinfo_t()
|
||||
tinfo.get_numbered_type(idaapi.cvar.idati, ordinal)
|
||||
vtables = {}
|
||||
if tinfo.is_struct():
|
||||
udt_data = idaapi.udt_type_data_t()
|
||||
tinfo.get_udt_details(udt_data)
|
||||
for field_udt in udt_data:
|
||||
if field_udt.type.is_ptr():
|
||||
possible_vtable = field_udt.type.get_pointed_object()
|
||||
if possible_vtable.is_struct():
|
||||
v_udt_data = idaapi.udt_type_data_t()
|
||||
possible_vtable.get_udt_details(v_udt_data)
|
||||
for possible_func_udt in v_udt_data:
|
||||
if not possible_func_udt.type.is_funcptr():
|
||||
break
|
||||
else:
|
||||
vtables[field_udt.offset / 8] = possible_vtable
|
||||
if vtables:
|
||||
class_ = Class(tinfo.dstr(), tinfo, ordinal)
|
||||
for offset, vtable_tinfo in vtables.iteritems():
|
||||
vtables[offset] = VirtualTable.create(vtable_tinfo, class_)
|
||||
class_.vtables = vtables
|
||||
return class_
|
||||
|
||||
def update_from_local_types(self):
|
||||
try:
|
||||
if self.modified:
|
||||
class_ = self.create_class(self.ordinal)
|
||||
if class_:
|
||||
self.name = class_.name
|
||||
self.modified = False
|
||||
for offset, vtable in class_.vtables.iteritems():
|
||||
self.vtables[offset].update()
|
||||
else:
|
||||
# TODO: drop class
|
||||
raise IndexError
|
||||
except IndexError:
|
||||
print "[ERROR] Something have been modified in Local types. Please refresh this view"
|
||||
|
||||
def update_local_type(self):
|
||||
if self.modified:
|
||||
for vtable in self.vtables.values():
|
||||
vtable.update_local_type()
|
||||
udt_data = idaapi.udt_type_data_t()
|
||||
tinfo = idaapi.tinfo_t()
|
||||
self.tinfo.get_udt_details(udt_data)
|
||||
tinfo.create_udt(udt_data, idaapi.BTF_STRUCT)
|
||||
tinfo.set_numbered_type(idaapi.cvar.idati, self.ordinal, idaapi.NTF_REPLACE, self.name)
|
||||
self.modified = False
|
||||
|
||||
def set_first_argument_type(self, class_name):
|
||||
if 0 in self.vtables:
|
||||
self.vtables[0].set_first_argument_type(class_name)
|
||||
|
||||
def has_function(self, regexp):
|
||||
for vtable in self.vtables.values():
|
||||
if filter(lambda func: regexp.indexIn(func.name) >= 0, vtable.virtual_functions):
|
||||
return True
|
||||
return False
|
||||
|
||||
def data(self, column):
|
||||
if column == 0:
|
||||
return self.name
|
||||
|
||||
def setData(self, column, value):
|
||||
if column == 0:
|
||||
if idaapi.isident(value) and self.name != value:
|
||||
self.name = value
|
||||
self.modified = True
|
||||
return True
|
||||
return False
|
||||
|
||||
def font(self, column):
|
||||
if self.modified:
|
||||
return QtGui.QFont("Consolas", 12, QtGui.QFont.Bold, italic=True)
|
||||
return QtGui.QFont("Consolas", 12, QtGui.QFont.Bold)
|
||||
|
||||
@property
|
||||
def color(self):
|
||||
# return QtGui.QBrush(QtGui.QColor("#ffb3ff")):
|
||||
return QtGui.QColor("#80ced6")
|
||||
|
||||
@property
|
||||
def tooltip(self):
|
||||
return None
|
||||
|
||||
def flags(self, column):
|
||||
if column == 0:
|
||||
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
|
||||
return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
return self.vtables.values()
|
||||
|
||||
@property
|
||||
def tinfo(self):
|
||||
tinfo = idaapi.tinfo_t()
|
||||
tinfo.get_numbered_type(idaapi.cvar.idati, self.ordinal)
|
||||
return tinfo
|
||||
|
||||
def open_function(self):
|
||||
pass
|
||||
|
||||
def __repr__(self):
|
||||
return self.name + " ^_^ " + str(self.vtables)
|
||||
|
||||
|
||||
class TreeItem:
|
||||
def __init__(self, item, row, parent):
|
||||
self.item = item
|
||||
self.parent = parent
|
||||
self.row = row
|
||||
self.children = []
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.item)
|
||||
|
||||
|
||||
class TreeModel(QtCore.QAbstractItemModel):
|
||||
# TODO: Add higlighting if eip in function, consider setting breakpoints
|
||||
|
||||
refreshed = QtCore.Signal()
|
||||
|
||||
def __init__(self):
|
||||
super(TreeModel, self).__init__()
|
||||
self.tree_data = []
|
||||
self.headers = ["Name", "Declaration", "Address"]
|
||||
|
||||
self.init()
|
||||
# import pydevd
|
||||
# pydevd.settrace("localhost", port=12345, stdoutToServer=True, stderrToServer=True)
|
||||
|
||||
def init(self):
|
||||
idaapi.show_wait_box("Looking for classes...")
|
||||
all_virtual_functions.clear()
|
||||
all_virtual_tables.clear()
|
||||
|
||||
classes = []
|
||||
for ordinal in xrange(1, idaapi.get_ordinal_qty(idaapi.cvar.idati)):
|
||||
result = Class.create_class(ordinal)
|
||||
if result:
|
||||
classes.append(result)
|
||||
|
||||
for class_row, class_ in enumerate(classes):
|
||||
class_item = TreeItem(class_, class_row, None)
|
||||
for vtable_row, vtable in class_.vtables.iteritems():
|
||||
vtable_item = TreeItem(vtable, vtable_row, class_item)
|
||||
vtable_item.children = [TreeItem(function, 0, vtable_item) for function in vtable.virtual_functions]
|
||||
class_item.children.append(vtable_item)
|
||||
self.tree_data.append(class_item)
|
||||
|
||||
idaapi.hide_wait_box()
|
||||
|
||||
def flags(self, index):
|
||||
if index.isValid():
|
||||
return index.internalPointer().item.flags(index.column())
|
||||
|
||||
def index(self, row, column, parent=QtCore.QModelIndex()):
|
||||
if parent.isValid():
|
||||
node = parent.internalPointer()
|
||||
return self.createIndex(row, column, node.children[row])
|
||||
else:
|
||||
return self.createIndex(row, column, self.tree_data[row])
|
||||
|
||||
def parent(self, index):
|
||||
if index.isValid():
|
||||
node = index.internalPointer()
|
||||
if node.parent:
|
||||
return self.createIndex(0, 0, node.parent)
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
def rowCount(self, index=QtCore.QModelIndex()):
|
||||
if index.isValid():
|
||||
node = index.internalPointer()
|
||||
if node:
|
||||
return len(node.children)
|
||||
return len(self.tree_data)
|
||||
|
||||
def columnCount(self, index=QtCore.QModelIndex()):
|
||||
return 3
|
||||
|
||||
def data(self, index, role=QtCore.Qt.DisplayRole):
|
||||
if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
|
||||
node = index.internalPointer()
|
||||
return node.item.data(index.column())
|
||||
elif role == QtCore.Qt.FontRole:
|
||||
return index.internalPointer().item.font(index.column())
|
||||
elif role == QtCore.Qt.ToolTipRole:
|
||||
return index.internalPointer().item.tooltip
|
||||
elif role == QtCore.Qt.BackgroundRole:
|
||||
return index.internalPointer().item.color
|
||||
elif role == QtCore.Qt.ForegroundRole:
|
||||
return QtGui.QBrush(QtGui.QColor("#191919"))
|
||||
return None
|
||||
|
||||
def setData(self, index, value, role=QtCore.Qt.DisplayRole):
|
||||
result = False
|
||||
if role == QtCore.Qt.EditRole and value != "":
|
||||
node = index.internalPointer()
|
||||
result = node.item.setData(index.column(), str(value))
|
||||
return result
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role == QtCore.Qt.DisplayRole and orientation == QtCore.Qt.Horizontal:
|
||||
return self.headers[section]
|
||||
|
||||
def set_first_argument_type(self, indexes):
|
||||
indexes = filter(lambda x: x.column() == 0, indexes)
|
||||
class_name = indexes[0].internalPointer().item.class_name
|
||||
if not class_name:
|
||||
classes = [[x.item.name] for x in self.tree_data]
|
||||
class_chooser = HexRaysPyTools.Forms.MyChoose(classes, "Select Class", [["Name", 25]])
|
||||
idx = class_chooser.Show(True)
|
||||
if idx != -1:
|
||||
class_name = classes[idx][0]
|
||||
if class_name:
|
||||
for index in indexes:
|
||||
index.internalPointer().item.set_first_argument_type(class_name)
|
||||
|
||||
def refresh(self):
|
||||
self.tree_data = []
|
||||
self.modelReset.emit()
|
||||
self.init()
|
||||
self.refreshed.emit()
|
||||
|
||||
def rollback(self):
|
||||
for class_item in self.tree_data:
|
||||
class_item.item.update_from_local_types()
|
||||
self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
|
||||
|
||||
def commit(self):
|
||||
for class_item in self.tree_data:
|
||||
if class_item.item.modified:
|
||||
class_item.item.update_local_type()
|
||||
|
||||
def open_function(self, index):
|
||||
if index.column() == 2:
|
||||
index.internalPointer().item.open_function()
|
||||
|
||||
|
||||
class ProxyModel(QtGui.QSortFilterProxyModel):
|
||||
def __init__(self):
|
||||
super(ProxyModel, self).__init__()
|
||||
self.filter_by_function = False
|
||||
|
||||
def set_regexp_filter(self, regexp):
|
||||
if regexp and regexp[0] == '!':
|
||||
self.filter_by_function = True
|
||||
self.setFilterRegExp(regexp[1:])
|
||||
else:
|
||||
self.filter_by_function = False
|
||||
self.setFilterRegExp(regexp)
|
||||
|
||||
def filterAcceptsRow(self, row, parent):
|
||||
if not parent.isValid() and self.filterRegExp():
|
||||
if self.filter_by_function:
|
||||
return self.sourceModel().tree_data[row].item.has_function(self.filterRegExp())
|
||||
return self.filterRegExp().indexIn(self.sourceModel().tree_data[row].item.class_name) >= 0
|
||||
return True
|
||||
Reference in New Issue
Block a user