diff options
| author | Mason Reed <mason@vector35.com> | 2025-03-10 11:05:40 -0400 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2025-04-02 05:36:54 -0400 |
| commit | 25cc02431b61097b2adfc2fbc493b648b0300c3b (patch) | |
| tree | a79d9c4f4f67234d3bf9bda413e8608f479a4cc8 /view/sharedcache | |
| parent | fa85bf28502286c4821427c5d0ed91a7ed46f8f6 (diff) | |
[SharedCache] Refactor Shared Cache
In absence of a better name, this commit refactors the shared cache code.
Diffstat (limited to 'view/sharedcache')
54 files changed, 7308 insertions, 9182 deletions
diff --git a/view/sharedcache/CMakeLists.txt b/view/sharedcache/CMakeLists.txt index cd40fa15..e1b6973a 100644 --- a/view/sharedcache/CMakeLists.txt +++ b/view/sharedcache/CMakeLists.txt @@ -33,6 +33,7 @@ endif() set(HARD_FAIL_MODE OFF CACHE BOOL "Enable hard fail mode") set(SLIDEINFO_DEBUG_TAGS OFF CACHE BOOL "Enable debug tags in slideinfo") set(VIEW_NAME "DSCView" CACHE STRING "Name of the view") +# TODO: Remove this, if we want to keep a metadata version around we must have this in the source. set(METADATA_VERSION 5 CACHE STRING "Version of the metadata") add_subdirectory(core) diff --git a/view/sharedcache/HeadlessPlugin.cpp b/view/sharedcache/HeadlessPlugin.cpp index c49ace5a..47c6b9de 100644 --- a/view/sharedcache/HeadlessPlugin.cpp +++ b/view/sharedcache/HeadlessPlugin.cpp @@ -1,9 +1,9 @@ #include <binaryninjaapi.h> -#include "DSCView.h" -#include "SharedCache.h" +#include "SharedCacheView.h" #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif extern void RegisterSharedCacheWorkflow(); #ifdef __cplusplus @@ -16,7 +16,7 @@ extern "C" BINARYNINJAPLUGIN bool CorePluginInit() { - InitDSCViewType(); + SharedCacheViewType::Register(); RegisterSharedCacheWorkflow(); return true; } diff --git a/view/sharedcache/api/python/sharedcache.py b/view/sharedcache/api/python/sharedcache.py index dab5583e..83be388b 100644 --- a/view/sharedcache/api/python/sharedcache.py +++ b/view/sharedcache/api/python/sharedcache.py @@ -1,389 +1,293 @@ -import os import ctypes import dataclasses -import traceback +from typing import Optional import binaryninja +from binaryninja import BinaryView from binaryninja._binaryninjacore import BNFreeStringList, BNAllocString, BNFreeString from . import _sharedcachecore as sccore from .sharedcache_enums import * - -@dataclasses.dataclass -class DSCMemoryMapping: - name: str - vmAddress: int - size: int - - def __str__(self): - return repr(self) - - def __repr__(self): - return f"<DSCMemoryMapping '{self.name}': {self.vmAddress:x}+{self.size:x}>" - - @dataclasses.dataclass -class LoadedRegion: - name: str - headerAddress: int - mappings: list[DSCMemoryMapping] - - def __str__(self): - return repr(self) - - def __repr__(self): - return f"<LoadedRegion {self.name} @ {self.headerAddress:x}>" - - -@dataclasses.dataclass -class DSCBackingCacheMapping: - vmAddress: int - size: int - fileOffset: int - - def __str__(self): - return repr(self) +class CacheRegion: + region_type: SharedCacheRegionType + name: str + start: int + size: int + image_start: int + # TODO: Might want to make this use the BN segment flag enum? + flags: sccore.SegmentFlagEnum - def __repr__(self): - return f"<DSCBackingCacheMapping {self.vmAddress:x}+{self.size:x} @ {self.fileOffset:x}" + def __str__(self): + return repr(self) + def __repr__(self): + return f"<CacheRegion '{self.name}': 0x{self.start:x} + {self.size:x}>" @dataclasses.dataclass -class DSCBackingCache: - path: str - cacheType: BackingCacheType - mappings: list[DSCBackingCacheMapping] - - def __str__(self): - return repr(self) - - def __repr__(self): - cache_type_str = 'Unknown' - if self.cacheType == BackingCacheType.BackingCacheTypePrimary: - cache_type_str = 'Primary' - elif self.cacheType == BackingCacheType.BackingCacheTypeSecondary: - cache_type_str = 'Secondary' - elif self.cacheType == BackingCacheType.BackingCacheTypeSymbols: - cache_type_str = 'Symbols' - return f"<DSCBackingCache {self.path} {cache_type_str} | {len(self.mappings)} mappings>" - - -@dataclasses.dataclass -class DSCImageMemoryMapping: - filePath: str - name: str - vmAddress: int - size: int - loaded: bool - rawViewOffset: int - - def __str__(self): - return repr(self) - - def __repr__(self): - return f"<DSCImageMemoryMapping '{self.name}' {os.path.basename(self.filePath)} raw<{self.rawViewOffset:x}>: {self.vmAddress:x}+{self.size:x}>" - - -@dataclasses.dataclass -class DSCImage: - name: str - headerAddress: int - mappings: list[DSCImageMemoryMapping] - - def __str__(self): - return repr(self) +class CacheImage: + name: str + header_address: int + region_starts: [int] - def __repr__(self): - return f"<DSCImage {self.name} @ {self.headerAddress:x}>" + def __str__(self): + return repr(self) + def __repr__(self): + return f"<CacheImage '{self.name}': 0x{self.header_address:x}>" @dataclasses.dataclass -class DSCSymbol: - name: str - image: str - address: int - - def __str__(self): - return repr(self) - - def __repr__(self): - return f"<DSCSymbol {self.name} @ {self.address:x} ({self.image}>" - - -class SharedCache: - """ - SharedCache is the primary class for interacting with the shared cache processor and DSCView metadata. - - You can create a SharedCache object from a BinaryView object by calling `SharedCache(bv)`, where `bv` is the BinaryView. - - By default `bv` in the console will return the instance of the BinaryView that is currently open, \ - so in the UI, you can use `dsc = SharedCache(bv)` to create a SharedCache object in the scripting console. - - Methods and attributes in this class have documentation which can be viewed by typing `SharedCache.method_or_attribute_name?` in the console. - """ - def __init__(self, view): - self.handle = sccore.BNGetSharedCache(view.handle) - - def load_image_with_install_name(self, install_name, skip_loading_objective_c = False) -> bool: - """ - Locate an image with the provided install name and load it into the shared cache view - - :param install_name: Install name of the image - :param skip_loading_objective_c: Whether to skip process Objective-C information for this image. Default false. - :return: - """ - return sccore.BNDSCViewLoadImageWithInstallName(self.handle, install_name, skip_loading_objective_c) - - def load_section_at_address(self, addr) -> bool: - """ - Load a singular section at the provided address into the shared cache view. - - This will partial-load the image, only mapping the requested segment containing this section. - - Image info will still be processed, but will only be applied to mapped regions. - - :param addr: Address within the section - :return: - """ - return sccore.BNDSCViewLoadSectionAtAddress(self.handle, addr) - - def load_image_containing_address(self, addr, skip_loading_objective_c = False) -> bool: - """ - Load the image containing the provided address into the shared cache view. - - :param addr: Address within the image to load - :param skip_loading_objective_c: Whether to skip processing Objective-C information for this image. Default false. - :return: - """ - return sccore.BNDSCViewLoadImageContainingAddress(self.handle, addr, skip_loading_objective_c) - - def process_objc_sections_for_image_with_install_name(self, install_name) -> bool: - """ - Process Objective-C information for the image with the provided install name. - - :param install_name: Install name of the image - :return: - """ - return sccore.BNDSCViewProcessObjCSectionsForImageWithInstallName(self.handle, install_name, False) - - def process_all_objc_sections(self) -> bool: - """ - Process Objective-C information for all images in the shared cache view. - - :return: - """ - return sccore.BNDSCViewProcessAllObjCSections(self.handle) - - @property - def caches(self) -> list[DSCBackingCache]: - """ - Get all backing caches in the shared cache. - :return: - """ - count = ctypes.c_ulonglong() - value = sccore.BNDSCViewGetBackingCaches(self.handle, count) - if value is None: - return [] - - result = [] - for i in range(count.value): - mappings = [] - for j in range(value[i].mappingCount): - mapping = DSCBackingCacheMapping( - value[i].mappings[j].vmAddress, - value[i].mappings[j].size, - value[i].mappings[j].fileOffset - ) - mappings.append(mapping) - result.append(DSCBackingCache( - value[i].path, - value[i].cacheType, - mappings - )) - - sccore.BNDSCViewFreeBackingCaches(value, count) - return result - - @property - def images(self) -> list[DSCImage]: - """ - Get all images in the shared cache - :return: - """ - count = ctypes.c_ulonglong() - value = sccore.BNDSCViewGetAllImages(self.handle, count) - if value is None: - return [] +class CacheSymbol: + symbol_type: sccore.SymbolTypeEnum + address: int + name: str - result = [] - for i in range(count.value): - mappings = [] - for j in range(value[i].mappingCount): - mapping = DSCImageMemoryMapping( - value[i].mappings[j].filePath, - value[i].mappings[j].name, - value[i].mappings[j].vmAddress, - value[i].mappings[j].size, - value[i].mappings[j].loaded, - value[i].mappings[j].rawViewOffset - ) - mappings.append(mapping) - result.append(DSCImage( - value[i].name, - value[i].headerAddress, - mappings - )) + def __str__(self): + return repr(self) - sccore.BNDSCViewFreeAllImages(value, count) - return result + def __repr__(self): + return f"<CacheSymbol '{self.name}': 0x{self.address:x}>" - @property - def loaded_regions(self) -> list[LoadedRegion]: - """ - Get all loaded regions in the shared cache +def region_from_api(region: sccore.BNSharedCacheRegion) -> CacheRegion: + return CacheRegion( + region_type=SharedCacheRegionType(region.regionType), + name=region.name, + start=region.vmAddress, + size=region.size, + image_start=region.imageStart, + flags=region.flags + ) - The internal logic for loading images treats a region as 'loaded' whenever - that region has been mapped into memory, and, if it's located within an image, header information has been applied to that region. +def region_to_api(region: CacheRegion) -> sccore.BNSharedCacheRegion: + return sccore.BNSharedCacheRegion( + regionType=region.region_type, + _name=BNAllocString(region.name), + vmAddress=region.start, + size=region.size, + imageStart=region.image_start, + flags=region.flags + ) - Individual segments within an image can be loaded independently of the image itself. +def image_from_api(image: sccore.BNSharedCacheImage) -> CacheImage: + region_starts = [] + for i in range(image.regionStartCount): + region_starts.append(image.regionStarts[i]) + return CacheImage( + name=image.name, + header_address=image.headerAddress, + region_starts=region_starts + ) - Only once all regions of an image are loaded will the header processor refuse to run on that region. - :return: - """ - count = ctypes.c_ulonglong() - value = sccore.BNDSCViewGetLoadedRegions(self.handle, count) - if value is None: - return [] +def image_to_api(image: CacheImage) -> sccore.BNSharedCacheImage: + region_start_array = (ctypes.c_ulonglong * len(image.region_starts))() + for i, region_start in enumerate(image.region_starts): + region_start_array[i] = region_start + core_region_starts = sccore.BNSharedCacheAllocRegionList(region_start_array, len(region_start_array)) + return sccore.BNSharedCacheImage( + _name=BNAllocString(image.name), + headerAddress=image.header_address, + regionStartCount=len(region_start_array), + regionStarts=core_region_starts + ) - result = [] - for i in range(count.value): - mapping = DSCMemoryMapping( - value[i].name, - value[i].vmAddress, - value[i].size, - ) - result.append(mapping) - sccore.BNDSCViewFreeLoadedRegions(value, count) - return result +def symbol_from_api(symbol: sccore.BNSharedCacheSymbol) -> CacheSymbol: + return CacheSymbol( + symbol_type=symbol.symbolType, + address=symbol.address, + name=symbol.name + ) - def load_all_symbols_and_wait(self) -> list[DSCSymbol]: - """ - Load all symbols in the shared cache. This will block on the current thread waiting for processing to finish. +def symbol_to_api(symbol: CacheSymbol) -> sccore.BNSharedCacheSymbol: + return sccore.BNSharedCacheSymbol( + symbolType=symbol.symbol_type, + address=symbol.address, + _name=BNAllocString(symbol.name) + ) - While all functions in this API are synchronous, this function can be particularly slow due to the large number - of symbols in the shared cache. "and_wait" is appended to the function name to indicate that this function - will block until processing is complete, and for performant applications, you should consider calling this - function in a separate thread and waiting on its return. An example of this is provided in the shared cache - triage view. +class SharedCacheController: + def __init__(self, view: BinaryView): + """ + Retrieve the shared cache controller for a given view. + Call `is_valid` to check if the controller is valid. + """ + self.handle = sccore.BNGetSharedCacheController(view.handle) - This may take several seconds if this is the first time this function is called. Subsequent calls will be faster. + def __del__(self): + if self.handle is not None: + sccore.BNFreeSharedCacheControllerReference(self.handle) - In UI-based API usage, it is likely that the triage view will have already performed this operation, and calls - to this function will be much faster. + def __str__(self): + return repr(self) - :return: A list of all symbols in the shared cache - """ - count = ctypes.c_ulonglong() - value = sccore.BNDSCViewLoadAllSymbolsAndWait(self.handle, count) - if value is None: - return [] - result = [] - for i in range(count.value): - sym = DSCSymbol( - value[i].name, - value[i].image, - value[i].address - ) - result.append(sym) + def __repr__(self): + return f"<SharedCacheController: {len(self.images)} images, {len(self.regions)} regions>" - sccore.BNDSCViewFreeSymbols(value, count) - return result + def is_valid(self) -> bool: + return self.handle is not None - @property - def image_names(self) -> list[str]: - """ - Get all image names in the shared cache - :return: - """ - count = ctypes.c_ulonglong() - value = sccore.BNDSCViewGetInstallNames(self.handle, count) - if value is None: - return [] + def apply_region(self, view: BinaryView, region: CacheRegion) -> bool: + api_region: sccore.BNSharedCacheRegion = region_to_api(region) + result = sccore.BNSharedCacheControllerApplyRegion(self.handle, view.handle, api_region) + sccore.BNSharedCacheFreeRegion(api_region) + return result - result = [] - for i in range(count.value): - result.append(value[i].decode('utf-8')) + def apply_image(self, view: BinaryView, image: CacheImage) -> bool: + api_image: sccore.BNSharedCacheImage = image_to_api(image) + result = sccore.BNSharedCacheControllerApplyImage(self.handle, view.handle, api_image) + sccore.BNSharedCacheFreeImage(api_image) + return result - BNFreeStringList(value, count) - return result + def is_region_loaded(self, region: CacheRegion) -> bool: + api_region: sccore.BNSharedCacheRegion = region_to_api(region) + result = sccore.BNSharedCacheControllerIsRegionLoaded(self.handle, api_region) + sccore.BNSharedCacheFreeRegion(api_region) + return result - @property - def state(self) -> DSCViewState: - """ - Get the current image state of the shared cache view. Useful for checking if images have been loaded yet or not. - :return: - """ - return DSCViewState(sccore.BNDSCViewGetState(self.handle)) + def is_image_loaded(self, image: CacheImage) -> bool: + api_image: sccore.BNSharedCacheImage = image_to_api(image) + result = sccore.BNSharedCacheControllerIsImageLoaded(self.handle, api_image) + sccore.BNSharedCacheFreeImage(api_image) + return result - def get_name_for_address(self, address) -> str: - """ - Get the "name" for the provided address. Specifically, the name of the memory region this address lies in. + def get_region_at(self, address: int) -> Optional[CacheRegion]: + api_region = sccore.BNSharedCacheRegion() + if not sccore.BNSharedCacheControllerGetRegionAt(self.handle, address, api_region): + return None + region = region_from_api(api_region) + sccore.BNSharedCacheFreeRegion(api_region) + return region - If this lies within an image segment, this will be in the format image_name + "::" + segment_name. + def get_region_containing(self, address: int) -> Optional[CacheRegion]: + api_region = sccore.BNSharedCacheRegion() + if not sccore.BNSharedCacheControllerGetRegionContaining(self.handle, address, api_region): + return None + region = region_from_api(api_region) + sccore.BNSharedCacheFreeRegion(api_region) + return region - It may also be the name of a branch pool or other non-image region. + def get_image_at(self, address: int) -> Optional[CacheImage]: + api_image = sccore.BNSharedCacheImage() + if not sccore.BNSharedCacheControllerGetImageAt(self.handle, address, api_image): + return None + image = image_from_api(api_image) + sccore.BNSharedCacheFreeImage(api_image) + return image - This is the API call utilized on the first dynamic entry in the right-click context menu. + def get_image_containing(self, address: int) -> Optional[CacheImage]: + api_image = sccore.BNSharedCacheImage() + if not sccore.BNSharedCacheControllerGetImageContaining(self.handle, address, api_image): + return None + image = image_from_api(api_image) + sccore.BNSharedCacheFreeImage(api_image) + return image - :param address: address to check - :return: - """ - name = sccore.BNDSCViewGetNameForAddress(self.handle, address) - if name is None: - return "" - result = name - return result + def get_image_with_name(self, name: str) -> Optional[CacheImage]: + api_image = sccore.BNSharedCacheImage() + if not sccore.BNSharedCacheControllerGetImageWithName(self.handle, name, api_image): + return None + image = image_from_api(api_image) + sccore.BNSharedCacheFreeImage(api_image) + return image - def get_image_name_for_address(self, address) -> str: - """ - Return the install name for the image containing the provided address. + def get_image_dependencies(self, image: CacheImage) -> [str]: + """ + Returns a list of image names that this image depends on. + """ + count = ctypes.c_ulonglong() + api_image: sccore.BNSharedCacheImage = image_to_api(image) + value = sccore.BNSharedCacheControllerGetImageDependencies(self.handle, api_image, count) + sccore.BNSharedCacheFreeImage(api_image) + if value is None: + return [] + result = [] + for i in range(count.value): + result.append(value[i].decode("utf-8")) + BNFreeStringList(value, count) + return result - If the address is not within an image, this will return an empty string. + @property + def regions(self) -> [CacheRegion]: + count = ctypes.c_ulonglong() + value = sccore.BNSharedCacheControllerGetRegions(self.handle, count) + if value is None: + return [] + result = [] + for i in range(count.value): + result.append(region_from_api(value[i])) + sccore.BNSharedCacheFreeRegionList(value, count) + return result - This is the API call used in the second dynamic entry in the right-click context menu. + @property + def loaded_regions(self) -> [CacheRegion]: + """ + Get a list of regions that are currently loaded in the view. + """ + count = ctypes.c_ulonglong() + value = sccore.BNSharedCacheControllerGetLoadedRegions(self.handle, count) + if value is None: + return [] + result = [] + for i in range(count.value): + result.append(region_from_api(value[i])) + sccore.BNSharedCacheFreeRegionList(value, count) + return result - :param address: address to check - :return: - """ - name = sccore.BNDSCViewGetImageNameForAddress(self.handle, address) - if name is None: - return "" - result = name - return result + @property + def images(self) -> [CacheImage]: + count = ctypes.c_ulonglong() + value = sccore.BNSharedCacheControllerGetImages(self.handle, count) + if value is None: + return [] + result = [] + for i in range(count.value): + result.append(image_from_api(value[i])) + sccore.BNSharedCacheFreeImageList(value, count) + return result - def find_symbol_at_addr_and_apply_to_addr(self, symbol_address, target_address, trigger_reanalysis) -> None: - """ - This is primarily a function utilized for automated backwards symbol propagation for stubs in the workflow, however - it is passed through here as well in the event you need to use it to do something similar, or want to create - your own version of the workflow. + @property + def loaded_images(self) -> [CacheImage]: + """ + Get a list of images that are currently loaded in the view. + """ + count = ctypes.c_ulonglong() + value = sccore.BNSharedCacheControllerGetLoadedImages(self.handle, count) + if value is None: + return [] + result = [] + for i in range(count.value): + result.append(image_from_api(value[i])) + sccore.BNSharedCacheFreeImageList(value, count) + return result - This is currently a blocking function. + @property + def symbols(self) -> [CacheSymbol]: + count = ctypes.c_ulonglong() + value = sccore.BNSharedCacheControllerGetSymbols(self.handle, count) + if value is None: + return [] + result = [] + for i in range(count.value): + result.append(symbol_from_api(value[i])) + sccore.BNSharedCacheFreeSymbolList(value, count) + return result - This will check the cache for a symbol located at symbol_address, and apply it to target_address, appending a - `j_` to the front of the symbol name copy at `target_address`. - It will additionally backwards-propagate type information that was specifically applied via TypeLibrary to the stub. +def _get_shared_cache(instance: binaryninja.PythonScriptingInstance): + if instance.interpreter.active_view is None: + return None + controller = SharedCacheController(instance.interpreter.active_view) + if not controller.is_valid(): + return None + return controller - This includes calling conventions and can be seen in stubs pointing to objc_release_x[register] functions. - This check will not run if: - - The symbol address and target address are the same - - The target address has already been given a name by this function +binaryninja.PythonScriptingProvider.register_magic_variable( + "dsc", + _get_shared_cache +) - :param symbol_address: Symbol address to check - :param target_address: Target address to apply symbol name and type to - :param trigger_reanalysis: Whether to reanalyze the function at target_address if the function already existed. - :return: None - """ - sccore.BNDSCFindSymbolAtAddressAndApplyToAddress(self.handle, symbol_address, target_address, trigger_reanalysis) +binaryninja.PythonScriptingProvider.register_magic_variable( + "shared_cache", + _get_shared_cache +) diff --git a/view/sharedcache/api/python/sharedcache_enums.py b/view/sharedcache/api/python/sharedcache_enums.py index ea86b5c6..9a98eba4 100644 --- a/view/sharedcache/api/python/sharedcache_enums.py +++ b/view/sharedcache/api/python/sharedcache_enums.py @@ -1,20 +1,38 @@ import enum -class BackingCacheType(enum.IntEnum): - BackingCacheTypePrimary = 0 - BackingCacheTypeSecondary = 1 - BackingCacheTypeSymbols = 2 +class SegmentFlag(enum.IntEnum): + SegmentExecutable = 1 + SegmentWritable = 2 + SegmentReadable = 4 + SegmentContainsData = 8 + SegmentContainsCode = 16 + SegmentDenyWrite = 32 + SegmentDenyExecute = 64 -class DSCViewLoadProgress(enum.IntEnum): - LoadProgressNotStarted = 0 - LoadProgressLoadingCaches = 1 - LoadProgressLoadingImages = 2 - LoadProgressFinished = 3 +class SharedCacheEntryType(enum.IntEnum): + SharedCacheEntryTypePrimary = 0 + SharedCacheEntryTypeSecondary = 1 + SharedCacheEntryTypeSymbols = 2 + SharedCacheEntryTypeDyldData = 3 + SharedCacheEntryTypeStub = 4 -class DSCViewState(enum.IntEnum): - Unloaded = 0 - Loaded = 1 - LoadedWithImages = 2 +class SharedCacheRegionType(enum.IntEnum): + SharedCacheRegionTypeImage = 0 + SharedCacheRegionTypeStubIsland = 1 + SharedCacheRegionTypeDyldData = 2 + SharedCacheRegionTypeNonImage = 3 + + +class SymbolType(enum.IntEnum): + FunctionSymbol = 0 + ImportAddressSymbol = 1 + ImportedFunctionSymbol = 2 + DataSymbol = 3 + ImportedDataSymbol = 4 + ExternalSymbol = 5 + LibraryFunctionSymbol = 6 + SymbolicFunctionSymbol = 7 + LocalLabelSymbol = 8 diff --git a/view/sharedcache/api/sharedcache.cpp b/view/sharedcache/api/sharedcache.cpp index d9838057..6d552f0d 100644 --- a/view/sharedcache/api/sharedcache.cpp +++ b/view/sharedcache/api/sharedcache.cpp @@ -4,231 +4,324 @@ #include "sharedcacheapi.h" -namespace SharedCacheAPI { +using namespace BinaryNinja; +using namespace SharedCacheAPI; - SharedCache::SharedCache(Ref<BinaryView> view) { - m_object = BNGetSharedCache(view->GetObject()); - } +BNSharedCacheImage ImageToApi(CacheImage image) +{ + BNSharedCacheImage apiImage; + apiImage.name = BNAllocString(image.name.c_str()); + apiImage.headerAddress = image.headerAddress; + apiImage.regionStartCount = image.regionStarts.size(); + // TODO: If we alloc then core cannot delete + uint64_t *regionStarts = new uint64_t[image.regionStarts.size()]; + for (size_t i = 0; i < image.regionStarts.size(); i++) + regionStarts[i] = image.regionStarts[i]; + apiImage.regionStarts = regionStarts; + return apiImage; +} - BNDSCViewLoadProgress SharedCache::GetLoadProgress(Ref<BinaryView> view) - { - return BNDSCViewGetLoadProgress(view->GetFile()->GetSessionId()); - } +CacheImage ImageFromApi(BNSharedCacheImage image) +{ + CacheImage apiImage; + apiImage.name = image.name; + apiImage.headerAddress = image.headerAddress; + apiImage.regionStarts.reserve(image.regionStartCount); + for (size_t i = 0; i < image.regionStartCount; i++) + apiImage.regionStarts.push_back(image.regionStarts[i]); + return apiImage; +} - uint64_t SharedCache::FastGetBackingCacheCount(Ref<BinaryView> view) - { - return BNDSCViewFastGetBackingCacheCount(view->GetObject()); - } +BNSharedCacheRegion RegionToApi(const CacheRegion ®ion) +{ + BNSharedCacheRegion apiRegion; + apiRegion.vmAddress = region.start; + apiRegion.name = BNAllocString(region.name.c_str()); + apiRegion.size = region.size; + apiRegion.flags = region.flags; + apiRegion.regionType = region.type; + // If not associated with image this will be zeroed. + apiRegion.imageStart = region.imageStart.value_or(0); + return apiRegion; +} - bool SharedCache::LoadImageWithInstallName(std::string installName, bool skipObjC) - { - char* str = BNAllocString(installName.c_str()); - return BNDSCViewLoadImageWithInstallName(m_object, str, skipObjC); - } +CacheRegion RegionFromApi(BNSharedCacheRegion apiRegion) +{ + CacheRegion region; + region.start = apiRegion.vmAddress; + region.name = apiRegion.name; + region.size = apiRegion.size; + region.flags = apiRegion.flags; + region.type = apiRegion.regionType; + return region; +} - bool SharedCache::LoadSectionAtAddress(uint64_t addr) - { - return BNDSCViewLoadSectionAtAddress(m_object, addr); - } +BNSharedCacheMappingInfo MappingToApi(const CacheMappingInfo &mapping) +{ + BNSharedCacheMappingInfo apiMapping; + apiMapping.vmAddress = mapping.vmAddress; + apiMapping.size = mapping.size; + apiMapping.fileOffset = mapping.fileOffset; + return apiMapping; +} - bool SharedCache::LoadImageContainingAddress(uint64_t addr, bool skipObjC) - { - return BNDSCViewLoadImageContainingAddress(m_object, addr, skipObjC); - } +CacheMappingInfo MappingFromApi(BNSharedCacheMappingInfo apiMapping) +{ + CacheMappingInfo mapping; + mapping.vmAddress = apiMapping.vmAddress; + mapping.size = apiMapping.size; + mapping.fileOffset = apiMapping.fileOffset; + return mapping; +} - std::vector<std::string> SharedCache::GetAvailableImages() - { - size_t count; - char** value = BNDSCViewGetInstallNames(m_object, &count); - if (value == nullptr) - { - return {}; - } +BNSharedCacheEntry EntryToApi(const CacheEntry &entry) +{ + BNSharedCacheEntry apiEntry; + apiEntry.path = BNAllocString(entry.path.c_str()); + apiEntry.entryType = entry.entryType; + const auto &mappings = entry.mappings; + apiEntry.mappingCount = mappings.size(); + // TODO: If we alloc then the core cannot delete. + apiEntry.mappings = new BNSharedCacheMappingInfo[mappings.size()]; + for (size_t i = 0; i < mappings.size(); i++) + apiEntry.mappings[i] = MappingToApi(mappings[i]); + return apiEntry; +} - std::vector<std::string> result; - for (size_t i = 0; i < count; i++) - { - result.push_back(value[i]); - } +CacheEntry EntryFromApi(BNSharedCacheEntry apiEntry) +{ + CacheEntry entry; + entry.path = apiEntry.path; + entry.entryType = apiEntry.entryType; + entry.mappings.reserve(apiEntry.mappingCount); + for (size_t i = 0; i < apiEntry.mappingCount; i++) + entry.mappings.push_back(MappingFromApi(apiEntry.mappings[i])); + return entry; +} - BNFreeStringList(value, count); - return result; - } +CacheSymbol SymbolFromApi(BNSharedCacheSymbol apiSymbol) +{ + CacheSymbol symbol; + symbol.name = apiSymbol.name; + symbol.address = apiSymbol.address; + symbol.type = apiSymbol.symbolType; + return symbol; +} - void SharedCache::ProcessObjCSectionsForImageWithInstallName(std::string installName) +std::string SharedCacheAPI::GetRegionTypeAsString(const BNSharedCacheRegionType &type) +{ + switch (type) { - char* str = BNAllocString(installName.c_str()); - BNDSCViewProcessObjCSectionsForImageWithInstallName(m_object, str, true); + case SharedCacheRegionTypeImage: + return "Image"; + case SharedCacheRegionTypeStubIsland: + return "StubIsland"; + case SharedCacheRegionTypeDyldData: + return "DyldData"; + case SharedCacheRegionTypeNonImage: + return "NonImage"; + default: + return "Unknown"; } +} - void SharedCache::ProcessAllObjCSections() - { - BNDSCViewProcessAllObjCSections(m_object); - } +SharedCacheController::SharedCacheController(BNSharedCacheController *controller) +{ + m_object = controller; +} - std::vector<DSCMemoryRegion> SharedCache::GetLoadedMemoryRegions() - { - size_t count; - BNDSCMappedMemoryRegion* value = BNDSCViewGetLoadedRegions(m_object, &count); - if (value == nullptr) - { - return {}; - } +DSCRef<SharedCacheController> SharedCacheController::GetController(BinaryView &view) +{ + BNSharedCacheController *controller = BNGetSharedCacheController(view.GetObject()); + if (controller == nullptr) + return nullptr; + return new SharedCacheController(controller); +} - std::vector<DSCMemoryRegion> result; - for (size_t i = 0; i < count; i++) - { - DSCMemoryRegion region; - region.vmAddress = value[i].vmAddress; - region.size = value[i].size; - region.prettyName = value[i].name; - result.push_back(region); - } +bool SharedCacheController::ApplyRegion(BinaryView &view, const CacheRegion ®ion) +{ + auto apiRegion = RegionToApi(region); + bool result = BNSharedCacheControllerApplyRegion(m_object, view.GetObject(), &apiRegion); + BNSharedCacheFreeRegion(apiRegion); + return result; +} - BNDSCViewFreeLoadedRegions(value, count); - return result; - } - std::vector<BackingCache> SharedCache::GetBackingCaches() - { - size_t count; - BNDSCBackingCache* value = BNDSCViewGetBackingCaches(m_object, &count); - if (value == nullptr) - { - return {}; - } +bool SharedCacheController::ApplyImage(BinaryView &view, const CacheImage &image) +{ + auto apiImage = ImageToApi(image); + bool result = BNSharedCacheControllerApplyImage(m_object, view.GetObject(), &apiImage); + BNSharedCacheFreeImage(apiImage); + return result; +} - std::vector<BackingCache> result; - for (size_t i = 0; i < count; i++) - { - BackingCache cache; - cache.path = value[i].path; - cache.cacheType = value[i].cacheType; - for (size_t j = 0; j < value[i].mappingCount; j++) - { - BackingCacheMapping mapping; - mapping.vmAddress = value[i].mappings[j].vmAddress; - mapping.size = value[i].mappings[j].size; - mapping.fileOffset = value[i].mappings[j].fileOffset; - cache.mappings.push_back(mapping); - } - result.push_back(cache); - } +bool SharedCacheController::IsRegionLoaded(const CacheRegion ®ion) const +{ + auto apiRegion = RegionToApi(region); + bool result = BNSharedCacheControllerIsRegionLoaded(m_object, &apiRegion); + BNSharedCacheFreeRegion(apiRegion); + return result; +} - BNDSCViewFreeBackingCaches(value, count); - return result; - } +bool SharedCacheController::IsImageLoaded(const CacheImage &image) const +{ + auto apiImage = ImageToApi(image); + bool result = BNSharedCacheControllerIsImageLoaded(m_object, &apiImage); + BNSharedCacheFreeImage(apiImage); + return result; +} - std::vector<DSCImage> SharedCache::GetImages() - { - size_t count; - BNDSCImage* value = BNDSCViewGetAllImages(m_object, &count); - if (value == nullptr) - { - return {}; - } +std::optional<CacheRegion> SharedCacheController::GetRegionAt(uint64_t address) const +{ + BNSharedCacheRegion apiRegion; + if (!BNSharedCacheControllerGetRegionAt(m_object, address, &apiRegion)) + return std::nullopt; + CacheRegion region = RegionFromApi(apiRegion); + BNSharedCacheFreeRegion(apiRegion); + return region; +} - std::vector<DSCImage> result; - for (size_t i = 0; i < count; i++) - { - DSCImage img; - img.name = value[i].name; - img.headerAddress = value[i].headerAddress; - for (size_t j = 0; j < value[i].mappingCount; j++) - { - DSCImageMemoryMapping mapping; - mapping.filePath = value[i].mappings[j].filePath; - mapping.name = value[i].mappings[j].name; - mapping.vmAddress = value[i].mappings[j].vmAddress; - mapping.rawViewOffset = value[i].mappings[j].rawViewOffset; - mapping.size = value[i].mappings[j].size; - mapping.loaded = value[i].mappings[j].loaded; - img.mappings.push_back(mapping); - } - result.push_back(img); - } +std::optional<CacheRegion> SharedCacheController::GetRegionContaining(uint64_t address) const +{ + BNSharedCacheRegion apiRegion; + if (!BNSharedCacheControllerGetRegionContaining(m_object, address, &apiRegion)) + return std::nullopt; + CacheRegion region = RegionFromApi(apiRegion); + BNSharedCacheFreeRegion(apiRegion); + return region; +} - BNDSCViewFreeAllImages(value, count); - return result; - } +std::optional<CacheImage> SharedCacheController::GetImageAt(uint64_t address) const +{ + BNSharedCacheImage apiImage; + if (!BNSharedCacheControllerGetImageAt(m_object, address, &apiImage)) + return std::nullopt; + CacheImage image = ImageFromApi(apiImage); + BNSharedCacheFreeImage(apiImage); + return image; +} - std::vector<DSCSymbol> SharedCache::LoadAllSymbolsAndWait() - { - size_t count; - BNDSCSymbolRep* value = BNDSCViewLoadAllSymbolsAndWait(m_object, &count); - if (value == nullptr) - { - return {}; - } +std::optional<CacheImage> SharedCacheController::GetImageContaining(uint64_t address) const +{ + BNSharedCacheImage apiImage; + if (!BNSharedCacheControllerGetImageContaining(m_object, address, &apiImage)) + return std::nullopt; + CacheImage image = ImageFromApi(apiImage); + BNSharedCacheFreeImage(apiImage); + return image; +} - std::vector<DSCSymbol> result; - result.reserve(count); - for (size_t i = 0; i < count; i++) - { - DSCSymbol sym; - sym.address = value[i].address; - sym.name = StringRef(BNDuplicateStringRef(value[i].name)); - sym.image = value[i].image; - result.push_back(sym); - } +std::optional<CacheImage> SharedCacheController::GetImageWithName(const std::string &name) const +{ + BNSharedCacheImage apiImage; + if (!BNSharedCacheControllerGetImageWithName(m_object, name.c_str(), &apiImage)) + return std::nullopt; + CacheImage image = ImageFromApi(apiImage); + BNSharedCacheFreeImage(apiImage); + return image; +} - BNDSCViewFreeSymbols(value, count); - return result; - } +std::vector<std::string> SharedCacheController::GetImageDependencies(const CacheImage &image) const +{ + size_t count; + BNSharedCacheImage apiImage = ImageToApi(image); + char **dependencies = BNSharedCacheControllerGetImageDependencies(m_object, &apiImage, &count); + BNSharedCacheFreeImage(apiImage); + std::vector<std::string> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(dependencies[i]); + BNFreeStringList(dependencies, count); + return result; +} - std::string SharedCache::GetNameForAddress(uint64_t address) - { - char* name = BNDSCViewGetNameForAddress(m_object, address); - if (name == nullptr) - return {}; - std::string result = name; - BNFreeString(name); - return result; - } +std::optional<CacheSymbol> SharedCacheController::GetSymbolAt(uint64_t address) const +{ + BNSharedCacheSymbol apiSymbol; + if (!BNSharedCacheControllerGetSymbolAt(m_object, address, &apiSymbol)) + return std::nullopt; + CacheSymbol symbol = SymbolFromApi(apiSymbol); + BNSharedCacheFreeSymbol(apiSymbol); + return symbol; +} - std::string SharedCache::GetImageNameForAddress(uint64_t address) - { - char* name = BNDSCViewGetImageNameForAddress(m_object, address); - if (name == nullptr) - return {}; - std::string result = name; - BNFreeString(name); - return result; - } +std::optional<CacheSymbol> SharedCacheController::GetSymbolWithName(const std::string &name) const +{ + BNSharedCacheSymbol apiSymbol; + if (!BNSharedCacheControllerGetSymbolWithName(m_object, name.c_str(), &apiSymbol)) + return std::nullopt; + CacheSymbol symbol = SymbolFromApi(apiSymbol); + BNSharedCacheFreeSymbol(apiSymbol); + return symbol; +} - std::optional<SharedCacheMachOHeader> SharedCache::GetMachOHeaderForImage(std::string name) - { - char* str = BNAllocString(name.c_str()); - char* outputStr = BNDSCViewGetImageHeaderForName(m_object, str); - if (outputStr == nullptr) - return {}; - std::string output = outputStr; - BNFreeString(outputStr); - if (output.empty()) - return {}; +std::vector<CacheEntry> SharedCacheController::GetEntries() const +{ + size_t count; + BNSharedCacheEntry *entries = BNSharedCacheControllerGetEntries(m_object, &count); + std::vector<CacheEntry> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(EntryFromApi(entries[i])); + BNSharedCacheFreeEntryList(entries, count); + return result; +} - return SharedCacheMachOHeader::LoadFromString(output); - } +std::vector<CacheRegion> SharedCacheController::GetLoadedRegions() const +{ + size_t count; + BNSharedCacheRegion *regions = BNSharedCacheControllerGetLoadedRegions(m_object, &count); + std::vector<CacheRegion> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(RegionFromApi(regions[i])); + BNSharedCacheFreeRegionList(regions, count); + return result; +} - std::optional<SharedCacheMachOHeader> SharedCache::GetMachOHeaderForAddress(uint64_t address) - { - char* outputStr = BNDSCViewGetImageHeaderForAddress(m_object, address); - if (outputStr == nullptr) - return {}; - std::string output = outputStr; - BNFreeString(outputStr); - if (output.empty()) - return {}; - return SharedCacheMachOHeader::LoadFromString(output); - } +std::vector<CacheRegion> SharedCacheController::GetRegions() const +{ + size_t count; + BNSharedCacheRegion *regions = BNSharedCacheControllerGetRegions(m_object, &count); + std::vector<CacheRegion> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(RegionFromApi(regions[i])); + BNSharedCacheFreeRegionList(regions, count); + return result; +} - BNDSCViewState SharedCache::GetState() - { - return BNDSCViewGetState(m_object); - } +std::vector<CacheImage> SharedCacheController::GetImages() const +{ + size_t count; + BNSharedCacheImage *images = BNSharedCacheControllerGetImages(m_object, &count); + std::vector<CacheImage> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(ImageFromApi(images[i])); + BNSharedCacheFreeImageList(images, count); + return result; +} - void SharedCache::FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) const - { - BNDSCFindSymbolAtAddressAndApplyToAddress(m_object, symbolLocation, targetLocation, triggerReanalysis); - } +std::vector<CacheImage> SharedCacheController::GetLoadedImages() const +{ + size_t count; + BNSharedCacheImage *images = BNSharedCacheControllerGetLoadedImages(m_object, &count); + std::vector<CacheImage> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(ImageFromApi(images[i])); + BNSharedCacheFreeImageList(images, count); + return result; +} -} // namespace SharedCacheAPI +std::vector<CacheSymbol> SharedCacheController::GetSymbols() const +{ + size_t count; + BNSharedCacheSymbol *symbols = BNSharedCacheControllerGetSymbols(m_object, &count); + std::vector<CacheSymbol> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(SymbolFromApi(symbols[i])); + BNSharedCacheFreeSymbolList(symbols, count); + return result; +} diff --git a/view/sharedcache/api/sharedcacheapi.h b/view/sharedcache/api/sharedcacheapi.h index 5c50e283..e12582f2 100644 --- a/view/sharedcache/api/sharedcacheapi.h +++ b/view/sharedcache/api/sharedcacheapi.h @@ -1,285 +1,327 @@ #pragma once #include <binaryninjaapi.h> -#include "../core/MetadataSerializable.hpp" -#include "view/macho/machoview.h" #include "sharedcachecore.h" -namespace SharedCacheAPI { - template<class T> - class SCRefCountObject { - void AddRefInternal() { m_refs.fetch_add(1); } +template<class T> +class DSCRefCountObject { + void AddRefInternal() { m_refs.fetch_add(1); } - void ReleaseInternal() { - if (m_refs.fetch_sub(1) == 1) - delete this; - } + void ReleaseInternal() { + if (m_refs.fetch_sub(1) == 1) + delete this; + } - public: - std::atomic<int> m_refs; - T *m_object; +public: + std::atomic<int> m_refs; + T *m_object; - SCRefCountObject() : m_refs(0), m_object(nullptr) {} + DSCRefCountObject() : m_refs(0), m_object(nullptr) {} - virtual ~SCRefCountObject() {} + virtual ~DSCRefCountObject() = default; - T *GetObject() const { return m_object; } + T *GetObject() const { return m_object; } - static T *GetObject(SCRefCountObject *obj) { - if (!obj) - return nullptr; - return obj->GetObject(); - } + static T *GetObject(DSCRefCountObject *obj) { + if (!obj) + return nullptr; + return obj->GetObject(); + } - void AddRef() { AddRefInternal(); } + void AddRef() { AddRefInternal(); } - void Release() { ReleaseInternal(); } + void Release() { ReleaseInternal(); } - void AddRefForRegistration() { AddRefInternal(); } - }; + void AddRefForRegistration() { AddRefInternal(); } +}; - template<class T, T *(*AddObjectReference)(T *), void (*FreeObjectReference)(T *)> - class SCCoreRefCountObject { - void AddRefInternal() { m_refs.fetch_add(1); } +template<class T, T *(*AddObjectReference)(T *), void (*FreeObjectReference)(T *)> +class DSCCoreRefCountObject { + void AddRefInternal() { m_refs.fetch_add(1); } - void ReleaseInternal() { - if (m_refs.fetch_sub(1) == 1) { - if (!m_registeredRef) - delete this; - } + void ReleaseInternal() { + if (m_refs.fetch_sub(1) == 1) { + if (!m_registeredRef) + delete this; } + } - public: - std::atomic<int> m_refs; - bool m_registeredRef = false; - T *m_object; +public: + std::atomic<int> m_refs; + bool m_registeredRef = false; + T *m_object; + + DSCCoreRefCountObject() : m_refs(0), m_object(nullptr) {} + + virtual ~DSCCoreRefCountObject() = default; + + T *GetObject() const { return m_object; } + + static T *GetObject(DSCCoreRefCountObject *obj) { + if (!obj) + return nullptr; + return obj->GetObject(); + } + + void AddRef() { + if (m_object && (m_refs != 0)) + AddObjectReference(m_object); + AddRefInternal(); + } + + void Release() { + if (m_object) + FreeObjectReference(m_object); + ReleaseInternal(); + } - SCCoreRefCountObject() : m_refs(0), m_object(nullptr) {} + void AddRefForRegistration() { m_registeredRef = true; } - virtual ~SCCoreRefCountObject() {} + void ReleaseForRegistration() { + m_object = nullptr; + m_registeredRef = false; + if (m_refs == 0) + delete this; + } +}; - T *GetObject() const { return m_object; } +template <class T> +class DSCRef +{ + T* m_obj; +#ifdef BN_REF_COUNT_DEBUG + void* m_assignmentTrace = nullptr; +#endif - static T *GetObject(SCCoreRefCountObject *obj) { - if (!obj) - return nullptr; - return obj->GetObject(); +public: + DSCRef() : m_obj(NULL) {} + + DSCRef(T* obj) : m_obj(obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif } + } - void AddRef() { - if (m_object && (m_refs != 0)) - AddObjectReference(m_object); - AddRefInternal(); + DSCRef(const DSCRef<T>& obj) : m_obj(obj.m_obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif } + } + + DSCRef(DSCRef<T>&& other) : m_obj(other.m_obj) + { + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + } - void Release() { - if (m_object) - FreeObjectReference(m_object); - ReleaseInternal(); + ~DSCRef() + { + if (m_obj) + { + m_obj->Release(); +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif } + } - void AddRefForRegistration() { m_registeredRef = true; } + DSCRef<T>& operator=(const BinaryNinja::Ref<T>& obj) + { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj.m_obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T* oldObj = m_obj; + m_obj = obj.m_obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } - void ReleaseForRegistration() { - m_object = nullptr; - m_registeredRef = false; - if (m_refs == 0) - delete this; + DSCRef<T>& operator=(DSCRef<T>&& other) + { + if (m_obj) + { +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif + m_obj->Release(); } - }; + m_obj = other.m_obj; + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + return *this; + } - struct DSCMemoryRegion { - uint64_t vmAddress; - uint64_t size; - std::string prettyName; - }; + DSCRef<T>& operator=(T* obj) + { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T* oldObj = m_obj; + m_obj = obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } - struct BackingCacheMapping { - uint64_t vmAddress; - uint64_t size; - uint64_t fileOffset; - }; + operator T*() const + { + return m_obj; + } - struct BackingCache { - std::string path; - BNBackingCacheType cacheType; - std::vector<BackingCacheMapping> mappings; - }; + T* operator->() const + { + return m_obj; + } - struct DSCImageMemoryMapping { - std::string filePath; - std::string name; - uint64_t vmAddress; - uint64_t size; - bool loaded; - uint64_t rawViewOffset; - }; + T& operator*() const + { + return *m_obj; + } - struct DSCImage { - std::string name; - uint64_t headerAddress; - std::vector<DSCImageMemoryMapping> mappings; - }; + bool operator!() const + { + return m_obj == NULL; + } - struct DSCSymbol { - uint64_t address; - BinaryNinja::StringRef name; - std::string image; - }; + bool operator==(const T* obj) const + { + return T::GetObject(m_obj) == T::GetObject(obj); + } - struct SharedCacheMachOHeader : public SharedCacheCore::MetadataSerializable<SharedCacheMachOHeader> { - uint64_t textBase = 0; - uint64_t loadCommandOffset = 0; - mach_header_64 ident; - std::string identifierPrefix; - std::string installName; + bool operator==(const DSCRef<T>& obj) const + { + return T::GetObject(m_obj) == T::GetObject(obj.m_obj); + } - std::vector<std::pair<uint64_t, bool>> entryPoints; - std::vector<uint64_t> m_entryPoints; // list of entrypoints + bool operator!=(const T* obj) const + { + return T::GetObject(m_obj) != T::GetObject(obj); + } - symtab_command symtab; - dysymtab_command dysymtab; - dyld_info_command dyldInfo; - routines_command_64 routines64; - function_starts_command functionStarts; - std::vector<section_64> moduleInitSections; - linkedit_data_command exportTrie; - linkedit_data_command chainedFixups {}; + bool operator!=(const DSCRef<T>& obj) const + { + return T::GetObject(m_obj) != T::GetObject(obj.m_obj); + } - uint64_t relocationBase; - // Section and program headers, internally use 64-bit form as it is a superset of 32-bit - std::vector<segment_command_64> segments; // only three types of sections __TEXT, __DATA, __IMPORT - segment_command_64 linkeditSegment; - std::vector<section_64> sections; - std::vector<std::string> sectionNames; + bool operator<(const T* obj) const + { + return T::GetObject(m_obj) < T::GetObject(obj); + } - std::vector<section_64> symbolStubSections; - std::vector<section_64> symbolPointerSections; + bool operator<(const DSCRef<T>& obj) const + { + return T::GetObject(m_obj) < T::GetObject(obj.m_obj); + } - std::vector<std::string> dylibs; + T* GetPtr() const + { + return m_obj; + } +}; - build_version_command buildVersion; - std::vector<build_tool_version> buildToolVersions; - std::string exportTriePath; - bool linkeditPresent = false; - bool dysymPresent = false; - bool dyldInfoPresent = false; - bool exportTriePresent = false; - bool chainedFixupsPresent = false; - bool routinesPresent = false; - bool functionStartsPresent = false; - bool relocatable = false; +// TODO: replace namespace? +namespace SharedCacheAPI { + struct CacheRegion + { + BNSharedCacheRegionType type; + std::string name; + uint64_t start; + uint64_t size; + std::optional<uint64_t> imageStart; + BNSegmentFlag flags; + }; - void Store(SharedCacheCore::SerializationContext& context) const { - MSS(textBase); - MSS(loadCommandOffset); - MSS_SUBCLASS(ident); - MSS(identifierPrefix); - MSS(installName); - MSS(entryPoints); - MSS(m_entryPoints); - MSS_SUBCLASS(symtab); - MSS_SUBCLASS(dysymtab); - MSS_SUBCLASS(dyldInfo); - MSS_SUBCLASS(routines64); - MSS_SUBCLASS(functionStarts); - MSS_SUBCLASS(moduleInitSections); - MSS_SUBCLASS(exportTrie); - MSS_SUBCLASS(chainedFixups); - MSS(relocationBase); - MSS_SUBCLASS(segments); - MSS_SUBCLASS(linkeditSegment); - MSS_SUBCLASS(sections); - MSS(sectionNames); - MSS_SUBCLASS(symbolStubSections); - MSS_SUBCLASS(symbolPointerSections); - MSS(dylibs); - MSS_SUBCLASS(buildVersion); - MSS_SUBCLASS(buildToolVersions); - MSS(exportTriePath); - MSS(linkeditPresent); - MSS(dysymPresent); - MSS(dyldInfoPresent); - MSS(exportTriePresent); - MSS(chainedFixupsPresent); - MSS(routinesPresent); - MSS(functionStartsPresent); - MSS(relocatable); - } + std::string GetRegionTypeAsString(const BNSharedCacheRegionType& type); - static SharedCacheMachOHeader Load(SharedCacheCore::DeserializationContext& context) { - SharedCacheMachOHeader header; - header.MSL(textBase); - header.MSL(loadCommandOffset); - header.MSL(ident); - header.MSL(identifierPrefix); - header.MSL(installName); - header.MSL(entryPoints); - header.MSL(m_entryPoints); - header.MSL(symtab); - header.MSL(dysymtab); - header.MSL(dyldInfo); - header.MSL(routines64); - header.MSL(functionStarts); - header.MSL(moduleInitSections); - header.MSL(exportTrie); - header.MSL(chainedFixups); - header.MSL(relocationBase); - header.MSL(segments); - header.MSL(linkeditSegment); - header.MSL(sections); - header.MSL(sectionNames); - header.MSL(symbolStubSections); - header.MSL(symbolPointerSections); - header.MSL(dylibs); - header.MSL(buildVersion); - header.MSL(buildToolVersions); - header.MSL(exportTriePath); - header.MSL(linkeditPresent); - header.MSL(dysymPresent); - header.MSL(dyldInfoPresent); - header.MSL(exportTriePresent); - header.MSL(chainedFixupsPresent); - header.MSL(routinesPresent); - header.MSL(functionStartsPresent); - header.MSL(relocatable); - return header; - } + struct CacheMappingInfo + { + uint64_t vmAddress; + uint64_t size; + uint64_t fileOffset; + }; + + struct CacheImage + { + uint64_t headerAddress; + std::string name; + std::vector<uint64_t> regionStarts; }; + struct CacheEntry + { + std::string path; + BNSharedCacheEntryType entryType; + std::vector<CacheMappingInfo> mappings; + }; + + struct CacheSymbol + { + BNSymbolType type; + uint64_t address; + std::string name; + }; - class SharedCache : public SCCoreRefCountObject<BNSharedCache, BNNewSharedCacheReference, BNFreeSharedCacheReference> { + class SharedCacheController : public DSCCoreRefCountObject<BNSharedCacheController, BNNewSharedCacheControllerReference, BNFreeSharedCacheControllerReference> { public: - SharedCache(Ref<BinaryView> view); + explicit SharedCacheController(BNSharedCacheController* controller); + static DSCRef<SharedCacheController> GetController(BinaryNinja::BinaryView& view); - BNDSCViewState GetState(); - static BNDSCViewLoadProgress GetLoadProgress(Ref<BinaryView> view); - static uint64_t FastGetBackingCacheCount(Ref<BinaryView> view); + bool ApplyRegion(BinaryNinja::BinaryView& view, const CacheRegion& region); - bool LoadImageWithInstallName(std::string installName, bool skipObjC = false); - bool LoadSectionAtAddress(uint64_t addr); - bool LoadImageContainingAddress(uint64_t addr, bool skipObjC = false); - std::vector<std::string> GetAvailableImages(); - - void ProcessObjCSectionsForImageWithInstallName(std::string installName); - void ProcessAllObjCSections(); + // Attempt to load the given image into the view. + // + // It is the callers responsibility to run linear sweep and update analysis, as you might want to add + // multiple images at a time. + bool ApplyImage(BinaryNinja::BinaryView& view, const CacheImage& image); - std::vector<DSCSymbol> LoadAllSymbolsAndWait(); + bool IsRegionLoaded(const CacheRegion& region) const; + bool IsImageLoaded(const CacheImage& image) const; - std::string GetNameForAddress(uint64_t address); - std::string GetImageNameForAddress(uint64_t address); + std::optional<CacheRegion> GetRegionAt(uint64_t address) const; + std::optional<CacheRegion> GetRegionContaining(uint64_t address) const; - std::vector<BackingCache> GetBackingCaches(); - std::vector<DSCImage> GetImages(); + std::optional<CacheImage> GetImageAt(uint64_t address) const; + std::optional<CacheImage> GetImageContaining(uint64_t address) const; + std::optional<CacheImage> GetImageWithName(const std::string& name) const; - std::optional<SharedCacheMachOHeader> GetMachOHeaderForImage(std::string name); - std::optional<SharedCacheMachOHeader> GetMachOHeaderForAddress(uint64_t address); + std::vector<std::string> GetImageDependencies(const CacheImage& image) const; - std::vector<DSCMemoryRegion> GetLoadedMemoryRegions(); + std::optional<CacheSymbol> GetSymbolAt(uint64_t address) const; + std::optional<CacheSymbol> GetSymbolWithName(const std::string& name) const; - void FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis = true) const; + std::vector<CacheEntry> GetEntries() const; + std::vector<CacheRegion> GetRegions() const; + std::vector<CacheRegion> GetLoadedRegions() const; + std::vector<CacheImage> GetImages() const; + std::vector<CacheImage> GetLoadedImages() const; + std::vector<CacheSymbol> GetSymbols() const; }; -}
\ No newline at end of file +} diff --git a/view/sharedcache/api/sharedcachecore.h b/view/sharedcache/api/sharedcachecore.h index 54899919..ab2acb60 100644 --- a/view/sharedcache/api/sharedcachecore.h +++ b/view/sharedcache/api/sharedcachecore.h @@ -1,11 +1,5 @@ #pragma once - -#ifdef __cplusplus -extern "C" -{ -#endif - #ifdef __GNUC__ #ifdef SHAREDCACHE_LIBRARY #define SHAREDCACHE_FFI_API __attribute__((visibility("default"))) @@ -28,137 +22,139 @@ extern "C" #endif // _MSC_VER #endif // __GNUC__C -#define CORE_ALLOCATED_STRUCT(T) - -#define CORE_ALLOCATED_CLASS(T) \ - public: \ - CORE_ALLOCATED_STRUCT(T) \ - private: - -#define DECLARE_SHAREDCACHE_API_OBJECT_INTERNAL(handle, cls, ns) \ - namespace ns { class cls; } struct handle { ns::cls* object; } - -#define DECLARE_SHAREDCACHE_API_OBJECT(handle, cls) DECLARE_SHAREDCACHE_API_OBJECT_INTERNAL(handle, cls, SharedCacheCore) - -#define IMPLEMENT_SHAREDCACHE_API_OBJECT(handle) \ - CORE_ALLOCATED_CLASS(handle) \ - private: \ - handle m_apiObject; \ - public: \ - typedef handle* APIHandle; \ - handle* GetAPIObject() { return &m_apiObject; } \ - private: -#define INIT_SHAREDCACHE_API_OBJECT() \ - m_apiObject.object = this; - - typedef enum BNDSCViewState { - Unloaded, - Loaded, - LoadedWithImages, - } BNDSCViewState; +#ifdef __cplusplus +extern "C" +{ +#endif - typedef enum BNDSCViewLoadProgress { - LoadProgressNotStarted, - LoadProgressLoadingCaches, - LoadProgressLoadingImages, - LoadProgressFinished, - } BNDSCViewLoadProgress; + // binaryninjacore.h is not included so we must duplicate enum types here. +#ifdef BN_TYPE_PARSER + typedef enum BNSegmentFlag + { + SegmentExecutable = 1, + SegmentWritable = 2, + SegmentReadable = 4, + SegmentContainsData = 8, + SegmentContainsCode = 0x10, + SegmentDenyWrite = 0x20, + SegmentDenyExecute = 0x40 + } BNSegmentFlag; - typedef enum BNBackingCacheType { - BackingCacheTypePrimary, - BackingCacheTypeSecondary, - BackingCacheTypeSymbols, - } BNBackingCacheType; + typedef enum BNSymbolType + { + FunctionSymbol = 0, + ImportAddressSymbol = 1, + ImportedFunctionSymbol = 2, + DataSymbol = 3, + ImportedDataSymbol = 4, + ExternalSymbol = 5, + LibraryFunctionSymbol = 6, + SymbolicFunctionSymbol = 7, + LocalLabelSymbol = 8, + } BNSymbolType; +#endif typedef struct BNBinaryView BNBinaryView; - typedef struct BNSharedCache BNSharedCache; - typedef struct BNStringRef BNStringRef; + typedef struct BNSharedCacheController BNSharedCacheController; - typedef struct BNDSCImageMemoryMapping { - char* filePath; - char* name; - uint64_t vmAddress; - uint64_t size; - bool loaded; - uint64_t rawViewOffset; - } BNDSCImageMemoryMapping; + typedef enum BNSharedCacheEntryType { + SharedCacheEntryTypePrimary, + SharedCacheEntryTypeSecondary, + SharedCacheEntryTypeSymbols, + SharedCacheEntryTypeDyldData, + SharedCacheEntryTypeStub, + } BNSharedCacheEntryType; - typedef struct BNDSCImage { + typedef enum BNSharedCacheRegionType { + SharedCacheRegionTypeImage, + SharedCacheRegionTypeStubIsland, + SharedCacheRegionTypeDyldData, + SharedCacheRegionTypeNonImage, + } BNSharedCacheRegionType; + + typedef struct BNSharedCacheImage { char* name; uint64_t headerAddress; - BNDSCImageMemoryMapping* mappings; - size_t mappingCount; - } BNDSCImage; + size_t regionStartCount; + uint64_t* regionStarts; + } BNSharedCacheImage; - typedef struct BNDSCMappedMemoryRegion { + typedef struct BNSharedCacheRegion { + BNSharedCacheRegionType regionType; + char* name; uint64_t vmAddress; uint64_t size; - char* name; - } BNDSCMappedMemoryRegion; + // NOTE: If not associated with an image this will be zero. + uint64_t imageStart; + BNSegmentFlag flags; + } BNSharedCacheRegion; - typedef struct BNDSCBackingCacheMapping { + typedef struct BNSharedCacheMappingInfo { uint64_t vmAddress; uint64_t size; uint64_t fileOffset; - } BNDSCBackingCacheMapping; + } BNSharedCacheMappingInfo; - typedef struct BNDSCBackingCache { + typedef struct BNSharedCacheEntry { char* path; - BNBackingCacheType cacheType; - BNDSCBackingCacheMapping* mappings; + BNSharedCacheEntryType entryType; size_t mappingCount; - } BNDSCBackingCache; - - typedef struct BNDSCMemoryUsageInfo { - uint64_t sharedCacheRefs; - uint64_t mmapRefs; - } BNDSCMemoryUsageInfo; + BNSharedCacheMappingInfo* mappings; + } BNSharedCacheEntry; - typedef struct BNDSCSymbolRep { + typedef struct BNSharedCacheSymbol { + BNSymbolType symbolType; uint64_t address; - BNStringRef* name; - char* image; - } BNDSCSymbolRep; + char* name; + } BNSharedCacheSymbol; - SHAREDCACHE_FFI_API BNSharedCache* BNGetSharedCache(BNBinaryView* data); + SHAREDCACHE_FFI_API BNSharedCacheController* BNGetSharedCacheController(BNBinaryView* data); - SHAREDCACHE_FFI_API BNSharedCache* BNNewSharedCacheReference(BNSharedCache* cache); - SHAREDCACHE_FFI_API void BNFreeSharedCacheReference(BNSharedCache* cache); + SHAREDCACHE_FFI_API BNSharedCacheController* BNNewSharedCacheControllerReference(BNSharedCacheController* controller); + SHAREDCACHE_FFI_API void BNFreeSharedCacheControllerReference(BNSharedCacheController* controller); - SHAREDCACHE_FFI_API char** BNDSCViewGetInstallNames(BNSharedCache* cache, size_t* count); + SHAREDCACHE_FFI_API bool BNSharedCacheControllerApplyImage(BNSharedCacheController* controller, BNBinaryView* view, BNSharedCacheImage* image); + SHAREDCACHE_FFI_API bool BNSharedCacheControllerApplyRegion(BNSharedCacheController* controller, BNBinaryView* view, BNSharedCacheRegion* region); - SHAREDCACHE_FFI_API bool BNDSCViewLoadImageWithInstallName(BNSharedCache* cache, char* name, bool skipObjC); - SHAREDCACHE_FFI_API bool BNDSCViewLoadSectionAtAddress(BNSharedCache* cache, uint64_t name); - SHAREDCACHE_FFI_API bool BNDSCViewLoadImageContainingAddress(BNSharedCache* cache, uint64_t address, bool skipObjC); + SHAREDCACHE_FFI_API bool BNSharedCacheControllerIsImageLoaded(BNSharedCacheController* controller, BNSharedCacheImage* image); + SHAREDCACHE_FFI_API bool BNSharedCacheControllerIsRegionLoaded(BNSharedCacheController* controller, BNSharedCacheRegion* region); - SHAREDCACHE_FFI_API void BNDSCViewProcessObjCSectionsForImageWithInstallName(BNSharedCache* cache, char* name, bool deallocName); - SHAREDCACHE_FFI_API void BNDSCViewProcessAllObjCSections(BNSharedCache* cache); + SHAREDCACHE_FFI_API bool BNSharedCacheControllerGetRegionAt(BNSharedCacheController* controller, uint64_t address, BNSharedCacheRegion* outRegion); + SHAREDCACHE_FFI_API bool BNSharedCacheControllerGetRegionContaining(BNSharedCacheController* controller, uint64_t address, BNSharedCacheRegion* region); + + SHAREDCACHE_FFI_API BNSharedCacheRegion* BNSharedCacheControllerGetRegions(BNSharedCacheController* controller, size_t* count); + SHAREDCACHE_FFI_API BNSharedCacheRegion* BNSharedCacheControllerGetLoadedRegions(BNSharedCacheController* controller, size_t* count); + + SHAREDCACHE_FFI_API uint64_t* BNSharedCacheAllocRegionList(uint64_t* list, size_t count); + + SHAREDCACHE_FFI_API void BNSharedCacheFreeRegion(BNSharedCacheRegion region); + SHAREDCACHE_FFI_API void BNSharedCacheFreeRegionList(BNSharedCacheRegion* regions, size_t count); + + SHAREDCACHE_FFI_API bool BNSharedCacheControllerGetImageAt(BNSharedCacheController* controller, uint64_t address, BNSharedCacheImage* image); + SHAREDCACHE_FFI_API bool BNSharedCacheControllerGetImageContaining(BNSharedCacheController* controller, uint64_t address, BNSharedCacheImage* image); + SHAREDCACHE_FFI_API bool BNSharedCacheControllerGetImageWithName(BNSharedCacheController* controller, const char* name, BNSharedCacheImage* image); - SHAREDCACHE_FFI_API char* BNDSCViewGetNameForAddress(BNSharedCache* cache, uint64_t address); - SHAREDCACHE_FFI_API char* BNDSCViewGetImageNameForAddress(BNSharedCache* cache, uint64_t address); + SHAREDCACHE_FFI_API char** BNSharedCacheControllerGetImageDependencies(BNSharedCacheController* controller, BNSharedCacheImage* image, size_t* count); - SHAREDCACHE_FFI_API BNDSCViewState BNDSCViewGetState(BNSharedCache* cache); - SHAREDCACHE_FFI_API BNDSCViewLoadProgress BNDSCViewGetLoadProgress(uint64_t sessionID); - SHAREDCACHE_FFI_API uint64_t BNDSCViewFastGetBackingCacheCount(BNBinaryView* view); + SHAREDCACHE_FFI_API BNSharedCacheImage* BNSharedCacheControllerGetImages(BNSharedCacheController* controller, size_t* count); + SHAREDCACHE_FFI_API BNSharedCacheImage* BNSharedCacheControllerGetLoadedImages(BNSharedCacheController* controller, size_t* count); - SHAREDCACHE_FFI_API BNDSCSymbolRep* BNDSCViewLoadAllSymbolsAndWait(BNSharedCache* cache, size_t* count); - SHAREDCACHE_FFI_API void BNDSCViewFreeSymbols(BNDSCSymbolRep* symbols, size_t count); + SHAREDCACHE_FFI_API void BNSharedCacheFreeImage(BNSharedCacheImage image); + SHAREDCACHE_FFI_API void BNSharedCacheFreeImageList(BNSharedCacheImage* images, size_t count); - SHAREDCACHE_FFI_API BNDSCMappedMemoryRegion* BNDSCViewGetLoadedRegions(BNSharedCache* cache, size_t* count); - SHAREDCACHE_FFI_API void BNDSCViewFreeLoadedRegions(BNDSCMappedMemoryRegion* images, size_t count); + SHAREDCACHE_FFI_API bool BNSharedCacheControllerGetSymbolAt(BNSharedCacheController* controller, uint64_t address, BNSharedCacheSymbol* symbol); + SHAREDCACHE_FFI_API bool BNSharedCacheControllerGetSymbolWithName(BNSharedCacheController* controller, const char* name, BNSharedCacheSymbol* symbol); - SHAREDCACHE_FFI_API BNDSCImage* BNDSCViewGetAllImages(BNSharedCache* cache, size_t* count); - SHAREDCACHE_FFI_API void BNDSCViewFreeAllImages(BNDSCImage* images, size_t count); + SHAREDCACHE_FFI_API BNSharedCacheSymbol* BNSharedCacheControllerGetSymbols(BNSharedCacheController* controller, size_t* count); - SHAREDCACHE_FFI_API BNDSCBackingCache* BNDSCViewGetBackingCaches(BNSharedCache* cache, size_t* count); - SHAREDCACHE_FFI_API void BNDSCViewFreeBackingCaches(BNDSCBackingCache* caches, size_t count); + SHAREDCACHE_FFI_API void BNSharedCacheFreeSymbol(BNSharedCacheSymbol symbol); + SHAREDCACHE_FFI_API void BNSharedCacheFreeSymbolList(BNSharedCacheSymbol* symbols, size_t count); - SHAREDCACHE_FFI_API void BNDSCFindSymbolAtAddressAndApplyToAddress(BNSharedCache* cache, uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis); + SHAREDCACHE_FFI_API BNSharedCacheEntry* BNSharedCacheControllerGetEntries(BNSharedCacheController* controller, size_t* count); - SHAREDCACHE_FFI_API char* BNDSCViewGetImageHeaderForAddress(BNSharedCache* cache, uint64_t address); - SHAREDCACHE_FFI_API char* BNDSCViewGetImageHeaderForName(BNSharedCache* cache, char* name); + SHAREDCACHE_FFI_API void BNSharedCacheFreeEntry(BNSharedCacheEntry entry); + SHAREDCACHE_FFI_API void BNSharedCacheFreeEntryList(BNSharedCacheEntry* entries, size_t count); - [[maybe_unused]] SHAREDCACHE_FFI_API BNDSCMemoryUsageInfo BNDSCViewGetMemoryUsageInfo(); #ifdef __cplusplus } diff --git a/view/sharedcache/core/CMakeLists.txt b/view/sharedcache/core/CMakeLists.txt index 03766920..a9aaa1bc 100644 --- a/view/sharedcache/core/CMakeLists.txt +++ b/view/sharedcache/core/CMakeLists.txt @@ -9,7 +9,7 @@ if((NOT BN_API_PATH) AND (NOT BN_INTERNAL_BUILD)) endif() endif() -file(GLOB SOURCES *.cpp *.h ../../../objectivec/*) +file(GLOB_RECURSE SOURCES *.cpp *.h ../../../objectivec/*) add_library(sharedcachecore OBJECT ${SOURCES}) diff --git a/view/sharedcache/core/DSCView.h b/view/sharedcache/core/DSCView.h deleted file mode 100644 index bde2f7c2..00000000 --- a/view/sharedcache/core/DSCView.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by kat on 5/23/23. -// - -#ifndef SHAREDCACHE_DSCVIEW_H -#define SHAREDCACHE_DSCVIEW_H - -#include <binaryninjaapi.h> - - -class DSCView : public BinaryNinja::BinaryView { - bool m_parseOnly; -public: - - DSCView(const std::string &typeName, BinaryView *data, bool parseOnly = false); - - ~DSCView() override; - - bool Init() override; -}; - - -class DSCViewType : public BinaryNinja::BinaryViewType { - -public: - DSCViewType(); - - BinaryNinja::Ref<BinaryNinja::BinaryView> Create(BinaryNinja::BinaryView *data) override; - - BinaryNinja::Ref<BinaryNinja::BinaryView> Parse(BinaryNinja::BinaryView *data) override; - - bool IsTypeValidForData(BinaryNinja::BinaryView *data) override; - - bool IsDeprecated() override { return false; } - - BinaryNinja::Ref<BinaryNinja::Settings> GetLoadSettingsForData(BinaryNinja::BinaryView *data) override; -}; - - -#endif //SHAREDCACHE_DSCVIEW_H diff --git a/view/sharedcache/core/Dyld.h b/view/sharedcache/core/Dyld.h new file mode 100644 index 00000000..70397877 --- /dev/null +++ b/view/sharedcache/core/Dyld.h @@ -0,0 +1,267 @@ +#pragma once + +// These types will be parsed directly from the files. + +#include <stdint.h> + +#if defined(__GNUC__) || defined(__clang__) + #define PACKED_STRUCT __attribute__((packed)) +#else + #define PACKED_STRUCT +#endif + +struct PACKED_STRUCT dyld_cache_mapping_info +{ + uint64_t address; + uint64_t size; + uint64_t fileOffset; + uint32_t maxProt; + uint32_t initProt; +}; + +struct dyld_cache_slide_info +{ + uint32_t version; + uint32_t toc_offset; + uint32_t toc_count; + uint32_t entries_offset; + uint32_t entries_count; + uint32_t entries_size; + // uint16_t toc[toc_count]; + // entrybitmap entries[entries_count]; +}; + +struct dyld_cache_slide_info_entry +{ + uint8_t bits[4096 / (8 * 4)]; // 128-byte bitmap +}; + +struct PACKED_STRUCT dyld_cache_mapping_and_slide_info +{ + uint64_t address; + uint64_t size; + uint64_t fileOffset; + uint64_t slideInfoFileOffset; + uint64_t slideInfoFileSize; + uint64_t flags; + uint32_t maxProt; + uint32_t initProt; +}; + +struct PACKED_STRUCT dyld_cache_slide_info_v2 +{ + uint32_t version; + uint32_t page_size; + uint32_t page_starts_offset; + uint32_t page_starts_count; + uint32_t page_extras_offset; + uint32_t page_extras_count; + uint64_t delta_mask; + uint64_t value_add; +}; +#define DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA 0x8000 // index is into extras array (not starts array) +#define DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE 0x4000 // page has no rebasing +#define DYLD_CACHE_SLIDE_PAGE_ATTR_END 0x8000 // last chain entry for page + +#define DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing + +struct PACKED_STRUCT dyld_cache_slide_info_v3 +{ + uint32_t version; + uint32_t page_size; + uint32_t page_starts_count; + uint32_t pad_i_guess; + uint64_t auth_value_add; +}; + + +// DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE +struct dyld_chained_ptr_arm64e_shared_cache_rebase +{ + uint64_t runtimeOffset : 34, // offset from the start of the shared cache + high8 : 8, unused : 10, + next : 11, // 8-byte stide + auth : 1; // == 0 +}; + +// DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE +struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase +{ + uint64_t runtimeOffset : 34, // offset from the start of the shared cache + diversity : 16, addrDiv : 1, + keyIsData : 1, // implicitly always the 'A' key. 0 -> IA. 1 -> DA + next : 11, // 8-byte stide + auth : 1; // == 1 +}; + +// TODO: dyld_cache_slide_info4 is used in watchOS which we are not close to supporting right now. + +#define DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing + +struct PACKED_STRUCT dyld_cache_slide_info_v5 +{ + uint32_t version; // currently 5 + uint32_t page_size; // currently 4096 (may also be 16384) + uint32_t page_starts_count; + uint32_t pad; // padding to ensure the value below is on an 8-byte boundary + uint64_t value_add; + // uint16_t page_starts[/* page_starts_count */]; +}; + + +struct PACKED_STRUCT dyld_cache_image_info +{ + uint64_t address; + uint64_t modTime; + uint64_t inode; + uint32_t pathFileOffset; + uint32_t pad; + + bool operator==(const dyld_cache_image_info& image) const; +}; + +union dyld_cache_slide_pointer5 +{ + uint64_t raw; + struct dyld_chained_ptr_arm64e_shared_cache_rebase regular; + struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase auth; +}; + + +struct PACKED_STRUCT dyld_cache_local_symbols_info +{ + uint32_t nlistOffset; // offset into this chunk of nlist entries + uint32_t nlistCount; // count of nlist entries + uint32_t stringsOffset; // offset into this chunk of string pool + uint32_t stringsSize; // byte count of string pool + uint32_t entriesOffset; // offset into this chunk of array of dyld_cache_local_symbols_entry + uint32_t entriesCount; // number of elements in dyld_cache_local_symbols_entry array +}; + +struct PACKED_STRUCT dyld_cache_local_symbols_entry +{ + uint32_t dylibOffset; // offset in cache file of start of dylib + uint32_t nlistStartIndex; // start index of locals for this dylib + uint32_t nlistCount; // number of local symbols for this dylib +}; + +struct PACKED_STRUCT dyld_cache_local_symbols_entry_64 +{ + uint64_t dylibOffset; // offset in cache buffer of start of dylib + uint32_t nlistStartIndex; // start index of locals for this dylib + uint32_t nlistCount; // number of local symbols for this dylib +}; + +union dyld_cache_slide_pointer3 +{ + uint64_t raw; + struct + { + uint64_t pointerValue : 51, offsetToNextPointer : 11, unused : 2; + } plain; + + struct + { + uint64_t offsetFromSharedCacheBase : 32, diversityData : 16, hasAddressDiversity : 1, key : 2, + offsetToNextPointer : 11, unused : 1, + authenticated : 1; // = 1; + } auth; +}; + + +struct PACKED_STRUCT dyld_cache_header +{ + char magic[16]; // e.g. "dyld_v0 i386" + uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info + uint32_t mappingCount; // number of dyld_cache_mapping_info entries + uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing + uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing + uint64_t dyldBaseAddress; // base address of dyld when cache was built + uint64_t codeSignatureOffset; // file offset of code signature blob + uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file) + uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info + uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info + uint64_t localSymbolsOffset; // file offset of where local symbols are stored + uint64_t localSymbolsSize; // size of local symbols information + uint8_t uuid[16]; // unique value for each shared cache file + uint64_t cacheType; // 0 for development, 1 for production, 2 for multi-cache + uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses + uint32_t branchPoolsCount; // number of uint64_t entries + uint64_t dyldInCacheMH; // (unslid) address of mach_header of dyld in cache + uint64_t dyldInCacheEntry; // (unslid) address of entry point (_dyld_start) of dyld in cache + uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info + uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries + uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info + uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info + uint64_t otherImageGroupAddrUnused; // unused + uint64_t otherImageGroupSizeUnused; // unused + uint64_t progClosuresAddr; // (unslid) address of list of program launch closures + uint64_t progClosuresSize; // size of list of program launch closures + uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures + uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures + uint32_t platform; // platform number (macOS=1, etc) + uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion + dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if + // cache is valid + simulator : 1, // for simulator of specified platform + locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache + builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used + // for overrides + padding : 20; // TBD + uint64_t sharedRegionStart; // base load address of cache if not slid + uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any + uint64_t maxSlide; // runtime slide of cache can be between zero and this value + uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache + uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache + uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs + uint64_t dylibsTrieSize; // size of trie of cached dylib paths + uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures + uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures + uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info + uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries + uint64_t dylibsPBLStateArrayAddrUnused; // unused + uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs + uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program + uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program + uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet + uint32_t programTrieSize; + uint32_t osVersion; // OS Version of dylibs in this cache for the main platform + uint32_t altPlatform; // e.g. iOSMac on macOS + uint32_t altOsVersion; // e.g. 14.0 for iOSMac + uint64_t swiftOptsOffset; // VM offset from cache_header* to Swift optimizations header + uint64_t swiftOptsSize; // size of Swift optimizations header + uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry + uint32_t subCacheArrayCount; // number of subCache entries + uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols + uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data + uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region + uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data + uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region + uint32_t imagesOffset; // file offset to first dyld_cache_image_info + uint32_t imagesCount; // number of dyld_cache_image_info entries + uint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2) + uint32_t padding2; + uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header + uint64_t objcOptsSize; // size of ObjC optimizations header + uint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection + uint64_t cacheAtlasSize; // size of embedded cache atlas + uint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header + uint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data + uint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info + uint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries +}; + +struct PACKED_STRUCT dyld_subcache_entry +{ + char uuid[16]; + uint64_t address; +}; + +struct PACKED_STRUCT dyld_subcache_entry2 +{ + char uuid[16]; + uint64_t address; + char fileExtension[32]; +};
\ No newline at end of file diff --git a/view/sharedcache/core/FileAccessorCache.cpp b/view/sharedcache/core/FileAccessorCache.cpp new file mode 100644 index 00000000..047bbe19 --- /dev/null +++ b/view/sharedcache/core/FileAccessorCache.cpp @@ -0,0 +1,112 @@ +#include "FileAccessorCache.h" + +#include <cassert> + +CacheAccessorID GetCacheAccessorID(const std::string& filePath) +{ + constexpr std::hash<std::string> hasher; + return static_cast<CacheAccessorID>(hasher(filePath)); +} + +FileAccessorCache::FileAccessorCache(size_t cacheSize) +{ + m_cacheSize = cacheSize; + m_accessors = {}; +} + +void FileAccessorCache::EvictLastUsed() +{ + if (m_cache.empty()) + return; + // Evict the least recently used element. + const auto lruID = m_cache.front(); + m_cache.pop_front(); + // Ensure the least recently used ID actually exists in the accessors map. + assert(m_accessors.find(lruID) != m_accessors.end() && "Evicting non-existent ID from accessors map"); + m_accessors.erase(lruID); +} + +FileAccessorCache& FileAccessorCache::Global() +{ + static FileAccessorCache cache {}; + return cache; +} + + +WeakFileAccessor FileAccessorCache::Open(const std::string& filePath) +{ + const auto id = GetCacheAccessorID(filePath); + std::unique_lock lock(m_mutex); + + // Check if the file is already in the cache. + if (const auto it = m_accessors.find(id); it != m_accessors.end()) + { + // Move the accessed ID to the back so we keep it in the cache. + auto pos = std::find(m_cache.begin(), m_cache.end(), id); + if (pos != m_cache.end()) + m_cache.erase(pos); + m_cache.push_back(id); + + return WeakFileAccessor(it->second, filePath); + } + + // Evict if we are going to go above the limit. + if (m_cache.size() >= m_cacheSize) + EvictLastUsed(); + + // Create a new file accessor and add it to the cache. + auto accessor = MappedFileAccessor::Open(filePath); + if (accessor == nullptr) + { + // We failed to open the file, we must throw hard! + // TODO: Make this mechanism more thought out... + throw std::runtime_error("Failed to open file: " + filePath); + } + auto sharedAccessor = std::make_shared<MappedFileAccessor>(std::move(*accessor)); + m_accessors.insert_or_assign(id, sharedAccessor); + m_cache.push_back(id); + + return WeakFileAccessor(sharedAccessor, filePath); +} + +void FileAccessorWriteLog::AddPointer(const size_t address, const size_t pointer) +{ + // TODO: A sharded map here would be better. see: rust dashmap + std::unique_lock<std::shared_mutex> lock(m_persistedMutex); + m_persistedPointers[address] = pointer; +} + +void FileAccessorWriteLog::ApplyWrites(MappedFileAccessor& accessor) +{ + std::shared_lock<std::shared_mutex> lock(m_persistedMutex); + for (const auto& [address, pointer] : m_persistedPointers) + accessor.WritePointer(address, pointer); +} + +std::shared_ptr<MappedFileAccessor> WeakFileAccessor::lock() +{ + auto sharedPtr = m_weakPtr.lock(); + if (!sharedPtr) + { + // This will revive other weak pointers to the same shared ptr. + // Update the weak pointer to the newly created shared instance + m_weakPtr = FileAccessorCache::Global().Open(m_filePath).m_weakPtr; + sharedPtr = m_weakPtr.lock(); + + // Apply any previously written pointers back. + if (sharedPtr) + m_writeLog->ApplyWrites(*sharedPtr); + } + + return sharedPtr; +} + +void WeakFileAccessor::WritePointer(const size_t address, const size_t pointer) +{ + // Persist the pointer after the file accessor is revived. + m_writeLog->AddPointer(address, pointer); + + // And then actually apply the written pointer... + if (auto sharedPtr = m_weakPtr.lock()) + sharedPtr->WritePointer(address, pointer); +} diff --git a/view/sharedcache/core/FileAccessorCache.h b/view/sharedcache/core/FileAccessorCache.h new file mode 100644 index 00000000..bb2f8084 --- /dev/null +++ b/view/sharedcache/core/FileAccessorCache.h @@ -0,0 +1,84 @@ +#pragma once + +#include <shared_mutex> + +#include "MappedFileAccessor.h" + +typedef uint32_t CacheAccessorID; + +// TODO: We might want to make this more than just the path, for example +// TODO: We might want to make it unique to a view session (session id). +// Get a unique entry id for the given file path. +CacheAccessorID GetCacheAccessorID(const std::string& filePath); + +class WeakFileAccessor; + +class FileAccessorCache +{ + size_t m_cacheSize; + std::mutex m_mutex; + // NOTE: If we end up wanting to handle 1000's of files we should consider std::list. + std::deque<CacheAccessorID> m_cache; + std::unordered_map<CacheAccessorID, std::shared_ptr<MappedFileAccessor>> m_accessors; + + explicit FileAccessorCache(size_t cacheSize = 8); + + void EvictLastUsed(); + +public: + static FileAccessorCache& Global(); + + // Get a weak reference to a file accessor, the reference at this point is alive. + // The reference is always alive at this point either because it is in the cache or it has been inserted in. + // Subsequent calls to this might kill the backing file accessor resulting in the weak ref recreating the file + // accessor and inserting itself back into its related cache. + WeakFileAccessor Open(const std::string& filePath); + + // Adjust the cache size limit. + // This will NOT evict current cache entries, as they are already available. + // Any subsequent call to `Open` will assume this cache size, evicting until the size is equal to the cache size. + void SetCacheSize(uint64_t size) { m_cacheSize = size; }; +}; + +// Write log to be used in conjunction with `WeakFileAccessor` to re-apply written data to a "revived" file. +struct FileAccessorWriteLog +{ + // To persist writes to a file accessor being revived (within the lock() function) + // we keep a list of writes that will be re-applied in the lock function. + std::shared_mutex m_persistedMutex; + std::unordered_map<size_t, uint64_t> m_persistedPointers; + + FileAccessorWriteLog() = default; + + // Add the pointer to the persisted pointers. + void AddPointer(size_t address, size_t pointer); + + // Apply all logged writes to the given accessor. + void ApplyWrites(MappedFileAccessor& accessor); +}; + +class WeakFileAccessor +{ + // Weak pointer to the mapped file accessor, once this is expired we will re-open. + std::weak_ptr<MappedFileAccessor> m_weakPtr; + // File path for re-opening if needed + std::string m_filePath; + + // Used to re-add writes once the file accessor is "revived". + std::shared_ptr<FileAccessorWriteLog> m_writeLog; + + // TODO: Store a weak_ptr/shared_ptr to FileAccessorCache? That way we dont access Global() + // TODO: Only need to do the above if we want multiple caches. + +public: + explicit WeakFileAccessor(std::weak_ptr<MappedFileAccessor> weakPtr, std::string filePath) : + m_weakPtr(std::move(weakPtr)), m_filePath(std::move(filePath)), + m_writeLog(std::make_shared<FileAccessorWriteLog>()) + {} + + std::shared_ptr<MappedFileAccessor> lock(); + + // Persists the written pointer within this weak file accessor. + // This works as we expect the weak file accessor to be stored per virtual memory region. + void WritePointer(size_t address, size_t pointer); +}; diff --git a/view/sharedcache/core/MachO.cpp b/view/sharedcache/core/MachO.cpp new file mode 100644 index 00000000..14fffefd --- /dev/null +++ b/view/sharedcache/core/MachO.cpp @@ -0,0 +1,667 @@ +#include "MachO.h" +#include "Utility.h" + +#include "SharedCache.h" +#include "VirtualMemory.h" + +using namespace BinaryNinja; + +std::vector<uint64_t> SharedCacheMachOHeader::ReadFunctionTable(VirtualMemory& vm) const +{ + // NOTE: The funcoff is relative to the file of the linkedit segment. + uint64_t funcStartsAddress = GetLinkEditFileBase() + functionStarts.funcoff; + auto funcStarts = vm.ReadBuffer(funcStartsAddress, functionStarts.funcsize); + uint64_t curfunc = textBase; + uint64_t curOffset = 0; + + std::vector<uint64_t> functionTable = {}; + auto current = static_cast<const uint8_t*>(funcStarts.GetData()); + auto end = current + funcStarts.GetLength(); + while (current != end) + { + curOffset = readLEB128(current, end); + // TODO: Verify this is the correct behavior. + // Skip unmapped functions. + if (curOffset == 0 || !vm.IsAddressMapped(curfunc)) + continue; + curfunc += curOffset; + uint64_t target = curfunc; + functionTable.push_back(target); + } + return functionTable; +} + +std::optional<SharedCacheMachOHeader> SharedCacheMachOHeader::ParseHeaderForAddress( + std::shared_ptr<VirtualMemory> vm, uint64_t address, const std::string& imagePath) +{ + // Sanity check to make sure that the header is mapped. + // This should really only fail if we didn't grab all the required entries. + if (!vm->IsAddressMapped(address)) + return std::nullopt; + + SharedCacheMachOHeader header; + + header.textBase = address; + header.installName = imagePath; + // The identifierPrefix is used for the display of the image name in the sections and segments. + header.identifierPrefix = BaseFileName(imagePath); + + std::string errorMsg; + VirtualMemoryReader reader(vm); + reader.Seek(address); + + header.ident.magic = reader.ReadUInt32(); + + BNEndianness endianness; + switch (header.ident.magic) + { + case MH_MAGIC: + case MH_MAGIC_64: + endianness = LittleEndian; + break; + case MH_CIGAM: + case MH_CIGAM_64: + endianness = BigEndian; + break; + default: + return {}; + } + + reader.SetEndianness(endianness); + header.ident.cputype = reader.ReadUInt32(); + header.ident.cpusubtype = reader.ReadUInt32(); + header.ident.filetype = reader.ReadUInt32(); + header.ident.ncmds = reader.ReadUInt32(); + header.ident.sizeofcmds = reader.ReadUInt32(); + header.ident.flags = reader.ReadUInt32(); + if ((header.ident.cputype & MachOABIMask) == MachOABI64) // address size == 8 + { + header.ident.reserved = reader.ReadUInt32(); + } + header.loadCommandOffset = reader.GetOffset(); + + bool first = true; + // Parse segment commands + try + { + for (size_t i = 0; i < header.ident.ncmds; i++) + { + // BNLogInfo("of 0x%llx", reader.GetOffset()); + load_command load; + segment_command_64 segment64; + section_64 sect = {}; + size_t curOffset = reader.GetOffset(); + load.cmd = reader.ReadUInt32(); + load.cmdsize = reader.ReadUInt32(); + size_t nextOffset = curOffset + load.cmdsize; + if (load.cmdsize < sizeof(load_command)) + return {}; + + switch (load.cmd) + { + case LC_MAIN: + { + uint64_t entryPoint = reader.ReadUInt64(); + header.entryPoints.push_back({entryPoint, true}); + (void)reader.ReadUInt64(); // Stack start + break; + } + case LC_SEGMENT: // map the 32bit version to 64 bits + segment64.cmd = LC_SEGMENT_64; + reader.Read(&segment64.segname, 16); + segment64.vmaddr = reader.ReadUInt32(); + segment64.vmsize = reader.ReadUInt32(); + segment64.fileoff = reader.ReadUInt32(); + segment64.filesize = reader.ReadUInt32(); + segment64.maxprot = reader.ReadUInt32(); + segment64.initprot = reader.ReadUInt32(); + segment64.nsects = reader.ReadUInt32(); + segment64.flags = reader.ReadUInt32(); + if (first) + { + if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) + || (segment64.flags & MACHO_VM_PROT_WRITE)) + { + header.relocationBase = segment64.vmaddr; + first = false; + } + } + for (size_t j = 0; j < segment64.nsects; j++) + { + reader.Read(§.sectname, 16); + reader.Read(§.segname, 16); + sect.addr = reader.ReadUInt32(); + sect.size = reader.ReadUInt32(); + sect.offset = reader.ReadUInt32(); + sect.align = reader.ReadUInt32(); + sect.reloff = reader.ReadUInt32(); + sect.nreloc = reader.ReadUInt32(); + sect.flags = reader.ReadUInt32(); + sect.reserved1 = reader.ReadUInt32(); + sect.reserved2 = reader.ReadUInt32(); + // if the segment isn't mapped into virtual memory don't add the corresponding sections. + if (segment64.vmsize > 0) + { + header.sections.push_back(sect); + } + if (!strncmp(sect.sectname, "__mod_init_func", 15)) + header.moduleInitSections.push_back(sect); + if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + header.symbolStubSections.push_back(sect); + if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + } + header.segments.push_back(segment64); + break; + case LC_SEGMENT_64: + segment64.cmd = LC_SEGMENT_64; + reader.Read(&segment64.segname, 16); + segment64.vmaddr = reader.ReadUInt64(); + segment64.vmsize = reader.ReadUInt64(); + segment64.fileoff = reader.ReadUInt64(); + segment64.filesize = reader.ReadUInt64(); + segment64.maxprot = reader.ReadUInt32(); + segment64.initprot = reader.ReadUInt32(); + segment64.nsects = reader.ReadUInt32(); + segment64.flags = reader.ReadUInt32(); + if (strncmp(segment64.segname, "__LINKEDIT", 10) == 0) + { + header.linkeditSegment = segment64; + header.linkeditPresent = true; + } + if (first) + { + if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) + || (segment64.flags & MACHO_VM_PROT_WRITE)) + { + header.relocationBase = segment64.vmaddr; + first = false; + } + } + for (size_t j = 0; j < segment64.nsects; j++) + { + reader.Read(§.sectname, 16); + reader.Read(§.segname, 16); + sect.addr = reader.ReadUInt64(); + sect.size = reader.ReadUInt64(); + sect.offset = reader.ReadUInt32(); + sect.align = reader.ReadUInt32(); + sect.reloff = reader.ReadUInt32(); + sect.nreloc = reader.ReadUInt32(); + sect.flags = reader.ReadUInt32(); + sect.reserved1 = reader.ReadUInt32(); + sect.reserved2 = reader.ReadUInt32(); + sect.reserved3 = reader.ReadUInt32(); + // if the segment isn't mapped into virtual memory don't add the corresponding sections. + if (segment64.vmsize > 0) + { + header.sections.push_back(sect); + } + + if (!strncmp(sect.sectname, "__mod_init_func", 15)) + header.moduleInitSections.push_back(sect); + if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + header.symbolStubSections.push_back(sect); + if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + } + header.segments.push_back(segment64); + break; + case LC_ROUTINES: // map the 32bit version to 64bits + header.routines64.cmd = LC_ROUTINES_64; + header.routines64.init_address = reader.ReadUInt32(); + header.routines64.init_module = reader.ReadUInt32(); + header.routines64.reserved1 = reader.ReadUInt32(); + header.routines64.reserved2 = reader.ReadUInt32(); + header.routines64.reserved3 = reader.ReadUInt32(); + header.routines64.reserved4 = reader.ReadUInt32(); + header.routines64.reserved5 = reader.ReadUInt32(); + header.routines64.reserved6 = reader.ReadUInt32(); + header.routinesPresent = true; + break; + case LC_ROUTINES_64: + header.routines64.cmd = LC_ROUTINES_64; + header.routines64.init_address = reader.ReadUInt64(); + header.routines64.init_module = reader.ReadUInt64(); + header.routines64.reserved1 = reader.ReadUInt64(); + header.routines64.reserved2 = reader.ReadUInt64(); + header.routines64.reserved3 = reader.ReadUInt64(); + header.routines64.reserved4 = reader.ReadUInt64(); + header.routines64.reserved5 = reader.ReadUInt64(); + header.routines64.reserved6 = reader.ReadUInt64(); + header.routinesPresent = true; + break; + case LC_FUNCTION_STARTS: + header.functionStarts.funcoff = reader.ReadUInt32(); + header.functionStarts.funcsize = reader.ReadUInt32(); + header.functionStartsPresent = true; + break; + case LC_SYMTAB: + header.symtab.symoff = reader.ReadUInt32(); + header.symtab.nsyms = reader.ReadUInt32(); + header.symtab.stroff = reader.ReadUInt32(); + header.symtab.strsize = reader.ReadUInt32(); + break; + case LC_DYSYMTAB: + header.dysymtab.ilocalsym = reader.ReadUInt32(); + header.dysymtab.nlocalsym = reader.ReadUInt32(); + header.dysymtab.iextdefsym = reader.ReadUInt32(); + header.dysymtab.nextdefsym = reader.ReadUInt32(); + header.dysymtab.iundefsym = reader.ReadUInt32(); + header.dysymtab.nundefsym = reader.ReadUInt32(); + header.dysymtab.tocoff = reader.ReadUInt32(); + header.dysymtab.ntoc = reader.ReadUInt32(); + header.dysymtab.modtaboff = reader.ReadUInt32(); + header.dysymtab.nmodtab = reader.ReadUInt32(); + header.dysymtab.extrefsymoff = reader.ReadUInt32(); + header.dysymtab.nextrefsyms = reader.ReadUInt32(); + header.dysymtab.indirectsymoff = reader.ReadUInt32(); + header.dysymtab.nindirectsyms = reader.ReadUInt32(); + header.dysymtab.extreloff = reader.ReadUInt32(); + header.dysymtab.nextrel = reader.ReadUInt32(); + header.dysymtab.locreloff = reader.ReadUInt32(); + header.dysymtab.nlocrel = reader.ReadUInt32(); + header.dysymPresent = true; + break; + case LC_DYLD_CHAINED_FIXUPS: + header.chainedFixups.dataoff = reader.ReadUInt32(); + header.chainedFixups.datasize = reader.ReadUInt32(); + header.chainedFixupsPresent = true; + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + header.dyldInfo.rebase_off = reader.ReadUInt32(); + header.dyldInfo.rebase_size = reader.ReadUInt32(); + header.dyldInfo.bind_off = reader.ReadUInt32(); + header.dyldInfo.bind_size = reader.ReadUInt32(); + header.dyldInfo.weak_bind_off = reader.ReadUInt32(); + header.dyldInfo.weak_bind_size = reader.ReadUInt32(); + header.dyldInfo.lazy_bind_off = reader.ReadUInt32(); + header.dyldInfo.lazy_bind_size = reader.ReadUInt32(); + header.dyldInfo.export_off = reader.ReadUInt32(); + header.dyldInfo.export_size = reader.ReadUInt32(); + header.exportTrie.dataoff = header.dyldInfo.export_off; + header.exportTrie.datasize = header.dyldInfo.export_size; + header.exportTriePresent = true; + header.dyldInfoPresent = true; + break; + case LC_DYLD_EXPORTS_TRIE: + header.exportTrie.dataoff = reader.ReadUInt32(); + header.exportTrie.datasize = reader.ReadUInt32(); + header.exportTriePresent = true; + break; + case LC_THREAD: + case LC_UNIXTHREAD: + /*while (reader.GetOffset() < nextOffset) + { + + thread_command thread; + thread.flavor = reader.ReadUInt32(); + thread.count = reader.ReadUInt32(); + switch (m_archId) + { + case MachOx64: + m_logger->LogDebug("x86_64 Thread state\n"); + if (thread.flavor != X86_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statex64, sizeof(thread.statex64)); + header.entryPoints.push_back({thread.statex64.rip, false}); + break; + case MachOx86: + m_logger->LogDebug("x86 Thread state\n"); + if (thread.flavor != X86_THREAD_STATE32) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statex86, sizeof(thread.statex86)); + header.entryPoints.push_back({thread.statex86.eip, false}); + break; + case MachOArm: + m_logger->LogDebug("Arm Thread state\n"); + if (thread.flavor != _ARM_THREAD_STATE) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statearmv7, sizeof(thread.statearmv7)); + header.entryPoints.push_back({thread.statearmv7.r15, false}); + break; + case MachOAarch64: + case MachOAarch6432: + m_logger->LogDebug("Aarch64 Thread state\n"); + if (thread.flavor != _ARM_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + reader.Read(&thread.stateaarch64, sizeof(thread.stateaarch64)); + header.entryPoints.push_back({thread.stateaarch64.pc, false}); + break; + case MachOPPC: + m_logger->LogDebug("PPC Thread state\n"); + if (thread.flavor != PPC_THREAD_STATE) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //Read individual entries for endian reasons + header.entryPoints.push_back({reader.ReadUInt32(), false}); + (void)reader.ReadUInt32(); + (void)reader.ReadUInt32(); + //Read the rest of the structure + (void)reader.Read(&thread.stateppc.r1, sizeof(thread.stateppc) - (3 * 4)); + break; + case MachOPPC64: + m_logger->LogDebug("PPC64 Thread state\n"); + if (thread.flavor != PPC_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + header.entryPoints.push_back({reader.ReadUInt64(), false}); + (void)reader.ReadUInt64(); + (void)reader.ReadUInt64(); // Stack start + (void)reader.Read(&thread.stateppc64.r1, sizeof(thread.stateppc64) - (3 * 8)); + break; + default: + m_logger->LogError("Unknown archid: %x", m_archId); + } + + }*/ + break; + case LC_LOAD_DYLIB: + { + uint32_t offset = reader.ReadUInt32(); + if (offset < nextOffset) + { + reader.Seek(curOffset + offset); + std::string libname = reader.ReadCString(reader.GetOffset()); + header.dylibs.push_back(libname); + } + } + break; + case LC_BUILD_VERSION: + { + // m_logger->LogDebug("LC_BUILD_VERSION:"); + header.buildVersion.platform = reader.ReadUInt32(); + header.buildVersion.minos = reader.ReadUInt32(); + header.buildVersion.sdk = reader.ReadUInt32(); + header.buildVersion.ntools = reader.ReadUInt32(); + // m_logger->LogDebug("Platform: %s", BuildPlatformToString(header.buildVersion.platform).c_str()); + // m_logger->LogDebug("MinOS: %s", BuildToolVersionToString(header.buildVersion.minos).c_str()); + // m_logger->LogDebug("SDK: %s", BuildToolVersionToString(header.buildVersion.sdk).c_str()); + for (uint32_t j = 0; (i < header.buildVersion.ntools) && (j < 10); j++) + { + uint32_t tool = reader.ReadUInt32(); + uint32_t version = reader.ReadUInt32(); + header.buildToolVersions.push_back({tool, version}); + // m_logger->LogDebug("Build Tool: %s: %s", BuildToolToString(tool).c_str(), + // BuildToolVersionToString(version).c_str()); + } + break; + } + case LC_FILESET_ENTRY: + { + throw ReadException(); + } + default: + // m_logger->LogDebug("Unhandled command: %s : %" PRIu32 "\n", CommandToString(load.cmd).c_str(), + // load.cmdsize); + break; + } + if (reader.GetOffset() != nextOffset) + { + // m_logger->LogDebug("Didn't parse load command: %s fully %" PRIx64 ":%" PRIxPTR, + // CommandToString(load.cmd).c_str(), reader.GetOffset(), nextOffset); + } + reader.Seek(nextOffset); + } + + for (auto& section : header.sections) + { + char sectionName[17]; + memcpy(sectionName, section.sectname, sizeof(section.sectname)); + sectionName[16] = 0; + header.sectionNames.push_back(header.identifierPrefix + "::" + sectionName); + } + } + catch (ReadException&) + { + return {}; + } + + return header; +} + +// TODO: Support reading from .symbols file. +// TODO: Replace view with address size? +std::vector<CacheSymbol> SharedCacheMachOHeader::ReadSymbolTable(BinaryView& view, VirtualMemory& vm) const +{ + auto addressSize = view.GetAddressSize(); + // NOTE: The symbol table will exist within the link edit segment, the table offsets are relative to the file not + // the linkedit segment. + uint64_t symbolsAddress = GetLinkEditFileBase() + symtab.symoff; + uint64_t stringsAddress = GetLinkEditFileBase() + symtab.stroff; + + // TODO: This needs to be passed in as an optional argument. + // TODO: Sometimes symbol tables are shared and we have to offset into the table for a specific header. + // TODO: The "shared" symbol tables are stored in .symbols files. + int nlistStartIndex = 0; + + std::vector<CacheSymbol> symbolList; + for (uint64_t i = 0; i < symtab.nsyms; i++) + { + uint64_t entryIndex = (nlistStartIndex + i); + + nlist_64 nlist = {}; + if (addressSize == 4) + { + // 32-bit DSC + struct nlist nlist32 = {}; + vm.Read(&nlist, symbolsAddress + (entryIndex * sizeof(nlist32)), sizeof(nlist32)); + nlist.n_strx = nlist32.n_strx; + nlist.n_type = nlist32.n_type; + nlist.n_sect = nlist32.n_sect; + nlist.n_desc = nlist32.n_desc; + nlist.n_value = nlist32.n_value; + } + else + { + // 64-bit DSC + vm.Read(&nlist, symbolsAddress + (entryIndex * sizeof(nlist)), sizeof(nlist)); + } + + auto symbolAddress = nlist.n_value; + if (((nlist.n_type & N_TYPE) == N_INDR) || symbolAddress == 0) + continue; + + if (nlist.n_strx >= symtab.strsize) + { + // TODO: where logger? + LogError( + "Symbol entry at index %llu has a string offset of %u which is outside the strings buffer of size %u " + "for symbol table %x", + entryIndex, nlist.n_strx, symtab.strsize, symtab.stroff); + continue; + } + + std::string symbolName = vm.ReadCString(stringsAddress + nlist.n_strx); + if (symbolName == "<redacted>") + continue; + + std::optional<BNSymbolType> symbolType; + if ((nlist.n_type & N_TYPE) == N_SECT && nlist.n_sect > 0 && (size_t)(nlist.n_sect - 1) < sections.size()) + symbolType = DataSymbol; + else if ((nlist.n_type & N_TYPE) == N_ABS) + symbolType = DataSymbol; + else if ((nlist.n_type & N_EXT)) + symbolType = ExternalSymbol; + + if (!symbolType.has_value()) + { + // TODO: Where logger? + LogError("Symbol %s at address %llx has unknown symbol type", symbolName.c_str(), symbolAddress); + continue; + } + + std::optional<uint32_t> flags; + for (auto s : sections) + { + if (s.addr <= symbolAddress && symbolAddress < s.addr + s.size) + { + // First section to contain the address we will use its flags. + flags = s.flags; + break; + } + } + + if (symbolType != ExternalSymbol) + { + if (!flags.has_value()) + { + // TODO: where logger? + LogError("Symbol %s at address %llx is not in any section", symbolName.c_str(), symbolAddress); + continue; + } + + if ((flags.value() & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS + || (flags.value() & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) + symbolType = FunctionSymbol; + else + symbolType = DataSymbol; + } + if ((nlist.n_desc & N_ARM_THUMB_DEF) == N_ARM_THUMB_DEF) + symbolAddress++; + + CacheSymbol symbol; + symbol.address = symbolAddress; + symbol.name = std::move(symbolName); + symbol.type = symbolType.value(); + symbolList.emplace_back(symbol); + } + + return symbolList; +} + +std::optional<CacheSymbol> SharedCacheMachOHeader::AddExportTerminalSymbol( + const std::string& symbolName, const uint8_t* current, const uint8_t* end) const +{ + uint64_t symbolFlags = readValidULEB128(current, end); + if (symbolFlags & EXPORT_SYMBOL_FLAGS_REEXPORT) + return std::nullopt; + + uint64_t imageOffset = readValidULEB128(current, end); + uint64_t symbolAddress = textBase + imageOffset; + if (symbolName.empty() || symbolAddress == 0) + return std::nullopt; + + // Tries to get the symbol type based off the section containing it. + auto sectionSymbolType = [&]() -> BNSymbolType { + uint32_t sectionFlags = 0; + for (const auto& section : sections) + { + if (symbolAddress >= section.addr && symbolAddress < section.addr + section.size) + { + // Take the flags from the first containing section. + sectionFlags = section.flags; + break; + } + } + + // TODO: Is this enough to determine a function symbol? + // TODO: Might be the cause of https://github.com/Vector35/binaryninja-api/issues/6526 + // Check the sections flags to see if we actually have a function symbol instead. + if (sectionFlags & S_ATTR_PURE_INSTRUCTIONS || sectionFlags & S_ATTR_SOME_INSTRUCTIONS) + return FunctionSymbol; + + // By default, just return data symbol. + return DataSymbol; + }; + + switch (symbolFlags & EXPORT_SYMBOL_FLAGS_KIND_MASK) + { + case EXPORT_SYMBOL_FLAGS_KIND_REGULAR: + case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: + return CacheSymbol(sectionSymbolType(), symbolAddress, symbolName); + case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: + return CacheSymbol(DataSymbol, symbolAddress, symbolName); + default: + LogWarn("Unhandled export symbol kind: %llx", symbolFlags & EXPORT_SYMBOL_FLAGS_KIND_MASK); + return std::nullopt; + } +} + +// TODO: This is like 90% of the runtime. +bool SharedCacheMachOHeader::ProcessLinkEditTrie(std::vector<CacheSymbol>& symbols, const std::string& currentText, + const uint8_t* begin, const uint8_t* current, const uint8_t* end) const +{ + if (current >= end) + return false; + + uint64_t terminalSize = readValidULEB128(current, end); + const uint8_t* child = current + terminalSize; + + // The terminal is an export symbol. + if (terminalSize != 0) + { + // Add the export symbol is applicable. + auto symbol = AddExportTerminalSymbol(currentText, current, end); + if (symbol.has_value()) + symbols.push_back(*symbol); + } + + // TODO: Make this look better + current = child; + uint8_t childCount = *current++; + std::string childText = currentText; + for (uint8_t i = 0; i < childCount; ++i) + { + if (current >= end) + return false; + const auto it = std::find(current, end, 0); + childText.append(current, it); + current = it + 1; + if (current >= end) + return false; + const auto next = readValidULEB128(current, end); + if (next == 0) + return false; + if (!ProcessLinkEditTrie(symbols, childText, begin, begin + next, end)) + return false; + childText.resize(currentText.size()); + } + + return true; +} + +std::vector<CacheSymbol> SharedCacheMachOHeader::ReadExportSymbolTrie(VirtualMemory& vm) const +{ + if (exportTrie.datasize == 0) + return {}; + + uint64_t exportTrieAddress = GetLinkEditFileBase() + exportTrie.dataoff; + std::vector<CacheSymbol> symbols = {}; + try + { + auto [begin, end] = vm.ReadSpan(exportTrieAddress, exportTrie.datasize); + ProcessLinkEditTrie(symbols, "", begin, begin, end); + } + catch (std::exception& e) + { + BNLogError("Failed to read Export Trie: %s", e.what()); + } + return symbols; +} diff --git a/view/sharedcache/core/MachO.h b/view/sharedcache/core/MachO.h new file mode 100644 index 00000000..37b82915 --- /dev/null +++ b/view/sharedcache/core/MachO.h @@ -0,0 +1,76 @@ +#pragma once + +#include "VirtualMemory.h" + +// TODO: Including this adds a bunch of binary ninja specific stuff :ugh: +#include "view/macho/machoview.h" + +struct CacheSymbol; + +struct SharedCacheMachOHeader +{ + uint64_t textBase = 0; + uint64_t loadCommandOffset = 0; + BinaryNinja::mach_header_64 ident; + // NOTE: This should never be empty. + std::string identifierPrefix; + std::string installName; + + std::vector<std::pair<uint64_t, bool>> entryPoints; + std::vector<uint64_t> m_entryPoints; // list of entrypoints + + BinaryNinja::symtab_command symtab; + BinaryNinja::dysymtab_command dysymtab; + BinaryNinja::dyld_info_command dyldInfo; + BinaryNinja::routines_command_64 routines64; + BinaryNinja::function_starts_command functionStarts; + std::vector<BinaryNinja::section_64> moduleInitSections; + BinaryNinja::linkedit_data_command exportTrie; + BinaryNinja::linkedit_data_command chainedFixups {}; + + uint64_t relocationBase; + // Section and program headers, internally use 64-bit form as it is a superset of 32-bit + std::vector<BinaryNinja::segment_command_64> segments; // only three types of sections __TEXT, __DATA, __IMPORT + BinaryNinja::segment_command_64 linkeditSegment; + std::vector<BinaryNinja::section_64> sections; + std::vector<std::string> sectionNames; + + std::vector<BinaryNinja::section_64> symbolStubSections; + std::vector<BinaryNinja::section_64> symbolPointerSections; + + std::vector<std::string> dylibs; + + BinaryNinja::build_version_command buildVersion; + std::vector<BinaryNinja::build_tool_version> buildToolVersions; + + std::string exportTriePath; + + bool linkeditPresent = false; + bool dysymPresent = false; + bool dyldInfoPresent = false; + bool exportTriePresent = false; + bool chainedFixupsPresent = false; + bool routinesPresent = false; + bool functionStartsPresent = false; + bool relocatable = false; + + // The base address of the link edit file. + // Use this if you want to read offsets relative to the file containing the link edit segment. + uint64_t GetLinkEditFileBase() const { return linkeditSegment.vmaddr - linkeditSegment.fileoff; }; + + static std::optional<SharedCacheMachOHeader> ParseHeaderForAddress( + std::shared_ptr<VirtualMemory> vm, uint64_t address, const std::string& imagePath); + + // TODO: Replace view with address size? + std::vector<CacheSymbol> ReadSymbolTable(BinaryNinja::BinaryView& view, VirtualMemory& vm) const; + + std::optional<CacheSymbol> AddExportTerminalSymbol( + const std::string& symbolName, const uint8_t* current, const uint8_t* end) const; + + bool ProcessLinkEditTrie(std::vector<CacheSymbol>& symbols, const std::string& currentText, const uint8_t* begin, + const uint8_t* current, const uint8_t* end) const; + + std::vector<CacheSymbol> ReadExportSymbolTrie(VirtualMemory& vm) const; + + std::vector<uint64_t> ReadFunctionTable(VirtualMemory& vm) const; +}; diff --git a/view/sharedcache/core/MachOProcessor.cpp b/view/sharedcache/core/MachOProcessor.cpp new file mode 100644 index 00000000..be2e9007 --- /dev/null +++ b/view/sharedcache/core/MachOProcessor.cpp @@ -0,0 +1,320 @@ +#include "MachOProcessor.h" +#include "SharedCache.h" + +using namespace BinaryNinja; + +SharedCacheMachOProcessor::SharedCacheMachOProcessor(Ref<BinaryView> view, std::shared_ptr<VirtualMemory> vm) +{ + m_view = view; + m_logger = new Logger("SharedCacheMachOProcessor", view->GetFile()->GetSessionId()); + m_vm = std::move(vm); + + // Adjust processor settings. + if (Ref<Settings> settings = m_view->GetLoadSettings(VIEW_NAME)) + { + if (settings->Contains("loader.dsc.processFunctionStarts")) + m_applyFunctions = settings->Get<bool>("loader.dsc.processFunctionStarts", m_view); + } +} + +void SharedCacheMachOProcessor::ApplyHeader(SharedCacheMachOHeader& header) +{ + // Add a section for the header itself. + std::string headerSection = fmt::format("{}::__macho_header", header.identifierPrefix); + // TODO: Support mach_header (non 64bit) + uint64_t headerSectionSize = sizeof(mach_header_64) + header.ident.sizeofcmds; + m_view->AddUserSection(headerSection, header.textBase, headerSectionSize, ReadOnlyDataSectionSemantics); + + ApplyHeaderSections(header); + ApplyHeaderDataVariables(header); + + if (header.linkeditPresent && m_vm->IsAddressMapped(header.linkeditSegment.vmaddr)) + { + if (m_applyFunctions && header.functionStartsPresent) + { + auto targetPlatform = m_view->GetDefaultPlatform(); + auto functions = header.ReadFunctionTable(*m_vm); + for (const auto& func : functions) + { + // TODO: Check to make sure the func exists in a loaded region? + // TODO: ^ this check existed prior so we should add it back. + m_view->AddFunctionForAnalysis(targetPlatform, func); + } + } + + auto typeLib = m_view->GetTypeLibrary(header.installName); + m_view->BeginBulkModifySymbols(); + + // TODO: Why does this need to only happen in linkeditSegment? + // Apply symbols from symbol table. + if (header.symtab.symoff != 0) + { + // Mach-O View symtab processing with + // a ton of stuff cut out so it can work + // NOTE: This table is read relative to the link edit segment file base. + // TODO: Remove this and use the m_symbols in the cache? + const auto symbols = header.ReadSymbolTable(*m_view, *m_vm); + for (const auto& sym : symbols) + ApplySymbol(m_view, typeLib, sym.ToBNSymbol()); + } + + // Apply symbols from export trie. + if (header.exportTriePresent) + { + // NOTE: This table is read relative to the link edit segment file base. + // TODO: Remove this and use the m_symbols in the cache? + const auto exportSymbols = header.ReadExportSymbolTrie(*m_vm); + for (const auto& sym : exportSymbols) + ApplySymbol(m_view, typeLib, sym.ToBNSymbol()); + } + m_view->EndBulkModifySymbols(); + } +} + +uint64_t SharedCacheMachOProcessor::ApplyHeaderSections(SharedCacheMachOHeader& header) +{ + auto initSection = [&](const section_64& section, const std::string& sectionName) { + if (!section.size) + return false; + + std::string type; + BNSectionSemantics semantics = DefaultSectionSemantics; + switch (section.flags & 0xff) + { + case S_REGULAR: + if (section.flags & S_ATTR_PURE_INSTRUCTIONS) + { + type = "PURE_CODE"; + semantics = ReadOnlyCodeSectionSemantics; + } + else if (section.flags & S_ATTR_SOME_INSTRUCTIONS) + { + type = "CODE"; + semantics = ReadOnlyCodeSectionSemantics; + } + else + { + type = "REGULAR"; + } + break; + case S_ZEROFILL: + type = "ZEROFILL"; + semantics = ReadWriteDataSectionSemantics; + break; + case S_CSTRING_LITERALS: + type = "CSTRING_LITERALS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_4BYTE_LITERALS: + type = "4BYTE_LITERALS"; + break; + case S_8BYTE_LITERALS: + type = "8BYTE_LITERALS"; + break; + case S_LITERAL_POINTERS: + type = "LITERAL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_NON_LAZY_SYMBOL_POINTERS: + type = "NON_LAZY_SYMBOL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_LAZY_SYMBOL_POINTERS: + type = "LAZY_SYMBOL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_SYMBOL_STUBS: + type = "SYMBOL_STUBS"; + semantics = ReadOnlyCodeSectionSemantics; + break; + case S_MOD_INIT_FUNC_POINTERS: + type = "MOD_INIT_FUNC_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_MOD_TERM_FUNC_POINTERS: + type = "MOD_TERM_FUNC_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_COALESCED: + type = "COALESCED"; + break; + case S_GB_ZEROFILL: + type = "GB_ZEROFILL"; + semantics = ReadWriteDataSectionSemantics; + break; + case S_INTERPOSING: + type = "INTERPOSING"; + break; + case S_16BYTE_LITERALS: + type = "16BYTE_LITERALS"; + break; + case S_DTRACE_DOF: + type = "DTRACE_DOF"; + break; + case S_LAZY_DYLIB_SYMBOL_POINTERS: + type = "LAZY_DYLIB_SYMBOL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_THREAD_LOCAL_REGULAR: + type = "THREAD_LOCAL_REGULAR"; + break; + case S_THREAD_LOCAL_ZEROFILL: + type = "THREAD_LOCAL_ZEROFILL"; + break; + case S_THREAD_LOCAL_VARIABLES: + type = "THREAD_LOCAL_VARIABLES"; + break; + case S_THREAD_LOCAL_VARIABLE_POINTERS: + type = "THREAD_LOCAL_VARIABLE_POINTERS"; + break; + case S_THREAD_LOCAL_INIT_FUNCTION_POINTERS: + type = "THREAD_LOCAL_INIT_FUNCTION_POINTERS"; + break; + default: + type = "UNKNOWN"; + break; + } + + if (strncmp(section.sectname, "__text", sizeof(section.sectname)) == 0) + semantics = ReadOnlyCodeSectionSemantics; + if (strncmp(section.sectname, "__const", sizeof(section.sectname)) == 0) + semantics = ReadOnlyDataSectionSemantics; + if (strncmp(section.sectname, "__data", sizeof(section.sectname)) == 0) + semantics = ReadWriteDataSectionSemantics; + if (strncmp(section.segname, "__DATA_CONST", sizeof(section.segname)) == 0) + semantics = ReadOnlyDataSectionSemantics; + if (strncmp(section.segname, "__auth_got", sizeof(section.segname)) == 0) + semantics = ReadOnlyDataSectionSemantics; + + m_view->AddUserSection(sectionName, section.addr, section.size, semantics, type, section.align); + + return true; + }; + + uint64_t addedSections = 0; + for (size_t i = 0; i < header.sections.size() && i < header.sectionNames.size(); i++) + { + if (initSection(header.sections[i], header.sectionNames[i])) + addedSections++; + } + return addedSections; +} + +void SharedCacheMachOProcessor::ApplyHeaderDataVariables(SharedCacheMachOHeader& header) +{ + // TODO: By using a binary reader we assume the sections have all been mapped. + // TODO: Maybe we should just use the virtual memory reader... + // TODO: We can define symbols and data variables even if there is no backing region FWIW + BinaryReader reader(m_view); + // TODO: Do we support non 64 bit header? + reader.Seek(header.textBase + sizeof(mach_header_64)); + + m_view->DefineDataVariable(header.textBase, Type::NamedType(m_view, QualifiedName("mach_header_64"))); + m_view->DefineAutoSymbol( + new Symbol(DataSymbol, "__macho_header::" + header.identifierPrefix, header.textBase, LocalBinding)); + + auto applyLoadCommand = [&](uint64_t cmdAddr, const load_command& load) { + switch (load.cmd) + { + case LC_SEGMENT: + { + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("segment_command"))); + reader.SeekRelative(5 * 8); + size_t numSections = reader.Read32(); + reader.SeekRelative(4); + for (size_t j = 0; j < numSections; j++) + { + m_view->DefineDataVariable(reader.GetOffset(), Type::NamedType(m_view, QualifiedName("section"))); + auto sectionSymName = + fmt::format("__macho_section::{}_[{}]", header.identifierPrefix, std::to_string(j)); + auto sectionSym = new Symbol(DataSymbol, sectionSymName, reader.GetOffset(), LocalBinding); + m_view->DefineAutoSymbol(sectionSym); + reader.SeekRelative((8 * 8) + 4); + } + break; + } + case LC_SEGMENT_64: + { + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("segment_command_64"))); + reader.SeekRelative(7 * 8); + size_t numSections = reader.Read32(); + reader.SeekRelative(4); + for (size_t j = 0; j < numSections; j++) + { + m_view->DefineDataVariable(reader.GetOffset(), Type::NamedType(m_view, QualifiedName("section_64"))); + auto sectionSymName = + fmt::format("__macho_section_64::{}_[{}]", header.identifierPrefix, std::to_string(j)); + auto sectionSym = new Symbol(DataSymbol, sectionSymName, reader.GetOffset(), LocalBinding); + m_view->DefineAutoSymbol(sectionSym); + reader.SeekRelative(10 * 8); + } + break; + } + case LC_SYMTAB: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("symtab"))); + break; + case LC_DYSYMTAB: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("dysymtab"))); + break; + case LC_UUID: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("uuid"))); + break; + case LC_ID_DYLIB: + case LC_LOAD_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("dylib_command"))); + if (load.cmdsize - 24 <= 150) + m_view->DefineDataVariable( + cmdAddr + 24, Type::ArrayType(Type::IntegerType(1, true), load.cmdsize - 24)); + break; + case LC_CODE_SIGNATURE: + case LC_SEGMENT_SPLIT_INFO: + case LC_FUNCTION_STARTS: + case LC_DATA_IN_CODE: + case LC_DYLIB_CODE_SIGN_DRS: + case LC_DYLD_EXPORTS_TRIE: + case LC_DYLD_CHAINED_FIXUPS: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("linkedit_data"))); + break; + case LC_ENCRYPTION_INFO: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("encryption_info"))); + break; + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("version_min"))); + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("dyld_info"))); + break; + default: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("load_command"))); + break; + } + }; + + try + { + for (size_t i = 0; i < header.ident.ncmds; i++) + { + load_command load {}; + uint64_t curOffset = reader.GetOffset(); + load.cmd = reader.Read32(); + load.cmdsize = reader.Read32(); + + applyLoadCommand(curOffset, load); + m_view->DefineAutoSymbol(new Symbol(DataSymbol, + "__macho_load_command::" + header.identifierPrefix + "_[" + std::to_string(i) + "]", curOffset, + LocalBinding)); + + uint64_t nextOffset = curOffset + load.cmdsize; + reader.Seek(nextOffset); + } + } + catch (ReadException&) + { + m_logger->LogError("Error when applying Mach-O header types at %llx", header.textBase); + } +} diff --git a/view/sharedcache/core/MachOProcessor.h b/view/sharedcache/core/MachOProcessor.h new file mode 100644 index 00000000..0564a831 --- /dev/null +++ b/view/sharedcache/core/MachOProcessor.h @@ -0,0 +1,23 @@ +#pragma once +#include "MachO.h" + +// Process `SharedCacheMachOHeader`. +class SharedCacheMachOProcessor +{ + BinaryNinja::Ref<BinaryNinja::BinaryView> m_view; + BinaryNinja::Ref<BinaryNinja::Logger> m_logger; + std::shared_ptr<VirtualMemory> m_vm; + + bool m_applyFunctions = true; + +public: + explicit SharedCacheMachOProcessor( + BinaryNinja::Ref<BinaryNinja::BinaryView> view, std::shared_ptr<VirtualMemory> vm); + + // Initialize header information such as sections and symbols. + void ApplyHeader(SharedCacheMachOHeader& header); + + uint64_t ApplyHeaderSections(SharedCacheMachOHeader& header); + + void ApplyHeaderDataVariables(SharedCacheMachOHeader& header); +}; diff --git a/view/sharedcache/core/MappedFile.cpp b/view/sharedcache/core/MappedFile.cpp new file mode 100644 index 00000000..e0b805bc --- /dev/null +++ b/view/sharedcache/core/MappedFile.cpp @@ -0,0 +1,193 @@ +#ifdef _MSC_VER + #include <windows.h> +#else + #include <sys/mman.h> + #include <fcntl.h> + #include <cstdlib> + #include <sys/resource.h> +#endif + +#include "MappedFile.h" +#include "binaryninjaapi.h" + +#ifndef _MSC_VER +uint64_t AdjustFileDescriptorLimit() +{ + // The soft file descriptor limit on Linux and Mac is a lot lower than + // on Windows (1024 for Linux, 256 for Mac). Recent iOS shared caches + // have 60+ files which may not leave much headroom if a user opens + // more than one at a time. Attempt to increase the file descriptor + // limit to 1024, and limit ourselves to caching half of them as a + // memory vs performance trade-off (closing and re-opening a file + // requires parsing and applying the slide information again). + uint64_t maxFPLimit = 1024; + + // check for BN_SHAREDCACHE_FP_MAX + // if it exists, set maxFPLimit to that value + if (auto env = getenv("BN_SHAREDCACHE_FP_MAX"); env) + { + // FIXME behav on 0 here is unintuitive, '0123' will interpret as octal and be 83 according to manpage. meh. + maxFPLimit = strtoull(env, nullptr, 0); + if (maxFPLimit < 10) + { + BinaryNinja::LogWarn( + "BN_SHAREDCACHE_FP_MAX set to below 10. A value of at least 10 is recommended for performant analysis " + "on SharedCache Binaries."); + } + if (maxFPLimit == 0) + { + BinaryNinja::LogError("BN_SHAREDCACHE_FP_MAX set to 0. Adjusting to 1"); + maxFPLimit = 1; + } + } + + rlimit rlim {}; + getrlimit(RLIMIT_NOFILE, &rlim); + uint64_t previousLimit = rlim.rlim_cur; + uint64_t targetLimit = std::min(maxFPLimit, rlim.rlim_max); + if (rlim.rlim_cur < targetLimit) + { + rlim.rlim_cur = targetLimit; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) + { + perror("setrlimit(RLIMIT_NOFILE)"); + rlim.rlim_cur = previousLimit; + } + } + + maxFPLimit = rlim.rlim_cur / 2; + return maxFPLimit; +} + +MappedFile::~MappedFile() +{ + Unmap(); + if (fd) + fclose(fd); +} + +std::optional<MappedFile> MappedFile::OpenFile(const std::string& path) +{ + MappedFile file; + file._mmap = nullptr; + file.fd = fopen(path.c_str(), "r"); + if (file.fd == nullptr) + return std::nullopt; + + fseek(file.fd, 0L, SEEK_END); + file.len = ftell(file.fd); + fseek(file.fd, 0L, SEEK_SET); + + return file; +} + +MapStatus MappedFile::Map() +{ + if (_mmap) + return MapStatus::Success; + + void* result = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(fd), 0u); + if (result == MAP_FAILED) + { + BinaryNinja::LogError("mmap failed: %s", strerror(errno)); // Use errno to log the reason + return MapStatus::Error; + } + _mmap = static_cast<uint8_t*>(result); + + return MapStatus::Success; +} + +MapStatus MappedFile::Unmap() +{ + if (_mmap) + { + munmap(_mmap, len); + _mmap = nullptr; + } + return MapStatus::Success; +} +#else +uint64_t AdjustFileDescriptorLimit() +{ + return 0x1000000; +} + +MappedFile::~MappedFile() +{ + Unmap(); + if (hFile) + CloseHandle(hFile); +} + +std::optional<MappedFile> MappedFile::OpenFile(const std::string& path) +{ + MappedFile file; + file._mmap = nullptr; + file.hFile = CreateFile(path.c_str(), // file name + GENERIC_READ, // desired access (read-only) + FILE_SHARE_READ, // share mode + NULL, // security attributes + OPEN_EXISTING, // creation disposition + FILE_ATTRIBUTE_NORMAL, // flags and attributes + NULL); // template file + + if (file.hFile == INVALID_HANDLE_VALUE) + return std::nullopt; + + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(file.hFile, &fileSize)) + { + CloseHandle(file.hFile); + return std::nullopt; + } + file.len = static_cast<size_t>(fileSize.QuadPart); + + return file; +} + +MapStatus MappedFile::Map() +{ + if (_mmap) + return MapStatus::Success; + + HANDLE hMapping = CreateFileMapping(hFile, // file handle + NULL, // security attributes + PAGE_WRITECOPY, // protection + 0, // maximum size (high-order DWORD) + 0, // maximum size (low-order DWORD) + NULL); // name of the mapping object + + if (hMapping == NULL) + { + CloseHandle(hFile); + return MapStatus::Error; + } + + _mmap = static_cast<uint8_t*>(MapViewOfFile(hMapping, // handle to the file mapping object + FILE_MAP_COPY, // desired access + 0, // file offset (high-order DWORD) + 0, // file offset (low-order DWORD) + 0)); // number of bytes to map (0 = entire file) + + if (_mmap == nullptr) + { + CloseHandle(hMapping); + CloseHandle(hFile); + return MapStatus::Error; + } + + CloseHandle(hMapping); + CloseHandle(hFile); + + return MapStatus::Success; +} + +MapStatus MappedFile::Unmap() +{ + if (_mmap) + { + UnmapViewOfFile(_mmap); + } + return MapStatus::Success; +} +#endif diff --git a/view/sharedcache/core/MappedFile.h b/view/sharedcache/core/MappedFile.h new file mode 100644 index 00000000..ced79f52 --- /dev/null +++ b/view/sharedcache/core/MappedFile.h @@ -0,0 +1,86 @@ +#pragma once + +#include <string> +#include <cstdint> +#include <cstdio> +#include <optional> + +// Call this when initializing the plugin so that the process file descriptor limit is raised. +uint64_t AdjustFileDescriptorLimit(); + +enum MapStatus : int +{ + Success, + // TODO: Split this error out into more contextual errors. + Error, +}; + +struct MappedFile +{ + uint8_t* _mmap = nullptr; + size_t len = 0; + +#ifdef _MSC_VER + HANDLE hFile = INVALID_HANDLE_VALUE; +#else + FILE* fd = nullptr; +#endif + + MappedFile() = default; + + ~MappedFile(); + + MappedFile(const MappedFile&) = delete; + + MappedFile& operator=(const MappedFile&) = delete; + + MappedFile(MappedFile&& other) noexcept : _mmap(other._mmap), len(other.len) + { +#ifdef _MSC_VER + hFile = other.hFile; + // Don't close the hFile in the move. + other.hFile = nullptr; +#else + fd = other.fd; + // Don't close the fd in the move. + other.fd = nullptr; +#endif + other._mmap = nullptr; + } + + // I hate C++ + MappedFile& operator=(MappedFile&& other) noexcept + { + if (this != &other) + { + Unmap(); +#ifdef _MSC_VER + if (hFile != nullptr) + { + CloseHandle(hFile); + } + hFile = other.hFile; + // Don't close the hFile in the move. + other.hFile = nullptr; +#else + if (fd != nullptr) + { + fclose(fd); + } + fd = other.fd; + // Don't close the fd in the move. + other.fd = nullptr; +#endif + len = other.len; + _mmap = other._mmap; + other._mmap = nullptr; + } + return *this; + } + + static std::optional<MappedFile> OpenFile(const std::string& path); + + MapStatus Map(); + + MapStatus Unmap(); +}; diff --git a/view/sharedcache/core/MappedFileAccessor.cpp b/view/sharedcache/core/MappedFileAccessor.cpp new file mode 100644 index 00000000..ddd6ce9b --- /dev/null +++ b/view/sharedcache/core/MappedFileAccessor.cpp @@ -0,0 +1,101 @@ +#include "MappedFileAccessor.h" + + +std::shared_ptr<MappedFileAccessor> MappedFileAccessor::Open(const std::string& filePath) +{ + auto file = MappedFile::OpenFile(filePath); + if (!file.has_value()) + return nullptr; + if (file->Map() != MapStatus::Success) + return nullptr; + return std::make_shared<MappedFileAccessor>(std::move(*file)); +} + +// TODO: Will obviously not work on 32bit binaries, need to make WritePointer64 and 32 equiv. +void MappedFileAccessor::WritePointer(size_t address, size_t pointer) +{ + m_dirty = true; + *reinterpret_cast<size_t*>(&m_file._mmap[address]) = pointer; +} + +std::string MappedFileAccessor::ReadNullTermString(size_t address, size_t maxLength) const +{ + if (address > Length()) + return ""; + auto start = m_file._mmap + address; + auto endLen = (maxLength > 0) ? maxLength : m_file.len; + auto end = start + endLen; + auto nul = std::find(start, end, 0); + return {start, nul}; +} + +uint8_t MappedFileAccessor::ReadUInt8(size_t address) +{ + return Read<uint8_t>(address); +} + +int8_t MappedFileAccessor::ReadInt8(size_t address) +{ + return Read<int8_t>(address); +} + +uint16_t MappedFileAccessor::ReadUInt16(size_t address) +{ + return Read<uint16_t>(address); +} + +int16_t MappedFileAccessor::ReadInt16(size_t address) +{ + return Read<int16_t>(address); +} + + +uint32_t MappedFileAccessor::ReadUInt32(size_t address) +{ + return Read<uint32_t>(address); +} + +int32_t MappedFileAccessor::ReadInt32(size_t address) +{ + return Read<int32_t>(address); +} + +uint64_t MappedFileAccessor::ReadUInt64(size_t address) +{ + return Read<uint64_t>(address); +} + +int64_t MappedFileAccessor::ReadInt64(size_t address) +{ + return Read<int64_t>(address); +} + +BinaryNinja::DataBuffer MappedFileAccessor::ReadBuffer(size_t addr, size_t length) +{ + if (addr + length > Length()) + throw UnmappedAccessException(addr + length, Length()); + return {&m_file._mmap[addr], length}; +} + +std::pair<const uint8_t*, const uint8_t*> MappedFileAccessor::ReadSpan(size_t addr, size_t length) +{ + if (addr + length > Length()) + throw UnmappedAccessException(addr + length, Length()); + const uint8_t* data = &m_file._mmap[addr]; + return {data, data + length}; +} + +void MappedFileAccessor::Read(void* dest, size_t addr, size_t length) const +{ + if (addr + length > Length()) + throw UnmappedAccessException(addr + length, Length()); + memcpy(dest, &m_file._mmap[addr], length); +} + +template <typename T> +T MappedFileAccessor::Read(size_t address) +{ + T result; + Read(&result, address, sizeof(T)); + return result; +} diff --git a/view/sharedcache/core/MappedFileAccessor.h b/view/sharedcache/core/MappedFileAccessor.h new file mode 100644 index 00000000..7b01cb0d --- /dev/null +++ b/view/sharedcache/core/MappedFileAccessor.h @@ -0,0 +1,103 @@ +#pragma once + +#include "binaryninjaapi.h" +#include "MappedFile.h" + +#include <cstdint> +#include <list> + +class UnmappedAccessException : public std::exception +{ + uint64_t m_address; + uint64_t m_fileLen; + +public: + explicit UnmappedAccessException(uint64_t address, uint64_t fileLength) : m_address(address), m_fileLen(fileLength) + {} + + virtual const char* what() const throw() + { + thread_local std::string message; + message = + fmt::format("Tried to access unmapped address {0:x} for file with length of {0:x}", m_address, m_fileLen); + return message.c_str(); + } +}; + +// std::enable_shared_from_this allows weak pointers to "revive" the shared pointer in the FileAccessorCache. +class MappedFileAccessor : public std::enable_shared_from_this<MappedFileAccessor> +{ + MappedFile m_file; + bool m_dirty = false; + +public: + explicit MappedFileAccessor(MappedFile file) : m_file(std::move(file)) {} + + ~MappedFileAccessor() = default; + + MappedFileAccessor(const MappedFileAccessor&) = delete; + + MappedFileAccessor& operator=(const MappedFileAccessor&) = delete; + + MappedFileAccessor(MappedFileAccessor&&) noexcept = default; + + MappedFileAccessor& operator=(MappedFileAccessor&&) noexcept = default; + + static std::shared_ptr<MappedFileAccessor> Open(const std::string& filePath); + + size_t Length() const { return m_file.len; }; + + void* Data() const { return m_file._mmap; }; + + bool IsDirty() const { return m_dirty; } + + /** + * Writes to files are implemented for performance reasons and should be treated with utmost care + * + * They _MAY_ disappear as _soon_ as you release the lock on the file. + * They may also NOT disappear for the lifetime of the application. + * + * The former is more likely to occur when concurrent DSC processing is happening. The latter is the typical + * scenario. + * + * This is used explicitly for slide information in a locked scope and _NOTHING_ else. It should probably not be + * used for anything else. + * + * \param address The address to write the pointer to + * \param pointer The pointer to be written + */ + void WritePointer(size_t address, size_t pointer); + + std::string ReadNullTermString(size_t address, size_t maxLength = -1) const; + + uint8_t ReadUInt8(size_t address); + + int8_t ReadInt8(size_t address); + + uint16_t ReadUInt16(size_t address); + + int16_t ReadInt16(size_t address); + + uint32_t ReadUInt32(size_t address); + + int32_t ReadInt32(size_t address); + + uint64_t ReadUInt64(size_t address); + + int64_t ReadInt64(size_t address); + + BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length); + + // Returns a range of pointers within the mapped memory region corresponding to + // {addr, length}. + // WARNING: The pointers returned by this method is only valid for the lifetime + // of this file accessor. + // TODO: This should use std::span<const uint8_t> once the minimum supported + // C++ version supports it. + std::pair<const uint8_t*, const uint8_t*> ReadSpan(size_t addr, size_t length); + + void Read(void* dest, size_t addr, size_t length) const; + + template <typename T> + T Read(size_t address); +}; diff --git a/view/sharedcache/core/MetadataSerializable.cpp b/view/sharedcache/core/MetadataSerializable.cpp deleted file mode 100644 index eaf82c8b..00000000 --- a/view/sharedcache/core/MetadataSerializable.cpp +++ /dev/null @@ -1,552 +0,0 @@ -#include "MetadataSerializable.hpp" - -namespace SharedCacheCore { - -void Serialize(SerializationContext& context, std::string_view str) { - context.writer.String(str.data(), str.length()); -} - -void Serialize(SerializationContext& context, const char* value) { - Serialize(context, std::string_view(value)); -} - -void Serialize(SerializationContext& context, bool b) -{ - context.writer.Bool(b); -} - -void Serialize(SerializationContext& context, int8_t value) { - context.writer.Int(value); -} - -void Serialize(SerializationContext& context, uint8_t value) { - context.writer.Uint(value); -} - -void Serialize(SerializationContext& context, int16_t value) { - context.writer.Int(value); -} - -void Serialize(SerializationContext& context, uint16_t value) { - context.writer.Uint(value); -} - -void Serialize(SerializationContext& context, int32_t value) { - context.writer.Int(value); -} - -void Serialize(SerializationContext& context, uint32_t value) { - context.writer.Uint(value); -} - -void Serialize(SerializationContext& context, int64_t value) { - context.writer.Int64(value); -} - -void Serialize(SerializationContext& context, uint64_t value) { - context.writer.Uint64(value); -} - -void Deserialize(DeserializationContext& context, std::string_view name, bool& b) { - b = context.doc[name.data()].GetBool(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, uint8_t& b) -{ - b = static_cast<uint8_t>(context.doc[name.data()].GetUint64()); -} - -void Deserialize(DeserializationContext& context, std::string_view name, uint16_t& b) -{ - b = static_cast<uint16_t>(context.doc[name.data()].GetUint64()); -} - -void Deserialize(DeserializationContext& context, std::string_view name, uint32_t& b) -{ - b = static_cast<uint32_t>(context.doc[name.data()].GetUint64()); -} - -void Deserialize(DeserializationContext& context, std::string_view name, uint64_t& b) -{ - b = context.doc[name.data()].GetUint64(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, int8_t& b) -{ - b = context.doc[name.data()].GetInt64(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, int16_t& b) -{ - b = context.doc[name.data()].GetInt64(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, int32_t& b) -{ - b = context.doc[name.data()].GetInt(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, int64_t& b) -{ - b = context.doc[name.data()].GetInt64(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::string& b) -{ - b = context.doc[name.data()].GetString(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::map<uint64_t, std::string>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetString(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, std::string>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetString(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, uint64_t>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetUint64(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::string key = i.GetArray()[0].GetString(); - std::unordered_map<uint64_t, uint64_t> memArray; - for (auto& member : i.GetArray()[1].GetArray()) - { - memArray[member.GetArray()[0].GetUint64()] = member.GetArray()[1].GetUint64(); - } - b[key] = memArray; - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::string>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - b[i.GetArray()[0].GetString()] = i.GetArray()[1].GetString(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::string>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - b.emplace_back(i.GetString()); -} - -// Note: This flattens the pair into [first, second.first, second.second] with no nested arrays. -void Serialize(SerializationContext& context, const std::pair<uint64_t, std::pair<uint64_t, uint64_t>>& value) -{ - context.writer.StartArray(); - Serialize(context, value.first); - Serialize(context, value.second.first); - Serialize(context, value.second.second); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::pair<uint64_t, std::pair<uint64_t, uint64_t>> j; - j.first = i.GetArray()[0].GetUint64(); - j.second.first = i.GetArray()[1].GetUint64(); - j.second.second = i.GetArray()[2].GetUint64(); - b.push_back(j); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, bool>>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::pair<uint64_t, bool> j; - j.first = i.GetArray()[0].GetUint64(); - j.second = i.GetArray()[1].GetBool(); - b.push_back(j); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<uint64_t>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - b.push_back(i.GetUint64()); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, uint64_t>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - b[i.GetArray()[0].GetString()] = i.GetArray()[1].GetUint64(); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>>>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>> j; - j.first = i.GetArray()[0].GetUint64(); - for (auto& k : i.GetArray()[1].GetArray()) - { - j.second.push_back({k.GetArray()[0].GetUint64(), k.GetArray()[1].GetString()}); - } - b.push_back(j); - } -} - -void Serialize(SerializationContext& context, const mach_header_64& value) { - context.writer.StartArray(); - Serialize(context, value.magic); - // cputype and cpusubtype are signed but were serialized as unsigned in - // v4.2 (metadata version 2). We continue serializing them as unsigned - // so we don't need to bump the metadata version. - Serialize(context, static_cast<uint32_t>(value.cputype)); - Serialize(context, static_cast<uint32_t>(value.cpusubtype)); - Serialize(context, value.filetype); - Serialize(context, value.ncmds); - Serialize(context, value.sizeofcmds); - Serialize(context, value.flags); - Serialize(context, value.reserved); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, mach_header_64& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.magic = bArr[0].GetUint(); - b.cputype = bArr[1].GetUint(); - b.cpusubtype = bArr[2].GetUint(); - b.filetype = bArr[3].GetUint(); - b.ncmds = bArr[4].GetUint(); - b.sizeofcmds = bArr[5].GetUint(); - b.flags = bArr[6].GetUint(); - b.reserved = bArr[7].GetUint(); -} - -void Serialize(SerializationContext& context, const symtab_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.symoff); - Serialize(context, value.nsyms); - Serialize(context, value.stroff); - Serialize(context, value.strsize); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, symtab_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.symoff = bArr[2].GetUint(); - b.nsyms = bArr[3].GetUint(); - b.stroff = bArr[4].GetUint(); - b.strsize = bArr[5].GetUint(); -} - -void Serialize(SerializationContext& context, const dysymtab_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.ilocalsym); - Serialize(context, value.nlocalsym); - Serialize(context, value.iextdefsym); - Serialize(context, value.nextdefsym); - Serialize(context, value.iundefsym); - Serialize(context, value.nundefsym); - Serialize(context, value.tocoff); - Serialize(context, value.ntoc); - Serialize(context, value.modtaboff); - Serialize(context, value.nmodtab); - Serialize(context, value.extrefsymoff); - Serialize(context, value.nextrefsyms); - Serialize(context, value.indirectsymoff); - Serialize(context, value.nindirectsyms); - Serialize(context, value.extreloff); - Serialize(context, value.nextrel); - Serialize(context, value.locreloff); - Serialize(context, value.nlocrel); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, dysymtab_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.ilocalsym = bArr[2].GetUint(); - b.nlocalsym = bArr[3].GetUint(); - b.iextdefsym = bArr[4].GetUint(); - b.nextdefsym = bArr[5].GetUint(); - b.iundefsym = bArr[6].GetUint(); - b.nundefsym = bArr[7].GetUint(); - b.tocoff = bArr[8].GetUint(); - b.ntoc = bArr[9].GetUint(); - b.modtaboff = bArr[10].GetUint(); - b.nmodtab = bArr[11].GetUint(); - b.extrefsymoff = bArr[12].GetUint(); - b.nextrefsyms = bArr[13].GetUint(); - b.indirectsymoff = bArr[14].GetUint(); - b.nindirectsyms = bArr[15].GetUint(); - b.extreloff = bArr[16].GetUint(); - b.nextrel = bArr[17].GetUint(); - b.locreloff = bArr[18].GetUint(); - b.nlocrel = bArr[19].GetUint(); -} - -void Serialize(SerializationContext& context, const dyld_info_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.rebase_off); - Serialize(context, value.rebase_size); - Serialize(context, value.bind_off); - Serialize(context, value.bind_size); - Serialize(context, value.weak_bind_off); - Serialize(context, value.weak_bind_size); - Serialize(context, value.lazy_bind_off); - Serialize(context, value.lazy_bind_size); - Serialize(context, value.export_off); - Serialize(context, value.export_size); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, dyld_info_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.rebase_off = bArr[2].GetUint(); - b.rebase_size = bArr[3].GetUint(); - b.bind_off = bArr[4].GetUint(); - b.bind_size = bArr[5].GetUint(); - b.weak_bind_off = bArr[6].GetUint(); - b.weak_bind_size = bArr[7].GetUint(); - b.lazy_bind_off = bArr[8].GetUint(); - b.lazy_bind_size = bArr[9].GetUint(); - b.export_off = bArr[10].GetUint(); - b.export_size = bArr[11].GetUint(); -} - -void Serialize(SerializationContext& context, const routines_command_64& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.init_address); - Serialize(context, value.init_module); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, routines_command_64& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - // Because we might open databases that had not previously serialized this, we must allow - // an empty array, otherwise we will crash! - if (bArr.Size() < 4) - return; - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.init_address = bArr[2].GetUint64(); - b.init_module = bArr[3].GetUint64(); -} - -void Serialize(SerializationContext& context, const function_starts_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.funcoff); - Serialize(context, value.funcsize); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, function_starts_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.funcoff = bArr[2].GetUint(); - b.funcsize = bArr[3].GetUint(); -} - -void Serialize(SerializationContext& context, const section_64& value) -{ - context.writer.StartArray(); - - std::string_view sectname(value.sectname, 16); - std::string_view segname(value.segname, 16); - - Serialize(context, sectname.substr(0, sectname.find('\0'))); - Serialize(context, segname.substr(0, segname.find('\0'))); - Serialize(context, value.addr); - Serialize(context, value.size); - Serialize(context, value.offset); - Serialize(context, value.align); - Serialize(context, value.reloff); - Serialize(context, value.nreloc); - Serialize(context, value.flags); - Serialize(context, value.reserved1); - Serialize(context, value.reserved2); - Serialize(context, value.reserved3); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<section_64>& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - for (auto& s : bArr) - { - section_64 sec; - auto s2 = s.GetArray(); - std::string sectNameStr = s2[0].GetString(); - memset(sec.sectname, 0, 16); - memcpy(sec.sectname, sectNameStr.c_str(), sectNameStr.size()); - std::string segNameStr = s2[1].GetString(); - memset(sec.segname, 0, 16); - memcpy(sec.segname, segNameStr.c_str(), segNameStr.size()); - sec.addr = s2[2].GetUint64(); - sec.size = s2[3].GetUint64(); - sec.offset = s2[4].GetUint(); - sec.align = s2[5].GetUint(); - sec.reloff = s2[6].GetUint(); - sec.nreloc = s2[7].GetUint(); - sec.flags = s2[8].GetUint(); - sec.reserved1 = s2[9].GetUint(); - sec.reserved2 = s2[10].GetUint(); - sec.reserved3 = s2[11].GetUint(); - b.push_back(std::move(sec)); - } -} - -void Serialize(SerializationContext& context, const linkedit_data_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.dataoff); - Serialize(context, value.datasize); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, linkedit_data_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.dataoff = bArr[2].GetUint(); - b.datasize = bArr[3].GetUint(); -} - -void Serialize(SerializationContext& context, const segment_command_64& value) -{ - context.writer.StartArray(); - std::string_view segname(value.segname, 16); - Serialize(context, segname.substr(0, segname.find('\0'))); - Serialize(context, value.vmaddr); - Serialize(context, value.vmsize); - Serialize(context, value.fileoff); - Serialize(context, value.filesize); - Serialize(context, value.maxprot); - Serialize(context, value.initprot); - Serialize(context, value.nsects); - Serialize(context, value.flags); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, segment_command_64& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - std::string segNameStr = bArr[0].GetString(); - memset(b.segname, 0, 16); - memcpy(b.segname, segNameStr.c_str(), segNameStr.size()); - b.vmaddr = bArr[1].GetUint64(); - b.vmsize = bArr[2].GetUint64(); - b.fileoff = bArr[3].GetUint64(); - b.filesize = bArr[4].GetUint64(); - b.maxprot = bArr[5].GetUint(); - b.initprot = bArr[6].GetUint(); - b.nsects = bArr[7].GetUint(); - b.flags = bArr[8].GetUint(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<segment_command_64>& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - for (auto& s : bArr) - { - segment_command_64 sec; - auto s2 = s.GetArray(); - std::string segNameStr = s2[0].GetString(); - memset(sec.segname, 0, 16); - memcpy(sec.segname, segNameStr.c_str(), segNameStr.size()); - sec.vmaddr = s2[1].GetUint64(); - sec.vmsize = s2[2].GetUint64(); - sec.fileoff = s2[3].GetUint64(); - sec.filesize = s2[4].GetUint64(); - sec.maxprot = s2[5].GetUint(); - sec.initprot = s2[6].GetUint(); - sec.nsects = s2[7].GetUint(); - sec.flags = s2[8].GetUint(); - b.push_back(std::move(sec)); - } -} - -void Serialize(SerializationContext& context, const build_version_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.platform); - Serialize(context, value.minos); - Serialize(context, value.sdk); - Serialize(context, value.ntools); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, build_version_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.platform = bArr[2].GetUint(); - b.minos = bArr[3].GetUint(); - b.sdk = bArr[4].GetUint(); - b.ntools = bArr[5].GetUint(); -} - -void Serialize(SerializationContext& context, const build_tool_version& value) -{ - context.writer.StartArray(); - Serialize(context, value.tool); - Serialize(context, value.version); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<build_tool_version>& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - for (auto& s : bArr) - { - build_tool_version sec; - auto s2 = s.GetArray(); - sec.tool = s2[0].GetUint(); - sec.version = s2[1].GetUint(); - b.push_back(sec); - } -} - -} // namespace SharedCacheCore diff --git a/view/sharedcache/core/MetadataSerializable.hpp b/view/sharedcache/core/MetadataSerializable.hpp deleted file mode 100644 index 24d8c3fd..00000000 --- a/view/sharedcache/core/MetadataSerializable.hpp +++ /dev/null @@ -1,254 +0,0 @@ -// -// Created by kat on 5/31/23. -// - -/* - * Welcome to, this file. - * - * This is a metadata serialization helper. - * - * Have you ever wished turning a complex datastructure into a Metadata object was as easy in C++ as it is in python? - * Do you like macros and templates? - * - * Great news. - * - * Implement these on your `public MetadataSerializable<T>` subclass: - * ``` - class MyClass : public MetadataSerializable<MyClass> { - void Store(SerializationContext& context) const { - MSS(m_someVariable); - MSS(m_someOtherVariable); - } - void Load(DeserializationContext& context) { - MSL(m_someVariable); - MSL(m_someOtherVariable); - } - } - ``` - * Then, you can turn your object into a Metadata object with `AsMetadata()`, and load it back with - `LoadFromMetadata()`. - * - * Serialized fields will be automatically repopulated. - * - * Other ser/deser formats (rapidjson objects, strings) also exist. You can use these to achieve nesting, but probably - avoid that. - * */ - -#ifndef SHAREDCACHE_CORE_METADATASERIALIZABLE_HPP -#define SHAREDCACHE_CORE_METADATASERIALIZABLE_HPP - -#include <cassert> -#include "binaryninjaapi.h" -#include "rapidjson/document.h" -#include "rapidjson/stringbuffer.h" -#include "rapidjson/prettywriter.h" -#include "../api/sharedcachecore.h" -#include "view/macho/machoview.h" - -using namespace BinaryNinja; - -namespace SharedCacheCore { - -#define MSS(name) context.store(#name, name) -#define MSS_CAST(name, type) context.store(#name, (type) name) -#define MSS_SUBCLASS(name) Serialize(context, #name, name) -#define MSL(name) name = context.load<decltype(name)>(#name) -#define MSL_CAST(name, storedType, type) name = (type)context.load<storedType>(#name) - -struct DeserializationContext; - -struct SerializationContext { - rapidjson::StringBuffer buffer; - rapidjson::Writer<rapidjson::StringBuffer> writer; - - SerializationContext() : buffer(), writer(buffer) { - } - - template <typename T> - void store(std::string_view x, const T& y) - { - Serialize(*this, x, y); - } -}; - -struct DeserializationContext { - rapidjson::Document doc; - - template <typename T> - T load(std::string_view x) - { - T value; - Deserialize(*this, x, value); - return value; - } -}; - -template <typename Derived, typename LoadResult = Derived> -class MetadataSerializable { -public: - template <typename... Args> - std::string AsString(Args&&... args) const { - SerializationContext context; - Store(context, std::forward<Args>(args)...); - - return context.buffer.GetString(); - } - - static LoadResult LoadFromString(const std::string& s) { - DeserializationContext context; - [[maybe_unused]] rapidjson::ParseResult result = context.doc.Parse(s.c_str()); - assert(result); - return Derived::Load(context); - } - - static LoadResult LoadFromValue(rapidjson::Value& s) { - DeserializationContext context; - context.doc.CopyFrom(s, context.doc.GetAllocator()); - return Derived::Load(context); - } - - template <typename... Args> - Ref<Metadata> AsMetadata(Args&&... args) const { - return new Metadata(AsString(std::forward<Args>(args)...)); - } - - template <typename... Args> - void Store(SerializationContext& context, Args&&... args) const { - context.writer.StartObject(); - AsDerived().Store(context, std::forward<Args>(args)...); - context.writer.EndObject(); - } - -private: - const Derived& AsDerived() const { return static_cast<const Derived&>(*this); } - Derived& AsDerived() { return static_cast<Derived&>(*this); } -}; - -// The functions below are not part of the FFI API, but are exported so they can be shared with sharedcacheui. - -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, std::string_view str); - -template <typename T> -inline void Serialize(SerializationContext& context, const MetadataSerializable<T>& value) -{ - value.Store(context); -} - -template <typename T> -inline void Serialize(SerializationContext& context, std::string_view name, const T& value) -{ - Serialize(context, name); - Serialize(context, value); -} - -template <typename First, typename Second> -void Serialize(SerializationContext& context, const std::pair<First, Second>& value) -{ - context.writer.StartArray(); - Serialize(context, value.first); - Serialize(context, value.second); - context.writer.EndArray(); -} - -template <typename K, typename V, typename L> -void Serialize(SerializationContext& context, const std::map<K, V, L>& value) -{ - context.writer.StartArray(); - for (auto& pair : value) - { - Serialize(context, pair); - } - context.writer.EndArray(); -} - -template <typename K, typename V> -void Serialize(SerializationContext& context, const std::unordered_map<K, V>& value) -{ - context.writer.StartArray(); - for (auto& pair : value) - { - Serialize(context, pair); - } - context.writer.EndArray(); -} - -template <typename T> -void Serialize(SerializationContext& context, const std::vector<T>& values) -{ - context.writer.StartArray(); - for (const auto& value : values) - { - Serialize(context, value); - } - context.writer.EndArray(); -} - -template <typename T> -void Serialize(SerializationContext& context, const std::optional<T>& value) -{ - if (value.has_value()) - Serialize(context, *value); - else - context.writer.Null(); -} - -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, const char*); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, bool b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, bool& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint8_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint8_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint16_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint16_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint32_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint32_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint64_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint64_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int8_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int8_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int16_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int16_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int32_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int32_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int64_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int64_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, std::string_view b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, const std::pair<uint64_t, std::pair<uint64_t, uint64_t>>& value); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::string& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::map<uint64_t, std::string>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, std::string>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, uint64_t>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::string>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::string>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, bool>>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<uint64_t>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, uint64_t>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>>>& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const mach_header_64& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, mach_header_64& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const symtab_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, symtab_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const dysymtab_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, dysymtab_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const dyld_info_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, dyld_info_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const routines_command_64& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, routines_command_64& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const function_starts_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, function_starts_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const section_64& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<section_64>& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const linkedit_data_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, linkedit_data_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const segment_command_64& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, segment_command_64& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<segment_command_64>& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const build_version_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, build_version_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const build_tool_version& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<build_tool_version>& b); - -} // namespace SharedCacheCore - -#endif // SHAREDCACHE_METADATASERIALIZABLE_HPP diff --git a/view/sharedcache/core/ObjC.cpp b/view/sharedcache/core/ObjC.cpp index c763b321..bb483542 100644 --- a/view/sharedcache/core/ObjC.cpp +++ b/view/sharedcache/core/ObjC.cpp @@ -1,95 +1,98 @@ #include "ObjC.h" +#include "SharedCacheController.h" + using namespace BinaryNinja; using namespace DSCObjC; -using namespace SharedCacheCore; -DSCObjCReader::DSCObjCReader(SharedCache* cache, size_t addressSize) : - m_reader(VMReader(cache->GetVMMap())), m_addressSize(addressSize) -{ -} +SharedCacheObjCReader::SharedCacheObjCReader(VirtualMemoryReader reader) : m_reader(reader) {} -void DSCObjCReader::Read(void* dest, size_t len) +void SharedCacheObjCReader::Read(void* dest, size_t len) { m_reader.Read(dest, len); } -std::string DSCObjCReader::ReadCString() +std::string SharedCacheObjCReader::ReadCString(size_t maxLength) { - return m_reader.ReadCString(m_reader.GetOffset()); + return m_reader.ReadCString(m_reader.GetOffset(), maxLength); } -uint8_t DSCObjCReader::Read8() +uint8_t SharedCacheObjCReader::Read8() { - return m_reader.Read8(); + return m_reader.ReadUInt8(); } -uint16_t DSCObjCReader::Read16() +uint16_t SharedCacheObjCReader::Read16() { - return m_reader.Read16(); + return m_reader.ReadUInt16(); } -uint32_t DSCObjCReader::Read32() +uint32_t SharedCacheObjCReader::Read32() { - return m_reader.Read32(); + return m_reader.ReadUInt32(); } -uint64_t DSCObjCReader::Read64() +uint64_t SharedCacheObjCReader::Read64() { - return m_reader.Read64(); + return m_reader.ReadUInt64(); } -int8_t DSCObjCReader::ReadS8() +int8_t SharedCacheObjCReader::ReadS8() { - return m_reader.ReadS8(); + return m_reader.ReadInt8(); } -int16_t DSCObjCReader::ReadS16() +int16_t SharedCacheObjCReader::ReadS16() { - return m_reader.ReadS16(); + return m_reader.ReadInt16(); } -int32_t DSCObjCReader::ReadS32() +int32_t SharedCacheObjCReader::ReadS32() { - return m_reader.ReadS32(); + return m_reader.ReadInt32(); } -int64_t DSCObjCReader::ReadS64() +int64_t SharedCacheObjCReader::ReadS64() { - return m_reader.ReadS64(); + return m_reader.ReadInt64(); } -uint64_t DSCObjCReader::ReadPointer() +uint64_t SharedCacheObjCReader::ReadPointer() { return m_reader.ReadPointer(); } -uint64_t DSCObjCReader::GetOffset() const +uint64_t SharedCacheObjCReader::GetOffset() const { return m_reader.GetOffset(); } -void DSCObjCReader::Seek(uint64_t offset) +void SharedCacheObjCReader::Seek(uint64_t offset) { m_reader.Seek(offset); } -void DSCObjCReader::SeekRelative(int64_t offset) +void SharedCacheObjCReader::SeekRelative(int64_t offset) { m_reader.SeekRelative(offset); } -VMReader& DSCObjCReader::GetVMReader() +VirtualMemoryReader& SharedCacheObjCReader::GetVMReader() { return m_reader; } -std::shared_ptr<ObjCReader> DSCObjCProcessor::GetReader() +std::shared_ptr<ObjCReader> SharedCacheObjCProcessor::GetReader() { - return std::make_shared<DSCObjCReader>(m_cache, m_data->GetAddressSize()); + const auto controller = DSC::SharedCacheController::FromView(*m_data); + // TODO: This should never happen. + if (!controller) + throw std::runtime_error("SharedCacheController not found for SharedCacheObjCProcessor::GetReader!"); + auto reader = VirtualMemoryReader(controller->GetCache().GetVirtualMemory(), m_data->GetAddressSize()); + return std::make_shared<SharedCacheObjCReader>(reader); } -void DSCObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth) +void SharedCacheObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth) { if (m_customRelativeMethodSelectorBase.has_value()) { @@ -103,14 +106,48 @@ void DSCObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth) } } -uint64_t DSCObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader) +std::optional<ObjCOptimizationHeader> GetObjCOptimizationHeader(SharedCache& cache, VirtualMemoryReader& reader) { - auto objCRelativeMethodsBaseAddr = m_cache->GetObjCRelativeMethodBaseAddress(static_cast<DSCObjCReader*>(reader)->GetVMReader()); - m_customRelativeMethodSelectorBase = objCRelativeMethodsBaseAddr; - return objCRelativeMethodsBaseAddr; + // Find the first primary entry and use that header to read the obj opt header. + // Don't ask me why this is done like this... + std::optional<dyld_cache_header> primaryCacheHeader = std::nullopt; + for (const auto& [_, entry] : cache.GetEntries()) + { + if (entry.GetType() == CacheEntryType::Primary) + { + primaryCacheHeader = entry.GetHeader(); + break; + } + } + + // Check if we even have the obj opt stuff. + if (!primaryCacheHeader || !primaryCacheHeader->objcOptsOffset || !primaryCacheHeader->objcOptsSize) + return std::nullopt; + + ObjCOptimizationHeader header = {}; + // Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior. + // TODO: The base address is the lowest region, however is that going to be where the primary cache header resides? + reader.Read(&header, cache.GetBaseAddress() + primaryCacheHeader->objcOptsOffset, sizeof(ObjCOptimizationHeader)); + + return header; } -DSCObjCProcessor::DSCObjCProcessor(BinaryView* data, SharedCache* cache, bool isBackedByDatabase) : - ObjCProcessor(data, "SharedCache.ObjC", isBackedByDatabase, true), m_cache(cache) +uint64_t SharedCacheObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader) { + // Try and retrieve the base address of the selector stuff. + if (const auto controller = DSC::SharedCacheController::FromView(*m_data)) + { + auto baseAddress = controller->GetCache().GetBaseAddress(); + auto dangerReader = dynamic_cast<SharedCacheObjCReader*>(reader)->GetVMReader(); + if (const auto header = GetObjCOptimizationHeader(controller->GetCache(), dangerReader); header.has_value()) + { + m_customRelativeMethodSelectorBase = baseAddress + header->relativeMethodSelectorBaseAddressOffset; + } + } + + return m_customRelativeMethodSelectorBase.value_or(0); } + +SharedCacheObjCProcessor::SharedCacheObjCProcessor(BinaryView* data, bool isBackedByDatabase) : + ObjCProcessor(data, "SharedCache.ObjC", isBackedByDatabase, true) +{} diff --git a/view/sharedcache/core/ObjC.h b/view/sharedcache/core/ObjC.h index 13790364..6576564b 100644 --- a/view/sharedcache/core/ObjC.h +++ b/view/sharedcache/core/ObjC.h @@ -1,52 +1,71 @@ -// -// Created by kat on 5/23/23. -// - -#ifndef SHAREDCACHE_OBJC_H -#define SHAREDCACHE_OBJC_H +#pragma once #include <binaryninjaapi.h> #include <objectivec/objc.h> #include "SharedCache.h" +struct ObjCOptimizationHeader +{ + uint32_t version; + uint32_t flags; + uint64_t headerInfoROCacheOffset; + uint64_t headerInfoRWCacheOffset; + uint64_t selectorHashTableCacheOffset; + uint64_t classHashTableCacheOffset; + uint64_t protocolHashTableCacheOffset; + uint64_t relativeMethodSelectorBaseAddressOffset; +}; + namespace DSCObjC { - class DSCObjCReader : public ObjCReader { - private: - VMReader m_reader; - size_t m_addressSize; + class SharedCacheObjCReader : public BinaryNinja::ObjCReader + { + VirtualMemoryReader m_reader; public: void Read(void* dest, size_t len) override; - std::string ReadCString() override; + + std::string ReadCString(size_t maxLength = -1) override; + uint8_t Read8() override; + uint16_t Read16() override; + uint32_t Read32() override; + uint64_t Read64() override; + int8_t ReadS8() override; + int16_t ReadS16() override; + int32_t ReadS32() override; + int64_t ReadS64() override; + uint64_t ReadPointer() override; + uint64_t GetOffset() const override; + void Seek(uint64_t offset) override; + void SeekRelative(int64_t offset) override; - VMReader& GetVMReader(); + VirtualMemoryReader& GetVMReader(); - DSCObjCReader(SharedCacheCore::SharedCache* cache, size_t addressSize); + SharedCacheObjCReader(VirtualMemoryReader reader); }; - class DSCObjCProcessor : public ObjCProcessor { + class SharedCacheObjCProcessor : public BinaryNinja::ObjCProcessor + { std::optional<uint64_t> m_customRelativeMethodSelectorBase = std::nullopt; - SharedCacheCore::SharedCache* m_cache; - std::shared_ptr<ObjCReader> GetReader() override; - void GetRelativeMethod(ObjCReader* reader, method_t& meth) override; - + std::shared_ptr<BinaryNinja::ObjCReader> GetReader() override; + + void GetRelativeMethod(BinaryNinja::ObjCReader* reader, BinaryNinja::method_t& meth) override; + public: - DSCObjCProcessor(BinaryView* data, SharedCacheCore::SharedCache* cache, bool isBackedByDatabase); - - uint64_t GetObjCRelativeMethodBaseAddress(ObjCReader* reader) override; + SharedCacheObjCProcessor(BinaryNinja::BinaryView* data, bool isBackedByDatabase); + + uint64_t GetObjCRelativeMethodBaseAddress(BinaryNinja::ObjCReader* reader) override; }; -} -#endif //SHAREDCACHE_OBJC_H +} // namespace DSCObjC diff --git a/view/sharedcache/core/SharedCache.cpp b/view/sharedcache/core/SharedCache.cpp index 27367a45..24bc4619 100644 --- a/view/sharedcache/core/SharedCache.cpp +++ b/view/sharedcache/core/SharedCache.cpp @@ -1,3975 +1,617 @@ -// -// Created by kat on 5/19/23. -// - -/* --- - * This is the primary image loader logic for Shared Caches - * - * It is standalone code that operates on a DSCView. - * - * This has to recreate _all_ of the Mach-O View logic, but slightly differently, as everything is spicy and weird and - * different enough that it's not worth trying to make a shared base class. - * - * The SharedCache api object is a 'Controller' that serializes its own state in view metadata. - * - * It is multithreading capable (multiple SharedCache objects can exist and do things on different threads, it will manage) - * - * View state is saved to BinaryView any time it changes, however due to json deser speed we must also cache it on heap. - * This cache is 'load bearing' and controllers on other threads may serialize it back to view after making changes, so it - * must be kept up to date. - * - * - * - * */ - #include "SharedCache.h" -#include "binaryninjaapi.h" -#include "DSCView.h" -#include "ObjC.h" -#include <algorithm> -#include <fcntl.h> +#include <regex> #include <filesystem> -#include <limits> -#include <memory> -#include <mutex> -#include <optional> -#include <unordered_map> -#include <utility> -#include <vector> +#include "MachO.h" +#include "SlideInfo.h" using namespace BinaryNinja; -using namespace SharedCacheCore; - -namespace SharedCacheCore { - -namespace { - -#ifdef _MSC_VER - -int count_trailing_zeros(uint64_t value) { - unsigned long index; // 32-bit long on Windows - if (_BitScanForward64(&index, value)) { - return index; - } else { - return 64; // If the value is 0, return 64. - } -} -#else -int count_trailing_zeros(uint64_t value) { - return value == 0 ? 64 : __builtin_ctzll(value); -} -#endif - -struct MemoryRegionStatus -{ - bool loaded = false; - bool headerInitialized = false; -}; - -} // unnamed namespace - -// State that does not change after `PerformInitialLoad`. -struct SharedCache::CacheInfo : - public MetadataSerializable<SharedCache::CacheInfo, std::optional<SharedCache::CacheInfo>> -{ - std::vector<BackingCache> backingCaches; - std::unordered_map<uint64_t, SharedCacheMachOHeader> headers; - std::vector<CacheImage> images; - std::unordered_map<std::string, uint64_t> imageStarts; - AddressRangeMap<MemoryRegion> memoryRegions; - - std::optional<std::pair<uint64_t, uint64_t>> objcOptimizationDataRange; - - std::string baseFilePath; - SharedCacheFormat cacheFormat = RegularCacheFormat; - - MemoryRegion* AddMemoryRegion(MemoryRegion region); - void AddPotentiallyOverlappingMemoryRegion(MemoryRegion region); -#ifndef NDEBUG - void Verify() const; -#endif - - uint64_t BaseAddress() const; - - void Store(SerializationContext&) const; - static std::optional<SharedCache::CacheInfo> Load(DeserializationContext&); -}; - -struct State : public MetadataSerializable<State> -{ - // Map from start address of a region to the region's status. - std::unordered_map<uint64_t, MemoryRegionStatus> memoryRegionStatus; - std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>> - exportInfos; - std::unordered_map<uint64_t, std::shared_ptr<std::vector<Ref<Symbol>>>> symbolInfos; - - // Store only. Loading is done via `ModifiedState`. - void Store(SerializationContext&, std::optional<DSCViewState> viewState) const; -}; - -struct SharedCache::ModifiedState : public State, public MetadataSerializable<SharedCache::ModifiedState> -{ - std::optional<DSCViewState> viewState; - - using Base = MetadataSerializable<SharedCache::ModifiedState>; - using Base::AsMetadata; - using Base::LoadFromString; - - void Store(SerializationContext&) const; - static SharedCache::ModifiedState Load(DeserializationContext&); - static SharedCache::ModifiedState LoadAll(BinaryNinja::BinaryView*, const CacheInfo&); - - void Merge(SharedCache::ModifiedState&& other); -}; - -struct SharedCache::ViewSpecificState -{ - std::mutex typeLibraryMutex; - std::unordered_map<std::string, Ref<TypeLibrary>> typeLibraries; - - std::mutex viewOperationsThatInfluenceMetadataMutex; - std::atomic<BNDSCViewLoadProgress> progress; +// The next id to use when calling Cache::AddEntry +static CacheEntryId nextId = 1; - std::mutex cacheInfoMutex; - std::shared_ptr<const SharedCache::CacheInfo> cacheInfo; - - std::mutex stateMutex; - struct State state; - - std::atomic<DSCViewState> viewState; - uint64_t savedModifications = 0; -}; - -namespace { - -std::shared_ptr<SharedCache::ViewSpecificState> ViewSpecificStateForId(uint64_t viewIdentifier, bool insertIfNeeded = true) +Ref<Symbol> CacheSymbol::ToBNSymbol() const { - static std::mutex viewSpecificStateMutex; - static std::unordered_map<uint64_t, std::weak_ptr<SharedCache::ViewSpecificState>> viewSpecificState; - - std::lock_guard lock(viewSpecificStateMutex); - - if (auto it = viewSpecificState.find(viewIdentifier); it != viewSpecificState.end()) - { - if (auto statePtr = it->second.lock()) - return statePtr; - } - - if (!insertIfNeeded) - return nullptr; - - auto statePtr = std::make_shared<SharedCache::ViewSpecificState>(); - viewSpecificState[viewIdentifier] = statePtr; - - // Prune entries for any views that are no longer in use. - for (auto it = viewSpecificState.begin(); it != viewSpecificState.end(); ) - { - if (it->second.expired()) - it = viewSpecificState.erase(it); - else - ++it; - } - - return statePtr; -} - -std::shared_ptr<SharedCache::ViewSpecificState> ViewSpecificStateForView(Ref<BinaryNinja::BinaryView> view) -{ - return ViewSpecificStateForId(view->GetFile()->GetSessionId()); -} - -std::string base_name(std::string const& path) -{ - return path.substr(path.find_last_of("/\\") + 1); + QualifiedName qname; + std::string shortName = name; + if (DemangleLLVM(name, qname, true)) + shortName = qname.GetString(); + return new Symbol(type, shortName, shortName, name, address, nullptr); } -BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt) +std::vector<std::string> CacheImage::GetDependencies() const { - - uint32_t flags = 0; - if (initProt & MACHO_VM_PROT_READ) - flags |= SegmentReadable; - if (initProt & MACHO_VM_PROT_WRITE) - flags |= SegmentWritable; - if (initProt & MACHO_VM_PROT_EXECUTE) - flags |= SegmentExecutable; - if (((initProt & MACHO_VM_PROT_WRITE) == 0) && - ((maxProt & MACHO_VM_PROT_WRITE) == 0)) - flags |= SegmentDenyWrite; - if (((initProt & MACHO_VM_PROT_EXECUTE) == 0) && - ((maxProt & MACHO_VM_PROT_EXECUTE) == 0)) - flags |= SegmentDenyExecute; - return (BNSegmentFlag)flags; -} - -BNSectionSemantics SectionSemanticsForRegion(const MemoryRegion& region) -{ - if ((region.flags & SegmentExecutable) && (region.flags & SegmentDenyWrite)) - return ReadOnlyCodeSectionSemantics; - - if (region.flags & SegmentExecutable) - return DefaultSectionSemantics; - - if (region.flags & SegmentDenyWrite) - return ReadOnlyDataSectionSemantics; - - return ReadWriteDataSectionSemantics; -} - - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-function" -static int64_t readSLEB128(const uint8_t*& current, const uint8_t* end) -{ - uint8_t cur; - int64_t value = 0; - size_t shift = 0; - while (current != end) - { - cur = *current++; - value |= (cur & 0x7f) << shift; - shift += 7; - if ((cur & 0x80) == 0) - break; - } - value = (value << (64 - shift)) >> (64 - shift); - return value; -} -#pragma clang diagnostic pop - - -static uint64_t readLEB128(const uint8_t*& current, const uint8_t* end) -{ - uint64_t result = 0; - int bit = 0; - do - { - if (current >= end) - return -1; - - uint64_t slice = *current & 0x7f; - - if (bit > 63) - return -1; - else - { - result |= (slice << bit); - bit += 7; - } - } while (*current++ & 0x80); - return result; -} - - -uint64_t readValidULEB128(const uint8_t*& current, const uint8_t* end) -{ - uint64_t value = readLEB128(current, end); - if ((int64_t)value == -1) - throw ReadException(); - return value; -} - -} // unnamed namespace - -uint64_t SharedCache::FastGetBackingCacheCount(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) -{ - std::shared_ptr<MMappedFileAccessor> baseFile; - try { - baseFile = MapFileWithoutApplyingSlide(dscView->GetFile()->GetOriginalFilename()); - } - catch (...){ - LogError("Shared Cache preload: Failed to open file %s", dscView->GetFile()->GetOriginalFilename().c_str()); - return 0; - } - - dyld_cache_header header {}; - size_t header_size = baseFile->ReadUInt32(16); - baseFile->Read(&header, 0, std::min(header_size, sizeof(dyld_cache_header))); - - SharedCacheFormat cacheFormat; - - if (header.imagesCountOld != 0) - cacheFormat = RegularCacheFormat; - - size_t subCacheOff = offsetof(struct dyld_cache_header, subCacheArrayOffset); - size_t headerEnd = header.mappingOffset; - if (headerEnd > subCacheOff) - { - if (header.cacheType != 2) - { - if (std::filesystem::exists(ResolveFilePath(dscView, baseFile->Path() + ".01"))) - cacheFormat = LargeCacheFormat; - else - cacheFormat = SplitCacheFormat; - } - else - cacheFormat = iOS16CacheFormat; - } - - switch (cacheFormat) - { - case RegularCacheFormat: - { - return 1; - } - case LargeCacheFormat: - { - auto subCacheCount = header.subCacheArrayCount; - return subCacheCount + 1; - } - case SplitCacheFormat: - { - auto subCacheCount = header.subCacheArrayCount; - return subCacheCount + 2; - } - case iOS16CacheFormat: - { - auto subCacheCount = header.subCacheArrayCount; - return subCacheCount + 2; - } - } -} - -MemoryRegion* SharedCache::CacheInfo::AddMemoryRegion(MemoryRegion region) -{ - if (!region.size) - return nullptr; - - auto [it, inserted] = memoryRegions.insert(std::make_pair(region.AsAddressRange(), std::move(region))); - assert(inserted); -#ifndef NDEBUG - Verify(); -#endif - return &it->second; -} - -void SharedCache::CacheInfo::AddPotentiallyOverlappingMemoryRegion(MemoryRegion region) -{ - if (!region.size) - return; - - // First region at or past the start of the region. - auto begin = memoryRegions.lower_bound(region.AsAddressRange().start); - if (begin == memoryRegions.end()) - { - AddMemoryRegion(std::move(region)); - return; - } - - // First region past the end of the region. - auto end = memoryRegions.lower_bound(region.AsAddressRange().end); - - for (auto it = begin; it != end; ++it) - { - uint64_t newRegionSize = it->second.start - region.start; - if (newRegionSize) - { - MemoryRegion newRegion(region); - newRegion.size = newRegionSize; - AddMemoryRegion(std::move(newRegion)); - } - - region.start = it->second.start + it->second.size; - region.size -= (newRegionSize + it->second.size); - } - - AddMemoryRegion(std::move(region)); -} - -#ifndef NDEBUG -void SharedCache::CacheInfo::Verify() const -{ - if (memoryRegions.size() < 2) - { - return; - } - - auto it = memoryRegions.begin(); - auto lastIt = it++; - for (auto lastIt = it++; it != memoryRegions.end(); lastIt = it++) - { - const auto& [lastAddress, lastRegion] = *lastIt; - const auto& [address, region] = *it; - assert(lastRegion.start == lastAddress.start && lastRegion.start + lastRegion.size == lastAddress.end); - assert(region.start == address.start && region.start + region.size == address.end); - assert(lastAddress.start < address.start); - assert(lastAddress.end <= address.start); - } -} -#endif - -static MemoryRegionStatus* StatusForMemoryRegion( - std::unordered_map<uint64_t, MemoryRegionStatus>& memoryRegionStatus, const MemoryRegion& region) -{ - auto it = memoryRegionStatus.find(region.start); - if (it == memoryRegionStatus.end()) - return nullptr; - - return &it->second; -} - -bool SharedCache::MemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region) const -{ - if (auto status = StatusForMemoryRegion(m_modifiedState->memoryRegionStatus, region)) - return status->loaded; - - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - return status->loaded; - - return false; -} - -void SharedCache::SetMemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region) -{ - auto [it, inserted] = m_modifiedState->memoryRegionStatus.insert({region.start, {}}); - if (inserted) - { - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - it->second = *status; - } - it->second.loaded = true; -} - -bool SharedCache::MemoryRegionIsHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region) const -{ - if (auto status = StatusForMemoryRegion(m_modifiedState->memoryRegionStatus, region)) - return status->headerInitialized; - - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - return status->headerInitialized; - - return false; + if (header) + return header->dylibs; + return {}; } -void SharedCache::SetMemoryRegionHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region) +CacheEntry::CacheEntry(std::string filePath, std::string fileName, CacheEntryType type, dyld_cache_header header, + std::vector<dyld_cache_mapping_info> mappings, std::unordered_map<std::string, dyld_cache_image_info> images) { - auto [it, inserted] = m_modifiedState->memoryRegionStatus.insert({region.start, {}}); - if (inserted) - { - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - it->second = *status; - } - it->second.headerInitialized = true; + m_filePath = std::move(filePath); + m_fileName = std::move(fileName); + m_type = type; + m_header = header; + m_mappings = std::move(mappings); + m_images = std::move(images); } -void SharedCache::PerformInitialLoad(std::lock_guard<std::mutex>& lock) +std::optional<CacheEntry> CacheEntry::FromFile(const std::string& filePath, const std::string& fileName, CacheEntryType type) { - std::unique_lock viewOperationsLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - auto path = m_dscView->GetFile()->GetOriginalFilename(); - auto baseFile = MapFileWithoutApplyingSlide(path); - - m_logger->LogInfo("Loading caches..."); - m_viewSpecificState->progress = LoadProgressLoadingCaches; - - CacheInfo initialState; - initialState.baseFilePath = path; - initialState.cacheFormat = RegularCacheFormat; + auto file = FileAccessorCache::Global().Open(filePath).lock(); - // TODO: If this fails we should prompt the user to select there file, this probably means that - // TODO: They are opening a database with no original file path. - DataBuffer sig = baseFile->ReadBuffer(0, 4); + // TODO: Pull this out into another function so we can do IsValidDSCFile or something. + // We first want to make sure that the base file is dyld. + // All entries must start with "dyld". + DataBuffer sig = file->ReadBuffer(0, 4); if (sig.GetLength() != 4) - throw std::runtime_error("Not a valid dyld_shared_cache file"); + return std::nullopt; const char* magic = (char*)sig.GetData(); if (strncmp(magic, "dyld", 4) != 0) - throw std::runtime_error("Not a valid dyld_shared_cache file"); - - dyld_cache_header primaryCacheHeader {}; - size_t header_size = baseFile->ReadUInt32(16); - baseFile->Read(&primaryCacheHeader, 0, std::min(header_size, sizeof(dyld_cache_header))); - - if (primaryCacheHeader.imagesCountOld != 0) - initialState.cacheFormat = RegularCacheFormat; - - size_t subCacheOff = offsetof(struct dyld_cache_header, subCacheArrayOffset); - size_t headerEnd = primaryCacheHeader.mappingOffset; - if (headerEnd > subCacheOff) - { - if (primaryCacheHeader.cacheType != 2) - { - if (std::filesystem::exists(ResolveFilePath(m_dscView, baseFile->Path() + ".01"))) - initialState.cacheFormat = LargeCacheFormat; - else - initialState.cacheFormat = SplitCacheFormat; - } - else - initialState.cacheFormat = iOS16CacheFormat; - } - - if (primaryCacheHeader.objcOptsOffset && primaryCacheHeader.objcOptsSize) - { - uint64_t objcOptsOffset = primaryCacheHeader.objcOptsOffset; - uint64_t objcOptsSize = primaryCacheHeader.objcOptsSize; - initialState.objcOptimizationDataRange = {objcOptsOffset, objcOptsSize}; - } - - std::vector<MemoryRegion> nonImageMemoryRegions; - switch (initialState.cacheFormat) - { - case RegularCacheFormat: - { - dyld_cache_mapping_info mapping {}; - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCountOld; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffsetOld + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - m_logger->LogInfo("Found %d images in the shared cache", primaryCacheHeader.imagesCountOld); - - if (primaryCacheHeader.branchPoolsCount) - { - std::vector<uint64_t> addresses; - addresses.reserve(primaryCacheHeader.branchPoolsCount); - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - addresses.push_back(baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize()))); - } - baseFile.reset(); // No longer needed, we're about to remap this file into VM space so we can load these. - uint64_t i = 0; - for (auto address : addresses) - { - i++; - auto vm = GetVMMap(); - auto machoHeader = SharedCache::LoadHeaderForAddress(vm, address, "dyld_shared_cache_branch_islands_" + std::to_string(i)); - if (machoHeader) - { - for (const auto& segment : machoHeader->segments) - { - MemoryRegion stubIslandRegion; - stubIslandRegion.start = segment.vmaddr; - stubIslandRegion.size = segment.filesize; - char segName[17]; - memcpy(segName, segment.segname, 16); - segName[16] = 0; - std::string segNameStr = std::string(segName); - stubIslandRegion.prettyName = "dyld_shared_cache_branch_islands_" + std::to_string(i) + "::" + segNameStr; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - } - } - } - - m_logger->LogInfo("Found %d branch pools in the shared cache", primaryCacheHeader.branchPoolsCount); - - break; - } - case LargeCacheFormat: - { - dyld_cache_mapping_info mapping {}; // We're going to reuse this for all of the mappings. We only need it - // briefly. - - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - if (primaryCacheHeader.branchPoolsCount) - { - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = - baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); - } - } - std::string mainFileName = base_name(path); - if (auto projectFile = m_dscView->GetFile()->GetProjectFile()) - mainFileName = projectFile->GetName(); - auto subCacheCount = primaryCacheHeader.subCacheArrayCount; - - dyld_subcache_entry2 _entry {}; - std::vector<dyld_subcache_entry2> subCacheEntries; - subCacheEntries.reserve(subCacheCount); - for (size_t i = 0; i < subCacheCount; i++) - { - baseFile->Read(&_entry, primaryCacheHeader.subCacheArrayOffset + (i * sizeof(dyld_subcache_entry2)), - sizeof(dyld_subcache_entry2)); - subCacheEntries.push_back(_entry); - } - - baseFile.reset(); - for (const auto& entry : subCacheEntries) - { - std::string subCachePath; - std::string subCacheFilename; - if (std::string(entry.fileExtension).find('.') != std::string::npos) - { - subCachePath = path + entry.fileExtension; - subCacheFilename = mainFileName + entry.fileExtension; - } - else - { - subCachePath = path + "." + entry.fileExtension; - subCacheFilename = mainFileName + "." + entry.fileExtension; - } - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - dyld_cache_mapping_info subCacheMapping {}; - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSecondary; - subCache.path = subCachePath; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 - && subCacheHeader.imagesTextOffset == 0) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion stubIslandRegion; - stubIslandRegion.start = address; - stubIslandRegion.size = size; - stubIslandRegion.prettyName = subCacheFilename + "::_stubs"; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - - initialState.backingCaches.push_back(std::move(subCache)); - } - break; - } - case SplitCacheFormat: - { - dyld_cache_mapping_info mapping {}; // We're going to reuse this for all of the mappings. We only need it - // briefly. - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - if (primaryCacheHeader.branchPoolsCount) - { - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = - baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); - } - } - - std::string mainFileName = base_name(path); - if (auto projectFile = m_dscView->GetFile()->GetProjectFile()) - mainFileName = projectFile->GetName(); - auto subCacheCount = primaryCacheHeader.subCacheArrayCount; - - baseFile.reset(); - - for (size_t i = 1; i <= subCacheCount; i++) - { - auto subCachePath = path + "." + std::to_string(i); - auto subCacheFilename = mainFileName + "." + std::to_string(i); - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSecondary; - subCache.path = subCachePath; - - dyld_cache_mapping_info subCacheMapping {}; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - initialState.backingCaches.push_back(std::move(subCache)); - - if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 - && subCacheHeader.imagesTextOffset == 0) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion stubIslandRegion; - stubIslandRegion.start = address; - stubIslandRegion.size = size; - stubIslandRegion.prettyName = subCacheFilename + "::_stubs"; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - } - - // Load .symbols subcache - try { - auto subCachePath = path + ".symbols"; - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - dyld_cache_mapping_info subCacheMapping {}; - BackingCache subCache; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - initialState.backingCaches.push_back(std::move(subCache)); - } - catch (...) - { - m_logger->LogWarn("Failed to locate .symbols subcache. Non-exported symbol information may be missing."); - } - break; - } - case iOS16CacheFormat: - { - dyld_cache_mapping_info mapping {}; - - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - if (primaryCacheHeader.branchPoolsCount) - { - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = - baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); - } - } - - std::string mainFileName = base_name(path); - if (auto projectFile = m_dscView->GetFile()->GetProjectFile()) - mainFileName = projectFile->GetName(); - auto subCacheCount = primaryCacheHeader.subCacheArrayCount; - - dyld_subcache_entry2 _entry {}; - - std::vector<dyld_subcache_entry2> subCacheEntries; - subCacheEntries.reserve(subCacheCount); - for (size_t i = 0; i < subCacheCount; i++) - { - baseFile->Read(&_entry, primaryCacheHeader.subCacheArrayOffset + (i * sizeof(dyld_subcache_entry2)), - sizeof(dyld_subcache_entry2)); - subCacheEntries.push_back(_entry); - } - - baseFile.reset(); - - for (const auto& entry : subCacheEntries) - { - std::string subCachePath; - std::string subCacheFilename; - if (std::string(entry.fileExtension).find('.') != std::string::npos) - { - subCachePath = path + entry.fileExtension; - subCacheFilename = mainFileName + entry.fileExtension; - } - else - { - subCachePath = path + "." + entry.fileExtension; - subCacheFilename = mainFileName + "." + entry.fileExtension; - } - - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - dyld_cache_mapping_info subCacheMapping {}; - - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSecondary; - subCache.path = subCachePath; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - - if (subCachePath.find(".dylddata") != std::string::npos) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion dyldDataRegion; - dyldDataRegion.start = address; - dyldDataRegion.size = size; - dyldDataRegion.prettyName = subCacheFilename + "::_data" + std::to_string(j); - dyldDataRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable); - dyldDataRegion.type = MemoryRegion::Type::DyldData; - nonImageMemoryRegions.push_back(std::move(dyldDataRegion)); - } - } - - initialState.backingCaches.push_back(std::move(subCache)); - - if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 - && subCacheHeader.imagesTextOffset == 0) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion stubIslandRegion; - stubIslandRegion.start = address; - stubIslandRegion.size = size; - stubIslandRegion.prettyName = subCacheFilename + "::_stubs"; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - } - - // Load .symbols subcache - try - { - auto subCachePath = path + ".symbols"; - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (subCacheFile->ReadUInt32(16) > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected, using default size"); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSymbols; - subCache.path = subCachePath; - - dyld_cache_mapping_info subCacheMapping {}; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - initialState.backingCaches.push_back(std::move(subCache)); - } - catch (...) - { - m_logger->LogWarn("Failed to load the symbols cache"); - } - break; - } - } - baseFile.reset(); - - m_logger->LogInfo("Loading images..."); - m_viewSpecificState->progress = LoadProgressLoadingImages; + return std::nullopt; - // We have set up enough metadata to map VM now. + // Read the header, this _should_ be compatible with all known DSC formats. + // Mason: the above is not true! https://github.com/Vector35/binaryninja-api/issues/6073 + dyld_cache_header header = {}; + file->Read(&header, 0, sizeof(header)); - auto vm = GetVMMap(initialState); - if (!vm) + // Read the mappings using the headers `mappingCount` and `mappingOffset`. + dyld_cache_mapping_info currentMapping = {}; + std::vector<dyld_cache_mapping_info> mappings; + for (size_t i = 0; i < header.mappingCount; i++) { - m_logger->LogError("Failed to map VM pages for Shared Cache on initial load, this is fatal."); - return; + file->Read(¤tMapping, header.mappingOffset + (i * sizeof(currentMapping)), sizeof(currentMapping)); + mappings.push_back(currentMapping); } - for (const auto& start : initialState.imageStarts) - { - try - { - auto imageHeader = SharedCache::LoadHeaderForAddress(vm, start.second, start.first); - if (!imageHeader) - { - m_logger->LogError("Failed to load Mach-O header for %s", start.first.c_str()); - continue; - } - if (imageHeader->linkeditPresent && vm->AddressIsMapped(imageHeader->linkeditSegment.vmaddr)) - { - auto mapping = vm->MappingAtAddress(imageHeader->linkeditSegment.vmaddr); - imageHeader->exportTriePath = mapping.first.fileAccessor->filePath(); - } - initialState.headers[start.second] = imageHeader.value(); - CacheImage image; - image.installName = start.first; - image.headerLocation = start.second; - for (const auto& segment : imageHeader->segments) - { - char segName[17]; - memcpy(segName, segment.segname, 16); - segName[16] = 0; - - // Many images include a __LINKEDIT segment that share a single region in the shared cache. - // Reuse the same `MemoryRegion` to represent all of these linkedit regions. - if (std::string(segName) == "__LINKEDIT") - { - if (auto it = initialState.memoryRegions.find(segment.vmaddr); - it != initialState.memoryRegions.end()) - { - image.regionStarts.push_back(it->second.start); - continue; - } - } - MemoryRegion sectionRegion; - sectionRegion.prettyName = imageHeader.value().identifierPrefix + "::" + std::string(segName); - sectionRegion.start = segment.vmaddr; - sectionRegion.size = segment.vmsize; - sectionRegion.imageStart = start.second; - uint32_t flags = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot); - - // if we're positive we have an entry point for some reason, force the segment - // executable. this helps with kernel images. - for (auto &entryPoint : imageHeader->m_entryPoints) - if (segment.vmaddr <= entryPoint && (entryPoint < (segment.vmaddr + segment.filesize))) - flags |= SegmentExecutable; - - sectionRegion.flags = (BNSegmentFlag)flags; - sectionRegion.type = MemoryRegion::Type::Image; - if (auto region = initialState.AddMemoryRegion(std::move(sectionRegion))) - image.regionStarts.push_back(region->start); - } - initialState.images.push_back(image); - } - catch (std::exception& ex) - { - m_logger->LogError("Failed to load Mach-O header for %s: %s", start.first.c_str(), ex.what()); - } - } - - m_logger->LogInfo("Loaded %d Mach-O headers", initialState.headers.size()); - - for (auto& memoryRegion : nonImageMemoryRegions) + // Handle special entry types. + if (fileName.find(".dylddata") != std::string::npos) { - initialState.AddPotentiallyOverlappingMemoryRegion(std::move(memoryRegion)); + // We found a single dyld data cache entry file. Mark it as such! + type = CacheEntryType::DyldData; } - m_logger->LogInfo("Loaded %zu stub island or dyld memory regions", nonImageMemoryRegions.size()); - - for (const auto& cache : initialState.backingCaches) + else if (fileName.find(".symbols") != std::string::npos) { - size_t i = 0; - for (const auto& mapping : cache.mappings) - { - MemoryRegion region; - region.start = mapping.address; - region.size = mapping.size; - region.prettyName = base_name(cache.path) + "::" + std::to_string(i++); - region.flags = SegmentFlagsFromMachOProtections(mapping.initProt, mapping.maxProt); - region.type = MemoryRegion::Type::NonImage; - initialState.AddPotentiallyOverlappingMemoryRegion(std::move(region)); - } + // We found a single symbols cache entry file. Mark it as such! + type = CacheEntryType::Symbols; } - - m_cacheInfo = std::make_shared<CacheInfo>(std::move(initialState)); - m_modifiedState->viewState = DSCViewStateLoaded; - SaveCacheInfoToDSCView(lock); - - m_logger->LogInfo("Finished loading..."); - m_viewSpecificState->progress = LoadProgressFinished; -} - -std::shared_ptr<VM> SharedCache::GetVMMap() -{ - return GetVMMap(*m_cacheInfo); -} - -std::shared_ptr<VM> SharedCache::GetVMMap(const CacheInfo& cacheInfo) -{ - std::shared_ptr<VM> vm = std::make_shared<VM>(0x1000); - - uint64_t baseAddress = cacheInfo.BaseAddress(); - Ref<Logger> logger = m_logger; - for (const auto& cache : cacheInfo.backingCaches) + else if (mappings.size() == 1 && header.imagesCountOld == 0 && header.imagesCount == 0 + && header.imagesTextOffset == 0) { - for (const auto& mapping : cache.mappings) - { - vm->MapPages(m_dscView, m_dscView->GetFile()->GetSessionId(), mapping.address, mapping.fileOffset, mapping.size, cache.path, - [vm, baseAddress, logger](std::shared_ptr<MMappedFileAccessor> mmap){ - ParseAndApplySlideInfoForFile(mmap, baseAddress, logger); - }); - } + // Stub entry file, should only have a single mapping and no images. + // NOTE: If we end up identifying something incorrectly as a stub we need to restrict this further. + // We found a single stub cache entry file. Mark it as such! + type = CacheEntryType::Stub; } - return vm; -} - - -bool SharedCache::DeserializeFromRawView(std::lock_guard<std::mutex>& lock) -{ - std::lock_guard cacheInfoLock(m_viewSpecificState->cacheInfoMutex); - - m_cacheInfo = std::make_shared<CacheInfo>(); - m_modifiedState = std::make_unique<ModifiedState>(); - m_modifiedState->viewState = DSCViewStateUnloaded; - - // First try and load from the view itself. - if (m_viewSpecificState->cacheInfo) + // Gather all images for the entry. + std::unordered_map<std::string, dyld_cache_image_info> images; + dyld_cache_image_info currentImg {}; + for (size_t i = 0; i < header.imagesCount; i++) { - m_cacheInfo = m_viewSpecificState->cacheInfo; - m_modifiedState->viewState = DSCViewStateLoaded; - return true; + file->Read( + ¤tImg, header.imagesOffset + (i * sizeof(dyld_cache_image_info)), sizeof(dyld_cache_image_info)); + auto imagePath = file->ReadNullTermString(currentImg.pathFileOffset); + images.insert_or_assign(imagePath, currentImg); } - // Second try and load from the view metadata. - if (SharedCacheMetadata::ViewHasMetadata(m_dscView)) + // Handle old dyld format that uses old images field. + for (size_t i = 0; i < header.imagesCountOld; i++) { - auto metadata = SharedCacheMetadata::LoadFromView(m_dscView); - if (!metadata) - return false; - - m_viewSpecificState->viewState = metadata->state->viewState.value_or(DSCViewStateUnloaded); - m_viewSpecificState->state = static_cast<State>(std::move(*metadata->state)); - m_viewSpecificState->cacheInfo = std::move(metadata->cacheInfo); - - m_cacheInfo = m_viewSpecificState->cacheInfo; - m_modifiedState->viewState = DSCViewStateLoaded; + file->Read( + ¤tImg, header.imagesOffsetOld + (i * sizeof(dyld_cache_image_info)), sizeof(dyld_cache_image_info)); + auto imagePath = file->ReadNullTermString(currentImg.pathFileOffset); + images.insert_or_assign(imagePath, currentImg); } - return true; -} - - -// static -void SharedCache::ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAccessor> file, uint64_t base, Ref<Logger> logger) -{ - if (file->SlideInfoWasApplied()) - return; - - dyld_cache_header baseHeader; - file->Read(&baseHeader, 0, sizeof(dyld_cache_header)); - - std::vector<std::pair<uint64_t, MappingInfo>> mappings; - - if (baseHeader.slideInfoOffsetUnused) + // NOTE: I am not sure how the header type has changed over time but if apple is replacing fields with other ones + // NOTE: And branchPoolsCount is not zero for earlier shared caches (non split cache ones) than we need to check + // this! Also make pseudo-image for the branch pools, so we can map them in to the binary view. + for (size_t i = 0; i < header.branchPoolsCount; i++) { - // Legacy - - auto slideInfoOff = baseHeader.slideInfoOffsetUnused; - auto slideInfoVersion = file->ReadUInt32(slideInfoOff); - if (slideInfoVersion != 2 && slideInfoVersion != 3) - { - logger->LogError("Unsupported slide info version %d", slideInfoVersion); - throw std::runtime_error("Unsupported slide info version"); - } - - MappingInfo map; - - file->Read(&map.mappingInfo, baseHeader.mappingOffset + sizeof(dyld_cache_mapping_info), sizeof(dyld_cache_mapping_info)); - map.slideInfoVersion = slideInfoVersion; - if (map.slideInfoVersion == 2) - file->Read(&map.slideInfoV2, slideInfoOff, sizeof(dyld_cache_slide_info_v2)); - else if (map.slideInfoVersion == 3) - file->Read(&map.slideInfoV3, slideInfoOff, sizeof(dyld_cache_slide_info_v3)); - - mappings.emplace_back(slideInfoOff, map); + dyld_cache_image_info branchIslandImg = {}; + // TODO: uint64_t means this only works on 64bit... tbh tho this is fine this is a new addition so 32bit doesnt + // apply here. + // TODO: If we want to make this work for other addr sizes we need the binary view in this function. + branchIslandImg.address = header.branchPoolsOffset + (i * sizeof(uint64_t)); + // Mason: why such a long name for the image??? + auto imageName = fmt::format("dyld_shared_cache_branch_islands_{}", i); + images.insert_or_assign(imageName, branchIslandImg); } - else - { - dyld_cache_header targetHeader; - file->Read(&targetHeader, 0, sizeof(dyld_cache_header)); - - if (targetHeader.mappingWithSlideCount == 0) - { - logger->LogDebug("No mappings with slide info found"); - } - - for (auto i = 0; i < targetHeader.mappingWithSlideCount; i++) - { - dyld_cache_mapping_and_slide_info mappingAndSlideInfo; - file->Read(&mappingAndSlideInfo, targetHeader.mappingWithSlideOffset + (i * sizeof(dyld_cache_mapping_and_slide_info)), sizeof(dyld_cache_mapping_and_slide_info)); - if (mappingAndSlideInfo.slideInfoFileOffset) - { - MappingInfo map; - if (mappingAndSlideInfo.size == 0) - continue; - map.slideInfoVersion = file->ReadUInt32(mappingAndSlideInfo.slideInfoFileOffset); - logger->LogDebug("Slide Info Version: %d", map.slideInfoVersion); - map.mappingInfo.address = mappingAndSlideInfo.address; - map.mappingInfo.size = mappingAndSlideInfo.size; - map.mappingInfo.fileOffset = mappingAndSlideInfo.fileOffset; - if (map.slideInfoVersion == 2) - { - file->Read( - &map.slideInfoV2, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info_v2)); - } - else if (map.slideInfoVersion == 3) - { - file->Read( - &map.slideInfoV3, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info_v3)); - map.slideInfoV3.auth_value_add = base; - } - else if (map.slideInfoVersion == 5) - { - file->Read( - &map.slideInfoV5, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info5)); - map.slideInfoV5.value_add = base; - } - else - { - logger->LogError("Unknown slide info version: %d", map.slideInfoVersion); - continue; - } - - uint64_t slideInfoOffset = mappingAndSlideInfo.slideInfoFileOffset; - mappings.emplace_back(slideInfoOffset, map); - logger->LogDebug("Filename: %s", file->Path().c_str()); - logger->LogDebug("Slide Info Offset: 0x%llx", slideInfoOffset); - logger->LogDebug("Mapping Address: 0x%llx", map.mappingInfo.address); - logger->LogDebug("Slide Info v", map.slideInfoVersion); - } - } - } - - if (mappings.empty()) - { - logger->LogDebug("No slide info found"); - file->SetSlideInfoWasApplied(true); - return; - } - - for (const auto& [off, mapping] : mappings) - { - logger->LogDebug("Slide Info Version: %d", mapping.slideInfoVersion); - uint64_t extrasOffset = off; - uint64_t pageStartsOffset = off; - uint64_t pageStartCount; - uint64_t pageSize; - - if (mapping.slideInfoVersion == 2) - { - pageStartsOffset += mapping.slideInfoV2.page_starts_offset; - pageStartCount = mapping.slideInfoV2.page_starts_count; - pageSize = mapping.slideInfoV2.page_size; - extrasOffset += mapping.slideInfoV2.page_extras_offset; - auto cursor = pageStartsOffset; - - for (size_t i = 0; i < pageStartCount; i++) - { - try - { - uint16_t start = file->ReadUShort(cursor); - cursor += sizeof(uint16_t); - if (start == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE) - continue; - - auto rebaseChain = [&](const dyld_cache_slide_info_v2& slideInfo, uint64_t pageContent, uint16_t startOffset) - { - uintptr_t slideAmount = 0; - - auto deltaMask = slideInfo.delta_mask; - auto valueMask = ~deltaMask; - auto valueAdd = slideInfo.value_add; - - auto deltaShift = count_trailing_zeros(deltaMask) - 2; - - uint32_t pageOffset = startOffset; - uint32_t delta = 1; - while ( delta != 0 ) - { - uint64_t loc = pageContent + pageOffset; - try - { - uintptr_t rawValue = file->ReadULong(loc); - delta = (uint32_t)((rawValue & deltaMask) >> deltaShift); - uintptr_t value = (rawValue & valueMask); - if (value != 0) - { - value += valueAdd; - value += slideAmount; - } - pageOffset += delta; - file->WritePointer(loc, value); - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v2 slide pointer at 0x%llx\n", loc); - break; - } - } - }; - - if (start & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) - { - int j=(start & 0x3FFF); - bool done = false; - do - { - uint64_t extraCursor = extrasOffset + (j * sizeof(uint16_t)); - try - { - auto extra = file->ReadUShort(extraCursor); - uint16_t aStart = extra; - uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i); - uint16_t pageStartOffset = (aStart & 0x3FFF)*4; - rebaseChain(mapping.slideInfoV2, page, pageStartOffset); - done = (extra & DYLD_CACHE_SLIDE_PAGE_ATTR_END); - ++j; - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v2 slide extra at 0x%llx\n", cursor); - break; - } - } while (!done); - } - else - { - uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i); - uint16_t pageStartOffset = start*4; - rebaseChain(mapping.slideInfoV2, page, pageStartOffset); - } - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v2 slide info at 0x%llx\n", cursor); - } - } - } - else if (mapping.slideInfoVersion == 3) { - // Slide Info Version 3 Logic - pageStartsOffset += sizeof(dyld_cache_slide_info_v3); - pageStartCount = mapping.slideInfoV3.page_starts_count; - pageSize = mapping.slideInfoV3.page_size; - auto cursor = pageStartsOffset; - - for (size_t i = 0; i < pageStartCount; i++) - { - try - { - uint16_t delta = file->ReadUShort(cursor); - cursor += sizeof(uint16_t); - if (delta == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE) - continue; - - delta = delta/sizeof(uint64_t); // initial offset is byte based - uint64_t loc = mapping.mappingInfo.fileOffset + (pageSize * i); - do - { - loc += delta * sizeof(dyld_cache_slide_pointer3); - try - { - dyld_cache_slide_pointer3 slideInfo = { file->ReadULong(loc) }; - delta = slideInfo.plain.offsetToNextPointer; - if (slideInfo.auth.authenticated) - { - uint64_t value = slideInfo.auth.offsetFromSharedCacheBase; - value += mapping.slideInfoV3.auth_value_add; - file->WritePointer(loc, value); - } - else - { - uint64_t value51 = slideInfo.plain.pointerValue; - uint64_t top8Bits = value51 & 0x0007F80000000000; - uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFF; - uint64_t value = (uint64_t)top8Bits << 13 | bottom43Bits; - file->WritePointer(loc, value); - } - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v3 slide pointer at 0x%llx\n", loc); - break; - } - } while (delta != 0); - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v3 slide info at 0x%llx\n", cursor); - } - } - } - else if (mapping.slideInfoVersion == 5) - { - pageStartsOffset += sizeof(dyld_cache_slide_info5); - pageStartCount = mapping.slideInfoV5.page_starts_count; - pageSize = mapping.slideInfoV5.page_size; - auto cursor = pageStartsOffset; - - for (size_t i = 0; i < pageStartCount; i++) - { - try - { - uint16_t delta = file->ReadUShort(cursor); - cursor += sizeof(uint16_t); - if (delta == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE) - continue; - - delta = delta/sizeof(uint64_t); // initial offset is byte based - uint64_t loc = mapping.mappingInfo.fileOffset + (pageSize * i); - do - { - loc += delta * sizeof(dyld_cache_slide_pointer5); - try - { - dyld_cache_slide_pointer5 slideInfo = { file->ReadULong(loc) }; - delta = slideInfo.regular.next; - if (slideInfo.auth.auth) - { - uint64_t value = mapping.slideInfoV5.value_add + slideInfo.auth.runtimeOffset; - file->WritePointer(loc, value); - } - else - { - uint64_t value = mapping.slideInfoV5.value_add + slideInfo.regular.runtimeOffset; - file->WritePointer(loc, value); - } - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v5 slide pointer at 0x%llx\n", loc); - break; - } - } while (delta != 0); - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v5 slide info at 0x%llx\n", cursor); - } - } - } - } - // logger->LogDebug("Applied slide info for %s (0x%llx rewrites)", file->Path().c_str(), rewrites.size()); - file->SetSlideInfoWasApplied(true); + return CacheEntry(filePath, fileName, type, header, mappings, images); } - -SharedCache::SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) : - m_dscView(dscView), m_viewSpecificState(ViewSpecificStateForView(dscView)) +WeakFileAccessor CacheEntry::GetAccessor() const { - std::lock_guard lock(m_mutex); - m_logger = LogRegistry::GetLogger("SharedCache", dscView->GetFile()->GetSessionId()); - if (dscView->GetTypeName() != VIEW_NAME) - { - // Unreachable? - m_logger->LogError("Attempted to create SharedCache object from non-Shared Cache view"); - return; - } - - ++sharedCacheReferences; - INIT_SHAREDCACHE_API_OBJECT() - if (!DeserializeFromRawView(lock)) - { - // TODO: We need a way to prompt the user and ask if they want to continue (like BNDB version upgrades) - // TODO: To do that we really need to consolidate _where_ SharedCache is called. - // TODO: Specifically the **first** call to this must originate in DSCView, which is NOT the case currently. - m_logger->LogWarn("Metadata was invalid, recreating initial load of shared cache information..."); - } - - if (m_modifiedState->viewState.value_or(m_viewSpecificState->viewState) != DSCViewStateUnloaded) - { - m_viewSpecificState->progress = LoadProgressFinished; - return; - } - - try { - PerformInitialLoad(lock); - } - catch (std::exception& e) - { - m_logger->LogError("Failed to perform initial load of Shared Cache: %s", e.what()); - } - - auto settings = m_dscView->GetLoadSettings(VIEW_NAME); - bool autoLoadLibsystem = true; - if (settings && settings->Contains("loader.dsc.autoLoadLibSystem")) - { - autoLoadLibsystem = settings->Get<bool>("loader.dsc.autoLoadLibSystem", m_dscView); - } - if (autoLoadLibsystem) - { - for (const auto& [_, header] : m_cacheInfo->headers) - { - if (header.installName.find("libsystem_c.dylib") != std::string::npos) - { - m_logger->LogInfo("Loading core libsystem_c.dylib library"); - LoadImageWithInstallName(lock, header.installName, false); - break; - } - } - } -} - -SharedCache::~SharedCache() { - --sharedCacheReferences; + return FileAccessorCache::Global().Open(m_filePath); } -SharedCache* SharedCache::GetFromDSCView(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) +std::optional<uint64_t> CacheEntry::GetHeaderAddress() const { - if (dscView->GetTypeName() != VIEW_NAME) - return nullptr; - try { - return new SharedCache(dscView); - } - catch (...) - { - return nullptr; - } + // The mapping at file offset 0 will contain the header (duh). + return GetMappedAddress(0); } -std::optional<uint64_t> SharedCache::GetImageStart(const std::string_view installName) +std::optional<uint64_t> CacheEntry::GetMappedAddress(uint64_t fileOffset) const { - const auto& imageStarts = m_cacheInfo->imageStarts; - auto it = std::find_if(imageStarts.begin(), imageStarts.end(), [&] (auto image) { - return image.first == installName; - }); - - if (it != imageStarts.end()) - return it->second; - + for (const auto& mapping : m_mappings) + if (mapping.fileOffset <= fileOffset && mapping.fileOffset + mapping.size > fileOffset) + return mapping.address + (fileOffset - mapping.fileOffset); return std::nullopt; } -const SharedCacheMachOHeader* SharedCache::HeaderForAddress(uint64_t address) -{ - // It is very common for `HeaderForAddress` to be called with an address corresponding to a header. - if (auto it = m_cacheInfo->headers.find(address); it != m_cacheInfo->headers.end()) - return &it->second; - - auto it = m_cacheInfo->memoryRegions.find(address); - if (it == m_cacheInfo->memoryRegions.end()) - return nullptr; - - if (auto headerAddress = it->second.imageStart) - return HeaderForAddress(headerAddress); - - // Found a region, but its `imageStart` was 0. This should mean it doesn't belong to an image. - assert(it->second.type != MemoryRegion::Type::Image); - return nullptr; -} - -std::string SharedCache::NameForAddress(uint64_t address) +SharedCache::SharedCache(uint64_t addressSize) { - if (auto it = m_cacheInfo->memoryRegions.find(address); it != m_cacheInfo->memoryRegions.end()) - return it->second.prettyName; - - return ""; + m_addressSize = addressSize; + m_vm = std::make_shared<VirtualMemory>(); } -std::string SharedCache::ImageNameForAddress(uint64_t address) -{ - if (auto header = HeaderForAddress(address)) - return header->identifierPrefix; - - return ""; -} -bool SharedCache::LoadImageContainingAddress(uint64_t address, bool skipObjC) +void SharedCache::AddImage(CacheImage image) { - if (auto header = HeaderForAddress(address)) { - std::lock_guard lock(m_mutex); - return LoadImageWithInstallName(lock, header->installName, skipObjC); - } - - return false; + m_images.insert({image.headerAddress, std::move(image)}); } -bool SharedCache::LoadSectionAtAddress(uint64_t address) +void SharedCache::AddRegion(CacheRegion region) { - std::lock(m_mutex, m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex, std::adopt_lock); - std::lock_guard lock(m_mutex, std::adopt_lock); - - auto vm = GetVMMap(); - if (!vm) { - m_logger->LogError("Failed to map VM pages for Shared Cache."); - return false; - } - - SharedCacheMachOHeader targetHeader; - const CacheImage* targetImage = nullptr; - const MemoryRegion* targetSegment = nullptr; - - auto it = m_cacheInfo->memoryRegions.find(address); - if (it != m_cacheInfo->memoryRegions.end()) + // Handle overlapping regions here. + const auto regionRange = region.AsAddressRange(); + // First region at or past the start of the region. + const auto begin = m_regions.lower_bound(regionRange.start); + if (begin == m_regions.end()) { - const MemoryRegion* region = &it->second; - for (auto& image : m_cacheInfo->images) - { - if (std::find(image.regionStarts.begin(), image.regionStarts.end(), region->start) == image.regionStarts.end()) - continue; - - targetHeader = m_cacheInfo->headers.at(image.headerLocation); - targetImage = ℑ - targetSegment = region; - break; - } + AddNonOverlappingRegion(std::move(region)); + return; } - if (!targetSegment) + // First region past the end of the region. + const auto end = m_regions.lower_bound(regionRange.end); + + for (auto it = begin; it != end; ++it) { - auto regionIt = m_cacheInfo->memoryRegions.find(address); - if (regionIt == m_cacheInfo->memoryRegions.end()) + const uint64_t newRegionSize = it->second.start - region.start; + if (newRegionSize) { - m_logger->LogError("Failed to find a segment containing address 0x%llx", address); - return false; + CacheRegion newRegion(region); + newRegion.size = newRegionSize; + AddNonOverlappingRegion(std::move(newRegion)); } - auto& region = regionIt->second; - if (MemoryRegionIsLoaded(lock, region)) - return true; - - m_logger->LogInfo( - "Loading region of type %d named %s @ 0x%llx", region.type, region.prettyName.c_str(), region.start); - auto targetFile = vm->MappingAtAddress(region.start).first.fileAccessor->lock(); - - auto reader = VMReader(vm); - auto buff = reader.ReadBuffer(region.start, region.size); - - m_dscView->GetMemoryMap()->AddDataMemoryRegion(region.prettyName, region.start, buff, region.flags); - m_dscView->AddUserSection(region.prettyName, region.start, region.size, SectionSemanticsForRegion(region)); - - SetMemoryRegionIsLoaded(lock, region); - - SaveModifiedStateToDSCView(lock); - - m_dscView->AddAnalysisOption("linearsweep"); - m_dscView->UpdateAnalysis(); - - return true; + region.start = it->second.start + it->second.size; + region.size -= (newRegionSize + it->second.size); } - auto id = m_dscView->BeginUndoActions(); - auto reader = VMReader(vm); - - m_logger->LogDebug("Partial loading image %s", targetHeader.installName.c_str()); - - auto targetFile = vm->MappingAtAddress(targetSegment->start).first.fileAccessor->lock(); - auto buff = reader.ReadBuffer(targetSegment->start, targetSegment->size); - m_dscView->GetMemoryMap()->AddDataMemoryRegion(targetSegment->prettyName, targetSegment->start, buff, targetSegment->flags); - - SetMemoryRegionIsLoaded(lock, *targetSegment); - - if (!MemoryRegionIsHeaderInitialized(lock, *targetSegment)) - SharedCache::InitializeHeader(lock, m_dscView, vm.get(), targetHeader, {targetSegment}); - - SaveModifiedStateToDSCView(lock); - - m_dscView->AddAnalysisOption("linearsweep"); - m_dscView->UpdateAnalysis(); - - m_dscView->CommitUndoActions(id); - - return true; + // Add remaining region. + if (region.size > 0) + AddNonOverlappingRegion(std::move(region)); } -static void GetObjCSettings(Ref<BinaryView> view, bool* processObjCMetadata, bool* processCFStrings) +bool SharedCache::AddNonOverlappingRegion(CacheRegion region) { - auto settings = view->GetLoadSettings(VIEW_NAME); - *processCFStrings = true; - *processObjCMetadata = true; - if (settings && settings->Contains("loader.dsc.processCFStrings")) - *processCFStrings = settings->Get<bool>("loader.dsc.processCFStrings", view); - if (settings && settings->Contains("loader.dsc.processObjC")) - *processObjCMetadata = settings->Get<bool>("loader.dsc.processObjC", view); + auto [_, inserted] = m_regions.insert(std::make_pair(region.AsAddressRange(), std::move(region))); + return inserted; } -static void ProcessObjCSectionsForImageWithName(std::string baseName, std::shared_ptr<VM> vm, std::shared_ptr<DSCObjC::DSCObjCProcessor> objc, bool processCFStrings, bool processObjCMetadata, Ref<Logger> logger) +void SharedCache::AddSymbol(CacheSymbol symbol) { - try - { - if (processObjCMetadata) - objc->ProcessObjCData(baseName); - if (processCFStrings) - objc->ProcessCFStrings(baseName); - } - catch (const std::exception& ex) - { - logger->LogWarn("Error processing ObjC data for image %s: %s", baseName.c_str(), ex.what()); - } - catch (...) - { - logger->LogWarn("Error processing ObjC data for image %s", baseName.c_str()); - } + m_symbols.insert({symbol.address, std::move(symbol)}); } -void SharedCache::ProcessObjCSectionsForImageWithInstallName(std::string installName) +void SharedCache::AddSymbols(std::vector<CacheSymbol> symbols) { - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - if (!processObjCMetadata && !processCFStrings) - return; - - auto objc = std::make_shared<DSCObjC::DSCObjCProcessor>(m_dscView, this, false); - auto vm = GetVMMap(); - - ProcessObjCSectionsForImageWithName(base_name(installName), vm, objc, processCFStrings, processObjCMetadata, m_logger); -} - -void SharedCache::ProcessAllObjCSections() -{ - std::lock_guard lock(m_mutex); - ProcessAllObjCSections(lock); + for (auto& symbol : symbols) + m_symbols.insert({symbol.address, std::move(symbol)}); } -void SharedCache::ProcessAllObjCSections(std::lock_guard<std::mutex>& lock) +CacheEntryId SharedCache::AddEntry(CacheEntry entry) { - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - if (!processObjCMetadata && !processCFStrings) - return; + // TODO: Maybe check to see if we already added the file? + // TODO: I doubt we will ever accidentally call this for the same entry... + // This is monotonically increasing so you can tell how many times we have called this function :) + CacheEntryId id = nextId++; - auto objc = std::make_shared<DSCObjC::DSCObjCProcessor>(m_dscView, this, false); - auto vm = GetVMMap(); + // Get the file accessor to associate with the virtual memory region. + auto fileAccessor = FileAccessorCache::Global().Open(entry.GetFilePath()); - std::set<uint64_t> processedImageHeaders; - for (auto region : GetMappedRegions()) + // Populate virtual memory using the entry mappings, by doing so we can now + // read the memory of the mapped regions of the cache entry file. + const auto& mappings = entry.GetMappings(); + for (const auto& mapping : mappings) { - // Don't repeat the same images multiple times - auto header = HeaderForAddress(region->start); - if (!header) - continue; - if (processedImageHeaders.find(header->textBase) != processedImageHeaders.end()) - continue; - processedImageHeaders.insert(header->textBase); + m_vm->MapRegion(fileAccessor, {mapping.address, mapping.address + mapping.size}, mapping.fileOffset); - ProcessObjCSectionsForImageWithName(header->identifierPrefix, vm, objc, processCFStrings, processObjCMetadata, m_logger); + // Recalculate the base address. + if (mapping.address < m_baseAddress || m_baseAddress == 0) + m_baseAddress = mapping.address; } -} -bool SharedCache::LoadImageWithInstallName(std::string installName, bool skipObjC) -{ - std::lock_guard lock(m_mutex); - return LoadImageWithInstallName(lock, installName, skipObjC); + // We are done and can make the entry visible to the entire cache. + m_entries.insert({id, std::move(entry)}); + return id; } -bool SharedCache::LoadImageWithInstallName(std::lock_guard<std::mutex>& lock, std::string installName, bool skipObjC) +bool SharedCache::ProcessEntryImage(const std::string& path, const dyld_cache_image_info& info) { - auto settings = m_dscView->GetLoadSettings(VIEW_NAME); - - std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - - m_logger->LogInfo("Loading image %s", installName.c_str()); - - auto vm = GetVMMap(); - const CacheImage* targetImage = nullptr; - - for (auto& cacheImage : m_cacheInfo->images) - { - if (cacheImage.installName == installName) - { - targetImage = &cacheImage; - break; - } - } - - if (!targetImage) - { - m_logger->LogError("Failed to find target image %s", installName.c_str()); - return false; - } - - auto it = m_cacheInfo->headers.find(targetImage->headerLocation); - if (it == m_cacheInfo->headers.end()) - { - m_logger->LogError("Failed to find target image header %s", installName.c_str()); - return false; - } - - const auto& header = it->second; - - auto id = m_dscView->BeginUndoActions(); - m_modifiedState->viewState = DSCViewStateLoadedWithImages; - - auto reader = VMReader(vm); - reader.Seek(targetImage->headerLocation); - - std::vector<const MemoryRegion*> regionsToLoad; - regionsToLoad.reserve(targetImage->regionStarts.size()); - - for (auto regionStart : targetImage->regionStarts) - { - const auto& region = m_cacheInfo->memoryRegions.find(regionStart)->second; - bool allowLoadingLinkedit = false; - if (settings && settings->Contains("loader.dsc.allowLoadingLinkeditSegments")) - allowLoadingLinkedit = settings->Get<bool>("loader.dsc.allowLoadingLinkeditSegments", m_dscView); - if ((region.prettyName.find("__LINKEDIT") != std::string::npos) && !allowLoadingLinkedit) - continue; - - if (MemoryRegionIsLoaded(lock, region)) - { - m_logger->LogDebug("Skipping region %s as it is already loaded.", region.prettyName.c_str()); - continue; - } - - auto targetFile = vm->MappingAtAddress(region.start).first.fileAccessor->lock(); - auto buff = reader.ReadBuffer(region.start, region.size); - m_dscView->GetMemoryMap()->AddDataMemoryRegion(region.prettyName, region.start, buff, region.flags); - - SetMemoryRegionIsLoaded(lock, region); - regionsToLoad.push_back(®ion); - } - - // Regions for this image are already loaded, skip un-needed analysis! - if (regionsToLoad.empty()) + auto imageHeader = SharedCacheMachOHeader::ParseHeaderForAddress(m_vm, info.address, path); + if (!imageHeader.has_value()) return false; - auto typeLib = TypeLibraryForImage(header.installName); - - auto h = SharedCache::LoadHeaderForAddress(vm, targetImage->headerLocation, installName); - if (!h) - { - SaveModifiedStateToDSCView(lock); - return false; - } - - SharedCache::InitializeHeader(lock, m_dscView, vm.get(), *h, regionsToLoad); - SaveModifiedStateToDSCView(lock); + // Add the image to the cache. + CacheImage image; + image.headerAddress = info.address; + image.path = path; - if (!skipObjC) + // Add all image regions. + for (const auto& segment : imageHeader->segments) { - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - ProcessObjCSectionsForImageWithName(h->identifierPrefix, vm, std::make_shared<DSCObjC::DSCObjCProcessor>(m_dscView, this, false), processCFStrings, processObjCMetadata, m_logger); - } - - m_dscView->AddAnalysisOption("linearsweep"); - m_dscView->UpdateAnalysis(); - - m_dscView->CommitUndoActions(id); - - return true; -} - -std::optional<SharedCacheMachOHeader> SharedCache::LoadHeaderForAddress(std::shared_ptr<VM> vm, uint64_t address, std::string installName) -{ - SharedCacheMachOHeader header; - - header.textBase = address; - header.installName = installName; - header.identifierPrefix = base_name(installName); + char segName[17]; + memcpy(segName, segment.segname, 16); + segName[16] = 0; - std::string errorMsg; - // address is a Raw file offset - VMReader reader(vm); - reader.Seek(address); - - header.ident.magic = reader.Read32(); - - BNEndianness endianness; - if (header.ident.magic == MH_MAGIC || header.ident.magic == MH_MAGIC_64) - endianness = LittleEndian; - else if (header.ident.magic == MH_CIGAM || header.ident.magic == MH_CIGAM_64) - endianness = BigEndian; - else - { - return {}; - } - - reader.SetEndianness(endianness); - header.ident.cputype = reader.Read32(); - header.ident.cpusubtype = reader.Read32(); - header.ident.filetype = reader.Read32(); - header.ident.ncmds = reader.Read32(); - header.ident.sizeofcmds = reader.Read32(); - header.ident.flags = reader.Read32(); - if ((header.ident.cputype & MachOABIMask) == MachOABI64) // address size == 8 - { - header.ident.reserved = reader.Read32(); - } - header.loadCommandOffset = reader.GetOffset(); - - bool first = true; - // Parse segment commands - try - { - for (size_t i = 0; i < header.ident.ncmds; i++) + // Many images include a __LINKEDIT segment that share a single region in the shared cache. + // Reuse the same `MemoryRegion` to represent all of these link edit regions. + // Check to see if we have a shared region, if so skip it. + if (std::string(segName) == "__LINKEDIT") { - // BNLogInfo("of 0x%llx", reader.GetOffset()); - load_command load; - segment_command_64 segment64; - section_64 sect; - memset(§, 0, sizeof(sect)); - size_t curOffset = reader.GetOffset(); - load.cmd = reader.Read32(); - load.cmdsize = reader.Read32(); - size_t nextOffset = curOffset + load.cmdsize; - if (load.cmdsize < sizeof(load_command)) - return {}; - - switch (load.cmd) - { - case LC_MAIN: + // TODO: Loosen this to any shared region? + if (const auto linkEditRegion = GetRegionAt(segment.vmaddr)) { - uint64_t entryPoint = reader.Read64(); - header.entryPoints.push_back({entryPoint, true}); - (void)reader.Read64(); // Stack start - break; - } - case LC_SEGMENT: // map the 32bit version to 64 bits - segment64.cmd = LC_SEGMENT_64; - reader.Read(&segment64.segname, 16); - segment64.vmaddr = reader.Read32(); - segment64.vmsize = reader.Read32(); - segment64.fileoff = reader.Read32(); - segment64.filesize = reader.Read32(); - segment64.maxprot = reader.Read32(); - segment64.initprot = reader.Read32(); - segment64.nsects = reader.Read32(); - segment64.flags = reader.Read32(); - if (first) - { - if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) - || (segment64.flags & MACHO_VM_PROT_WRITE)) - { - header.relocationBase = segment64.vmaddr; - first = false; - } - } - for (size_t j = 0; j < segment64.nsects; j++) - { - reader.Read(§.sectname, 16); - reader.Read(§.segname, 16); - sect.addr = reader.Read32(); - sect.size = reader.Read32(); - sect.offset = reader.Read32(); - sect.align = reader.Read32(); - sect.reloff = reader.Read32(); - sect.nreloc = reader.Read32(); - sect.flags = reader.Read32(); - sect.reserved1 = reader.Read32(); - sect.reserved2 = reader.Read32(); - // if the segment isn't mapped into virtual memory don't add the corresponding sections. - if (segment64.vmsize > 0) - { - header.sections.push_back(sect); - } - if (!strncmp(sect.sectname, "__mod_init_func", 15)) - header.moduleInitSections.push_back(sect); - if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - header.symbolStubSections.push_back(sect); - if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - } - header.segments.push_back(segment64); - break; - case LC_SEGMENT_64: - segment64.cmd = LC_SEGMENT_64; - reader.Read(&segment64.segname, 16); - segment64.vmaddr = reader.Read64(); - segment64.vmsize = reader.Read64(); - segment64.fileoff = reader.Read64(); - segment64.filesize = reader.Read64(); - segment64.maxprot = reader.Read32(); - segment64.initprot = reader.Read32(); - segment64.nsects = reader.Read32(); - segment64.flags = reader.Read32(); - if (strncmp(segment64.segname, "__LINKEDIT", 10) == 0) - { - header.linkeditSegment = segment64; - header.linkeditPresent = true; - } - if (first) - { - if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) - || (segment64.flags & MACHO_VM_PROT_WRITE)) - { - header.relocationBase = segment64.vmaddr; - first = false; - } - } - for (size_t j = 0; j < segment64.nsects; j++) - { - reader.Read(§.sectname, 16); - reader.Read(§.segname, 16); - sect.addr = reader.Read64(); - sect.size = reader.Read64(); - sect.offset = reader.Read32(); - sect.align = reader.Read32(); - sect.reloff = reader.Read32(); - sect.nreloc = reader.Read32(); - sect.flags = reader.Read32(); - sect.reserved1 = reader.Read32(); - sect.reserved2 = reader.Read32(); - sect.reserved3 = reader.Read32(); - // if the segment isn't mapped into virtual memory don't add the corresponding sections. - if (segment64.vmsize > 0) - { - header.sections.push_back(sect); - } - - if (!strncmp(sect.sectname, "__mod_init_func", 15)) - header.moduleInitSections.push_back(sect); - if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - header.symbolStubSections.push_back(sect); - if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - } - header.segments.push_back(segment64); - break; - case LC_ROUTINES: // map the 32bit version to 64bits - header.routines64.cmd = LC_ROUTINES_64; - header.routines64.init_address = reader.Read32(); - header.routines64.init_module = reader.Read32(); - header.routines64.reserved1 = reader.Read32(); - header.routines64.reserved2 = reader.Read32(); - header.routines64.reserved3 = reader.Read32(); - header.routines64.reserved4 = reader.Read32(); - header.routines64.reserved5 = reader.Read32(); - header.routines64.reserved6 = reader.Read32(); - header.routinesPresent = true; - break; - case LC_ROUTINES_64: - header.routines64.cmd = LC_ROUTINES_64; - header.routines64.init_address = reader.Read64(); - header.routines64.init_module = reader.Read64(); - header.routines64.reserved1 = reader.Read64(); - header.routines64.reserved2 = reader.Read64(); - header.routines64.reserved3 = reader.Read64(); - header.routines64.reserved4 = reader.Read64(); - header.routines64.reserved5 = reader.Read64(); - header.routines64.reserved6 = reader.Read64(); - header.routinesPresent = true; - break; - case LC_FUNCTION_STARTS: - header.functionStarts.funcoff = reader.Read32(); - header.functionStarts.funcsize = reader.Read32(); - header.functionStartsPresent = true; - break; - case LC_SYMTAB: - header.symtab.symoff = reader.Read32(); - header.symtab.nsyms = reader.Read32(); - header.symtab.stroff = reader.Read32(); - header.symtab.strsize = reader.Read32(); - break; - case LC_DYSYMTAB: - header.dysymtab.ilocalsym = reader.Read32(); - header.dysymtab.nlocalsym = reader.Read32(); - header.dysymtab.iextdefsym = reader.Read32(); - header.dysymtab.nextdefsym = reader.Read32(); - header.dysymtab.iundefsym = reader.Read32(); - header.dysymtab.nundefsym = reader.Read32(); - header.dysymtab.tocoff = reader.Read32(); - header.dysymtab.ntoc = reader.Read32(); - header.dysymtab.modtaboff = reader.Read32(); - header.dysymtab.nmodtab = reader.Read32(); - header.dysymtab.extrefsymoff = reader.Read32(); - header.dysymtab.nextrefsyms = reader.Read32(); - header.dysymtab.indirectsymoff = reader.Read32(); - header.dysymtab.nindirectsyms = reader.Read32(); - header.dysymtab.extreloff = reader.Read32(); - header.dysymtab.nextrel = reader.Read32(); - header.dysymtab.locreloff = reader.Read32(); - header.dysymtab.nlocrel = reader.Read32(); - header.dysymPresent = true; - break; - case LC_DYLD_CHAINED_FIXUPS: - header.chainedFixups.dataoff = reader.Read32(); - header.chainedFixups.datasize = reader.Read32(); - header.chainedFixupsPresent = true; - break; - case LC_DYLD_INFO: - case LC_DYLD_INFO_ONLY: - header.dyldInfo.rebase_off = reader.Read32(); - header.dyldInfo.rebase_size = reader.Read32(); - header.dyldInfo.bind_off = reader.Read32(); - header.dyldInfo.bind_size = reader.Read32(); - header.dyldInfo.weak_bind_off = reader.Read32(); - header.dyldInfo.weak_bind_size = reader.Read32(); - header.dyldInfo.lazy_bind_off = reader.Read32(); - header.dyldInfo.lazy_bind_size = reader.Read32(); - header.dyldInfo.export_off = reader.Read32(); - header.dyldInfo.export_size = reader.Read32(); - header.exportTrie.dataoff = header.dyldInfo.export_off; - header.exportTrie.datasize = header.dyldInfo.export_size; - header.exportTriePresent = true; - header.dyldInfoPresent = true; - break; - case LC_DYLD_EXPORTS_TRIE: - header.exportTrie.dataoff = reader.Read32(); - header.exportTrie.datasize = reader.Read32(); - header.exportTriePresent = true; - break; - case LC_THREAD: - case LC_UNIXTHREAD: - /*while (reader.GetOffset() < nextOffset) - { - - thread_command thread; - thread.flavor = reader.Read32(); - thread.count = reader.Read32(); - switch (m_archId) - { - case MachOx64: - m_logger->LogDebug("x86_64 Thread state\n"); - if (thread.flavor != X86_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statex64, sizeof(thread.statex64)); - header.entryPoints.push_back({thread.statex64.rip, false}); - break; - case MachOx86: - m_logger->LogDebug("x86 Thread state\n"); - if (thread.flavor != X86_THREAD_STATE32) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statex86, sizeof(thread.statex86)); - header.entryPoints.push_back({thread.statex86.eip, false}); - break; - case MachOArm: - m_logger->LogDebug("Arm Thread state\n"); - if (thread.flavor != _ARM_THREAD_STATE) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statearmv7, sizeof(thread.statearmv7)); - header.entryPoints.push_back({thread.statearmv7.r15, false}); - break; - case MachOAarch64: - case MachOAarch6432: - m_logger->LogDebug("Aarch64 Thread state\n"); - if (thread.flavor != _ARM_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - reader.Read(&thread.stateaarch64, sizeof(thread.stateaarch64)); - header.entryPoints.push_back({thread.stateaarch64.pc, false}); - break; - case MachOPPC: - m_logger->LogDebug("PPC Thread state\n"); - if (thread.flavor != PPC_THREAD_STATE) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //Read individual entries for endian reasons - header.entryPoints.push_back({reader.Read32(), false}); - (void)reader.Read32(); - (void)reader.Read32(); - //Read the rest of the structure - (void)reader.Read(&thread.stateppc.r1, sizeof(thread.stateppc) - (3 * 4)); - break; - case MachOPPC64: - m_logger->LogDebug("PPC64 Thread state\n"); - if (thread.flavor != PPC_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - header.entryPoints.push_back({reader.Read64(), false}); - (void)reader.Read64(); - (void)reader.Read64(); // Stack start - (void)reader.Read(&thread.stateppc64.r1, sizeof(thread.stateppc64) - (3 * 8)); - break; - default: - m_logger->LogError("Unknown archid: %x", m_archId); - } - - }*/ - break; - case LC_LOAD_DYLIB: - { - uint32_t offset = reader.Read32(); - if (offset < nextOffset) - { - reader.Seek(curOffset + offset); - std::string libname = reader.ReadCString(reader.GetOffset()); - header.dylibs.push_back(libname); - } - } - break; - case LC_BUILD_VERSION: - { - // m_logger->LogDebug("LC_BUILD_VERSION:"); - header.buildVersion.platform = reader.Read32(); - header.buildVersion.minos = reader.Read32(); - header.buildVersion.sdk = reader.Read32(); - header.buildVersion.ntools = reader.Read32(); - // m_logger->LogDebug("Platform: %s", BuildPlatformToString(header.buildVersion.platform).c_str()); - // m_logger->LogDebug("MinOS: %s", BuildToolVersionToString(header.buildVersion.minos).c_str()); - // m_logger->LogDebug("SDK: %s", BuildToolVersionToString(header.buildVersion.sdk).c_str()); - for (uint32_t j = 0; (i < header.buildVersion.ntools) && (j < 10); j++) - { - uint32_t tool = reader.Read32(); - uint32_t version = reader.Read32(); - header.buildToolVersions.push_back({tool, version}); - // m_logger->LogDebug("Build Tool: %s: %s", BuildToolToString(tool).c_str(), - // BuildToolVersionToString(version).c_str()); - } - break; - } - case LC_FILESET_ENTRY: - { - throw ReadException(); - } - default: - // m_logger->LogDebug("Unhandled command: %s : %" PRIu32 "\n", CommandToString(load.cmd).c_str(), - // load.cmdsize); - break; - } - if (reader.GetOffset() != nextOffset) - { - // m_logger->LogDebug("Didn't parse load command: %s fully %" PRIx64 ":%" PRIxPTR, - // CommandToString(load.cmd).c_str(), reader.GetOffset(), nextOffset); - } - reader.Seek(nextOffset); - } - - for (auto& section : header.sections) - { - char sectionName[17]; - memcpy(sectionName, section.sectname, sizeof(section.sectname)); - sectionName[16] = 0; - if (header.identifierPrefix.empty()) - header.sectionNames.push_back(sectionName); - else - header.sectionNames.push_back(header.identifierPrefix + "::" + sectionName); - } - } - catch (ReadException&) - { - return {}; - } - - return header; -} - - -void SharedCache::ProcessSymbols(std::shared_ptr<MMappedFileAccessor> file, const SharedCacheMachOHeader& header, uint64_t stringsOffset, size_t stringsSize, uint64_t nlistEntriesOffset, uint32_t nlistCount, uint32_t nlistStartIndex) -{ - auto addressSize = m_dscView->GetAddressSize(); - auto strings = file->ReadBuffer(stringsOffset, stringsSize); - - std::vector<Ref<Symbol>> symbolList; - for (uint64_t i = 0; i < nlistCount; i++) - { - uint64_t entryIndex = (nlistStartIndex + i); - - nlist_64 nlist = {}; - if (addressSize == 4) - { - // 32-bit DSC - struct nlist nlist32 = {}; - file->Read(&nlist, nlistEntriesOffset + (entryIndex * sizeof(nlist32)), sizeof(nlist32)); - nlist.n_strx = nlist32.n_strx; - nlist.n_type = nlist32.n_type; - nlist.n_sect = nlist32.n_sect; - nlist.n_desc = nlist32.n_desc; - nlist.n_value = nlist32.n_value; - } - else - { - // 64-bit DSC - file->Read(&nlist, nlistEntriesOffset + (entryIndex * sizeof(nlist)), sizeof(nlist)); - } - - auto symbolAddress = nlist.n_value; - if (((nlist.n_type & N_TYPE) == N_INDR) || symbolAddress == 0) - continue; - - if (nlist.n_strx >= stringsSize) - { - m_logger->LogError("Symbol entry at index %llu has a string offset of %u which is outside the strings buffer of size %llu for file %s", entryIndex, nlist.n_strx, stringsSize, file->Path().c_str()); - continue; - } - - std::string symbolName((char*)strings.GetDataAt(nlist.n_strx)); - if (symbolName == "<redacted>") - continue; - - std::optional<BNSymbolType> symbolType; - if ((nlist.n_type & N_TYPE) == N_SECT && nlist.n_sect > 0 && (size_t)(nlist.n_sect - 1) < header.sections.size()) - { - symbolType = DataSymbol; - } - else if ((nlist.n_type & N_TYPE) == N_ABS) - { - symbolType = DataSymbol; - } - else if ((nlist.n_type & N_EXT)) - { - symbolType = ExternalSymbol; - } - - if (!symbolType.has_value()) - { - m_logger->LogError("Symbol %s at address %" PRIx64 " has unknown symbol type", symbolName.c_str(), symbolAddress); - continue; - } - - std::optional<uint32_t> flags; - for (auto s : header.sections) - { - if (s.addr <= symbolAddress && symbolAddress < s.addr + s.size) - { - flags = s.flags; - } - } - - if (symbolType != ExternalSymbol) - { - if (!flags.has_value()) - { - m_logger->LogError("Symbol %s at address %" PRIx64 " is not in any section", symbolName.c_str(), symbolAddress); + image.regionStarts.push_back(linkEditRegion->start); continue; } - - if ((flags.value() & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS - || (flags.value() & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) - symbolType = FunctionSymbol; - else - symbolType = DataSymbol; } - if ((nlist.n_desc & N_ARM_THUMB_DEF) == N_ARM_THUMB_DEF) - symbolAddress++; - QualifiedName demangledName = {}; - std::string shortName = symbolName; - if (DemangleLLVM(symbolName, demangledName, true)) - shortName = demangledName.GetString(); - Ref<Symbol> sym = new Symbol(symbolType.value(), shortName, shortName, symbolName, symbolAddress, nullptr); - symbolList.emplace_back(sym); - } + CacheRegion sectionRegion; + sectionRegion.type = CacheRegionType::Image; + sectionRegion.name = imageHeader->identifierPrefix + "::" + std::string(segName); + sectionRegion.start = segment.vmaddr; + sectionRegion.size = segment.vmsize; + // Associate this region with this image, this makes it easier to identify what image owns this region. + sectionRegion.imageStart = image.headerAddress; - auto symListPtr = std::make_shared<std::vector<Ref<Symbol>>>(std::move(symbolList)); - m_modifiedState->symbolInfos.emplace(header.textBase, symListPtr); -} + uint32_t flags = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot); + // if we're positive we have an entry point for some reason, force the segment + // executable. this helps with kernel images. + for (const auto& entryPoint : imageHeader->m_entryPoints) + if (segment.vmaddr <= entryPoint && (entryPoint < (segment.vmaddr + segment.filesize))) + flags |= SegmentExecutable; + sectionRegion.flags = static_cast<BNSegmentFlag>(flags); -void SharedCache::ApplySymbol(Ref<BinaryView> view, Ref<TypeLibrary> typeLib, Ref<Symbol> symbol) -{ - Ref<Function> func = nullptr; - auto symbolAddress = symbol->GetAddress(); - - if (symbol->GetType() == FunctionSymbol) - { - Ref<Platform> targetPlatform = view->GetDefaultPlatform(); - func = view->AddFunctionForAnalysis(targetPlatform, symbolAddress); + // Add the image section to the cache and also to the image region starts + AddRegion(sectionRegion); + image.regionStarts.push_back(sectionRegion.start); } - if (typeLib) - { - auto type = m_dscView->ImportTypeLibraryObject(typeLib, {symbol->GetFullName()}); - // TODO: This is still auto - if (type) - view->DefineAutoSymbolAndVariableOrFunction(view->GetDefaultPlatform(), symbol, type); - else - view->DefineAutoUserSymbol(symbol); - } - else - { - view->DefineAutoUserSymbol(symbol); - } + // Add the exported symbols to the available symbols. + std::vector<CacheSymbol> exportSymbols = imageHeader->ReadExportSymbolTrie(*m_vm); + AddSymbols(std::move(exportSymbols)); - if (!func) - func = view->GetAnalysisFunction(view->GetDefaultPlatform(), symbolAddress); - if (func) - { - if (symbol->GetFullName() == "_objc_msgSend") - { - func->SetHasVariableArguments(false); - } - else if (symbol->GetFullName().find("_objc_retain_x") != std::string::npos || symbol->GetFullName().find("_objc_release_x") != std::string::npos) - { - auto x = symbol->GetFullName().rfind("x"); - auto num = symbol->GetFullName().substr(x + 1); - - std::vector<BinaryNinja::FunctionParameter> callTypeParams; - auto cc = m_dscView->GetDefaultArchitecture()->GetCallingConventionByName("apple-arm64-objc-fast-arc-" + num); + // This is behind a shared pointer as the header itself is very large. + // TODO: Make this a unique pointer? I think the image should own the header at this point? + image.header = std::make_shared<SharedCacheMachOHeader>(*imageHeader); - callTypeParams.push_back({"obj", m_dscView->GetTypeByName({ "id" }), true, BinaryNinja::Variable()}); - - auto funcType = BinaryNinja::Type::FunctionType(m_dscView->GetTypeByName({ "id" }), cc, callTypeParams); - func->SetUserType(funcType); - } - } + AddImage(std::move(image)); + return true; } - -void SharedCache::InitializeHeader( - std::lock_guard<std::mutex>& lock, - Ref<BinaryView> view, VM* vm, const SharedCacheMachOHeader& header, std::vector<const MemoryRegion*> regionsToLoad) +// At this point all relevant mapping should be loaded in the virtual memory. +void SharedCache::ProcessEntryImages(const CacheEntry& entry) { - Ref<Settings> settings = view->GetLoadSettings(VIEW_NAME); - bool applyFunctionStarts = true; - if (settings && settings->Contains("loader.dsc.processFunctionStarts")) - applyFunctionStarts = settings->Get<bool>("loader.dsc.processFunctionStarts", view); - - for (size_t i = 0; i < header.sections.size(); i++) - { - bool skip = true; - for (const auto& region : regionsToLoad) - { - if (header.sections[i].addr >= region->start && header.sections[i].addr < region->start + region->size) - { - if (!MemoryRegionIsHeaderInitialized(lock, *region)) - skip = false; - break; - } - } - if (!header.sections[i].size || skip) - continue; - - std::string type; - BNSectionSemantics semantics = DefaultSectionSemantics; - switch (header.sections[i].flags & 0xff) - { - case S_REGULAR: - if (header.sections[i].flags & S_ATTR_PURE_INSTRUCTIONS) - { - type = "PURE_CODE"; - semantics = ReadOnlyCodeSectionSemantics; - } - else if (header.sections[i].flags & S_ATTR_SOME_INSTRUCTIONS) - { - type = "CODE"; - semantics = ReadOnlyCodeSectionSemantics; - } - else - { - type = "REGULAR"; - } - break; - case S_ZEROFILL: - type = "ZEROFILL"; - semantics = ReadWriteDataSectionSemantics; - break; - case S_CSTRING_LITERALS: - type = "CSTRING_LITERALS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_4BYTE_LITERALS: - type = "4BYTE_LITERALS"; - break; - case S_8BYTE_LITERALS: - type = "8BYTE_LITERALS"; - break; - case S_LITERAL_POINTERS: - type = "LITERAL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_NON_LAZY_SYMBOL_POINTERS: - type = "NON_LAZY_SYMBOL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_LAZY_SYMBOL_POINTERS: - type = "LAZY_SYMBOL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_SYMBOL_STUBS: - type = "SYMBOL_STUBS"; - semantics = ReadOnlyCodeSectionSemantics; - break; - case S_MOD_INIT_FUNC_POINTERS: - type = "MOD_INIT_FUNC_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_MOD_TERM_FUNC_POINTERS: - type = "MOD_TERM_FUNC_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_COALESCED: - type = "COALESCED"; - break; - case S_GB_ZEROFILL: - type = "GB_ZEROFILL"; - semantics = ReadWriteDataSectionSemantics; - break; - case S_INTERPOSING: - type = "INTERPOSING"; - break; - case S_16BYTE_LITERALS: - type = "16BYTE_LITERALS"; - break; - case S_DTRACE_DOF: - type = "DTRACE_DOF"; - break; - case S_LAZY_DYLIB_SYMBOL_POINTERS: - type = "LAZY_DYLIB_SYMBOL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_THREAD_LOCAL_REGULAR: - type = "THREAD_LOCAL_REGULAR"; - break; - case S_THREAD_LOCAL_ZEROFILL: - type = "THREAD_LOCAL_ZEROFILL"; - break; - case S_THREAD_LOCAL_VARIABLES: - type = "THREAD_LOCAL_VARIABLES"; - break; - case S_THREAD_LOCAL_VARIABLE_POINTERS: - type = "THREAD_LOCAL_VARIABLE_POINTERS"; - break; - case S_THREAD_LOCAL_INIT_FUNCTION_POINTERS: - type = "THREAD_LOCAL_INIT_FUNCTION_POINTERS"; - break; - default: - type = "UNKNOWN"; - break; - } - if (i >= header.sectionNames.size()) - break; - if (strncmp(header.sections[i].sectname, "__text", sizeof(header.sections[i].sectname)) == 0) - semantics = ReadOnlyCodeSectionSemantics; - if (strncmp(header.sections[i].sectname, "__const", sizeof(header.sections[i].sectname)) == 0) - semantics = ReadOnlyDataSectionSemantics; - if (strncmp(header.sections[i].sectname, "__data", sizeof(header.sections[i].sectname)) == 0) - semantics = ReadWriteDataSectionSemantics; - if (strncmp(header.sections[i].segname, "__DATA_CONST", sizeof(header.sections[i].segname)) == 0) - semantics = ReadOnlyDataSectionSemantics; - - view->AddUserSection(header.sectionNames[i], header.sections[i].addr, header.sections[i].size, semantics, - type, header.sections[i].align); - } - - auto typeLib = view->GetTypeLibrary(header.installName); + for (const auto& [imagePath, imageInfo] : entry.GetImages()) + ProcessEntryImage(imagePath, imageInfo); +} - BinaryReader virtualReader(view); +// At this point all relevant mapping should be loaded in the virtual memory. +void SharedCache::ProcessEntryRegions(const CacheEntry& entry) +{ + auto entryHeader = entry.GetHeader(); - bool applyHeaderTypes = false; - for (const auto& region : regionsToLoad) + // Collect pool addresses as non image memory regions. + for (size_t i = 0; i < entryHeader.branchPoolsCount; i++) { - if (header.textBase >= region->start && header.textBase < region->start + region->size) - { - if (!MemoryRegionIsHeaderInitialized(lock, *region)) - applyHeaderTypes = true; - + auto branchPoolAddr = entryHeader.branchPoolsOffset + (i * m_addressSize); + auto header = SharedCacheMachOHeader::ParseHeaderForAddress( + m_vm, branchPoolAddr, "dyld_shared_cache_branch_islands_" + std::to_string(i)); + // Stop processing branch pools if a header fails to parse. + if (!header.has_value()) break; - } - } - if (applyHeaderTypes) - { - view->DefineDataVariable(header.textBase, Type::NamedType(view, QualifiedName("mach_header_64"))); - view->DefineAutoSymbol( - new Symbol(DataSymbol, "__macho_header::" + header.identifierPrefix, header.textBase, LocalBinding)); - try + // Gather all non image regions from the branch islands. + for (const auto& segment : header->segments) { - virtualReader.Seek(header.textBase + sizeof(mach_header_64)); - size_t sectionNum = 0; - for (size_t i = 0; i < header.ident.ncmds; i++) - { - load_command load; - uint64_t curOffset = virtualReader.GetOffset(); - load.cmd = virtualReader.Read32(); - load.cmdsize = virtualReader.Read32(); - uint64_t nextOffset = curOffset + load.cmdsize; - switch (load.cmd) - { - case LC_SEGMENT: - { - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("segment_command"))); - virtualReader.SeekRelative(5 * 8); - size_t numSections = virtualReader.Read32(); - virtualReader.SeekRelative(4); - for (size_t j = 0; j < numSections; j++) - { - view->DefineDataVariable( - virtualReader.GetOffset(), Type::NamedType(view, QualifiedName("section"))); - view->DefineUserSymbol(new Symbol(DataSymbol, - "__macho_section::" + header.identifierPrefix + "_[" + std::to_string(sectionNum++) + "]", - virtualReader.GetOffset(), LocalBinding)); - virtualReader.SeekRelative((8 * 8) + 4); - } - break; - } - case LC_SEGMENT_64: - { - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("segment_command_64"))); - virtualReader.SeekRelative(7 * 8); - size_t numSections = virtualReader.Read32(); - virtualReader.SeekRelative(4); - for (size_t j = 0; j < numSections; j++) - { - view->DefineDataVariable( - virtualReader.GetOffset(), Type::NamedType(view, QualifiedName("section_64"))); - view->DefineUserSymbol(new Symbol(DataSymbol, - "__macho_section_64::" + header.identifierPrefix + "_[" + std::to_string(sectionNum++) + "]", - virtualReader.GetOffset(), LocalBinding)); - virtualReader.SeekRelative(10 * 8); - } - break; - } - case LC_SYMTAB: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("symtab"))); - break; - case LC_DYSYMTAB: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dysymtab"))); - break; - case LC_UUID: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("uuid"))); - break; - case LC_ID_DYLIB: - case LC_LOAD_DYLIB: - case LC_REEXPORT_DYLIB: - case LC_LOAD_WEAK_DYLIB: - case LC_LOAD_UPWARD_DYLIB: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dylib_command"))); - if (load.cmdsize - 24 <= 150) - view->DefineDataVariable( - curOffset + 24, Type::ArrayType(Type::IntegerType(1, true), load.cmdsize - 24)); - break; - case LC_CODE_SIGNATURE: - case LC_SEGMENT_SPLIT_INFO: - case LC_FUNCTION_STARTS: - case LC_DATA_IN_CODE: - case LC_DYLIB_CODE_SIGN_DRS: - case LC_DYLD_EXPORTS_TRIE: - case LC_DYLD_CHAINED_FIXUPS: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("linkedit_data"))); - break; - case LC_ENCRYPTION_INFO: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("encryption_info"))); - break; - case LC_VERSION_MIN_MACOSX: - case LC_VERSION_MIN_IPHONEOS: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("version_min"))); - break; - case LC_DYLD_INFO: - case LC_DYLD_INFO_ONLY: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dyld_info"))); - break; - default: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("load_command"))); - break; - } + CacheRegion stubIslandRegion; + stubIslandRegion.start = segment.vmaddr; + stubIslandRegion.size = segment.filesize; + char segName[17]; + memcpy(segName, segment.segname, 16); + segName[16] = 0; + std::string segNameStr = std::string(segName); + stubIslandRegion.name = fmt::format("dyld_shared_cache_branch_islands_{}::{}", i, segNameStr); + stubIslandRegion.flags = static_cast<BNSegmentFlag>(SegmentReadable | SegmentExecutable); + stubIslandRegion.type = CacheRegionType::StubIsland; - view->DefineAutoSymbol(new Symbol(DataSymbol, - "__macho_load_command::" + header.identifierPrefix + "_[" + std::to_string(i) + "]", curOffset, - LocalBinding)); - virtualReader.Seek(nextOffset); - } - } - catch (ReadException&) - { - LogError("Error when applying Mach-O header types at %" PRIx64, header.textBase); - } - } - - if (applyFunctionStarts && header.functionStartsPresent && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) - { - auto funcStarts = - vm->MappingAtAddress(header.linkeditSegment.vmaddr) - .first.fileAccessor->lock() - ->ReadBuffer(header.functionStarts.funcoff, header.functionStarts.funcsize); - uint64_t curfunc = header.textBase; - uint64_t curOffset; - - auto current = static_cast<const uint8_t*>(funcStarts.GetData()); - auto end = current + funcStarts.GetLength(); - while (current != end) - { - curOffset = readLEB128(current, end); - bool addFunction = false; - for (const auto& region : regionsToLoad) - { - if (curfunc >= region->start && curfunc < region->start + region->size) - { - if (!MemoryRegionIsHeaderInitialized(lock, *region)) - addFunction = true; - } - } - // LogError("0x%llx, 0x%llx", header.textBase, curOffset); - if (curOffset == 0 || !addFunction) - continue; - curfunc += curOffset; - uint64_t target = curfunc; - Ref<Platform> targetPlatform = view->GetDefaultPlatform(); - view->AddFunctionForAnalysis(targetPlatform, target); + // Add the stub islands to the cache. + AddRegion(std::move(stubIslandRegion)); } } - if (header.symtab.symoff != 0 && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) - { - // Mach-O View symtab processing with - // a ton of stuff cut out so it can work - auto reader = vm->MappingAtAddress(header.linkeditSegment.vmaddr).first.fileAccessor->lock(); - ProcessSymbols( - reader, - header, - header.symtab.stroff, - header.symtab.strsize, - header.symtab.symoff, - header.symtab.nsyms - ); - } - - view->BeginBulkModifySymbols(); - for (const auto& symbol : *m_modifiedState->symbolInfos[header.textBase]) - ApplySymbol(view, typeLib, symbol); + // Get the mapping. + const auto& entryMappings = entry.GetMappings(); - if (header.exportTriePresent && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) + // Add the mapping regions for the given entry type. + // By default, we will just add all the mappings as read-write. + switch (entry.GetType()) { - auto symbols = GetExportListForHeader(lock, header, [&]() { - return vm->MappingAtAddress(header.linkeditSegment.vmaddr).first.fileAccessor->lock(); - }); - - for (const auto& [symbolAddress, symbol] : *symbols) - ApplySymbol(view, typeLib, symbol); - } - view->EndBulkModifySymbols(); - - for (auto region : regionsToLoad) - { - SetMemoryRegionHeaderInitialized(lock, *region); - } -} - - -void SharedCache::ReadExportNode(std::vector<Ref<Symbol>>& symbolList, const SharedCacheMachOHeader& header, - const uint8_t* begin, const uint8_t* end, const uint8_t* current, uint64_t textBase, const std::string& currentText) -{ - if (current >= end) - throw ReadException(); - - uint64_t terminalSize = readValidULEB128(current, end); - const uint8_t* child = current + terminalSize; - if (terminalSize != 0) + case CacheEntryType::DyldData: { - uint64_t flags = readValidULEB128(current, end); - if (!(flags & EXPORT_SYMBOL_FLAGS_REEXPORT)) + size_t lastMappingIndex = 0; + for (const auto& mapping : entryMappings) { - uint64_t imageOffset = readValidULEB128(current, end); - if (!currentText.empty() && textBase + imageOffset) - { - uint32_t flags; - BNSymbolType type; - for (auto s : header.sections) - { - if (s.addr < textBase + imageOffset) - { - if (s.addr + s.size > textBase + imageOffset) - { - flags = s.flags; - break; - } - } - } - if ((flags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS - || (flags & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) - type = FunctionSymbol; - else - type = DataSymbol; + CacheRegion mappingRegion; + mappingRegion.start = mapping.address; + mappingRegion.size = mapping.size; + mappingRegion.name = fmt::format("{}::_data_{}", entry.GetFileName(), lastMappingIndex++); + mappingRegion.flags = SegmentReadable; + mappingRegion.type = CacheRegionType::DyldData; -#if EXPORT_TRIE_DEBUG - // BNLogInfo("export: %s -> 0x%llx", n.text.c_str(), image.baseAddress + n.offset); -#endif - auto symbol = new Symbol(type, currentText, textBase + imageOffset, nullptr); - symbolList.emplace_back(symbol); - } + // Add the dyld data mapping as a region to the cache. + AddRegion(std::move(mappingRegion)); } + break; } - current = child; - uint8_t childCount = *current++; - std::string childText = currentText; - for (uint8_t i = 0; i < childCount; ++i) - { - if (current >= end) - throw ReadException(); - auto it = std::find(current, end, 0); - childText.append(current, it); - current = it + 1; - if (current >= end) - throw ReadException(); - auto next = readValidULEB128(current, end); - if (next == 0) - throw ReadException(); - ReadExportNode(symbolList, header, begin, end, begin + next, textBase, childText); - childText.resize(currentText.size()); - } -} - - -std::vector<Ref<Symbol>> SharedCache::ParseExportTrie(std::shared_ptr<MMappedFileAccessor> linkeditFile, const SharedCacheMachOHeader& header) -{ - if (!header.exportTrie.datasize) - return {}; - - try - { - std::vector<Ref<Symbol>> symbols; - auto [begin, end] = linkeditFile->ReadSpan(header.exportTrie.dataoff, header.exportTrie.datasize); - ReadExportNode(symbols, header, begin, end, begin, header.textBase, ""); - return symbols; - } - catch (std::exception& e) - { - BNLogError("Failed to load Export Trie"); - return {}; - } -} - -std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> SharedCache::GetExistingExportListForBaseAddress(std::lock_guard<std::mutex>&, uint64_t baseAddress) const { - if (auto it = m_modifiedState->exportInfos.find(baseAddress); it != m_modifiedState->exportInfos.end()) - return it->second; - - std::lock_guard viewSpecificStateLock(m_viewSpecificState->stateMutex); - if (auto it = m_viewSpecificState->state.exportInfos.find(baseAddress); it != m_viewSpecificState->state.exportInfos.end()) - return it->second; - - return nullptr; -} - - -std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> SharedCache::GetExportListForHeader( - std::lock_guard<std::mutex>& lock, const SharedCacheMachOHeader& header, - std::function<std::shared_ptr<MMappedFileAccessor>()> provideLinkeditFile, bool* didModifyExportList) -{ - if (auto exportList = GetExistingExportListForBaseAddress(lock, header.textBase)) - { - if (didModifyExportList) - *didModifyExportList = false; - - return exportList; - } - - std::shared_ptr<MMappedFileAccessor> linkeditFile = provideLinkeditFile(); - if (!linkeditFile) + case CacheEntryType::Stub: { - if (didModifyExportList) - *didModifyExportList = false; - - return nullptr; - } + // Stub entry file, should only have a single mapping and no images. + auto stubMapping = entryMappings[0]; + CacheRegion stubIslandRegion; + stubIslandRegion.start = stubMapping.address; + stubIslandRegion.size = stubMapping.size; + stubIslandRegion.name = fmt::format("{}::_stubs", entry.GetFileName()); + stubIslandRegion.flags = static_cast<BNSegmentFlag>(SegmentReadable | SegmentExecutable); + stubIslandRegion.type = CacheRegionType::StubIsland; - // FIXME: This is the only place ParseExportTrie is used, it can be optimized for the output we need here. - std::vector<Ref<Symbol>> exportList = SharedCache::ParseExportTrie(linkeditFile, header); - auto exportMapping = std::make_shared<std::unordered_map<uint64_t, Ref<Symbol>>>(exportList.size()); - for (auto& sym : exportList) - { - exportMapping->insert_or_assign(sym->GetAddress(), std::move(sym)); + // Add the stub island to the cache. + AddRegion(std::move(stubIslandRegion)); } - - m_modifiedState->exportInfos.emplace(header.textBase, exportMapping); - if (didModifyExportList) - *didModifyExportList = true; - - return exportMapping; -} - - -std::vector<std::string> SharedCache::GetAvailableImages() -{ - std::vector<std::string> installNames; - installNames.reserve(m_cacheInfo->headers.size()); - for (const auto& header : m_cacheInfo->headers) + default: { - installNames.push_back(header.second.installName); - } - return installNames; -} - - -std::unordered_map<std::string, std::vector<Ref<Symbol>>> SharedCache::LoadAllSymbolsAndWait() -{ - std::lock(m_mutex, m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex, std::adopt_lock); - std::lock_guard lock(m_mutex, std::adopt_lock); - - bool doSave = false; - std::unordered_map<std::string, std::vector<Ref<Symbol>>> symbolsByImageName(m_cacheInfo->images.size()); - - for (const auto& img : m_cacheInfo->images) - { - auto header = HeaderForAddress(img.headerLocation); - auto exportList = GetExportListForHeader(lock, *header, [&]() { - try { - return MapFile(header->exportTriePath); - } - catch (...) - { - m_logger->LogWarn("Serious Error: Failed to open export trie %s for %s", - header->exportTriePath.c_str(), - header->installName.c_str()); - return std::shared_ptr<MMappedFileAccessor>(nullptr); - } - }, &doSave); - - if (!exportList) - continue; - - auto& symbols = symbolsByImageName[img.installName]; - symbols.reserve(exportList->size()); - for (const auto& [_, symbol] : *exportList) + // Fill in all the gaps in the mapping with non image regions. + size_t lastMappingIndex = 0; + for (const auto& mapping : entryMappings) { - symbols.push_back(symbol); + // Add the remaining gap. + CacheRegion nonImageRegion; + nonImageRegion.start = mapping.address; + nonImageRegion.size = mapping.size; + nonImageRegion.name = fmt::format("{}::{}", entry.GetFileName(), lastMappingIndex++); + nonImageRegion.flags = static_cast<BNSegmentFlag>(SegmentReadable | SegmentWritable); + nonImageRegion.type = CacheRegionType::NonImage; + AddRegion(std::move(nonImageRegion)); } + break; } - - // Only save to DSC view if a header was actually loaded - if (doSave) - SaveModifiedStateToDSCView(lock); - - return symbolsByImageName; -} - - -std::string SharedCache::SerializedImageHeaderForAddress(uint64_t address) -{ - auto header = HeaderForAddress(address); - if (header) - { - return header->AsString(); - } - return ""; -} - - -std::string SharedCache::SerializedImageHeaderForName(std::string name) -{ - if (auto it = m_cacheInfo->imageStarts.find(name); it != m_cacheInfo->imageStarts.end()) - { - if (auto header = HeaderForAddress(it->second)) - return header->AsString(); - } - return ""; -} - -Ref<TypeLibrary> SharedCache::TypeLibraryForImage(const std::string& installName) -{ - std::lock_guard lock(m_viewSpecificState->typeLibraryMutex); - if (auto it = m_viewSpecificState->typeLibraries.find(installName); it != m_viewSpecificState->typeLibraries.end()) - return it->second; - - auto typeLib = m_dscView->GetTypeLibrary(installName); - if (!typeLib) - { - auto typeLibs = m_dscView->GetDefaultPlatform()->GetTypeLibrariesByName(installName); - if (!typeLibs.empty()) - { - typeLib = typeLibs[0]; - m_dscView->AddTypeLibrary(typeLib); - } } - - m_viewSpecificState->typeLibraries[installName] = typeLib; - return typeLib; } -void SharedCache::FindSymbolAtAddrAndApplyToAddr( - uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) +void SharedCache::ProcessEntrySlideInfo(const CacheEntry& entry) { - std::lock_guard lock(m_mutex); - - std::string prefix = ""; - if (symbolLocation != targetLocation) - prefix = "j_"; - - if (auto targetSymbol = m_dscView->GetSymbolByAddress(targetLocation)) - { - // A symbol already exists at the target location. If the source and target address are the same, - // there's nothing more to do. If they're different but the symbol has the `j_` prefix that is added - // to stubs, there's also nothing more to do. - if (symbolLocation == targetLocation || targetSymbol->GetFullName().find("j_") != std::string::npos) - return; - } - - if (symbolLocation != targetLocation) - { - if (auto symbol = m_dscView->GetSymbolByAddress(symbolLocation)) - { - // A symbol already exists at the source location. Add a stub symbol at `targetLocation` based on the existing symbol. - auto id = m_dscView->BeginUndoActions(); - if (m_dscView->GetAnalysisFunction(m_dscView->GetDefaultPlatform(), targetLocation)) - m_dscView->DefineUserSymbol(new Symbol(FunctionSymbol, prefix + symbol->GetFullName(), targetLocation)); - else - m_dscView->DefineUserSymbol(new Symbol(symbol->GetType(), prefix + symbol->GetFullName(), targetLocation)); - m_dscView->ForgetUndoActions(id); - return; - } - } - - // No existing symbol was found at `symbolLocation` or `targetLocation`. Search the export list - // for the image containing `symbolLocation` to find a symbol corresponding to that address. - - auto header = HeaderForAddress(symbolLocation); - if (!header) - return; - - auto exportList = GetExportListForHeader(lock, *header, [&]() { - try { - return MapFile(header->exportTriePath); - } catch (...) { - m_logger->LogWarn("Serious Error: Failed to open export trie %s for %s", header->exportTriePath.c_str(), header->installName.c_str()); - return std::shared_ptr<MMappedFileAccessor>(nullptr); - } - }); - - if (!exportList) - return; - - auto it = exportList->find(symbolLocation); - if (it == exportList->end()) - return; - - const auto& symbol = it->second; - auto id = m_dscView->BeginUndoActions(); - auto typeLib = TypeLibraryForImage(header->installName); - auto type = typeLib ? m_dscView->ImportTypeLibraryObject(typeLib, {symbol->GetFullName()}) : nullptr; - - if (auto func = m_dscView->GetAnalysisFunction(m_dscView->GetDefaultPlatform(), targetLocation)) - { - m_dscView->DefineUserSymbol( - new Symbol(FunctionSymbol, prefix + symbol->GetFullName(), targetLocation)); - if (type) - func->SetUserType(type); - if (triggerReanalysis) - func->Reanalyze(); - } - else - { - m_dscView->DefineUserSymbol( - new Symbol(symbol->GetType(), prefix + symbol->GetFullName(), targetLocation)); - if (type) - m_dscView->DefineUserDataVariable(targetLocation, type); - } - - m_dscView->ForgetUndoActions(id); + auto slideInfoProcessor = SlideInfoProcessor(GetBaseAddress()); + slideInfoProcessor.ProcessEntry(*m_vm, entry); } - -bool SharedCache::SaveCacheInfoToDSCView(std::lock_guard<std::mutex>&) +std::optional<CacheEntry> SharedCache::GetEntryContaining(const uint64_t address) const { - if (!m_dscView) - return false; - - // The initial load should only populate `m_cacheInfo` and should not modify any state. - assert(m_modifiedState->exportInfos.size() == 0); - assert(m_modifiedState->symbolInfos.size() == 0); - assert(m_modifiedState->memoryRegionStatus.size() == 0); - - auto data = m_cacheInfo->AsMetadata(); - m_dscView->StoreMetadata(SharedCacheMetadata::Tag, data); - + for (const auto& [_, entry] : m_entries) { - std::lock_guard lock(m_viewSpecificState->cacheInfoMutex); - if (m_cacheInfo) + for (const auto& mapping : entry.GetMappings()) { - if (!m_viewSpecificState->cacheInfo) - m_viewSpecificState->cacheInfo = m_cacheInfo; - - // At this point we expect cache info and view state cache to be the same. - assert(m_viewSpecificState->cacheInfo == m_cacheInfo); + if (address >= mapping.address && address < mapping.address + mapping.size) + return entry; } } - m_metadataValid = true; - return true; + return std::nullopt; } -bool SharedCache::SaveModifiedStateToDSCView(std::lock_guard<std::mutex>&) +std::optional<CacheEntry> SharedCache::GetEntryWithImage(const CacheImage& image) const { - if (!m_dscView) - return false; - + for (const auto& [_, entry] : m_entries) { - std::lock_guard lock(m_viewSpecificState->stateMutex); - - uint64_t modificationNumber = m_viewSpecificState->savedModifications++; - if (modificationNumber == 0) - { - // The cached state in the view-specific state has not yet been saved. - // For the initial load of a shared cache this will be empty, but if - // the shared cache has been loaded from a database then this will - // contain the full state that was saved. - std::string metadataKey = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(modificationNumber); - auto data = m_viewSpecificState->state.AsMetadata(m_viewSpecificState->viewState); - - m_dscView->StoreMetadata(metadataKey, data); - modificationNumber = m_viewSpecificState->savedModifications++; - } - - std::string metadataKey = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(modificationNumber); - auto data = m_modifiedState->AsMetadata(); - - m_dscView->StoreMetadata(metadataKey, data); - - Ref<Metadata> count = new Metadata(m_viewSpecificState->savedModifications); - m_dscView->StoreMetadata(SharedCacheMetadata::ModifiedStateCountTag, count); - - m_viewSpecificState->state.exportInfos.merge(m_modifiedState->exportInfos); - m_viewSpecificState->state.symbolInfos.merge(m_modifiedState->symbolInfos); - // `merge` will move a node to the target map if the corresponding key does not yet exist. - // If we've redundantly loaded symbols, we may be left with symbols in the source maps. - m_modifiedState->exportInfos.clear(); - m_modifiedState->symbolInfos.clear(); - - for (auto& [region, status] : m_modifiedState->memoryRegionStatus) + for (const auto& [_, currentImage] : entry.GetImages()) { - m_viewSpecificState->state.memoryRegionStatus[region] = status; + if (currentImage.address == image.headerAddress) + return entry; } - m_modifiedState->memoryRegionStatus.clear(); - - // Clean up any metadata entries past the current modification number. - // These can happen after being loaded from a database as all modifications are - // merged into a single state object and the modification count is reset to zero. - for (size_t i = modificationNumber + 1; i < std::numeric_limits<size_t>::max(); ++i) - { - std::string metadataKey = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(i); - bool done = true; - if (m_dscView->QueryMetadata(metadataKey)) - { - done = false; - m_dscView->RemoveMetadata(metadataKey); - } - if (m_dscView->GetParentView()->QueryMetadata(metadataKey)) - { - done = false; - m_dscView->GetParentView()->RemoveMetadata(metadataKey); - } - if (done) - break; - } - } - - if (m_modifiedState->viewState) - { - m_viewSpecificState->viewState = m_modifiedState->viewState.value(); - m_modifiedState->viewState = std::nullopt; } - m_metadataValid = true; - - return true; -} - - -std::vector<const MemoryRegion*> SharedCache::GetMappedRegions() const -{ - std::scoped_lock lock(m_mutex, m_viewSpecificState->stateMutex); - - std::vector<const MemoryRegion*> regions; - regions.reserve(m_viewSpecificState->state.memoryRegionStatus.size() + m_modifiedState->memoryRegionStatus.size()); - for (auto& [regionStart, status] : m_viewSpecificState->state.memoryRegionStatus) - { - if (status.loaded) - { - const auto* region = &m_cacheInfo->memoryRegions.find(regionStart)->second; - regions.push_back(region); - } - } - for (auto& [regionStart, status] : m_modifiedState->memoryRegionStatus) - { - if (status.loaded) - { - const auto* region = &m_cacheInfo->memoryRegions.find(regionStart)->second; - regions.push_back(region); - } - } - std::sort(regions.begin(), regions.end()); - regions.erase(std::unique(regions.begin(), regions.end()), regions.end()); - return regions; -} - -bool SharedCache::IsMemoryMapped(uint64_t address) -{ - return m_dscView->IsValidOffset(address); -} - -void Serialize(SerializationContext& context, const dyld_cache_mapping_info& value) -{ - context.writer.StartArray(); - Serialize(context, value.address); - Serialize(context, value.size); - Serialize(context, value.fileOffset); - Serialize(context, value.maxProt); - Serialize(context, value.initProt); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<dyld_cache_mapping_info>& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - for (auto& s : bArr) - { - dyld_cache_mapping_info mapping; - auto s2 = s.GetArray(); - mapping.address = s2[0].GetUint64(); - mapping.size = s2[1].GetUint64(); - mapping.fileOffset = s2[2].GetUint64(); - mapping.maxProt = s2[3].GetUint(); - mapping.initProt = s2[4].GetUint(); - b.push_back(mapping); - } -} - -void Deserialize( - DeserializationContext& context, std::string_view name, std::optional<std::pair<uint64_t, uint64_t>>& value) -{ - if (!context.doc.HasMember(name.data())) - { - value = std::nullopt; - return; - } - - auto array = context.doc[name.data()].GetArray(); - value = {array[0].GetUint64(), array[1].GetUint64()}; -} - -void Serialize(SerializationContext& context, const AddressRange& value) -{ - Serialize(context, std::make_pair(value.start, value.end)); -} - -void Deserialize(DeserializationContext& context, std::string_view name, AddressRange& value) -{ - auto array = context.doc[name.data()].GetArray(); - value = {array[0].GetUint64(), array[1].GetUint64()}; -} - -void Serialize(SerializationContext& context, const MemoryRegionStatus& status) -{ - context.writer.StartArray(); - Serialize(context, status.loaded); - Serialize(context, status.headerInitialized); - context.writer.EndArray(); -} - -void Deserialize( - DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, MemoryRegionStatus>& statuses) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& pair : array) - { - auto statusArray = pair[1].GetArray(); - MemoryRegionStatus status; - status.loaded = statusArray[0].GetBool(); - status.headerInitialized = statusArray[1].GetBool(); - statuses[pair[0].GetUint64()] = std::move(status); - } -} - -void Serialize(SerializationContext& context, const Ref<Symbol>& value) -{ - context.writer.StartArray(); - Serialize(context, value->GetRawNameRef()); - Serialize(context, value->GetAddress()); - Serialize(context, value->GetType()); - context.writer.EndArray(); -} - -void Serialize(SerializationContext& context, const std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>& value) -{ - context.writer.StartArray(); - for (const auto& [_, symbol] : *value) - { - Serialize(context, symbol); - } - context.writer.EndArray(); -} - -void Serialize(SerializationContext& context, const std::shared_ptr<std::vector<Ref<Symbol>>>& value) -{ - Serialize(context, *value); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::optional<DSCViewState>& viewState) -{ - auto& value = context.doc[name.data()]; - if (value.IsNull()) - viewState = std::nullopt; - else - viewState = (DSCViewState)value.GetUint(); -} - -void SharedCache::CacheInfo::Store(SerializationContext& context) const -{ - Serialize(context, "metadataVersion", METADATA_VERSION); - - MSS(backingCaches); - MSS(headers); - MSS(images); - MSS(imageStarts); - MSS(memoryRegions); - MSS(objcOptimizationDataRange); - MSS(baseFilePath); - MSS_CAST(cacheFormat, uint8_t); + return std::nullopt; } -// static -std::optional<SharedCache::CacheInfo> SharedCache::CacheInfo::Load(DeserializationContext& context) +std::optional<CacheRegion> SharedCache::GetRegionAt(const uint64_t address) const { - if (!context.doc.HasMember("metadataVersion")) - { - LogError("Shared Cache metadata version missing"); + const auto it = m_regions.find(address); + if (it == m_regions.end()) return std::nullopt; - } - - if (context.doc["metadataVersion"].GetUint() != METADATA_VERSION) - return std::nullopt; - - CacheInfo cacheInfo; - cacheInfo.MSL(backingCaches); - cacheInfo.MSL(headers); - cacheInfo.MSL(images); - cacheInfo.MSL(imageStarts); - cacheInfo.MSL(memoryRegions); - cacheInfo.MSL(objcOptimizationDataRange); - cacheInfo.MSL(baseFilePath); - cacheInfo.MSL_CAST(cacheFormat, uint8_t, SharedCacheFormat); - - // Older metadata may be missing the `imageStart` field on `MemoryRegion`. - bool regionsMissingImageStart = - std::any_of(cacheInfo.memoryRegions.begin(), cacheInfo.memoryRegions.end(), [](const auto& pair) { - const auto& region = pair.second; - return region.type == MemoryRegion::Type::Image && region.imageStart == 0; - }); - - if (regionsMissingImageStart) - { - for (const auto& [start, header] : cacheInfo.headers) - { - for (const auto& segment : header.segments) - { - const auto regionIt = cacheInfo.memoryRegions.find(segment.vmaddr); - assert(regionIt != cacheInfo.memoryRegions.end()); - auto& region = regionIt->second; - assert(!region.imageStart || region.imageStart == start); - region.imageStart = start; - } - } - } - - return cacheInfo; -} -void State::Store(SerializationContext& context, std::optional<DSCViewState> viewState) const -{ - MSS(memoryRegionStatus); - MSS(viewState); + return it->second; } -void SharedCache::ModifiedState::Store(SerializationContext& context) const +std::optional<CacheRegion> SharedCache::GetRegionContaining(const uint64_t address) const { - State::Store(context, viewState); -} - -SharedCache::ModifiedState SharedCache::ModifiedState::Load(DeserializationContext& context) -{ - SharedCache::ModifiedState state; - state.MSL(memoryRegionStatus); - state.MSL(viewState); - return state; -} - -SharedCache::ModifiedState SharedCache::ModifiedState::LoadAll(BinaryNinja::BinaryView *dscView, const CacheInfo& cacheInfo) -{ - uint64_t stateCount = dscView->GetUIntMetadata(SharedCacheMetadata::ModifiedStateCountTag); - SharedCache::ModifiedState state; - for (uint64_t i = 0; i < stateCount; ++i) - { - std::string key = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(i); - std::string serialized = dscView->GetStringMetadata(key); - auto thisState = SharedCache::ModifiedState::LoadFromString(serialized); - state.Merge(std::move(thisState)); - } - return state; -} - -void SharedCache::ModifiedState::Merge(SharedCache::ModifiedState&& newer) -{ - memoryRegionStatus.merge(newer.memoryRegionStatus); - exportInfos.merge(newer.exportInfos); - symbolInfos.merge(newer.symbolInfos); - - if (newer.viewState) - viewState = newer.viewState; -} - -void BackingCache::Store(SerializationContext& context) const -{ - MSS(path); - MSS_CAST(cacheType, uint32_t); - MSS(mappings); -} - -BackingCache BackingCache::Load(DeserializationContext& context) -{ - BackingCache cache; - cache.MSL(path); - cache.MSL_CAST(cacheType, uint32_t, BNBackingCacheType); - cache.MSL(mappings); - return cache; -} - -void CacheImage::Store(SerializationContext& context) const -{ - MSS(installName); - MSS(headerLocation); - MSS(regionStarts); -} - -// static -CacheImage CacheImage::Load(DeserializationContext& context) -{ - CacheImage cacheImage; - cacheImage.MSL(installName); - cacheImage.MSL(headerLocation); - cacheImage.MSL(regionStarts); - return cacheImage; -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<BackingCache>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& value: array) - b.push_back(BackingCache::LoadFromValue(value)); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<CacheImage>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& value: array) - b.push_back(CacheImage::LoadFromValue(value)); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, SharedCacheMachOHeader>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& pair_value : array) - { - auto pair = pair_value.GetArray(); - b[pair[0].GetUint64()] = SharedCacheMachOHeader::LoadFromValue(pair[1]); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, AddressRangeMap<MemoryRegion>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& key_value : array) - { - auto key_value_pair = key_value.GetArray(); - auto key_pair = key_value_pair[0].GetArray(); - AddressRange key = {key_pair[0].GetUint64(), key_pair[1].GetUint64()}; - b[key] = MemoryRegion::LoadFromValue(key_value_pair[1]); - } -} - -const std::vector<BackingCache>& SharedCache::BackingCaches() const -{ - return m_cacheInfo->backingCaches; -} - -DSCViewState SharedCache::ViewState() const { - { - std::lock_guard lock(m_mutex); - if (auto& viewState = m_modifiedState->viewState) - return *viewState; - } - - return m_viewSpecificState->viewState; -} - -const std::unordered_map<std::string, uint64_t>& SharedCache::AllImageStarts() const -{ - return m_cacheInfo->imageStarts; -} - -const std::unordered_map<uint64_t, SharedCacheMachOHeader>& SharedCache::AllImageHeaders() const -{ - return m_cacheInfo->headers; -} - -uint64_t SharedCache::CacheInfo::BaseAddress() const -{ - uint64_t base = std::numeric_limits<uint64_t>::max(); - for (const auto& backingCache : backingCaches) - { - for (const auto& mapping : backingCache.mappings) - { - if (mapping.address < base) - { - base = mapping.address; - break; - } - } - } - return base; -} - -// Intentionally takes a copy to avoid modifying the cursor position in the original reader. -std::optional<ObjCOptimizationHeader> SharedCache::GetObjCOptimizationHeader(VMReader reader) const -{ - if (!m_cacheInfo->objcOptimizationDataRange) - return {}; - - ObjCOptimizationHeader header{}; - // Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior. - reader.Read(&header, m_cacheInfo->BaseAddress() + m_cacheInfo->objcOptimizationDataRange->first, sizeof(ObjCOptimizationHeader)); - - return header; -} - -uint64_t SharedCache::GetObjCRelativeMethodBaseAddress(const VMReader& reader) const -{ - if (auto header = GetObjCOptimizationHeader(reader); header.has_value()) - return m_cacheInfo->BaseAddress() + header->relativeMethodSelectorBaseAddressOffset; - return 0; + for (const auto& [range, region] : m_regions) + if (address >= range.start && address < range.end) + return region; + return std::nullopt; } -std::shared_ptr<MMappedFileAccessor> SharedCache::MapFile(const std::string& path) +std::optional<CacheImage> SharedCache::GetImageAt(const uint64_t address) const { - uint64_t baseAddress = m_cacheInfo->BaseAddress(); - return MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), path, - [baseAddress, logger = m_logger](std::shared_ptr<MMappedFileAccessor> mmap) { - ParseAndApplySlideInfoForFile(mmap, baseAddress, logger); - }) - ->lock(); + const auto it = m_images.find(address); + if (it == m_images.end()) + return std::nullopt; + return it->second; } -std::shared_ptr<MMappedFileAccessor> SharedCache::MapFileWithoutApplyingSlide(const std::string& path) +std::optional<CacheImage> SharedCache::GetImageContaining(const uint64_t address) const { - return std::make_shared<MMappedFileAccessor>(path); + // TODO: What if we are using this on a shared region? Return a list of images? + auto region = GetRegionContaining(address); + if (region.has_value()) + return GetImageAt(*region->imageStart); + return std::nullopt; } -const std::string SharedCacheMetadata::Tag = "SHAREDCACHE-SharedCacheData"; -const std::string SharedCacheMetadata::CacheInfoTag = "SHAREDCACHE-CacheInfo"; -const std::string SharedCacheMetadata::ModifiedStateTagPrefix = "SHAREDCACHE-ModifiedState-"; -const std::string SharedCacheMetadata::ModifiedStateCountTag = "SHAREDCACHE-ModifiedState-Count"; - -SharedCacheMetadata::~SharedCacheMetadata() = default; -SharedCacheMetadata::SharedCacheMetadata(SharedCacheMetadata&&) = default; -SharedCacheMetadata& SharedCacheMetadata::operator=(SharedCacheMetadata&&) = default; - -SharedCacheMetadata::SharedCacheMetadata(SharedCache::CacheInfo cacheInfo, SharedCache::ModifiedState state) : - cacheInfo(std::make_unique<SharedCache::CacheInfo>(std::move(cacheInfo))), - state(std::make_unique<SharedCache::ModifiedState>(std::move(state))) -{} - - -// static -bool SharedCacheMetadata::ViewHasMetadata(BinaryView* view) +std::optional<CacheImage> SharedCache::GetImageWithName(const std::string& name) const { - return view->QueryMetadata(Tag); + for (const auto& [address, image] : m_images) + if (image.path == name) + return image; + return std::nullopt; } -std::optional<unsigned int> SharedCacheMetadata::ViewMetadataVersion(BinaryView* view) +std::optional<CacheSymbol> SharedCache::GetSymbolAt(uint64_t address) const { - // Whether the view has compatible metadata. I.e. if the version differs. - Ref<Metadata> viewMetadata = view->QueryMetadata(Tag); - if (!viewMetadata) - return std::nullopt; - DeserializationContext context; - rapidjson::ParseResult result = context.doc.Parse(viewMetadata->GetString().c_str()); - if (!result) - return std::nullopt; - if (!context.doc.HasMember("metadataVersion")) + const auto it = m_symbols.find(address); + if (it == m_symbols.end()) return std::nullopt; - return context.doc["metadataVersion"].GetUint(); + return it->second; } -// static -std::optional<SharedCacheMetadata> SharedCacheMetadata::LoadFromView(BinaryView* view) +std::optional<CacheSymbol> SharedCache::GetSymbolWithName(const std::string& name) const { - Ref<Metadata> viewMetadata = view->QueryMetadata(Tag); - if (!viewMetadata) - return std::nullopt; - - auto cacheInfo = SharedCache::CacheInfo::LoadFromString(viewMetadata->GetString()); - if (!cacheInfo) - return std::nullopt; - - auto modifiedState = SharedCache::ModifiedState::LoadAll(view, *cacheInfo); - return SharedCacheMetadata(std::move(*cacheInfo), std::move(modifiedState)); + for (const auto& [address, symbol] : m_symbols) + if (symbol.name == name) + return symbol; + return std::nullopt; } -const std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>>& SharedCacheMetadata::ExportInfos() const +CacheProcessor::CacheProcessor(Ref<BinaryView> view) { - return state->exportInfos; + m_view = std::move(view); + m_logger = new Logger("CacheProcessor", m_view->GetFile()->GetSessionId()); } -std::string SharedCacheMetadata::InstallNameForImageBaseAddress(uint64_t baseAddress) const +bool CacheProcessor::ProcessCache(SharedCache& cache) { - auto it = std::find_if(cacheInfo->imageStarts.begin(), cacheInfo->imageStarts.end(), [=](auto& pair) { - return pair.second == baseAddress; - }); - - if (it == cacheInfo->imageStarts.end()) - return ""; - - return it->first; -} - -} // namespace SharedCacheCore - -void InitDSCViewType() { - MMappedFileAccessor::InitialVMSetup(); - std::atexit(VMShutdown); - - static DSCViewType type; - BinaryViewType::Register(&type); + // If we are in a project, use the project cache processor. + if (m_view->GetFile()->GetProjectFile()) + return ProcessProjectCache(cache); + return ProcessFileCache(cache); } -extern "C" +bool CacheProcessor::ProcessFileCache(SharedCache& cache) { - BNSharedCache* BNGetSharedCache(BNBinaryView* data) - { - if (!data) - return nullptr; - - Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); - if (auto cache = SharedCache::GetFromDSCView(view)) - { - cache->AddAPIRef(); - return cache->GetAPIObject(); - } - - return nullptr; - } - - BNSharedCache* BNNewSharedCacheReference(BNSharedCache* cache) - { - if (!cache->object) - return nullptr; - - cache->object->AddAPIRef(); - return cache; - } + // We assume that the binary view location has all the files we need. + // If we ever want to allow users to override the shared cache file location + // we should really make a cache processor constructor with entry file paths. + std::string baseFilePath = m_view->GetFile()->GetOriginalFilename(); + std::string baseFileName = BaseFileName(baseFilePath); - void BNFreeSharedCacheReference(BNSharedCache* cache) - { - if (!cache->object) - return; - - cache->object->ReleaseAPIRef(); - } - - bool BNDSCViewLoadImageWithInstallName(BNSharedCache* cache, char* name, bool skipObjC) - { - std::string imageName = std::string(name); - BNFreeString(name); - - if (cache->object) - return cache->object->LoadImageWithInstallName(imageName, skipObjC); - - return false; - } - - bool BNDSCViewLoadSectionAtAddress(BNSharedCache* cache, uint64_t addr) + // Add this file to the entries + try { - if (cache->object) - { - return cache->object->LoadSectionAtAddress(addr); - } + auto baseEntry = CacheEntry::FromFile(baseFilePath, baseFileName, CacheEntryType::Primary); + if (!baseEntry.has_value()) + return false; - return false; + // Before we do anything else, add this to the cache so it's available to other entries. + cache.AddEntry(std::move(*baseEntry)); } - - bool BNDSCViewLoadImageContainingAddress(BNSharedCache* cache, uint64_t address, bool skipObjC) + catch (const std::exception& e) { - if (cache->object) - { - return cache->object->LoadImageContainingAddress(address, skipObjC); - } - + // Just return false so the view init can continue. return false; } - void BNDSCViewProcessObjCSectionsForImageWithInstallName(BNSharedCache* cache, char* name, bool deallocName) - { - std::string imageName = std::string(name); - if (deallocName) - BNFreeString(name); - - if (cache->object) - cache->object->ProcessObjCSectionsForImageWithInstallName(imageName); - } - - void BNDSCViewProcessAllObjCSections(BNSharedCache* cache) - { - if (cache->object) - cache->object->ProcessAllObjCSections(); - } - - char** BNDSCViewGetInstallNames(BNSharedCache* cache, size_t* count) - { - if (cache->object) - { - auto value = cache->object->GetAvailableImages(); - *count = value.size(); - - std::vector<const char*> cstrings; - cstrings.reserve(value.size()); - for (size_t i = 0; i < value.size(); i++) - { - cstrings.push_back(value[i].c_str()); - } - return BNAllocStringList(cstrings.data(), cstrings.size()); - } - *count = 0; - return nullptr; - } - - BNDSCSymbolRep* BNDSCViewLoadAllSymbolsAndWait(BNSharedCache* cache, size_t* count) + // Locate all possible related entry files and add them to the cache. + std::filesystem::path basePath = std::filesystem::path(baseFilePath).parent_path(); + for (const auto& entry : std::filesystem::directory_iterator(basePath)) { - if (cache->object) + if (!entry.is_regular_file()) + continue; + auto currentFilePath = entry.path().string(); + auto currentFileName = BaseFileName(currentFilePath); + // Skip our base file, obviously. + if (currentFilePath== baseFilePath) + continue; + // Filter files that don't contain the base file name i.e. "dyld_shared_cache_arm64e" + if (currentFilePath.find(baseFileName) == std::string::npos) + continue; + // Skip map files, they contain some nice information... we don't use. + if (entry.path().extension() == ".map") + continue; + try { - auto symbolsByImageName = cache->object->LoadAllSymbolsAndWait(); - size_t totalSymbolCount = 0; - for (const auto& [_, symbols] : symbolsByImageName) + auto additionalEntry = CacheEntry::FromFile(currentFilePath, currentFileName, CacheEntryType::Secondary); + if (!additionalEntry.has_value()) { - totalSymbolCount += symbols.size(); - } - *count = totalSymbolCount; - - BNDSCSymbolRep* outputSymbols = new BNDSCSymbolRep[totalSymbolCount]; - size_t i = 0; - for (const auto& [imageName, symbols] : symbolsByImageName) - { - for (const auto& symbol : symbols) - { - outputSymbols[i].address = symbol->GetAddress(); - outputSymbols[i].name = BNDuplicateStringRef(symbol->GetRawNameRef().GetObject()); - outputSymbols[i].image = BNAllocStringWithLength(imageName.c_str(), imageName.length()); - ++i; - } + m_logger->LogErrorF("Failed to load entry {}...", currentFileName); + continue; } - assert(i == totalSymbolCount); - return outputSymbols; - } - *count = 0; - return nullptr; - } - - void BNDSCViewFreeSymbols(BNDSCSymbolRep* symbols, size_t count) - { - for (size_t i = 0; i < count; i++) - { - BNFreeStringRef(symbols[i].name); - BNFreeString(symbols[i].image); - } - delete symbols; - } - - char* BNDSCViewGetNameForAddress(BNSharedCache* cache, uint64_t address) - { - if (cache->object) - { - return BNAllocString(cache->object->NameForAddress(address).c_str()); - } - - return nullptr; - } - - char* BNDSCViewGetImageNameForAddress(BNSharedCache* cache, uint64_t address) - { - if (cache->object) - { - return BNAllocString(cache->object->ImageNameForAddress(address).c_str()); - } - - return nullptr; - } - uint64_t BNDSCViewLoadedImageCount(BNSharedCache* cache) - { - // FIXME? - return 0; - } - - BNDSCViewState BNDSCViewGetState(BNSharedCache* cache) - { - if (cache->object) - { - return (BNDSCViewState)cache->object->ViewState(); + // Add this file as an entry to the cache + cache.AddEntry(std::move(*additionalEntry)); } - - return BNDSCViewState::Unloaded; + catch (const std::exception& e) + {} } + return true; +} - BNDSCMappedMemoryRegion* BNDSCViewGetLoadedRegions(BNSharedCache* cache, size_t* count) - { - if (cache->object) - { - auto regions = cache->object->GetMappedRegions(); - *count = regions.size(); - BNDSCMappedMemoryRegion* mappedRegions = new BNDSCMappedMemoryRegion[regions.size()]; - for (size_t i = 0; i < regions.size(); i++) - { - mappedRegions[i].vmAddress = regions[i]->start; - mappedRegions[i].size = regions[i]->size; - mappedRegions[i].name = - BNAllocStringWithLength(regions[i]->prettyName.c_str(), regions[i]->prettyName.length()); - } - return mappedRegions; - } - *count = 0; - return nullptr; - } +bool CacheProcessor::ProcessProjectCache(SharedCache& cache) +{ + auto baseProjectFile = m_view->GetFile()->GetProjectFile(); + std::string baseFilePath = baseProjectFile->GetPathOnDisk(); + // TODO: I dont think the project file name will have anything other than the base name so this might be redundant. + std::string baseFileName = BaseFileName(baseProjectFile->GetName()); - void BNDSCViewFreeLoadedRegions(BNDSCMappedMemoryRegion* images, size_t count) - { - for (size_t i = 0; i < count; i++) - { - BNFreeString(images[i].name); - } - delete images; - } + // Add this file to the entries + auto baseEntry = CacheEntry::FromFile(baseFilePath, baseFileName, CacheEntryType::Primary); + if (!baseEntry.has_value()) + return false; + // Before we do anything else, add this to the cache so it's available to other entries. + cache.AddEntry(std::move(*baseEntry)); - BNDSCBackingCache* BNDSCViewGetBackingCaches(BNSharedCache* cache, size_t* count) + // Enumerate the project files folder to gather the necessary sub caches. + const auto project = baseProjectFile->GetProject(); + const auto folder = baseProjectFile->GetFolder(); + for (const auto& projectFile : project->GetFiles()) { - BNDSCBackingCache* caches = nullptr; - - if (cache->object) + auto projectFilePath = projectFile->GetPathOnDisk(); + auto projectFileName = projectFile->GetName(); + auto currentFolder = projectFile->GetFolder(); + // Skip our base project file, obviously. + if (projectFile->GetId() == baseProjectFile->GetId()) + continue; + // Filter files that don't contain the base file name i.e. "dyld_shared_cache_arm64e" + if (projectFileName.find(baseFileName) == std::string::npos) + continue; + // Filter out .map files, they contain some nice info for rebasing... that we don't do. + if (projectFileName.find(".map") != std::string::npos) + continue; + // If both top level, or we are in the same folder as the base project file add it. + if ((!folder && !currentFolder) || (folder && currentFolder)) { - auto viewCaches = cache->object->BackingCaches(); - *count = viewCaches.size(); - caches = new BNDSCBackingCache[viewCaches.size()]; - for (size_t i = 0; i < viewCaches.size(); i++) + try { - caches[i].path = BNAllocString(viewCaches[i].path.c_str()); - caches[i].cacheType = viewCaches[i].cacheType; - - BNDSCBackingCacheMapping* mappings; - mappings = new BNDSCBackingCacheMapping[viewCaches[i].mappings.size()]; - - size_t j = 0; - for (const auto& mapping : viewCaches[i].mappings) - { - mappings[j].vmAddress = mapping.address; - mappings[j].size = mapping.size; - mappings[j].fileOffset = mapping.fileOffset; - j++; - } - caches[i].mappings = mappings; - caches[i].mappingCount = viewCaches[i].mappings.size(); - } - } - - return caches; - } - - void BNDSCViewFreeBackingCaches(BNDSCBackingCache* caches, size_t count) - { - for (size_t i = 0; i < count; i++) - { - delete[] caches[i].mappings; - BNFreeString(caches[i].path); - } - delete[] caches; - } - - void BNDSCFindSymbolAtAddressAndApplyToAddress(BNSharedCache* cache, uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) - { - if (cache->object) - { - cache->object->FindSymbolAtAddrAndApplyToAddr(symbolLocation, targetLocation, triggerReanalysis); - } - } - - BNDSCImage* BNDSCViewGetAllImages(BNSharedCache* cache, size_t* count) - { - if (cache->object) - { - try { - auto vm = cache->object->GetVMMap(); - auto viewImageHeaders = cache->object->AllImageHeaders(); - *count = viewImageHeaders.size(); - BNDSCImage* images = new BNDSCImage[viewImageHeaders.size()]; - size_t i = 0; - for (const auto& [baseAddress, header] : viewImageHeaders) + auto additionalEntry = CacheEntry::FromFile(projectFilePath, projectFileName, CacheEntryType::Secondary); + if (!additionalEntry.has_value()) { - images[i].name = BNAllocString(header.installName.c_str()); - images[i].headerAddress = baseAddress; - images[i].mappingCount = header.sections.size(); - images[i].mappings = new BNDSCImageMemoryMapping[header.sections.size()]; - for (size_t j = 0; j < header.sections.size(); j++) - { - const auto sectionStart = header.sections[j].addr; - images[i].mappings[j].rawViewOffset = header.sections[j].offset; - images[i].mappings[j].vmAddress = sectionStart; - images[i].mappings[j].size = header.sections[j].size; - images[i].mappings[j].name = BNAllocString(header.sectionNames[j].c_str()); - auto fileAccessor = vm->MappingAtAddress(sectionStart).first.fileAccessor; - images[i].mappings[j].filePath = BNAllocStringWithLength(fileAccessor->filePath().data(), fileAccessor->filePath().length()); - images[i].mappings[j].loaded = cache->object->IsMemoryMapped(sectionStart); - } - i++; + m_logger->LogErrorF("Failed to load entry {}...", projectFileName); + continue; } - return images; - } - catch (...) - { - LogError("SharedCache: Failed to load image listing. Likely caused by a ser/deserialization error or load failure"); - *count = 0; - return nullptr; - } - } - *count = 0; - return nullptr; - } - void BNDSCViewFreeAllImages(BNDSCImage* images, size_t count) - { - for (size_t i = 0; i < count; i++) - { - for (size_t j = 0; j < images[i].mappingCount; j++) - { - BNFreeString(images[i].mappings[j].name); - BNFreeString(images[i].mappings[j].filePath); + // Add this file as an entry to the cache + cache.AddEntry(std::move(*additionalEntry)); } - delete[] images[i].mappings; - BNFreeString(images[i].name); + catch (const std::exception& e) + {} } - delete[] images; } - char* BNDSCViewGetImageHeaderForAddress(BNSharedCache* cache, uint64_t address) - { - if (cache->object) - { - auto header = cache->object->SerializedImageHeaderForAddress(address); - return BNAllocString(header.c_str()); - } - - return nullptr; - } - - char* BNDSCViewGetImageHeaderForName(BNSharedCache* cache, char* name) - { - std::string imageName = std::string(name); - BNFreeString(name); - if (cache->object) - { - auto header = cache->object->SerializedImageHeaderForName(imageName); - return BNAllocString(header.c_str()); - } - - return nullptr; - } - - BNDSCMemoryUsageInfo BNDSCViewGetMemoryUsageInfo() - { - BNDSCMemoryUsageInfo info; - info.mmapRefs = MMapCount(); - info.sharedCacheRefs = sharedCacheReferences.load(); - return info; - } - - BNDSCViewLoadProgress BNDSCViewGetLoadProgress(uint64_t sessionID) - { - if (auto viewSpecificState = ViewSpecificStateForId(sessionID, false)) - return viewSpecificState->progress; - - return LoadProgressNotStarted; - } - - uint64_t BNDSCViewFastGetBackingCacheCount(BNBinaryView* data) - { - Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); - return SharedCache::FastGetBackingCacheCount(view); - } -}
\ No newline at end of file + return true; +} diff --git a/view/sharedcache/core/SharedCache.h b/view/sharedcache/core/SharedCache.h index fadcdd3a..54ec9dfd 100644 --- a/view/sharedcache/core/SharedCache.h +++ b/view/sharedcache/core/SharedCache.h @@ -1,705 +1,278 @@ -// -// Created by kat on 5/19/23. -// +#pragma once -#ifndef SHAREDCACHE_SHAREDCACHE_H -#define SHAREDCACHE_SHAREDCACHE_H +#include <vector> +#include <Dyld.h> -#include <binaryninjaapi.h> -#include <cstdint> -#include <memory> -#include <mutex> -#include <unordered_map> -#include "VM.h" -#include "view/macho/machoview.h" -#include "MetadataSerializable.hpp" -#include "../api/sharedcachecore.h" +#include "binaryninjaapi.h" +#include "MachO.h" +#include "VirtualMemory.h" -#include <optional> +class SharedCache; -DECLARE_SHAREDCACHE_API_OBJECT(BNSharedCache, SharedCache); +struct CacheSymbol +{ + BNSymbolType type; + uint64_t address; + std::string name; -namespace SharedCacheCore { + CacheSymbol() = default; - enum DSCViewState - { - DSCViewStateUnloaded, - DSCViewStateLoaded, - DSCViewStateLoadedWithImages, - }; - - struct MemoryRegion : public MetadataSerializable<MemoryRegion> - { - enum class Type - { - Image, - StubIsland, - DyldData, - NonImage, - }; - - std::string prettyName; - uint64_t start; - uint64_t size; - // Start address of the image this region belongs to. - // 0 if the region does not belong to any image. - uint64_t imageStart = 0; - BNSegmentFlag flags; - Type type; + CacheSymbol(BNSymbolType type, uint64_t address, std::string name) : + type(type), address(address), name(std::move(name)) + {} + ~CacheSymbol() = default; - AddressRange AsAddressRange() const - { - return {start, start + size}; - } + CacheSymbol(const CacheSymbol& other) = default; - void Store(SerializationContext& context) const - { - MSS(prettyName); - MSS(start); - MSS(size); - MSS(imageStart); - MSS_CAST(flags, uint64_t); - MSS_CAST(type, uint8_t); - } + CacheSymbol& operator=(const CacheSymbol& other) = default; - static MemoryRegion Load(DeserializationContext& context) - { - MemoryRegion region; - region.MSL(prettyName); - region.MSL(start); - region.MSL(size); - region.MSL_CAST(flags, uint64_t, BNSegmentFlag); - region.MSL_CAST(type, uint8_t, Type); - if (context.doc.HasMember("imageStart")) - region.MSL(imageStart); + CacheSymbol(CacheSymbol&& other) noexcept = default; - return region; - } - }; + CacheSymbol& operator=(CacheSymbol&& other) noexcept = default; - struct CacheImage : public MetadataSerializable<CacheImage> { - std::string installName; - uint64_t headerLocation; - // Start addresses of the memory regions in this image. - std::vector<uint64_t> regionStarts; + // NOTE: you should really only call this when adding the symbol to the view. + BinaryNinja::Ref<BinaryNinja::Symbol> ToBNSymbol() const; +}; - void Store(SerializationContext& context) const; - static CacheImage Load(DeserializationContext& context); - }; +enum class CacheRegionType +{ + Image, + StubIsland, + DyldData, + NonImage, +}; - #if defined(__GNUC__) || defined(__clang__) - #define PACKED_STRUCT __attribute__((packed)) - #else - #define PACKED_STRUCT - #endif +struct CacheRegion +{ + CacheRegionType type; + std::string name; + uint64_t start; + uint64_t size; + // Associate this region with this image, this makes it easier to identify what image owns this region. + std::optional<uint64_t> imageStart; + BNSegmentFlag flags; - #if defined(_MSC_VER) - #pragma pack(push, 1) - #else + CacheRegion() = default; - #endif + ~CacheRegion() = default; - struct PACKED_STRUCT dyld_cache_mapping_info - { - uint64_t address; - uint64_t size; - uint64_t fileOffset; - uint32_t maxProt; - uint32_t initProt; - }; - - struct BackingCache : public MetadataSerializable<BackingCache> { - std::string path; - BNBackingCacheType cacheType = BackingCacheTypeSecondary; - std::vector<dyld_cache_mapping_info> mappings; + CacheRegion(const CacheRegion& other) = default; - void Store(SerializationContext& context) const; - static BackingCache Load(DeserializationContext& context); - }; + CacheRegion& operator=(const CacheRegion& other) = default; - struct LoadedMapping - { - std::shared_ptr<MMappedFileAccessor> backingFile; - dyld_cache_mapping_info mappingInfo; - }; + CacheRegion(CacheRegion&& other) noexcept = default; - struct dyld_cache_slide_info - { - uint32_t version; - uint32_t toc_offset; - uint32_t toc_count; - uint32_t entries_offset; - uint32_t entries_count; - uint32_t entries_size; - // uint16_t toc[toc_count]; - // entrybitmap entries[entries_count]; - }; + CacheRegion& operator=(CacheRegion&& other) noexcept = default; - struct dyld_cache_slide_info_entry { - uint8_t bits[4096/(8*4)]; // 128-byte bitmap - }; + AddressRange AsAddressRange() const { return {start, start + size}; } - struct PACKED_STRUCT dyld_cache_mapping_and_slide_info + BNSectionSemantics SectionSemanticsForRegion() const { - uint64_t address; - uint64_t size; - uint64_t fileOffset; - uint64_t slideInfoFileOffset; - uint64_t slideInfoFileSize; - uint64_t flags; - uint32_t maxProt; - uint32_t initProt; - }; + if ((flags & SegmentExecutable) && (flags & SegmentDenyWrite)) + return ReadOnlyCodeSectionSemantics; - struct PACKED_STRUCT dyld_cache_slide_info_v2 - { - uint32_t version; - uint32_t page_size; - uint32_t page_starts_offset; - uint32_t page_starts_count; - uint32_t page_extras_offset; - uint32_t page_extras_count; - uint64_t delta_mask; - uint64_t value_add; - }; - #define DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA 0x8000 // index is into extras array (not starts array) - #define DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE 0x4000 // page has no rebasing - #define DYLD_CACHE_SLIDE_PAGE_ATTR_END 0x8000 // last chain entry for page - - #define DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing - - struct PACKED_STRUCT dyld_cache_slide_info_v3 - { - uint32_t version; - uint32_t page_size; - uint32_t page_starts_count; - uint32_t pad_i_guess; - uint64_t auth_value_add; - }; - - - // DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE - struct dyld_chained_ptr_arm64e_shared_cache_rebase - { - uint64_t runtimeOffset : 34, // offset from the start of the shared cache - high8 : 8, - unused : 10, - next : 11, // 8-byte stide - auth : 1; // == 0 - }; + if (flags & SegmentExecutable) + return DefaultSectionSemantics; - // DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE - struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase - { - uint64_t runtimeOffset : 34, // offset from the start of the shared cache - diversity : 16, - addrDiv : 1, - keyIsData : 1, // implicitly always the 'A' key. 0 -> IA. 1 -> DA - next : 11, // 8-byte stide - auth : 1; // == 1 - }; + if (flags & SegmentDenyWrite) + return ReadOnlyDataSectionSemantics; - // dyld_cache_slide_info4 is used in watchOS which we are not close to supporting right now. + return ReadWriteDataSectionSemantics; + } +}; - #define DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing +// Represents a single image and its associated memory regions. +struct CacheImage +{ + uint64_t headerAddress; + std::string path; + // A list to the start of memory regions associated with the image. + // This lets us load all regions for a given image easily. + std::vector<uint64_t> regionStarts; + std::shared_ptr<SharedCacheMachOHeader> header; - struct PACKED_STRUCT dyld_cache_slide_info5 - { - uint32_t version; // currently 5 - uint32_t page_size; // currently 4096 (may also be 16384) - uint32_t page_starts_count; - uint32_t pad; // padding to ensure the value below is on an 8-byte boundary - uint64_t value_add; - // uint16_t page_starts[/* page_starts_count */]; - }; + CacheImage() = default; + ~CacheImage() = default; - struct PACKED_STRUCT dyld_cache_image_info - { - uint64_t address; - uint64_t modTime; - uint64_t inode; - uint32_t pathFileOffset; - uint32_t pad; - }; + CacheImage(const CacheImage& other) = default; - union dyld_cache_slide_pointer5 - { - uint64_t raw; - struct dyld_chained_ptr_arm64e_shared_cache_rebase regular; - struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase auth; - }; + CacheImage& operator=(const CacheImage& other) = default; + CacheImage(CacheImage&& other) noexcept = default; - struct PACKED_STRUCT dyld_cache_local_symbols_info - { - uint32_t nlistOffset; // offset into this chunk of nlist entries - uint32_t nlistCount; // count of nlist entries - uint32_t stringsOffset; // offset into this chunk of string pool - uint32_t stringsSize; // byte count of string pool - uint32_t entriesOffset; // offset into this chunk of array of dyld_cache_local_symbols_entry - uint32_t entriesCount; // number of elements in dyld_cache_local_symbols_entry array - }; + CacheImage& operator=(CacheImage&& other) noexcept = default; - struct PACKED_STRUCT dyld_cache_local_symbols_entry - { - uint32_t dylibOffset; // offset in cache file of start of dylib - uint32_t nlistStartIndex; // start index of locals for this dylib - uint32_t nlistCount; // number of local symbols for this dylib - }; + // Get the file name from the path. + std::string GetName() const { return BaseFileName(path); } - struct PACKED_STRUCT dyld_cache_local_symbols_entry_64 - { - uint64_t dylibOffset; // offset in cache buffer of start of dylib - uint32_t nlistStartIndex; // start index of locals for this dylib - uint32_t nlistCount; // number of local symbols for this dylib - }; + // Get the names of the dependencies. + std::vector<std::string> GetDependencies() const; +}; - union dyld_cache_slide_pointer3 - { - uint64_t raw; - struct - { - uint64_t pointerValue : 51, offsetToNextPointer : 11, unused : 2; - } plain; +enum class CacheEntryType +{ + Primary, + Secondary, + // A special entry that holds symbols for other cache entries. + // TODO: We dont need this i think. + Symbols, + // If the type is marked as this then all mappings will be marked as such. + DyldData, + // A single stub mapping file. + Stub, +}; - struct - { - uint64_t offsetFromSharedCacheBase : 32, diversityData : 16, hasAddressDiversity : 1, key : 2, - offsetToNextPointer : 11, unused : 1, - authenticated : 1; // = 1; - } auth; - }; +// Describes a single files cache information +class CacheEntry +{ + CacheEntryType m_type; + std::string m_filePath; + std::string m_fileName; + dyld_cache_header m_header {}; + // Mappings tell us _where_ to map the regions within the flat address space. + // Without this we wouldn't know where the entry is supposed to exist in the address space. + std::vector<dyld_cache_mapping_info> m_mappings {}; + // TODO: We really should remove this methinks. + // TODO: Storing this here is basically useless? IDK + // Mapping of image path to image info, used within ProcessImagesAndRegions to add them to the cache. + std::unordered_map<std::string, dyld_cache_image_info> m_images {}; +public: + CacheEntry(std::string filePath, std::string fileName, CacheEntryType type, dyld_cache_header header, + std::vector<dyld_cache_mapping_info> mappings, std::unordered_map<std::string, dyld_cache_image_info> images); - struct PACKED_STRUCT dyld_cache_header - { - char magic[16]; // e.g. "dyld_v0 i386" - uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info - uint32_t mappingCount; // number of dyld_cache_mapping_info entries - uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing - uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing - uint64_t dyldBaseAddress; // base address of dyld when cache was built - uint64_t codeSignatureOffset; // file offset of code signature blob - uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file) - uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info - uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info - uint64_t localSymbolsOffset; // file offset of where local symbols are stored - uint64_t localSymbolsSize; // size of local symbols information - uint8_t uuid[16]; // unique value for each shared cache file - uint64_t cacheType; // 0 for development, 1 for production, 2 for multi-cache - uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses - uint32_t branchPoolsCount; // number of uint64_t entries - uint64_t dyldInCacheMH; // (unslid) address of mach_header of dyld in cache - uint64_t dyldInCacheEntry; // (unslid) address of entry point (_dyld_start) of dyld in cache - uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info - uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries - uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info - uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info - uint64_t otherImageGroupAddrUnused; // unused - uint64_t otherImageGroupSizeUnused; // unused - uint64_t progClosuresAddr; // (unslid) address of list of program launch closures - uint64_t progClosuresSize; // size of list of program launch closures - uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures - uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures - uint32_t platform; // platform number (macOS=1, etc) - uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion - dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid - simulator : 1, // for simulator of specified platform - locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache - builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used for overrides - padding : 20; // TBD - uint64_t sharedRegionStart; // base load address of cache if not slid - uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any - uint64_t maxSlide; // runtime slide of cache can be between zero and this value - uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache - uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache - uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs - uint64_t dylibsTrieSize; // size of trie of cached dylib paths - uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures - uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures - uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures - uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures - uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info - uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries - uint64_t dylibsPBLStateArrayAddrUnused; // unused - uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs - uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program - uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program - uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet - uint32_t programTrieSize; - uint32_t osVersion; // OS Version of dylibs in this cache for the main platform - uint32_t altPlatform; // e.g. iOSMac on macOS - uint32_t altOsVersion; // e.g. 14.0 for iOSMac - uint64_t swiftOptsOffset; // VM offset from cache_header* to Swift optimizations header - uint64_t swiftOptsSize; // size of Swift optimizations header - uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry - uint32_t subCacheArrayCount; // number of subCache entries - uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols - uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data - uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region - uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data - uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region - uint32_t imagesOffset; // file offset to first dyld_cache_image_info - uint32_t imagesCount; // number of dyld_cache_image_info entries - uint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2) - uint32_t padding2; - uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header - uint64_t objcOptsSize; // size of ObjC optimizations header - uint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection - uint64_t cacheAtlasSize; // size of embedded cache atlas - uint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header - uint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data - uint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info - uint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries - }; + CacheEntry() = default; - struct PACKED_STRUCT dyld_subcache_entry - { - char uuid[16]; - uint64_t address; - }; + CacheEntry(const CacheEntry&) = default; - struct PACKED_STRUCT dyld_subcache_entry2 - { - char uuid[16]; - uint64_t address; - char fileExtension[32]; - }; + CacheEntry(CacheEntry&&) = default; - struct ObjCOptimizationHeader - { - uint32_t version; - uint32_t flags; - uint64_t headerInfoROCacheOffset; - uint64_t headerInfoRWCacheOffset; - uint64_t selectorHashTableCacheOffset; - uint64_t classHashTableCacheOffset; - uint64_t protocolHashTableCacheOffset; - uint64_t relativeMethodSelectorBaseAddressOffset; - }; + CacheEntry& operator=(CacheEntry&&) = default; - #if defined(_MSC_VER) - #pragma pack(pop) - #else + // Construct a cache entry from the file on disk. + // TODO: Seperate this out a bit more. + static std::optional<CacheEntry> FromFile(const std::string& filePath, const std::string& fileName, CacheEntryType type); - #endif - - struct SharedCacheMachOHeader : public MetadataSerializable<SharedCacheMachOHeader> - { - uint64_t textBase = 0; - uint64_t loadCommandOffset = 0; - mach_header_64 ident; - std::string identifierPrefix; - std::string installName; + // TODO: From Project file? - std::vector<std::pair<uint64_t, bool>> entryPoints; - std::vector<uint64_t> m_entryPoints; // list of entrypoints + WeakFileAccessor GetAccessor() const; - symtab_command symtab; - dysymtab_command dysymtab; - dyld_info_command dyldInfo; - routines_command_64 routines64; - function_starts_command functionStarts; - std::vector<section_64> moduleInitSections; - linkedit_data_command exportTrie; - linkedit_data_command chainedFixups {}; + // Get the headers virtual address. + // This is useful if you need to read relative to the start of the entry file. + std::optional<uint64_t> GetHeaderAddress() const; - uint64_t relocationBase; - // Section and program headers, internally use 64-bit form as it is a superset of 32-bit - std::vector<segment_command_64> segments; // only three types of sections __TEXT, __DATA, __IMPORT - segment_command_64 linkeditSegment; - std::vector<section_64> sections; - std::vector<std::string> sectionNames; + // Get the mapped address for a given file offset. + // Ex. passing 0x0 will retrieve the mapped address for the start of the file (i.e. the header) + std::optional<uint64_t> GetMappedAddress(uint64_t fileOffset) const; - std::vector<section_64> symbolStubSections; - std::vector<section_64> symbolPointerSections; + CacheEntryType GetType() const { return m_type; } + // Ex. "/myuser/mypath/dyld_shared_cache_arm64e" + const std::string& GetFilePath() const { return m_filePath; } + // Ex. "dyld_shared_cache_arm64e" + const std::string GetFileName() const { return m_fileName; } + const dyld_cache_header& GetHeader() const { return m_header; } + const std::vector<dyld_cache_mapping_info>& GetMappings() const { return m_mappings; } + const std::unordered_map<std::string, dyld_cache_image_info>& GetImages() const { return m_images; } +}; - std::vector<std::string> dylibs; +// The ID for a given CacheEntry, use this instead of passing a pointer around to avoid complexity :V +typedef uint32_t CacheEntryId; - build_version_command buildVersion; - std::vector<build_tool_version> buildToolVersions; +// TODO: Add a "ViewCache" that keeps track of what has been added to the view. - std::string exportTriePath; +// The C in DSC. +// This represents the entire cache, all regions and images are visible from here. +// This is the dump for all the information, and what the workflow activities and the UI want. +// Creating this is expensive, both in actual processing and just copying, so we only generate this +// once every time the database is open. +class SharedCache +{ + uint64_t m_addressSize = 8; + uint64_t m_baseAddress = 0; + // TODO: Figure out when to lock the mutex on this shit lmfao + // The shared cache can own the virtual memory, this is fine... + std::shared_ptr<VirtualMemory> m_vm; + std::unordered_map<CacheEntryId, CacheEntry> m_entries {}; + // This information is used in tandem with the cache images to load memory regions into the binary view. + AddressRangeMap<CacheRegion> m_regions {}; + // Describes the images of the cache. + std::unordered_map<uint64_t, CacheImage> m_images {}; + // All the symbols for this cache. Both mapped and unmapped (not in the view). + std::unordered_map<uint64_t, CacheSymbol> m_symbols {}; - bool linkeditPresent = false; - bool dysymPresent = false; - bool dyldInfoPresent = false; - bool exportTriePresent = false; - bool chainedFixupsPresent = false; - bool routinesPresent = false; - bool functionStartsPresent = false; - bool relocatable = false; + bool ProcessEntryImage(const std::string& path, const dyld_cache_image_info& info); - void Store(SerializationContext& context) const { - MSS(textBase); - MSS(loadCommandOffset); - MSS_SUBCLASS(ident); - MSS(identifierPrefix); - MSS(installName); - MSS(entryPoints); - MSS(m_entryPoints); - MSS_SUBCLASS(symtab); - MSS_SUBCLASS(dysymtab); - MSS_SUBCLASS(dyldInfo); - MSS_SUBCLASS(routines64); - MSS_SUBCLASS(functionStarts); - MSS_SUBCLASS(moduleInitSections); - MSS_SUBCLASS(exportTrie); - MSS_SUBCLASS(chainedFixups); - MSS(relocationBase); - MSS_SUBCLASS(segments); - MSS_SUBCLASS(linkeditSegment); - MSS_SUBCLASS(sections); - MSS(sectionNames); - MSS_SUBCLASS(symbolStubSections); - MSS_SUBCLASS(symbolPointerSections); - MSS(dylibs); - MSS_SUBCLASS(buildVersion); - MSS_SUBCLASS(buildToolVersions); - MSS(exportTriePath); - MSS(linkeditPresent); - MSS(dysymPresent); - MSS(dyldInfoPresent); - MSS(exportTriePresent); - MSS(chainedFixupsPresent); - MSS(routinesPresent); - MSS(functionStartsPresent); - MSS(relocatable); - } + // Add a region known not to overlap with another, otherwise use AddRegion. + // returns whether the region was inserted. + bool AddNonOverlappingRegion(CacheRegion region); - static SharedCacheMachOHeader Load(DeserializationContext& context) { - SharedCacheMachOHeader header; - header.MSL(textBase); - header.MSL(loadCommandOffset); - header.MSL(ident); - header.MSL(identifierPrefix); - header.MSL(installName); - header.MSL(entryPoints); - header.MSL(m_entryPoints); - header.MSL(symtab); - header.MSL(dysymtab); - header.MSL(dyldInfo); - header.MSL(routines64); - header.MSL(functionStarts); - header.MSL(moduleInitSections); - header.MSL(exportTrie); - header.MSL(chainedFixups); - header.MSL(relocationBase); - header.MSL(segments); - header.MSL(linkeditSegment); - header.MSL(sections); - header.MSL(sectionNames); - header.MSL(symbolStubSections); - header.MSL(symbolPointerSections); - header.MSL(dylibs); - header.MSL(buildVersion); - header.MSL(buildToolVersions); - header.MSL(exportTriePath); - header.MSL(linkeditPresent); - header.MSL(dysymPresent); - header.MSL(dyldInfoPresent); - header.MSL(exportTriePresent); - header.MSL(chainedFixupsPresent); - header.MSL(routinesPresent); - header.MSL(functionStartsPresent); - header.MSL(relocatable); - return header; - } - }; +public: + explicit SharedCache(uint64_t addressSize); - struct MappingInfo - { - dyld_cache_mapping_info mappingInfo; - uint32_t slideInfoVersion; - dyld_cache_slide_info_v2 slideInfoV2; - dyld_cache_slide_info_v3 slideInfoV3; - dyld_cache_slide_info5 slideInfoV5; - }; + uint64_t GetBaseAddress() const { return m_baseAddress; } + std::shared_ptr<VirtualMemory> GetVirtualMemory() { return m_vm; } + const std::unordered_map<CacheEntryId, CacheEntry>& GetEntries() const { return m_entries; } + const AddressRangeMap<CacheRegion>& GetRegions() const { return m_regions; } + const std::unordered_map<uint64_t, CacheImage>& GetImages() const { return m_images; } + const std::unordered_map<uint64_t, CacheSymbol>& GetSymbols() const { return m_symbols; } + void AddImage(CacheImage image); - class ScopedVMMapSession; + // Add a region that may overlap with another. + void AddRegion(CacheRegion region); - static std::atomic<uint64_t> sharedCacheReferences = 0; + void AddSymbol(CacheSymbol symbol); - class SharedCache - { - IMPLEMENT_SHAREDCACHE_API_OBJECT(BNSharedCache); + void AddSymbols(std::vector<CacheSymbol> symbols); - std::atomic<int> m_refs = 0; + // Adds the cache entry and populates the virtual memory using the mapping information. + // After being added the entry is read only, there is nothing that can modify it. + CacheEntryId AddEntry(CacheEntry entry); - public: - virtual void AddRef() { m_refs.fetch_add(1); } + void ProcessEntryImages(const CacheEntry& entry); - virtual void Release() - { - // undo actions will lock a file lock we hold and then wait for main thread - // so we need to release the ref later. - WorkerPriorityEnqueue([this]() { - if (m_refs.fetch_sub(1) == 1) - delete this; - }); - } + void ProcessEntryRegions(const CacheEntry& entry); - virtual void AddAPIRef() { AddRef(); } + void ProcessEntrySlideInfo(const CacheEntry& entry); - virtual void ReleaseAPIRef() { Release(); } + std::optional<CacheEntry> GetEntryContaining(uint64_t address) const; - public: - enum SharedCacheFormat - { - RegularCacheFormat, - SplitCacheFormat, - LargeCacheFormat, - iOS16CacheFormat, - }; + std::optional<CacheEntry> GetEntryWithImage(const CacheImage& image) const; - struct CacheInfo; - struct ModifiedState; + std::optional<CacheRegion> GetRegionAt(uint64_t address) const; - struct ViewSpecificState; + std::optional<CacheRegion> GetRegionContaining(uint64_t address) const; + std::optional<CacheImage> GetImageAt(uint64_t address) const; - private: - Ref<Logger> m_logger; - /* VIEW STATE BEGIN -- SERIALIZE ALL OF THIS AND STORE IT IN RAW VIEW */ - - // State that is initialized during `PerformInitialLoad` and does - // not change thereafter. - std::shared_ptr<const CacheInfo> m_cacheInfo; - - // Protects member variables below. - mutable std::mutex m_mutex; - - // State that has been modified since this instance was created - // or last saved to the view-specific state. - // To get an accurate view of the current state, both these modifications - // and the view-specific state must be consulted. - std::unique_ptr<ModifiedState> m_modifiedState; - - // Serialized once by PerformInitialLoad and available after m_viewState == Loaded - bool m_metadataValid = false; - - /* VIEWSTATE END -- NOTHING PAST THIS IS SERIALIZED */ - - /* API VIEW START */ - BinaryNinja::Ref<BinaryNinja::BinaryView> m_dscView; - /* API VIEW END */ - - std::shared_ptr<ViewSpecificState> m_viewSpecificState; - - private: - void PerformInitialLoad(std::lock_guard<std::mutex>&); - bool DeserializeFromRawView(std::lock_guard<std::mutex>&); - - public: - std::shared_ptr<VM> GetVMMap(); - std::shared_ptr<VM> GetVMMap(const CacheInfo& staticState); - - static SharedCache* GetFromDSCView(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView); - static uint64_t FastGetBackingCacheCount(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView); - bool SaveCacheInfoToDSCView(std::lock_guard<std::mutex>&); - bool SaveModifiedStateToDSCView(std::lock_guard<std::mutex>&); - - static void ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAccessor> file, uint64_t baseAddress, Ref<Logger> logger); - std::optional<uint64_t> GetImageStart(std::string_view installName); - const SharedCacheMachOHeader* HeaderForAddress(uint64_t); - bool LoadImageWithInstallName(std::string installName, bool skipObjC); - bool LoadSectionAtAddress(uint64_t address); - bool LoadImageContainingAddress(uint64_t address, bool skipObjC); - void ProcessObjCSectionsForImageWithInstallName(std::string installName); - void ProcessAllObjCSections(); - std::string NameForAddress(uint64_t address); - std::string ImageNameForAddress(uint64_t address); - std::vector<std::string> GetAvailableImages(); - - std::vector<const MemoryRegion*> GetMappedRegions() const; - bool IsMemoryMapped(uint64_t address); - - std::unordered_map<std::string, std::vector<Ref<Symbol>>> LoadAllSymbolsAndWait(); - - const std::unordered_map<std::string, uint64_t>& AllImageStarts() const; - const std::unordered_map<uint64_t, SharedCacheMachOHeader>& AllImageHeaders() const; - - std::string SerializedImageHeaderForAddress(uint64_t address); - std::string SerializedImageHeaderForName(std::string name); - - void FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis); - - const std::vector<BackingCache>& BackingCaches() const; - - DSCViewState ViewState() const; - - explicit SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> rawView); - virtual ~SharedCache(); - - uint64_t GetObjCRelativeMethodBaseAddress(const VMReader& reader) const; - -private: - std::optional<SharedCacheMachOHeader> LoadHeaderForAddress( - std::shared_ptr<VM> vm, uint64_t address, std::string installName); - void InitializeHeader( - std::lock_guard<std::mutex>&, Ref<BinaryView> view, VM* vm, const SharedCacheMachOHeader& header, - std::vector<const MemoryRegion*> regionsToLoad); - void ReadExportNode(std::vector<Ref<Symbol>>& symbolList, const SharedCacheMachOHeader& header, const uint8_t* begin, - const uint8_t *end, const uint8_t* current, uint64_t textBase, const std::string& currentText); - std::vector<Ref<Symbol>> ParseExportTrie( - std::shared_ptr<MMappedFileAccessor> linkeditFile, const SharedCacheMachOHeader& header); - std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> GetExportListForHeader(std::lock_guard<std::mutex>&, const SharedCacheMachOHeader& header, - std::function<std::shared_ptr<MMappedFileAccessor>()> provideLinkeditFile, bool* didModifyExportList = nullptr); - std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> GetExistingExportListForBaseAddress(std::lock_guard<std::mutex>&, uint64_t baseAddress) const; - void ProcessSymbols(std::shared_ptr<MMappedFileAccessor> file, const SharedCacheMachOHeader& header, - uint64_t stringsOffset, size_t stringsSize, uint64_t nlistEntriesOffset, uint32_t nlistCount, uint32_t nlistStartIndex = 0); - void ApplySymbol(Ref<BinaryView> view, Ref<TypeLibrary> typeLib, Ref<Symbol> symbol); - - void ProcessAllObjCSections(std::lock_guard<std::mutex>&); - bool LoadImageWithInstallName(std::lock_guard<std::mutex>&, std::string installName, bool skipObjC); - - bool MemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region) const; - void SetMemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region); - bool MemoryRegionIsHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region) const; - void SetMemoryRegionHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region); - - Ref<TypeLibrary> TypeLibraryForImage(const std::string& installName); - - std::optional<ObjCOptimizationHeader> GetObjCOptimizationHeader(VMReader reader) const; - - std::shared_ptr<MMappedFileAccessor> MapFile(const std::string& path); - static std::shared_ptr<MMappedFileAccessor> MapFileWithoutApplyingSlide(const std::string& path); - }; - - class SharedCacheMetadata - { - public: - static std::optional<SharedCacheMetadata> LoadFromView(BinaryView*); - static bool ViewHasMetadata(BinaryView*); - static std::optional<unsigned int> ViewMetadataVersion(BinaryView*); - - const std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>>& ExportInfos() const; - std::string InstallNameForImageBaseAddress(uint64_t baseAddress) const; + std::optional<CacheImage> GetImageContaining(uint64_t address) const; - ~SharedCacheMetadata(); - SharedCacheMetadata(SharedCacheMetadata&&); - SharedCacheMetadata& operator=(SharedCacheMetadata&&); + // TODO: Rename to GetImageWithPath and then make another one for the image name. + std::optional<CacheImage> GetImageWithName(const std::string& name) const; - private: - SharedCacheMetadata(SharedCache::CacheInfo, SharedCache::ModifiedState); + std::optional<CacheSymbol> GetSymbolAt(uint64_t address) const; - std::unique_ptr<SharedCache::CacheInfo> cacheInfo; - std::unique_ptr<SharedCache::ModifiedState> state; + std::optional<CacheSymbol> GetSymbolWithName(const std::string& name) const; +}; - friend struct SharedCache::ModifiedState; - friend class SharedCache; +// This constructs a Cache, give it a file path, and it will add all relevant cache entries. +class CacheProcessor +{ + BinaryNinja::Ref<BinaryNinja::BinaryView> m_view; + BinaryNinja::Ref<BinaryNinja::Logger> m_logger; - static const std::string Tag; - static const std::string CacheInfoTag; - static const std::string ModifiedStateTagPrefix; - static const std::string ModifiedStateCountTag; - }; -} +public: + explicit CacheProcessor(BinaryNinja::Ref<BinaryNinja::BinaryView> view); -void InitDSCViewType(); + // Construct a cache from the root file, this will parse the cache header and locate all + // applicable cache entries and add them as well. + bool ProcessCache(SharedCache& cache); -#endif //SHAREDCACHE_SHAREDCACHE_H + // Process a cache on the file system, this is for when not using a project. + bool ProcessFileCache(SharedCache& cache); + // Process a cache using Binary Ninja's project system. + bool ProcessProjectCache(SharedCache& cache); +}; diff --git a/view/sharedcache/core/SharedCacheController.cpp b/view/sharedcache/core/SharedCacheController.cpp new file mode 100644 index 00000000..29c1192a --- /dev/null +++ b/view/sharedcache/core/SharedCacheController.cpp @@ -0,0 +1,261 @@ +#include "SharedCacheController.h" + +#include <utility> + +#include "MachOProcessor.h" +#include "ObjC.h" +#include "SlideInfo.h" + +using namespace BinaryNinja; +using namespace BinaryNinja::DSC; + +// Unique ID for a given Binary View. +typedef uint64_t ViewId; + +std::shared_mutex GlobalControllersMutex; + +std::map<ViewId, DSCRef<SharedCacheController>>& GlobalControllers() +{ + // To make initialization order consistent we place the static in a function. + static std::map<ViewId, DSCRef<SharedCacheController>> g_dscViews = {}; + return g_dscViews; +} + +ViewId GetViewIdFromView(BinaryView& view) +{ + // Currently the view id is just the views session id. + // NOTE: If we want more than one shared cache controller per view we would need to make this more unique. + return view.GetFile()->GetSessionId(); +} + +void DeleteController(BinaryView& view) +{ + const auto id = GetViewIdFromView(view); + std::unique_lock<std::shared_mutex> lock(GlobalControllersMutex); + auto& dscViews = GlobalControllers(); + if (auto it = dscViews.find(id); it != dscViews.end()) + { + // Someone is still holding the controller, lets warn about this. + if (it->second->m_refs > 1) + LogWarn("Deleting SharedCacheController for view %llx, but there are still %d references", id, + it->second->m_refs.load()); + dscViews.erase(it); + LogDebug("Deleted SharedCacheController for view %s", view.GetFile()->GetFilename().c_str()); + } +} + +void RegisterSharedCacheControllerDestructor() +{ + BNObjectDestructionCallbacks callbacks = {}; + callbacks.destructBinaryView = [](void* ctx, BNBinaryView* obj) -> void { + auto view = BinaryView(obj); + DeleteController(view); + }; + BNRegisterObjectDestructionCallbacks(&callbacks); +} + +SharedCacheController::SharedCacheController(SharedCache cache, Ref<Logger> logger) : m_cache(std::move(cache)) +{ + INIT_DSC_API_OBJECT(); + m_logger = std::move(logger); + m_loadedRegions = {}; + m_loadedImages = {}; +} + +DSCRef<SharedCacheController> SharedCacheController::Initialize(BinaryView& view, SharedCache cache) +{ + auto id = GetViewIdFromView(view); + std::unique_lock<std::shared_mutex> lock(GlobalControllersMutex); + auto logger = new Logger("SharedCacheController", view.GetFile()->GetSessionId()); + DSCRef<SharedCacheController> dscView = new SharedCacheController(std::move(cache), logger); + + // Pull the settings from the view. + if (Ref<Settings> settings = view.GetLoadSettings(VIEW_NAME)) + { + if (settings->Contains("loader.dsc.processObjC")) + dscView->m_processObjC = settings->Get<bool>("loader.dsc.processObjC", &view); + if (settings->Contains("loader.dsc.processCFStrings")) + dscView->m_processCFStrings = settings->Get<bool>("loader.dsc.processCFStrings", &view); + if (settings->Contains("loader.dsc.regionFilter")) + dscView->m_regionFilter = std::regex(settings->Get<std::string>("loader.dsc.regionFilter", &view)); + } + + // Check the view auto metadata for shared cache information. + // This effectively restores the state of the opened database to when it was last saved. + auto metadata = view.GetAutoMetadata()->GetKeyValueStore(); + if (metadata.find(METADATA_KEY) != metadata.end()) + dscView->LoadMetadata(*metadata[METADATA_KEY]); + + GlobalControllers().insert({id, dscView}); + return dscView; +} + +DSCRef<SharedCacheController> SharedCacheController::FromView(BinaryView& view) +{ + auto id = GetViewIdFromView(view); + std::shared_lock<std::shared_mutex> lock(GlobalControllersMutex); + auto& dscViews = GlobalControllers(); + auto dscView = dscViews.find(id); + if (dscView == dscViews.end()) + return nullptr; + return dscView->second; +} + +bool SharedCacheController::ApplyRegionAtAddress(BinaryView& view, const uint64_t address) +{ + auto region = m_cache.GetRegionAt(address); + if (!region) + return false; + return ApplyRegion(view, *region); +} + +bool SharedCacheController::ApplyRegion(BinaryView& view, const CacheRegion& region) +{ + // TODO: Check m_loadedRegions? This seems redundant... + // Loads the given region into the BinaryView and marks it as loaded. + // First check to make sure there isn't already a segment at the address in the view. + if (view.GetSegmentAt(region.start)) + return false; + + // Skip filtered regions, this defaults to just LINKEDIT regions. + if (std::regex_match(region.name, m_regionFilter)) + { + LogDebug("Skipping filtered region at %llx", region.start); + return false; + } + + auto vm = m_cache.GetVirtualMemory(); + auto reader = VirtualMemoryReader(vm); + DataBuffer buffer = {}; + try + { + buffer = reader.ReadBuffer(region.start, region.size); + } + catch (std::exception& e) + { + // This happens if we have not mapped in all the relevant entries. + m_logger->LogError("Failed to read region: %s", e.what()); + return false; + } + + // Unique memory region name so that we don't cause collisions. + // TODO: Better name? I dont really think so... + const auto memoryRegionName = fmt::format("{}_0x{:x}", region.name, region.start); + + // NOTE: Adding a data memory region will store the entire contents of the region in the BNDB. + // TODO: We can use the AddRemoteMemoryRegion if we want to reload on view init. + // TODO: ^ The above is only useful if we assume that all files will be available across database loads. + // TODO: we might allow a user to select non-persisted memory regions as an option. + view.GetMemoryMap()->AddDataMemoryRegion(memoryRegionName, region.start, buffer, region.flags); + // TODO: We might want to make this auto if we decide to "reload" all loaded region in view init. + // If we are not associated with an image we can create a section here to set the semantics. + // This is important for stub regions, as they will deref non image data that we want to retrieve the value of. + if (region.type != CacheRegionType::Image) + view.AddUserSection(memoryRegionName, region.start, region.size, region.SectionSemanticsForRegion()); + + m_loadedRegions.insert(region.start); + + // TODO: This needs to be done in a "database save" callback. + view.StoreMetadata(METADATA_KEY, GetMetadata()); + + return true; +} + +bool SharedCacheController::IsRegionLoaded(const CacheRegion& region) const +{ + return std::any_of(m_loadedRegions.begin(), m_loadedRegions.end(), [&](const auto& loadedRegion) { + return loadedRegion == region.start; + }); +} + +bool SharedCacheController::ApplyImage(BinaryView& view, const CacheImage& image) +{ + // TODO: Check m_loadedImages? This seems redundant... + // Load all regions of an image and mark the image as loaded. + bool loadedRegion = false; + for (const auto& regionStart : image.regionStarts) + if (ApplyRegionAtAddress(view, regionStart)) + loadedRegion = true; + + // If there was no loaded regions than we just want to forgo loading the image. + if (!loadedRegion) + return false; + + if (image.header) + { + // Header information is applied to the view here, such as sections. + auto machoProcessor = SharedCacheMachOProcessor(&view, m_cache.GetVirtualMemory()); + machoProcessor.ApplyHeader(*image.header); + + // TODO: Make this optional. + // Load objective-c information. + auto objcProcessor = DSCObjC::SharedCacheObjCProcessor(&view, false); + // TODO: Passing in an image name here is weird considering this is shared with the MACHO view. + // TODO: We should abstract out the "image" into an objc image type that represents what is required, which ig is the name? + try + { + if (m_processObjC) + objcProcessor.ProcessObjCData(image.GetName()); + if (m_processObjC) + objcProcessor.ProcessCFStrings(image.GetName()); + } + catch (std::exception& e) + { + // Let the user know there was an error in processing the objc stuff but let the image load + // regardless, as its non-critical. + m_logger->LogError("Failed to process ObjC information: %s", e.what()); + } + } + + m_loadedImages.insert(image.headerAddress); + + // TODO: This needs to be done in a "database save" callback. + view.StoreMetadata(METADATA_KEY, GetMetadata()); + + // TODO: Partial failure state (i.e. 2 regions loaded, one failed) + return true; +} + +bool SharedCacheController::IsImageLoaded(const CacheImage& image) const +{ + return std::any_of(m_loadedImages.begin(), m_loadedImages.end(), [&](const auto& loadedImage) { + return loadedImage == image.headerAddress; + }); +} + +Ref<Metadata> SharedCacheController::GetMetadata() const +{ + std::map<std::string, Ref<Metadata>> controllerMeta; + + std::vector<uint64_t> loadedImages; + std::vector<uint64_t> loadedRegions; + loadedImages.reserve(m_loadedImages.size()); + loadedRegions.reserve(m_loadedRegions.size()); + for (const auto& loadedImage : m_loadedImages) + loadedImages.push_back(loadedImage); + for (const auto& loadedRegion : m_loadedRegions) + loadedRegions.push_back(loadedRegion); + + controllerMeta["loadedImages"] = new Metadata(loadedImages); + controllerMeta["loadedRegions"] = new Metadata(loadedRegions); + + return new Metadata(controllerMeta); +} + +void SharedCacheController::LoadMetadata(const Metadata& metadata) +{ + auto controllerMeta = metadata.GetKeyValueStore(); + if (controllerMeta.find("loadedImages") != controllerMeta.end()) + { + const auto loadedImages = controllerMeta["loadedImages"]->GetUnsignedIntegerList(); + for (const auto& image : loadedImages) + m_loadedImages.insert(image); + } + + if (controllerMeta.find("loadedRegions") != controllerMeta.end()) + { + const auto loadedRegions = controllerMeta["loadedRegions"]->GetUnsignedIntegerList(); + for (const auto& region : loadedRegions) + m_loadedImages.insert(region); + } +} diff --git a/view/sharedcache/core/SharedCacheController.h b/view/sharedcache/core/SharedCacheController.h new file mode 100644 index 00000000..354762eb --- /dev/null +++ b/view/sharedcache/core/SharedCacheController.h @@ -0,0 +1,65 @@ +#pragma once + +#include <regex> + +#include "SharedCache.h" +#include "refcountobject.h" +#include "ffi_global.h" + +DECLARE_DSC_API_OBJECT(BNSharedCacheController, SharedCacheController); + +void RegisterSharedCacheControllerDestructor(); + +namespace BinaryNinja::DSC { + static const char* METADATA_KEY = "shared_cache"; + + // Represents the view state for a given `DSCache` + class SharedCacheController : public DSCRefCountObject + { + IMPLEMENT_DSC_API_OBJECT(BNSharedCacheController); + Ref<Logger> m_logger; + + SharedCache m_cache; + // Store the open images. + // Things other than the cache here will be serialized. + std::unordered_set<uint64_t> m_loadedRegions; + std::unordered_set<uint64_t> m_loadedImages; + + // Settings from the view. + std::regex m_regionFilter; + bool m_processObjC; + bool m_processCFStrings; + + explicit SharedCacheController(SharedCache cache, Ref<Logger> logger); + + public: + // Initialize the DSCacheView, this should be called from the view initialize function only! + static DSCRef<SharedCacheController> Initialize(BinaryView& view, SharedCache cache); + + // NOTE: This will not create one if it does not exist. To create one for the view call `Initialize`. + static DSCRef<SharedCacheController> FromView(BinaryView& view); + + SharedCache& GetCache() { return m_cache; }; + const std::unordered_set<uint64_t>& GetLoadedRegions() { return m_loadedRegions; }; + const std::unordered_set<uint64_t>& GetLoadedImages() { return m_loadedImages; }; + + // TODO: LoadResult type? AlreadyLoaded, Loaded, NotLoaded. + // NOTE: `address` should be the start of a region, not containing the address. + bool ApplyRegionAtAddress(BinaryView& view, uint64_t address); + + bool ApplyRegion(BinaryView& view, const CacheRegion& region); + + bool IsRegionLoaded(const CacheRegion& region) const; + + // Loads the relevant image info into the view. This does not update analysis so if you + // call this make sure at some point you update analysis and likely with linear sweep. + bool ApplyImage(BinaryView& view, const CacheImage& image); + + bool IsImageLoaded(const CacheImage& image) const; + + // Get the metadata for saving the state of the shared cache. + Ref<Metadata> GetMetadata() const; + + void LoadMetadata(const Metadata& metadata); + }; +} // namespace BinaryNinja::DSC diff --git a/view/sharedcache/core/DSCView.cpp b/view/sharedcache/core/SharedCacheView.cpp index add2faff..bf80c6eb 100644 --- a/view/sharedcache/core/DSCView.cpp +++ b/view/sharedcache/core/SharedCacheView.cpp @@ -1,82 +1,208 @@ -// -// Created by kat on 5/23/23. -// +#include "SharedCacheView.h" +#include <regex> +#include "SharedCacheController.h" +#include "FileAccessorCache.h" +#include "MappedFileAccessor.h" -/* - * If you're here looking for the code to load caches, out of luck. - * - * The VIEW_NAME is essentially a blank slate that only knows how to deserialize and reserialize itself - * based on some metadata encoded in it. - * - * The actual controller logic that does _all_ of the image loading is invoked via API -> SharedCache.cpp - * - * */ +using namespace BinaryNinja; +using namespace BinaryNinja::DSC; -#include "DSCView.h" -#include "view/macho/machoview.h" -#include "SharedCache.h" +SharedCacheViewType::SharedCacheViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME) {} -using namespace BinaryNinja; +// We register all our one-shot stuff here, such as the object destructor. +void SharedCacheViewType::Register() +{ + auto fdLimit = AdjustFileDescriptorLimit(); + LogDebug("Shared Cache processing initialized with a max file descriptor limit of %lld", fdLimit); + + // Adjust the global accessor cache to the fdlimit. + FileAccessorCache::Global().SetCacheSize(fdLimit); + + // TODO: Register object destructor to clear accessor cache + RegisterSharedCacheControllerDestructor(); + + static SharedCacheViewType type; + BinaryViewType::Register(&type); +} + +Ref<BinaryView> SharedCacheViewType::Create(BinaryView* data) +{ + return new SharedCacheView(VIEW_NAME, data, false); +} + +Ref<Settings> SharedCacheViewType::GetLoadSettingsForData(BinaryView* data) +{ + Ref<BinaryView> viewRef = Parse(data); + if (!viewRef || !viewRef->Init()) + { + LogWarn("Failed to initialize view of type '%s'. Generating default load settings.", GetName().c_str()); + viewRef = data; + } + + Ref<Settings> settings = GetDefaultLoadSettingsForData(viewRef); + + // specify default load settings that can be overridden + std::vector<std::string> overrides = {"loader.imageBase", "loader.platform"}; + settings->UpdateProperty("loader.imageBase", "message", "Note: File indicates image is not relocatable."); + + for (const auto& override : overrides) + { + if (settings->Contains(override)) + settings->UpdateProperty(override, "readOnly", false); + } + + Ref<Settings> programSettings = Settings::Instance(); + programSettings->Set("analysis.workflows.functionWorkflow", "core.function.sharedCache", viewRef); + + settings->RegisterSetting("loader.dsc.processCFStrings", + R"({ + "title" : "Process CFString Metadata", + "type" : "boolean", + "default" : true, + "description" : "Processes CoreFoundation strings, applying string values from encoded metadata" + })"); + + settings->RegisterSetting("loader.dsc.autoLoadPattern", + R"({ + "title" : "Image Auto-Load Regex Pattern", + "type" : "string", + "default" : ".*libsystem_c.dylib", + "description" : "A regex pattern to auto load matching images at the end of view init, defaults to the system_c image only." + })"); + + settings->RegisterSetting("loader.dsc.processObjC", + R"({ + "title" : "Process Objective-C Metadata", + "type" : "boolean", + "default" : true, + "description" : "Processes Objective-C metadata, applying class and method names from encoded metadata" + })"); + + settings->RegisterSetting("loader.dsc.regionFilter", + R"({ + "title" : "Region Regex Filter", + "type" : "string", + "default" : "LINKEDIT", + "description" : "Regex filter for region names to skip loading, by default this filters out the link edit region which is not necessary to be applied to the view." + })"); + + settings->RegisterSetting("loader.dsc.autoLoadObjCStubRequirements", + R"({ + "title" : "Auto-Load Objective-C Stub Requirements", + "type" : "boolean", + "default" : true, + "description" : "Automatically loads segments required for inlining Objective-C stubs. Recommended you keep this on." + })"); + + settings->RegisterSetting("loader.dsc.autoLoadStubsAndDyldData", + R"({ + "title" : "Auto-Load Stub Islands", + "type" : "boolean", + "default" : true, + "description" : "Automatically loads stub and dylddata regions that contain just branches and pointers. These are required for resolving stub names, and performance impact is minimal. Recommended you keep this on." + })"); + + settings->RegisterSetting("loader.dsc.processFunctionStarts", + R"({ + "title" : "Process Mach-O Function Starts Tables", + "type" : "boolean", + "default" : true, + "description" : "Add function starts sourced from the Function Starts tables to the core for analysis." + })"); + // Merge existing load settings if they exist. This allows for the selection of a specific object file from a Mach-O + // Universal file. The 'Universal' BinaryViewType generates a schema with 'loader.universal.architectures'. This + // schema contains an appropriate 'Mach-O' load schema for selecting a specific object file. The embedded schema + // contains 'loader.macho.universalImageOffset'. + Ref<Settings> loadSettings = viewRef->GetLoadSettings(GetName()); + if (loadSettings && !loadSettings->IsEmpty()) + settings->DeserializeSchema(loadSettings->SerializeSchema()); -DSCView::DSCView(const std::string& typeName, BinaryView* data, bool parseOnly) : + return settings; +} + +Ref<BinaryView> SharedCacheViewType::Parse(BinaryView* data) +{ + return new SharedCacheView(VIEW_NAME, data, true); +} + +bool SharedCacheViewType::IsTypeValidForData(BinaryView* data) +{ + if (!data) + return false; + + DataBuffer sig = data->ReadBuffer(data->GetStart(), 4); + if (sig.GetLength() != 4) + return false; + + const char* magic = (char*)sig.GetData(); + if (strncmp(magic, "dyld", 4) == 0) + return true; + + return false; +} + +SharedCacheView::SharedCacheView(const std::string& typeName, BinaryView* data, bool parseOnly) : BinaryView(typeName, data->GetFile(), data), m_parseOnly(parseOnly) { CreateLogger("SharedCache"); CreateLogger("SharedCache.ObjC"); } -DSCView::~DSCView() +enum DSCPlatform { -} - -enum DSCPlatform { DSCPlatformMacOS = 1, DSCPlatformiOS = 2, DSCPlatformTVOS = 3, DSCPlatformWatchOS = 4, - DSCPlatformBridgeOS = 5, // T1/T2 APL1023/T8012, this is your touchbar/touchid in intel macs. Similar to watchOS. + DSCPlatformBridgeOS = 5, // T1/T2 APL1023/T8012, this is your touchbar/touchid in intel macs. Similar to watchOS. // DSCPlatformMacCatalyst = 6, DSCPlatformiOSSimulator = 7, DSCPlatformTVOSSimulator = 8, DSCPlatformWatchOSSimulator = 9, - DSCPlatformVisionOS = 11, // Apple Vision Pro - DSCPlatformVisionOSSimulator = 12 // Apple Vision Pro Simulator + DSCPlatformVisionOS = 11, // Apple Vision Pro + DSCPlatformVisionOSSimulator = 12 // Apple Vision Pro Simulator }; -bool DSCView::Init() +bool SharedCacheView::Init() { std::string os; std::string arch; + auto logger = new Logger("SharedCacheView", GetFile()->GetSessionId()); + uint32_t platform; GetParentView()->Read(&platform, 0xd8, 4); char magic[17]; GetParentView()->Read(&magic, 0, 16); magic[16] = 0; + + // TODO: Do we want to add any warnings about platform support here? + // TODO: Do we still consider macos experimental? switch (platform) { - case DSCPlatformMacOS: - case DSCPlatformTVOS: - case DSCPlatformTVOSSimulator: - os = "mac"; - break; - case DSCPlatformiOS: - case DSCPlatformiOSSimulator: - case DSCPlatformVisionOS: - case DSCPlatformVisionOSSimulator: - os = "ios"; - break; - // armv7 or slide info v1 (unsupported) - case DSCPlatformWatchOS: - case DSCPlatformWatchOSSimulator: - case DSCPlatformBridgeOS: - default: - LogError("Unknown platform: %d", platform); - return false; + case DSCPlatformMacOS: + case DSCPlatformTVOS: + case DSCPlatformTVOSSimulator: + os = "mac"; + break; + case DSCPlatformiOS: + case DSCPlatformiOSSimulator: + case DSCPlatformVisionOS: + case DSCPlatformVisionOSSimulator: + os = "ios"; + break; + // armv7 or slide info v1 (unsupported) + case DSCPlatformWatchOS: + case DSCPlatformWatchOSSimulator: + case DSCPlatformBridgeOS: + default: + logger->LogError("Unknown platform: %d", platform); + return false; } - if (std::string(magic) == "dyld_v1 arm64" || std::string(magic) == "dyld_v1 arm64e" || std::string(magic) == "dyld_v1arm64_32") + if (std::string(magic) == "dyld_v1 arm64" || std::string(magic) == "dyld_v1 arm64e" + || std::string(magic) == "dyld_v1arm64_32") { arch = "aarch64"; } @@ -86,24 +212,38 @@ bool DSCView::Init() } else { - LogError("Unknown magic: %s", magic); + logger->LogError("Unknown magic: %s", magic); return false; } SetDefaultPlatform(Platform::GetByName(os + "-" + arch)); SetDefaultArchitecture(Architecture::GetByName(arch)); + Ref<Settings> settings = GetLoadSettings(GetTypeName()); + + if (!settings) + { + Ref<Settings> programSettings = Settings::Instance(); + programSettings->Set("analysis.workflows.functionWorkflow", "core.function.sharedCache", this); + } + + if (m_parseOnly) + return true; + QualifiedNameAndType headerType; std::string err; - ParseTypeString("\n" + ParseTypeString( + "\n" "\tstruct dyld_cache_header\n" "\t{\n" "\t\tchar magic[16];\t\t\t\t\t // e.g. \"dyld_v0 i386\"\n" "\t\tuint32_t mappingOffset;\t\t\t // file offset to first dyld_cache_mapping_info\n" "\t\tuint32_t mappingCount;\t\t\t // number of dyld_cache_mapping_info entries\n" - "\t\tuint32_t imagesOffsetOld;\t\t // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing\n" - "\t\tuint32_t imagesCountOld;\t\t // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing\n" + "\t\tuint32_t imagesOffsetOld;\t\t // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from " + "crashing\n" + "\t\tuint32_t imagesCountOld;\t\t // UNUSED: moved to imagesCount to prevent older dsc_extarctors from " + "crashing\n" "\t\tuint64_t dyldBaseAddress;\t\t // base address of dyld when cache was built\n" "\t\tuint64_t codeSignatureOffset;\t // file offset of code signature blob\n" "\t\tuint64_t codeSignatureSize;\t\t // size of code signature blob (zero means to end of file)\n" @@ -120,7 +260,8 @@ bool DSCView::Init() "\t\tuint64_t imagesTextOffset;\t\t // file offset to first dyld_cache_image_text_info\n" "\t\tuint64_t imagesTextCount;\t\t // number of dyld_cache_image_text_info entries\n" "\t\tuint64_t patchInfoAddr;\t\t\t // (unslid) address of dyld_cache_patch_info\n" - "\t\tuint64_t patchInfoSize;\t // Size of all of the patch information pointed to via the dyld_cache_patch_info\n" + "\t\tuint64_t patchInfoSize;\t // Size of all of the patch information pointed to via the " + "dyld_cache_patch_info\n" "\t\tuint64_t otherImageGroupAddrUnused;\t // unused\n" "\t\tuint64_t otherImageGroupSizeUnused;\t // unused\n" "\t\tuint64_t progClosuresAddr;\t\t\t // (unslid) address of list of program launch closures\n" @@ -129,10 +270,12 @@ bool DSCView::Init() "\t\tuint64_t progClosuresTrieSize;\t\t // size of trie of indexes into program launch closures\n" "\t\tuint32_t platform;\t\t\t\t\t // platform number (macOS=1, etc)\n" "\t\tuint32_t formatVersion : 8,\t\t\t // dyld3::closure::kFormatVersion\n" - "\t\t\tdylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid\n" + "\t\t\tdylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to " + "see if cache is valid\n" "\t\t\tsimulator : 1,\t\t\t // for simulator of specified platform\n" "\t\t\tlocallyBuiltCache : 1,\t // 0 for B&I built cache, 1 for locally built cache\n" - "\t\t\tbuiltFromChainedFixups : 1,\t // some dylib in cache was built using chained fixups, so patch tables must be used for overrides\n" + "\t\t\tbuiltFromChainedFixups : 1,\t // some dylib in cache was built using chained fixups, so patch tables " + "must be used for overrides\n" "\t\t\tpadding : 20;\t\t\t\t // TBD\n" "\t\tuint64_t sharedRegionStart;\t\t // base load address of cache if not slid\n" "\t\tuint64_t sharedRegionSize;\t\t // overall size required to map the cache and all subCaches, if any\n" @@ -141,9 +284,11 @@ bool DSCView::Init() "\t\tuint64_t dylibsImageArraySize;\t // size of ImageArray for dylibs in this cache\n" "\t\tuint64_t dylibsTrieAddr;\t\t // (unslid) address of trie of indexes of all cached dylibs\n" "\t\tuint64_t dylibsTrieSize;\t\t // size of trie of cached dylib paths\n" - "\t\tuint64_t otherImageArrayAddr;\t // (unslid) address of ImageArray for dylibs and bundles with dlopen closures\n" + "\t\tuint64_t otherImageArrayAddr;\t // (unslid) address of ImageArray for dylibs and bundles with dlopen " + "closures\n" "\t\tuint64_t otherImageArraySize;\t // size of ImageArray for dylibs and bundles with dlopen closures\n" - "\t\tuint64_t otherTrieAddr;\t // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures\n" + "\t\tuint64_t otherTrieAddr;\t // (unslid) address of trie of indexes of all dylibs and bundles with dlopen " + "closures\n" "\t\tuint64_t otherTrieSize;\t // size of trie of dylibs and bundles with dlopen closures\n" "\t\tuint32_t mappingWithSlideOffset;\t\t // file offset to first dyld_cache_mapping_and_slide_info\n" "\t\tuint32_t mappingWithSlideCount;\t\t\t // number of dyld_cache_mapping_and_slide_info entries\n" @@ -160,49 +305,37 @@ bool DSCView::Init() "\t\tuint64_t swiftOptsSize;\t\t\t// size of Swift optimizations header\n" "\t\tuint32_t subCacheArrayOffset;\t// file offset to first dyld_subcache_entry\n" "\t\tuint32_t subCacheArrayCount;\t// number of subCache entries\n" - "\t\tuint8_t symbolFileUUID[16];\t\t// unique value for the shared cache file containing unmapped local symbols\n" - "\t\tuint64_t rosettaReadOnlyAddr;\t// (unslid) address of the start of where Rosetta can add read-only/executable data\n" + "\t\tuint8_t symbolFileUUID[16];\t\t// unique value for the shared cache file containing unmapped local " + "symbols\n" + "\t\tuint64_t rosettaReadOnlyAddr;\t// (unslid) address of the start of where Rosetta can add " + "read-only/executable data\n" "\t\tuint64_t rosettaReadOnlySize;\t// maximum size of the Rosetta read-only/executable region\n" - "\t\tuint64_t rosettaReadWriteAddr;\t// (unslid) address of the start of where Rosetta can add read-write data\n" + "\t\tuint64_t rosettaReadWriteAddr;\t// (unslid) address of the start of where Rosetta can add read-write " + "data\n" "\t\tuint64_t rosettaReadWriteSize;\t// maximum size of the Rosetta read-write region\n" "\t\tuint32_t imagesOffset;\t\t\t// file offset to first dyld_cache_image_info\n" "\t\tuint32_t imagesCount;\t\t\t// number of dyld_cache_image_info entries\n" - "\t\tuint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2)\n" + "\t\tuint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is " + "multi-cache(2)\n" "\t\tuint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header\n" "\t\tuint64_t objcOptsSize; // size of ObjC optimizations header\n" - "\t\tuint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection\n" + "\t\tuint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process " + "introspection\n" "\t\tuint64_t cacheAtlasSize; // size of embedded cache atlas\n" - "\t\tuint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header\n" + "\t\tuint64_t dynamicDataOffset; // VM offset from cache_header* to the location of " + "dyld_cache_dynamic_data_header\n" "\t\tuint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data\n" "\t\tuint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info\n" "\t\tuint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries\n" - "\t};", headerType, err); + "\t};", + headerType, err); if (!err.empty() || !headerType.type) { - LogError("Failed to parse header type: %s", err.c_str()); + logger->LogError("Failed to parse header type: %s", err.c_str()); return false; } - Ref<Settings> settings = GetLoadSettings(GetTypeName()); - - if (!settings) - { - Ref<Settings> programSettings = Settings::Instance(); - programSettings->Set("analysis.workflows.functionWorkflow", "core.function.dsc", this); - } - - if (m_parseOnly) - return true; - - // Check the metadata version if there is one, so we can alert the user to why they have no loaded images. - // TODO: Once the view is able to be exited early we should offer to close the view if the user does not want to upgrade. - auto metadataVersion = SharedCacheCore::SharedCacheMetadata::ViewMetadataVersion(GetParentView()); - if (metadataVersion.has_value() && metadataVersion.value() != METADATA_VERSION) - { - ShowMessageBox("Invalid Shared Cache Metadata!", "The BNDB shared cache metadata was created with a different version of the Shared Cache view, to continue the metadata has to be recreated. You will need to add your images back again."); - } - // Add Mach-O file header type info EnumerationBuilder cpuTypeBuilder; cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ANY", MACHO_CPU_TYPE_ANY); @@ -335,17 +468,17 @@ bool DSCView::Init() cmdTypeBuilder.AddMemberWithValue("LC_RPATH", LC_RPATH); // (0x1c | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_CODE_SIGNATURE", LC_CODE_SIGNATURE); cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_SPLIT_INFO", LC_SEGMENT_SPLIT_INFO); - cmdTypeBuilder.AddMemberWithValue("LC_REEXPORT_DYLIB", LC_REEXPORT_DYLIB); // (0x1f | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_REEXPORT_DYLIB", LC_REEXPORT_DYLIB); // (0x1f | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_LAZY_LOAD_DYLIB", LC_LAZY_LOAD_DYLIB); cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO", LC_ENCRYPTION_INFO); cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO", LC_DYLD_INFO); - cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO_ONLY", LC_DYLD_INFO_ONLY); // (0x22 | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO_ONLY", LC_DYLD_INFO_ONLY); // (0x22 | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_LOAD_UPWARD_DYLIB", LC_LOAD_UPWARD_DYLIB); // (0x23 | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_MACOSX", LC_VERSION_MIN_MACOSX); cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_IPHONEOS", LC_VERSION_MIN_IPHONEOS); cmdTypeBuilder.AddMemberWithValue("LC_FUNCTION_STARTS", LC_FUNCTION_STARTS); cmdTypeBuilder.AddMemberWithValue("LC_DYLD_ENVIRONMENT", LC_DYLD_ENVIRONMENT); - cmdTypeBuilder.AddMemberWithValue("LC_MAIN", LC_MAIN); // (0x28 | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_MAIN", LC_MAIN); // (0x28 | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_DATA_IN_CODE", LC_DATA_IN_CODE); cmdTypeBuilder.AddMemberWithValue("LC_SOURCE_VERSION", LC_SOURCE_VERSION); cmdTypeBuilder.AddMemberWithValue("LC_DYLIB_CODE_SIGN_DRS", LC_DYLIB_CODE_SIGN_DRS); @@ -622,14 +755,14 @@ bool DSCView::Init() GetParentView()->Read(&basePointer, 16, 4); if (basePointer == 0) { - LogError("Failed to read base pointer"); + logger->LogError("Failed to read base pointer"); return false; } uint64_t primaryBase = 0; GetParentView()->Read(&primaryBase, basePointer, 8); if (primaryBase == 0) { - LogError("Failed to read primary base at 0x%llx", basePointer); + logger->LogError("Failed to read primary base at 0x%llx", basePointer); return false; } @@ -641,131 +774,81 @@ bool DSCView::Init() AddAutoSegment(primaryBase, headerSize, 0, headerSize, SegmentReadable); AddAutoSection("__dsc_header", primaryBase, headerSize, ReadOnlyDataSectionSemantics); DefineType("dyld_cache_header", headerType.name, headerType.type); - DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type); + DefineAutoSymbolAndVariableOrFunction( + GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type); - return true; -} - - -DSCViewType::DSCViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME) -{ -} + auto sharedCache = SharedCache(GetAddressSize()); -BinaryNinja::Ref<BinaryNinja::BinaryView> DSCViewType::Create(BinaryNinja::BinaryView* data) -{ - return new DSCView(VIEW_NAME, data, false); -} - - -Ref<Settings> DSCViewType::GetLoadSettingsForData(BinaryView* data) -{ - Ref<BinaryView> viewRef = Parse(data); - if (!viewRef || !viewRef->Init()) { - LogWarn("Failed to initialize view of type '%s'. Generating default load settings.", GetName().c_str()); - viewRef = data; + // Add up all the cache entries using the cache processor. + // After this we should have all the mappings available as well. + CacheProcessor cacheProcessor = CacheProcessor(this); + auto startTime = std::chrono::high_resolution_clock::now(); + if (!cacheProcessor.ProcessCache(sharedCache)) + { + // Oh no, we failed to process the cache, this likely means the primary on-disk file was not able to be + // found. + // TODO: Prompt the user to select the primary cache file? We can still recover from here. + logger->LogError("Failed to process cache, likely missing cache files."); + // NOTE: An interaction handler headlessly could select yes to this, however for the purposes of this we will just let them continue by defualt. + if (IsUIEnabled() && ShowMessageBox("Continue opening", "This shared cache file was unable to be processed, would you like to open without shared cache information?", YesNoButtonSet, QuestionIcon) != YesButton) + return false; + } + auto endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double> elapsed = endTime - startTime; + logger->LogInfo("Processing %zu entries took %.3f seconds", sharedCache.GetEntries().size(), elapsed.count()); } - Ref<Settings> settings = GetDefaultLoadSettingsForData(viewRef); - - // specify default load settings that can be overridden - std::vector<std::string> overrides = {"loader.imageBase", "loader.platform"}; - settings->UpdateProperty("loader.imageBase", "message", "Note: File indicates image is not relocatable."); - - for (const auto& override : overrides) { - if (settings->Contains(override)) - settings->UpdateProperty(override, "readOnly", false); + // Write all the slide info pointers to the virtual memory. + // This should be done before any other work begins, as the backing data will be altered by this process. + auto startTime = std::chrono::high_resolution_clock::now(); + for (const auto& [_, entry] : sharedCache.GetEntries()) + sharedCache.ProcessEntrySlideInfo(entry); + auto endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double> elapsed = endTime - startTime; + logger->LogInfo("Processing slide info took %.3f seconds", elapsed.count()); } - Ref<Settings> programSettings = Settings::Instance(); - programSettings->Set("analysis.workflows.functionWorkflow", "core.function.dsc", viewRef); - - settings->RegisterSetting("loader.dsc.processCFStrings", - R"({ - "title" : "Process CFString Metadata", - "type" : "boolean", - "default" : true, - "description" : "Processes CoreFoundation strings, applying string values from encoded metadata" - })"); - - settings->RegisterSetting("loader.dsc.autoLoadLibSystem", - R"({ - "title" : "Auto-Load libSystem", - "type" : "boolean", - "default" : true, - "description" : "Whether to automatically load libsystem_c.dylib. This image contains frequently used noreturn symbols, and not loading it will result in frequently incorrect control flows." - })"); - - settings->RegisterSetting("loader.dsc.processObjC", - R"({ - "title" : "Process Objective-C Metadata", - "type" : "boolean", - "default" : true, - "description" : "Processes Objective-C metadata, applying class and method names from encoded metadata" - })"); - - settings->RegisterSetting("loader.dsc.autoLoadObjCStubRequirements", - R"({ - "title" : "Auto-Load Objective-C Stub Requirements", - "type" : "boolean", - "default" : true, - "description" : "Automatically loads segments required for inlining Objective-C stubs. Recommended you keep this on." - })"); - - settings->RegisterSetting("loader.dsc.autoLoadStubsAndDyldData", - R"({ - "title" : "Auto-Load Stub Islands", - "type" : "boolean", - "default" : true, - "description" : "Automatically loads stub and dylddata regions that contain just branches and pointers. These are required for resolving stub names, and performance impact is minimal. Recommended you keep this on." - })"); - - settings->RegisterSetting("loader.dsc.allowLoadingLinkeditSegments", - R"({ - "title" : "Allow Loading __LINKEDIT Segments", - "type" : "boolean", - "default" : false, - "description" : "Allow mapping __LINKEDIT segments. These are large regions of symbol data that are automatically processed by BinaryNinja without the need for mapping. On newer caches, __LINKEDIT for all images may end up merged and be >300MB in size. This will likely cause severe performance degradation with _zero_ benefit." - })"); - - settings->RegisterSetting("loader.dsc.processFunctionStarts", - R"({ - "title" : "Process Mach-O Function Starts Tables", - "type" : "boolean", - "default" : true, - "description" : "Add function starts sourced from the Function Starts tables to the core for analysis." - })"); - - // Merge existing load settings if they exist. This allows for the selection of a specific object file from a Mach-O - // Universal file. The 'Universal' BinaryViewType generates a schema with 'loader.universal.architectures'. This - // schema contains an appropriate 'Mach-O' load schema for selecting a specific object file. The embedded schema - // contains 'loader.macho.universalImageOffset'. - Ref<Settings> loadSettings = viewRef->GetLoadSettings(GetName()); - if (loadSettings && !loadSettings->IsEmpty()) - settings->DeserializeSchema(loadSettings->SerializeSchema()); - - return settings; -} - + { + // Load up all images into the cache before adding extra regions. + // Currently, it is expected that a primary entry contain all relevant images, so we only check that one. + auto startTime = std::chrono::high_resolution_clock::now(); + for (const auto& [_, entry] : sharedCache.GetEntries()) + if (entry.GetType() == CacheEntryType::Primary) + sharedCache.ProcessEntryImages(entry); + auto endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double> elapsed = endTime - startTime; + auto images = sharedCache.GetImages(); + logger->LogInfo("Processing %zu images took %.3f seconds", images.size(), elapsed.count()); + // Warn if we found no images, and provide the likely explanation + if (images.empty()) + logger->LogWarn("Failed to process any images, likely missing cache files."); + } -BinaryNinja::Ref<BinaryNinja::BinaryView> DSCViewType::Parse(BinaryNinja::BinaryView* data) -{ - return new DSCView(VIEW_NAME, data, true); -} + { + // TODO: Run this up on a separate thread maybe and have callback notifications? + // Load up all the regions into the cache. + auto startTime = std::chrono::high_resolution_clock::now(); + for (const auto& [_, entry] : sharedCache.GetEntries()) + sharedCache.ProcessEntryRegions(entry); + auto endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double> elapsed = endTime - startTime; + logger->LogInfo("Processing %zu regions took %.3f seconds", sharedCache.GetRegions().size(), elapsed.count()); + } -bool DSCViewType::IsTypeValidForData(BinaryNinja::BinaryView* data) -{ - if (!data) - return false; + auto cacheController = SharedCacheController::Initialize(*this, std::move(sharedCache)); - DataBuffer sig = data->ReadBuffer(data->GetStart(), 4); - if (sig.GetLength() != 4) - return false; + // Users can adjust which images are loaded by default using the `loader.dsc.autoLoadPattern` setting. + std::string autoLoadPattern = ".*libsystem_c.dylib"; + if (settings && settings->Contains("loader.dsc.autoLoadPattern")) + autoLoadPattern = settings->Get<std::string>("loader.dsc.autoLoadPattern", this); + logger->LogDebug("Loading images using pattern: %s", autoLoadPattern.c_str()); - const char* magic = (char*)sig.GetData(); - if (strncmp(magic, "dyld", 4) == 0) - return true; + std::regex autoLoadRegex(autoLoadPattern); + for (const auto& [_, image] : cacheController->GetCache().GetImages()) + if (std::regex_match(image.GetName(), autoLoadRegex)) + cacheController->ApplyImage(*this, image); - return false; + return true; } diff --git a/view/sharedcache/core/SharedCacheView.h b/view/sharedcache/core/SharedCacheView.h new file mode 100644 index 00000000..1ead0b33 --- /dev/null +++ b/view/sharedcache/core/SharedCacheView.h @@ -0,0 +1,43 @@ +// +// Created by kat on 5/23/23. +// + +#ifndef SHAREDCACHE_DSCVIEW_H +#define SHAREDCACHE_DSCVIEW_H + +#include <binaryninjaapi.h> + + +class SharedCacheView : public BinaryNinja::BinaryView +{ + bool m_parseOnly; + +public: + SharedCacheView(const std::string& typeName, BinaryView* data, bool parseOnly = false); + + ~SharedCacheView() override = default; + + bool Init() override; +}; + + +class SharedCacheViewType : public BinaryNinja::BinaryViewType +{ +public: + SharedCacheViewType(); + + static void Register(); + + BinaryNinja::Ref<BinaryNinja::BinaryView> Create(BinaryNinja::BinaryView* data) override; + + BinaryNinja::Ref<BinaryNinja::BinaryView> Parse(BinaryNinja::BinaryView* data) override; + + bool IsTypeValidForData(BinaryNinja::BinaryView* data) override; + + bool IsDeprecated() override { return false; } + + BinaryNinja::Ref<BinaryNinja::Settings> GetLoadSettingsForData(BinaryNinja::BinaryView* data) override; +}; + + +#endif // SHAREDCACHE_DSCVIEW_H diff --git a/view/sharedcache/core/SlideInfo.cpp b/view/sharedcache/core/SlideInfo.cpp new file mode 100644 index 00000000..e258a90c --- /dev/null +++ b/view/sharedcache/core/SlideInfo.cpp @@ -0,0 +1,292 @@ +#include "SlideInfo.h" + +#include "Dyld.h" +#include "SharedCache.h" +#include "Utility.h" + +SlideInfoProcessor::SlideInfoProcessor(uint64_t baseAddress) +{ + m_logger = new BinaryNinja::Logger("SlideInfoProcessor"); + m_baseAddress = baseAddress; +} + +void ApplySlideInfoV5(VirtualMemory& vm, const SlideMappingInfo& mapping) +{ + uint64_t pageStartsOffset = mapping.address + sizeof(dyld_cache_slide_info_v5); + uint64_t pageStartCount = mapping.slideInfoV5.page_starts_count; + uint64_t pageSize = mapping.slideInfoV5.page_size; + + uint64_t offset; + // Retrieve this once so we don't need to keep querying the region through the VM. + auto region = vm.GetRegionAtAddress(mapping.address, offset); + if (!region.has_value()) + throw UnmappedRegionException(mapping.address); + + auto cursor = pageStartsOffset; + for (size_t i = 0; i < pageStartCount; i++) + { + uint16_t delta = vm.ReadUInt16(cursor); + cursor += sizeof(uint16_t); + if (delta == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE) + continue; + + delta = delta / sizeof(uint64_t); // initial offset is byte based + uint64_t loc = mapping.mappingInfo.fileOffset + (pageSize * i); + do + { + loc += delta * sizeof(dyld_cache_slide_pointer5); + dyld_cache_slide_pointer5 slideInfo = {vm.ReadUInt64(loc)}; + delta = slideInfo.regular.next; + if (slideInfo.auth.auth) + { + uint64_t value = mapping.slideInfoV5.value_add + slideInfo.auth.runtimeOffset; + vm.WritePointer(loc, value); + } + else + { + uint64_t value = mapping.slideInfoV5.value_add + slideInfo.regular.runtimeOffset; + vm.WritePointer(loc, value); + } + } while (delta != 0); + } +} + +void ApplySlideInfoV3(VirtualMemory& vm, const SlideMappingInfo& mapping) +{ + uint64_t pageStartsOffset = mapping.address + sizeof(dyld_cache_slide_info_v3); + uint64_t pageStartCount = mapping.slideInfoV3.page_starts_count; + uint64_t pageSize = mapping.slideInfoV3.page_size; + + auto cursor = pageStartsOffset; + for (size_t i = 0; i < pageStartCount; i++) + { + uint16_t delta = vm.ReadUInt16(cursor); + cursor += sizeof(uint16_t); + if (delta == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE) + continue; + + delta = delta / sizeof(uint64_t); // initial offset is byte based + uint64_t loc = mapping.mappingInfo.fileOffset + (pageSize * i); + do + { + loc += delta * sizeof(dyld_cache_slide_pointer3); + dyld_cache_slide_pointer3 slideInfo = {vm.ReadUInt64(loc)}; + delta = slideInfo.plain.offsetToNextPointer; + + if (slideInfo.auth.authenticated) + { + uint64_t value = slideInfo.auth.offsetFromSharedCacheBase; + value += mapping.slideInfoV3.auth_value_add; + vm.WritePointer(loc, value); + } + else + { + uint64_t value51 = slideInfo.plain.pointerValue; + uint64_t top8Bits = value51 & 0x0007F80000000000; + uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFF; + uint64_t value = (uint64_t)top8Bits << 13 | bottom43Bits; + vm.WritePointer(loc, value); + } + } while (delta != 0); + } +} + +void ApplySlideInfoV2(VirtualMemory& vm, const SlideMappingInfo& mapping) +{ + auto rebaseChain = [&](const dyld_cache_slide_info_v2& slideInfo, uint64_t pageContent, uint16_t startOffset) { + // TODO: This is always zero? + // TODO: This is probably something for runtime offsets provided to the shared cache. + uintptr_t slideAmount = 0; + + auto deltaMask = slideInfo.delta_mask; + auto valueMask = ~deltaMask; + auto valueAdd = slideInfo.value_add; + + auto deltaShift = CountTrailingZeros(deltaMask) - 2; + + uint32_t pageOffset = startOffset; + uint32_t delta = 1; + while (delta != 0) + { + uint64_t loc = pageContent + pageOffset; + uintptr_t rawValue = vm.ReadUInt64(loc); + delta = (uint32_t)((rawValue & deltaMask) >> deltaShift); + uintptr_t value = (rawValue & valueMask); + if (value != 0) + { + value += valueAdd; + // TODO: slideAmount += value? + // TODO: slideAmount is always zero? What is this suppose to do? + value += slideAmount; + } + pageOffset += delta; + vm.WritePointer(loc, value); + } + }; + + uint64_t extrasOffset = mapping.address + mapping.slideInfoV2.page_extras_offset; + uint64_t pageStartsOffset = mapping.address + sizeof(dyld_cache_slide_info_v2); + uint64_t pageStartCount = mapping.slideInfoV2.page_starts_count; + uint64_t pageSize = mapping.slideInfoV2.page_size; + + auto cursor = pageStartsOffset; + for (size_t i = 0; i < pageStartCount; i++) + { + uint16_t start = vm.ReadUInt16(cursor); + cursor += sizeof(uint16_t); + if (start == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE) + continue; + + if (start & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) + { + int j = (start & 0x3FFF); + bool done = false; + do + { + uint64_t extraCursor = extrasOffset + (j * sizeof(uint16_t)); + auto extra = vm.ReadUInt16(extraCursor); + uint16_t aStart = extra; + uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i); + uint16_t pageStartOffset = (aStart & 0x3FFF) * 4; + rebaseChain(mapping.slideInfoV2, page, pageStartOffset); + done = (extra & DYLD_CACHE_SLIDE_PAGE_ATTR_END); + ++j; + } while (!done); + } + else + { + uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i); + uint16_t pageStartOffset = start * 4; + rebaseChain(mapping.slideInfoV2, page, pageStartOffset); + } + } +} + +std::vector<SlideMappingInfo> SlideInfoProcessor::ReadEntryInfo(VirtualMemory& vm, const CacheEntry& entry) const +{ + auto& baseHeader = entry.GetHeader(); + + // Handle legacy, single mapping slide info. + if (baseHeader.slideInfoOffsetUnused) + { + auto slideInfoAddress = entry.GetMappedAddress(baseHeader.slideInfoOffsetUnused); + if (!slideInfoAddress.has_value()) + { + m_logger->LogError("Unmapped slide info address %llx", slideInfoAddress); + return {}; + } + auto slideInfoVersion = vm.ReadUInt32(*slideInfoAddress); + if (slideInfoVersion != 2 && slideInfoVersion != 3) + { + m_logger->LogError("Unsupported slide info version %d", slideInfoVersion); + return {}; + } + + SlideMappingInfo singleMapping = {}; + singleMapping.address = *slideInfoAddress; + + auto mappingAddress = entry.GetMappedAddress(baseHeader.mappingOffset + sizeof(dyld_cache_mapping_info)); + if (!mappingAddress.has_value()) + { + m_logger->LogError("Unmapped mapping address %llx", mappingAddress); + return {}; + } + vm.Read(&singleMapping.mappingInfo, *mappingAddress, sizeof(dyld_cache_mapping_info)); + singleMapping.slideInfoVersion = slideInfoVersion; + if (singleMapping.slideInfoVersion == 2) + vm.Read(&singleMapping.slideInfoV2, *slideInfoAddress, sizeof(dyld_cache_slide_info_v2)); + else if (singleMapping.slideInfoVersion == 3) + vm.Read(&singleMapping.slideInfoV3, *slideInfoAddress, sizeof(dyld_cache_slide_info_v3)); + + return {singleMapping}; + } + + std::vector<SlideMappingInfo> mappings = {}; + for (auto i = 0; i < baseHeader.mappingWithSlideCount; i++) + { + dyld_cache_mapping_and_slide_info mappingAndSlideInfo = {}; + auto mappingAndSlideInfoAddress = + entry.GetMappedAddress(baseHeader.mappingWithSlideOffset + (i * sizeof(dyld_cache_mapping_and_slide_info))); + if (!mappingAndSlideInfoAddress.has_value()) + { + m_logger->LogError("Unmapped mapping and slide info address %llx", mappingAndSlideInfoAddress); + continue; + } + + vm.Read(&mappingAndSlideInfo, *mappingAndSlideInfoAddress, sizeof(dyld_cache_mapping_and_slide_info)); + if (mappingAndSlideInfo.size == 0 || mappingAndSlideInfo.slideInfoFileOffset == 0) + continue; + + SlideMappingInfo map = {}; + map.address = *entry.GetMappedAddress(mappingAndSlideInfo.slideInfoFileOffset); + map.slideInfoVersion = vm.ReadUInt32(map.address); + map.mappingInfo.address = mappingAndSlideInfo.address; + map.mappingInfo.size = mappingAndSlideInfo.size; + map.mappingInfo.fileOffset = *entry.GetMappedAddress(mappingAndSlideInfo.fileOffset); + if (map.slideInfoVersion == 2) + { + vm.Read(&map.slideInfoV2, map.address, sizeof(dyld_cache_slide_info_v2)); + } + else if (map.slideInfoVersion == 3) + { + vm.Read(&map.slideInfoV3, map.address, sizeof(dyld_cache_slide_info_v3)); + map.slideInfoV3.auth_value_add = m_baseAddress; + } + else if (map.slideInfoVersion == 5) + { + vm.Read(&map.slideInfoV5, map.address, sizeof(dyld_cache_slide_info_v5)); + map.slideInfoV5.value_add = m_baseAddress; + } + else + { + m_logger->LogError("Unknown slide info version: %d", map.slideInfoVersion); + continue; + } + + mappings.emplace_back(map); + m_logger->LogDebug("File: %s", entry.GetFilePath().c_str()); + m_logger->LogDebug("Slide Info Address: 0x%llx", map.address); + m_logger->LogDebug("Mapping Address: 0x%llx", map.mappingInfo.address); + m_logger->LogDebug("Slide Info Version: %d", map.slideInfoVersion); + } + + return mappings; +} + +void SlideInfoProcessor::ApplyMappings(VirtualMemory& vm, const std::vector<SlideMappingInfo>& mappings) +{ + // Apply the slide information to the mapped file. + for (const auto& mapping : mappings) + { + switch (mapping.slideInfoVersion) + { + case 2: + ApplySlideInfoV2(vm, mapping); + break; + case 3: + ApplySlideInfoV3(vm, mapping); + break; + case 5: + ApplySlideInfoV5(vm, mapping); + break; + default: + m_logger->LogError( + "Cannot apply slide info version: %d @ %llx", mapping.slideInfoVersion, mapping.mappingInfo.address); + break; + } + } +} + +void SlideInfoProcessor::ProcessEntry(VirtualMemory& vm, const CacheEntry& entry) +{ + try + { + ApplyMappings(vm, ReadEntryInfo(vm, entry)); + } + catch (const std::exception& e) + { + // Just log an error, we technically can continue as if the slide info is not applied for a given entry it does + // not necessarily mean we cannot do analysis on others. + m_logger->LogError("Error processing slide info for entry `%s`: %s", entry.GetFileName().c_str(), e.what()); + } +} diff --git a/view/sharedcache/core/SlideInfo.h b/view/sharedcache/core/SlideInfo.h new file mode 100644 index 00000000..bc165589 --- /dev/null +++ b/view/sharedcache/core/SlideInfo.h @@ -0,0 +1,42 @@ +#pragma once + +#include "binaryninjaapi.h" +#include "SharedCache.h" + +// Information required to apply slide (relocation) info to a mapping +struct SlideMappingInfo +{ + dyld_cache_mapping_info mappingInfo; + // NOTE: Offset is relative to the beginning of the entry file. + uint64_t address; + uint16_t slideInfoVersion; + + union + { + dyld_cache_slide_info_v2 slideInfoV2; + dyld_cache_slide_info_v3 slideInfoV3; + dyld_cache_slide_info_v5 slideInfoV5; + }; +}; + +// Current usages of the slide info are: +// - Reading export symbols requires slide info to be processed. +// - Reading objc stuff +// - Loading an image or region +// - Reading branch island mappings???? +class SlideInfoProcessor +{ + BinaryNinja::Ref<BinaryNinja::Logger> m_logger; + // Base address of the shared cache, NOT the base address of the entry. + uint64_t m_baseAddress; + +public: + explicit SlideInfoProcessor(uint64_t baseAddress); + + std::vector<SlideMappingInfo> ReadEntryInfo(VirtualMemory& vm, const CacheEntry& entry) const; + + // Write the slide information back to the entries memory mapped regions. + void ApplyMappings(VirtualMemory& vm, const std::vector<SlideMappingInfo>& mappings); + + void ProcessEntry(VirtualMemory& vm, const CacheEntry& entry); +}; diff --git a/view/sharedcache/core/Utility.cpp b/view/sharedcache/core/Utility.cpp new file mode 100644 index 00000000..91729578 --- /dev/null +++ b/view/sharedcache/core/Utility.cpp @@ -0,0 +1,131 @@ +#include "Utility.h" +#include "binaryninjaapi.h" +#include "view/macho/machoview.h" + +using namespace BinaryNinja; + +BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt) +{ + uint32_t flags = 0; + if (initProt & MACHO_VM_PROT_READ) + flags |= SegmentReadable; + if (initProt & MACHO_VM_PROT_WRITE) + flags |= SegmentWritable; + if (initProt & MACHO_VM_PROT_EXECUTE) + flags |= SegmentExecutable; + if (((initProt & MACHO_VM_PROT_WRITE) == 0) && ((maxProt & MACHO_VM_PROT_WRITE) == 0)) + flags |= SegmentDenyWrite; + if (((initProt & MACHO_VM_PROT_EXECUTE) == 0) && ((maxProt & MACHO_VM_PROT_EXECUTE) == 0)) + flags |= SegmentDenyExecute; + return static_cast<BNSegmentFlag>(flags); +} + +int64_t readSLEB128(const uint8_t*& current, const uint8_t* end) +{ + uint8_t cur; + int64_t value = 0; + size_t shift = 0; + while (current != end) + { + cur = *current++; + value |= (cur & 0x7f) << shift; + shift += 7; + if ((cur & 0x80) == 0) + break; + } + value = (value << (64 - shift)) >> (64 - shift); + return value; +} + +uint64_t readLEB128(const uint8_t*& current, const uint8_t* end) +{ + uint64_t result = 0; + int bit = 0; + do + { + if (current >= end) + return -1; + + uint64_t slice = *current & 0x7f; + + if (bit > 63) + return -1; + result |= (slice << bit); + bit += 7; + } while (*current++ & 0x80); + return result; +} + + +uint64_t readValidULEB128(const uint8_t*& current, const uint8_t* end) +{ + uint64_t value = readLEB128(current, end); + if ((int64_t)value == -1) + throw ReadException(); + return value; +} + +void ApplySymbol(Ref<BinaryView> view, Ref<TypeLibrary> typeLib, Ref<Symbol> symbol) +{ + Ref<Function> func = nullptr; + auto symbolAddress = symbol->GetAddress(); + auto symbolName = symbol->GetFullName(); + + if (symbol->GetType() == FunctionSymbol) + { + Ref<Platform> targetPlatform = view->GetDefaultPlatform(); + func = view->AddFunctionForAnalysis(targetPlatform, symbolAddress); + } + + if (typeLib) + { + auto type = view->ImportTypeLibraryObject(typeLib, {symbolName}); + // TODO: This is still auto + if (type) + view->DefineAutoSymbolAndVariableOrFunction(view->GetDefaultPlatform(), symbol, type); + else + view->DefineAutoSymbol(symbol); + } + else + { + view->DefineAutoSymbol(symbol); + } + + if (!func) + func = view->GetAnalysisFunction(view->GetDefaultPlatform(), symbolAddress); + if (func) + { + if (symbolName == "_objc_msgSend") + { + func->SetHasVariableArguments(false); + } + else if (symbolName.find("_objc_retain_x") != std::string::npos + || symbolName.find("_objc_release_x") != std::string::npos) + { + auto x = symbolName.rfind('x'); + auto num = symbolName.substr(x + 1); + + std::vector<FunctionParameter> callTypeParams; + auto cc = view->GetDefaultArchitecture()->GetCallingConventionByName("apple-arm64-objc-fast-arc-" + num); + + if (auto idType = view->GetTypeByName({"id"})) + { + callTypeParams.emplace_back("obj", idType, true, Variable()); + auto funcType = Type::FunctionType(idType, cc, callTypeParams); + func->SetUserType(funcType); + } + else + { + LogWarn("Failed to find id type for %llx, objective-c processor not ran?", func->GetStart()); + } + } + } +} + +std::string BaseFileName(const std::string& path) +{ + auto lastSlashPos = path.find_last_of("/\\"); + if (lastSlashPos != std::string::npos) + return path.substr(lastSlashPos + 1); + return path; +} diff --git a/view/sharedcache/core/Utility.h b/view/sharedcache/core/Utility.h new file mode 100644 index 00000000..86715557 --- /dev/null +++ b/view/sharedcache/core/Utility.h @@ -0,0 +1,106 @@ +#pragma once + +#include <cstdint> +#include <map> + +#include "binaryninjaapi.h" + +#ifdef _MSC_VER +inline int CountTrailingZeros(uint64_t value) +{ + unsigned long index; // 32-bit long on Windows + if (_BitScanForward64(&index, value)) + { + return index; + } + else + { + return 64; // If the value is 0, return 64. + } +} +#else +inline int CountTrailingZeros(uint64_t value) +{ + return value == 0 ? 64 : __builtin_ctzll(value); +} +#endif + +BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt); + +int64_t readSLEB128(const uint8_t*& current, const uint8_t* end); + +uint64_t readLEB128(const uint8_t*& current, const uint8_t* end); + +uint64_t readValidULEB128(const uint8_t*& current, const uint8_t* end); + +void ApplySymbol(BinaryNinja::Ref<BinaryNinja::BinaryView> view, BinaryNinja::Ref<BinaryNinja::TypeLibrary> typeLib, + BinaryNinja::Ref<BinaryNinja::Symbol> symbol); + +// Returns the on disk file for a given path, this is required to support project files. +std::string ResolveFilePath(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, const std::string& path); + +// Returns the "image name" for a given path. +// /blah/foo/bar/libObjCThing.dylib -> libObjCThing.dylib +std::string BaseFileName(const std::string& path); + +// Represents a range of addresses [start, end). +// Note that `end` is not included within the range. +struct AddressRange +{ + uint64_t start; + uint64_t end; + + AddressRange(uint64_t start, uint64_t end) : start(start), end(end) {} + + AddressRange() : start(0), end(0) {} + + bool Overlaps(const AddressRange& b) const { return start < b.end && b.start < end; } + + bool operator<(const AddressRange& b) const { return start < b.start || (start == b.start && end < b.end); } + + friend bool operator<(const AddressRange& range, uint64_t address) { return range.end <= address; } + + friend bool operator<(uint64_t address, const AddressRange& range) { return address < range.start; } +}; + +// A map keyed by address ranges that can be looked up via any +// address within a range thanks to C++14's transparent comparators. +template <typename Value> +using AddressRangeMap = std::map<AddressRange, Value, std::less<>>; + +// TODO: Document this! +template <typename T> +class WeakAllocPtr +{ +protected: + std::weak_ptr<T> m_weakPtr; // Weak reference to the object + std::function<std::shared_ptr<T>()> m_allocator; // Function to recreate the object + +public: + explicit WeakAllocPtr(std::function<std::shared_ptr<T>()> allocator) : m_allocator(allocator) {} + + WeakAllocPtr(std::weak_ptr<T> weakPtr, std::function<std::shared_ptr<T>()> allocator) : m_allocator(allocator) + { + if (weakPtr == nullptr) + { + m_weakPtr = {}; + } + else + { + m_weakPtr = weakPtr; + } + } + + std::shared_ptr<T> lock() + { + std::shared_ptr<T> sharedPtr = m_weakPtr.lock(); + if (!sharedPtr) + { + sharedPtr = m_allocator(); + m_weakPtr = sharedPtr; + } + return sharedPtr; + } + + std::shared_ptr<T> lock_no_allocate() { return m_weakPtr.lock(); } +}; diff --git a/view/sharedcache/core/VM.cpp b/view/sharedcache/core/VM.cpp deleted file mode 100644 index 711c2a88..00000000 --- a/view/sharedcache/core/VM.cpp +++ /dev/null @@ -1,891 +0,0 @@ -// -// Created by kat on 5/23/23. -// - -/* - This is the cross-plat file buffering logic used for SharedCache processing. - This is used for reading large amounts of large files in a performant manner. - - Here be no dragons, but this code is very complex, beware. - - We in _all_ cases memory map the files, as we hardly ever need more than a few pages per file for most intensive operations. - - Memory Map Implementation: - Of interest is that on several platforms we have to account for very low file pointer limits, and when mapping - 40+ files, these are trivially reachable. - - We handle this with a "SelfAllocatingWeakPtr": - - Calling .lock() ALWAYS delivers a shared_ptr guaranteed to stay valid. This may block waiting for a free pointer - - As soon as that lock is released, that file pointer MAY be freed if another thread wants to open a new one, and we are at our limit. - - Calling .lock() again on this same theoretical object will then wait for another file pointer to be freeable. - - VM Implementation: - - - Since the caches we're operating on are by nature page aligned, we are able to use nice optimizations under the hood to translate - "VM Addresses" to their actual in-memory counterparts. - - We do this with a page table, which is a map of page -> file offset. - - We also implement a "VMReader" here, which is a drop-in replacement for BinaryReader that operates on the VM. - see "ObjC.cpp" for where this is used. - -*/ - - -#include "VM.h" -#include <utility> -#include <memory> -#include <cstring> -#include <stdio.h> -#include <filesystem> -#include <binaryninjaapi.h> - -#ifdef _MSC_VER - #include <windows.h> -#else - #include <sys/mman.h> - #include <fcntl.h> - #include <stdlib.h> - #include <sys/resource.h> -#endif - -static std::atomic<uint64_t> mmapCount = 0; - -uint64_t MMapCount() -{ - return mmapCount; -} - -void VMShutdown() {} - - -std::string ResolveFilePath(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, const std::string& path) -{ - auto dscProjectFile = dscView->GetFile()->GetProjectFile(); - BinaryNinja::LogDebugF( - "ResolveFilePath:\n of: {}\n path: {}\n pfn: {}\n pfp: {}", - dscView->GetFile()->GetOriginalFilename(), - path, - dscProjectFile ? dscProjectFile->GetName() : "", - dscProjectFile ? dscProjectFile->GetPathOnDisk() : "" - ); - - // If we're not in a project, just return the path we were given - if (!dscProjectFile) - { - return path; - } - - // TODO: do we need to support looking in subfolders? - // Replace project file path on disk with project file name for resolution - std::string projectFilePathOnDisk = dscProjectFile->GetPathOnDisk(); - std::string originalFilePathOnDisk = dscView->GetFile()->GetOriginalFilename(); - std::string cleanPath = path; - if (auto pindex = cleanPath.find(projectFilePathOnDisk); pindex != std::string::npos) - { - cleanPath.replace(pindex, projectFilePathOnDisk.size(), dscProjectFile->GetName()); - } - else if (auto oindex = cleanPath.find(originalFilePathOnDisk); oindex != std::string::npos) - { - BinaryNinja::Ref<BinaryNinja::ProjectFile> originalProjectFile = dscProjectFile->GetProject()->GetFileByPathOnDisk(originalFilePathOnDisk); - if (!originalProjectFile) - { - BinaryNinja::LogErrorF("Failed to resolve file path for {}: original file {} not found", path, originalFilePathOnDisk); - return path; - } - cleanPath.replace(oindex, originalFilePathOnDisk.size(), originalProjectFile->GetName()); - } - - size_t lastSlashPos = cleanPath.find_last_of("/\\"); - std::string fileName; - - if (lastSlashPos != std::string::npos) { - fileName = cleanPath.substr(lastSlashPos + 1); - } else { - fileName = cleanPath; - } - - auto project = dscProjectFile->GetProject(); - auto dscProjectFolder = dscProjectFile->GetFolder(); - for (const auto& file : project->GetFiles()) - { - auto fileFolder = file->GetFolder(); - bool isSibling = false; - if (!dscProjectFolder && !fileFolder) - { - // Both top-level - isSibling = true; - } - else if (dscProjectFolder && fileFolder) - { - // Have same parent folder - isSibling = dscProjectFolder->GetId() == fileFolder->GetId(); - } - - if (isSibling && file->GetName() == fileName) - { - return file->GetPathOnDisk(); - } - } - - BinaryNinja::LogErrorF("Failed to resolve file path for {}", path); - - // If we couldn't find a sibling filename, just return the path we were given - return path; -} - - -void MMAP::Map() -{ - if (mapped) - return; -#ifdef _MSC_VER - LARGE_INTEGER fileSize; - if (!GetFileSizeEx(hFile, &fileSize)) - { - // Handle error - CloseHandle(hFile); - return; - } - len = static_cast<size_t>(fileSize.QuadPart); - - HANDLE hMapping = CreateFileMapping( - hFile, // file handle - NULL, // security attributes - PAGE_WRITECOPY, // protection - 0, // maximum size (high-order DWORD) - 0, // maximum size (low-order DWORD) - NULL); // name of the mapping object - - if (hMapping == NULL) - { - // Handle error - CloseHandle(hFile); - return; - } - - _mmap = static_cast<uint8_t*>(MapViewOfFile( - hMapping, // handle to the file mapping object - FILE_MAP_COPY, // desired access - 0, // file offset (high-order DWORD) - 0, // file offset (low-order DWORD) - 0)); // number of bytes to map (0 = entire file) - - if (_mmap == nullptr) - { - // Handle error - CloseHandle(hMapping); - CloseHandle(hFile); - return; - } - - mapped = true; - - CloseHandle(hMapping); - CloseHandle(hFile); - -#else - fseek(fd, 0L, SEEK_END); - len = ftell(fd); - fseek(fd, 0L, SEEK_SET); - - void *result = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(fd), 0u); - if (result == MAP_FAILED) - { - // Handle error - return; - } - - _mmap = static_cast<uint8_t*>(result); - mapped = true; -#endif -} - -void MMAP::Unmap() -{ -#ifdef _MSC_VER - if (_mmap) - { - UnmapViewOfFile(_mmap); - mapped = false; - } -#else - if (mapped) - { - munmap(_mmap, len); - mapped = false; - } -#endif -} - -FileAccessorCache& FileAccessorCache::Shared() -{ - static FileAccessorCache& shared = *new FileAccessorCache; - return shared; -}; - -FileAccessorCache::FileAccessorCache() = default; - -void FileAccessorCache::SetCacheSize(uint64_t cacheSize) -{ - m_cacheSize = cacheSize; - EvictFromCacheIfNeeded(); -} - -void FileAccessorCache::RecordAccess(const std::string& path) -{ - auto it = m_accessors.find(path); - if (it == m_accessors.end()) - return; - - // Move the entry for `path` to the end of `m_leastRecentlyOpened`. - m_leastRecentlyOpened.splice(m_leastRecentlyOpened.end(), m_leastRecentlyOpened, it->second.second); -} - -void FileAccessorCache::EvictFromCacheIfNeeded() -{ - std::lock_guard lock(m_mutex); - while (m_accessors.size() > m_cacheSize) - { - m_accessors.erase(m_leastRecentlyOpened.front()); - m_leastRecentlyOpened.pop_front(); - } -} - -std::shared_ptr<LazyMappedFileAccessor> FileAccessorCache::OpenLazily( - const uint64_t sessionID, const std::string& path, - std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine) -{ - std::lock_guard lock(m_mutex); - if (auto it = m_lazyAccessors.find(path); it != m_lazyAccessors.end()) - { - RecordAccess(path); - return it->second; - } - - auto accessor = std::make_shared<LazyMappedFileAccessor>(path, - [=, postAllocationRoutine = std::move(postAllocationRoutine)]( - const std::string& path) { - auto accessor = Open(sessionID, path); - if (postAllocationRoutine) - { - postAllocationRoutine(accessor); - } - return accessor; - }); - - m_lazyAccessors.insert_or_assign(path, accessor); - return accessor; -} - -std::shared_ptr<MMappedFileAccessor> FileAccessorCache::Open( - const uint64_t sessionID, const std::string& path) -{ - EvictFromCacheIfNeeded(); - - mmapCount++; - auto accessor = std::shared_ptr<MMappedFileAccessor>(new MMappedFileAccessor(path), - [this](MMappedFileAccessor* accessor) { Close(accessor); }); - - std::lock_guard lock(m_mutex); - auto [it, inserted] = m_accessors.insert({path, {accessor, m_leastRecentlyOpened.end()}}); - if (inserted) - { - m_leastRecentlyOpened.push_back(path); - it->second.second = std::prev(it->second.second); - } - else - { - RecordAccess(path); - } - - return accessor; -} - -void FileAccessorCache::Close(MMappedFileAccessor* accessor) -{ - mmapCount--; - delete accessor; -} - -std::shared_ptr<LazyMappedFileAccessor> MMappedFileAccessor::Open( - const uint64_t sessionID, const std::string& path, - std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine) -{ - return FileAccessorCache::Shared().OpenLazily(sessionID, path, std::move(postAllocationRoutine)); -} - -void MMappedFileAccessor::InitialVMSetup() -{ - static std::once_flag once; - std::call_once(once, []{ - // check for BN_SHAREDCACHE_FP_MAX - // if it exists, set maxFPLimit to that value - unsigned long long maxFPLimit = 0; - if (auto env = getenv("BN_SHAREDCACHE_FP_MAX"); env) - { - // FIXME behav on 0 here is unintuitive, '0123' will interpret as octal and be 83 according to manpage. meh. - maxFPLimit = strtoull(env, nullptr, 0); - if (maxFPLimit < 10) - { - BinaryNinja::LogWarn("BN_SHAREDCACHE_FP_MAX set to below 10. A value of at least 10 is recommended for performant analysis on SharedCache Binaries."); - } - if (maxFPLimit == 0) - { - BinaryNinja::LogError("BN_SHAREDCACHE_FP_MAX set to 0. Adjusting to 1"); - maxFPLimit = 1; - } - } - else - { -#ifdef _MSC_VER - // It is not _super_ clear what the max file pointer limit is on windows, - // but to my understanding, we are using the windows API to map files, - // so we should have at least 2^24; - // kind of funny to me that windows would be the most effecient OS to - // parallelize sharedcache processing on in terms of FP usage concerns - maxFPLimit = 0x1000000; -#else - // The soft file descriptor limit on Linux and Mac is a lot lower than - // on Windows (1024 for Linux, 256 for Mac). Recent iOS shared caches - // have 60+ files which may not leave much headroom if a user opens - // more than one at a time. Attempt to increase the file descriptor - // limit to 1024, and limit ourselves to caching half of them as a - // memory vs performance trade-off (closing and re-opening a file - // requires parsing and applying the slide information again). - constexpr rlim_t TargetFileDescriptorLimit = 1024; - struct rlimit rlim; - getrlimit(RLIMIT_NOFILE, &rlim); - unsigned long long previousLimit = rlim.rlim_cur; - if (rlim.rlim_cur < TargetFileDescriptorLimit) - { - rlim.rlim_cur = std::min(TargetFileDescriptorLimit, rlim.rlim_max); - if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) - { - perror("setrlimit(RLIMIT_NOFILE)"); - rlim.rlim_cur = previousLimit; - } - } - maxFPLimit = rlim.rlim_cur / 2; -#endif - } - BinaryNinja::LogInfo("Shared Cache processing initialized with a max file descriptor limit of %lld", maxFPLimit); - FileAccessorCache::Shared().SetCacheSize(maxFPLimit); - }); -} - - -MMappedFileAccessor::MMappedFileAccessor(const std::string& path) : m_path(path) -{ -#ifdef _MSC_VER - m_mmap.hFile = CreateFile( - path.c_str(), // file name - GENERIC_READ, // desired access (read-only) - FILE_SHARE_READ, // share mode - NULL, // security attributes - OPEN_EXISTING, // creation disposition - FILE_ATTRIBUTE_NORMAL, // flags and attributes - NULL); // template file - - if (m_mmap.hFile == INVALID_HANDLE_VALUE) - { - // BNLogInfo("Couldn't read file at %s", path.c_str()); - throw MissingFileException(); - } - -#else -#ifdef ABORT_FAILURES - if (path.empty()) - { - cerr << "Path is empty." << endl; - abort(); - } -#endif - m_mmap.fd = fopen(path.c_str(), "r"); - if (m_mmap.fd == nullptr) - { - BNLogError("Serious VM Error: Couldn't read file at %s", path.c_str()); - -#ifndef _MSC_VER - try { - throw BinaryNinja::ExceptionWithStackTrace("Unable to Read file"); - } - catch (ExceptionWithStackTrace &ex) - { - BNLogError("%s", ex.m_stackTrace.c_str()); - BNLogError("Error: %d (%s)", errno, strerror(errno)); - } -#endif - throw MissingFileException(); - } -#endif - - m_mmap.Map(); -} - -MMappedFileAccessor::~MMappedFileAccessor() -{ - // BNLogInfo("Unmapping %s", m_path.c_str()); - m_mmap.Unmap(); - -#ifdef _MSC_VER - if (m_mmap.hFile != INVALID_HANDLE_VALUE) - { - CloseHandle(m_mmap.hFile); - } -#else - if (m_mmap.fd != nullptr) - { - fclose(m_mmap.fd); - } -#endif -} - -void MMappedFileAccessor::WritePointer(size_t address, size_t pointer) -{ - *(size_t*)&m_mmap._mmap[address] = pointer; -} - -template <typename T> -T MMappedFileAccessor::Read(size_t address) -{ - T result; - Read(&result, address, sizeof(T)); - return result; -} - -std::string MMappedFileAccessor::ReadNullTermString(size_t address) -{ - if (address > m_mmap.len) - return ""; - auto start = m_mmap._mmap + address; - auto end = m_mmap._mmap + m_mmap.len; - auto nul = std::find(start, end, 0); - return std::string(start, nul); -} - -uint8_t MMappedFileAccessor::ReadUChar(size_t address) -{ - return Read<uint8_t>(address); -} - -int8_t MMappedFileAccessor::ReadChar(size_t address) -{ - return Read<int8_t>(address); -} - -uint16_t MMappedFileAccessor::ReadUShort(size_t address) -{ - return Read<uint16_t>(address); -} - -int16_t MMappedFileAccessor::ReadShort(size_t address) -{ - return Read<int16_t>(address); -} - -uint32_t MMappedFileAccessor::ReadUInt32(size_t address) -{ - return Read<uint32_t>(address); -} - -int32_t MMappedFileAccessor::ReadInt32(size_t address) -{ - return Read<int32_t>(address); -} - -uint64_t MMappedFileAccessor::ReadULong(size_t address) -{ - return Read<uint64_t>(address); -} - -int64_t MMappedFileAccessor::ReadLong(size_t address) -{ - return Read<int64_t>(address); -} - -BinaryNinja::DataBuffer MMappedFileAccessor::ReadBuffer(size_t address, size_t length) -{ - if (m_mmap.len <= length || address > m_mmap.len - length) - throw MappingReadException(); - - return BinaryNinja::DataBuffer(&m_mmap._mmap[address], length); -} - -std::pair<const uint8_t*, const uint8_t*> MMappedFileAccessor::ReadSpan(size_t address, size_t length) -{ - if (address > m_mmap.len) - throw MappingReadException(); - if (address + length > m_mmap.len) - throw MappingReadException(); - const uint8_t* data = (&(((uint8_t*)m_mmap._mmap)[address])); - return {data, data + length}; -} - - -void MMappedFileAccessor::Read(void* dest, size_t address, size_t length) -{ - if (m_mmap.len <= length || address > m_mmap.len - length) - throw MappingReadException(); - - memcpy(dest, &m_mmap._mmap[address], length); -} - - -VM::VM(size_t pageSize, bool safe) : m_pageSize(pageSize), m_safe(safe) -{ -} - -VM::~VM() -{ -} - - -void VM::MapPages(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, uint64_t sessionID, size_t vm_address, size_t fileoff, size_t size, const std::string& filePath, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine) -{ - // The mappings provided for shared caches will always be page aligned. - // We can use this to our advantage and gain considerable performance via page tables. - // This could probably be sped up if c++ were avoided? - // We want to create a map of page -> file offset - - if (vm_address % m_pageSize != 0 || size % m_pageSize != 0) - { - throw MappingPageAlignmentException(); - } - - auto accessor = - MMappedFileAccessor::Open(sessionID, ResolveFilePath(dscView, filePath), std::move(postAllocationRoutine)); - auto [it, inserted] = m_map.insert_or_assign({vm_address, vm_address + size}, PageMapping(std::move(accessor), fileoff)); - if (m_safe && !inserted) - { - BNLogWarn("Remapping page 0x%zx (f: 0x%zx)", vm_address, fileoff); - throw MappingCollisionException(); - } -} - -std::pair<PageMapping, size_t> VM::MappingAtAddress(size_t address) -{ - if (auto it = m_map.find(address); it != m_map.end()) - { - // The PageMapping object returned contains the page, and more importantly, the file pointer (there can be - // multiple in newer caches) This is relevant for reading out the data in the rest of this file. - // The second item in the returned pair is the offset of `address` within the file. - auto& range = it->first; - auto& mapping = it->second; - return {mapping, mapping.fileOffset + (address - range.start)}; - } - - throw MappingReadException(); -} - - -bool VM::AddressIsMapped(uint64_t address) -{ - auto it = m_map.find(address); - return it != m_map.end(); -} - - -uint64_t VMReader::ReadULEB128(size_t limit) -{ - uint64_t result = 0; - int bit = 0; - auto mapping = m_vm->MappingAtAddress(m_cursor); - auto fileCursor = mapping.second; - auto fileLimit = fileCursor + (limit - m_cursor); - auto fa = mapping.first.fileAccessor->lock(); - auto* fileBuff = (uint8_t*)fa->Data(); - do - { - if (fileCursor >= fileLimit) - return -1; - uint64_t slice = ((uint64_t*)&((fileBuff)[fileCursor]))[0] & 0x7f; - if (bit > 63) - return -1; - else - { - result |= (slice << bit); - bit += 7; - } - } while (((uint64_t*)&(fileBuff[fileCursor++]))[0] & 0x80); - fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer - return result; -} - - -int64_t VMReader::ReadSLEB128(size_t limit) -{ - uint8_t cur; - int64_t value = 0; - size_t shift = 0; - - auto mapping = m_vm->MappingAtAddress(m_cursor); - auto fileCursor = mapping.second; - auto fileLimit = fileCursor + (limit - m_cursor); - auto fa = mapping.first.fileAccessor->lock(); - auto* fileBuff = (uint8_t*)fa->Data(); - - while (fileCursor < fileLimit) - { - cur = ((uint64_t*)&((fileBuff)[fileCursor]))[0]; - fileCursor++; - value |= (cur & 0x7f) << shift; - shift += 7; - if ((cur & 0x80) == 0) - break; - } - value = (value << (64 - shift)) >> (64 - shift); - fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer - return value; -} - -std::string VM::ReadNullTermString(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadNullTermString(mapping.second); -} - -uint8_t VM::ReadUChar(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second); -} - -int8_t VM::ReadChar(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadChar(mapping.second); -} - -uint16_t VM::ReadUShort(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second); -} - -int16_t VM::ReadShort(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadShort(mapping.second); -} - -uint32_t VM::ReadUInt32(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second); -} - -int32_t VM::ReadInt32(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second); -} - -uint64_t VM::ReadULong(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadULong(mapping.second); -} - -int64_t VM::ReadLong(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadLong(mapping.second); -} - -BinaryNinja::DataBuffer VM::ReadBuffer(size_t addr, size_t length) -{ - auto mapping = MappingAtAddress(addr); - return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length); -} - - -void VM::Read(void* dest, size_t addr, size_t length) -{ - auto mapping = MappingAtAddress(addr); - mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length); -} - -VMReader::VMReader(std::shared_ptr<VM> vm, size_t addressSize) : m_vm(vm), m_cursor(0), m_addressSize(addressSize) {} - - -void VMReader::Seek(size_t address) -{ - m_cursor = address; -} - -void VMReader::SeekRelative(size_t offset) -{ - m_cursor += offset; -} - -std::string VMReader::ReadCString(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadNullTermString(mapping.second); -} - -uint8_t VMReader::ReadUChar(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 1; - return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second); -} - -int8_t VMReader::ReadChar(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 1; - return mapping.first.fileAccessor->lock()->ReadChar(mapping.second); -} - -uint16_t VMReader::ReadUShort(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 2; - return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second); -} - -int16_t VMReader::ReadShort(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 2; - return mapping.first.fileAccessor->lock()->ReadShort(mapping.second); -} - -uint32_t VMReader::ReadUInt32(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 4; - return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second); -} - -int32_t VMReader::ReadInt32(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 4; - return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second); -} - -uint64_t VMReader::ReadULong(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 8; - return mapping.first.fileAccessor->lock()->ReadULong(mapping.second); -} - -int64_t VMReader::ReadLong(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 8; - return mapping.first.fileAccessor->lock()->ReadLong(mapping.second); -} - - -size_t VMReader::ReadPointer(size_t address) -{ - if (m_addressSize == 8) - return ReadULong(address); - else if (m_addressSize == 4) - return ReadUInt32(address); - - // no idea what horrible arch we have, should probably die here. - return 0; -} - - -size_t VMReader::ReadPointer() -{ - if (m_addressSize == 8) - return Read64(); - else if (m_addressSize == 4) - return Read32(); - - return 0; -} - -BinaryNinja::DataBuffer VMReader::ReadBuffer(size_t length) -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += length; - return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length); -} - -BinaryNinja::DataBuffer VMReader::ReadBuffer(size_t addr, size_t length) -{ - auto mapping = m_vm->MappingAtAddress(addr); - m_cursor = addr + length; - return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length); -} - -void VMReader::Read(void* dest, size_t length) -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += length; - mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length); -} - -void VMReader::Read(void* dest, size_t addr, size_t length) -{ - auto mapping = m_vm->MappingAtAddress(addr); - m_cursor = addr + length; - mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length); -} - - -uint8_t VMReader::Read8() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 1; - return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second); -} - -int8_t VMReader::ReadS8() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 1; - return mapping.first.fileAccessor->lock()->ReadChar(mapping.second); -} - -uint16_t VMReader::Read16() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 2; - return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second); -} - -int16_t VMReader::ReadS16() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 2; - return mapping.first.fileAccessor->lock()->ReadShort(mapping.second); -} - -uint32_t VMReader::Read32() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 4; - return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second); -} - -int32_t VMReader::ReadS32() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 4; - return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second); -} - -uint64_t VMReader::Read64() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 8; - return mapping.first.fileAccessor->lock()->ReadULong(mapping.second); -} - -int64_t VMReader::ReadS64() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 8; - return mapping.first.fileAccessor->lock()->ReadLong(mapping.second); -} diff --git a/view/sharedcache/core/VM.h b/view/sharedcache/core/VM.h deleted file mode 100644 index e245751a..00000000 --- a/view/sharedcache/core/VM.h +++ /dev/null @@ -1,368 +0,0 @@ -// -// Created by kat on 5/23/23. -// - -#ifndef SHAREDCACHE_VM_H -#define SHAREDCACHE_VM_H - -#include <binaryninjaapi.h> - -#include <list> -#include <mutex> -#include <unordered_map> - -void VMShutdown(); - -std::string ResolveFilePath(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, const std::string& path); - -template <typename T> -class SelfAllocatingWeakPtr { -public: - SelfAllocatingWeakPtr(std::function<std::shared_ptr<T>()> allocator) : allocator(allocator) {} - - std::shared_ptr<T> lock() { - std::shared_ptr<T> sharedPtr = weakPtr.lock(); - if (!sharedPtr) { - sharedPtr = allocator(); - weakPtr = sharedPtr; - } - return sharedPtr; - } - - std::shared_ptr<T> lock_no_allocate() { - return weakPtr.lock(); - } - -private: - std::weak_ptr<T> weakPtr; // Weak reference to the object - std::function<std::shared_ptr<T>()> allocator; // Function to recreate the object -}; - - -class MissingFileException : public std::exception -{ - virtual const char* what() const throw() - { - return "Missing File."; - } -}; - -class MMappedFileAccessor; - -class MMAP { - friend MMappedFileAccessor; - - uint8_t *_mmap; - FILE *fd; - size_t len; - -#ifdef _MSC_VER - HANDLE hFile = INVALID_HANDLE_VALUE; // For Windows -#endif - - bool mapped = false; - - void Map(); - - void Unmap(); -}; - -class LazyMappedFileAccessor : public SelfAllocatingWeakPtr<MMappedFileAccessor> { -public: - LazyMappedFileAccessor( - std::string filePath, std::function<std::shared_ptr<MMappedFileAccessor>(const std::string&)> allocator) : - SelfAllocatingWeakPtr([this, allocator = std::move(allocator)] { return allocator(m_filePath); }), - m_filePath(std::move(filePath)) - {} - ~LazyMappedFileAccessor() = default; - - std::string_view filePath() const { return m_filePath; } - -private: - std::string m_filePath; -}; - -uint64_t MMapCount(); - -class MMappedFileAccessor { - std::string m_path; - MMAP m_mmap; - bool m_slideInfoWasApplied = false; - -public: - MMappedFileAccessor(const std::string &path); - ~MMappedFileAccessor(); - - static std::shared_ptr<LazyMappedFileAccessor> Open(const uint64_t sessionID, const std::string &path, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine = nullptr); - - static void CloseAll(const uint64_t sessionID); - - static void InitialVMSetup(); - - const std::string& Path() const { return m_path; }; - - size_t Length() const { return m_mmap.len; }; - - void *Data() const { return m_mmap._mmap; }; - - bool SlideInfoWasApplied() const { return m_slideInfoWasApplied; } - - void SetSlideInfoWasApplied(bool slideInfoWasApplied) { m_slideInfoWasApplied = slideInfoWasApplied; } - - /** - * Writes to files are implemented for performance reasons and should be treated with utmost care - * - * They _MAY_ disappear as _soon_ as you release the lock on the this file. - * They may also NOT disappear for the lifetime of the application. - * - * The former is more likely to occur when concurrent DSC processing is happening. The latter is the typical scenario. - * - * This is used explicitly for slide information in a locked scope and _NOTHING_ else. It should probably not be used for anything else. - * - * \param address - * \param pointer - */ - void WritePointer(size_t address, size_t pointer); - - std::string ReadNullTermString(size_t address); - - uint8_t ReadUChar(size_t address); - - int8_t ReadChar(size_t address); - - uint16_t ReadUShort(size_t address); - - int16_t ReadShort(size_t address); - - uint32_t ReadUInt32(size_t address); - - int32_t ReadInt32(size_t address); - - uint64_t ReadULong(size_t address); - - int64_t ReadLong(size_t address); - - BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length); - - // Returns a range of pointers within the mapped memory region corresponding to - // {addr, length}. - // WARNING: The pointers returned by this method is only valid for the lifetime - // of this file accessor. - // TODO: This should use std::span<const uint8_t> once the minimum supported - // C++ version supports it. - std::pair<const uint8_t*, const uint8_t*> ReadSpan(size_t addr, size_t length); - - void Read(void *dest, size_t addr, size_t length); - - template <typename T> - T Read(size_t address); -}; - -class FileAccessorCache -{ -public: - static FileAccessorCache& Shared(); - - std::shared_ptr<LazyMappedFileAccessor> OpenLazily( - const uint64_t sessionID, const std::string& path, - std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine); - - void SetCacheSize(uint64_t size); - -private: - FileAccessorCache(); - - std::shared_ptr<MMappedFileAccessor> Open( - const uint64_t sessionID, const std::string& path); - - void Close(MMappedFileAccessor* accessor); - - void RecordAccess(const std::string& path); - void EvictFromCacheIfNeeded(); - - std::mutex m_mutex; - std::unordered_map<std::string, std::shared_ptr<LazyMappedFileAccessor>> m_lazyAccessors; - - // Ordered from least recently opened (front) to most recently opened (end). - std::list<std::string> m_leastRecentlyOpened; - std::unordered_map<std::string, std::pair<std::shared_ptr<MMappedFileAccessor>, std::list<std::string>::iterator>> - m_accessors; - - uint64_t m_cacheSize = 8; -}; - -struct PageMapping { - std::shared_ptr<LazyMappedFileAccessor> fileAccessor; - size_t fileOffset; - PageMapping(std::shared_ptr<LazyMappedFileAccessor> fileAccessor, size_t fileOffset) - : fileAccessor(std::move(fileAccessor)), fileOffset(fileOffset) {} -}; - - -class VMException : public std::exception { - virtual const char *what() const throw() { - return "Generic VM Exception"; - } -}; - -class MappingPageAlignmentException : public VMException { - virtual const char *what() const throw() { - return "Tried to create a mapping not aligned to given page size"; - } -}; - -class MappingReadException : VMException { - virtual const char *what() const throw() { - return "Tried to access unmapped page"; - } -}; - -class MappingCollisionException : VMException { - virtual const char *what() const throw() { - return "Tried to remap a page"; - } -}; - -class VMReader; - -// Represents a range of addresses [start, end). -// Note that `end` is not included within the range. -struct AddressRange { - uint64_t start; - uint64_t end; - - bool operator<(const AddressRange& b) const { - return start < b.start || (start == b.start && end < b.end); - } - - friend bool operator<(const AddressRange& range, uint64_t address) { - return range.end <= address; - } - - friend bool operator<(uint64_t address, const AddressRange& range) { - return address < range.start; - } -}; - -// A map keyed by address ranges that can be looked up via any -// address within a range thanks to C++14's transparent comparators. -template <typename Value> -using AddressRangeMap = std::map<AddressRange, Value, std::less<>>; - -class VM { - // A map keyed by address ranges that can be looked up via any - // address within a range thanks to C++14's transparent comparators. - AddressRangeMap<PageMapping> m_map; - size_t m_pageSize; - bool m_safe; - - friend VMReader; - -public: - - VM(size_t pageSize, bool safe = true); - - ~VM(); - - void MapPages(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, uint64_t sessionID, size_t vm_address, size_t fileoff, size_t size, const std::string& filePath, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine); - - bool AddressIsMapped(uint64_t address); - - std::pair<PageMapping, size_t> MappingAtAddress(size_t address); - - std::string ReadNullTermString(size_t address); - - uint8_t ReadUChar(size_t address); - - int8_t ReadChar(size_t address); - - uint16_t ReadUShort(size_t address); - - int16_t ReadShort(size_t address); - - uint32_t ReadUInt32(size_t address); - - int32_t ReadInt32(size_t address); - - uint64_t ReadULong(size_t address); - - int64_t ReadLong(size_t address); - - BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length); - - void Read(void *dest, size_t addr, size_t length); -}; - - -class VMReader { - std::shared_ptr<VM> m_vm; - size_t m_cursor; - size_t m_addressSize; - - BNEndianness m_endianness = LittleEndian; - -public: - VMReader(std::shared_ptr<VM> vm, size_t addressSize = 8); - - void SetEndianness(BNEndianness endianness) { m_endianness = endianness; } - - BNEndianness GetEndianness() const { return m_endianness; } - - void Seek(size_t address); - - void SeekRelative(size_t offset); - - [[nodiscard]] size_t GetOffset() const { return m_cursor; } - - std::string ReadCString(size_t address); - - uint64_t ReadULEB128(size_t cursorLimit); - - int64_t ReadSLEB128(size_t cursorLimit); - - uint8_t Read8(); - - int8_t ReadS8(); - - uint16_t Read16(); - - int16_t ReadS16(); - - uint32_t Read32(); - - int32_t ReadS32(); - - uint64_t Read64(); - - int64_t ReadS64(); - - size_t ReadPointer(); - - uint8_t ReadUChar(size_t address); - - int8_t ReadChar(size_t address); - - uint16_t ReadUShort(size_t address); - - int16_t ReadShort(size_t address); - - uint32_t ReadUInt32(size_t address); - - int32_t ReadInt32(size_t address); - - uint64_t ReadULong(size_t address); - - int64_t ReadLong(size_t address); - - size_t ReadPointer(size_t address); - - BinaryNinja::DataBuffer ReadBuffer(size_t length); - - BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length); - - void Read(void *dest, size_t length); - - void Read(void *dest, size_t addr, size_t length); -}; - -#endif //SHAREDCACHE_VM_H diff --git a/view/sharedcache/core/VirtualMemory.cpp b/view/sharedcache/core/VirtualMemory.cpp new file mode 100644 index 00000000..b55ddefe --- /dev/null +++ b/view/sharedcache/core/VirtualMemory.cpp @@ -0,0 +1,375 @@ +#include "VirtualMemory.h" + +// TODO: Add back the ability to do relocations on this stuff. +// TODO: ^ the above is currently handled only by the consumers of the VirtualMemory +// TODO: however we might still want to have this be done here as well for persistence. + +void VirtualMemory::MapRegion(WeakFileAccessor fileAccessor, AddressRange mappedRange, uint64_t fileOffset) +{ + // Create a new VirtualMemoryRegion object + VirtualMemoryRegion region {fileOffset, std::move(fileAccessor)}; + + // TODO: How to handle overlapping regions? + for (const auto& [existingRange, existingRegion] : m_regions) + { + if (existingRange.Overlaps(mappedRange)) + { + // Handle overlapping regions, e.g., throw an exception or skip the mapping + BinaryNinja::LogError("Overlapping memory region %llx", existingRange.start); + } + } + + // Insert the region into the map + m_regions.insert_or_assign(mappedRange, region); +} + +std::optional<VirtualMemoryRegion> VirtualMemory::GetRegionAtAddress(uint64_t address, uint64_t& addressOffset) +{ + if (const auto& it = m_regions.find(address); it != m_regions.end()) + { + // The VirtualMemoryRegion object returned contains the page, and more importantly, the file pointer (there can + // be multiple in newer caches) This is relevant for reading out the data in the rest of this file. The second + // item in the returned pair is the offset of `address` within the file. + const auto& range = it->first; + auto mapping = it->second; + addressOffset = mapping.fileOffset + (address - range.start); + return mapping; + } + + return std::nullopt; +} + +std::optional<VirtualMemoryRegion> VirtualMemory::GetRegionAtAddress(uint64_t address) +{ + uint64_t offset; + return GetRegionAtAddress(address, offset); +} + +bool VirtualMemory::IsAddressMapped(uint64_t address) +{ + return m_regions.find(address) != m_regions.end(); +} + +void VirtualMemory::WritePointer(size_t address, size_t pointer) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + // Unique to the `VirtualMemory::WritePointer` we actually have an interface on the WeakFileAccessor to use. + // this is the mechanism for persisting our written pointers. + region->fileAccessor.WritePointer(offset, pointer); +} + +std::string VirtualMemory::ReadCString(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadNullTermString(offset); +} + +uint8_t VirtualMemory::ReadUInt8(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadUInt8(offset); +} + +int8_t VirtualMemory::ReadInt8(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadInt8(offset); +} + +uint16_t VirtualMemory::ReadUInt16(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadUInt16(offset); +} + +int16_t VirtualMemory::ReadInt16(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadInt16(offset); +} + +uint32_t VirtualMemory::ReadUInt32(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadUInt32(offset); +} + +int32_t VirtualMemory::ReadInt32(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadInt32(offset); +} + +uint64_t VirtualMemory::ReadUInt64(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadUInt64(offset); +} + +int64_t VirtualMemory::ReadInt64(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadInt64(offset); +} + +BinaryNinja::DataBuffer VirtualMemory::ReadBuffer(uint64_t address, size_t length) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadBuffer(offset, length); +} + +std::pair<const uint8_t*, const uint8_t*> VirtualMemory::ReadSpan(size_t address, size_t length) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadSpan(offset, length); +} + +void VirtualMemory::Read(void* dest, uint64_t address, size_t length) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + region->fileAccessor.lock()->Read(dest, offset, length); +} + +VirtualMemoryReader::VirtualMemoryReader(std::shared_ptr<VirtualMemory> memory, uint64_t addressSize) +{ + m_memory = memory; + m_addressSize = addressSize; + m_cursor = 0; +} + +std::string VirtualMemoryReader::ReadCString(uint64_t address, size_t maxLength) +{ + uint64_t offset; + auto region = m_memory->GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + // TODO: Advance cursor? + return region->fileAccessor.lock()->ReadNullTermString(offset); +} + +uint64_t VirtualMemoryReader::ReadULEB128(size_t cursorLimit) +{ + uint64_t result = 0; + int bit = 0; + uint64_t offset; + auto mapping = m_memory->GetRegionAtAddress(m_cursor, offset); + auto fileLimit = offset + (cursorLimit - m_cursor); + auto fa = mapping->fileAccessor.lock(); + auto* fileBuff = (uint8_t*)fa->Data(); + do + { + if (offset >= fileLimit) + return -1; + uint64_t slice = ((uint64_t*)&((fileBuff)[offset]))[0] & 0x7f; + if (bit > 63) + return -1; + else + { + result |= (slice << bit); + bit += 7; + } + } while (((uint64_t*)&(fileBuff[offset++]))[0] & 0x80); + // TODO: There has got to be a better way to prevent this... + fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer + return result; +} + +int64_t VirtualMemoryReader::ReadSLEB128(size_t cursorLimit) +{ + constexpr size_t BYTE_SIZE = 7; // Number of bits in each SLEB128 byte + constexpr size_t INT64_BITS = 64; // Total number of bits in an int64_t + + int64_t value = 0; + size_t shift = 0; + uint64_t offset; + + // Retrieve associated memory region and the file buffer + auto mapping = m_memory->GetRegionAtAddress(m_cursor, offset); + auto fileLimit = offset + (cursorLimit - m_cursor); + auto fileAccessor = mapping->fileAccessor.lock(); + auto* fileBuffer = static_cast<uint8_t*>(fileAccessor->Data()); + + // Loop through the SLEB128 encoded bytes + while (offset < fileLimit) + { + uint8_t currentByte = fileBuffer[offset++]; + value |= (static_cast<int64_t>(currentByte & 0x7F) << shift); + shift += BYTE_SIZE; + + if ((currentByte & 0x80) == 0) // If MSB is not set, we're done + break; + } + + // Properly sign-extend the value according to its size + value = (value << (INT64_BITS - shift)) >> (INT64_BITS - shift); + + // TODO: There has got to be a better way to prevent this... + // Prevent deallocation of the fileAccessor + fileAccessor->Data(); + + return value; +} + +uint64_t VirtualMemoryReader::ReadPointer() +{ + return ReadPointer(m_cursor); +} + +uint64_t VirtualMemoryReader::ReadPointer(uint64_t address) +{ + if (m_addressSize == 8) + return ReadUInt64(address); + if (m_addressSize == 4) + return ReadUInt32(address); + // TODO: Throw here or assert. + return 0; +} + +uint8_t VirtualMemoryReader::ReadUInt8() +{ + return ReadUInt8(m_cursor); +} + +uint8_t VirtualMemoryReader::ReadUInt8(uint64_t address) +{ + m_cursor = address + 1; + return m_memory->ReadUInt8(address); +} + +int8_t VirtualMemoryReader::ReadInt8() +{ + return ReadUInt8(m_cursor); +} + +int8_t VirtualMemoryReader::ReadInt8(uint64_t address) +{ + m_cursor = address + 1; + return m_memory->ReadInt8(address); +} + +uint16_t VirtualMemoryReader::ReadUInt16() +{ + return ReadUInt16(m_cursor); +} + +uint16_t VirtualMemoryReader::ReadUInt16(uint64_t address) +{ + m_cursor = address + 2; + return m_memory->ReadUInt16(address); +} + +int16_t VirtualMemoryReader::ReadInt16() +{ + return ReadInt16(m_cursor); +} + +int16_t VirtualMemoryReader::ReadInt16(uint64_t address) +{ + m_cursor = address + 2; + return m_memory->ReadInt16(address); +} + +uint32_t VirtualMemoryReader::ReadUInt32() +{ + return ReadUInt32(m_cursor); +} + +uint32_t VirtualMemoryReader::ReadUInt32(uint64_t address) +{ + m_cursor = address + 4; + return m_memory->ReadUInt32(address); +} + +int32_t VirtualMemoryReader::ReadInt32() +{ + return ReadInt32(m_cursor); +} + +int32_t VirtualMemoryReader::ReadInt32(uint64_t address) +{ + m_cursor = address + 4; + return m_memory->ReadInt32(address); +} + +uint64_t VirtualMemoryReader::ReadUInt64() +{ + return ReadUInt64(m_cursor); +} + +uint64_t VirtualMemoryReader::ReadUInt64(uint64_t address) +{ + m_cursor = address + 8; + return m_memory->ReadUInt64(address); +} + +int64_t VirtualMemoryReader::ReadInt64() +{ + return ReadInt64(m_cursor); +} + +int64_t VirtualMemoryReader::ReadInt64(uint64_t address) +{ + m_cursor = address + 8; + return m_memory->ReadInt64(address); +} + +BinaryNinja::DataBuffer VirtualMemoryReader::ReadBuffer(size_t length) +{ + return ReadBuffer(m_cursor, length); +} + +BinaryNinja::DataBuffer VirtualMemoryReader::ReadBuffer(uint64_t address, size_t length) +{ + m_cursor = address + length; + return m_memory->ReadBuffer(address, length); +} + +void VirtualMemoryReader::Read(void* dest, size_t length) +{ + Read(dest, m_cursor, length); +} + +void VirtualMemoryReader::Read(void* dest, uint64_t address, size_t length) +{ + m_cursor = address + length; + m_memory->Read(dest, address, length); +} diff --git a/view/sharedcache/core/VirtualMemory.h b/view/sharedcache/core/VirtualMemory.h new file mode 100644 index 00000000..fb00ebaf --- /dev/null +++ b/view/sharedcache/core/VirtualMemory.h @@ -0,0 +1,157 @@ +#pragma once +#include "FileAccessorCache.h" +#include "MappedFileAccessor.h" +#include "Utility.h" + +class UnmappedRegionException : public std::exception +{ + uint64_t m_address; + +public: + explicit UnmappedRegionException(uint64_t address) : m_address(address) {} + + virtual const char* what() const throw() + { + thread_local std::string message; + message = fmt::format("Tried to access unmapped region using address {0:x}", m_address); + return message.c_str(); + } +}; + +// A region within the virtual memory +struct VirtualMemoryRegion +{ + uint64_t fileOffset; + // Access the memory regions contents through this. + // NOTE: Any read through this should be seeked to `fileOffset` + WeakFileAccessor fileAccessor; + + VirtualMemoryRegion(const VirtualMemoryRegion&) = default; + + VirtualMemoryRegion& operator=(const VirtualMemoryRegion&) = default; + + VirtualMemoryRegion(VirtualMemoryRegion&&) = default; + + VirtualMemoryRegion& operator=(VirtualMemoryRegion&&) = default; +}; + +// Contains information to handle mapping of multiple mapped files into a single memory space. +// This models how the loader of DYLD shared caches would operate, so that we can effectively query memory regions +// and map them into Binary Ninja. +class VirtualMemory +{ + std::shared_mutex m_regionMutex; + AddressRangeMap<VirtualMemoryRegion> m_regions; + +public: + // At no point do we ever store a strong pointer to a file accessor, that is the job of the `FileAccessorCache`. + void MapRegion(WeakFileAccessor fileAccessor, AddressRange mappedRange, uint64_t fileOffset); + + // Returns the region in virtual memory, along with the offset into that region where the address is located. + // Using the regions file accessor and the address offset you can read a regions content. + std::optional<VirtualMemoryRegion> GetRegionAtAddress(uint64_t address, uint64_t& addressOffset); + + std::optional<VirtualMemoryRegion> GetRegionAtAddress(uint64_t address); + + bool IsAddressMapped(uint64_t address); + + // Write a pointer at a given address. This pointer will be persisted + // for a given `VirtualMemoryRegion` region, unlike using the MappedFileAccessor directly. + // The persistence is provided through the WeakFileAccessor itself and thus is unique to the construction. + void WritePointer(size_t address, size_t pointer); + + std::string ReadCString(uint64_t address); + + uint8_t ReadUInt8(uint64_t address); + + int8_t ReadInt8(uint64_t address); + + uint16_t ReadUInt16(uint64_t address); + + int16_t ReadInt16(uint64_t address); + + uint32_t ReadUInt32(uint64_t address); + + int32_t ReadInt32(uint64_t address); + + uint64_t ReadUInt64(uint64_t address); + + int64_t ReadInt64(uint64_t address); + + BinaryNinja::DataBuffer ReadBuffer(uint64_t address, size_t length); + + std::pair<const uint8_t*, const uint8_t*> ReadSpan(size_t address, size_t length); + + void Read(void* dest, uint64_t address, size_t length); +}; + +class VirtualMemoryReader +{ + std::shared_ptr<VirtualMemory> m_memory; + uint64_t m_cursor; + uint64_t m_addressSize; + BNEndianness m_endianness = LittleEndian; + +public: + explicit VirtualMemoryReader(std::shared_ptr<VirtualMemory> memory, uint64_t addressSize = 8); + + void SetEndianness(BNEndianness endianness) { m_endianness = endianness; } + + BNEndianness GetEndianness() const { return m_endianness; } + + void Seek(const uint64_t address) { m_cursor = address; }; + + void SeekRelative(const size_t offset) { m_cursor += offset; }; + + size_t GetOffset() const { return m_cursor; } + + std::string ReadCString(uint64_t address, size_t maxLength = -1); + + uint64_t ReadULEB128(size_t cursorLimit); + + int64_t ReadSLEB128(size_t cursorLimit); + + uint64_t ReadPointer(); + + uint64_t ReadPointer(uint64_t address); + + uint8_t ReadUInt8(); + + uint8_t ReadUInt8(uint64_t address); + + int8_t ReadInt8(); + + int8_t ReadInt8(uint64_t address); + + uint16_t ReadUInt16(); + + uint16_t ReadUInt16(uint64_t address); + + int16_t ReadInt16(); + + int16_t ReadInt16(uint64_t address); + + uint32_t ReadUInt32(); + + uint32_t ReadUInt32(uint64_t address); + + int32_t ReadInt32(); + + int32_t ReadInt32(uint64_t address); + + uint64_t ReadUInt64(); + + uint64_t ReadUInt64(uint64_t address); + + int64_t ReadInt64(); + + int64_t ReadInt64(uint64_t address); + + BinaryNinja::DataBuffer ReadBuffer(size_t length); + + BinaryNinja::DataBuffer ReadBuffer(uint64_t address, size_t length); + + void Read(void* dest, size_t length); + + void Read(void* dest, uint64_t address, size_t length); +}; diff --git a/view/sharedcache/core/ffi.cpp b/view/sharedcache/core/ffi.cpp new file mode 100644 index 00000000..0b37196f --- /dev/null +++ b/view/sharedcache/core/ffi.cpp @@ -0,0 +1,429 @@ +#include "SharedCacheController.h" +#include "../api/sharedcachecore.h" + +using namespace BinaryNinja; +using namespace BinaryNinja::DSC; + +BNSharedCacheImage ImageToApi(const CacheImage& image) +{ + BNSharedCacheImage apiImage; + apiImage.name = BNAllocString(image.path.c_str()); + apiImage.headerAddress = image.headerAddress; + apiImage.regionStartCount = image.regionStarts.size(); + uint64_t* regionStarts = new uint64_t[image.regionStarts.size()]; + for (size_t i = 0; i < image.regionStarts.size(); i++) + regionStarts[i] = image.regionStarts[i]; + apiImage.regionStarts = regionStarts; + return apiImage; +} + +CacheImage ImageFromApi(const BNSharedCacheImage& image) +{ + CacheImage apiImage; + apiImage.path = image.name; + apiImage.headerAddress = image.headerAddress; + apiImage.regionStarts.reserve(image.regionStartCount); + for (size_t i = 0; i < image.regionStartCount; i++) + apiImage.regionStarts.push_back(image.regionStarts[i]); + apiImage.header = nullptr; + return apiImage; +} + +BNSharedCacheRegionType RegionTypeToApi(const CacheRegionType& regionType) +{ + switch (regionType) + { + case CacheRegionType::Image: + return SharedCacheRegionTypeImage; + case CacheRegionType::StubIsland: + return SharedCacheRegionTypeStubIsland; + case CacheRegionType::DyldData: + return SharedCacheRegionTypeDyldData; + default: + case CacheRegionType::NonImage: + return SharedCacheRegionTypeNonImage; + } +} + +CacheRegionType RegionTypeFromApi(const BNSharedCacheRegionType regionType) +{ + switch (regionType) + { + case SharedCacheRegionTypeImage: + return CacheRegionType::Image; + case SharedCacheRegionTypeStubIsland: + return CacheRegionType::StubIsland; + case SharedCacheRegionTypeDyldData: + return CacheRegionType::DyldData; + default: + case SharedCacheRegionTypeNonImage: + return CacheRegionType::NonImage; + } +} + +BNSharedCacheRegion RegionToApi(const CacheRegion& region) +{ + BNSharedCacheRegion apiRegion; + apiRegion.vmAddress = region.start; + apiRegion.name = BNAllocString(region.name.c_str()); + apiRegion.size = region.size; + apiRegion.flags = region.flags; + apiRegion.regionType = RegionTypeToApi(region.type); + // If not associated with image this will be zeroed. + apiRegion.imageStart = region.imageStart.value_or(0); + return apiRegion; +} + +CacheRegion RegionFromApi(const BNSharedCacheRegion& apiRegion) +{ + CacheRegion region; + region.start = apiRegion.vmAddress; + region.name = apiRegion.name; + region.size = apiRegion.size; + region.flags = apiRegion.flags; + region.type = RegionTypeFromApi(apiRegion.regionType); + return region; +} + +BNSharedCacheSymbol SymbolToApi(const CacheSymbol& symbol) +{ + BNSharedCacheSymbol apiSymbol; + apiSymbol.name = BNAllocString(symbol.name.c_str()); + apiSymbol.address = symbol.address; + apiSymbol.symbolType = symbol.type; + return apiSymbol; +} + +CacheSymbol SymbolFromApi(const BNSharedCacheSymbol& apiSymbol) +{ + CacheSymbol symbol; + symbol.name = apiSymbol.name; + symbol.address = apiSymbol.address; + symbol.type = apiSymbol.symbolType; + return symbol; +} + +BNSharedCacheEntryType EntryTypeToApi(const CacheEntryType& entryType) +{ + switch (entryType) + { + case CacheEntryType::Primary: + return SharedCacheEntryTypePrimary; + case CacheEntryType::Stub: + return SharedCacheEntryTypeStub; + case CacheEntryType::Symbols: + return SharedCacheEntryTypeSymbols; + case CacheEntryType::DyldData: + return SharedCacheEntryTypeDyldData; + default: + case CacheEntryType::Secondary: + return SharedCacheEntryTypeSecondary; + } +} + +BNSharedCacheMappingInfo MappingToApi(const dyld_cache_mapping_info& mapping) +{ + BNSharedCacheMappingInfo apiMapping; + apiMapping.vmAddress = mapping.address; + apiMapping.size = mapping.size; + apiMapping.fileOffset = mapping.fileOffset; + return apiMapping; +} + +BNSharedCacheEntry EntryToApi(const CacheEntry& entry) +{ + BNSharedCacheEntry apiEntry; + apiEntry.path = BNAllocString(entry.GetFilePath().c_str()); + apiEntry.entryType = EntryTypeToApi(entry.GetType()); + const auto& mappings = entry.GetMappings(); + apiEntry.mappingCount = mappings.size(); + apiEntry.mappings = new BNSharedCacheMappingInfo[mappings.size()]; + for (size_t i = 0; i < mappings.size(); i++) + apiEntry.mappings[i] = MappingToApi(mappings[i]); + return apiEntry; +} + +extern "C" +{ + BNSharedCacheController* BNGetSharedCacheController(BNBinaryView* data) + { + Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); + auto controller = SharedCacheController::FromView(*view); + if (!controller) + return nullptr; + return DSC_API_OBJECT_REF(controller); + } + + BNSharedCacheController* BNNewSharedCacheControllerReference(BNSharedCacheController* controller) + { + return DSC_API_OBJECT_NEW_REF(controller); + } + + void BNFreeSharedCacheControllerReference(BNSharedCacheController* controller) + { + DSC_API_OBJECT_FREE(controller); + } + + bool BNSharedCacheControllerApplyImage( + BNSharedCacheController* controller, BNBinaryView* data, BNSharedCacheImage* image) + { + Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); + // LoadImage will use the header, lets do everyone a favor and use the existing image! + if (const auto realImage = controller->object->GetCache().GetImageAt(image->headerAddress)) + return controller->object->ApplyImage(*view, *realImage); + // They gave us an unknown image, we will not have header information. + return controller->object->ApplyImage(*view, ImageFromApi(*image)); + } + + bool BNSharedCacheControllerApplyRegion( + BNSharedCacheController* controller, BNBinaryView* data, BNSharedCacheRegion* region) + { + Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); + return controller->object->ApplyRegion(*view, RegionFromApi(*region)); + } + + bool BNSharedCacheControllerIsRegionLoaded(BNSharedCacheController* controller, BNSharedCacheRegion* region) + { + return controller->object->IsRegionLoaded(RegionFromApi(*region)); + } + + bool BNSharedCacheControllerIsImageLoaded(BNSharedCacheController* controller, BNSharedCacheImage* image) + { + return controller->object->IsImageLoaded(ImageFromApi(*image)); + } + + bool BNSharedCacheControllerGetRegionAt( + BNSharedCacheController* controller, uint64_t address, BNSharedCacheRegion* outRegion) + { + auto region = controller->object->GetCache().GetRegionAt(address); + if (!region) + return false; + *outRegion = RegionToApi(*region); + return true; + } + + bool BNSharedCacheControllerGetRegionContaining( + BNSharedCacheController* controller, uint64_t address, BNSharedCacheRegion* outRegion) + { + auto region = controller->object->GetCache().GetRegionContaining(address); + if (!region) + return false; + *outRegion = RegionToApi(*region); + return true; + } + + BNSharedCacheRegion* BNSharedCacheControllerGetRegions(BNSharedCacheController* controller, size_t* count) + { + auto regions = controller->object->GetCache().GetRegions(); + *count = regions.size(); + BNSharedCacheRegion* apiRegions = new BNSharedCacheRegion[*count]; + int idx = 0; + for (const auto& [_, region] : regions) + apiRegions[idx++] = RegionToApi(region); + return apiRegions; + } + + BNSharedCacheRegion* BNSharedCacheControllerGetLoadedRegions(BNSharedCacheController* controller, size_t* count) + { + auto loadedRegionStarts = controller->object->GetLoadedRegions(); + + // TODO: This translation should likely exist in the core cache controller class? + std::vector<CacheRegion> loadedRegions; + for (auto start : loadedRegionStarts) + { + auto region = controller->object->GetCache().GetRegionAt(start); + if (region) + loadedRegions.push_back(*region); + } + + *count = loadedRegions.size(); + BNSharedCacheRegion* apiRegions = new BNSharedCacheRegion[*count]; + // I am too lazy to add a real conversion here. + int idx = 0; + for (const auto& region : loadedRegions) + { + apiRegions[idx] = RegionToApi(region); + idx++; + } + return apiRegions; + } + + uint64_t* BNSharedCacheAllocRegionList(uint64_t* list, size_t count) + { + uint64_t* newList = new uint64_t[count]; + for (size_t i = 0; i < count; i++) + newList[i] = list[i]; + return newList; + } + + void BNSharedCacheFreeRegion(BNSharedCacheRegion region) + { + BNFreeString(region.name); + } + + void BNSharedCacheFreeRegionList(BNSharedCacheRegion* regions, size_t count) + { + for (size_t i = 0; i < count; i++) + BNSharedCacheFreeRegion(regions[i]); + delete[] regions; + } + + bool BNSharedCacheControllerGetImageAt( + BNSharedCacheController* controller, uint64_t address, BNSharedCacheImage* outImage) + { + auto image = controller->object->GetCache().GetImageAt(address); + if (!image) + return false; + *outImage = ImageToApi(*image); + return true; + } + + bool BNSharedCacheControllerGetImageContaining( + BNSharedCacheController* controller, uint64_t address, BNSharedCacheImage* outImage) + { + auto image = controller->object->GetCache().GetImageContaining(address); + if (!image) + return false; + *outImage = ImageToApi(*image); + return true; + } + + bool BNSharedCacheControllerGetImageWithName( + BNSharedCacheController* controller, const char* name, BNSharedCacheImage* outImage) + { + auto image = controller->object->GetCache().GetImageWithName(name); + if (!image) + return false; + *outImage = ImageToApi(*image); + return true; + } + + char** BNSharedCacheControllerGetImageDependencies( + BNSharedCacheController* controller, BNSharedCacheImage* image, size_t* count) + { + // GetDependencies will use the header, lets do everyone a favor and use the existing image! + const auto realImage = controller->object->GetCache().GetImageAt(image->headerAddress); + if (!realImage.has_value()) + return nullptr; + const auto dependencies = realImage->GetDependencies(); + + std::vector<const char*> dependencyPtrs; + dependencyPtrs.reserve(dependencies.size()); + for (const auto& dependency : dependencies) + dependencyPtrs.push_back(dependency.c_str()); + *count = dependencyPtrs.size(); + return BNAllocStringList(dependencyPtrs.data(), dependencyPtrs.size()); + } + + BNSharedCacheImage* BNSharedCacheControllerGetImages(BNSharedCacheController* controller, size_t* count) + { + auto images = controller->object->GetCache().GetImages(); + *count = images.size(); + BNSharedCacheImage* apiImages = new BNSharedCacheImage[*count]; + size_t idx = 0; + for (const auto& [_, image] : images) + apiImages[idx++] = ImageToApi(image); + return apiImages; + } + + BNSharedCacheImage* BNSharedCacheControllerGetLoadedImages(BNSharedCacheController* controller, size_t* count) + { + auto loadedImageStarts = controller->object->GetLoadedImages(); + + // TODO: This translation should likely exist in the core cache controller class? + std::vector<CacheImage> loadedImages; + for (auto start : loadedImageStarts) + { + auto image = controller->object->GetCache().GetImageAt(start); + if (image) + loadedImages.push_back(*image); + } + + *count = loadedImages.size(); + BNSharedCacheImage* apiImages = new BNSharedCacheImage[*count]; + for (size_t i = 0; i < *count; i++) + apiImages[i] = ImageToApi(loadedImages[i]); + return apiImages; + } + + void BNSharedCacheFreeImage(BNSharedCacheImage image) + { + BNFreeString(image.name); + delete[] image.regionStarts; + } + + void BNSharedCacheFreeImageList(BNSharedCacheImage* images, size_t count) + { + for (size_t i = 0; i < count; i++) + BNSharedCacheFreeImage(images[i]); + delete[] images; + } + + bool BNSharedCacheControllerGetSymbolAt( + BNSharedCacheController* controller, uint64_t address, BNSharedCacheSymbol* outSymbol) + { + auto symbol = controller->object->GetCache().GetSymbolAt(address); + if (!symbol) + return false; + *outSymbol = SymbolToApi(*symbol); + return true; + } + + bool BNSharedCacheControllerGetSymbolWithName( + BNSharedCacheController* controller, const char* name, BNSharedCacheSymbol* outSymbol) + { + auto symbol = controller->object->GetCache().GetSymbolWithName(name); + if (!symbol) + return false; + *outSymbol = SymbolToApi(*symbol); + return true; + } + + BNSharedCacheSymbol* BNSharedCacheControllerGetSymbols(BNSharedCacheController* controller, size_t* count) + { + auto symbols = controller->object->GetCache().GetSymbols(); + *count = symbols.size(); + BNSharedCacheSymbol* apiSymbols = new BNSharedCacheSymbol[*count]; + size_t idx = 0; + for (const auto& [_, symbol] : symbols) + apiSymbols[idx++] = SymbolToApi(symbol); + return apiSymbols; + } + + + void BNSharedCacheFreeSymbol(BNSharedCacheSymbol symbol) + { + BNFreeString(symbol.name); + } + + void BNSharedCacheFreeSymbolList(BNSharedCacheSymbol* symbols, size_t count) + { + for (size_t i = 0; i < count; i++) + BNSharedCacheFreeSymbol(symbols[i]); + delete[] symbols; + } + + BNSharedCacheEntry* BNSharedCacheControllerGetEntries(BNSharedCacheController* controller, size_t* count) + { + auto entries = controller->object->GetCache().GetEntries(); + *count = entries.size(); + BNSharedCacheEntry* apiEntries = new BNSharedCacheEntry[*count]; + size_t idx = 0; + for (const auto& [_, entry] : entries) + apiEntries[idx++] = EntryToApi(entry); + return apiEntries; + } + + void BNSharedCacheFreeEntry(BNSharedCacheEntry entry) + { + BNFreeString(entry.path); + delete[] entry.mappings; + } + + void BNSharedCacheFreeEntryList(BNSharedCacheEntry* entries, size_t count) + { + for (size_t i = 0; i < count; i++) + BNSharedCacheFreeEntry(entries[i]); + delete[] entries; + } +}; diff --git a/view/sharedcache/core/ffi_global.h b/view/sharedcache/core/ffi_global.h new file mode 100644 index 00000000..cd2727b9 --- /dev/null +++ b/view/sharedcache/core/ffi_global.h @@ -0,0 +1,41 @@ +/* +Copyright 2020-2024 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once + +// Define macros for defining objects exposed by the API +#define DECLARE_DSC_API_OBJECT(handle, cls) \ + namespace BinaryNinja::DSC { \ + class cls; \ + } \ + struct handle \ + { \ + BinaryNinja::DSC::cls* object; \ + } +#define IMPLEMENT_DSC_API_OBJECT(handle) \ +\ +private: \ + handle m_apiObject; \ +\ +public: \ + typedef handle* APIHandle; \ + handle* GetAPIObject() \ + { \ + return &m_apiObject; \ + } \ +\ +private: +#define INIT_DSC_API_OBJECT() m_apiObject.object = this; diff --git a/view/sharedcache/core/refcountobject.h b/view/sharedcache/core/refcountobject.h new file mode 100644 index 00000000..1b79f12f --- /dev/null +++ b/view/sharedcache/core/refcountobject.h @@ -0,0 +1,223 @@ +/* +Copyright 2020-2024 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once + +#ifdef WIN32 + #include <windows.h> +#endif +#include <stddef.h> +#include <vector> +#include <atomic> + +namespace BinaryNinja::DSC { + class DSCRefCountObject + { + // CORE_ALLOCATED_CLASS(RefCountObject) + + public: + std::atomic<int> m_refs; + + DSCRefCountObject() : m_refs(0) {} + + virtual ~DSCRefCountObject() {} + + virtual void AddRef() { m_refs.fetch_add(1); } + + virtual void Release() + { + if (m_refs.fetch_sub(1) == 1) + delete this; + } + + virtual void AddAPIRef() { AddRef(); } + + virtual void ReleaseAPIRef() { Release(); } + }; + + + template <class T> + class DSCRef + { + T* m_obj; +#ifdef BN_REF_COUNT_DEBUG + void* m_assignmentTrace = nullptr; +#endif + + public: + DSCRef<T>() : m_obj(NULL) {} + + DSCRef<T>(T* obj) : m_obj(obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + } + } + + DSCRef<T>(const DSCRef<T>& obj) : m_obj(obj.m_obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + } + } + + ~DSCRef<T>() + { + if (m_obj) + { + m_obj->Release(); +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif + } + } + + // move constructor + DSCRef<T>(DSCRef<T>&& other) : m_obj(other.m_obj) + { + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + } + + // move assignment (inefficient in this case) + // Ref<T>& operator=(Ref<T>&& other) + // { + // if (m_obj) + // { + // #ifdef BN_REF_COUNT_DEBUG + // BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + // #endif + // m_obj->Release(); + // } + // m_obj = other.m_obj; + // other.m_obj = 0; + // #ifdef BN_REF_COUNT_DEBUG + // m_assignmentTrace = other.m_assignmentTrace; + // #endif + // return *this; + // } + + DSCRef<T>& operator=(const DSCRef<T>& obj) + { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj.m_obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T* oldObj = m_obj; + m_obj = obj.m_obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } + + DSCRef<T>& operator=(T* obj) + { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T* oldObj = m_obj; + m_obj = obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } + + operator T*() const { return m_obj; } + + T* operator->() const { return m_obj; } + + T& operator*() const { return *m_obj; } + + bool operator!() const { return m_obj == NULL; } + + T* GetPtr() const { return m_obj; } + + bool operator==(const DSCRef<T>& obj) const { return m_obj == obj.m_obj; } + + bool operator!=(const DSCRef<T>& obj) const { return m_obj != obj.m_obj; } + + bool operator<(const DSCRef<T>& obj) const { return m_obj < obj.m_obj; } + + template <typename H> + friend H AbslHashValue(H h, const DSCRef<T>& value) + { + return AbslHashValue(std::move(h), value.m_obj); + } + }; + + + // Macro-like functions to manage referenced objects for the external API + template <class T> + static typename T::APIHandle DSC_API_OBJECT_REF(T* obj) + { + if (obj == nullptr) + return nullptr; + obj->AddAPIRef(); + return obj->GetAPIObject(); + } + + template <class T> + static typename T::APIHandle DSC_API_OBJECT_REF(const DSCRef<T>& obj) + { + if (!obj) + return nullptr; + obj->AddAPIRef(); + return obj->GetAPIObject(); + } + + // template <class T> + // static typename T::APIHandle DSC_API_OBJECT_REF(const APIRef<T>& obj) + //{ + // if (!obj) + // return nullptr; + // obj->AddAPIRef(); + // return obj->GetAPIObject(); + // } + + template <class T> + static T* DSC_API_OBJECT_NEW_REF(T* obj) + { + if (obj) + obj->object->AddAPIRef(); + return obj; + } + + template <class T> + static void DSC_API_OBJECT_FREE(T* obj) + { + if (obj) + obj->object->ReleaseAPIRef(); + } +}; // namespace BinaryNinja::DSC diff --git a/view/sharedcache/tests/validate_images.py b/view/sharedcache/tests/validate_images.py new file mode 100644 index 00000000..40eb37db --- /dev/null +++ b/view/sharedcache/tests/validate_images.py @@ -0,0 +1,94 @@ +import re +import sys +import os +from binaryninja import sharedcache, load + + +# This is some apple map file thingy, you will know if you have one because you will have a .map file. +def parse_map_file(map_file_path): + mappings = [] + libraries = [] + + with open(map_file_path, 'r') as file: + lines = file.readlines() + + mapping_pattern = re.compile( + r"mapping\s+(?P<type>[A-Z]+)\s+(?P<size>[\dMKB]+)\s+0x(?P<start>[a-fA-F0-9]+)\s+->\s+0x(?P<end>[a-fA-F0-9]+)" + ) + library_pattern = re.compile( + r"^(?P<library>.+)$" + ) + section_pattern = re.compile( + r"\s+(?P<section>[^\s]+)\s+0x(?P<start>[a-fA-F0-9]+)\s+->\s+0x[a-fA-F0-9]+" + ) + + current_library = None + + for line in lines: + # Check for a region mapping line + mapping_match = mapping_pattern.match(line) + if mapping_match: + mappings.append({ + "type": mapping_match.group("type"), + "size": mapping_match.group("size"), + "start": int(mapping_match.group("start"), 16) + }) + continue + + # Check for a section line within a library + section_match = section_pattern.match(line) + if section_match and current_library is not None: + current_library["sections"].append({ + "section": section_match.group("section"), + "start": int(section_match.group("start"), 16) + }) + continue + + # Check for a library name line + library_match = library_pattern.match(line) + if library_match: + current_library = { + "name": library_match.group("library"), + "sections": [] + } + libraries.append(current_library) + continue + + return mappings, libraries + + +def main(): + if len(sys.argv) < 2: + print("Please provide a shared cache binary path to validate. There must be an adjacent .map file.") + sys.exit(1) + + binary_path = sys.argv[1] + bv = load(binary_path) + assert bv is not None, f"Failed to create BinaryView for {str(binary_path)}" + controller = sharedcache.SharedCacheController(bv) + assert controller.is_valid + + map_file_path = bv.file.filename + ".map" + if not os.path.exists(map_file_path): + print(f"Error: Map file does not exist at path: {map_file_path}") + sys.exit(1) + + mappings, map_images = parse_map_file(map_file_path) + + # Validate images and sections + for map_image in map_images: + image = controller.get_image_with_name(map_image["name"]) + if not image: + raise ValueError(f"Image not found: {map_image['name']}") + print(f"Checking image... {image.name}") + + for section in map_image["sections"]: + if not section["start"] in image.region_starts: + raise ValueError( + f"Section not found in image '{image.name}': {section['start']} -> {image.region_starts}") + + print("Validation successful!") + + +if __name__ == "__main__": + main() diff --git a/view/sharedcache/ui/SharedCacheBDNotifications.cpp b/view/sharedcache/ui/SharedCacheBDNotifications.cpp deleted file mode 100644 index f59f313c..00000000 --- a/view/sharedcache/ui/SharedCacheBDNotifications.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// -// Created by kat on 8/22/24. -// - -#include "SharedCacheBDNotifications.h" - - -SharedCacheBDNotifications::SharedCacheBDNotifications(Ref<BinaryView> view) - : BinaryDataNotification(FunctionUpdates | DataVariableUpdates) -{ -} - -void SharedCacheBDNotifications::OnAnalysisFunctionAdded(BinaryView* view, Function* func) -{ - // - // We just cannot do this until one of: - // "Component::AddAutoFunction" - // BinaryView::BeginIgnoredUndoActions - // some similar fix - - /* - if (view->GetTypeName() == VIEW_NAME) - { - auto sections = view->GetSectionsAt(func->GetStart()); - if (sections.size() > 0) - { - auto section = sections[0]; - auto imageName = section->GetName().substr(0, section->GetName().find("::")); - auto id = view->BeginUndoActions(); - auto comp = view->GetComponentByPath(imageName); - if (!comp) - { - comp = view->CreateComponentWithName(imageName); - } - comp.value()->AddFunction(func); - view->ForgetUndoActions(id); - } - } - */ -} - - -void SharedCacheBDNotifications::OnSectionAdded(BinaryView* data, Section* section) -{ - -} - - -void SharedCacheBDNotifications::OnDataVariableAdded(BinaryView* view, const DataVariable& var) -{ - /* - if (view->GetTypeName() == VIEW_NAME) - { - auto sections = view->GetSectionsAt(var.address); - if (sections.size() > 0) - { - auto section = sections[0]; - auto imageName = section->GetName().substr(0, section->GetName().find("::")); - auto comp = view->GetComponentByPath(imageName); - auto id = view->BeginUndoActions(); - if (!comp) - { - comp = view->CreateComponentWithName(imageName); - } - comp.value()->AddDataVariable(var); - view->ForgetUndoActions(id); - } - }*/ -} diff --git a/view/sharedcache/ui/SharedCacheBDNotifications.h b/view/sharedcache/ui/SharedCacheBDNotifications.h deleted file mode 100644 index 632fca28..00000000 --- a/view/sharedcache/ui/SharedCacheBDNotifications.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Created by kat on 8/22/24. -// - -#pragma once - -#include <binaryninjaapi.h> -#include "ui/uicontext.h" -#include "SharedCacheUINotifications.h" - -using namespace BinaryNinja; - -class SharedCacheBDNotifications : public BinaryDataNotification -{ -public: - SharedCacheBDNotifications(Ref<BinaryView> view); - void OnAnalysisFunctionAdded(BinaryView* view, Function* func) override; - void OnDataVariableAdded(BinaryView* view, const DataVariable& var) override; - void OnSectionAdded(BinaryView* data, Section* section) override; -}; diff --git a/view/sharedcache/ui/SharedCacheUINotifications.cpp b/view/sharedcache/ui/SharedCacheUINotifications.cpp index 9d5893f9..a7b190ff 100644 --- a/view/sharedcache/ui/SharedCacheUINotifications.cpp +++ b/view/sharedcache/ui/SharedCacheUINotifications.cpp @@ -3,14 +3,15 @@ // #include "SharedCacheUINotifications.h" -#include <QLayout> #include <sharedcacheapi.h> #include "ui/sidebar.h" #include "ui/linearview.h" #include "ui/viewframe.h" #include "dscpicker.h" #include "progresstask.h" -#include "SharedCacheBDNotifications.h" + +using namespace BinaryNinja; +using namespace SharedCacheAPI; UINotifications* UINotifications::m_instance = nullptr; @@ -20,123 +21,141 @@ void UINotifications::init() UIContext::registerNotification(m_instance); } - void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const QString& type) { if (!frame) return; - // FIXME there is a bv func for this - static std::function<bool(Ref<BinaryView>, uint64_t)> isAddrMapped = [](Ref<BinaryView> view, uint64_t addr) { - if (view && view->GetTypeName() == VIEW_NAME) + auto view = frame->getCurrentBinaryView(); + if (!view || view->GetTypeName() != VIEW_NAME) + return; + + auto viewInt = frame->getCurrentViewInterface(); + if (!viewInt) + return; + + auto ah = viewInt->actionHandler(); + // Check to see if we have already bound these actions. + if (ah->isBoundAction("Load Image by Name")) + return; + + static auto loadRegionAtAddr = [](BinaryView& view, uint64_t addr) { + auto controller = SharedCacheController::GetController(view); + if (!controller) + return; + if (auto foundRegion = controller->GetRegionContaining(addr)) { - for (const auto& seg : view->GetSegments()) - { - if (seg->GetStart() <= addr && seg->GetEnd() > addr) - return true; - } + // If we did not load the region, then we don't need to run analysis. + if (!controller->ApplyRegion(view, *foundRegion)) + return; + view.AddAnalysisOption("linearsweep"); + view.UpdateAnalysis(); } - return false; }; - auto view = frame->getCurrentBinaryView(); - if (view && view->GetTypeName() == VIEW_NAME) - { - if (auto viewInt = frame->getCurrentViewInterface()) + static auto loadImageAtAddr = [](BinaryView& view, uint64_t addr) { + auto controller = SharedCacheController::GetController(view); + if (!controller) + return; + if (auto foundImage = controller->GetImageContaining(addr)) { - auto ah = viewInt->actionHandler(); - if (!ah->isBoundAction("Load Image by Name")) - { - ah->bindAction("Load Image by Name", UIAction([view = view](const UIActionContext& ctx) { - DisplayDSCPicker(ctx.context, view); - })); - ah->bindAction("Load Section by Address", UIAction([view = view](const UIActionContext& ctx) { - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = 0; - bool gotAddr = GetAddressInput(addr, "Address", "Address"); - if (gotAddr) - { - BackgroundThread::create(ctx.context->mainWindow())->thenBackground( - [cache=cache, addr=addr]() { - cache->LoadSectionAtAddress(addr); - })->start(); - } - })); - ah->bindAction("Load ADDRHERE", - UIAction( - [](const UIActionContext& ctx) { - Ref<BinaryView> view = ctx.binaryView; - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (addr) - { - BackgroundThread::create(ctx.context->mainWindow())->thenBackground( - [cache=cache, addr=addr]() { - cache->LoadSectionAtAddress(addr); - })->start(); - } - }, - [](const UIActionContext& ctx) { - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (isAddrMapped(ctx.binaryView, addr)) - return false; - return addr && cache->GetNameForAddress(addr) != ""; // bool - })); - ah->bindAction("Load IMGHERE", - UIAction( - [](const UIActionContext& ctx) { - Ref<BinaryView> view = ctx.binaryView; - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(view); - uint64_t addr = ctx.token.token.value; - if (addr) - { - BackgroundThread::create(ctx.context->mainWindow())->thenBackground( - [cache=cache, addr=addr]() { - cache->LoadImageContainingAddress(addr); - })->start(); - } - }, - [](const UIActionContext& ctx) { - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (isAddrMapped(ctx.binaryView, addr)) - return false; - return addr && cache->GetImageNameForAddress(addr) != ""; // bool - })); - ah->setActionDisplayName("Load ADDRHERE", [](const UIActionContext& ctx) { - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (addr) - return QString("Load ") + cache->GetNameForAddress(addr).c_str(); - return QString("Error"); - }); - ah->setActionDisplayName("Load IMGHERE", [](const UIActionContext& ctx) { - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (addr) - return QString("Load ") + cache->GetImageNameForAddress(addr).c_str(); - return QString("Error"); - }); - if (auto linearView = qobject_cast<LinearView*>(viewInt->widget())) - { - linearView->contextMenu().addAction("Load ADDRHERE", VIEW_NAME); - linearView->contextMenu().addAction("Load IMGHERE", VIEW_NAME); - linearView->contextMenu().addAction("Load Image by Name", "DSCView2"); - linearView->contextMenu().addAction("Load Section by Address", "DSCView2"); - linearView->contextMenu().setGroupOrdering(VIEW_NAME, 0); - linearView->contextMenu().setGroupOrdering("DSCView2", 1); - } - } + // If we did not load the image, then we don't need to run analysis. + if (!controller->ApplyImage(view, *foundImage)) + return; + view.AddAnalysisOption("linearsweep"); + view.UpdateAnalysis(); } + }; + + auto loadImageNameAction = [](const UIActionContext& ctx) { + DisplayDSCPicker(ctx.context, ctx.binaryView); + }; + + auto loadSectionAddrAction = [](const UIActionContext& ctx) { + uint64_t addr = 0; + if (GetAddressInput(addr, "Address", "Address")) + { + BackgroundThread::create(ctx.context->mainWindow()) + ->thenBackground([ctx, addr](){ loadRegionAtAddr(*ctx.binaryView, addr); }) + ->start(); + } + }; + + auto loadRegionTokenAction = [](const UIActionContext& ctx) { + BackgroundThread::create(ctx.context->mainWindow()) + ->thenBackground([ctx](){ loadRegionAtAddr(*ctx.binaryView, ctx.token.token.value); }) + ->start(); + }; + + auto loadImageTokenAction = [](const UIActionContext& ctx) { + BackgroundThread::create(ctx.context->mainWindow()) + ->thenBackground([ctx](){ loadImageAtAddr(*ctx.binaryView, ctx.token.token.value); }) + ->start(); + }; + + auto isValidUnloadedRegionAction = [](const UIActionContext& ctx) { + uint64_t addr = ctx.token.token.value; + // Check if the region is already loaded in the view. + if (!ctx.binaryView->GetSectionsAt(addr).empty()) + return false; + auto controller = SharedCacheController::GetController(*ctx.binaryView); + if (!controller) + return false; + return controller->GetRegionContaining(addr).has_value(); + }; + + auto isValidUnloadedImageAction = [](const UIActionContext& ctx) { + uint64_t addr = ctx.token.token.value; + // Check if the image is already loaded in the view. + if (!ctx.binaryView->GetSectionsAt(addr).empty()) + return false; + auto controller = SharedCacheController::GetController(*ctx.binaryView); + if (!controller) + return false; + return controller->GetImageContaining(addr).has_value(); + }; + + ah->bindAction("Load Image by Name", UIAction(loadImageNameAction)); + ah->bindAction("Load Section by Address", UIAction(loadSectionAddrAction)); + + ah->bindAction("Load ADDRHERE", UIAction(loadRegionTokenAction, isValidUnloadedRegionAction)); + ah->bindAction("Load IMGHERE", UIAction(loadImageTokenAction, isValidUnloadedImageAction)); + + ah->setActionDisplayName("Load ADDRHERE", [](const UIActionContext& ctx) { + auto controller = SharedCacheController::GetController(*ctx.binaryView); + if (!controller) + return QString("NO CONTROLLER"); + uint64_t addr = ctx.token.token.value; + auto region = controller->GetRegionContaining(addr); + if (!region) + return QString("NO REGION"); + return QString("Load ") + region->name.c_str(); + }); + + ah->setActionDisplayName("Load IMGHERE", [](const UIActionContext& ctx) { + auto controller = SharedCacheController::GetController(*ctx.binaryView); + if (!controller) + return QString("NO CONTROLLER"); + uint64_t addr = ctx.token.token.value; + auto image = controller->GetImageContaining(addr); + if (!image) + return QString("NO IMAGE"); + return QString("Load ") + image->name.c_str(); + }); + + // Finally add the actions to the context menu. + if (auto linearView = qobject_cast<LinearView*>(viewInt->widget())) + { + linearView->contextMenu().addAction("Load ADDRHERE", VIEW_NAME); + linearView->contextMenu().addAction("Load IMGHERE", VIEW_NAME); + linearView->contextMenu().addAction("Load Image by Name", "DSCView2"); + linearView->contextMenu().addAction("Load Section by Address", "DSCView2"); + linearView->contextMenu().setGroupOrdering(VIEW_NAME, 0); + linearView->contextMenu().setGroupOrdering("DSCView2", 1); } } + void UINotifications::OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) { - if (frame->getCurrentBinaryView()) - { - auto listener = new SharedCacheBDNotifications(frame->getCurrentBinaryView()); - frame->getCurrentBinaryView()->RegisterNotification(listener); - } UIContextNotification::OnAfterOpenFile(context, file, frame); } diff --git a/view/sharedcache/ui/dscpicker.cpp b/view/sharedcache/ui/dscpicker.cpp index a82da4fc..c6448ff4 100644 --- a/view/sharedcache/ui/dscpicker.cpp +++ b/view/sharedcache/ui/dscpicker.cpp @@ -6,37 +6,56 @@ #include <sharedcacheapi.h> #include "progresstask.h" -#include <utility> - using namespace BinaryNinja; +using namespace SharedCacheAPI; void DisplayDSCPicker(UIContext* ctx, Ref<BinaryView> dscView) { - BackgroundThread::create(ctx ? ctx->mainWindow() : nullptr)->thenBackground( - [dscView=dscView](QVariant var) { - QStringList entries; - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(dscView); + static auto getImageNames = [dscView](QVariant var) { + auto controller = SharedCacheController::GetController(*dscView); + if (!controller) + return QStringList(); + + QStringList entries = {}; + for (const auto& img : controller->GetImages()) + entries.push_back(QString::fromStdString(img.name)); + return entries; + }; + + static auto getChosenImage = [ctx](QVariant var) { + QStringList entries = var.toStringList(); + + auto choiceDialog = new MetadataChoiceDialog(ctx ? ctx->mainWindow() : nullptr, "Pick Image", "Select", entries); + choiceDialog->AddWidthRequiredByItem(ctx, 300); + choiceDialog->AddHeightRequiredByItem(ctx, 150); + choiceDialog->exec(); + + if (!choiceDialog->GetChosenEntry().has_value()) + return QVariant(""); + + return QVariant(QString::fromStdString(entries.at((qsizetype)choiceDialog->GetChosenEntry().value().idx).toStdString())); + }; - for (const auto& img : cache->GetAvailableImages()) - entries.push_back(QString::fromStdString(img)); + static auto loadSelectedImage = [dscView](QVariant var) { + auto selectedImageName = var.toString().toStdString(); + if (selectedImageName.empty()) + return; - return entries; - })->thenMainThread([ctx](QVariant var){ - QStringList entries = var.toStringList(); + if (auto controller = SharedCacheController::GetController(*dscView)) + { + if (const auto selectedImage = controller->GetImageWithName( selectedImageName)) + { + controller->ApplyImage(*dscView, *selectedImage); + dscView->AddAnalysisOption("linearsweep"); + dscView->UpdateAnalysis(); + } + } + }; - auto choiceDialog = new MetadataChoiceDialog(ctx ? ctx->mainWindow() : nullptr, "Pick Image", "Select", entries); - choiceDialog->AddWidthRequiredByItem(ctx, 300); - choiceDialog->AddHeightRequiredByItem(ctx, 150); - choiceDialog->exec(); - if (choiceDialog->GetChosenEntry().has_value()) - return QVariant(QString::fromStdString(entries.at((qsizetype)choiceDialog->GetChosenEntry().value().idx).toStdString())); - else - return QVariant(""); - })->thenBackground([dscView=dscView](QVariant var){ - if (var.toString().isEmpty()) - return; - Ref<SharedCacheAPI::SharedCache> cache = new SharedCacheAPI::SharedCache(dscView); - cache->LoadImageWithInstallName(var.toString().toStdString()); - })->start(); + BackgroundThread::create(ctx ? ctx->mainWindow() : nullptr) + ->thenBackground([](QVariant var){ return getImageNames(var); }) + ->thenMainThread([](QVariant var){ return getChosenImage(var); }) + ->thenBackground([](QVariant var){ return loadSelectedImage(var); }) + ->start(); } diff --git a/view/sharedcache/ui/dsctriage.cpp b/view/sharedcache/ui/dsctriage.cpp index 41e7ed8b..aab71aac 100644 --- a/view/sharedcache/ui/dsctriage.cpp +++ b/view/sharedcache/ui/dsctriage.cpp @@ -2,698 +2,133 @@ // Created by kat on 8/15/24. // -#include "dsctriage.h" #include "ui/fontsettings.h" -#include <QPainter> -#include <QTextBrowser> #include "tabwidget.h" #include "globalarea.h" #include "progresstask.h" - -#include <cmath> #include <QMessageBox> +#include <QHeaderView> +#include "dsctriage.h" +#include <QTableWidgetItem> +#include "symboltable.h" +using namespace BinaryNinja; +using namespace SharedCacheAPI; -#define QSETTINGS_KEY_SELECTED_TAB "DSCTriage-SelectedTab" -#define QSETTINGS_KEY_TAB_LAYOUT "DSCTriage-TabLayout" -#define QSETTINGS_KEY_IMAGELOAD_TAB_LAYOUT "DSCTriage-ImageLoadTabLayout" -#define QSETTINGS_KEY_ALPHA_POPUP_SEEN "DSCTriage-AlphaPopupSeenV2" - - -DSCCacheBlocksView::DSCCacheBlocksView(QWidget* parent, BinaryViewRef data, Ref<SharedCacheAPI::SharedCache> cache) - : QWidget(parent), m_data(data), m_cache(cache) -{ - setMouseTracking(true); - m_backingCacheCount = SharedCacheAPI::SharedCache::FastGetBackingCacheCount(data); - if (m_backingCacheCount == 0) - return; - m_blockLuminance.resize(m_backingCacheCount, 128); - m_blockSizeRatios.resize(m_backingCacheCount, 1); - m_currentProgress = m_cache->GetLoadProgress(data); - m_targetBlockSizeForAnimation.resize(m_backingCacheCount, 0); - - m_blockWaveAnimation = Animation::create(this) - ->withDuration(1200) - ->withEasingCurve(QEasingCurve::Linear) - ->thenOnValueChanged([this](double v) - { - for (size_t i = 0; i < m_backingCacheCount; i++) - { - // Create a wave effect. - // We use sine to create the initial wave effect, and then cube it to make it more pronounced. - m_blockLuminance[i] = 128 + 95 * (pow((sin(v * 2 * M_PI + i * M_PI / m_backingCacheCount) + 1) / 2, 3)); - } - update(); - }) - ->thenOnEnd([this](QAbstractAnimation::Direction) - { - m_currentProgress = m_cache->GetLoadProgress(m_data); - if (m_currentProgress == BNDSCViewLoadProgress::LoadProgressFinished) - { - m_backingCaches = m_cache->GetBackingCaches(); - m_blockExpandAnimation->start(); - } - else - { - m_blockWaveAnimation->start(); - } - }); - m_blockExpandAnimation = Animation::create(this) - ->withDuration(600) - ->withEasingCurve(QEasingCurve::InOutCirc) - ->thenOnStart([this](QAbstractAnimation::Direction) - { - if (m_backingCaches.size() < m_backingCacheCount) - { - return; - } - uint64_t totalSize = 0; - uint64_t sumCountForAvg = 0; - for (size_t i = 0; i < m_backingCacheCount; i++) - { - const auto& backingCache = m_backingCaches[i]; - double sizeSum = 0.0; - - for (const auto& mapping : backingCache.mappings) - { - sizeSum += mapping.size; - } - m_targetBlockSizeForAnimation[i] = sizeSum; - totalSize += sizeSum; - sumCountForAvg++; - } - - uint64_t avgSize = totalSize / sumCountForAvg; - - for (size_t i = 0; i < m_backingCacheCount; i++) - { - m_blockSizeRatios[i] = avgSize; - } - - m_averageBlockSizeForAnimationInterp = avgSize; - }) - ->thenOnValueChanged([this](double v) - { - for (size_t i = 0; i < m_backingCacheCount; i++) - { - m_blockSizeRatios[i] = m_averageBlockSizeForAnimationInterp + (v/2) * (m_targetBlockSizeForAnimation[i] - ((1.0 - (v/2)) * m_averageBlockSizeForAnimationInterp)); - - // Adjust luminance based on animation progress - m_blockLuminance[i] = 128 + (63 * v); - } - update(); - }) - ->thenOnEnd([this](QAbstractAnimation::Direction) - { - std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); - update(); - // wait 300, somehow - emit loadDone(); - m_selectedBlock = 0; - m_blockAutoselectAnimation->start(); - }); - - m_blockAutoselectAnimation = Animation::create(this) - ->withDuration(100) - ->withEasingCurve(QEasingCurve::InOutCirc) - ->thenOnValueChanged([this](double v){ - m_blockLuminance[0] = 191 + (64 * v); - update(); - }) - ->thenOnEnd([this](QAbstractAnimation::Direction) - { - if (m_backingCaches.size() == 0) - return; - emit selectionChanged(m_backingCaches[0], true); - }); - - m_blockWaveAnimation->setDirection(QAbstractAnimation::Backward); - m_blockWaveAnimation->start(); - -} - -DSCCacheBlocksView::~DSCCacheBlocksView() -{ - -} - -void DSCCacheBlocksView::mousePressEvent(QMouseEvent* event) -{ - if (m_currentProgress != BNDSCViewLoadProgress::LoadProgressFinished - || m_selectedBlock == -1) - { - return; - } - int blockIndex = getBlockIndexAtPosition(event->pos()); - blockSelected(blockIndex); - QWidget::mousePressEvent(event); -} - - -void DSCCacheBlocksView::mouseReleaseEvent(QMouseEvent* event) -{ - QWidget::mouseReleaseEvent(event); -} - - -void DSCCacheBlocksView::mouseDoubleClickEvent(QMouseEvent* event) -{ - QWidget::mouseDoubleClickEvent(event); -} - - -void DSCCacheBlocksView::mouseMoveEvent(QMouseEvent* event) -{ - if (m_selectedBlock == -1) - { - return; - } - uint64_t hoveredIndex = getBlockIndexAtPosition(event->pos()); - std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); - if (hoveredIndex != -1) - { - m_blockLuminance[hoveredIndex] = 255 - 32; - } - m_blockLuminance[m_selectedBlock] = 255; - update(); -} - - -void DSCCacheBlocksView::keyPressEvent(QKeyEvent* event) -{ - QWidget::keyPressEvent(event); -} - - -void DSCCacheBlocksView::keyReleaseEvent(QKeyEvent* event) -{ - QWidget::keyReleaseEvent(event); - if (m_selectedBlock == -1) - { - return; - } - - // left/right arrows, inc/dec m_selectedBlock - if (event->key() == Qt::Key_Left) - { - if (m_selectedBlock > 0) - { - blockSelected(m_selectedBlock - 1); - } - } - else if (event->key() == Qt::Key_Right) - { - if (m_selectedBlock < m_backingCacheCount - 1) - { - blockSelected(m_selectedBlock + 1); - } - } -} - - -void DSCCacheBlocksView::focusInEvent(QFocusEvent* event) -{ - QWidget::focusInEvent(event); -} - - -void DSCCacheBlocksView::focusOutEvent(QFocusEvent* event) -{ - QWidget::focusOutEvent(event); -} - - -void DSCCacheBlocksView::enterEvent(QEnterEvent* event) -{ - QWidget::enterEvent(event); -} - - -void DSCCacheBlocksView::leaveEvent(QEvent* event) -{ - QWidget::leaveEvent(event); -} - -void DSCCacheBlocksView::paintEvent(QPaintEvent* event) -{ - QPainter painter(this); - painter.setRenderHint(QPainter::Antialiasing, true); - - // Initial X position and total width of the widget - int totalWidth = this->width(); - int totalHeight = 30; // Height of the rectangles - int totalSpacing = (m_blockSizeRatios.size() - 1) * 5; - int availableWidth = totalWidth - (50 * 2) - totalSpacing; // availableWidth minus the initial padding - - // Calculate the total ratio of block sizes - uint64_t totalRatio = 0; - for (const auto& ratio : m_blockSizeRatios) { - totalRatio += ratio; - } - - std::vector<int> originalWidths; - originalWidths.resize(m_blockSizeRatios.size(), (availableWidth / m_blockSizeRatios.size())); - - - // Calculate center points for each block - std::vector<int> centers; - centers.reserve(m_blockSizeRatios.size()); - int currentX = 50; - for (size_t i = 0; i < originalWidths.size(); ++i) { - centers.push_back(currentX + (originalWidths[i] / 2)); // Store the center point - currentX += originalWidths[i] + 5; // Update currentX for the next block - } - - // Now draw the blocks, adjusting the position to keep the center point constant - currentX = 50; - uint64_t lastBlockEnd = currentX - 5; - for (size_t i = 0; i < m_blockSizeRatios.size(); ++i) { - // Recalculate the width during animation - uint64_t adjustedAvailableWidth = availableWidth * m_blockSizeRatios[i]; - int blockWidth = std::max(10, static_cast<int>(adjustedAvailableWidth / totalRatio)); - - // Calculate the new X position to maintain the center - int newX = centers[i] - (blockWidth / 2); - if (newX > lastBlockEnd + 5) - { - int diff = newX - (lastBlockEnd + 5); - newX -= diff; - blockWidth += diff; - } - if (newX < lastBlockEnd + 5) - { - int diff = (lastBlockEnd + 5) - newX; - newX += diff; - blockWidth -= diff; - } - lastBlockEnd = newX + blockWidth; - - QRect blockRect(newX, (height() - totalHeight) / 2, blockWidth, totalHeight); - QColor blockColor(m_blockLuminance[i], m_blockLuminance[i], m_blockLuminance[i]); - painter.setBrush(blockColor); - painter.setPen(blockColor); - painter.drawRect(blockRect); - - currentX += blockWidth + 5; // Move to the next block's position - } -} - - -int DSCCacheBlocksView::getBlockIndexAtPosition(const QPoint& clickPosition) -{ - // Initial X position and total width of the widget - int totalWidth = this->width(); - int totalHeight = 50; // Height of the rectangles - int totalSpacing = (m_blockSizeRatios.size() - 1) * 5; - int availableWidth = totalWidth - (50 * 2) - totalSpacing; // availableWidth minus the initial padding - - // Calculate the total ratio of block sizes - uint64_t totalRatio = 0; - for (const auto& ratio : m_blockSizeRatios) - { - totalRatio += ratio; - } - - // Calculate center points for each block - std::vector<int> originalWidths; - originalWidths.resize(m_blockSizeRatios.size(), (availableWidth / m_blockSizeRatios.size())); - - std::vector<int> centers; - centers.reserve(m_blockSizeRatios.size()); - int currentX = 50; - for (size_t i = 0; i < originalWidths.size(); ++i) - { - centers.push_back(currentX + (originalWidths[i] / 2)); // Store the center point - currentX += originalWidths[i] + 5; // Update currentX for the next block - } - - // Now find the block that contains the click - currentX = 50; - uint64_t lastBlockEnd = currentX - 5; - for (size_t i = 0; i < m_blockSizeRatios.size(); ++i) - { - // Recalculate the width during animation - uint64_t adjustedAvailableWidth = availableWidth * m_blockSizeRatios[i]; - int blockWidth = std::max(10, static_cast<int>(adjustedAvailableWidth / totalRatio)); - - // Calculate the new X position to maintain the center - int newX = centers[i] - (blockWidth / 2); - if (newX > lastBlockEnd + 5) - { - int diff = newX - (lastBlockEnd + 5); - newX -= diff; - blockWidth += diff; - } - if (newX < lastBlockEnd + 5) - { - int diff = (lastBlockEnd + 5) - newX; - newX += diff; - blockWidth -= diff; - } - lastBlockEnd = newX + blockWidth; - - // Check if the clickPosition is inside the current block's rectangle - QRect blockRect(newX, (height() - totalHeight) / 2, blockWidth, totalHeight); - if (blockRect.contains(clickPosition)) - { - return static_cast<int>(i); // Return the index of the clicked block - } - - currentX += blockWidth + 5; // Move to the next block's position - } - - return -1; // Return -1 if no block was clicked -} - - -void DSCCacheBlocksView::blockSelected(int index) -{ - std::fill(m_blockLuminance.begin(), m_blockLuminance.end(), 191); - m_selectedBlock = index; - if (index != -1) - m_blockLuminance[index] = 255; - update(); - if (index != -1) - emit selectionChanged(m_backingCaches[index], false); -} - - -void DSCCacheBlocksView::resizeEvent(QResizeEvent* event) -{ - QWidget::resizeEvent(event); -} - +DSCTriageViewType::DSCTriageViewType() + : ViewType("DSCTriage", "Dyld Shared Cache Triage") +{} -QSize DSCCacheBlocksView::sizeHint() const +int DSCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) { - return QWidget::sizeHint(); + if (data->GetTypeName() == VIEW_NAME) + return 100; + return 0; } - -QSize DSCCacheBlocksView::minimumSizeHint() const +QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) { - return QWidget::minimumSizeHint(); -} - - -SymbolTableModel::SymbolTableModel(SymbolTableView* parent) - : QAbstractTableModel(parent), m_parent(parent) { -} - -int SymbolTableModel::rowCount(const QModelIndex& parent) const { - Q_UNUSED(parent); - return static_cast<int>(m_symbols.size()); -} - -int SymbolTableModel::columnCount(const QModelIndex& parent) const { - Q_UNUSED(parent); - // We have 3 columns: Address, Name, and Image - return 3; -} - -QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { - if (!index.isValid() || role != Qt::DisplayRole) { - return QVariant(); - } - - const SharedCacheAPI::DSCSymbol& symbol = m_symbols.at(index.row()); - - switch (index.column()) { - case 0: // Address column - return QString("0x%1").arg(symbol.address, 0, 16); // Display address as hexadecimal - case 1: // Name column - return QString::fromUtf8(symbol.name.c_str(), symbol.name.size()); - case 2: // Image column - return QString::fromStdString(symbol.image); - default: - return QVariant(); - } -} - -QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { - return QVariant(); - } - - switch (section) { - case 0: - return QString("Address"); - case 1: - return QString("Name"); - case 2: - return QString("Image"); - default: - return QVariant(); - } -} - -void SymbolTableModel::updateSymbols() { - m_symbols = m_parent->m_symbols; - setFilter(m_filter); -} - -const SharedCacheAPI::DSCSymbol& SymbolTableModel::symbolAt(int row) const { - if (row < 0 || row >= static_cast<int>(m_symbols.size())) { - return m_symbols.at(0); - } - return m_symbols.at(row); + if (data->GetTypeName() != VIEW_NAME) + return nullptr; + // TODO: Check for dyld start. Then continue. + return new DSCTriageView(viewFrame, data); } - -void SymbolTableModel::setFilter(std::string text) +void DSCTriageViewType::Register() { - beginResetModel(); - - m_filter = text; - m_symbols.clear(); - - if (m_filter.empty()) - { - m_symbols = m_parent->m_symbols; - } - else - { - m_symbols.reserve(m_parent->m_symbols.size()); - for (const auto& symbol : m_parent->m_symbols) - { - if (((std::string_view)symbol.name).find(m_filter) != std::string::npos) - { - m_symbols.push_back(symbol); - } - } - m_symbols.shrink_to_fit(); - } - - endResetModel(); -} - - -SymbolTableView::SymbolTableView(QWidget* parent, Ref<SharedCacheAPI::SharedCache> cache) - : m_model(new SymbolTableModel(this)) { - - // Set up the filter model - setModel(m_model); - - // Configure view settings - horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - setSelectionBehavior(QAbstractItemView::SelectRows); - setSelectionMode(QAbstractItemView::SingleSelection); - - BackgroundThread::create(this)->thenBackground([this, cache=cache](){ - // LogInfo("Symbol Search: Loading symbols..."); - m_symbols = cache->LoadAllSymbolsAndWait(); - // LogInfo("Symbol Search: Loaded 0x%zx symbols", m_symbols.size()); - })->thenMainThread([this](){ - m_model->updateSymbols(); - })->start(); -} - -SymbolTableView::~SymbolTableView() { - delete m_model; -} - -void SymbolTableView::setFilter(const std::string& filter) { - m_model->setFilter(filter); + registerViewType(new DSCTriageViewType()); } - -DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), View(), m_data(data), m_cache(new SharedCacheAPI::SharedCache(data)) +DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), m_data(data) { setBinaryDataNavigable(false); setupView(this); + UIContext::registerNotification(this); + m_triageCollection = new DockableTabCollection(); m_triageTabs = new SplitTabWidget(m_triageCollection); auto triageTabStyle = new GlobalAreaTabStyle(); m_triageTabs->setTabStyle(triageTabStyle); - auto cacheInfoWidget = new QWidget; - auto cacheInfoLayout = new QVBoxLayout(cacheInfoWidget); - - QSplitter* containerWidget = new QSplitter; - containerWidget->setOrientation(Qt::Vertical); - - DSCCacheBlocksView* cacheBlocksView = new DSCCacheBlocksView(containerWidget, data, m_cache); - cacheBlocksView->setMinimumHeight(60); - - auto cacheInfo = new CollapsibleSection(this); - cacheInfo->setTitle(QString::fromStdString(data->GetFile()->GetOriginalFilename().substr(data->GetFile()->GetOriginalFilename().find_last_of('/') + 1))); - - auto cacheInfoSubwidget = new QWidget; - - auto mappingTable = new QTableView(cacheInfoSubwidget); - auto mappingModel = new QStandardItemModel(0, 3, mappingTable); - mappingModel->setHorizontalHeaderLabels({"VM Address", "File Address", "Size"}); - - mappingTable->setModel(mappingModel); - - mappingTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - mappingTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - mappingTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); - - mappingTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - - auto sectionTable = new QTableView(cacheInfoSubwidget); - auto sectionModel = new QStandardItemModel(0, 3, sectionTable); - sectionModel->setHorizontalHeaderLabels({"Name", "VM Address", "Size"}); - - sectionTable->setModel(sectionModel); - - sectionTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); - sectionTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); - sectionTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); - - sectionTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - - auto mappingLabel = new QLabel("Mappings"); - auto sectionLabel = new QLabel("Sections"); - - auto mappingLayout = new QVBoxLayout; - mappingLayout->addWidget(mappingLabel); - mappingLayout->addWidget(mappingTable); - - auto sectionLayout = new QVBoxLayout; - sectionLayout->addWidget(sectionLabel); - sectionLayout->addWidget(sectionTable); - - cacheInfoLayout->addLayout(mappingLayout); - cacheInfoLayout->addLayout(sectionLayout); - - cacheInfo->setContentWidget(cacheInfoSubwidget); - - cacheInfo->setMinimumHeight(170); - - connect(cacheBlocksView, &DSCCacheBlocksView::selectionChanged, [this, sectionModel, cacheInfo, cacheInfoWidget, mappingModel](const SharedCacheAPI::BackingCache& index, bool _auto) - { - if (!_auto) - m_triageTabs->selectWidget(cacheInfoWidget); - mappingModel->removeRows(0, mappingModel->rowCount()); - sectionModel->removeRows(0, sectionModel->rowCount()); - auto basename = index.path.substr(index.path.find_last_of('/') + 1); - cacheInfo->setTitle(QString::fromStdString(basename)); - size_t sizeInBits = 0; - for (const auto& mapping : index.mappings) - { - sizeInBits += mapping.size; - mappingModel->appendRow({ - new QStandardItem(QString("0x%1").arg(mapping.vmAddress, 0, 16)), - new QStandardItem(QString("0x%1").arg(mapping.fileOffset, 0, 16)), - new QStandardItem(QString("0x%1").arg(mapping.size, 0, 16))}); - } + QWidget* defaultWidget = nullptr; - for (const auto& header : m_headers) - { - uint64_t i = 0; - for (const auto& section : header.sections) - { - for (const auto& mapping : index.mappings) - { - if (section.addr >= mapping.vmAddress && section.addr < mapping.vmAddress + mapping.size) - { - sectionModel->appendRow({ - new QStandardItem(QString::fromStdString(header.sectionNames[i])), - new QStandardItem(QString("0x%1").arg(section.addr, 0, 16)), - new QStandardItem(QString("0x%1").arg(section.size, 0, 16))}); - break; - } - } - i++; - } - continue; - } + static auto loadImagesWithAddr = [this](const std::vector<uint64_t>& addresses) { + auto controller = SharedCacheController::GetController(*this->m_data); + if (!controller) + return; - std::string sizeStr; - if (sizeInBits < 1024) - { - sizeStr = std::to_string(sizeInBits) + " B"; - } - else if (sizeInBits < 1024 * 1024) - { - sizeStr = std::to_string(sizeInBits / 1024) + " KB"; - } - else if (sizeInBits < 1024 * 1024 * 1024) + std::map<uint64_t, CacheImage> images = {}; + for (const uint64_t& addr : addresses) { - sizeStr = std::to_string(sizeInBits / (1024 * 1024)) + " MB"; - } - else - { - sizeStr = std::to_string(sizeInBits / (1024 * 1024 * 1024)) + " GB"; + auto image = controller->GetImageContaining(addr); + // Only try to load if we have not already. + if (image.has_value() && !controller->IsImageLoaded(*image)) + images.insert({image->headerAddress, *image}); } - cacheInfo->setSubtitleRight(QString::fromStdString(sizeStr)); - }); + // Don't create a worker action if we don't have any images. + if (images.empty()) + return; - containerWidget->addWidget(cacheInfo); + WorkerPriorityEnqueue([controller, this, images]() { + size_t loadedImages = 0; + const std::string initialLoad = fmt::format("Loading images... (0/{})", images.size()); + auto imageLoadTask = BackgroundTask(initialLoad, true); - QWidget* defaultWidget = nullptr; + for (const auto& [addr, image] : images) + { + if (imageLoadTask.IsCancelled()) + break; + std::string newLoad = fmt::format("Loading images... ({}/{})", loadedImages++, images.size()); + imageLoadTask.SetProgressText(newLoad); + if (controller->ApplyImage(*this->m_data, image)) + setImageLoaded(image.headerAddress); + } + imageLoadTask.Finish(); - m_bottomRegionCollection = new DockableTabCollection(); - m_bottomRegionTabs = new SplitTabWidget(m_bottomRegionCollection); - m_bottomRegionTabs->setTabStyle(new GlobalAreaTabStyle()); + // We have loaded images, lets make sure to update analysis! + this->m_data->AddAnalysisOption("linearsweep"); + this->m_data->UpdateAnalysis(); + }); + }; + // Tab: Images auto loadImageTable = new FilterableTableView; { - auto loadImageModel = new QStandardItemModel(0, 2, loadImageTable); + m_imageModel = new QStandardItemModel(0, 3, loadImageTable); { - connect( - cacheBlocksView, &DSCCacheBlocksView::loadDone, [this, loadImageModel]() - { - for (const auto& img : m_cache->GetImages()) - { - if (auto header = m_cache->GetMachOHeaderForAddress(img.headerAddress); header) - { - m_headers.push_back(*header); - } - loadImageModel->appendRow({ - new QStandardItem(QString::fromStdString(img.name)), - new QStandardItem(QString("0x%1").arg(img.headerAddress, 0, 16))}); - } - }); - loadImageModel->setHorizontalHeaderLabels({"Name", "VM Address"}); + m_imageModel->setHorizontalHeaderLabels({"Address", "Loaded", "Name"}); } // loadImageModel auto loadImageButton = new CustomStyleFlatPushButton(); { connect(loadImageButton, &QPushButton::clicked, - [this, loadImageTable](bool) { + [loadImageTable](bool) { auto selected = loadImageTable->selectionModel()->selectedRows(); - if (selected.size() == 0) - { - return; - } - - auto name = selected[0].data().toString().toStdString(); - WorkerPriorityEnqueue([this, name]() { m_cache->LoadImageWithInstallName(name); }); + std::vector<uint64_t> addresses; + for (const auto& row : selected) + addresses.push_back(row.data().toString().toULongLong(nullptr, 16)); + loadImagesWithAddr(addresses); }); - loadImageButton->setText("Load"); + loadImageButton->setText("Load Selected"); loadImageButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); loadImageButton->setMinimumWidth(100); loadImageButton->setMinimumHeight(30); - } // loadImageButton - loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + + auto refreshDataButton = new CustomStyleFlatPushButton(); + { + // TODO: Might want to introduce a cooldown for this button (if we even keep it) + connect(refreshDataButton, &QPushButton::clicked, [this](bool) { RefreshData(); }); + refreshDataButton->setText("Refresh"); + + refreshDataButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + refreshDataButton->setMinimumWidth(100); + refreshDataButton->setMinimumHeight(30); + } // refreshDataButton auto loadImageFilterEdit = new FilterEdit(loadImageTable); { @@ -704,317 +139,325 @@ DSCTriageView::DSCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(pare connect(loadImageTable, &FilterableTableView::activated, this, [=](const QModelIndex& index) { - auto name = loadImageModel->item(index.row(), 0)->text().toStdString(); - WorkerPriorityEnqueue([this, name]() - { - m_cache->LoadImageWithInstallName(name); - }); + auto addr = m_imageModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); + loadImagesWithAddr({addr}); }); + auto loadImageLayout = new QVBoxLayout; loadImageLayout->addWidget(loadImageFilterEdit); loadImageLayout->addWidget(loadImageTable); - loadImageLayout->addWidget(loadImageButton); + auto buttonLayout = new QHBoxLayout; + buttonLayout->addWidget(loadImageButton); + buttonLayout->addWidget(refreshDataButton); + buttonLayout->setAlignment(Qt::AlignLeft); + loadImageLayout->addLayout(buttonLayout); auto loadImageWidget = new QWidget; loadImageWidget->setLayout(loadImageLayout); - m_bottomRegionTabs->addTab(loadImageWidget, "Load an Image"); + loadImageTable->setModel(m_imageModel); - loadImageTable->setModel(loadImageModel); + loadImageTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + loadImageTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); loadImageTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + loadImageTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); loadImageTable->setSelectionBehavior(QAbstractItemView::SelectRows); - loadImageTable->setSelectionMode(QAbstractItemView::SingleSelection); + loadImageTable->setSelectionMode(QAbstractItemView::ExtendedSelection); + + loadImageTable->setSortingEnabled(true); + + loadImageTable->verticalHeader()->setVisible(false); m_triageTabs->addTab(loadImageWidget, "Images"); defaultWidget = loadImageWidget; m_triageTabs->setCanCloseTab(loadImageWidget, false); } // loadImageTable - auto symbolSearch = new SymbolTableView(this, m_cache); + // Tab: Symbols + m_symbolTable = new SymbolTableView(this); { - auto symbolFilterEdit = new FilterEdit(symbolSearch); + auto symbolFilterEdit = new FilterEdit(m_symbolTable); { - connect(symbolFilterEdit, &FilterEdit::textChanged, [symbolSearch](const QString& filter) { - symbolSearch->setFilter(filter.toStdString()); + connect(symbolFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { + m_symbolTable->setFilter(filter.toStdString()); }); } + auto loadSymbolImageButton = new CustomStyleFlatPushButton(); + { + connect(loadSymbolImageButton, &QPushButton::clicked, + [this](bool) { + auto selected = m_symbolTable->selectionModel()->selectedRows(); + std::vector<uint64_t> addresses; + for (const auto& row : selected) + addresses.push_back(row.data().toString().toULongLong(nullptr, 16)); + loadImagesWithAddr(addresses); + }); + loadSymbolImageButton->setText("Load Image"); + + loadSymbolImageButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + loadSymbolImageButton->setMinimumWidth(100); + loadSymbolImageButton->setMinimumHeight(30); + } // loadImageButton + + // Shows the current selected rows image name. + auto currentImageLabel = new QLabel(this); { + currentImageLabel->setText(""); + currentImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + } + + // Update the label whenever the selection changes. + connect(m_symbolTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, + [this, currentImageLabel](const QModelIndex ¤t, const QModelIndex &) { + auto symbol = m_symbolTable->getSymbolAtRow(current.row()); + auto controller = SharedCacheController::GetController(*this->m_data); + if (!controller) + return; + auto image = controller->GetImageContaining(symbol.address); + if (image) + currentImageLabel->setText("Image: " + QString::fromStdString(image->name)); + else + currentImageLabel->setText(""); + }); + + auto symbolFooterLayout = new QHBoxLayout; + symbolFooterLayout->addWidget(loadSymbolImageButton); + symbolFooterLayout->addWidget(currentImageLabel); + + symbolFooterLayout->setAlignment(Qt::AlignLeft); + auto symbolLayout = new QVBoxLayout; symbolLayout->addWidget(symbolFilterEdit); - symbolLayout->addWidget(symbolSearch); + symbolLayout->addWidget(m_symbolTable); + symbolLayout->addLayout(symbolFooterLayout); auto symbolWidget = new QWidget; symbolWidget->setLayout(symbolLayout); - symbolSearch->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // Address - symbolSearch->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Name - symbolSearch->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); // Image + m_symbolTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); // Address + m_symbolTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); // Name - symbolSearch->setSelectionBehavior(QAbstractItemView::SelectRows); - symbolSearch->setSelectionMode(QAbstractItemView::SingleSelection); + m_symbolTable->setSelectionBehavior(QAbstractItemView::SelectRows); + m_symbolTable->setSelectionMode(QAbstractItemView::SingleSelection); - std::function<void(uint64_t)> navigateToAddress = [=](uint64_t addr){ - ExecuteOnMainThread([addr, this](){ - if (BinaryNinja::Settings::Instance()->Get<bool>("ui.view.graph.preferred")) - m_data->Navigate("Graph:DSCView", addr); - else - m_data->Navigate("Linear:DSCView", addr); - }); - }; + connect(m_symbolTable, &SymbolTableView::activated, this, [=](const QModelIndex& index){ + auto symbol = m_symbolTable->getSymbolAtRow(index.row()); + auto dialog = new QMessageBox(this); - connect(symbolSearch, &SymbolTableView::activated, this, [=](const QModelIndex& index) - { - auto symbol = symbolSearch->getSymbolAtRow(index.row()); - WorkerPriorityEnqueue([this, symbol, navigateToAddress]() + auto controller = SharedCacheController::GetController(*this->m_data); + if (!controller) + return; + + auto image = controller->GetImageContaining(symbol.address); + if (!image.has_value()) + return; + + dialog->setText("Load " + QString::fromStdString(image->name) + "?"); + dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + connect(dialog, &QMessageBox::buttonClicked, this, [=](QAbstractButton* button) { - if (m_data->IsValidOffset(symbol.address)) - navigateToAddress(symbol.address); - else - { - m_cache->LoadImageWithInstallName(symbol.image); - navigateToAddress(symbol.address); - } + if (button == dialog->button(QMessageBox::Yes)) + loadImagesWithAddr({image->headerAddress}); }); + + dialog->exec(); }); - m_triageTabs->addTab(symbolWidget, "Symbol Search"); + m_triageTabs->addTab(symbolWidget, "Symbols"); m_triageTabs->setCanCloseTab(symbolWidget, false); } // symbolSearch - auto loadedRegions = new QTreeView; + // Tab: Mappings & Regions + auto cacheInfoWidget = new QWidget; { - auto loadedRegionsModel = new QStandardItemModel(0, 3, loadedRegions); - loadedRegionsModel->setHorizontalHeaderLabels({"VM Address", "Size", "Pretty Name"}); + auto cacheInfoLayout = new QVBoxLayout(cacheInfoWidget); - auto loadedRegionsLayout = new QVBoxLayout; - loadedRegionsLayout->addWidget(loadedRegions); + auto cacheInfoSubwidget = new QWidget; - auto loadedRegionsWidget = new QWidget; - loadedRegionsWidget->setLayout(loadedRegionsLayout); + auto mappingTable = new QTableView(cacheInfoSubwidget); + m_mappingModel = new QStandardItemModel(0, 4, mappingTable); + m_mappingModel->setHorizontalHeaderLabels({"Address", "Size", "File Address", "File Path"}); - loadedRegions->setModel(loadedRegionsModel); + mappingTable->setModel(m_mappingModel); - loadedRegions->header()->setSectionResizeMode(QHeaderView::Stretch); + mappingTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::ResizeToContents); + mappingTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); - loadedRegions->setSelectionBehavior(QAbstractItemView::SelectRows); - loadedRegions->setSelectionMode(QAbstractItemView::SingleSelection); + mappingTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - connect(loadedRegions, &QTreeView::doubleClicked, this, [=](const QModelIndex& index) - { - auto addr = loadedRegionsModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); - }); + mappingTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mappingTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - connect(loadedRegions, &QTreeView::activated, this, [=](const QModelIndex& index) - { - auto addr = loadedRegionsModel->item(index.row(), 0)->text().toULongLong(nullptr, 16); - }); + mappingTable->setSortingEnabled(true); - // m_triageTabs->addTab(loadedRegionsWidget, "Loaded Regions"); - } // loadedRegions + mappingTable->verticalHeader()->setVisible(false); - m_triageTabs->addTab(cacheInfoWidget, "Cache Info"); - m_triageTabs->setCanCloseTab(cacheInfoWidget, false); + auto regionTable = new FilterableTableView(cacheInfoSubwidget); + m_regionModel = new QStandardItemModel(0, 4, regionTable); + m_regionModel->setHorizontalHeaderLabels({"Address", "Size", "Type", "Name"}); - // check for alpha popup qsetting - QSettings settings; + regionTable->setModel(m_regionModel); - QTextBrowser *tb = new QTextBrowser(this); - { - tb->setOpenExternalLinks(true); - auto alphaHtml = - R"( -<h1>Dyld Shared Cache Alpha</h1> + regionTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + regionTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + regionTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Interactive); + regionTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); -<p> This is an experimental alpha release of the dyld shared cache view! We are hard at work improving this and adding features, but we wanted -to make it available for users to experiment with as soon as possible. </p> + regionTable->setEditTriggers(QAbstractItemView::NoEditTriggers); -<h2> Platforms </h2> -<dl> - <dt> iOS 11-17 </dt><dd> Full Support </dd> - <dt> iOS 18 </dt><dd> Partial support, may experience issues -- specifically, Objective-C optimization parsing is not implemented </dd> - <dt> macOS x86/arm64e </dt><dd> Partial support, may experience issues </dd> -</dl> + regionTable->setSelectionBehavior(QAbstractItemView::SelectRows); + regionTable->setSelectionMode(QAbstractItemView::ExtendedSelection); -<p> iOS parsing should work fairly well for now. macOS parsing should be usable, but both are still a work in progress. </p> -<h2> Getting the latest version of the plugin </h2> -<p> We frequently release "dev" builds which will contain the latest version of the dyld shared cache plugin (and many other things). </p> -<p> You can find instructions on how to install these builds <a href="https://docs.binary.ninja/guide/index.html#development-branch">here</a>. </p> + regionTable->setSortingEnabled(true); -<h3> Reading / building the source </h3> -<p> Like most of our platforms, architectures, debug information parsing, and the entire API and documentation, this plugin is open source. </p> -<p> You can read the source and find instructions for building it <a href="https://github.com/Vector35/binaryninja-api/tree/dev/view/sharedcache">here</a>. </p> -<p> Contributions are always welcome! </p> -)"; - tb->setHtml(alphaHtml); + regionTable->verticalHeader()->setVisible(false); - m_triageTabs->addTab(tb, "Dyld Shared Cache Alpha"); - m_triageTabs->setCanCloseTab(tb, false); + auto mappingLabel = new QLabel("Mappings"); - } - if (!(settings.contains(QSETTINGS_KEY_ALPHA_POPUP_SEEN) && settings.value(QSETTINGS_KEY_ALPHA_POPUP_SEEN).toBool())) - { - LogAlert("dyld_shared_cache support is highly experimental! We do not expect it to work on all versions yet! See the 'Dlyd Shared Cache Alpha' tab in the DSCTriage view for more information. "); - settings.setValue(QSETTINGS_KEY_ALPHA_POPUP_SEEN, true); - defaultWidget = tb; - } + auto regionLabel = new QLabel("Regions"); + auto regionFilterEdit = new FilterEdit(regionTable); + { + connect(regionFilterEdit, &FilterEdit::textChanged, [regionTable](const QString& filter) { + regionTable->setFilter(filter.toStdString()); + }); + } + + auto regionHeaderLayout = new QHBoxLayout; + regionHeaderLayout->addWidget(regionLabel); + regionHeaderLayout->addWidget(regionFilterEdit); + regionHeaderLayout->setAlignment(Qt::AlignJustify); + regionHeaderLayout->setSpacing(30); + + auto mappingLayout = new QVBoxLayout; + mappingLayout->addWidget(mappingLabel); + mappingLayout->addWidget(mappingTable); + + auto regionLayout = new QVBoxLayout; + regionLayout->addLayout(regionHeaderLayout); + regionLayout->addWidget(regionTable); + + cacheInfoLayout->addLayout(mappingLayout); + cacheInfoLayout->addLayout(regionLayout); - containerWidget->addWidget(m_bottomRegionTabs); + m_triageTabs->addTab(cacheInfoWidget, "Mappings & Regions"); + m_triageTabs->setCanCloseTab(cacheInfoWidget, false); + } // cacheInfoSection m_layout = new QVBoxLayout(this); - m_layout->addWidget(cacheBlocksView); m_layout->addWidget(m_triageTabs); setLayout(m_layout); + // In case we have already initialized the controller (user has opened this view type again) + // we will call refresh data. If this is the first triage view constructed (i.e. before view init) then this + // will do nothing. + RefreshData(); + m_triageTabs->selectWidget(defaultWidget); } +DSCTriageView::~DSCTriageView() +{ + UIContext::unregisterNotification(this); +} QFont DSCTriageView::getFont() { return getMonospaceFont(this); } - BinaryViewRef DSCTriageView::getData() { return m_data; } - bool DSCTriageView::navigate(uint64_t offset) { - return false; + // TODO: We have to set this to true otherwise view restore does not pickup this view. + return true; } - uint64_t DSCTriageView::getCurrentOffset() { return 0; } - -CollapsibleSection::CollapsibleSection(QWidget* parent) - : QWidget(parent) +void DSCTriageView::OnAfterOpenFile(UIContext *context, FileContext *file, ViewFrame *frame) { - auto layout = new QVBoxLayout(this); - { - layout->setContentsMargins(0, 0, 0, 0); - - auto hLayout = new QHBoxLayout; - { - hLayout->setContentsMargins(0, 0, 0, 0); - - m_titleLabel = new QLabel; - m_titleLabel->setStyleSheet("font-weight: bold; font-size: 16px;"); - hLayout->addWidget(m_titleLabel, 1); - - m_subtitleRightLabel = new QLabel; - m_subtitleRightLabel->setStyleSheet("font-size: 12px;"); - hLayout->addWidget(m_subtitleRightLabel); - - m_collapseButton = new CustomStyleFlatPushButton; - m_collapseButton->setFlat(true); - m_collapseButton->setCheckable(true); - } - - layout->addLayout(hLayout); - } - - m_contentWidgetContainer = new QWidget; - { - layout->addWidget(m_contentWidgetContainer); - new QVBoxLayout(m_contentWidgetContainer); - } - -} - - -void CollapsibleSection::setTitle(const QString& title) -{ - m_titleLabel->setText(title); + RefreshData(); + UIContextNotification::OnAfterOpenFile(context, file, frame); } - -void CollapsibleSection::setSubtitleRight(const QString& subtitle) -{ - m_subtitleRightLabel->setVisible(subtitle != ""); - m_subtitleRightLabel->setText(subtitle); -} - - -void CollapsibleSection::setContentWidget(QWidget* contentWidget) +// Called when shared cache information has changed. +void DSCTriageView::RefreshData() { - m_contentWidget = contentWidget; - m_contentWidgetContainer->layout()->addWidget(contentWidget); -} - - -QSize CollapsibleSection::sizeHint() const -{ - return QWidget::sizeHint(); -} - + // Controller should be available after view init. + auto controller = SharedCacheController::GetController(*m_data); + if (!controller) + return; -void CollapsibleSection::setCollapsed(bool collapsed, bool animated) -{ - if (collapsed == m_collapsed) + m_imageModel->setRowCount(0); + for (const auto& img : controller->GetImages()) { - return; + m_imageModel->appendRow({ + new QStandardItem(QString("0x%1").arg(img.headerAddress, 0, 16)), + new QStandardItem(""), + new QStandardItem(QString::fromStdString(img.name)) + }); } - m_collapsed = collapsed; + // Set images as loaded (updating the relevant image row) + for (const auto& loadedImg : controller->GetLoadedImages()) + setImageLoaded(loadedImg.headerAddress); - if (m_collapsed) + m_regionModel->setRowCount(0); + for (const auto& region : controller->GetRegions()) { - m_contentWidget->hide(); - } - else - { - m_contentWidget->show(); + m_regionModel->appendRow({ + new QStandardItem(QString("0x%1").arg(region.start, 0, 16)), + new QStandardItem(QString("0x%1").arg(region.size, 0, 16)), + new QStandardItem(QString::fromStdString(GetRegionTypeAsString(region.type))), + new QStandardItem(QString::fromStdString(region.name)) + }); } - if (animated) + m_mappingModel->setRowCount(0); + for (const auto& entry : controller->GetEntries()) { - m_onContentAddedAnimation->start(); + for (const auto& mapping : entry.mappings) + { + m_mappingModel->appendRow({ + new QStandardItem(QString("0x%1").arg(mapping.vmAddress, 0, 16)), + new QStandardItem(QString("0x%1").arg(mapping.size, 0, 16)), + new QStandardItem(QString("0x%1").arg(mapping.fileOffset, 0, 16)), + new QStandardItem(QString::fromStdString(entry.path)) + }); + } } -} - - -DSCTriageViewType::DSCTriageViewType() - : ViewType("DSCTriage", "Dyld Shared Cache Triage") -{ + m_symbolTable->populateSymbols(*m_data); } - -int DSCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) +void DSCTriageView::setImageLoaded(const uint64_t imageHeaderAddr) { - if (data->GetTypeName() == VIEW_NAME) - { - return 100; - } - return 0; -} - + // TODO: Better icon... probably something like ✅ + static QIcon loadedIcon(":/icons/images/plus.png"); -QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) -{ - if (data->GetTypeName() != VIEW_NAME) + // Go through the m_loadImageModel and find the image associated with the address + // then set the image as loaded. + for (int i = 0; i < m_imageModel->rowCount(); i++) { - return nullptr; - } - if (SharedCacheAPI::SharedCache::FastGetBackingCacheCount(data) == 0) - { - return nullptr; + auto addrCol = m_imageModel->index(i, 0); + const auto addr = addrCol.data().toString().toULongLong(nullptr, 16); + if (addr == imageHeaderAddr) + { + auto statusCol = m_imageModel->index(i, 1); + m_imageModel->setData(statusCol, QString("•"), Qt::DisplayRole); + break; + } } - return new DSCTriageView(viewFrame, data); -} - - -void DSCTriageViewType::Register() -{ - ViewType::registerViewType(new DSCTriageViewType()); } diff --git a/view/sharedcache/ui/dsctriage.h b/view/sharedcache/ui/dsctriage.h index 39f06ce7..8c4ceb3f 100644 --- a/view/sharedcache/ui/dsctriage.h +++ b/view/sharedcache/ui/dsctriage.h @@ -2,118 +2,32 @@ // Created by kat on 8/15/24. // -#include <sharedcacheapi.h> #include <binaryninjaapi.h> +#include <QStyledItemDelegate> + #include "uitypes.h" #include "viewframe.h" #include "animation.h" #include "uicontext.h" -#include <QTableView> -#include <QStandardItemModel> -#include <QSortFilterProxyModel> -#include <QHeaderView> #include "filter.h" +#include "symboltable.h" #ifndef BINARYNINJA_DSCTRIAGE_H #define BINARYNINJA_DSCTRIAGE_H - -class DSCCacheBlocksView : public QWidget -{ - Q_OBJECT - - BinaryViewRef m_data; - Ref<SharedCacheAPI::SharedCache> m_cache; - - uint64_t m_backingCacheCount = 0; - std::vector<SharedCacheAPI::BackingCache> m_backingCaches; - - std::atomic<BNDSCViewLoadProgress> m_currentProgress; - std::vector<uint64_t> m_blockSizeRatios; - std::vector<uint64_t> m_targetBlockSizeForAnimation; - uint64_t m_averageBlockSizeForAnimationInterp = 0; - std::vector<uint64_t> m_blockLuminance; - Animation* m_blockWaveAnimation; - Animation* m_blockExpandAnimation; - Animation* m_blockAutoselectAnimation; - - int m_selectedBlock = -1; - - int getBlockIndexAtPosition(const QPoint& clickPosition); - - void blockSelected(int index); - -public: - DSCCacheBlocksView(QWidget* parent, BinaryViewRef data, Ref<SharedCacheAPI::SharedCache> cache); - virtual ~DSCCacheBlocksView() override; - -protected: - void mousePressEvent(QMouseEvent* event) override; - void mouseReleaseEvent(QMouseEvent* event) override; - void mouseDoubleClickEvent(QMouseEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; - void focusInEvent(QFocusEvent* event) override; - void focusOutEvent(QFocusEvent* event) override; - void enterEvent(QEnterEvent* event) override; - void leaveEvent(QEvent* event) override; - void paintEvent(QPaintEvent* event) override; - void resizeEvent(QResizeEvent* event) override; - -public: - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - -signals: - void loadDone(); - void selectionChanged(const SharedCacheAPI::BackingCache& index, bool automatic); -}; - - -class CollapsibleSection : public QWidget -{ - Q_OBJECT - - QLabel* m_titleLabel; - QLabel* m_subtitleRightLabel; - QPushButton* m_collapseButton; - - bool m_collapsed = true; - - Animation* m_onContentAddedAnimation; - - QWidget* m_contentWidgetContainer; - QWidget* m_contentWidget; - -protected: - QSize sizeHint() const override; - -public: - CollapsibleSection(QWidget* parent); - void setTitle(const QString& title); - void setSubtitleRight(const QString& subtitle); - - void setContentWidget(QWidget* contentWidget); - - void setCollapsed(bool collapsed, bool animated = true); - bool isCollapsed() const { return m_collapsed; } -}; - - class FilterableTableView : public QTableView, public FilterTarget { Q_OBJECT bool m_filterByHiding; public: - FilterableTableView(QWidget* parent = nullptr, bool filterByHiding = true) + explicit FilterableTableView(QWidget* parent = nullptr, bool filterByHiding = true) : QTableView(parent), m_filterByHiding(filterByHiding) { viewport()->installEventFilter(this); } - ~FilterableTableView() override {} + ~FilterableTableView() override = default; void setFilter(const std::string& filter) override { if (!m_filterByHiding) @@ -183,102 +97,34 @@ signals: void filterTextChanged(const QString& text); }; -class SymbolTableView; - -class SymbolTableModel : public QAbstractTableModel { - Q_OBJECT - - SymbolTableView* m_parent; - std::string m_filter; - std::vector<SharedCacheAPI::DSCSymbol> m_symbols; - -public: - explicit SymbolTableModel(SymbolTableView* parent); - - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - - void updateSymbols(); - - void setFilter(std::string text); - - const SharedCacheAPI::DSCSymbol& symbolAt(int row) const; -}; - - -class SymbolTableView : public QTableView, public FilterTarget -{ - Q_OBJECT - friend class SymbolTableModel; - - std::vector<SharedCacheAPI::DSCSymbol> m_symbols; - - SymbolTableModel* m_model; - -public: - SymbolTableView(QWidget* parent, Ref<SharedCacheAPI::SharedCache> cache); - virtual ~SymbolTableView() override; - - void scrollToFirstItem() override { - if (model()->rowCount() > 0) { - scrollTo(model()->index(0, 0)); - } - } - - void scrollToCurrentItem() override { - QModelIndex currentIndex = selectionModel()->currentIndex(); - if (currentIndex.isValid()) { - scrollTo(currentIndex); - } - } - - void selectFirstItem() override { - if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); - } - } - - void activateFirstItem() override { - if (model()->rowCount() > 0) { - QModelIndex firstIndex = model()->index(0, 0); - setCurrentIndex(firstIndex); - emit activated(firstIndex); - } - } - - SharedCacheAPI::DSCSymbol getSymbolAtRow(int row) const - { - return m_model->symbolAt(row); - } - - void setFilter(const std::string& filter) override; -}; - - -class DSCTriageView : public QWidget, public View +class DSCTriageView : public QWidget, public View, public UIContextNotification { BinaryViewRef m_data; QVBoxLayout* m_layout; - Ref<SharedCacheAPI::SharedCache> m_cache; SplitTabWidget* m_triageTabs; DockableTabCollection* m_triageCollection; - SplitTabWidget* m_bottomRegionTabs; - DockableTabCollection* m_bottomRegionCollection; + QStandardItemModel* m_imageModel; + + SymbolTableView* m_symbolTable; - std::vector<SharedCacheAPI::SharedCacheMachOHeader> m_headers; + QStandardItemModel* m_mappingModel; + + QStandardItemModel* m_regionModel; public: DSCTriageView(QWidget* parent, BinaryViewRef data); + ~DSCTriageView() override; BinaryViewRef getData() override; void setSelectionOffsets(BNAddressRange range) override {}; QFont getFont() override; bool navigate(uint64_t offset) override; uint64_t getCurrentOffset() override; + + void OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) override; + void RefreshData(); + void setImageLoaded(uint64_t imageHeaderAddr); }; diff --git a/view/sharedcache/ui/symboltable.cpp b/view/sharedcache/ui/symboltable.cpp new file mode 100644 index 00000000..c5e2d7bf --- /dev/null +++ b/view/sharedcache/ui/symboltable.cpp @@ -0,0 +1,129 @@ +#include <progresstask.h> +#include "symboltable.h" + +#include <QHeaderView> + +#include "binaryninjaapi.h" + +using namespace BinaryNinja; +using namespace SharedCacheAPI; + +SymbolTableModel::SymbolTableModel(SymbolTableView* parent) + : QAbstractTableModel(parent), m_parent(parent) { +} + +int SymbolTableModel::rowCount(const QModelIndex& parent) const { + Q_UNUSED(parent); + return static_cast<int>(m_symbols.size()); +} + +int SymbolTableModel::columnCount(const QModelIndex& parent) const { + Q_UNUSED(parent); + // We have 2 columns: Address, Name + return 2; +} + +QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || role != Qt::DisplayRole) { + return QVariant(); + } + + const CacheSymbol& symbol = m_symbols.at(index.row()); + + switch (index.column()) { + case 0: // Address column + return QString("0x%1").arg(symbol.address, 0, 16); // Display address as hexadecimal + case 1: // Name column + return QString::fromUtf8(symbol.name.c_str(), symbol.name.size()); + default: + return QVariant(); + } +} + +QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { + return QVariant(); + } + + switch (section) { + case 0: + return QString("Address"); + case 1: + return QString("Name"); + default: + return QVariant(); + } +} + +void SymbolTableModel::updateSymbols() { + m_symbols = m_parent->m_symbols; + setFilter(m_filter); +} + +const CacheSymbol& SymbolTableModel::symbolAt(int row) const { + return m_symbols.at(row); +} + + +void SymbolTableModel::setFilter(std::string text) +{ + beginResetModel(); + + m_filter = text; + m_symbols.clear(); + + if (m_filter.empty()) + { + m_symbols = m_parent->m_symbols; + } + else + { + m_symbols.reserve(m_parent->m_symbols.size()); + for (const auto& symbol : m_parent->m_symbols) + { + if (((std::string_view)symbol.name).find(m_filter) != std::string::npos) + { + m_symbols.push_back(symbol); + } + } + m_symbols.shrink_to_fit(); + } + + endResetModel(); +} + + +SymbolTableView::SymbolTableView(QWidget* parent) + : m_model(new SymbolTableModel(this)) { + + // Set up the filter model + setModel(m_model); + + // Configure view settings + horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + + setSortingEnabled(true); +} + +SymbolTableView::~SymbolTableView() { + delete m_model; +} + +void SymbolTableView::populateSymbols(BinaryView &view) +{ + if (auto controller = SharedCacheController::GetController(view)) { + BackgroundThread::create(this) + ->thenBackground([this, controller]() { + std::vector<CacheSymbol> newSymbols = controller->GetSymbols(); + m_symbols.swap(newSymbols); + }) + ->thenMainThread([this](){ m_model->updateSymbols(); }) + ->start(); + } +} + +void SymbolTableView::setFilter(const std::string& filter) { + m_model->setFilter(filter); +} diff --git a/view/sharedcache/ui/symboltable.h b/view/sharedcache/ui/symboltable.h new file mode 100644 index 00000000..41e2597e --- /dev/null +++ b/view/sharedcache/ui/symboltable.h @@ -0,0 +1,87 @@ +#pragma once + +#include <sharedcacheapi.h> +#include "viewframe.h" +#include "animation.h" + +#include <QTableView> +#include <QStandardItemModel> +#include "filter.h" + +class SymbolTableView; + +class SymbolTableModel : public QAbstractTableModel { + Q_OBJECT + + SymbolTableView* m_parent; + std::string m_filter; + std::vector<SharedCacheAPI::CacheSymbol> m_symbols; + +public: + explicit SymbolTableModel(SymbolTableView* parent); + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + void updateSymbols(); + + void setFilter(std::string text); + + const SharedCacheAPI::CacheSymbol& symbolAt(int row) const; +}; + + +class SymbolTableView : public QTableView, public FilterTarget +{ + Q_OBJECT + friend class SymbolTableModel; + + // TODO: Both the model and the view store the symbols? + std::vector<SharedCacheAPI::CacheSymbol> m_symbols; + + SymbolTableModel* m_model; + +public: + SymbolTableView(QWidget* parent); + virtual ~SymbolTableView() override; + + void scrollToFirstItem() override { + if (model()->rowCount() > 0) { + scrollTo(model()->index(0, 0)); + } + } + + // Call this to populate the symbols from the given view. + void populateSymbols(BinaryNinja::BinaryView& view); + + void scrollToCurrentItem() override { + QModelIndex currentIndex = selectionModel()->currentIndex(); + if (currentIndex.isValid()) { + scrollTo(currentIndex); + } + } + + void selectFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + selectionModel()->select(firstIndex, QItemSelectionModel::ClearAndSelect); + } + } + + void activateFirstItem() override { + if (model()->rowCount() > 0) { + QModelIndex firstIndex = model()->index(0, 0); + setCurrentIndex(firstIndex); + emit activated(firstIndex); + } + } + + SharedCacheAPI::CacheSymbol getSymbolAtRow(int row) const + { + return m_model->symbolAt(row); + } + + void setFilter(const std::string& filter) override; +};
\ No newline at end of file diff --git a/view/sharedcache/workflow/ObjCActivity.cpp b/view/sharedcache/workflow/ObjCActivity.cpp new file mode 100644 index 00000000..55a8a8fa --- /dev/null +++ b/view/sharedcache/workflow/ObjCActivity.cpp @@ -0,0 +1,129 @@ +#include "ObjCActivity.h" +#include "lowlevelilinstruction.h" + +// TODO: Consolidate this with the Obj-C workflow at some point https://github.com/Vector35/workflow_objc + +using namespace BinaryNinja; + +void ObjCActivity::Register(Workflow &workflow) +{ + workflow.RegisterActivity(new Activity("core.analysis.objc.adjustCallType", &AdjustCallType)); + workflow.Insert("core.function.analyzeTailCalls", "core.analysis.objc.adjustCallType"); +} + +std::vector<std::string> splitSelector(const std::string& selector) { + std::vector<std::string> components; + std::istringstream stream(selector); + std::string component; + + while (std::getline(stream, component, ':')) { + if (!component.empty()) { + components.push_back(component); + } + } + + return components; +} + +std::vector<std::string> generateArgumentNames(const std::vector<std::string>& components) { + std::vector<std::string> argumentNames; + + for (const std::string& component : components) { + size_t startPos = component.find_last_of(' '); + std::string argumentName = (startPos == std::string::npos) ? component : component.substr(startPos + 1); + argumentNames.push_back(argumentName); + } + + return argumentNames; +} + +void ObjCActivity::AdjustCallType(Ref<AnalysisContext> ctx) +{ + const auto func = ctx->GetFunction(); + const auto arch = func->GetArchitecture(); + const auto bv = func->GetView(); + + const auto llil = ctx->GetLowLevelILFunction(); + if (!llil) { + return; + } + const auto ssa = llil->GetSSAForm(); + if (!ssa) { + return; + } + + const auto rewriteIfEligible = [bv, ssa](size_t insnIndex) { + auto insn = ssa->GetInstruction(insnIndex); + if (insn.operation != LLIL_CALL_SSA) + return; + + // Filter out calls that aren't to `objc_msgSend`. + auto callExpr = insn.GetDestExpr<LLIL_CALL_SSA>(); + if (auto symbol = bv->GetSymbolByAddress(callExpr.GetValue().value)) + if (symbol->GetRawName() != "_objc_msgSend") + return; + + const auto params = insn.GetParameterExprs<LLIL_CALL_SSA>(); + // The second parameter passed to the objc_msgSend call is the address of + // either the selector reference or the method's name, which in both cases + // is dereferenced to retrieve a selector. + if (params.size() < 2) + return; + uint64_t rawSelector = 0; + if (params[1].operation == LLIL_REG_SSA) + { + const auto selectorRegister = params[1].GetSourceSSARegister<LLIL_REG_SSA>(); + rawSelector = ssa->GetSSARegisterValue(selectorRegister).value; + } + else if (params[0].operation == LLIL_SEPARATE_PARAM_LIST_SSA) + { + if (params[0].GetParameterExprs<LLIL_SEPARATE_PARAM_LIST_SSA>().size() == 0) + return; + const auto selectorRegister = params[0].GetParameterExprs<LLIL_SEPARATE_PARAM_LIST_SSA>()[1].GetSourceSSARegister<LLIL_REG_SSA>(); + rawSelector = ssa->GetSSARegisterValue(selectorRegister).value; + } + if (!rawSelector || !bv->IsValidOffset(rawSelector)) + return; + + // -- Do callsite override + auto reader = BinaryReader(bv); + reader.Seek(rawSelector); + auto selector = reader.ReadCString(500); + auto additionalArgumentCount = std::count(selector.begin(), selector.end(), ':'); + + auto retType = bv->GetTypeByName({ "id" }); + if (!retType) + retType = Type::PointerType(ssa->GetArchitecture(), Type::VoidType()); + + std::vector<FunctionParameter> callTypeParams; + auto cc = bv->GetDefaultPlatform()->GetDefaultCallingConvention(); + + callTypeParams.emplace_back("self", retType, true, Variable()); + + auto selType = bv->GetTypeByName({ "SEL" }); + if (!selType) + selType = Type::PointerType(ssa->GetArchitecture(), Type::IntegerType(1, true)); + callTypeParams.emplace_back("sel", selType, true, Variable()); + + std::vector<std::string> selectorComponents = splitSelector(selector); + std::vector<std::string> argumentNames = generateArgumentNames(selectorComponents); + + for (size_t i = 0; i < additionalArgumentCount; i++) + { + auto argType = Type::IntegerType(bv->GetAddressSize(), true); + if (argumentNames.size() > i && !argumentNames[i].empty()) + callTypeParams.emplace_back(argumentNames[i], argType, true, Variable()); + else + callTypeParams.emplace_back("arg" + std::to_string(i), argType, true, Variable()); + } + + auto funcType = Type::FunctionType(retType, cc, callTypeParams); + ssa->GetFunction()->SetAutoCallTypeAdjustment(ssa->GetFunction()->GetArchitecture(), insn.address, {funcType, BN_DEFAULT_CONFIDENCE}); + // -- + }; + + for (const auto& block : ssa->GetBasicBlocks()) + for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) + rewriteIfEligible(i); +} + diff --git a/view/sharedcache/workflow/ObjCActivity.h b/view/sharedcache/workflow/ObjCActivity.h new file mode 100644 index 00000000..05a0d1b8 --- /dev/null +++ b/view/sharedcache/workflow/ObjCActivity.h @@ -0,0 +1,9 @@ +#pragma once +#include "binaryninjaapi.h" + +class ObjCActivity +{ + static void AdjustCallType(BinaryNinja::Ref<BinaryNinja::AnalysisContext> ctx); +public: + static void Register(BinaryNinja::Workflow& workflow); +};
\ No newline at end of file diff --git a/view/sharedcache/workflow/SharedCacheWorkflow.cpp b/view/sharedcache/workflow/SharedCacheWorkflow.cpp index c1ee45b7..6587abfe 100644 --- a/view/sharedcache/workflow/SharedCacheWorkflow.cpp +++ b/view/sharedcache/workflow/SharedCacheWorkflow.cpp @@ -15,22 +15,26 @@ #include "thread" #include <shared_mutex> +#include "ObjCActivity.h" + +using namespace BinaryNinja; using namespace SharedCacheAPI; struct GlobalWorkflowState { - std::mutex imageLoadMutex; + // Mutex to guard against duplicate region/image loads that cause reanalysis. + std::mutex loadMutex; bool autoLoadStubsAndDyldData = true; bool autoLoadObjCStubRequirements = true; }; -static std::unordered_map<uint64_t, std::shared_ptr<GlobalWorkflowState>> globalWorkflowState; -std::shared_mutex globalWorkflowStateMutex; - std::shared_ptr<GlobalWorkflowState> GetGlobalWorkflowState(Ref<BinaryView> view) { + static std::shared_mutex globalWorkflowStateMutex; + static std::unordered_map<uint64_t, std::shared_ptr<GlobalWorkflowState>> globalWorkflowState; + std::shared_lock<std::shared_mutex> readLock(globalWorkflowStateMutex); - uint64_t viewId = view->GetFile()->GetSessionId(); + const uint64_t viewId = view->GetFile()->GetSessionId(); auto foundState = globalWorkflowState.find(viewId); if (foundState != globalWorkflowState.end()) return foundState->second; @@ -39,398 +43,342 @@ std::shared_ptr<GlobalWorkflowState> GetGlobalWorkflowState(Ref<BinaryView> view std::unique_lock<std::shared_mutex> writeLock(globalWorkflowStateMutex); globalWorkflowState[viewId] = std::make_shared<GlobalWorkflowState>(); Ref<Settings> settings = view->GetLoadSettings(VIEW_NAME); + bool autoLoadStubsAndDyldData = true; if (settings && settings->Contains("loader.dsc.autoLoadStubsAndDyldData")) - { autoLoadStubsAndDyldData = settings->Get<bool>("loader.dsc.autoLoadStubsAndDyldData", view); - } globalWorkflowState[viewId]->autoLoadStubsAndDyldData = autoLoadStubsAndDyldData; + bool autoLoadObjC = true; if (settings && settings->Contains("loader.dsc.autoLoadObjCStubRequirements")) - { autoLoadObjC = settings->Get<bool>("loader.dsc.autoLoadObjCStubRequirements", view); - } globalWorkflowState[viewId]->autoLoadObjCStubRequirements = autoLoadObjC; - return globalWorkflowState[viewId]; -} - -std::vector<std::string> splitSelector(const std::string& selector) { - std::vector<std::string> components; - std::istringstream stream(selector); - std::string component; - - while (std::getline(stream, component, ':')) { - if (!component.empty()) { - components.push_back(component); - } - } - - return components; + return globalWorkflowState[viewId]; } -std::vector<std::string> generateArgumentNames(const std::vector<std::string>& components) { - std::vector<std::string> argumentNames; - - for (const std::string& component : components) { - size_t startPos = component.find_last_of(" "); - std::string argumentName = (startPos == std::string::npos) ? component : component.substr(startPos + 1); - argumentNames.push_back(argumentName); - } +// TODO: Add a type library cache to this workflow. (so we dont take global file lock) +Ref<TypeLibrary> TypeLibraryFromName(BinaryView& view, const std::string& name) { + // Check to see if we have already loaded the type library. + if (auto typeLib = view.GetTypeLibrary(name)) + return typeLib; - return argumentNames; + // TODO: Use the functions platform instead. + auto typeLibs = view.GetDefaultPlatform()->GetTypeLibrariesByName(name); + if (!typeLibs.empty()) + return typeLibs.front(); + return nullptr; } +void IdentifyStub(BinaryView& view, const SharedCacheController& controller, uint64_t stubFuncAddr, uint64_t symbolAddr) { + static const char* STUB_PREFIX = "j_"; + // TODO: Just check for if there is a user symbol instead? + // TODO: ^ well we really should be using an auto symbol no? + // Check to see if there is a symbol already at the target. If so we should just stop. + if (symbolAddr == stubFuncAddr) + if (const auto targetSymbol = view.GetSymbolByAddress(stubFuncAddr)) + if (targetSymbol->GetShortName().find(STUB_PREFIX) != std::string::npos) + return; -void ProcessStubImageCall(const std::shared_ptr<GlobalWorkflowState> &workflowState, Ref<SharedCache> cache, Ref<Function> func, - const Ref<Section>& section, uint64_t target) -{ - if (!workflowState->imageLoadMutex.try_lock()) - return; - - auto view = func->GetView(); - if (!view->IsValidOffset(target)) + // Try and apply a version of the symbol address to the target address + if (symbolAddr != stubFuncAddr) { - if (!cache->GetImageNameForAddress(target).empty()) - cache->LoadImageContainingAddress(target); - else - cache->LoadSectionAtAddress(target); - - // Check to see if there are any functions inside the newly added image. - bool newFunctions = false; - for (const auto §Func : view->GetAnalysisFunctionList()) + if (const auto symbol = view.GetSymbolByAddress(symbolAddr)) { - if (section->GetStart() <= sectFunc->GetStart() && sectFunc->GetStart() < section->GetEnd()) - newFunctions = true; + // A symbol already exists at the source location. Add a stub symbol at `targetLocation` based on the existing symbol. + const auto id = view.BeginUndoActions(); + if (auto targetFunc = view.GetAnalysisFunction(view.GetDefaultPlatform(), stubFuncAddr)) + view.DefineUserSymbol(new Symbol(FunctionSymbol, STUB_PREFIX + symbol->GetShortName(), stubFuncAddr)); + else + view.DefineUserSymbol(new Symbol(symbol->GetType(), STUB_PREFIX + symbol->GetShortName(), stubFuncAddr)); + view.ForgetUndoActions(id); + return; } - - // If there are any new functions we should re-analyze the current function. - if (newFunctions) - func->Reanalyze(); } - workflowState->imageLoadMutex.unlock(); -} - + // No existing symbol located, try and search through the symbols of the cache. + auto symbol = controller.GetSymbolAt(symbolAddr); + if (!symbol.has_value()) + return; -void SharedCacheWorkflow::ProcessOffImageCall(Ref<AnalysisContext> ctx, Ref<SharedCache> cache, Ref<Function> func, - Ref<MediumLevelILFunction> mssa, const MediumLevelILInstruction dest, - bool applySymbolIfFoundToCurrentFunction) -{ - auto bv = func->GetView(); - WorkerPriorityEnqueue([bv=std::move(bv), cache=std::move(cache), dest=dest, func=func, applySymbolIfFoundToCurrentFunction](){ - auto workflowState = GetGlobalWorkflowState(bv); - if (dest.operation != MLIL_CONST_PTR && dest.operation != MLIL_CONST) - return; - if (workflowState->autoLoadStubsAndDyldData && - (cache->GetNameForAddress(dest.GetConstant()).find("dyld_shared_cache_branch_islands") != std::string::npos - || cache->GetNameForAddress(dest.GetConstant()).find("::_stubs") != std::string::npos - ) - ) + // NOTE: The type library name is expected to be the image name currently. + // Try and pull the type from the associated type library (if there is one) + Ref<Type> type = nullptr; + if (const auto image = controller.GetImageContaining(symbolAddr)) + if (auto typeLib = TypeLibraryFromName(view, image->name)) + type = view.ImportTypeLibraryObject(typeLib, {symbol->name}); - { - if (cache->LoadSectionAtAddress(dest.GetConstant())) - { - func->Reanalyze(); - } - } - else - { - if (applySymbolIfFoundToCurrentFunction) - cache->FindSymbolAtAddrAndApplyToAddr(dest.GetConstant(), func->GetStart(), false); - else - cache->FindSymbolAtAddrAndApplyToAddr(dest.GetConstant(), dest.GetConstant(), false); - } - }); + // Define the symbol and type (if found) + auto targetFunc = view.GetAnalysisFunction(view.GetDefaultPlatform(), stubFuncAddr); + if (type && targetFunc) + targetFunc->SetUserType(type); + // TODO: When to reanalysis function (mark updates required?) + view.DefineUserSymbol(new Symbol(symbol->type, STUB_PREFIX + symbol->name, stubFuncAddr)); } +// Loads the associated image or region for the specified target address. Returning if it was loaded or not. +bool TryLoadTarget(BinaryView& view, SharedCacheController& controller, const uint64_t target) { + if (const auto image = controller.GetImageContaining(target)) + return controller.ApplyImage(view, *image); + // Failed to find image, try and apply region instead. + if (const auto region = controller.GetRegionContaining(target)) + return controller.ApplyRegion(view, *region); + return false; +}; -void SharedCacheWorkflow::FixupStubs(Ref<AnalysisContext> ctx) +void FixupStubs(Ref<AnalysisContext> ctx) { - try - { - const auto func = ctx->GetFunction(); - const auto arch = func->GetArchitecture(); + const auto func = ctx->GetFunction(); + const auto view = func->GetView(); + const auto mlil = ctx->GetMediumLevelILFunction(); + if (!mlil) + return; + const auto mssa = mlil->GetSSAForm(); + if (!mssa) + return; - const auto bv = func->GetView(); + auto workflowState = GetGlobalWorkflowState(view); + auto controller = SharedCacheController::GetController(*view); + if (!controller) + return; - auto workflowState = GetGlobalWorkflowState(bv); + // Get the containing section for section specific tasks. + auto funcStart = func->GetStart(); + auto sections = view->GetSectionsAt(funcStart); + if (sections.empty()) + return; + const auto& section = sections.front(); + const auto sectionName = section->GetName(); - auto funcStart = func->GetStart(); - auto sections = bv->GetSectionsAt(funcStart); - if (sections.empty()) + // Load the target region if applicable and then trigger re-analysis for all functions in our section. + auto processStubImageCall = [&](const uint64_t target) { + // TODO: If this fails that doesn't necessarily mean we are duplicating work and thus allowing early return... + if (!workflowState->loadMutex.try_lock()) return; - // Just get the first section - const auto& section = sections.front(); - const auto mlil = ctx->GetMediumLevelILFunction(); - if (!mlil) - return; - const auto mssa = mlil->GetSSAForm(); - if (!mssa) - return; + if (!view->IsValidOffset(target) && TryLoadTarget(*view, *controller, target)) + { + // Update all the functions inside our current activities function section. + for (const auto §Func : view->GetAnalysisFunctionList()) + if (section->GetStart() <= sectFunc->GetStart() && sectFunc->GetStart() < section->GetEnd()) + sectFunc->Reanalyze(); + } - Ref<SharedCache> cache = new SharedCache(bv); - if (cache->m_object == nullptr) - return; + workflowState->loadMutex.unlock(); + }; - // Processor that automatically loads the libObjC image when it encounters a stub (so we can do inlining). - if (workflowState->autoLoadObjCStubRequirements && section->GetName().find("__objc_stubs") != std::string::npos) + // TODO: Split this out into another function. + // Processor that automatically loads the libObjC image when it encounters a stub (so we can do inlining). + if (workflowState->autoLoadObjCStubRequirements && sectionName.find("__objc_stubs") != std::string::npos) + { + auto firstInstruction = mlil->GetInstruction(0); + if (firstInstruction.operation == MLIL_TAILCALL) { - auto firstInstruction = mlil->GetInstruction(0); - if (firstInstruction.operation == MLIL_TAILCALL) - { - auto dest = firstInstruction.GetDestExpr<MLIL_TAILCALL>(); - if (dest.operation == MLIL_CONST_PTR) - { - // We're ready, everything is here - func->SetAutoInlinedDuringAnalysis(true); - return; - } - } - for (const auto& block : mssa->GetBasicBlocks()) + auto dest = firstInstruction.GetDestExpr<MLIL_TAILCALL>(); + if (dest.operation == MLIL_CONST_PTR) { - for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) - { - auto instr = mssa->GetInstruction(i); - if (instr.operation == MLIL_JUMP) - { - if (instr.GetDestExpr<MLIL_JUMP>().operation == MLIL_VAR_SSA) - { - auto dest = instr.GetDestExpr<MLIL_JUMP>(); - auto value = mssa->GetSSAVarValue(dest.GetSourceSSAVariable()); - if (value.state == UndeterminedValue) - { - auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable()); - auto defInstr = mssa->GetInstruction(def); - auto targetOffset = defInstr.GetSourceExpr().GetSourceExpr().GetConstant(); - ProcessStubImageCall(workflowState, cache, func, section, targetOffset); - } - } - else if (instr.GetDestExpr<MLIL_JUMP>().operation == MLIL_CONST_PTR) - { - auto dest = instr.GetDestExpr<MLIL_JUMP>(); - auto targetOffset = dest.GetConstant(); - ProcessStubImageCall(workflowState, cache, func, section, targetOffset); - } - } - } + // We're ready, everything is here + func->SetAutoInlinedDuringAnalysis(true); + return; } - - return; } - if (section->GetName().find("::_stubs") != std::string::npos // Branch Islands (iOS 16) - || section->GetName().find("dyld_shared_cache_branch_islands") != std::string::npos // Branch Islands (iOS 11-?) - || section->GetName().find("::__stubs") != std::string::npos // Stubs (non arm64e) - || section->GetName().find("::__auth_stubs") != std::string::npos // Stubs (arm64e) - ) + for (const auto& block : mssa->GetBasicBlocks()) { - auto firstInstruction = mlil->GetInstruction(0); - if (firstInstruction.operation == MLIL_TAILCALL) - { - auto dest = firstInstruction.GetDestExpr<MLIL_TAILCALL>(); - if (dest.operation == MLIL_CONST_PTR) - { - if (auto symbol = bv->GetSymbolByAddress(dest.GetConstant())) - { - auto newSymbol = new Symbol(FunctionSymbol, "j_" + symbol->GetRawName(), func->GetStart()); - bv->DefineUserSymbol(newSymbol); - } - } - } - else if (firstInstruction.operation == MLIL_JUMP) + for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) { - auto dest = firstInstruction.GetDestExpr<MLIL_JUMP>(); - if (dest.operation == MLIL_CONST_PTR) - { - if (!bv->IsValidOffset(dest.GetConstant())) - { - ProcessOffImageCall(ctx, cache, func, mssa, dest, true); - } - } - - else if (dest.operation == MLIL_LOAD) + auto instr = mssa->GetInstruction(i); + if (instr.operation == MLIL_JUMP) { - if (dest.GetSourceExpr().operation == MLIL_CONST_PTR) + if (instr.GetDestExpr<MLIL_JUMP>().operation == MLIL_VAR_SSA) { - dest = dest.GetSourceExpr(); - if (!bv->IsValidOffset(dest.GetConstant())) + auto dest = instr.GetDestExpr<MLIL_JUMP>(); + auto value = mssa->GetSSAVarValue(dest.GetSourceSSAVariable()); + if (value.state == UndeterminedValue) { - ProcessOffImageCall(ctx, cache, func, mssa, dest); + auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable()); + auto defInstr = mssa->GetInstruction(def); + auto targetOffset = defInstr.GetSourceExpr().GetSourceExpr().GetConstant(); + processStubImageCall(targetOffset); } } - } - } - else - { - for (const auto& block : mssa->GetBasicBlocks()) - { - for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) + else if (instr.GetDestExpr<MLIL_JUMP>().operation == MLIL_CONST_PTR) { - auto instr = mssa->GetInstruction(i); - if (instr.operation == MLIL_JUMP) - { - if (instr.GetDestExpr<MLIL_JUMP>().operation == MLIL_VAR_SSA) - { - auto dest = instr.GetDestExpr<MLIL_JUMP>(); - auto value = mssa->GetSSAVarValue(dest.GetSourceSSAVariable()); - if (value.state == UndeterminedValue) - { - auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable()); - auto defInstr = mssa->GetInstruction(def); - auto targetOffset = defInstr.GetSourceExpr().GetSourceExpr().GetConstant(); - ProcessStubImageCall(workflowState, cache, func, section, targetOffset); - } - } - } + auto dest = instr.GetDestExpr<MLIL_JUMP>(); + auto targetOffset = dest.GetConstant(); + processStubImageCall(targetOffset); } } } - - return; } + return; + } + + // TODO: Split this out into another function. + // If this is a stub function we should map in the called region / image. + // NOTE: We cant use the region type here as these sections will be found under larger image region + // "_stubs" => Branch Islands / Stubs (iOS 16 / macOs) + // "dyld_shared_cache_branch_islands" => Branch Islands (iOS 11-?) + if (sectionName.rfind("_stubs") != std::string::npos || sectionName.rfind("dyld_shared_cache_branch_islands") != std::string::npos) + { + // Stage 0: Load the jumped to region, so that x16 is resolved. + // 0 @ 180359b58 (MLIL_SET_VAR.q x16 = (MLIL_LOAD.q [(MLIL_CONST_PTR.q &data_1eacf40f8)].q)) + // 1 @ 180359b5c (MLIL_JUMP jump((MLIL_VAR.q x16))) for (const auto& block : mssa->GetBasicBlocks()) { for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) { auto instr = mssa->GetInstruction(i); - if (instr.operation == MLIL_CALL_SSA) + if (instr.operation == MLIL_JUMP) { - if (instr.GetDestExpr<MLIL_CALL_SSA>().operation == MLIL_CONST_PTR) + if (instr.GetDestExpr<MLIL_JUMP>().operation == MLIL_VAR_SSA) { - auto dest = instr.GetDestExpr<MLIL_CALL_SSA>(); - if (!bv->IsValidOffset(dest.GetConstant())) + auto dest = instr.GetDestExpr<MLIL_JUMP>(); + auto value = mssa->GetSSAVarValue(dest.GetSourceSSAVariable()); + if (value.state == UndeterminedValue) { - ProcessOffImageCall(ctx, cache, func, mssa, dest); + auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable()); + auto defInstr = mssa->GetInstruction(def); + auto targetOffset = defInstr.GetSourceExpr().GetSourceExpr().GetConstant(); + processStubImageCall(targetOffset); } } } } } } - catch (...) - {} } - -static constexpr auto workflowInfo = R"({ - "title": "Shared Cache Workflow", - "description": "Shared Cache Workflow", - "capabilities": [] -})"; - - -void fixObjCCallTypes(Ref<AnalysisContext> ctx) +// TODO: FixupOffImageAccess +void FixupOffImageCalls(Ref<AnalysisContext> ctx) { const auto func = ctx->GetFunction(); - const auto arch = func->GetArchitecture(); - const auto bv = func->GetView(); - - const auto llil = ctx->GetLowLevelILFunction(); - if (!llil) { + const auto view = func->GetView(); + const auto mlil = ctx->GetMediumLevelILFunction(); + if (!mlil) return; - } - const auto ssa = llil->GetSSAForm(); - if (!ssa) { + const auto mssa = mlil->GetSSAForm(); + if (!mssa) return; - } - const auto rewriteIfEligible = [bv, ssa](size_t insnIndex) { - auto insn = ssa->GetInstruction(insnIndex); + auto workflowState = GetGlobalWorkflowState(view); + auto controller = SharedCacheController::GetController(*view); + if (!controller) + return; - if (insn.operation == LLIL_CALL_SSA) - { - // Filter out calls that aren't to `objc_msgSend`. - auto callExpr = insn.GetDestExpr<LLIL_CALL_SSA>(); - bool isMessageSend = false; - if (auto symbol = bv->GetSymbolByAddress(callExpr.GetValue().value)) - isMessageSend = symbol->GetRawName() == "_objc_msgSend"; - if (!isMessageSend) - return; + auto processOffImageCall = [&](const uint64_t stubFuncAddr, const uint64_t symbolAddr) { + const auto region = controller->GetRegionContaining(symbolAddr); + if (!region.has_value()) + return; - const auto llil = ssa->GetNonSSAForm(); - const auto insn = ssa->GetInstruction(insnIndex); - const auto params = insn.GetParameterExprs<LLIL_CALL_SSA>(); + // Load stub region if not already loaded and reanalyze the function (to pickup stub functions) + if (workflowState->autoLoadStubsAndDyldData && region->type == SharedCacheRegionTypeStubIsland) + if (controller->ApplyRegion(*view, *region)) + func->Reanalyze(); // TODO: Call mark updates required instead??? Use incremental update type instead??? - // The second parameter passed to the objc_msgSend call is the address of - // either the selector reference or the method's name, which in both cases - // is dereferenced to retrieve a selector. - if (params.size() < 2) - return; - uint64_t rawSelector = 0; - if (params[1].operation == LLIL_REG_SSA) - { - const auto selectorRegister = params[1].GetSourceSSARegister<LLIL_REG_SSA>(); - rawSelector = ssa->GetSSARegisterValue(selectorRegister).value; - } - else if (params[0].operation == LLIL_SEPARATE_PARAM_LIST_SSA) + // TODO: Is there a point to this at all???? + // TODO: is there a point to calling this if workflowState->autoLoadStubsAndDyldData is not on??? + // Apply symbols (and possibly a type) to the relevant stub functions + IdentifyStub(*view, *controller, stubFuncAddr, symbolAddr); + }; + + // Load all unmapped STUB regions / images that are called in this function. + for (const auto& block : mssa->GetBasicBlocks()) + { + for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) + { + auto instr = mssa->GetInstruction(i); + if (instr.operation == MLIL_CALL_SSA) { - if (params[0].GetParameterExprs<LLIL_SEPARATE_PARAM_LIST_SSA>().size() == 0) + if (instr.GetDestExpr<MLIL_CALL_SSA>().operation == MLIL_CONST_PTR) { - return; + auto targetAddr = instr.GetDestExpr<MLIL_CALL_SSA>().GetConstant(); + if (!view->IsValidOffset(targetAddr)) + { + processOffImageCall(targetAddr, targetAddr); + } } - const auto selectorRegister = params[0].GetParameterExprs<LLIL_SEPARATE_PARAM_LIST_SSA>()[1].GetSourceSSARegister<LLIL_REG_SSA>(); - rawSelector = ssa->GetSSARegisterValue(selectorRegister).value; } - if (!rawSelector || !bv->IsValidOffset(rawSelector)) - return; - - // -- Do callsite override - auto reader = BinaryNinja::BinaryReader(bv); - reader.Seek(rawSelector); - auto selector = reader.ReadCString(500); - auto additionalArgumentCount = std::count(selector.begin(), selector.end(), ':'); - - auto retType = bv->GetTypeByName({ "id" }); - if (!retType) - retType = BinaryNinja::Type::PointerType(ssa->GetArchitecture(), BinaryNinja::Type::VoidType()); - - std::vector<BinaryNinja::FunctionParameter> callTypeParams; - auto cc = bv->GetDefaultPlatform()->GetDefaultCallingConvention(); - - callTypeParams.push_back({"self", retType, true, BinaryNinja::Variable()}); - - auto selType = bv->GetTypeByName({ "SEL" }); - if (!selType) - selType = BinaryNinja::Type::PointerType(ssa->GetArchitecture(), BinaryNinja::Type::IntegerType(1, true)); - callTypeParams.push_back({"sel", selType, true, BinaryNinja::Variable()}); - - std::vector<std::string> selectorComponents = splitSelector(selector); - std::vector<std::string> argumentNames = generateArgumentNames(selectorComponents); - - for (size_t i = 0; i < additionalArgumentCount; i++) + else if (instr.operation == MLIL_JUMP) { - auto argType = BinaryNinja::Type::IntegerType(bv->GetAddressSize(), true); - if (argumentNames.size() > i && !argumentNames[i].empty()) - callTypeParams.push_back({argumentNames[i], argType, true, BinaryNinja::Variable()}); - else - callTypeParams.push_back({"arg" + std::to_string(i), argType, true, BinaryNinja::Variable()}); + auto destExpr = instr.GetDestExpr<MLIL_JUMP>(); + if (destExpr.operation == MLIL_VAR_SSA) + { + // 1 @ 180359b5c (MLIL_JUMP jump((MLIL_VAR.q x16))) + auto dest = instr.GetDestExpr<MLIL_JUMP>(); + auto value = mssa->GetSSAVarValue(dest.GetSourceSSAVariable()); + if (value.state == UndeterminedValue) + { + auto def = mssa->GetSSAVarDefinition(dest.GetSourceSSAVariable()); + auto defInstr = mssa->GetInstruction(def); + if (defInstr.operation == MLIL_SET_VAR_SSA) + { + // 0 @ 180359b58 (MLIL_SET_VAR.q x16 = (MLIL_LOAD.q [(MLIL_CONST_PTR.q &data_1eacf40f8)].q)) + auto loadExpr = defInstr.GetSourceExpr<MLIL_SET_VAR_SSA>(); + if (loadExpr.operation == MLIL_LOAD_SSA) + { + auto ptrExpr = loadExpr.GetSourceExpr<MLIL_LOAD_SSA>(); + if (ptrExpr.operation == MLIL_CONST_PTR) + { + auto targetAddr = ptrExpr.GetConstant(); + if (!view->IsValidOffset(targetAddr)) + { + processOffImageCall(targetAddr, targetAddr); + } + } + } + } + } + else if (destExpr.operation == MLIL_CONST_PTR) + { + // 4 @ 18aa08208 (MLIL_JUMP jump((MLIL_CONST_PTR.q 0x18c1369f0))) + auto targetAddr = destExpr.GetConstant(); + if (!view->IsValidOffset(targetAddr)) + { + processOffImageCall(targetAddr, targetAddr); + } + } + else if (destExpr.operation == MLIL_LOAD_SSA) + { + auto ptrExpr = destExpr.GetSourceExpr<MLIL_LOAD_SSA>(); + if (ptrExpr.operation == MLIL_CONST_PTR) + { + auto targetAddr = ptrExpr.GetConstant(); + if (!view->IsValidOffset(targetAddr)) + { + processOffImageCall(targetAddr, targetAddr); + } + } + } + } } - - auto funcType = BinaryNinja::Type::FunctionType(retType, cc, callTypeParams); - ssa->GetFunction()->SetAutoCallTypeAdjustment(ssa->GetFunction()->GetArchitecture(), insn.address, {funcType, BN_DEFAULT_CONFIDENCE}); - // -- + // TODO: Check all instructions for accesses to select region types (stub etc...) + // TODO: ^ we actually dont really need to do this, the other type of access cont.. + // TODO: the other two types of accesses (load & save) we dont want to load their regions, just + // TODO: their symbol information if available. } - }; - - for (const auto& block : ssa->GetBasicBlocks()) - for (size_t i = block->GetStart(), end = block->GetEnd(); i < end; ++i) - rewriteIfEligible(i); + } } - +static constexpr auto WORKFLOW_DESCRIPTION = R"({ + "title": "Shared Cache Workflow", + "description": "Shared Cache Workflow", + "capabilities": [] +})"; void SharedCacheWorkflow::Register() { - Ref<Workflow> wf = BinaryNinja::Workflow::Instance("core.function.baseAnalysis")->Clone("core.function.dsc"); - wf->RegisterActivity(new BinaryNinja::Activity("core.analysis.dscstubs", &SharedCacheWorkflow::FixupStubs)); - wf->RegisterActivity(new BinaryNinja::Activity("core.analysis.fixObjCCallTypes", &fixObjCCallTypes)); - wf->Insert("core.function.analyzeTailCalls", "core.analysis.fixObjCCallTypes"); - wf->Insert("core.function.analyzeTailCalls", "core.analysis.dscstubs"); + Ref<Workflow> workflow = Workflow::Instance("core.function.baseAnalysis")->Clone("core.function.sharedCache"); + + // Register and insert activities here. + ObjCActivity::Register(*workflow); + workflow->RegisterActivity(new Activity("core.analysis.sharedCache.stubs", &FixupStubs)); + workflow->RegisterActivity(new Activity("core.analysis.sharedCache.calls", &FixupOffImageCalls)); + workflow->Insert("core.function.analyzeTailCalls", "core.analysis.sharedCache.stubs"); + workflow->Insert("core.function.analyzeTailCalls", "core.analysis.sharedCache.calls"); - BinaryNinja::Workflow::RegisterWorkflow(wf, workflowInfo); + Workflow::RegisterWorkflow(workflow, WORKFLOW_DESCRIPTION); } extern "C" diff --git a/view/sharedcache/workflow/SharedCacheWorkflow.h b/view/sharedcache/workflow/SharedCacheWorkflow.h index 528c69f4..fa94f513 100644 --- a/view/sharedcache/workflow/SharedCacheWorkflow.h +++ b/view/sharedcache/workflow/SharedCacheWorkflow.h @@ -1,14 +1,8 @@ -#ifndef SHAREDCACHE_SHAREDCACHEWORKFLOW_H -#define SHAREDCACHE_SHAREDCACHEWORKFLOW_H - -#include "binaryninjaapi.h" -#include "view/sharedcache/api/sharedcacheapi.h" +#pragma once class SharedCacheWorkflow { public: - static void ProcessOffImageCall(Ref<AnalysisContext> ctx, Ref<SharedCacheAPI::SharedCache> cache, Ref<Function> func, Ref<MediumLevelILFunction> il, const MediumLevelILInstruction instr, bool applySymbolIfFoundToCurrentFunction = false); - static void FixupStubs(Ref<AnalysisContext> ctx); static void Register(); }; @@ -19,5 +13,3 @@ extern "C" { #ifdef __cplusplus } #endif - -#endif //SHAREDCACHE_SHAREDCACHEWORKFLOW_H |
