import idaapi
import idautils
import idc
from .utils import *
from .type import get_type_by_name, fixup_this_arg_types
[docs]def translate_vptr_references(cfunc):
''' The real 'work' function of Devirtualize. This function takes a
cfuncptr and devirtualizes calls.
'''
def set_obj_ea(expr, val):
''' This function is an unholy incantation. Every time it is called
it is as if millions of voices cry out all at once and are suddenly
silenced. But, for the time being, it is necessary. So here goes:
In the IDA AST representation, function calls (cot_call) have one
child for each argument, and an additional child for the 'object'
doing the call. For normal function calls this child will be a
cot_obj, but it will be a pointer for function pointer calls.
We need to replace this child (it will be the x operand) in the
virtual function calls with a cot_obj once we have resolved the
function. This is a simple procedure in principle, we just create
a new cexpr_t and set 'op', 'type', and 'obj_ea' to the necessary
values. However the python API currently has no way to set the
'obj_ea'. To work around this without the C++ API, we use ctypes
to access the appropriate offset from the this pointer and modify
the value directly. Obviously this will break if the layout of
cexpr_t or its parents change (i.e., with 64bit builds), but it
is the best we can do for now.
'''
from ctypes import POINTER, c_ulonglong, cast
assert(expr.op == idaapi.cot_obj)
c_ulonglong_p = POINTER(c_ulonglong)
offset = TARGET_ADDRESS_SIZE + 12
cast(int(expr.this)+offset, c_ulonglong_p).contents.value = val
assert(expr.obj_ea == val)
class vptr_translator_t(idaapi.ctree_visitor_t):
''' The translator converts the virtual function callsites in the
ast into normal function calls. This process requires a few pieces
of information:
1. The virtual call location
2. The type of the object
3. The index of the vtable being accessed
This information can be difficult to gather because, for example,
the decompiler can choose to use an indexing operation (i.e., vptr[3])
or addition (vptr + 24) to access the function pointer.
'''
def __init__(self):
idaapi.ctree_visitor_t.__init__(self, idaapi.CV_FAST|idaapi.CV_POST)
self.reset()
def reset(self):
self.func = None
self.index = None
self.cast_type = None
self.type = None
self.viable = False
def visit_expr(self, e):
if e.op == idaapi.cot_call:
self.reset()
self.func = e
elif e.op == idaapi.cot_memref or e.op == idaapi.cot_memptr:
if e.type.dstr() == "_vfunc **":
self.viable = True
self.cast_type = e.x.type
elif e.op == idaapi.cot_add:
if (self.func is None or
self.index is not None or
self.type is not None):
self.reset()
return 0
if e.y.op == idaapi.cot_num:
self.index = e.y.numval()
else:
self.reset()
elif e.op == idaapi.cot_var:
if self.func is None:
self.reset()
return 0
elif self.type is None:
self.type = e.type
return 0
return 0
def leave_expr(self, e):
if e.op == idaapi.cot_call:
self.reset()
elif self.func is not None and e.is_child_of(self.func):
if (self.viable is False or
self.cast_type is None or
self.func is None or
self.type is None):
self.reset()
return 0
else:
self.type.remove_ptr_or_array()
# Currently we do this so that base classes have a
# cast_type that is equal to this_type, but maybe
# it causes problems sometimes?
self.cast_type.remove_ptr_or_array()
this_type = get_type_by_name(self.type.dstr())
cast_type = get_type_by_name(self.cast_type.dstr())
if cast_type != this_type:
table = this_type.table_for_cast(cast_type)
else:
table = this_type.tablegroup.primary_table()
if self.index is None:
self.index = 0
func = table.functions[self.index]
# Create a new cot_obj expression and fill it with
# the ea of the function. Also use the type of the
# function.
obj = idaapi.cexpr_t()
obj.op = idaapi.cot_obj
t = tinfo_for_ea(func)
if t is not None:
t.create_ptr(t)
obj.type = t
#FIXME: this is a quick fix to correct the number of
# args to agree with the function type. Type info is
# still not propagated correctly. This might be fixed
# by running the visitor at an earlier maturity, but
# it is more difficult to identify virtual calls at
# earlier stages.
self.func.a.resize(t.get_nargs())
else:
obj.type = self.func.x.type
set_obj_ea(obj, func)
# Replace the existing func object (the virtual function
# pointer) with the new cot_obj
obj.swap(self.func.x)
self.reset()
return 0
translator = vptr_translator_t()
translator.apply_to_exprs(cfunc.body, None)
def translator_callback(event, *args):
if event == idaapi.hxe_maturity:
cfunc, maturity = args
if maturity == idaapi.CMAT_BUILT:
fixup_this_arg_types(cfunc)
elif maturity == idaapi.CMAT_FINAL:
translate_vptr_references(cfunc)
return 0
def register_vptr_translator():
idaapi.install_hexrays_callback(translator_callback)