Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import idautils
- import idc
- import ida_bytes
- from pprint import pprint, pformat
- def demangle_name(name):
- return idc.Demangle(name, idc.GetLongPrm(idc.INF_SHORT_DN)) or name
- def get_primary_vtables():
- vtables = {}
- for ea, name in idautils.Names():
- dname = idc.Demangle(name, idc.GetLongPrm(idc.INF_SHORT_DN))
- if dname != None and 'table' in dname:
- if not name.endswith('_0'): # Skip secondary vtables with the _0 suffix.
- vtables[dname] = ea
- return vtables
- def get_new_size_arg(ea, typename=""):
- cur_ea = ea
- # Search backwards for a call to new
- found_new_call = False
- for i in range(50):
- cur_ea = idc.PrevHead(cur_ea)
- if idc.GetMnem(cur_ea) == "call":
- dname = demangle_name(idc.GetOpnd(cur_ea, 0))
- if dname == "operator new(unsigned __int64)":
- found_new_call = True
- break
- if not found_new_call:
- return -1
- # Found the new call, now search for the (r||e)cx register argument.
- for i in range(20):
- cur_ea = idc.PrevHead(cur_ea)
- if idc.GetMnem(cur_ea) == "mov" and idc.GetOpnd(cur_ea, 0).endswith('cx'):
- if idc.GetOpType(cur_ea, 1) == idc.o_imm:
- return idc.GetOperandValue(cur_ea, 1)
- else:
- print("[CWT!] ERROR: got non-immediate operand for `new` argument. Can't parse this.")
- print("[CWT!] \tAt:{:X}, for starting ea: {:X}, type:{}".format(cur_ea, ea, typename))
- return -1
- return -1
- def get_new_size_arg_from_first_xref_ea(ea, typename=""):
- cur_ea = -1
- # Get ea of first xref.
- for xref in idautils.XrefsTo(ea, 0):
- cur_ea = xref.frm
- break
- if cur_ea == -1:
- return -1
- return get_new_size_arg(cur_ea, typename)
- def get_free_size_arg_from_vtable_ea(vtable_ea, typename):
- # Read the dtor from vtable[0].
- dtor_ea = ida_bytes.get_qword(vtable_ea)
- cur_ea = dtor_ea
- # Search forwards for a call to free.
- found_free_call = False
- func_end_ea = idc.GetFunctionAttr(dtor_ea, idc.FUNCATTR_END)
- while cur_ea < func_end_ea:
- cur_ea = idc.NextHead(cur_ea)
- if idc.GetMnem(cur_ea) == "call":
- dname = demangle_name(idc.GetOpnd(cur_ea, 0))
- if 'j_j_free' in dname:
- found_free_call = True
- break
- if not found_free_call:
- return -1
- # Now go back and find the size argument passed in (r||e)dx
- while cur_ea > dtor_ea:
- cur_ea = idc.PrevHead(cur_ea)
- if idc.GetMnem(cur_ea) == "mov" and idc.GetOpnd(cur_ea, 0).endswith('dx'):
- if idc.GetOpType(cur_ea, 1) == idc.o_imm:
- return idc.GetOperandValue(cur_ea, 1)
- else:
- print("[CWT_MARKERS!] ERROR: got non-immediate operand for `free` argument. Can't parse this.")
- print("[CWT_MARKERS!] \tAt:{:X}, for dtor: {:X}, type:{}".format(cur_ea, dtor_ea, typename))
- return -1
- return -1
- def get_exception_size(typename):
- # Go over each name until we get the RTTI type descriptor,
- # then go over it's xrefs until we find a comment in some RTTI that ida parses, but can't demangle the name of,
- # then grab the parsed size.
- for ea, name in idautils.Names():
- dname = idc.Demangle(name, idc.GetLongPrm(idc.INF_SHORT_DN))
- if dname != None and typename in dname and 'RTTI Type Descriptor' in dname:
- for xref in idautils.XrefsTo(ea, 0):
- size_ea = xref.frm+16
- if(Comment(size_ea) == "size of thrown object"):
- return ida_bytes.get_dword(size_ea)
- return -1
- # Goes over a function and returns a list of called function ea values.
- def get_called_funcs(ea):
- cur_ea = idc.GetFunctionAttr(ea, idc.FUNCATTR_START)
- end_ea = idc.GetFunctionAttr(ea, idc.FUNCATTR_END)
- called_funcs = []
- while cur_ea < end_ea:
- cur_ea = idc.NextHead(cur_ea)
- if idc.GetMnem(cur_ea) == "call":
- called_funcs.append(idc.GetOperandValue(cur_ea, 0))
- return called_funcs
- # Tracks value moved into (r||e)cx from an lea displacement offset.
- def get_tracked_ctor_cx_lea_offset(ea, ctor_typename=""):
- tracked_reg = "rcx"
- func_start_ea = idc.GetFunctionAttr(ea, idc.FUNCATTR_START)
- cur_ea = ea
- while cur_ea > func_start_ea:
- cur_ea = idc.PrevHead(cur_ea)
- mnem = idc.GetMnem(cur_ea)
- if mnem == "mov" and idc.GetOpnd(cur_ea, 0) == tracked_reg:
- if idc.GetOpType(cur_ea, 1) == idc.o_reg:
- tracked_reg = idc.GetOpnd(cur_ea, 1)
- elif idc.GetOpType(cur_ea, 1) == idc.o_displ:
- return idc.GetOperandValue(cur_ea, 1)
- else:
- print("[CWT_MARKERS!] Got non reg -> reg mov while tracking ctor rcx lea at 0x{:X} for ctor: 0x{:X}({})! Can't parse!".format(cur_ea, ea, ctor_typename))
- return -1
- elif mnem == "lea":
- if idc.GetOpnd(cur_ea, 0) == tracked_reg:
- if idc.GetOpType(cur_ea, 1) == idc.o_displ:
- return idc.GetOperandValue(cur_ea, 1)
- elif mnem == "add":
- if idc.GetOpnd(cur_ea, 0) == tracked_reg:
- if idc.GetOpType(cur_ea, 1) == idc.o_imm:
- return idc.GetOperandValue(cur_ea, 1)
- return 0
- def get_string_ea(s):
- return idc.FindBinary(idaapi.get_imagebase(), idc.SEARCH_NEXT|idc.SEARCH_DOWN|idc.SEARCH_CASE, '"{}"'.format(s))
- def method_ea_from_xrefed_str(s):
- return idc.GetFunctionAttr(list(idautils.XrefsTo(get_string_ea(s)))[0].frm, idc.FUNCATTR_START)
- def method_ea_from_aob(aob):
- return id
- def main():
- type_to_info = {}
- # Parse out the type name from the vftable symbol.
- for key, value in get_primary_vtables().iteritems():
- typename = key
- if typename.endswith("::`vftable'"):
- typename = typename[:-11]
- if typename.startswith("const "):
- typename = typename[6:]
- type_to_info[typename] = {'vtable_ea' :value, 'ctor_ea': -1, 'ctor_inlined': False, 'fields': []}
- # Get a list of dtor-related functions so that we don't get a dtor when trying to xref for a ctor.
- dtor_related_functions = []
- for typename, typeinfo in type_to_info.iteritems():
- dtor_ea = ida_bytes.get_qword(typeinfo['vtable_ea'])
- dtor_related_functions.append(dtor_ea)
- dtor_called = get_called_funcs(dtor_ea)
- dtor_related_functions.extend(dtor_called)
- # Find out which ctor belongs to which class
- # by checking which vtable is refenced first in each.
- ctor_functions = {}
- for typename, typeinfo in type_to_info.iteritems():
- for xref in idautils.XrefsTo(typeinfo['vtable_ea'], 0):
- ctor_ea = idc.GetFunctionAttr(xref.frm, idc.FUNCATTR_START)
- if ctor_ea not in dtor_related_functions:
- if ctor_ea not in ctor_functions:
- ctor_functions[ctor_ea] = []
- ctor_functions[ctor_ea].append((xref.frm, typename))
- break
- for ctor_ea in ctor_functions.keys():
- # Find the owner
- ctor_owner = (0xFFFFFFFFFFFFFFFF, '')
- for vtable_ref in ctor_functions[ctor_ea]:
- if vtable_ref[0] < ctor_owner[0]:
- ctor_owner = vtable_ref
- # Set the class's ctor_ea's
- for vtable_ref in ctor_functions[ctor_ea]:
- if vtable_ref[1] == ctor_owner[1]:
- type_to_info[vtable_ref[1]]['ctor_ea'] = ctor_ea
- else:
- type_to_info[vtable_ref[1]]['ctor_inlined'] = True
- # Try to get class size.
- for typename, typeinfo in type_to_info.iteritems():
- # First try to the size via `free` in vtable[0] dtor.
- size = get_free_size_arg_from_vtable_ea(typeinfo['vtable_ea'], typename)
- # If the size wasn't gotten via `free`, try to get it via the `new` call before ctor.
- if size == -1 and not type_to_info[typename]['ctor_inlined']:
- size = get_new_size_arg_from_first_xref_ea(typeinfo['ctor_ea'], typename)
- # If the size still wasn't gotten, the ctor might be inlined:
- if size == -1:
- size = get_new_size_arg_from_first_xref_ea(typeinfo['vtable_ea'])
- if size != -1:
- type_to_info[typename]['ctor_inlined'] = True
- # Or maybe it's an exception after all.
- if size == -1:
- size = get_exception_size(typename)
- if size == -1:
- # Aww.
- print("[CWT_MARKERS!] Failed to get size for {}".format(typename))
- type_to_info[typename]['size'] = size
- # Make an ctor ea -> typename map for quick lookup.
- ctor_to_typename = {}
- for typename, typeinfo in type_to_info.iteritems():
- ctor_to_typename[typeinfo['ctor_ea']] = typename
- func_to_datatype = {
- idc.get_name_ea(0, "InitializeCriticalSectionAndSpinCount"): "CRITICAL_SECTION",
- method_ea_from_xrefed_str("SRAND initialize"): "cube::World*", # cube::World::SeedExtend
- #method_ea_from_aob("f3 0f ?? ?? ?? ?? ?? ?? 75 ?? 80 ?? ?? ?? 00 00 01 75"): "cube::Creature*"
- }
- # Try to get some offsets from class ctor calls and other functions.
- for typename, typeinfo in type_to_info.iteritems():
- ctor_start_ea = idc.GetFunctionAttr(typeinfo['ctor_ea'], idc.FUNCATTR_START)
- ctor_end_ea = idc.GetFunctionAttr(typeinfo['ctor_ea'], idc.FUNCATTR_END)
- cur_ea = ctor_start_ea
- while cur_ea < ctor_end_ea:
- cur_ea = idc.NextHead(cur_ea)
- if idc.GetMnem(cur_ea) == "call":
- # Check if it's calling a class ctor.
- op_val = idc.GetOperandValue(cur_ea, 0)
- if op_val in ctor_to_typename:
- member_type = ctor_to_typename[op_val]
- dname = demangle_name(idc.GetOpnd(cur_ea, 0))
- # Make sure it's not inlined or `new`ed.
- if not type_to_info[member_type]['ctor_inlined']:
- if type_to_info[member_type]['size'] != get_new_size_arg(cur_ea):
- #print("{}::ctor -> {} ({})".format(typename, dname, ctor_to_typename[op_val]))
- type_to_info[typename]['fields'].append((get_tracked_ctor_cx_lea_offset(cur_ea, member_type), ctor_to_typename[op_val]))
- # Check if it's calling one of our other func -> datatype functions.
- elif op_val in func_to_datatype:
- datatype = func_to_datatype[op_val]
- type_to_info[typename]['fields'].append((get_tracked_ctor_cx_lea_offset(cur_ea, datatype), datatype))
- # Print the classes and their sizes
- for typename in sorted(type_to_info):
- typeinfo = type_to_info[typename]
- if typename.startswith("cube") or typename.startswith("plasma") or typename.startswith("gfx"):
- print("class {} // VTable:0x{:X}, Size:0x{:X}, ctor_inlined:{}".format(typename, typeinfo['vtable_ea'], typeinfo['size'], typeinfo['ctor_inlined']))
- print("{")
- for (offset, type) in typeinfo['fields']:
- print("\t+{:X} {}".format(offset, type))
- print("};")
- #print("Class: {}, {} Size: 0x{:X}".format(typename, name_pad, typeinfo['size']))
- #pprint(type_to_info)
- main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement