def _symbolicate_overrides_for_classinfo(classinfo, processed): """A recursive function to symbolicate vtable overrides for a class and its superclasses.""" # If we've already been processed, stop. if classinfo in processed: return # First propagate symbol information to our superclass. if classinfo.superclass: _symbolicate_overrides_for_classinfo(classinfo.superclass, processed) # Now symbolicate the superclass. for _, override, original in class_vtable_overrides(classinfo, methods=True): # Skip this method if the override already has a name and we can't rename it. override_name = idau.get_ea_name(override, user=True) if override_name and not _ok_to_rename_method(override, override_name): continue # Skip this method if the original does not have a name or if it's a bad name. original_name = idau.get_ea_name(original, user=True) if not original_name or _bad_name_dont_use_as_override(original_name): continue # Get the new override name if we substitute for the override class's name. new_name = _vtable_method_symbol_substitute_class(original_name, classinfo.classname) if not new_name: _log(0, 'Could not substitute class {} into method symbol {} for override {:#x}', classinfo.classname, original_name, override) continue # Now that we have the new name, set it. if override_name: _log(2, 'Renaming {} -> {}', override_name, new_name) if not idau.set_ea_name(override, new_name, rename=True): _log(0, 'Could not set name {} for method {:#x}', new_name, override) # We're done. processed.add(classinfo)
def _process_offset(offset, ea, next_offset): """Process an offset in a __got section.""" # Convert the address containing the offset into an offset in IDA, but continue if it fails. if not idc.OpOff(ea, 0, 0): _log(1, 'Could not convert {:#x} into an offset', ea) # Get the name to which the offset refers. name = idau.get_ea_name(offset, user=True) if not name: _log(3, 'Offset at address {:#x} has target {:#x} without a name', ea, offset) return False # Make sure this isn't an offset to another stub or to a jump function to another stub. See the # comment in _symbolicate_stub. if stub.symbol_references_stub(name): _log( 1, 'Offset at address {:#x} has target {:#x} (name {}) that references a stub', ea, offset, name) return False # Set the new name for the offset. symbol = next_offset(name) if symbol is None: _log(0, 'Could not generate offset symbol for {}: names exhausted', name) return False if not idau.set_ea_name(ea, symbol, auto=True): _log(2, 'Could not set name {} for offset at {:#x}', symbol, ea) return False return True
def _populate_vmethods_struct(sid, classinfo): """Populate the ::vmethods struct.""" # Loop over the new vtable methods. super_nmethods = 0 if classinfo.superclass: super_nmethods = classinfo.superclass.vtable_nmethods members = set() for index, vmethod in enumerate(vtable.class_vtable_methods(classinfo)): # Skip entries in the superclass's vtable. if index < super_nmethods: continue # Get the base name of the method (i.e., for Class::method(args), extract method). sym = idau.get_ea_name(vmethod, user=True) base = symbol.method_name(sym) if not base: base = 'method_{}'.format(index) base = symbol.make_ident(base) # We'll try to use the base as our method name, but if it already exists, try appending # "_1", "_2", etc. name = base suffix = 0 while name in members: suffix += 1 name = '{}_{}'.format(base, suffix) members.add(name) # Create the member. offset = (index - super_nmethods) * idau.WORD_SIZE ret = idau.struct_add_ptr(sid, name, offset, type='void *') if ret != 0: _log(0, 'Could not create {}::vmethods.{}: {}', classinfo.classname, name, ret) return False return True
def _symbolicate_stub(stub, target, next_stub): """Set a symbol for a stub function.""" name = idau.get_ea_name(target, username=True) if not name: _log(3, 'Stub {:#x} has target {:#x} without a name', stub, target) return False # Sometimes the target of the stub is a thunk in another kext. This is sometimes OK, but makes # a right mess of things when that thunk is itself a jump function for another stub, and # especially when there are multiple such jump functions to that stub in that kext. # Autorenaming of thunks interacts poorly with autonaming of stubs (you get things like # 'j_TARGET___stub_2_0', which stub_name_target() no longer thinks of as a stub). Thus, if the # current thing has '__stub_' in it, don't rename. The reason we don't just extract the inner # stub reference is that these jump functions are really wrappers with different names and # semantics in the original code, so it's not appropriate for us to cover that up with a stub. if symbol_references_stub(name): _log( 1, 'Stub {:#x} has target {:#x} (name {}) that references another stub', stub, target, name) return False symbol = next_stub(name) if symbol is None: _log(0, 'Could not generate stub symbol for {}: names exhausted', name) return False if not idau.set_ea_name(stub, symbol, auto=True): _log(2, 'Could not set name {} for stub at {:#x}', symbol, stub) return False return True
def _process_stubs_section(segstart, make_thunk, next_stub): """Process all the functions in a __stubs section.""" segend = idc.SegEnd(segstart) # We'll go through each address and check if it has a reference. If it does, it is likely a # stub. As long as the address doesn't already have a stub name, process it. for ea in idau.Addresses(segstart, segend, step=1): if idc.isRef(idc.GetFlags(ea)) and not stub_name_target(idau.get_ea_name(ea)): _process_possible_stub(ea, make_thunk, next_stub)
def _process_offsets_section(segstart, next_offset): """Process all the offsets in a __got section.""" for offset, ea in idau.ReadWords(segstart, idc.SegEnd(segstart), addresses=True): if not offset_name_target(idau.get_ea_name(ea)): # This is not a previously named offset. if idau.is_mapped(offset, value=False): _process_offset(offset, ea, next_offset) else: _log(-1, 'Offset {:#x} at address {:#x} is unmapped', offset, ea)
def add_vtable_symbol(vtable, classname): """Add a symbol for the virtual method table at the specified address. Arguments: vtable: The address of the virtual method table. classname: The name of the C++ class with this virtual method table. Returns: True if the data was successfully converted into a vtable and the symbol was added. """ vtable_symbol = vtable_symbol_for_class(classname) if not idau.set_ea_name(vtable, vtable_symbol): _log(0, 'Address {:#x} already has name {} instead of vtable symbol {}' .format(vtable, idau.get_ea_name(vtable), vtable_symbol)) return False return True
def add_metaclass_symbol(metaclass, classname): """Add a symbol for the OSMetaClass instance at the specified address. Arguments: metaclass: The address of the OSMetaClass instance. classname: The name of the C++ class with this OSMetaClass instance. Returns: True if the OSMetaClass instance's symbol was created successfully. """ metaclass_symbol = metaclass_symbol_for_class(classname) if not idau.set_ea_name(metaclass, metaclass_symbol): _log(0, 'Address {:#x} already has name {} instead of OSMetaClass instance symbol {}' .format(metaclass, idau.get_ea_name(metaclass), metaclass_symbol)) return False return True
def found_vtable(metaclass, vtable, length): # Add our vtable length. vtable_lengths[vtable] = length # If our classname has a defined vtable symbol and that symbol's address isn't this vtable, # don't add the link. classname = metaclass_info[metaclass].classname proper_vtable_symbol = symbol.vtable_symbol_for_class(classname) proper_vtable_symbol_ea = idau.get_name_ea(proper_vtable_symbol) if proper_vtable_symbol_ea not in (idc.BADADDR, vtable): return # If our vtable has a symbol and it doesn't match the metaclass, skip adding a link. vtable_symbol = idau.get_ea_name(vtable, user=True) if vtable_symbol: vtable_classname = symbol.vtable_symbol_get_class(vtable_symbol) if vtable_classname != classname: _log( 2, 'Declining association between metaclass {:x} ({}) and vtable {:x} ({})', metaclass, classname, vtable, vtable_classname) return # Add a link if they are in the same kext. if segment.kernelcache_kext(metaclass) == segment.kernelcache_kext( vtable): metaclass_to_vtable_builder.add_link(metaclass, vtable)