From 38eebeb5484e14bc3e4ae64189599ccc698cf1be Mon Sep 17 00:00:00 2001 From: Yoav Sternberg Date: Fri, 25 Jul 2025 13:30:58 +0300 Subject: [PATCH 1/2] Fix vtable function name conflict handling by adding suffixes to duplicate names --- ida_kernelcache/phases/create_types.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ida_kernelcache/phases/create_types.py b/ida_kernelcache/phases/create_types.py index f519c1d..e050468 100644 --- a/ida_kernelcache/phases/create_types.py +++ b/ida_kernelcache/phases/create_types.py @@ -100,13 +100,18 @@ def _create_types_bfs(self): # Might raise StringExtractionError or DemanglingError func_name = symbols.extract_method_name(vtable_entry.vmethod_info.mangled_symbol) - # TODO: IDA has a bug that does will fail to parse vtable declaration if two vmethods have the same name, - # even if they have a different signature. + # IDA has a bug that does will fail to parse vtable declaration if two vmethods have the same name, + # even if they have a different signature. We will add a suffix to the function name in this case. if func_name in func_names: func_name_conflicts += 1 - # TODO: maybe just add a running suffix - func_name = consts.VMETHOD_NAME_TEMPLATE.format(index=vtable_entry.index) + for i in range(20): + new_func_name = f'{func_name}{i}' + if new_func_name not in func_names: + func_name = new_func_name + break + else: + func_name = consts.VMETHOD_NAME_TEMPLATE.format(index=vtable_entry.index) func_names.add(func_name) From 370a0271ea8465b7eb1b61e6fc7491da735786f8 Mon Sep 17 00:00:00 2001 From: Yoav Sternberg Date: Fri, 25 Jul 2025 15:59:22 +0300 Subject: [PATCH 2/2] Fix pac algorithm --- ida_kernelcache/phases/pac_symbolicate.py | 43 ++++++++++++----------- ida_kernelcache/rtti.py | 7 ++-- tasks.todo | 1 + 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/ida_kernelcache/phases/pac_symbolicate.py b/ida_kernelcache/phases/pac_symbolicate.py index 423b6a4..831ba75 100644 --- a/ida_kernelcache/phases/pac_symbolicate.py +++ b/ida_kernelcache/phases/pac_symbolicate.py @@ -81,6 +81,9 @@ def run(self): f'pv:{int(vtable_entry.pure_virtual)} ' f'a:{int(vtable_entry.added)}') + if class_info.superclass is not None and class_info.superclass.vtable_info is None: + self.log.warning(f"{class_info.class_name}: Superclass vtable info is None, Could not handle overrides.") + num_not_symbolicated = len(self._kc.vmethod_info_map) - self._kc.vmethod_info_map.num_symbolicated self.log.info(f'{self.num_total_vtable_entries} total vtable entries (multiple may point to the same vmethod)') @@ -111,29 +114,27 @@ def _handle_added(self, origin_class_info: 'ClassInfo', vtable_entry: 'VtableEnt If the vtable entry was added in this current class we search the symbols db. Unfortunately, in case we don't find a match, we don't have information about this vmethod So we use some generic class_name::vmethod_{i} template. We may use the "guess type" functionality of IDA but it is far from accurate. - Searching the descendants adds ~100 symbols """ - for class_info in origin_class_info.descendants(inclusive=True): - mangled_symbol = self._lookup_db(class_info.class_name, vtable_entry.pac_diversifier) - if mangled_symbol: - self.num_added += 1 - - # In case we were able to resolve through superclass pac dict then - # must adjust the classname of the symbol to fit the current class - if class_info != origin_class_info: - return symbols.sub_classname(mangled_symbol, origin_class_info.class_name) - return mangled_symbol + mangled_symbol = self._lookup_db(origin_class_info.class_name, vtable_entry.pac_diversifier) + if mangled_symbol: + self.num_added += 1 + return mangled_symbol + return None def _handle_overrides(self, origin_class_info: 'ClassInfo', vtable_entry: 'VtableEntry') -> str | None: # Search the entire inheritance branch - for class_info in itertools.chain(origin_class_info.ancestors(True), origin_class_info.descendants(inclusive=False)): - mangled_symbol = self._lookup_db(class_info.class_name, vtable_entry.pac_diversifier) - if mangled_symbol: - self.num_overrides += 1 - - # In case we were able to resolve through superclass pac dict then - # must adjust the classname of the symbol to fit the current class - if class_info != origin_class_info: - return symbols.sub_classname(mangled_symbol, origin_class_info.class_name) - return mangled_symbol + parent_class_info = origin_class_info.superclass + if parent_class_info is None: + raise PhaseException(f'Unexpected Overrides for {origin_class_info.class_name} on {vtable_entry} - no parent class info!') + elif parent_class_info.vtable_info is None: + # This case is logged once in the parent method + self.num_invalid += 1 + return None + + parent_vtable_item = parent_class_info.vtable_info.entries[vtable_entry.index] + if parent_vtable_item.has_symbol: + mangled_symbol = parent_vtable_item.vmethod_info.mangled_symbol + self.num_overrides += 1 + return symbols.sub_classname(mangled_symbol, origin_class_info.class_name) + return None diff --git a/ida_kernelcache/rtti.py b/ida_kernelcache/rtti.py index 54bda49..0540a1b 100644 --- a/ida_kernelcache/rtti.py +++ b/ida_kernelcache/rtti.py @@ -209,9 +209,12 @@ def __hash__(self): def __eq__(self, other): return self.entry_ea == other.entry_ea + @property + def has_symbol(self) -> bool: + return bool(self.vmethod_info and self.vmethod_info.mangled_symbol) + def __repr__(self) -> str: - has_symbol = int(bool(self.vmethod_info and self.vmethod_info.mangled_symbol)) - return f'VtableEntry(index={self.index}, entry_ea={self.entry_ea:#x}, pac_diversifier={self.pac_diversifier:#x}, o={int(self.overrides)}, i={int(self.inherited)}, pv={int(self.pure_virtual)}, a={int(self.added)}, symbolicated={has_symbol})' + return f'VtableEntry(index={self.index}, entry_ea={self.entry_ea:#x}, pac_diversifier={self.pac_diversifier:#x}, o={int(self.overrides)}, i={int(self.inherited)}, pv={int(self.pure_virtual)}, a={int(self.added)}, symbolicated={int(self.has_symbol)})' class VtableInfo: diff --git a/tasks.todo b/tasks.todo index 28d2919..845e595 100644 --- a/tasks.todo +++ b/tasks.todo @@ -21,6 +21,7 @@ Symbolication: ✔ Implement getting the PAC diversifier of every vtable entry @done(25-01-27 11:47) ✔ Fix errors "does not seem to be a signed PAC pointer". UPDATE: seems like some objects are subject to multiple inheritance? In the past this was not supported by libkern++ @done(25-01-27 11:47) according to Apple's documentation. There are these sentinels and non virtual thunks, weird. On a recent kernelcache there are 93 classes that contain a sentinel. + ☐ Create PAC Signatures for pure virtual methods, including OSMetaClassBase methods that OSObject implements. Get rid of old code!: ✔ Delete old and unused scripts! @done(25-01-27 11:47)