256 lines
11 KiB
Python
256 lines
11 KiB
Python
import re
|
|
import logging
|
|
import idaapi
|
|
import idc
|
|
import Helper
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def parse_lvar_comment(lvar):
|
|
if lvar.type().is_ptr():
|
|
m = re.search('```(.+)```', lvar.cmt)
|
|
if m:
|
|
structure_name, offset = m.group(1).split('+')
|
|
offset = int(offset)
|
|
parent_tinfo = idaapi.tinfo_t()
|
|
if parent_tinfo.get_named_type(idaapi.cvar.idati, structure_name) and parent_tinfo.get_size() > offset:
|
|
member_name = dict(find_deep_members(parent_tinfo, lvar.type().get_pointed_object())).get(offset, None)
|
|
if member_name:
|
|
return NegativeLocalInfo(lvar.type().get_pointed_object(), parent_tinfo, offset, member_name)
|
|
return None
|
|
|
|
|
|
def find_deep_members(parent_tinfo, target_tinfo):
|
|
udt_data = idaapi.udt_type_data_t()
|
|
parent_tinfo.get_udt_details(udt_data)
|
|
result = []
|
|
for udt_member in udt_data:
|
|
if udt_member.type.equals_to(target_tinfo):
|
|
result.append((udt_member.offset / 8, udt_member.name))
|
|
elif udt_member.type.is_udt():
|
|
for offset, name in find_deep_members(udt_member.type, target_tinfo):
|
|
final_name = udt_member.name + '.' + name if udt_member.name else name
|
|
result.append((udt_member.offset / 8 + offset, final_name))
|
|
return result
|
|
|
|
|
|
class NegativeLocalInfo:
|
|
def __init__(self, tinfo, parent_tinfo, offset, member_name):
|
|
self.tinfo = tinfo
|
|
self.size = tinfo.get_size() if tinfo.is_udt else 0
|
|
self.parent_tinfo = parent_tinfo
|
|
self.offset = offset
|
|
self.member_name = member_name
|
|
|
|
def __repr__(self):
|
|
return "Type - {0}, parent type - {1}, offset - {2}, member_name - {3}".format(
|
|
self.tinfo.dstr(),
|
|
self.parent_tinfo.dstr(),
|
|
self.offset,
|
|
self.member_name
|
|
)
|
|
|
|
|
|
class NegativeLocalCandidate:
|
|
def __init__(self, tinfo, offset):
|
|
"""
|
|
Tinfo - type of the structure tha local variable points to. So it's stripped from pointer. Offset - is first
|
|
found offset that points outside of the structure.
|
|
:param tinfo: idaapi.tinfo_t
|
|
:param offset: int
|
|
"""
|
|
self.tinfo = tinfo
|
|
self.offsets = [offset]
|
|
|
|
def __repr__(self):
|
|
return self.tinfo.dstr() + ' ' + str(self.offsets)
|
|
|
|
def is_structure_offset(self, tinfo, offset):
|
|
# Checks if structure tinfo contains a member at given offset
|
|
# TODO:array checking
|
|
udt_member = idaapi.udt_member_t()
|
|
udt_member.offset = offset * 8
|
|
if offset >= 0 and tinfo.find_udt_member(idaapi.STRMEM_OFFSET, udt_member) != -1:
|
|
if udt_member.type.is_udt():
|
|
return self.is_structure_offset(udt_member.type, offset - udt_member.offset / 8)
|
|
return udt_member.offset == offset * 8
|
|
return False
|
|
|
|
def find_containing_structures(self, type_library):
|
|
"""
|
|
Given the type library creates a list of structures from this library, that contains this structure and
|
|
satisfy offset conditions.
|
|
:param type_library: idaapi.til_t
|
|
:returns: ordinal, offset, member_name, containing structure name
|
|
"""
|
|
|
|
min_offset = min(self.offsets)
|
|
min_offset = min_offset if min_offset < 0 else 0
|
|
max_offset = max(self.offsets)
|
|
max_offset = max_offset if max_offset > 0 else self.tinfo.get_size()
|
|
# TODO: Check if all offsets are legal
|
|
|
|
# Least acceptable size of the containing structure
|
|
min_struct_size = max_offset - min_offset
|
|
result = []
|
|
parent_tinfo = idaapi.tinfo_t()
|
|
target_tinfo = idaapi.tinfo_t()
|
|
if not target_tinfo.get_named_type(type_library, self.tinfo.dstr()):
|
|
print "[Warning] Such type doesn't exist in '{0}' library".format(type_library.name)
|
|
return result
|
|
for ordinal in xrange(1, idaapi.get_ordinal_qty(type_library)):
|
|
parent_tinfo.create_typedef(type_library, ordinal)
|
|
if parent_tinfo.get_size() >= min_struct_size:
|
|
for offset, name in find_deep_members(parent_tinfo, target_tinfo):
|
|
# print "[DEBUG] Found {0} at {1} in {2}".format(name, offset, parent_tinfo.dstr())
|
|
if offset + min_offset >= 0 and offset + max_offset <= parent_tinfo.get_size():
|
|
result.append((ordinal, offset, name, parent_tinfo.dstr()))
|
|
return result
|
|
|
|
|
|
class ReplaceVisitor(idaapi.ctree_parentee_t):
|
|
|
|
def __init__(self, negative_lvars):
|
|
super(ReplaceVisitor, self).__init__()
|
|
self.negative_lvars = negative_lvars
|
|
self.pvoid_tinfo = idaapi.tinfo_t(idaapi.BT_VOID)
|
|
self.pvoid_tinfo.create_ptr(self.pvoid_tinfo)
|
|
|
|
def visit_expr(self, expression):
|
|
if expression.op == idaapi.cot_add and expression.x.op == idaapi.cot_var and expression.y.op == idaapi.cot_num:
|
|
index = expression.x.v.idx
|
|
if index in self.negative_lvars:
|
|
offset = expression.y.numval()
|
|
if offset >= self.negative_lvars[index].size:
|
|
self.create_containing_record(expression, index, offset)
|
|
elif expression.op == idaapi.cot_sub and expression.x.op == idaapi.cot_var and expression.y.op == idaapi.cot_num:
|
|
index = expression.x.v.idx
|
|
if index in self.negative_lvars:
|
|
offset = -expression.y.n.value(idaapi.tinfo_t(idaapi.BT_INT))
|
|
self.create_containing_record(expression, index, offset)
|
|
return 0
|
|
|
|
def create_containing_record(self, expression, index, offset):
|
|
negative_lvar = self.negative_lvars[index]
|
|
logger.debug("Creating CONTAINING_RECORD macro, offset: {}, negative offset: {}, TYPE: {}".format(
|
|
negative_lvar.offset,
|
|
offset,
|
|
negative_lvar.parent_tinfo.dstr()
|
|
))
|
|
|
|
arg_address = idaapi.carg_t()
|
|
if expression.op == idaapi.cot_var:
|
|
arg_address.assign(expression)
|
|
else:
|
|
arg_address.assign(expression.x)
|
|
|
|
arg_type = idaapi.carg_t()
|
|
cexpr_helper = idaapi.create_helper(True, self.pvoid_tinfo, negative_lvar.parent_tinfo.dstr())
|
|
arg_type.assign(cexpr_helper)
|
|
|
|
arg_field = idaapi.carg_t()
|
|
cexpr_helper = idaapi.create_helper(
|
|
True,
|
|
self.pvoid_tinfo,
|
|
negative_lvar.member_name
|
|
)
|
|
arg_field.assign(cexpr_helper)
|
|
return_tinfo = idaapi.tinfo_t(negative_lvar.parent_tinfo)
|
|
return_tinfo.create_ptr(return_tinfo)
|
|
new_cexpr_call = idaapi.call_helper(return_tinfo, None, "CONTAINING_RECORD")
|
|
new_cexpr_call.a.push_back(arg_address)
|
|
new_cexpr_call.a.push_back(arg_type)
|
|
new_cexpr_call.a.push_back(arg_field)
|
|
new_cexpr_call.thisown = False
|
|
|
|
parent = reversed(self.parents).next().cexpr
|
|
|
|
diff = negative_lvar.offset + offset
|
|
if diff:
|
|
number = idaapi.make_num(diff)
|
|
number.thisown = False
|
|
new_cexpr_add = Helper.my_cexpr_t(idaapi.cot_add, x=new_cexpr_call, y=number)
|
|
new_cexpr_add.type = return_tinfo
|
|
|
|
if parent.op == idaapi.cot_ptr:
|
|
tmp_tinfo = idaapi.tinfo_t()
|
|
tmp_tinfo.create_ptr(parent.type)
|
|
new_cexpr_cast = Helper.my_cexpr_t(idaapi.cot_cast, x=new_cexpr_add)
|
|
new_cexpr_cast.thisown = False
|
|
new_cexpr_cast.type = tmp_tinfo
|
|
expression.assign(new_cexpr_cast)
|
|
else:
|
|
expression.assign(new_cexpr_add)
|
|
else:
|
|
if parent.op == idaapi.cot_ptr:
|
|
tmp_tinfo = idaapi.tinfo_t()
|
|
tmp_tinfo.create_ptr(parent.type)
|
|
new_cexpr_cast = Helper.my_cexpr_t(idaapi.cot_cast, x=new_cexpr_call)
|
|
new_cexpr_cast.thisown = False
|
|
new_cexpr_cast.type = tmp_tinfo
|
|
expression.assign(new_cexpr_cast)
|
|
else:
|
|
expression.assign(new_cexpr_call)
|
|
|
|
|
|
class SearchVisitor(idaapi.ctree_parentee_t):
|
|
def __init__(self, cfunc):
|
|
super(SearchVisitor, self).__init__()
|
|
self.cfunc = cfunc
|
|
self.result = {}
|
|
|
|
def visit_expr(self, expression):
|
|
if expression.op == idaapi.cot_call and expression.x.op == idaapi.cot_helper and len(expression.a) == 3:
|
|
if expression.x.helper == "CONTAINING_RECORD":
|
|
if expression.a[0].op == idaapi.cot_var:
|
|
idx = expression.a[0].v.idx
|
|
if expression.a[1].op == idaapi.cot_helper and expression.a[2].op == idaapi.cot_helper:
|
|
parent_name = expression.a[1].helper
|
|
member_name = expression.a[2].helper
|
|
parent_tinfo = idaapi.tinfo_t()
|
|
if not parent_tinfo.get_named_type(idaapi.cvar.idati, parent_name):
|
|
return 0
|
|
udt_data = idaapi.udt_type_data_t()
|
|
parent_tinfo.get_udt_details(udt_data)
|
|
udt_member = filter(lambda x: x.name == member_name, udt_data)
|
|
if udt_member:
|
|
tinfo = udt_member[0].type
|
|
self.result[idx] = NegativeLocalInfo(
|
|
tinfo,
|
|
parent_tinfo,
|
|
udt_member[0].offset / 8,
|
|
member_name
|
|
)
|
|
return 1
|
|
return 0
|
|
|
|
|
|
class AnalyseVisitor(idaapi.ctree_parentee_t):
|
|
def __init__(self, candidates, potential_negatives):
|
|
super(AnalyseVisitor, self).__init__()
|
|
self.candidates = candidates
|
|
self.potential_negatives = potential_negatives
|
|
self.potential_negatives.clear()
|
|
|
|
def visit_expr(self, expression):
|
|
if expression.op == idaapi.cot_add and expression.y.op == idaapi.cot_num:
|
|
if expression.x.op == idaapi.cot_var and expression.x.v.idx in self.candidates:
|
|
idx = expression.x.v.idx
|
|
number = expression.y.numval()
|
|
if self.candidates[idx].get_size() <= number:
|
|
if idx in self.potential_negatives:
|
|
self.potential_negatives[idx].offsets.append(number)
|
|
else:
|
|
self.potential_negatives[idx] = NegativeLocalCandidate(self.candidates[idx], number)
|
|
elif expression.op == idaapi.cot_sub and expression.y.op == idaapi.cot_num:
|
|
if expression.x.op == idaapi.cot_var and expression.x.v.idx in self.candidates:
|
|
idx = expression.x.v.idx
|
|
number = -expression.y.numval()
|
|
if idx in self.potential_negatives:
|
|
self.potential_negatives[idx].offsets.append(number)
|
|
else:
|
|
self.potential_negatives[idx] = NegativeLocalCandidate(self.candidates[idx], number)
|
|
|
|
return 0
|