def _create_class_structs__slices(classinfo, endmarkers=True): """Create the IDA structs for a C++ class.""" classname = classinfo.classname # Open or create the structs. sidf = idau.struct_open(classname + '::fields', create=True) sid = idau.struct_open(classname, create=True) if sid is None or sidf is None: _log(0, 'Could not create class structs for {}', classname) return None assert all(not idc.IsUnion(s) for s in (sidf, sid)) # Calculate the size of the ::fields struct. if classinfo.superclass: # If we have a superclass, our fields start after our superclass's fields end. fields_start = classinfo.superclass.class_size else: # If we don't have a superclass, our fields start after our vtable. fields_start = idau.WORD_SIZE fields_size = classinfo.class_size - fields_start # Add an ::end member to the fields struct if requested. if endmarkers: ret = idc.AddStrucMember(sidf, classname + '::end', fields_size, idc.FF_UNK, -1, 0) if ret not in (0, idc.STRUC_ERROR_MEMBER_NAME, idc.STRUC_ERROR_MEMBER_OFFSET): # If that didn't work that's too bad, but continue anyway. _log(0, 'Could not create {}::end', classname) return sid, sidf, fields_start
def _create_class_structs__unions(classinfo): """Create the IDA structs for a C++ class.""" classname = classinfo.classname sidf = idau.struct_open(classname + '::fields', create=True) sid = idau.struct_open(classname, union=True, create=True) if sid is None or sidf is None: _log(0, 'Could not create class structs for {}', classname) return None return sid, sidf
def _populate_wrapper_struct__slices(sid, classinfo): """Fill in the members of the wrapper struct.""" # First add the vtable pointer. offset = 0 vtable_ptr_type = '{}::vtable *'.format(classinfo.classname) ret = idau.struct_add_ptr(sid, 'vtable', offset, type=vtable_ptr_type) if ret not in (0, idc.STRUC_ERROR_MEMBER_OFFSET): _log(0, 'Could not create {}.vtable: {}', classinfo.classname, ret) return False # Now add all the ::fields structs. offset += idau.WORD_SIZE for ci in classinfo.ancestors(inclusive=True): # Get the sid of the ::fields struct. fields_sid = idau.struct_open(ci.classname + '::fields') if fields_sid is None: _log(0, 'Could not find {}::fields', ci.classname) return False # If this is a 0-length struct (no fields), skip it. size = idc.GetStrucSize(fields_sid) if size == 0: continue # If this is already in the wrapper struct, skip it. This avoids weird # STRUC_ERROR_MEMBER_VARLAST errors. if idc.GetMemberOffset(sid, ci.classname) != -1: continue # Add the ::fields struct to the wrapper. ret = idau.struct_add_struct(sid, ci.classname, offset, fields_sid) if ret != 0: _log(0, 'Could not create {}.{}: {}', classinfo.classname, ci.classname, ret) return False offset += size return True
def _populate_vtable_struct(sid, classinfo): """Populate the ::vtable struct.""" # For each ancestor from root down to us (inclusive), add our ::vmethods struct. for ci in classinfo.ancestors(inclusive=True): # Get the offset at which the ::vmethods for ci will be. offset = 0 if ci.superclass: offset = ci.superclass.vtable_nmethods * idau.WORD_SIZE # The size is ci's vtable length minus the offset. vmethods_size = ci.vtable_nmethods * idau.WORD_SIZE - offset # If the vmethods_size is 0, skip this entry. Otherwise we get weird # "struct->til conversion failed" errors. if vmethods_size == 0: continue # Get the sid for ci's ::vmethods. vmethods_sid = idau.struct_open(ci.classname + '::vmethods') if vmethods_sid is None: _log(0, 'Could not find {}::vmethods', ci.classname) return False # Add this ::vmethods slice to the ::vtable struct. ret = idau.struct_add_struct(sid, ci.classname, offset, vmethods_sid) if ret != 0: _log(0, 'Could not add {}::vmethods to {}::vtable', ci.classname, classinfo.classname) return False return True
def create_struct_fields(sid=None, name=None, accesses=None, create=False, base=0): """Create an IDA struct with fields corresponding to the specified access pattern. Given a sequence of (offset, size) tuples designating the valid access points to the struct, create fields in the struct at the corresponding positions. Options: sid: The struct id, if the struct already exists. name: The name of the struct to update or create. accesses: The set of (offset, size) tuples representing the valid access points in the struct. create: If True, then the struct will be created with the specified name if it does not already exist. Default is False. base: The base offset for the struct. Offsets smaller than this are ignored, otherwise the field is created at the offset minus the base. Default is 0. Either sid or name must be specified. """ # Get the struct id. if sid is None: sid = idau.struct_open(name, create=True) if sid is None: _log(0, 'Could not open struct {}', name) return False else: name = idc.GetStrucName(sid) if name is None: _log(0, 'Invalid struct id {}', sid) return False # Now, for each (offset, size) pair, create a struct member. Right now we completely ignore the # possibility that some members will overlap (for various reasons; it's actually more common # than I initially thought, though I haven't investigated why). # TODO: In the future we should address this by either automatically generating sub-unions or # choosing the most appropriate member when permissible (e.g. (0, 8), (0, 2), (4, 4) might # create (0, 2), (2, 2), (4, 4)). I think the most reasonable default policy is to create the # biggest members that satisfy all accesses. success = True for offset, size in accesses: if offset < base: continue member = field_name(offset) ret = idau.struct_add_word(sid, member, offset - base, size) if ret != 0: if ret == idc.STRUC_ERROR_MEMBER_OFFSET: _log(2, 'Could not add {}.{} for access ({}, {})', name, member, offset, size) else: success = False _log(1, 'Could not add {}.{} for access ({}, {}): {}', name, member, offset, size, ret) return success
def _convert_operands_to_struct_offsets(access_addresses): """Convert the operands that generated struct accesses into struct offsets.""" for classname, addresses_and_deltas in access_addresses.items(): sid = idau.struct_open(classname) if sid is not None: for ea, delta in addresses_and_deltas: insn = idautils.DecodeInstruction(ea) if insn: for op in insn.Operands: if op.type == idaapi.o_displ: if not idau.insn_op_stroff(insn, op.n, sid, delta): _log(1, 'Could not convert {:#x} to struct offset for class {} ' 'delta {}', ea, classname, delta)
def _propagate_virtual_method_type_for_method(classinfo, class_vindex, vmethod): """Propagate the type of a class's virtual method to the vtable struct.""" if not idau.is_function_start(vmethod): _log(2, 'Not a function start: {:x}', vmethod) return False vmethod_type = idc.GuessType(vmethod) if not vmethod_type: _log(2, 'No guessed type: {:x}', vmethod) return False vmethod_ptr_type = symbol.convert_function_type_to_function_pointer_type(vmethod_type) if not vmethod_ptr_type: _log(2, 'Could not convert to function pointer type: {:x}', vmethod) return False vmethods_sid = idau.struct_open(classinfo.classname + '::vmethods') vmethod_offset = class_vindex * idau.WORD_SIZE vmethod_mid = idc.GetMemberId(vmethods_sid, vmethod_offset) if not bool(idc.SetType(vmethod_mid, vmethod_ptr_type)): _log(2, 'Could not set vmethod field type: {:x}, {}, {}', vmethod, classinfo.classname, class_vindex) return False return True
def _set_class_style(style): """Set the global class style.""" global _style_was_set, _create_class_structs, _populate_class_structs assert style in (CLASS_SLICES, CLASS_UNIONS) # Check the current style based on OSObject, a class that should always exist. sid = idau.struct_open('OSObject') want_union = style == CLASS_UNIONS if sid is None: # No global style has been set. idau.struct_create('OSObject', union=want_union) else: # A style already exists. Check that the requested style matches. is_union = bool(idc.IsUnion(sid)) if is_union != want_union: raise ValueError('Incompatible style {}', style) # Set the appropriate functions based on the style. if style == CLASS_SLICES: _create_class_structs = _create_class_structs__slices _populate_class_structs = _populate_class_structs__slices else: _create_class_structs = _create_class_structs__unions _populate_class_structs = _populate_class_structs__unions
def _populate_wrapper_struct__unions(sid, classinfo): """Fill in the members of the wrapper struct.""" # First add the vtable pointer. vtable_ptr_type = '{}::vtable *'.format(classinfo.classname) ret = idau.struct_add_ptr(sid, 'vtable', -1, type=vtable_ptr_type) if ret not in (0, idc.STRUC_ERROR_MEMBER_NAME): _log(0, 'Could not create {}.vtable: {}', classinfo.classname, ret) return False # Now add all the ::fields structs. for ci in classinfo.ancestors(inclusive=True): # Get the sid of the ::fields struct. fields_sid = idau.struct_open(ci.classname + '::fields') if fields_sid is None: _log(0, 'Could not find {}::fields', ci.classname) return False # Add the ::fields struct to the wrapper. Ignore STRUC_ERROR_MEMBER_UNIVAR if the ::fields # struct has length 0. ret = idau.struct_add_struct(sid, ci.classname, -1, fields_sid) if ret not in (0, idc.STRUC_ERROR_MEMBER_NAME, idc.STRUC_ERROR_MEMBER_UNIVAR): _log(0, 'Could not create {}.{}: {}', classinfo.classname, ci.classname, ret) return False return True