summaryrefslogtreecommitdiff
path: root/view/kernelcache/api/python/kernelcache.py
diff options
context:
space:
mode:
authorkat <kat@vector35.com>2025-03-19 09:12:52 -0400
committerkat <kat@vector35.com>2025-03-19 15:04:23 -0400
commit7f3f394c8bb50c987a5237bc74aa503486571058 (patch)
tree9f5ada34f6226562b79f0dce70bfebab124a1bdb /view/kernelcache/api/python/kernelcache.py
parente6d4d38e2a5dc66d3c8004cc917bc08e39337482 (diff)
Add iOS/macOS MH_FILESET KernelCache View and loader.
This loader is inspired by/based on our dyld_shared_cache loader, following the same design language. It targets primarily the latest kernels, but should support any with the MH_FILESET format. It allows you to decide which images you would like to map in (the kernel itself included), resulting in targeted analysis when you may only need to load a singular image. It also supports dropping in compressed KernelCaches, directly from the ipsw. This is an early solution and we have many more changes and improvements planned. We look forward to your feedback
Diffstat (limited to 'view/kernelcache/api/python/kernelcache.py')
-rw-r--r--view/kernelcache/api/python/kernelcache.py238
1 files changed, 238 insertions, 0 deletions
diff --git a/view/kernelcache/api/python/kernelcache.py b/view/kernelcache/api/python/kernelcache.py
new file mode 100644
index 00000000..2148f2a8
--- /dev/null
+++ b/view/kernelcache/api/python/kernelcache.py
@@ -0,0 +1,238 @@
+import os
+import ctypes
+import dataclasses
+import traceback
+
+import binaryninja
+from binaryninja._binaryninjacore import BNFreeStringList, BNAllocString, BNFreeString
+
+from . import _kernelcachecore as kccore
+from .kernelcache_enums import *
+
+
+@dataclasses.dataclass
+class KCImageMemoryMapping:
+ name: str
+ vmAddress: int
+ rawViewOffset: int
+ size: int
+
+ def __str__(self):
+ return repr(self)
+
+ def __repr__(self):
+ return (f"<KCImageMemoryMapping '{self.name}' {self.vmAddress:x}+{self.size:x} "
+ f"raw<{self.rawViewOffset:x}>>")
+
+
+@dataclasses.dataclass
+class KCImage:
+ name: str
+ headerFileAddress: int
+ mappings: list[KCImageMemoryMapping]
+
+ def __str__(self):
+ return repr(self)
+
+ def __repr__(self):
+ return f"<KCImage {self.name} @ {self.headerFileAddress:x}>"
+
+
+@dataclasses.dataclass
+class KCSymbol:
+ name: str
+ image: str
+ address: int
+
+ def __str__(self):
+ return repr(self)
+
+ def __repr__(self):
+ return f"<KCSymbol {self.name} @ {self.address:x} ({self.image})>"
+
+
+@dataclasses.dataclass
+class KernelCacheMachOHeader:
+ header: str
+
+ @classmethod
+ def LoadFromString(cls, s: str):
+ return cls(header=s)
+
+ def __repr__(self):
+ return f"<KernelCacheMachOHeader {self.header!r}>"
+
+
+class KernelCache:
+ def __init__(self, view):
+ # Create a KernelCache object from a BinaryView.
+ self.handle = kccore.BNGetKernelCache(view.handle)
+
+ @staticmethod
+ def get_load_progress(view) -> int:
+ """
+ Returns the current load progress.
+ Note: In the C++ FFI this function takes the BinaryView's file session ID.
+ """
+ return KCViewLoadProgress(kccore.BNKCViewGetLoadProgress(view.file.session_id))
+
+ @staticmethod
+ def fast_get_image_count(view) -> int:
+ """
+ Quickly returns the number of images in the kernel cache.
+ """
+ return kccore.BNKCViewFastGetImageCount(view.handle)
+
+ def load_image_with_install_name(self, install_name: str) -> bool:
+ """
+ Load a kernel cache image by its install name.
+ """
+ return kccore.BNKCViewLoadImageWithInstallName(self.handle, install_name)
+
+ def load_image_containing_address(self, addr: int) -> bool:
+ """
+ Load the image that contains the given address.
+ """
+ return kccore.BNKCViewLoadImageContainingAddress(self.handle, addr)
+
+ @property
+ def image_names(self) -> list[str]:
+ """
+ Return a list of available kernel cache image install names.
+ """
+ count = ctypes.c_ulonglong()
+ value = kccore.BNKCViewGetInstallNames(self.handle, count)
+ if value is None:
+ return []
+ result = []
+ for i in range(count.value):
+ result.append(value[i].decode('utf-8'))
+ BNFreeStringList(value, count)
+ return result
+
+ @property
+ def images(self) -> list[KCImage]:
+ """
+ Return all kernel cache images.
+ """
+ count = ctypes.c_ulonglong()
+ value = kccore.BNKCViewGetAllImages(self.handle, count)
+ if value is None:
+ return []
+ result = []
+ for i in range(count.value):
+ mappings = []
+ for j in range(value[i].mappingCount):
+ mapping = KCImageMemoryMapping(
+ name=value[i].mappings[j].name,
+ vmAddress=value[i].mappings[j].vmAddress,
+ rawViewOffset=value[i].mappings[j].rawViewOffset,
+ size=value[i].mappings[j].size,
+ )
+ mappings.append(mapping)
+ result.append(KCImage(
+ name=value[i].name,
+ headerFileAddress=value[i].headerFileAddress,
+ mappings=mappings
+ ))
+ kccore.BNKCViewFreeAllImages(value, count)
+ return result
+
+ @property
+ def loaded_images(self) -> list[KCImage]:
+ """
+ Return the kernel cache images that are currently loaded.
+ """
+ count = ctypes.c_ulonglong()
+ images = kccore.BNKCViewGetLoadedImages(self.handle, count)
+ if images is None:
+ return []
+ result = []
+ for i in range(count.value):
+ mappings = []
+ for j in range(images[i].mappingCount):
+ mapping = KCImageMemoryMapping(
+ name=images[i].mappings[j].name,
+ vmAddress=images[i].mappings[j].vmAddress,
+ rawViewOffset=images[i].mappings[j].rawViewOffset,
+ size=images[i].mappings[j].size,
+ )
+ mappings.append(mapping)
+ result.append(KCImage(
+ name=images[i].name,
+ headerFileAddress=images[i].headerFileAddress,
+ mappings=mappings
+ ))
+ kccore.BNKCViewFreeAllImages(images, count)
+ return result
+
+ def load_all_symbols_and_wait(self) -> list[KCSymbol]:
+ """
+ Load all symbols from the kernel cache and wait for completion.
+ """
+ count = ctypes.c_ulonglong()
+ value = kccore.BNKCViewLoadAllSymbolsAndWait(self.handle, count)
+ if value is None:
+ return []
+ result = []
+ for i in range(count.value):
+ sym = KCSymbol(
+ name=value[i].name,
+ image=value[i].image,
+ address=value[i].address
+ )
+ result.append(sym)
+ kccore.BNKCViewFreeSymbols(value, count)
+ return result
+
+ def get_name_for_address(self, address: int) -> str:
+ """
+ Return the symbol name for a given address.
+ """
+ name = kccore.BNKCViewGetNameForAddress(self.handle, address)
+ if name is None:
+ return ""
+ return name
+
+ def get_image_name_for_address(self, address: int) -> str:
+ """
+ Return the image name for a given address.
+ """
+ name = kccore.BNKCViewGetImageNameForAddress(self.handle, address)
+ if name is None:
+ return ""
+ return name
+
+ def get_macho_header_for_image(self, name: str):
+ """
+ Return a KernelCacheMachOHeader for the image with the given install name.
+ """
+ s = BNAllocString(name)
+ outputStr = kccore.BNKCViewGetImageHeaderForName(self.handle, s)
+ if outputStr is None:
+ return None
+ output = outputStr
+ BNFreeString(outputStr)
+ if output == "":
+ return None
+ return KernelCacheMachOHeader.LoadFromString(output)
+
+ def get_macho_header_for_address(self, address: int):
+ """
+ Return a KernelCacheMachOHeader for the image containing the given address.
+ """
+ outputStr = kccore.BNKCViewGetImageHeaderForAddress(self.handle, address)
+ if outputStr is None:
+ return None
+ output = outputStr
+ BNFreeString(outputStr)
+ if output == "":
+ return None
+ return KernelCacheMachOHeader.LoadFromString(output)
+
+ @property
+ def state(self):
+ """
+ Return the current state of the kernel cache.
+ """
+ return KCViewState(kccore.BNKCViewGetState(self.handle))