From 25cc02431b61097b2adfc2fbc493b648b0300c3b Mon Sep 17 00:00:00 2001 From: Mason Reed Date: Mon, 10 Mar 2025 11:05:40 -0400 Subject: [SharedCache] Refactor Shared Cache In absence of a better name, this commit refactors the shared cache code. --- view/sharedcache/CMakeLists.txt | 1 + view/sharedcache/HeadlessPlugin.cpp | 8 +- view/sharedcache/api/python/sharedcache.py | 646 ++- view/sharedcache/api/python/sharedcache_enums.py | 44 +- view/sharedcache/api/sharedcache.cpp | 545 ++- view/sharedcache/api/sharedcacheapi.h | 516 +-- view/sharedcache/api/sharedcachecore.h | 210 +- view/sharedcache/core/CMakeLists.txt | 2 +- view/sharedcache/core/DSCView.cpp | 771 ---- view/sharedcache/core/DSCView.h | 40 - view/sharedcache/core/Dyld.h | 267 ++ view/sharedcache/core/FileAccessorCache.cpp | 112 + view/sharedcache/core/FileAccessorCache.h | 84 + view/sharedcache/core/MachO.cpp | 667 +++ view/sharedcache/core/MachO.h | 76 + view/sharedcache/core/MachOProcessor.cpp | 320 ++ view/sharedcache/core/MachOProcessor.h | 23 + view/sharedcache/core/MappedFile.cpp | 193 + view/sharedcache/core/MappedFile.h | 86 + view/sharedcache/core/MappedFileAccessor.cpp | 101 + view/sharedcache/core/MappedFileAccessor.h | 103 + view/sharedcache/core/MetadataSerializable.cpp | 552 --- view/sharedcache/core/MetadataSerializable.hpp | 254 -- view/sharedcache/core/ObjC.cpp | 113 +- view/sharedcache/core/ObjC.h | 65 +- view/sharedcache/core/SharedCache.cpp | 4296 +++----------------- view/sharedcache/core/SharedCache.h | 853 +--- view/sharedcache/core/SharedCacheController.cpp | 261 ++ view/sharedcache/core/SharedCacheController.h | 65 + view/sharedcache/core/SharedCacheView.cpp | 854 ++++ view/sharedcache/core/SharedCacheView.h | 43 + view/sharedcache/core/SlideInfo.cpp | 292 ++ view/sharedcache/core/SlideInfo.h | 42 + view/sharedcache/core/Utility.cpp | 131 + view/sharedcache/core/Utility.h | 106 + view/sharedcache/core/VM.cpp | 891 ---- view/sharedcache/core/VM.h | 368 -- view/sharedcache/core/VirtualMemory.cpp | 375 ++ view/sharedcache/core/VirtualMemory.h | 157 + view/sharedcache/core/ffi.cpp | 429 ++ view/sharedcache/core/ffi_global.h | 41 + view/sharedcache/core/refcountobject.h | 223 + view/sharedcache/tests/validate_images.py | 94 + view/sharedcache/ui/SharedCacheBDNotifications.cpp | 69 - view/sharedcache/ui/SharedCacheBDNotifications.h | 20 - view/sharedcache/ui/SharedCacheUINotifications.cpp | 229 +- view/sharedcache/ui/dscpicker.cpp | 77 +- view/sharedcache/ui/dsctriage.cpp | 1145 ++---- view/sharedcache/ui/dsctriage.h | 188 +- view/sharedcache/ui/symboltable.cpp | 129 + view/sharedcache/ui/symboltable.h | 87 + view/sharedcache/workflow/ObjCActivity.cpp | 129 + view/sharedcache/workflow/ObjCActivity.h | 9 + view/sharedcache/workflow/SharedCacheWorkflow.cpp | 570 ++- view/sharedcache/workflow/SharedCacheWorkflow.h | 10 +- 55 files changed, 8054 insertions(+), 9928 deletions(-) delete mode 100644 view/sharedcache/core/DSCView.cpp delete mode 100644 view/sharedcache/core/DSCView.h create mode 100644 view/sharedcache/core/Dyld.h create mode 100644 view/sharedcache/core/FileAccessorCache.cpp create mode 100644 view/sharedcache/core/FileAccessorCache.h create mode 100644 view/sharedcache/core/MachO.cpp create mode 100644 view/sharedcache/core/MachO.h create mode 100644 view/sharedcache/core/MachOProcessor.cpp create mode 100644 view/sharedcache/core/MachOProcessor.h create mode 100644 view/sharedcache/core/MappedFile.cpp create mode 100644 view/sharedcache/core/MappedFile.h create mode 100644 view/sharedcache/core/MappedFileAccessor.cpp create mode 100644 view/sharedcache/core/MappedFileAccessor.h delete mode 100644 view/sharedcache/core/MetadataSerializable.cpp delete mode 100644 view/sharedcache/core/MetadataSerializable.hpp create mode 100644 view/sharedcache/core/SharedCacheController.cpp create mode 100644 view/sharedcache/core/SharedCacheController.h create mode 100644 view/sharedcache/core/SharedCacheView.cpp create mode 100644 view/sharedcache/core/SharedCacheView.h create mode 100644 view/sharedcache/core/SlideInfo.cpp create mode 100644 view/sharedcache/core/SlideInfo.h create mode 100644 view/sharedcache/core/Utility.cpp create mode 100644 view/sharedcache/core/Utility.h delete mode 100644 view/sharedcache/core/VM.cpp delete mode 100644 view/sharedcache/core/VM.h create mode 100644 view/sharedcache/core/VirtualMemory.cpp create mode 100644 view/sharedcache/core/VirtualMemory.h create mode 100644 view/sharedcache/core/ffi.cpp create mode 100644 view/sharedcache/core/ffi_global.h create mode 100644 view/sharedcache/core/refcountobject.h create mode 100644 view/sharedcache/tests/validate_images.py delete mode 100644 view/sharedcache/ui/SharedCacheBDNotifications.cpp delete mode 100644 view/sharedcache/ui/SharedCacheBDNotifications.h create mode 100644 view/sharedcache/ui/symboltable.cpp create mode 100644 view/sharedcache/ui/symboltable.h create mode 100644 view/sharedcache/workflow/ObjCActivity.cpp create mode 100644 view/sharedcache/workflow/ObjCActivity.h (limited to 'view/sharedcache') 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 -#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"" - - @dataclasses.dataclass -class LoadedRegion: - name: str - headerAddress: int - mappings: list[DSCMemoryMapping] - - def __str__(self): - return repr(self) - - def __repr__(self): - return f"" - - -@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"" @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"" - - -@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": {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"" + def __str__(self): + return repr(self) + def __repr__(self): + return f"" @dataclasses.dataclass -class DSCSymbol: - name: str - image: str - address: int - - def __str__(self): - return repr(self) - - def __repr__(self): - return f"" - - -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 [] - - 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 - )) - - sccore.BNDSCViewFreeAllImages(value, count) - return result - - @property - def loaded_regions(self) -> list[LoadedRegion]: - """ - Get all loaded regions in the shared cache - - 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. - - Individual segments within an image can be loaded independently of the image itself. - - 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 [] - - 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 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. - - 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. - - This may take several seconds if this is the first time this function is called. Subsequent calls will be faster. - - 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. - - :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) - - sccore.BNDSCViewFreeSymbols(value, count) - return result - - @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 [] - - result = [] - for i in range(count.value): - result.append(value[i].decode('utf-8')) - - BNFreeStringList(value, count) - 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 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. - - If this lies within an image segment, this will be in the format image_name + "::" + segment_name. - - It may also be the name of a branch pool or other non-image region. - - This is the API call utilized on the first dynamic entry in the right-click context menu. - - :param address: address to check - :return: - """ - name = sccore.BNDSCViewGetNameForAddress(self.handle, address) - if name is None: - return "" - result = name - return result - - def get_image_name_for_address(self, address) -> str: - """ - Return the install name for the image containing the provided address. - - If the address is not within an image, this will return an empty string. - - This is the API call used in the second dynamic entry in the right-click context menu. - - :param address: address to check - :return: - """ - name = sccore.BNDSCViewGetImageNameForAddress(self.handle, address) - if name is None: - return "" - result = name - 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. - - This is currently a blocking function. - - 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. - - 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 - - :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) +class CacheSymbol: + symbol_type: sccore.SymbolTypeEnum + address: int + name: str + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"" + +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 + ) + +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 + ) + +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 + ) + +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 + ) + +def symbol_from_api(symbol: sccore.BNSharedCacheSymbol) -> CacheSymbol: + return CacheSymbol( + symbol_type=symbol.symbolType, + address=symbol.address, + name=symbol.name + ) + +def symbol_to_api(symbol: CacheSymbol) -> sccore.BNSharedCacheSymbol: + return sccore.BNSharedCacheSymbol( + symbolType=symbol.symbol_type, + address=symbol.address, + _name=BNAllocString(symbol.name) + ) + +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) + + def __del__(self): + if self.handle is not None: + sccore.BNFreeSharedCacheControllerReference(self.handle) + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"" + + def is_valid(self) -> bool: + return self.handle is not None + + 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 + + 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 + + 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 + + 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_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 + + 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 + + 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 + + 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 + + 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_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 + + @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 + + @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 + + @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 + + @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 + + @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 + + +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 + + +binaryninja.PythonScriptingProvider.register_magic_variable( + "dsc", + _get_shared_cache +) + +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 { - - SharedCache::SharedCache(Ref view) { - m_object = BNGetSharedCache(view->GetObject()); - } - - BNDSCViewLoadProgress SharedCache::GetLoadProgress(Ref view) - { - return BNDSCViewGetLoadProgress(view->GetFile()->GetSessionId()); - } - - uint64_t SharedCache::FastGetBackingCacheCount(Ref view) - { - return BNDSCViewFastGetBackingCacheCount(view->GetObject()); - } - - bool SharedCache::LoadImageWithInstallName(std::string installName, bool skipObjC) - { - char* str = BNAllocString(installName.c_str()); - return BNDSCViewLoadImageWithInstallName(m_object, str, skipObjC); - } - - bool SharedCache::LoadSectionAtAddress(uint64_t addr) - { - return BNDSCViewLoadSectionAtAddress(m_object, addr); - } - - bool SharedCache::LoadImageContainingAddress(uint64_t addr, bool skipObjC) - { - return BNDSCViewLoadImageContainingAddress(m_object, addr, skipObjC); - } - - std::vector SharedCache::GetAvailableImages() - { - size_t count; - char** value = BNDSCViewGetInstallNames(m_object, &count); - if (value == nullptr) - { - return {}; - } - - std::vector result; - for (size_t i = 0; i < count; i++) - { - result.push_back(value[i]); - } - - BNFreeStringList(value, count); - return result; - } - - void SharedCache::ProcessObjCSectionsForImageWithInstallName(std::string installName) - { - char* str = BNAllocString(installName.c_str()); - BNDSCViewProcessObjCSectionsForImageWithInstallName(m_object, str, true); - } - - void SharedCache::ProcessAllObjCSections() - { - BNDSCViewProcessAllObjCSections(m_object); - } - - std::vector SharedCache::GetLoadedMemoryRegions() +using namespace BinaryNinja; +using namespace SharedCacheAPI; + +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; +} + +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; +} + +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; +} + +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; +} + +BNSharedCacheMappingInfo MappingToApi(const CacheMappingInfo &mapping) +{ + BNSharedCacheMappingInfo apiMapping; + apiMapping.vmAddress = mapping.vmAddress; + apiMapping.size = mapping.size; + apiMapping.fileOffset = mapping.fileOffset; + return apiMapping; +} + +CacheMappingInfo MappingFromApi(BNSharedCacheMappingInfo apiMapping) +{ + CacheMappingInfo mapping; + mapping.vmAddress = apiMapping.vmAddress; + mapping.size = apiMapping.size; + mapping.fileOffset = apiMapping.fileOffset; + return mapping; +} + +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; +} + +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; +} + +CacheSymbol SymbolFromApi(BNSharedCacheSymbol apiSymbol) +{ + CacheSymbol symbol; + symbol.name = apiSymbol.name; + symbol.address = apiSymbol.address; + symbol.type = apiSymbol.symbolType; + return symbol; +} + +std::string SharedCacheAPI::GetRegionTypeAsString(const BNSharedCacheRegionType &type) +{ + switch (type) { - size_t count; - BNDSCMappedMemoryRegion* value = BNDSCViewGetLoadedRegions(m_object, &count); - if (value == nullptr) - { - return {}; - } - - std::vector 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); - } - - BNDSCViewFreeLoadedRegions(value, count); - return result; + case SharedCacheRegionTypeImage: + return "Image"; + case SharedCacheRegionTypeStubIsland: + return "StubIsland"; + case SharedCacheRegionTypeDyldData: + return "DyldData"; + case SharedCacheRegionTypeNonImage: + return "NonImage"; + default: + return "Unknown"; } - std::vector SharedCache::GetBackingCaches() - { - size_t count; - BNDSCBackingCache* value = BNDSCViewGetBackingCaches(m_object, &count); - if (value == nullptr) - { - return {}; - } - - std::vector 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); - } - - BNDSCViewFreeBackingCaches(value, count); - return result; - } - - std::vector SharedCache::GetImages() - { - size_t count; - BNDSCImage* value = BNDSCViewGetAllImages(m_object, &count); - if (value == nullptr) - { - return {}; - } - - std::vector 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); - } - - BNDSCViewFreeAllImages(value, count); - return result; - } - - std::vector SharedCache::LoadAllSymbolsAndWait() - { - size_t count; - BNDSCSymbolRep* value = BNDSCViewLoadAllSymbolsAndWait(m_object, &count); - if (value == nullptr) - { - return {}; - } - - std::vector 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); - } - - BNDSCViewFreeSymbols(value, 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::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 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 {}; - - return SharedCacheMachOHeader::LoadFromString(output); - } - - std::optional 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); - } - - BNDSCViewState SharedCache::GetState() - { - return BNDSCViewGetState(m_object); - } - - void SharedCache::FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) const - { - BNDSCFindSymbolAtAddressAndApplyToAddress(m_object, symbolLocation, targetLocation, triggerReanalysis); - } - -} // namespace SharedCacheAPI +} + +SharedCacheController::SharedCacheController(BNSharedCacheController *controller) +{ + m_object = controller; +} + +DSCRef SharedCacheController::GetController(BinaryView &view) +{ + BNSharedCacheController *controller = BNGetSharedCacheController(view.GetObject()); + if (controller == nullptr) + return nullptr; + return new SharedCacheController(controller); +} + +bool SharedCacheController::ApplyRegion(BinaryView &view, const CacheRegion ®ion) +{ + auto apiRegion = RegionToApi(region); + bool result = BNSharedCacheControllerApplyRegion(m_object, view.GetObject(), &apiRegion); + BNSharedCacheFreeRegion(apiRegion); + return result; +} + +bool SharedCacheController::ApplyImage(BinaryView &view, const CacheImage &image) +{ + auto apiImage = ImageToApi(image); + bool result = BNSharedCacheControllerApplyImage(m_object, view.GetObject(), &apiImage); + BNSharedCacheFreeImage(apiImage); + return result; +} + +bool SharedCacheController::IsRegionLoaded(const CacheRegion ®ion) const +{ + auto apiRegion = RegionToApi(region); + bool result = BNSharedCacheControllerIsRegionLoaded(m_object, &apiRegion); + BNSharedCacheFreeRegion(apiRegion); + return result; +} + +bool SharedCacheController::IsImageLoaded(const CacheImage &image) const +{ + auto apiImage = ImageToApi(image); + bool result = BNSharedCacheControllerIsImageLoaded(m_object, &apiImage); + BNSharedCacheFreeImage(apiImage); + return result; +} + +std::optional 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::optional 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; +} + +std::optional 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::optional 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::optional 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; +} + +std::vector SharedCacheController::GetImageDependencies(const CacheImage &image) const +{ + size_t count; + BNSharedCacheImage apiImage = ImageToApi(image); + char **dependencies = BNSharedCacheControllerGetImageDependencies(m_object, &apiImage, &count); + BNSharedCacheFreeImage(apiImage); + std::vector result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(dependencies[i]); + BNFreeStringList(dependencies, count); + return result; +} + +std::optional 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::optional 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::vector SharedCacheController::GetEntries() const +{ + size_t count; + BNSharedCacheEntry *entries = BNSharedCacheControllerGetEntries(m_object, &count); + std::vector result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(EntryFromApi(entries[i])); + BNSharedCacheFreeEntryList(entries, count); + return result; +} + +std::vector SharedCacheController::GetLoadedRegions() const +{ + size_t count; + BNSharedCacheRegion *regions = BNSharedCacheControllerGetLoadedRegions(m_object, &count); + std::vector result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(RegionFromApi(regions[i])); + BNSharedCacheFreeRegionList(regions, count); + return result; +} + +std::vector SharedCacheController::GetRegions() const +{ + size_t count; + BNSharedCacheRegion *regions = BNSharedCacheControllerGetRegions(m_object, &count); + std::vector result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(RegionFromApi(regions[i])); + BNSharedCacheFreeRegionList(regions, count); + return result; +} + +std::vector SharedCacheController::GetImages() const +{ + size_t count; + BNSharedCacheImage *images = BNSharedCacheControllerGetImages(m_object, &count); + std::vector result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(ImageFromApi(images[i])); + BNSharedCacheFreeImageList(images, count); + return result; +} + +std::vector SharedCacheController::GetLoadedImages() const +{ + size_t count; + BNSharedCacheImage *images = BNSharedCacheControllerGetLoadedImages(m_object, &count); + std::vector result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(ImageFromApi(images[i])); + BNSharedCacheFreeImageList(images, count); + return result; +} + +std::vector SharedCacheController::GetSymbols() const +{ + size_t count; + BNSharedCacheSymbol *symbols = BNSharedCacheControllerGetSymbols(m_object, &count); + std::vector 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 -#include "../core/MetadataSerializable.hpp" -#include "view/macho/machoview.h" #include "sharedcachecore.h" -namespace SharedCacheAPI { - template - class SCRefCountObject { - void AddRefInternal() { m_refs.fetch_add(1); } +template +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 m_refs; - T *m_object; +public: + std::atomic 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 SCCoreRefCountObject { - void AddRefInternal() { m_refs.fetch_add(1); } +template +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 m_refs; - bool m_registeredRef = false; - T *m_object; - - SCCoreRefCountObject() : m_refs(0), m_object(nullptr) {} - - virtual ~SCCoreRefCountObject() {} - - T *GetObject() const { return m_object; } - - static T *GetObject(SCCoreRefCountObject *obj) { - if (!obj) - return nullptr; - return obj->GetObject(); + } + +public: + std::atomic 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(); + } + + void AddRefForRegistration() { m_registeredRef = true; } + + void ReleaseForRegistration() { + m_object = nullptr; + m_registeredRef = false; + if (m_refs == 0) + delete this; + } +}; + +template +class DSCRef +{ + T* m_obj; +#ifdef BN_REF_COUNT_DEBUG + void* m_assignmentTrace = nullptr; +#endif + +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& obj) : m_obj(obj.m_obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif } - - void Release() { - if (m_object) - FreeObjectReference(m_object); - ReleaseInternal(); + } + + DSCRef(DSCRef&& other) : m_obj(other.m_obj) + { + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + } + + ~DSCRef() + { + if (m_obj) + { + m_obj->Release(); +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif } - - void AddRefForRegistration() { m_registeredRef = true; } - - void ReleaseForRegistration() { - m_object = nullptr; - m_registeredRef = false; - if (m_refs == 0) - delete this; + } + + DSCRef& operator=(const BinaryNinja::Ref& 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& operator=(DSCRef&& other) + { + if (m_obj) + { +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif + m_obj->Release(); } - }; - - struct DSCMemoryRegion { - uint64_t vmAddress; + m_obj = other.m_obj; + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + return *this; + } + + DSCRef& 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; + } + + bool operator==(const T* obj) const + { + return T::GetObject(m_obj) == T::GetObject(obj); + } + + bool operator==(const DSCRef& obj) const + { + return T::GetObject(m_obj) == T::GetObject(obj.m_obj); + } + + bool operator!=(const T* obj) const + { + return T::GetObject(m_obj) != T::GetObject(obj); + } + + bool operator!=(const DSCRef& obj) const + { + return T::GetObject(m_obj) != T::GetObject(obj.m_obj); + } + + bool operator<(const T* obj) const + { + return T::GetObject(m_obj) < T::GetObject(obj); + } + + bool operator<(const DSCRef& obj) const + { + return T::GetObject(m_obj) < T::GetObject(obj.m_obj); + } + + T* GetPtr() const + { + return m_obj; + } +}; + + + +// TODO: replace namespace? +namespace SharedCacheAPI { + struct CacheRegion + { + BNSharedCacheRegionType type; + std::string name; + uint64_t start; uint64_t size; - std::string prettyName; + std::optional imageStart; + BNSegmentFlag flags; }; - struct BackingCacheMapping { + std::string GetRegionTypeAsString(const BNSharedCacheRegionType& type); + + struct CacheMappingInfo + { uint64_t vmAddress; uint64_t size; uint64_t fileOffset; }; - struct BackingCache { - std::string path; - BNBackingCacheType cacheType; - std::vector mappings; - }; - - struct DSCImageMemoryMapping { - std::string filePath; + struct CacheImage + { + uint64_t headerAddress; std::string name; - uint64_t vmAddress; - uint64_t size; - bool loaded; - uint64_t rawViewOffset; + std::vector regionStarts; }; - struct DSCImage { - std::string name; - uint64_t headerAddress; - std::vector mappings; + struct CacheEntry + { + std::string path; + BNSharedCacheEntryType entryType; + std::vector mappings; }; - struct DSCSymbol { + struct CacheSymbol + { + BNSymbolType type; uint64_t address; - BinaryNinja::StringRef name; - std::string image; - }; - - struct SharedCacheMachOHeader : public SharedCacheCore::MetadataSerializable { - uint64_t textBase = 0; - uint64_t loadCommandOffset = 0; - mach_header_64 ident; - std::string identifierPrefix; - std::string installName; - - std::vector> entryPoints; - std::vector m_entryPoints; // list of entrypoints - - symtab_command symtab; - dysymtab_command dysymtab; - dyld_info_command dyldInfo; - routines_command_64 routines64; - function_starts_command functionStarts; - std::vector moduleInitSections; - linkedit_data_command exportTrie; - 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 segments; // only three types of sections __TEXT, __DATA, __IMPORT - segment_command_64 linkeditSegment; - std::vector sections; - std::vector sectionNames; - - std::vector symbolStubSections; - std::vector symbolPointerSections; - - std::vector dylibs; - - build_version_command buildVersion; - std::vector 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; - - 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); - } - - 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; - } + std::string name; }; - - class SharedCache : public SCCoreRefCountObject { + class SharedCacheController : public DSCCoreRefCountObject { public: - SharedCache(Ref view); + explicit SharedCacheController(BNSharedCacheController* controller); + static DSCRef GetController(BinaryNinja::BinaryView& view); - BNDSCViewState GetState(); - static BNDSCViewLoadProgress GetLoadProgress(Ref view); - static uint64_t FastGetBackingCacheCount(Ref 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 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 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 GetRegionAt(uint64_t address) const; + std::optional GetRegionContaining(uint64_t address) const; - std::vector GetBackingCaches(); - std::vector GetImages(); + std::optional GetImageAt(uint64_t address) const; + std::optional GetImageContaining(uint64_t address) const; + std::optional GetImageWithName(const std::string& name) const; - std::optional GetMachOHeaderForImage(std::string name); - std::optional GetMachOHeaderForAddress(uint64_t address); + std::vector GetImageDependencies(const CacheImage& image) const; - std::vector GetLoadedMemoryRegions(); + std::optional GetSymbolAt(uint64_t address) const; + std::optional GetSymbolWithName(const std::string& name) const; - void FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis = true) const; + std::vector GetEntries() const; + std::vector GetRegions() const; + std::vector GetLoadedRegions() const; + std::vector GetImages() const; + std::vector GetLoadedImages() const; + std::vector 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; - - typedef enum BNDSCViewLoadProgress { - LoadProgressNotStarted, - LoadProgressLoadingCaches, - LoadProgressLoadingImages, - LoadProgressFinished, - } BNDSCViewLoadProgress; - - typedef enum BNBackingCacheType { - BackingCacheTypePrimary, - BackingCacheTypeSecondary, - BackingCacheTypeSymbols, - } BNBackingCacheType; - - typedef struct BNBinaryView BNBinaryView; - typedef struct BNSharedCache BNSharedCache; - typedef struct BNStringRef BNStringRef; +#ifdef __cplusplus +extern "C" +{ +#endif - typedef struct BNDSCImageMemoryMapping { - char* filePath; - char* name; - uint64_t vmAddress; - uint64_t size; - bool loaded; - uint64_t rawViewOffset; - } BNDSCImageMemoryMapping; + // 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 BNSymbolType + { + FunctionSymbol = 0, + ImportAddressSymbol = 1, + ImportedFunctionSymbol = 2, + DataSymbol = 3, + ImportedDataSymbol = 4, + ExternalSymbol = 5, + LibraryFunctionSymbol = 6, + SymbolicFunctionSymbol = 7, + LocalLabelSymbol = 8, + } BNSymbolType; +#endif - typedef struct BNDSCImage { + typedef struct BNBinaryView BNBinaryView; + typedef struct BNSharedCacheController BNSharedCacheController; + + typedef enum BNSharedCacheEntryType { + SharedCacheEntryTypePrimary, + SharedCacheEntryTypeSecondary, + SharedCacheEntryTypeSymbols, + SharedCacheEntryTypeDyldData, + SharedCacheEntryTypeStub, + } BNSharedCacheEntryType; + + 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; + BNSharedCacheMappingInfo* mappings; + } BNSharedCacheEntry; - typedef struct BNDSCMemoryUsageInfo { - uint64_t sharedCacheRefs; - uint64_t mmapRefs; - } BNDSCMemoryUsageInfo; - - 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.cpp b/view/sharedcache/core/DSCView.cpp deleted file mode 100644 index add2faff..00000000 --- a/view/sharedcache/core/DSCView.cpp +++ /dev/null @@ -1,771 +0,0 @@ -// -// Created by kat on 5/23/23. -// - -/* - * 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 - * - * */ - -#include "DSCView.h" -#include "view/macho/machoview.h" -#include "SharedCache.h" - -using namespace BinaryNinja; - - -DSCView::DSCView(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 { - 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. - // DSCPlatformMacCatalyst = 6, - DSCPlatformiOSSimulator = 7, - DSCPlatformTVOSSimulator = 8, - DSCPlatformWatchOSSimulator = 9, - DSCPlatformVisionOS = 11, // Apple Vision Pro - DSCPlatformVisionOSSimulator = 12 // Apple Vision Pro Simulator -}; - -bool DSCView::Init() -{ - std::string os; - std::string arch; - - uint32_t platform; - GetParentView()->Read(&platform, 0xd8, 4); - char magic[17]; - GetParentView()->Read(&magic, 0, 16); - magic[16] = 0; - 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; - } - - if (std::string(magic) == "dyld_v1 arm64" || std::string(magic) == "dyld_v1 arm64e" || std::string(magic) == "dyld_v1arm64_32") - { - arch = "aarch64"; - } - else if (std::string(magic) == "dyld_v1 x86_64" || std::string(magic) == "dyld_v1 x86_64h") - { - arch = "x86_64"; - } - else - { - LogError("Unknown magic: %s", magic); - return false; - } - - SetDefaultPlatform(Platform::GetByName(os + "-" + arch)); - SetDefaultArchitecture(Architecture::GetByName(arch)); - - QualifiedNameAndType headerType; - std::string err; - - 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\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" - "\t\tuint64_t slideInfoOffsetUnused;\t // unused. Used to be file offset of kernel slid info\n" - "\t\tuint64_t slideInfoSizeUnused;\t // unused. Used to be size of kernel slid info\n" - "\t\tuint64_t localSymbolsOffset;\t // file offset of where local symbols are stored\n" - "\t\tuint64_t localSymbolsSize;\t\t // size of local symbols information\n" - "\t\tuint8_t uuid[16];\t\t\t\t // unique value for each shared cache file\n" - "\t\tuint64_t cacheType;\t\t\t\t // 0 for development, 1 for production // Kat: , 2 for iOS 16?\n" - "\t\tuint32_t branchPoolsOffset;\t\t // file offset to table of uint64_t pool addresses\n" - "\t\tuint32_t branchPoolsCount;\t\t // number of uint64_t entries\n" - "\t\tuint64_t accelerateInfoAddr;\t // (unslid) address of optimization info\n" - "\t\tuint64_t accelerateInfoSize;\t // size of optimization info\n" - "\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 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" - "\t\tuint64_t progClosuresSize;\t\t\t // size of list of program launch closures\n" - "\t\tuint64_t progClosuresTrieAddr;\t\t // (unslid) address of trie of indexes into program launch closures\n" - "\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\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\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" - "\t\tuint64_t maxSlide;\t\t\t\t // runtime slide of cache can be between zero and this value\n" - "\t\tuint64_t dylibsImageArrayAddr;\t // (unslid) address of ImageArray for dylibs in this cache\n" - "\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 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 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" - "\t\tuint64_t dylibsPBLStateArrayAddrUnused;\t // unused\n" - "\t\tuint64_t dylibsPBLSetAddr;\t\t\t\t // (unslid) address of PrebuiltLoaderSet of all cached dylibs\n" - "\t\tuint64_t programsPBLSetPoolAddr;\t\t // (unslid) address of pool of PrebuiltLoaderSet for each program\n" - "\t\tuint64_t programsPBLSetPoolSize;\t\t // size of pool of PrebuiltLoaderSet for each program\n" - "\t\tuint64_t programTrieAddr;\t\t\t\t // (unslid) address of trie mapping program path to PrebuiltLoaderSet\n" - "\t\tuint32_t programTrieSize;\n" - "\t\tuint32_t osVersion;\t\t\t\t// OS Version of dylibs in this cache for the main platform\n" - "\t\tuint32_t altPlatform;\t\t\t// e.g. iOSMac on macOS\n" - "\t\tuint32_t altOsVersion;\t\t\t// e.g. 14.0 for iOSMac\n" - "\t\tuint64_t swiftOptsOffset;\t\t// file offset to Swift optimizations header\n" - "\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\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 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\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 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 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); - - if (!err.empty() || !headerType.type) - { - LogError("Failed to parse header type: %s", err.c_str()); - return false; - } - - Ref settings = GetLoadSettings(GetTypeName()); - - if (!settings) - { - Ref 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); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_VAX", MACHO_CPU_TYPE_VAX); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC680x0", MACHO_CPU_TYPE_MC680x0); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_X86", MACHO_CPU_TYPE_X86); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_X86_64", MACHO_CPU_TYPE_X86_64); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MIPS", MACHO_CPU_TYPE_MIPS); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC98000", MACHO_CPU_TYPE_MC98000); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_HPPA", MACHO_CPU_TYPE_HPPA); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM", MACHO_CPU_TYPE_ARM); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM64", MACHO_CPU_TYPE_ARM64); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM64_32", MACHO_CPU_TYPE_ARM64_32); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC88000", MACHO_CPU_TYPE_MC88000); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_SPARC", MACHO_CPU_TYPE_SPARC); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_I860", MACHO_CPU_TYPE_I860); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ALPHA", MACHO_CPU_TYPE_ALPHA); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_POWERPC", MACHO_CPU_TYPE_POWERPC); - cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_POWERPC64", MACHO_CPU_TYPE_POWERPC64); - Ref cpuTypeEnum = cpuTypeBuilder.Finalize(); - - Ref cpuTypeEnumType = Type::EnumerationType(nullptr, cpuTypeEnum, 4, false); - std::string cpuTypeEnumName = "cpu_type_t"; - std::string cpuTypeEnumId = Type::GenerateAutoTypeId("macho", cpuTypeEnumName); - DefineType(cpuTypeEnumId, cpuTypeEnumName, cpuTypeEnumType); - - EnumerationBuilder fileTypeBuilder; - fileTypeBuilder.AddMemberWithValue("MH_OBJECT", MH_OBJECT); - fileTypeBuilder.AddMemberWithValue("MH_EXECUTE", MH_EXECUTE); - fileTypeBuilder.AddMemberWithValue("MH_FVMLIB", MH_FVMLIB); - fileTypeBuilder.AddMemberWithValue("MH_CORE", MH_CORE); - fileTypeBuilder.AddMemberWithValue("MH_PRELOAD", MH_PRELOAD); - fileTypeBuilder.AddMemberWithValue("MH_DYLIB", MH_DYLIB); - fileTypeBuilder.AddMemberWithValue("MH_DYLINKER", MH_DYLINKER); - fileTypeBuilder.AddMemberWithValue("MH_BUNDLE", MH_BUNDLE); - fileTypeBuilder.AddMemberWithValue("MH_DYLIB_STUB", MH_DYLIB_STUB); - fileTypeBuilder.AddMemberWithValue("MH_DSYM", MH_DSYM); - fileTypeBuilder.AddMemberWithValue("MH_KEXT_BUNDLE", MH_KEXT_BUNDLE); - fileTypeBuilder.AddMemberWithValue("MH_FILESET", MH_FILESET); - Ref fileTypeEnum = fileTypeBuilder.Finalize(); - - Ref fileTypeEnumType = Type::EnumerationType(nullptr, fileTypeEnum, 4, false); - std::string fileTypeEnumName = "file_type_t"; - std::string fileTypeEnumId = Type::GenerateAutoTypeId("macho", fileTypeEnumName); - DefineType(fileTypeEnumId, fileTypeEnumName, fileTypeEnumType); - - EnumerationBuilder flagsTypeBuilder; - flagsTypeBuilder.AddMemberWithValue("MH_NOUNDEFS", MH_NOUNDEFS); - flagsTypeBuilder.AddMemberWithValue("MH_INCRLINK", MH_INCRLINK); - flagsTypeBuilder.AddMemberWithValue("MH_DYLDLINK", MH_DYLDLINK); - flagsTypeBuilder.AddMemberWithValue("MH_BINDATLOAD", MH_BINDATLOAD); - flagsTypeBuilder.AddMemberWithValue("MH_PREBOUND", MH_PREBOUND); - flagsTypeBuilder.AddMemberWithValue("MH_SPLIT_SEGS", MH_SPLIT_SEGS); - flagsTypeBuilder.AddMemberWithValue("MH_LAZY_INIT", MH_LAZY_INIT); - flagsTypeBuilder.AddMemberWithValue("MH_TWOLEVEL", MH_TWOLEVEL); - flagsTypeBuilder.AddMemberWithValue("MH_FORCE_FLAT", MH_FORCE_FLAT); - flagsTypeBuilder.AddMemberWithValue("MH_NOMULTIDEFS", MH_NOMULTIDEFS); - flagsTypeBuilder.AddMemberWithValue("MH_NOFIXPREBINDING", MH_NOFIXPREBINDING); - flagsTypeBuilder.AddMemberWithValue("MH_PREBINDABLE", MH_PREBINDABLE); - flagsTypeBuilder.AddMemberWithValue("MH_ALLMODSBOUND", MH_ALLMODSBOUND); - flagsTypeBuilder.AddMemberWithValue("MH_SUBSECTIONS_VIA_SYMBOLS", MH_SUBSECTIONS_VIA_SYMBOLS); - flagsTypeBuilder.AddMemberWithValue("MH_CANONICAL", MH_CANONICAL); - flagsTypeBuilder.AddMemberWithValue("MH_WEAK_DEFINES", MH_WEAK_DEFINES); - flagsTypeBuilder.AddMemberWithValue("MH_BINDS_TO_WEAK", MH_BINDS_TO_WEAK); - flagsTypeBuilder.AddMemberWithValue("MH_ALLOW_STACK_EXECUTION", MH_ALLOW_STACK_EXECUTION); - flagsTypeBuilder.AddMemberWithValue("MH_ROOT_SAFE", MH_ROOT_SAFE); - flagsTypeBuilder.AddMemberWithValue("MH_SETUID_SAFE", MH_SETUID_SAFE); - flagsTypeBuilder.AddMemberWithValue("MH_NO_REEXPORTED_DYLIBS", MH_NO_REEXPORTED_DYLIBS); - flagsTypeBuilder.AddMemberWithValue("MH_PIE", MH_PIE); - flagsTypeBuilder.AddMemberWithValue("MH_DEAD_STRIPPABLE_DYLIB", MH_DEAD_STRIPPABLE_DYLIB); - flagsTypeBuilder.AddMemberWithValue("MH_HAS_TLV_DESCRIPTORS", MH_HAS_TLV_DESCRIPTORS); - flagsTypeBuilder.AddMemberWithValue("MH_NO_HEAP_EXECUTION", MH_NO_HEAP_EXECUTION); - flagsTypeBuilder.AddMemberWithValue("MH_APP_EXTENSION_SAFE", _MH_APP_EXTENSION_SAFE); - flagsTypeBuilder.AddMemberWithValue("MH_NLIST_OUTOFSYNC_WITH_DYLDINFO", _MH_NLIST_OUTOFSYNC_WITH_DYLDINFO); - flagsTypeBuilder.AddMemberWithValue("MH_SIM_SUPPORT", _MH_SIM_SUPPORT); - flagsTypeBuilder.AddMemberWithValue("MH_DYLIB_IN_CACHE", _MH_DYLIB_IN_CACHE); - Ref flagsTypeEnum = flagsTypeBuilder.Finalize(); - - Ref flagsTypeEnumType = Type::EnumerationType(nullptr, flagsTypeEnum, 4, false); - std::string flagsTypeEnumName = "flags_type_t"; - std::string flagsTypeEnumId = Type::GenerateAutoTypeId("macho", flagsTypeEnumName); - DefineType(flagsTypeEnumId, flagsTypeEnumName, flagsTypeEnumType); - - StructureBuilder machoHeaderBuilder; - machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "magic"); - machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("cpu_type_t")), "cputype"); - machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "cpusubtype"); - machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("file_type_t")), "filetype"); - machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "ncmds"); - machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "sizeofcmds"); - machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("flags_type_t")), "flags"); - if (GetAddressSize() == 8) - machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "reserved"); - Ref machoHeaderStruct = machoHeaderBuilder.Finalize(); - QualifiedName headerName = (GetAddressSize() == 8) ? std::string("mach_header_64") : std::string("mach_header"); - - std::string headerTypeId = Type::GenerateAutoTypeId("macho", headerName); - Ref machoHeaderType = Type::StructureType(machoHeaderStruct); - DefineType(headerTypeId, headerName, machoHeaderType); - - EnumerationBuilder cmdTypeBuilder; - cmdTypeBuilder.AddMemberWithValue("LC_REQ_DYLD", LC_REQ_DYLD); - cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT", LC_SEGMENT); - cmdTypeBuilder.AddMemberWithValue("LC_SYMTAB", LC_SYMTAB); - cmdTypeBuilder.AddMemberWithValue("LC_SYMSEG", LC_SYMSEG); - cmdTypeBuilder.AddMemberWithValue("LC_THREAD", LC_THREAD); - cmdTypeBuilder.AddMemberWithValue("LC_UNIXTHREAD", LC_UNIXTHREAD); - cmdTypeBuilder.AddMemberWithValue("LC_LOADFVMLIB", LC_LOADFVMLIB); - cmdTypeBuilder.AddMemberWithValue("LC_IDFVMLIB", LC_IDFVMLIB); - cmdTypeBuilder.AddMemberWithValue("LC_IDENT", LC_IDENT); - cmdTypeBuilder.AddMemberWithValue("LC_FVMFILE", LC_FVMFILE); - cmdTypeBuilder.AddMemberWithValue("LC_PREPAGE", LC_PREPAGE); - cmdTypeBuilder.AddMemberWithValue("LC_DYSYMTAB", LC_DYSYMTAB); - cmdTypeBuilder.AddMemberWithValue("LC_LOAD_DYLIB", LC_LOAD_DYLIB); - cmdTypeBuilder.AddMemberWithValue("LC_ID_DYLIB", LC_ID_DYLIB); - cmdTypeBuilder.AddMemberWithValue("LC_LOAD_DYLINKER", LC_LOAD_DYLINKER); - cmdTypeBuilder.AddMemberWithValue("LC_ID_DYLINKER", LC_ID_DYLINKER); - cmdTypeBuilder.AddMemberWithValue("LC_PREBOUND_DYLIB", LC_PREBOUND_DYLIB); - cmdTypeBuilder.AddMemberWithValue("LC_ROUTINES", LC_ROUTINES); - cmdTypeBuilder.AddMemberWithValue("LC_SUB_FRAMEWORK", LC_SUB_FRAMEWORK); - cmdTypeBuilder.AddMemberWithValue("LC_SUB_UMBRELLA", LC_SUB_UMBRELLA); - cmdTypeBuilder.AddMemberWithValue("LC_SUB_CLIENT", LC_SUB_CLIENT); - cmdTypeBuilder.AddMemberWithValue("LC_SUB_LIBRARY", LC_SUB_LIBRARY); - cmdTypeBuilder.AddMemberWithValue("LC_TWOLEVEL_HINTS", LC_TWOLEVEL_HINTS); - cmdTypeBuilder.AddMemberWithValue("LC_PREBIND_CKSUM", LC_PREBIND_CKSUM); - cmdTypeBuilder.AddMemberWithValue("LC_LOAD_WEAK_DYLIB", LC_LOAD_WEAK_DYLIB); // (0x18 | LC_REQ_DYLD) - cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_64", LC_SEGMENT_64); - cmdTypeBuilder.AddMemberWithValue("LC_ROUTINES_64", LC_ROUTINES_64); - cmdTypeBuilder.AddMemberWithValue("LC_UUID", LC_UUID); - 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_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_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_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); - cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO_64", _LC_ENCRYPTION_INFO_64); - cmdTypeBuilder.AddMemberWithValue("LC_LINKER_OPTION", _LC_LINKER_OPTION); - cmdTypeBuilder.AddMemberWithValue("LC_LINKER_OPTIMIZATION_HINT", _LC_LINKER_OPTIMIZATION_HINT); - cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_TVOS", _LC_VERSION_MIN_TVOS); - cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_WATCHOS", LC_VERSION_MIN_WATCHOS); - cmdTypeBuilder.AddMemberWithValue("LC_NOTE", LC_NOTE); - cmdTypeBuilder.AddMemberWithValue("LC_BUILD_VERSION", LC_BUILD_VERSION); - cmdTypeBuilder.AddMemberWithValue("LC_DYLD_EXPORTS_TRIE", LC_DYLD_EXPORTS_TRIE); - cmdTypeBuilder.AddMemberWithValue("LC_DYLD_CHAINED_FIXUPS", LC_DYLD_CHAINED_FIXUPS); - cmdTypeBuilder.AddMemberWithValue("LC_FILESET_ENTRY", LC_FILESET_ENTRY); - Ref cmdTypeEnum = cmdTypeBuilder.Finalize(); - - Ref cmdTypeEnumType = Type::EnumerationType(nullptr, cmdTypeEnum, 4, false); - std::string cmdTypeEnumName = "load_command_type_t"; - std::string cmdTypeEnumId = Type::GenerateAutoTypeId("macho", cmdTypeEnumName); - DefineType(cmdTypeEnumId, cmdTypeEnumName, cmdTypeEnumType); - - StructureBuilder loadCommandBuilder; - loadCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - loadCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - Ref loadCommandStruct = loadCommandBuilder.Finalize(); - QualifiedName loadCommandName = std::string("load_command"); - std::string loadCommandTypeId = Type::GenerateAutoTypeId("macho", loadCommandName); - Ref loadCommandType = Type::StructureType(loadCommandStruct); - DefineType(loadCommandTypeId, loadCommandName, loadCommandType); - - EnumerationBuilder protTypeBuilder; - protTypeBuilder.AddMemberWithValue("VM_PROT_NONE", MACHO_VM_PROT_NONE); - protTypeBuilder.AddMemberWithValue("VM_PROT_READ", MACHO_VM_PROT_READ); - protTypeBuilder.AddMemberWithValue("VM_PROT_WRITE", MACHO_VM_PROT_WRITE); - protTypeBuilder.AddMemberWithValue("VM_PROT_EXECUTE", MACHO_VM_PROT_EXECUTE); - // protTypeBuilder.AddMemberWithValue("VM_PROT_DEFAULT", MACHO_VM_PROT_DEFAULT); - // protTypeBuilder.AddMemberWithValue("VM_PROT_ALL", MACHO_VM_PROT_ALL); - protTypeBuilder.AddMemberWithValue("VM_PROT_NO_CHANGE", MACHO_VM_PROT_NO_CHANGE); - protTypeBuilder.AddMemberWithValue("VM_PROT_COPY_OR_WANTS_COPY", MACHO_VM_PROT_COPY); - // protTypeBuilder.AddMemberWithValue("VM_PROT_WANTS_COPY", MACHO_VM_PROT_WANTS_COPY); - Ref protTypeEnum = protTypeBuilder.Finalize(); - - Ref protTypeEnumType = Type::EnumerationType(nullptr, protTypeEnum, 4, false); - std::string protTypeEnumName = "vm_prot_t"; - std::string protTypeEnumId = Type::GenerateAutoTypeId("macho", protTypeEnumName); - DefineType(protTypeEnumId, protTypeEnumName, protTypeEnumType); - - EnumerationBuilder segFlagsTypeBuilder; - segFlagsTypeBuilder.AddMemberWithValue("SG_HIGHVM", SG_HIGHVM); - segFlagsTypeBuilder.AddMemberWithValue("SG_FVMLIB", SG_FVMLIB); - segFlagsTypeBuilder.AddMemberWithValue("SG_NORELOC", SG_NORELOC); - segFlagsTypeBuilder.AddMemberWithValue("SG_PROTECTED_VERSION_1", SG_PROTECTED_VERSION_1); - Ref segFlagsTypeEnum = segFlagsTypeBuilder.Finalize(); - - Ref segFlagsTypeEnumType = Type::EnumerationType(nullptr, segFlagsTypeEnum, 4, false); - std::string segFlagsTypeEnumName = "sg_flags_t"; - std::string segFlagsTypeEnumId = Type::GenerateAutoTypeId("macho", segFlagsTypeEnumName); - DefineType(segFlagsTypeEnumId, segFlagsTypeEnumName, segFlagsTypeEnumType); - - StructureBuilder loadSegmentCommandBuilder; - loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - loadSegmentCommandBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); - loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "vmaddr"); - loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "vmsize"); - loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "fileoff"); - loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "filesize"); - loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "maxprot"); - loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "initprot"); - loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "nsects"); - loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("sg_flags_t")), "flags"); - Ref loadSegmentCommandStruct = loadSegmentCommandBuilder.Finalize(); - QualifiedName loadSegmentCommandName = std::string("segment_command"); - std::string loadSegmentCommandTypeId = Type::GenerateAutoTypeId("macho", loadSegmentCommandName); - Ref loadSegmentCommandType = Type::StructureType(loadSegmentCommandStruct); - DefineType(loadSegmentCommandTypeId, loadSegmentCommandName, loadSegmentCommandType); - - StructureBuilder loadSegmentCommand64Builder; - loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - loadSegmentCommand64Builder.AddMember(Type::IntegerType(4, false), "cmdsize"); - loadSegmentCommand64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); - loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "vmaddr"); - loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "vmsize"); - loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "fileoff"); - loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "filesize"); - loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "maxprot"); - loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "initprot"); - loadSegmentCommand64Builder.AddMember(Type::IntegerType(4, false), "nsects"); - loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("sg_flags_t")), "flags"); - Ref loadSegmentCommand64Struct = loadSegmentCommand64Builder.Finalize(); - QualifiedName loadSegment64CommandName = std::string("segment_command_64"); - std::string loadSegment64CommandTypeId = Type::GenerateAutoTypeId("macho", loadSegment64CommandName); - Ref loadSegment64CommandType = Type::StructureType(loadSegmentCommand64Struct); - DefineType(loadSegment64CommandTypeId, loadSegment64CommandName, loadSegment64CommandType); - - StructureBuilder sectionBuilder; - sectionBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "sectname"); - sectionBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); - sectionBuilder.AddMember(Type::IntegerType(4, false), "addr"); - sectionBuilder.AddMember(Type::IntegerType(4, false), "size"); - sectionBuilder.AddMember(Type::IntegerType(4, false), "offset"); - sectionBuilder.AddMember(Type::IntegerType(4, false), "align"); - sectionBuilder.AddMember(Type::IntegerType(4, false), "reloff"); - sectionBuilder.AddMember(Type::IntegerType(4, false), "nreloc"); - sectionBuilder.AddMember(Type::IntegerType(4, false), "flags"); - sectionBuilder.AddMember(Type::IntegerType(4, false), "reserved1"); - sectionBuilder.AddMember(Type::IntegerType(4, false), "reserved2"); - Ref sectionStruct = sectionBuilder.Finalize(); - QualifiedName sectionName = std::string("section"); - std::string sectionTypeId = Type::GenerateAutoTypeId("macho", sectionName); - Ref sectionType = Type::StructureType(sectionStruct); - DefineType(sectionTypeId, sectionName, sectionType); - - StructureBuilder section64Builder; - section64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "sectname"); - section64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); - section64Builder.AddMember(Type::IntegerType(8, false), "addr"); - section64Builder.AddMember(Type::IntegerType(8, false), "size"); - section64Builder.AddMember(Type::IntegerType(4, false), "offset"); - section64Builder.AddMember(Type::IntegerType(4, false), "align"); - section64Builder.AddMember(Type::IntegerType(4, false), "reloff"); - section64Builder.AddMember(Type::IntegerType(4, false), "nreloc"); - section64Builder.AddMember(Type::IntegerType(4, false), "flags"); - section64Builder.AddMember(Type::IntegerType(4, false), "reserved1"); - section64Builder.AddMember(Type::IntegerType(4, false), "reserved2"); - section64Builder.AddMember(Type::IntegerType(4, false), "reserved3"); - Ref section64Struct = section64Builder.Finalize(); - QualifiedName section64Name = std::string("section_64"); - std::string section64TypeId = Type::GenerateAutoTypeId("macho", section64Name); - Ref section64Type = Type::StructureType(section64Struct); - DefineType(section64TypeId, section64Name, section64Type); - - StructureBuilder symtabBuilder; - symtabBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - symtabBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - symtabBuilder.AddMember(Type::IntegerType(4, false), "symoff"); - symtabBuilder.AddMember(Type::IntegerType(4, false), "nsyms"); - symtabBuilder.AddMember(Type::IntegerType(4, false), "stroff"); - symtabBuilder.AddMember(Type::IntegerType(4, false), "strsize"); - Ref symtabStruct = symtabBuilder.Finalize(); - QualifiedName symtabName = std::string("symtab"); - std::string symtabTypeId = Type::GenerateAutoTypeId("macho", symtabName); - Ref symtabType = Type::StructureType(symtabStruct); - DefineType(symtabTypeId, symtabName, symtabType); - - StructureBuilder dynsymtabBuilder; - dynsymtabBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "ilocalsym"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nlocalsym"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "iextdefsym"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextdefsym"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "iundefsym"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nundefsym"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "tocoff"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "ntoc"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "modtaboff"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nmodtab"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "extrefsymoff"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextrefsyms"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "indirectsymoff"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nindirectsyms"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "extreloff"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextrel"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "locreloff"); - dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nlocrel"); - Ref dynsymtabStruct = dynsymtabBuilder.Finalize(); - QualifiedName dynsymtabName = std::string("dynsymtab"); - std::string dynsymtabTypeId = Type::GenerateAutoTypeId("macho", dynsymtabName); - Ref dynsymtabType = Type::StructureType(dynsymtabStruct); - DefineType(dynsymtabTypeId, dynsymtabName, dynsymtabType); - - StructureBuilder uuidBuilder; - uuidBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - uuidBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - uuidBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, false), 16), "uuid"); - Ref uuidStruct = uuidBuilder.Finalize(); - QualifiedName uuidName = std::string("uuid"); - std::string uuidTypeId = Type::GenerateAutoTypeId("macho", uuidName); - Ref uuidType = Type::StructureType(uuidStruct); - DefineType(uuidTypeId, uuidName, uuidType); - - StructureBuilder linkeditDataBuilder; - linkeditDataBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "dataoff"); - linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "datasize"); - Ref linkeditDataStruct = linkeditDataBuilder.Finalize(); - QualifiedName linkeditDataName = std::string("linkedit_data"); - std::string linkeditDataTypeId = Type::GenerateAutoTypeId("macho", linkeditDataName); - Ref linkeditDataType = Type::StructureType(linkeditDataStruct); - DefineType(linkeditDataTypeId, linkeditDataName, linkeditDataType); - - StructureBuilder encryptionInfoBuilder; - encryptionInfoBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptoff"); - encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptsize"); - encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptid"); - Ref encryptionInfoStruct = encryptionInfoBuilder.Finalize(); - QualifiedName encryptionInfoName = std::string("encryption_info"); - std::string encryptionInfoTypeId = Type::GenerateAutoTypeId("macho", encryptionInfoName); - Ref encryptionInfoType = Type::StructureType(encryptionInfoStruct); - DefineType(encryptionInfoTypeId, encryptionInfoName, encryptionInfoType); - - StructureBuilder versionMinBuilder; - versionMinBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - versionMinBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - versionMinBuilder.AddMember(Type::IntegerType(4, false), "version"); - versionMinBuilder.AddMember(Type::IntegerType(4, false), "sdk"); - Ref versionMinStruct = versionMinBuilder.Finalize(); - QualifiedName versionMinName = std::string("version_min"); - std::string versionMinTypeId = Type::GenerateAutoTypeId("macho", versionMinName); - Ref versionMinType = Type::StructureType(versionMinStruct); - DefineType(versionMinTypeId, versionMinName, versionMinType); - - StructureBuilder dyldInfoBuilder; - dyldInfoBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "rebase_off"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "rebase_size"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "bind_off"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "bind_size"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "weak_bind_off"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "weak_bind_size"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "lazy_bind_off"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "lazy_bind_size"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "export_off"); - dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "export_size"); - Ref dyldInfoStruct = dyldInfoBuilder.Finalize(); - QualifiedName dyldInfoName = std::string("dyld_info"); - std::string dyldInfoTypeId = Type::GenerateAutoTypeId("macho", dyldInfoName); - Ref dyldInfoType = Type::StructureType(dyldInfoStruct); - DefineType(dyldInfoTypeId, dyldInfoName, dyldInfoType); - - StructureBuilder dylibBuilder; - dylibBuilder.AddMember(Type::IntegerType(4, false), "name"); - dylibBuilder.AddMember(Type::IntegerType(4, false), "timestamp"); - dylibBuilder.AddMember(Type::IntegerType(4, false), "current_version"); - dylibBuilder.AddMember(Type::IntegerType(4, false), "compatibility_version"); - Ref dylibStruct = dylibBuilder.Finalize(); - QualifiedName dylibName = std::string("dylib"); - std::string dylibTypeId = Type::GenerateAutoTypeId("macho", dylibName); - Ref dylibType = Type::StructureType(dylibStruct); - DefineType(dylibTypeId, dylibName, dylibType); - - StructureBuilder dylibCommandBuilder; - dylibCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - dylibCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - dylibCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("dylib")), "dylib"); - Ref dylibCommandStruct = dylibCommandBuilder.Finalize(); - QualifiedName dylibCommandName = std::string("dylib_command"); - std::string dylibCommandTypeId = Type::GenerateAutoTypeId("macho", dylibCommandName); - Ref dylibCommandType = Type::StructureType(dylibCommandStruct); - DefineType(dylibCommandTypeId, dylibCommandName, dylibCommandType); - - StructureBuilder filesetEntryCommandBuilder; - filesetEntryCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); - filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); - filesetEntryCommandBuilder.AddMember(Type::IntegerType(8, false), "vmaddr"); - filesetEntryCommandBuilder.AddMember(Type::IntegerType(8, false), "fileoff"); - filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "entry_id"); - filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "reserved"); - Ref filesetEntryCommandStruct = filesetEntryCommandBuilder.Finalize(); - QualifiedName filesetEntryCommandName = std::string("fileset_entry_command"); - std::string filesetEntryCommandTypeId = Type::GenerateAutoTypeId("macho", filesetEntryCommandName); - Ref filesetEntryCommandType = Type::StructureType(filesetEntryCommandStruct); - DefineType(filesetEntryCommandTypeId, filesetEntryCommandName, filesetEntryCommandType); - - // uint32_t at 0x10 is offset to mapping table. - // first mapping struct in that table is base of primary - // first uint64_t in that struct is the base address of the primary - // double gpv here because DSCRaw explicitly stops at the start of this mapping table - uint64_t basePointer = 0; - GetParentView()->Read(&basePointer, 16, 4); - if (basePointer == 0) - { - 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); - return false; - } - - uint64_t headerSize = std::min(basePointer, headerType.type->GetWidth()); - // Truncate the `dyld_cache_header` structure to the structure present in the cache file. - auto newStructure = StructureBuilder(headerType.type->GetStructure()).SetWidth(headerSize).Finalize(); - headerType.type = TypeBuilder::StructureType(newStructure).Finalize(); - - 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); - - return true; -} - - -DSCViewType::DSCViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME) -{ -} - -BinaryNinja::Ref DSCViewType::Create(BinaryNinja::BinaryView* data) -{ - return new DSCView(VIEW_NAME, data, false); -} - - -Ref DSCViewType::GetLoadSettingsForData(BinaryView* data) -{ - Ref 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 = GetDefaultLoadSettingsForData(viewRef); - - // specify default load settings that can be overridden - std::vector 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 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 loadSettings = viewRef->GetLoadSettings(GetName()); - if (loadSettings && !loadSettings->IsEmpty()) - settings->DeserializeSchema(loadSettings->SerializeSchema()); - - return settings; -} - - -BinaryNinja::Ref DSCViewType::Parse(BinaryNinja::BinaryView* data) -{ - return new DSCView(VIEW_NAME, data, true); -} - -bool DSCViewType::IsTypeValidForData(BinaryNinja::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; -} 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 - - -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 Create(BinaryNinja::BinaryView *data) override; - - BinaryNinja::Ref Parse(BinaryNinja::BinaryView *data) override; - - bool IsTypeValidForData(BinaryNinja::BinaryView *data) override; - - bool IsDeprecated() override { return false; } - - BinaryNinja::Ref 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 + +#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 + +CacheAccessorID GetCacheAccessorID(const std::string& filePath) +{ + constexpr std::hash hasher; + return static_cast(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(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 lock(m_persistedMutex); + m_persistedPointers[address] = pointer; +} + +void FileAccessorWriteLog::ApplyWrites(MappedFileAccessor& accessor) +{ + std::shared_lock lock(m_persistedMutex); + for (const auto& [address, pointer] : m_persistedPointers) + accessor.WritePointer(address, pointer); +} + +std::shared_ptr 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 + +#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 m_cache; + std::unordered_map> 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 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 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 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 weakPtr, std::string filePath) : + m_weakPtr(std::move(weakPtr)), m_filePath(std::move(filePath)), + m_writeLog(std::make_shared()) + {} + + std::shared_ptr 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 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 functionTable = {}; + auto current = static_cast(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::ParseHeaderForAddress( + std::shared_ptr 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 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 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 == "") + continue; + + std::optional 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 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 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& 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 SharedCacheMachOHeader::ReadExportSymbolTrie(VirtualMemory& vm) const +{ + if (exportTrie.datasize == 0) + return {}; + + uint64_t exportTrieAddress = GetLinkEditFileBase() + exportTrie.dataoff; + std::vector 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> entryPoints; + std::vector 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 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 segments; // only three types of sections __TEXT, __DATA, __IMPORT + BinaryNinja::segment_command_64 linkeditSegment; + std::vector sections; + std::vector sectionNames; + + std::vector symbolStubSections; + std::vector symbolPointerSections; + + std::vector dylibs; + + BinaryNinja::build_version_command buildVersion; + std::vector 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 ParseHeaderForAddress( + std::shared_ptr vm, uint64_t address, const std::string& imagePath); + + // TODO: Replace view with address size? + std::vector ReadSymbolTable(BinaryNinja::BinaryView& view, VirtualMemory& vm) const; + + std::optional AddExportTerminalSymbol( + const std::string& symbolName, const uint8_t* current, const uint8_t* end) const; + + bool ProcessLinkEditTrie(std::vector& symbols, const std::string& currentText, const uint8_t* begin, + const uint8_t* current, const uint8_t* end) const; + + std::vector ReadExportSymbolTrie(VirtualMemory& vm) const; + + std::vector 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 view, std::shared_ptr vm) +{ + m_view = view; + m_logger = new Logger("SharedCacheMachOProcessor", view->GetFile()->GetSessionId()); + m_vm = std::move(vm); + + // Adjust processor settings. + if (Ref settings = m_view->GetLoadSettings(VIEW_NAME)) + { + if (settings->Contains("loader.dsc.processFunctionStarts")) + m_applyFunctions = settings->Get("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 m_view; + BinaryNinja::Ref m_logger; + std::shared_ptr m_vm; + + bool m_applyFunctions = true; + +public: + explicit SharedCacheMachOProcessor( + BinaryNinja::Ref view, std::shared_ptr 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 +#else + #include + #include + #include + #include +#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::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(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::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(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(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 +#include +#include +#include + +// 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 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::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(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(&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(address); +} + +int8_t MappedFileAccessor::ReadInt8(size_t address) +{ + return Read(address); +} + +uint16_t MappedFileAccessor::ReadUInt16(size_t address) +{ + return Read(address); +} + +int16_t MappedFileAccessor::ReadInt16(size_t address) +{ + return Read(address); +} + + +uint32_t MappedFileAccessor::ReadUInt32(size_t address) +{ + return Read(address); +} + +int32_t MappedFileAccessor::ReadInt32(size_t address) +{ + return Read(address); +} + +uint64_t MappedFileAccessor::ReadUInt64(size_t address) +{ + return Read(address); +} + +int64_t MappedFileAccessor::ReadInt64(size_t address) +{ + return Read(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 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 +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 +#include + +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 +{ + 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 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 once the minimum supported + // C++ version supports it. + std::pair ReadSpan(size_t addr, size_t length); + + void Read(void* dest, size_t addr, size_t length) const; + + template + 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(context.doc[name.data()].GetUint64()); -} - -void Deserialize(DeserializationContext& context, std::string_view name, uint16_t& b) -{ - b = static_cast(context.doc[name.data()].GetUint64()); -} - -void Deserialize(DeserializationContext& context, std::string_view name, uint32_t& b) -{ - b = static_cast(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& 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& 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& 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>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::string key = i.GetArray()[0].GetString(); - std::unordered_map 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& 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& 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>& 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>>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::pair> 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>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::pair 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& 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& 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>>>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::pair>> 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(value.cputype)); - Serialize(context, static_cast(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& 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& 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& 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` subclass: - * ``` - class MyClass : public MetadataSerializable { - 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 -#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(#name) -#define MSL_CAST(name, storedType, type) name = (type)context.load(#name) - -struct DeserializationContext; - -struct SerializationContext { - rapidjson::StringBuffer buffer; - rapidjson::Writer writer; - - SerializationContext() : buffer(), writer(buffer) { - } - - template - void store(std::string_view x, const T& y) - { - Serialize(*this, x, y); - } -}; - -struct DeserializationContext { - rapidjson::Document doc; - - template - T load(std::string_view x) - { - T value; - Deserialize(*this, x, value); - return value; - } -}; - -template -class MetadataSerializable { -public: - template - std::string AsString(Args&&... args) const { - SerializationContext context; - Store(context, std::forward(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 - Ref AsMetadata(Args&&... args) const { - return new Metadata(AsString(std::forward(args)...)); - } - - template - void Store(SerializationContext& context, Args&&... args) const { - context.writer.StartObject(); - AsDerived().Store(context, std::forward(args)...); - context.writer.EndObject(); - } - -private: - const Derived& AsDerived() const { return static_cast(*this); } - Derived& AsDerived() { return static_cast(*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 -inline void Serialize(SerializationContext& context, const MetadataSerializable& value) -{ - value.Store(context); -} - -template -inline void Serialize(SerializationContext& context, std::string_view name, const T& value) -{ - Serialize(context, name); - Serialize(context, value); -} - -template -void Serialize(SerializationContext& context, const std::pair& value) -{ - context.writer.StartArray(); - Serialize(context, value.first); - Serialize(context, value.second); - context.writer.EndArray(); -} - -template -void Serialize(SerializationContext& context, const std::map& value) -{ - context.writer.StartArray(); - for (auto& pair : value) - { - Serialize(context, pair); - } - context.writer.EndArray(); -} - -template -void Serialize(SerializationContext& context, const std::unordered_map& value) -{ - context.writer.StartArray(); - for (auto& pair : value) - { - Serialize(context, pair); - } - context.writer.EndArray(); -} - -template -void Serialize(SerializationContext& context, const std::vector& values) -{ - context.writer.StartArray(); - for (const auto& value : values) - { - Serialize(context, value); - } - context.writer.EndArray(); -} - -template -void Serialize(SerializationContext& context, const std::optional& 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>& 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& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector>>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector>>>& 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& 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& 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& 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 DSCObjCProcessor::GetReader() +std::shared_ptr SharedCacheObjCProcessor::GetReader() { - return std::make_shared(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(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 GetObjCOptimizationHeader(SharedCache& cache, VirtualMemoryReader& reader) { - auto objCRelativeMethodsBaseAddr = m_cache->GetObjCRelativeMethodBaseAddress(static_cast(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 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(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 #include #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 m_customRelativeMethodSelectorBase = std::nullopt; - SharedCacheCore::SharedCache* m_cache; - std::shared_ptr GetReader() override; - void GetRelativeMethod(ObjCReader* reader, method_t& meth) override; - + std::shared_ptr 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -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> -{ - std::vector backingCaches; - std::unordered_map headers; - std::vector images; - std::unordered_map imageStarts; - AddressRangeMap memoryRegions; - - std::optional> 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 Load(DeserializationContext&); -}; - -struct State : public MetadataSerializable -{ - // Map from start address of a region to the region's status. - std::unordered_map memoryRegionStatus; - std::unordered_map>>> - exportInfos; - std::unordered_map>>> symbolInfos; - - // Store only. Loading is done via `ModifiedState`. - void Store(SerializationContext&, std::optional viewState) const; -}; - -struct SharedCache::ModifiedState : public State, public MetadataSerializable -{ - std::optional viewState; - - using Base = MetadataSerializable; - 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> typeLibraries; - - std::mutex viewOperationsThatInfluenceMetadataMutex; - - std::atomic progress; - - std::mutex cacheInfoMutex; - std::shared_ptr cacheInfo; - - std::mutex stateMutex; - struct State state; - - std::atomic viewState; - uint64_t savedModifications = 0; -}; - -namespace { - -std::shared_ptr ViewSpecificStateForId(uint64_t viewIdentifier, bool insertIfNeeded = true) -{ - static std::mutex viewSpecificStateMutex; - static std::unordered_map> 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(); - 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 ViewSpecificStateForView(Ref view) -{ - return ViewSpecificStateForId(view->GetFile()->GetSessionId()); -} - -std::string base_name(std::string const& path) -{ - return path.substr(path.find_last_of("/\\") + 1); -} - -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 (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 dscView) -{ - std::shared_ptr 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& 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&, 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&, 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&, 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; -} - -void SharedCache::SetMemoryRegionHeaderInitialized(std::lock_guard&, 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.headerInitialized = true; -} - -void SharedCache::PerformInitialLoad(std::lock_guard& lock) -{ - 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; - - // 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); - if (sig.GetLength() != 4) - throw std::runtime_error("Not a valid dyld_shared_cache file"); - 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 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 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 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 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; - - // We have set up enough metadata to map VM now. - - auto vm = GetVMMap(initialState); - if (!vm) - { - m_logger->LogError("Failed to map VM pages for Shared Cache on initial load, this is fatal."); - return; - } - 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) - { - initialState.AddPotentiallyOverlappingMemoryRegion(std::move(memoryRegion)); - } - m_logger->LogInfo("Loaded %zu stub island or dyld memory regions", nonImageMemoryRegions.size()); - - for (const auto& cache : initialState.backingCaches) - { - 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)); - } - } - - m_cacheInfo = std::make_shared(std::move(initialState)); - m_modifiedState->viewState = DSCViewStateLoaded; - SaveCacheInfoToDSCView(lock); - - m_logger->LogInfo("Finished loading..."); - m_viewSpecificState->progress = LoadProgressFinished; -} - -std::shared_ptr SharedCache::GetVMMap() -{ - return GetVMMap(*m_cacheInfo); -} - -std::shared_ptr SharedCache::GetVMMap(const CacheInfo& cacheInfo) -{ - std::shared_ptr vm = std::make_shared(0x1000); - - uint64_t baseAddress = cacheInfo.BaseAddress(); - Ref logger = m_logger; - for (const auto& cache : cacheInfo.backingCaches) - { - 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 mmap){ - ParseAndApplySlideInfoForFile(mmap, baseAddress, logger); - }); - } - } - - return vm; -} - - -bool SharedCache::DeserializeFromRawView(std::lock_guard& lock) -{ - std::lock_guard cacheInfoLock(m_viewSpecificState->cacheInfoMutex); - - m_cacheInfo = std::make_shared(); - m_modifiedState = std::make_unique(); - m_modifiedState->viewState = DSCViewStateUnloaded; - - // First try and load from the view itself. - if (m_viewSpecificState->cacheInfo) - { - m_cacheInfo = m_viewSpecificState->cacheInfo; - m_modifiedState->viewState = DSCViewStateLoaded; - return true; - } - - // Second try and load from the view metadata. - if (SharedCacheMetadata::ViewHasMetadata(m_dscView)) - { - auto metadata = SharedCacheMetadata::LoadFromView(m_dscView); - if (!metadata) - return false; - - m_viewSpecificState->viewState = metadata->state->viewState.value_or(DSCViewStateUnloaded); - m_viewSpecificState->state = static_cast(std::move(*metadata->state)); - m_viewSpecificState->cacheInfo = std::move(metadata->cacheInfo); - - m_cacheInfo = m_viewSpecificState->cacheInfo; - m_modifiedState->viewState = DSCViewStateLoaded; - } - - return true; -} - - -// static -void SharedCache::ParseAndApplySlideInfoForFile(std::shared_ptr file, uint64_t base, Ref logger) -{ - if (file->SlideInfoWasApplied()) - return; - - dyld_cache_header baseHeader; - file->Read(&baseHeader, 0, sizeof(dyld_cache_header)); - - std::vector> mappings; - - if (baseHeader.slideInfoOffsetUnused) - { - // 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); - } - 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); -} - - -SharedCache::SharedCache(BinaryNinja::Ref dscView) : - m_dscView(dscView), m_viewSpecificState(ViewSpecificStateForView(dscView)) -{ - 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("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; -} - -SharedCache* SharedCache::GetFromDSCView(BinaryNinja::Ref dscView) -{ - if (dscView->GetTypeName() != VIEW_NAME) - return nullptr; - try { - return new SharedCache(dscView); - } - catch (...) - { - return nullptr; - } -} - -std::optional SharedCache::GetImageStart(const std::string_view installName) -{ - 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; - - 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) -{ - if (auto it = m_cacheInfo->memoryRegions.find(address); it != m_cacheInfo->memoryRegions.end()) - return it->second.prettyName; - - return ""; -} - -std::string SharedCache::ImageNameForAddress(uint64_t address) -{ - if (auto header = HeaderForAddress(address)) - return header->identifierPrefix; - - return ""; -} - -bool SharedCache::LoadImageContainingAddress(uint64_t address, bool skipObjC) -{ - if (auto header = HeaderForAddress(address)) { - std::lock_guard lock(m_mutex); - return LoadImageWithInstallName(lock, header->installName, skipObjC); - } - - return false; -} - -bool SharedCache::LoadSectionAtAddress(uint64_t address) -{ - 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()) - { - 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; - } - } - - if (!targetSegment) - { - auto regionIt = m_cacheInfo->memoryRegions.find(address); - if (regionIt == m_cacheInfo->memoryRegions.end()) - { - m_logger->LogError("Failed to find a segment containing address 0x%llx", address); - return false; - } - - 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; - } - - 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; -} - -static void GetObjCSettings(Ref view, bool* processObjCMetadata, bool* processCFStrings) -{ - auto settings = view->GetLoadSettings(VIEW_NAME); - *processCFStrings = true; - *processObjCMetadata = true; - if (settings && settings->Contains("loader.dsc.processCFStrings")) - *processCFStrings = settings->Get("loader.dsc.processCFStrings", view); - if (settings && settings->Contains("loader.dsc.processObjC")) - *processObjCMetadata = settings->Get("loader.dsc.processObjC", view); -} - -static void ProcessObjCSectionsForImageWithName(std::string baseName, std::shared_ptr vm, std::shared_ptr objc, bool processCFStrings, bool processObjCMetadata, Ref logger) -{ - 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()); - } -} - -void SharedCache::ProcessObjCSectionsForImageWithInstallName(std::string installName) -{ - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - if (!processObjCMetadata && !processCFStrings) - return; - - auto objc = std::make_shared(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); -} - -void SharedCache::ProcessAllObjCSections(std::lock_guard& lock) -{ - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - if (!processObjCMetadata && !processCFStrings) - return; - - auto objc = std::make_shared(m_dscView, this, false); - auto vm = GetVMMap(); - - std::set processedImageHeaders; - for (auto region : GetMappedRegions()) - { - // 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); - - ProcessObjCSectionsForImageWithName(header->identifierPrefix, vm, objc, processCFStrings, processObjCMetadata, m_logger); - } -} - -bool SharedCache::LoadImageWithInstallName(std::string installName, bool skipObjC) -{ - std::lock_guard lock(m_mutex); - return LoadImageWithInstallName(lock, installName, skipObjC); -} - -bool SharedCache::LoadImageWithInstallName(std::lock_guard& lock, std::string installName, bool skipObjC) -{ - 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 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("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()) - 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); - - if (!skipObjC) - { - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - ProcessObjCSectionsForImageWithName(h->identifierPrefix, vm, std::make_shared(m_dscView, this, false), processCFStrings, processObjCMetadata, m_logger); - } - - m_dscView->AddAnalysisOption("linearsweep"); - m_dscView->UpdateAnalysis(); - - m_dscView->CommitUndoActions(id); - - return true; -} - -std::optional SharedCache::LoadHeaderForAddress(std::shared_ptr vm, uint64_t address, std::string installName) -{ - SharedCacheMachOHeader header; - - header.textBase = address; - header.installName = installName; - header.identifierPrefix = base_name(installName); - - 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++) - { - // 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: - { - 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 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> 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 == "") - continue; - - std::optional 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 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); - 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 sym = new Symbol(symbolType.value(), shortName, shortName, symbolName, symbolAddress, nullptr); - symbolList.emplace_back(sym); - } - - auto symListPtr = std::make_shared>>(std::move(symbolList)); - m_modifiedState->symbolInfos.emplace(header.textBase, symListPtr); -} - -void SharedCache::ApplySymbol(Ref view, Ref typeLib, Ref symbol) -{ - Ref func = nullptr; - auto symbolAddress = symbol->GetAddress(); - - if (symbol->GetType() == FunctionSymbol) - { - Ref targetPlatform = view->GetDefaultPlatform(); - func = view->AddFunctionForAnalysis(targetPlatform, symbolAddress); - } - - 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); - } - - 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 callTypeParams; - auto cc = m_dscView->GetDefaultArchitecture()->GetCallingConventionByName("apple-arm64-objc-fast-arc-" + num); - - 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); - } - } -} - - -void SharedCache::InitializeHeader( - std::lock_guard& lock, - Ref view, VM* vm, const SharedCacheMachOHeader& header, std::vector regionsToLoad) -{ - Ref settings = view->GetLoadSettings(VIEW_NAME); - bool applyFunctionStarts = true; - if (settings && settings->Contains("loader.dsc.processFunctionStarts")) - applyFunctionStarts = settings->Get("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); - - BinaryReader virtualReader(view); - - bool applyHeaderTypes = false; - for (const auto& region : regionsToLoad) - { - if (header.textBase >= region->start && header.textBase < region->start + region->size) - { - if (!MemoryRegionIsHeaderInitialized(lock, *region)) - applyHeaderTypes = true; - - 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 - { - 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; - } - - 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(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 targetPlatform = view->GetDefaultPlatform(); - view->AddFunctionForAnalysis(targetPlatform, target); - } - } - - 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); - - if (header.exportTriePresent && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) - { - 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>& 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) - { - uint64_t flags = readValidULEB128(current, end); - if (!(flags & EXPORT_SYMBOL_FLAGS_REEXPORT)) - { - 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; - -#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); - } - } - } - 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> SharedCache::ParseExportTrie(std::shared_ptr linkeditFile, const SharedCacheMachOHeader& header) -{ - if (!header.exportTrie.datasize) - return {}; - - try - { - std::vector> 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>> SharedCache::GetExistingExportListForBaseAddress(std::lock_guard&, 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>> SharedCache::GetExportListForHeader( - std::lock_guard& lock, const SharedCacheMachOHeader& header, - std::function()> provideLinkeditFile, bool* didModifyExportList) -{ - if (auto exportList = GetExistingExportListForBaseAddress(lock, header.textBase)) - { - if (didModifyExportList) - *didModifyExportList = false; - - return exportList; - } - - std::shared_ptr linkeditFile = provideLinkeditFile(); - if (!linkeditFile) - { - if (didModifyExportList) - *didModifyExportList = false; - - return nullptr; - } - - // FIXME: This is the only place ParseExportTrie is used, it can be optimized for the output we need here. - std::vector> exportList = SharedCache::ParseExportTrie(linkeditFile, header); - auto exportMapping = std::make_shared>>(exportList.size()); - for (auto& sym : exportList) - { - exportMapping->insert_or_assign(sym->GetAddress(), std::move(sym)); - } - - m_modifiedState->exportInfos.emplace(header.textBase, exportMapping); - if (didModifyExportList) - *didModifyExportList = true; - - return exportMapping; -} - - -std::vector SharedCache::GetAvailableImages() -{ - std::vector installNames; - installNames.reserve(m_cacheInfo->headers.size()); - for (const auto& header : m_cacheInfo->headers) - { - installNames.push_back(header.second.installName); - } - return installNames; -} - - -std::unordered_map>> 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>> 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(nullptr); - } - }, &doSave); - - if (!exportList) - continue; - - auto& symbols = symbolsByImageName[img.installName]; - symbols.reserve(exportList->size()); - for (const auto& [_, symbol] : *exportList) - { - symbols.push_back(symbol); - } - } - - // 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 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) -{ - 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(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); -} - - -bool SharedCache::SaveCacheInfoToDSCView(std::lock_guard&) -{ - 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); - - { - std::lock_guard lock(m_viewSpecificState->cacheInfoMutex); - if (m_cacheInfo) - { - 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); - } - } - - m_metadataValid = true; - return true; -} - -bool SharedCache::SaveModifiedStateToDSCView(std::lock_guard&) -{ - if (!m_dscView) - return false; - - { - 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 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) - { - m_viewSpecificState->state.memoryRegionStatus[region] = status; - } - 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::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 SharedCache::GetMappedRegions() const -{ - std::scoped_lock lock(m_mutex, m_viewSpecificState->stateMutex); - - std::vector 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& 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>& 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& 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& 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>>& value) -{ - context.writer.StartArray(); - for (const auto& [_, symbol] : *value) - { - Serialize(context, symbol); - } - context.writer.EndArray(); -} - -void Serialize(SerializationContext& context, const std::shared_ptr>>& value) -{ - Serialize(context, *value); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::optional& 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); -} - -// static -std::optional SharedCache::CacheInfo::Load(DeserializationContext& context) -{ - if (!context.doc.HasMember("metadataVersion")) - { - LogError("Shared Cache metadata version missing"); - 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; - } - } - } +#include "SharedCache.h" - return cacheInfo; -} -void State::Store(SerializationContext& context, std::optional viewState) const -{ - MSS(memoryRegionStatus); - MSS(viewState); -} +#include +#include -void SharedCache::ModifiedState::Store(SerializationContext& context) const -{ - State::Store(context, viewState); -} +#include "MachO.h" +#include "SlideInfo.h" -SharedCache::ModifiedState SharedCache::ModifiedState::Load(DeserializationContext& context) -{ - SharedCache::ModifiedState state; - state.MSL(memoryRegionStatus); - state.MSL(viewState); - return state; -} +using namespace BinaryNinja; -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; -} +// The next id to use when calling Cache::AddEntry +static CacheEntryId nextId = 1; -void SharedCache::ModifiedState::Merge(SharedCache::ModifiedState&& newer) +Ref CacheSymbol::ToBNSymbol() const { - memoryRegionStatus.merge(newer.memoryRegionStatus); - exportInfos.merge(newer.exportInfos); - symbolInfos.merge(newer.symbolInfos); - - if (newer.viewState) - viewState = newer.viewState; + QualifiedName qname; + std::string shortName = name; + if (DemangleLLVM(name, qname, true)) + shortName = qname.GetString(); + return new Symbol(type, shortName, shortName, name, address, nullptr); } -void BackingCache::Store(SerializationContext& context) const +std::vector CacheImage::GetDependencies() const { - MSS(path); - MSS_CAST(cacheType, uint32_t); - MSS(mappings); + if (header) + return header->dylibs; + return {}; } -BackingCache BackingCache::Load(DeserializationContext& context) +CacheEntry::CacheEntry(std::string filePath, std::string fileName, CacheEntryType type, dyld_cache_header header, + std::vector mappings, std::unordered_map images) { - BackingCache cache; - cache.MSL(path); - cache.MSL_CAST(cacheType, uint32_t, BNBackingCacheType); - cache.MSL(mappings); - return cache; + 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 CacheImage::Store(SerializationContext& context) const +std::optional CacheEntry::FromFile(const std::string& filePath, const std::string& fileName, CacheEntryType type) { - MSS(installName); - MSS(headerLocation); - MSS(regionStarts); -} + auto file = FileAccessorCache::Global().Open(filePath).lock(); -// static -CacheImage CacheImage::Load(DeserializationContext& context) -{ - CacheImage cacheImage; - cacheImage.MSL(installName); - cacheImage.MSL(headerLocation); - cacheImage.MSL(regionStarts); - return cacheImage; -} + // 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) + return std::nullopt; + const char* magic = (char*)sig.GetData(); + if (strncmp(magic, "dyld", 4) != 0) + return std::nullopt; -void Deserialize(DeserializationContext& context, std::string_view name, std::vector& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& value: array) - b.push_back(BackingCache::LoadFromValue(value)); -} + // 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)); -void Deserialize(DeserializationContext& context, std::string_view name, std::vector& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& value: array) - b.push_back(CacheImage::LoadFromValue(value)); -} + // Read the mappings using the headers `mappingCount` and `mappingOffset`. + dyld_cache_mapping_info currentMapping = {}; + std::vector mappings; + for (size_t i = 0; i < header.mappingCount; i++) + { + file->Read(¤tMapping, header.mappingOffset + (i * sizeof(currentMapping)), sizeof(currentMapping)); + mappings.push_back(currentMapping); + } -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& pair_value : array) + // Handle special entry types. + if (fileName.find(".dylddata") != std::string::npos) { - auto pair = pair_value.GetArray(); - b[pair[0].GetUint64()] = SharedCacheMachOHeader::LoadFromValue(pair[1]); + // We found a single dyld data cache entry file. Mark it as such! + type = CacheEntryType::DyldData; + } + else if (fileName.find(".symbols") != std::string::npos) + { + // We found a single symbols cache entry file. Mark it as such! + type = CacheEntryType::Symbols; + } + else if (mappings.size() == 1 && header.imagesCountOld == 0 && header.imagesCount == 0 + && header.imagesTextOffset == 0) + { + // 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; } -} -void Deserialize(DeserializationContext& context, std::string_view name, AddressRangeMap& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& key_value : array) + // Gather all images for the entry. + std::unordered_map images; + dyld_cache_image_info currentImg {}; + for (size_t i = 0; i < header.imagesCount; i++) { - 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]); + 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); } -} -const std::vector& SharedCache::BackingCaches() const -{ - return m_cacheInfo->backingCaches; -} + // Handle old dyld format that uses old images field. + for (size_t i = 0; i < header.imagesCountOld; i++) + { + 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); + } -DSCViewState SharedCache::ViewState() const { + // 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++) { - std::lock_guard lock(m_mutex); - if (auto& viewState = m_modifiedState->viewState) - return *viewState; + 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); } - return m_viewSpecificState->viewState; + return CacheEntry(filePath, fileName, type, header, mappings, images); } -const std::unordered_map& SharedCache::AllImageStarts() const +WeakFileAccessor CacheEntry::GetAccessor() const { - return m_cacheInfo->imageStarts; + return FileAccessorCache::Global().Open(m_filePath); } -const std::unordered_map& SharedCache::AllImageHeaders() const +std::optional CacheEntry::GetHeaderAddress() const { - return m_cacheInfo->headers; + // The mapping at file offset 0 will contain the header (duh). + return GetMappedAddress(0); } -uint64_t SharedCache::CacheInfo::BaseAddress() const +std::optional CacheEntry::GetMappedAddress(uint64_t fileOffset) const { - uint64_t base = std::numeric_limits::max(); - for (const auto& backingCache : backingCaches) - { - for (const auto& mapping : backingCache.mappings) - { - if (mapping.address < base) - { - base = mapping.address; - break; - } - } - } - return base; + for (const auto& mapping : m_mappings) + if (mapping.fileOffset <= fileOffset && mapping.fileOffset + mapping.size > fileOffset) + return mapping.address + (fileOffset - mapping.fileOffset); + return std::nullopt; } -// Intentionally takes a copy to avoid modifying the cursor position in the original reader. -std::optional SharedCache::GetObjCOptimizationHeader(VMReader reader) const +SharedCache::SharedCache(uint64_t addressSize) { - 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; + m_addressSize = addressSize; + m_vm = std::make_shared(); } -uint64_t SharedCache::GetObjCRelativeMethodBaseAddress(const VMReader& reader) const -{ - if (auto header = GetObjCOptimizationHeader(reader); header.has_value()) - return m_cacheInfo->BaseAddress() + header->relativeMethodSelectorBaseAddressOffset; - return 0; -} -std::shared_ptr SharedCache::MapFile(const std::string& path) +void SharedCache::AddImage(CacheImage image) { - uint64_t baseAddress = m_cacheInfo->BaseAddress(); - return MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), path, - [baseAddress, logger = m_logger](std::shared_ptr mmap) { - ParseAndApplySlideInfoForFile(mmap, baseAddress, logger); - }) - ->lock(); + m_images.insert({image.headerAddress, std::move(image)}); } -std::shared_ptr SharedCache::MapFileWithoutApplyingSlide(const std::string& path) +void SharedCache::AddRegion(CacheRegion region) { - return std::make_shared(path); -} - -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"; + // 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()) + { + AddNonOverlappingRegion(std::move(region)); + return; + } -SharedCacheMetadata::~SharedCacheMetadata() = default; -SharedCacheMetadata::SharedCacheMetadata(SharedCacheMetadata&&) = default; -SharedCacheMetadata& SharedCacheMetadata::operator=(SharedCacheMetadata&&) = default; + // First region past the end of the region. + const auto end = m_regions.lower_bound(regionRange.end); -SharedCacheMetadata::SharedCacheMetadata(SharedCache::CacheInfo cacheInfo, SharedCache::ModifiedState state) : - cacheInfo(std::make_unique(std::move(cacheInfo))), - state(std::make_unique(std::move(state))) -{} + for (auto it = begin; it != end; ++it) + { + const uint64_t newRegionSize = it->second.start - region.start; + if (newRegionSize) + { + CacheRegion newRegion(region); + newRegion.size = newRegionSize; + AddNonOverlappingRegion(std::move(newRegion)); + } + region.start = it->second.start + it->second.size; + region.size -= (newRegionSize + it->second.size); + } -// static -bool SharedCacheMetadata::ViewHasMetadata(BinaryView* view) -{ - return view->QueryMetadata(Tag); + // Add remaining region. + if (region.size > 0) + AddNonOverlappingRegion(std::move(region)); } -std::optional SharedCacheMetadata::ViewMetadataVersion(BinaryView* view) +bool SharedCache::AddNonOverlappingRegion(CacheRegion region) { - // Whether the view has compatible metadata. I.e. if the version differs. - Ref 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")) - return std::nullopt; - return context.doc["metadataVersion"].GetUint(); + auto [_, inserted] = m_regions.insert(std::make_pair(region.AsAddressRange(), std::move(region))); + return inserted; } -// static -std::optional SharedCacheMetadata::LoadFromView(BinaryView* view) +void SharedCache::AddSymbol(CacheSymbol symbol) { - Ref 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)); + m_symbols.insert({symbol.address, std::move(symbol)}); } -const std::unordered_map>>>& SharedCacheMetadata::ExportInfos() const +void SharedCache::AddSymbols(std::vector symbols) { - return state->exportInfos; + for (auto& symbol : symbols) + m_symbols.insert({symbol.address, std::move(symbol)}); } -std::string SharedCacheMetadata::InstallNameForImageBaseAddress(uint64_t baseAddress) const +CacheEntryId SharedCache::AddEntry(CacheEntry entry) { - auto it = std::find_if(cacheInfo->imageStarts.begin(), cacheInfo->imageStarts.end(), [=](auto& pair) { - return pair.second == baseAddress; - }); - - if (it == cacheInfo->imageStarts.end()) - 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++; - return it->first; -} + // Get the file accessor to associate with the virtual memory region. + auto fileAccessor = FileAccessorCache::Global().Open(entry.GetFilePath()); -} // namespace SharedCacheCore + // 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) + { + m_vm->MapRegion(fileAccessor, {mapping.address, mapping.address + mapping.size}, mapping.fileOffset); -void InitDSCViewType() { - MMappedFileAccessor::InitialVMSetup(); - std::atexit(VMShutdown); + // Recalculate the base address. + if (mapping.address < m_baseAddress || m_baseAddress == 0) + m_baseAddress = mapping.address; + } - static DSCViewType type; - BinaryViewType::Register(&type); + // We are done and can make the entry visible to the entire cache. + m_entries.insert({id, std::move(entry)}); + return id; } -extern "C" +bool SharedCache::ProcessEntryImage(const std::string& path, const dyld_cache_image_info& info) { - BNSharedCache* BNGetSharedCache(BNBinaryView* data) + auto imageHeader = SharedCacheMachOHeader::ParseHeaderForAddress(m_vm, info.address, path); + if (!imageHeader.has_value()) + return false; + + // Add the image to the cache. + CacheImage image; + image.headerAddress = info.address; + image.path = path; + + // Add all image regions. + for (const auto& segment : imageHeader->segments) { - if (!data) - return nullptr; + char segName[17]; + memcpy(segName, segment.segname, 16); + segName[16] = 0; - Ref view = new BinaryView(BNNewViewReference(data)); - if (auto cache = SharedCache::GetFromDSCView(view)) + // 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") { - cache->AddAPIRef(); - return cache->GetAPIObject(); + // TODO: Loosen this to any shared region? + if (const auto linkEditRegion = GetRegionAt(segment.vmaddr)) + { + image.regionStarts.push_back(linkEditRegion->start); + continue; + } } - return nullptr; - } + 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; - BNSharedCache* BNNewSharedCacheReference(BNSharedCache* cache) - { - if (!cache->object) - return nullptr; + 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(flags); - cache->object->AddAPIRef(); - return cache; + // Add the image section to the cache and also to the image region starts + AddRegion(sectionRegion); + image.regionStarts.push_back(sectionRegion.start); } - void BNFreeSharedCacheReference(BNSharedCache* cache) - { - if (!cache->object) - return; + // Add the exported symbols to the available symbols. + std::vector exportSymbols = imageHeader->ReadExportSymbolTrie(*m_vm); + AddSymbols(std::move(exportSymbols)); - cache->object->ReleaseAPIRef(); - } + // 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(*imageHeader); - bool BNDSCViewLoadImageWithInstallName(BNSharedCache* cache, char* name, bool skipObjC) - { - std::string imageName = std::string(name); - BNFreeString(name); + AddImage(std::move(image)); + return true; +} - if (cache->object) - return cache->object->LoadImageWithInstallName(imageName, skipObjC); +// At this point all relevant mapping should be loaded in the virtual memory. +void SharedCache::ProcessEntryImages(const CacheEntry& entry) +{ + for (const auto& [imagePath, imageInfo] : entry.GetImages()) + ProcessEntryImage(imagePath, imageInfo); +} - return false; - } +// At this point all relevant mapping should be loaded in the virtual memory. +void SharedCache::ProcessEntryRegions(const CacheEntry& entry) +{ + auto entryHeader = entry.GetHeader(); - bool BNDSCViewLoadSectionAtAddress(BNSharedCache* cache, uint64_t addr) + // Collect pool addresses as non image memory regions. + for (size_t i = 0; i < entryHeader.branchPoolsCount; i++) { - if (cache->object) - { - return cache->object->LoadSectionAtAddress(addr); - } - - return false; - } + 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; - bool BNDSCViewLoadImageContainingAddress(BNSharedCache* cache, uint64_t address, bool skipObjC) - { - if (cache->object) + // Gather all non image regions from the branch islands. + for (const auto& segment : header->segments) { - return cache->object->LoadImageContainingAddress(address, skipObjC); - } + 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(SegmentReadable | SegmentExecutable); + stubIslandRegion.type = CacheRegionType::StubIsland; - return false; + // Add the stub islands to the cache. + AddRegion(std::move(stubIslandRegion)); + } } - 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); - } + // Get the mapping. + const auto& entryMappings = entry.GetMappings(); - void BNDSCViewProcessAllObjCSections(BNSharedCache* cache) + // Add the mapping regions for the given entry type. + // By default, we will just add all the mappings as read-write. + switch (entry.GetType()) { - if (cache->object) - cache->object->ProcessAllObjCSections(); - } - - char** BNDSCViewGetInstallNames(BNSharedCache* cache, size_t* count) + case CacheEntryType::DyldData: { - if (cache->object) + size_t lastMappingIndex = 0; + for (const auto& mapping : entryMappings) { - auto value = cache->object->GetAvailableImages(); - *count = value.size(); + 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; - std::vector 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()); + // Add the dyld data mapping as a region to the cache. + AddRegion(std::move(mappingRegion)); } - *count = 0; - return nullptr; + break; } - - BNDSCSymbolRep* BNDSCViewLoadAllSymbolsAndWait(BNSharedCache* cache, size_t* count) + case CacheEntryType::Stub: { - if (cache->object) - { - auto symbolsByImageName = cache->object->LoadAllSymbolsAndWait(); - size_t totalSymbolCount = 0; - for (const auto& [_, symbols] : symbolsByImageName) - { - totalSymbolCount += symbols.size(); - } - *count = totalSymbolCount; + // 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(SegmentReadable | SegmentExecutable); + stubIslandRegion.type = CacheRegionType::StubIsland; - 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; - } - } - assert(i == totalSymbolCount); - return outputSymbols; - } - *count = 0; - return nullptr; + // Add the stub island to the cache. + AddRegion(std::move(stubIslandRegion)); } - - void BNDSCViewFreeSymbols(BNDSCSymbolRep* symbols, size_t count) + default: { - for (size_t i = 0; i < count; i++) + // Fill in all the gaps in the mapping with non image regions. + size_t lastMappingIndex = 0; + for (const auto& mapping : entryMappings) { - BNFreeStringRef(symbols[i].name); - BNFreeString(symbols[i].image); + // 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(SegmentReadable | SegmentWritable); + nonImageRegion.type = CacheRegionType::NonImage; + AddRegion(std::move(nonImageRegion)); } - delete symbols; + break; } - - 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) +void SharedCache::ProcessEntrySlideInfo(const CacheEntry& entry) +{ + auto slideInfoProcessor = SlideInfoProcessor(GetBaseAddress()); + slideInfoProcessor.ProcessEntry(*m_vm, entry); +} + +std::optional SharedCache::GetEntryContaining(const uint64_t address) const +{ + for (const auto& [_, entry] : m_entries) { - if (cache->object) + for (const auto& mapping : entry.GetMappings()) { - return BNAllocString(cache->object->ImageNameForAddress(address).c_str()); + if (address >= mapping.address && address < mapping.address + mapping.size) + return entry; } - - return nullptr; } - uint64_t BNDSCViewLoadedImageCount(BNSharedCache* cache) - { - // FIXME? - return 0; - } + return std::nullopt; +} - BNDSCViewState BNDSCViewGetState(BNSharedCache* cache) +std::optional SharedCache::GetEntryWithImage(const CacheImage& image) const +{ + for (const auto& [_, entry] : m_entries) { - if (cache->object) + for (const auto& [_, currentImage] : entry.GetImages()) { - return (BNDSCViewState)cache->object->ViewState(); + if (currentImage.address == image.headerAddress) + return entry; } - - return BNDSCViewState::Unloaded; } + return std::nullopt; +} - 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; - } +std::optional SharedCache::GetRegionAt(const uint64_t address) const +{ + const auto it = m_regions.find(address); + if (it == m_regions.end()) + return std::nullopt; + return it->second; +} - void BNDSCViewFreeLoadedRegions(BNDSCMappedMemoryRegion* images, size_t count) - { - for (size_t i = 0; i < count; i++) - { - BNFreeString(images[i].name); - } - delete images; - } +std::optional SharedCache::GetRegionContaining(const uint64_t address) const +{ + for (const auto& [range, region] : m_regions) + if (address >= range.start && address < range.end) + return region; + return std::nullopt; +} +std::optional SharedCache::GetImageAt(const uint64_t address) const +{ + const auto it = m_images.find(address); + if (it == m_images.end()) + return std::nullopt; + return it->second; +} - BNDSCBackingCache* BNDSCViewGetBackingCaches(BNSharedCache* cache, size_t* count) - { - BNDSCBackingCache* caches = nullptr; +std::optional SharedCache::GetImageContaining(const uint64_t address) const +{ + // 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; +} - if (cache->object) - { - auto viewCaches = cache->object->BackingCaches(); - *count = viewCaches.size(); - caches = new BNDSCBackingCache[viewCaches.size()]; - for (size_t i = 0; i < viewCaches.size(); i++) - { - caches[i].path = BNAllocString(viewCaches[i].path.c_str()); - caches[i].cacheType = viewCaches[i].cacheType; +std::optional SharedCache::GetImageWithName(const std::string& name) const +{ + for (const auto& [address, image] : m_images) + if (image.path == name) + return image; + return std::nullopt; +} - BNDSCBackingCacheMapping* mappings; - mappings = new BNDSCBackingCacheMapping[viewCaches[i].mappings.size()]; +std::optional SharedCache::GetSymbolAt(uint64_t address) const +{ + const auto it = m_symbols.find(address); + if (it == m_symbols.end()) + return std::nullopt; + return it->second; +} - 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(); - } - } +std::optional SharedCache::GetSymbolWithName(const std::string& name) const +{ + for (const auto& [address, symbol] : m_symbols) + if (symbol.name == name) + return symbol; + return std::nullopt; +} - return caches; - } +CacheProcessor::CacheProcessor(Ref view) +{ + m_view = std::move(view); + m_logger = new Logger("CacheProcessor", m_view->GetFile()->GetSessionId()); +} - 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; - } +bool CacheProcessor::ProcessCache(SharedCache& cache) +{ + // If we are in a project, use the project cache processor. + if (m_view->GetFile()->GetProjectFile()) + return ProcessProjectCache(cache); + return ProcessFileCache(cache); +} - void BNDSCFindSymbolAtAddressAndApplyToAddress(BNSharedCache* cache, uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) +bool CacheProcessor::ProcessFileCache(SharedCache& 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); + + // Add this file to the entries + try { - if (cache->object) - { - cache->object->FindSymbolAtAddrAndApplyToAddr(symbolLocation, targetLocation, triggerReanalysis); - } - } + auto baseEntry = CacheEntry::FromFile(baseFilePath, baseFileName, CacheEntryType::Primary); + if (!baseEntry.has_value()) + return false; - BNDSCImage* BNDSCViewGetAllImages(BNSharedCache* cache, size_t* count) + // Before we do anything else, add this to the cache so it's available to other entries. + cache.AddEntry(std::move(*baseEntry)); + } + catch (const std::exception& e) { - 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) - { - 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++; - } - 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; + // Just return false so the view init can continue. + return false; } - void BNDSCViewFreeAllImages(BNDSCImage* images, 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)) { - for (size_t i = 0; i < count; i++) + 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 { - for (size_t j = 0; j < images[i].mappingCount; j++) + auto additionalEntry = CacheEntry::FromFile(currentFilePath, currentFileName, CacheEntryType::Secondary); + if (!additionalEntry.has_value()) { - BNFreeString(images[i].mappings[j].name); - BNFreeString(images[i].mappings[j].filePath); + m_logger->LogErrorF("Failed to load entry {}...", currentFileName); + continue; } - delete[] images[i].mappings; - BNFreeString(images[i].name); - } - delete[] images; - } - char* BNDSCViewGetImageHeaderForAddress(BNSharedCache* cache, uint64_t address) - { - if (cache->object) - { - auto header = cache->object->SerializedImageHeaderForAddress(address); - return BNAllocString(header.c_str()); + // Add this file as an entry to the cache + cache.AddEntry(std::move(*additionalEntry)); } - - return nullptr; + catch (const std::exception& e) + {} } - 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 true; +} - 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()); - BNDSCMemoryUsageInfo BNDSCViewGetMemoryUsageInfo() - { - BNDSCMemoryUsageInfo info; - info.mmapRefs = MMapCount(); - info.sharedCacheRefs = sharedCacheReferences.load(); - return info; - } + // 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)); - BNDSCViewLoadProgress BNDSCViewGetLoadProgress(uint64_t sessionID) + // 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()) { - if (auto viewSpecificState = ViewSpecificStateForId(sessionID, false)) - return viewSpecificState->progress; + 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)) + { + try + { + auto additionalEntry = CacheEntry::FromFile(projectFilePath, projectFileName, CacheEntryType::Secondary); + if (!additionalEntry.has_value()) + { + m_logger->LogErrorF("Failed to load entry {}...", projectFileName); + continue; + } - return LoadProgressNotStarted; + // Add this file as an entry to the cache + cache.AddEntry(std::move(*additionalEntry)); + } + catch (const std::exception& e) + {} + } } - uint64_t BNDSCViewFastGetBackingCacheCount(BNBinaryView* data) - { - Ref 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 +#include -#include -#include -#include -#include -#include -#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 +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, - }; + CacheSymbol(BNSymbolType type, uint64_t address, std::string name) : + type(type), address(address), name(std::move(name)) + {} - struct MemoryRegion : public MetadataSerializable - { - 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; - - - AddressRange AsAddressRange() const - { - return {start, start + size}; - } - - void Store(SerializationContext& context) const - { - MSS(prettyName); - MSS(start); - MSS(size); - MSS(imageStart); - MSS_CAST(flags, uint64_t); - MSS_CAST(type, uint8_t); - } - - 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); - - return region; - } - }; - - struct CacheImage : public MetadataSerializable { - std::string installName; - uint64_t headerLocation; - // Start addresses of the memory regions in this image. - std::vector regionStarts; - - void Store(SerializationContext& context) const; - static CacheImage Load(DeserializationContext& context); - }; - - #if defined(__GNUC__) || defined(__clang__) - #define PACKED_STRUCT __attribute__((packed)) - #else - #define PACKED_STRUCT - #endif - - #if defined(_MSC_VER) - #pragma pack(push, 1) - #else - - #endif - - 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 { - std::string path; - BNBackingCacheType cacheType = BackingCacheTypeSecondary; - std::vector mappings; - - void Store(SerializationContext& context) const; - static BackingCache Load(DeserializationContext& context); - }; - - struct LoadedMapping - { - std::shared_ptr backingFile; - dyld_cache_mapping_info mappingInfo; - }; + ~CacheSymbol() = 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]; - }; - - 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; - }; + CacheSymbol(const CacheSymbol& other) = default; + CacheSymbol& operator=(const CacheSymbol& other) = default; - // 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 - }; + CacheSymbol(CacheSymbol&& other) noexcept = default; - // dyld_cache_slide_info4 is used in watchOS which we are not close to supporting right now. + CacheSymbol& operator=(CacheSymbol&& other) noexcept = default; - #define DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing + // NOTE: you should really only call this when adding the symbol to the view. + BinaryNinja::Ref ToBNSymbol() const; +}; - 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 */]; - }; +enum class CacheRegionType +{ + Image, + StubIsland, + DyldData, + NonImage, +}; +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 imageStart; + BNSegmentFlag flags; - struct PACKED_STRUCT dyld_cache_image_info - { - uint64_t address; - uint64_t modTime; - uint64_t inode; - uint32_t pathFileOffset; - uint32_t pad; - }; - - 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; - }; + CacheRegion() = default; + ~CacheRegion() = 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 - }; - - 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 - }; + CacheRegion(const CacheRegion& other) = default; - 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 - }; + CacheRegion& operator=(const CacheRegion& other) = default; - 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]; - }; + CacheRegion(CacheRegion&& other) noexcept = 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; - }; - - #if defined(_MSC_VER) - #pragma pack(pop) - #else - - #endif - - struct SharedCacheMachOHeader : public MetadataSerializable - { - uint64_t textBase = 0; - uint64_t loadCommandOffset = 0; - mach_header_64 ident; - std::string identifierPrefix; - std::string installName; - - std::vector> entryPoints; - std::vector m_entryPoints; // list of entrypoints - - symtab_command symtab; - dysymtab_command dysymtab; - dyld_info_command dyldInfo; - routines_command_64 routines64; - function_starts_command functionStarts; - std::vector moduleInitSections; - linkedit_data_command exportTrie; - 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 segments; // only three types of sections __TEXT, __DATA, __IMPORT - segment_command_64 linkeditSegment; - std::vector sections; - std::vector sectionNames; - - std::vector symbolStubSections; - std::vector symbolPointerSections; - - std::vector dylibs; - - build_version_command buildVersion; - std::vector 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; - - 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); - } - - 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; - } - }; - - 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; - }; + CacheRegion& operator=(CacheRegion&& other) noexcept = default; + AddressRange AsAddressRange() const { return {start, start + size}; } - class ScopedVMMapSession; - - static std::atomic sharedCacheReferences = 0; - - class SharedCache + BNSectionSemantics SectionSemanticsForRegion() const { - IMPLEMENT_SHAREDCACHE_API_OBJECT(BNSharedCache); - - std::atomic m_refs = 0; - - public: - virtual void AddRef() { m_refs.fetch_add(1); } + if ((flags & SegmentExecutable) && (flags & SegmentDenyWrite)) + return ReadOnlyCodeSectionSemantics; - 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; - }); - } + if (flags & SegmentExecutable) + return DefaultSectionSemantics; + + if (flags & SegmentDenyWrite) + return ReadOnlyDataSectionSemantics; + + return ReadWriteDataSectionSemantics; + } +}; - virtual void AddAPIRef() { AddRef(); } +// 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 regionStarts; + std::shared_ptr header; + + CacheImage() = default; + + ~CacheImage() = default; + + CacheImage(const CacheImage& other) = default; + + CacheImage& operator=(const CacheImage& other) = default; + + CacheImage(CacheImage&& other) noexcept = default; + + CacheImage& operator=(CacheImage&& other) noexcept = default; + + // Get the file name from the path. + std::string GetName() const { return BaseFileName(path); } + + // Get the names of the dependencies. + std::vector GetDependencies() const; +}; + +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, +}; + +// 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 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 m_images {}; - virtual void ReleaseAPIRef() { Release(); } +public: + CacheEntry(std::string filePath, std::string fileName, CacheEntryType type, dyld_cache_header header, + std::vector mappings, std::unordered_map images); - public: - enum SharedCacheFormat - { - RegularCacheFormat, - SplitCacheFormat, - LargeCacheFormat, - iOS16CacheFormat, - }; + CacheEntry() = default; - struct CacheInfo; - struct ModifiedState; + CacheEntry(const CacheEntry&) = default; - struct ViewSpecificState; + CacheEntry(CacheEntry&&) = default; + CacheEntry& operator=(CacheEntry&&) = default; - private: - Ref m_logger; - /* VIEW STATE BEGIN -- SERIALIZE ALL OF THIS AND STORE IT IN RAW VIEW */ + // Construct a cache entry from the file on disk. + // TODO: Seperate this out a bit more. + static std::optional FromFile(const std::string& filePath, const std::string& fileName, CacheEntryType type); - // State that is initialized during `PerformInitialLoad` and does - // not change thereafter. - std::shared_ptr m_cacheInfo; + // TODO: From Project file? - // Protects member variables below. - mutable std::mutex m_mutex; + WeakFileAccessor GetAccessor() const; - // 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 m_modifiedState; + // Get the headers virtual address. + // This is useful if you need to read relative to the start of the entry file. + std::optional GetHeaderAddress() const; - // Serialized once by PerformInitialLoad and available after m_viewState == Loaded - bool m_metadataValid = false; + // 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 GetMappedAddress(uint64_t fileOffset) const; - /* VIEWSTATE END -- NOTHING PAST THIS IS SERIALIZED */ + 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& GetMappings() const { return m_mappings; } + const std::unordered_map& GetImages() const { return m_images; } +}; - /* API VIEW START */ - BinaryNinja::Ref m_dscView; - /* API VIEW END */ +// The ID for a given CacheEntry, use this instead of passing a pointer around to avoid complexity :V +typedef uint32_t CacheEntryId; - std::shared_ptr m_viewSpecificState; +// TODO: Add a "ViewCache" that keeps track of what has been added to the view. - private: - void PerformInitialLoad(std::lock_guard&); - bool DeserializeFromRawView(std::lock_guard&); +// 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 m_vm; + std::unordered_map m_entries {}; + // This information is used in tandem with the cache images to load memory regions into the binary view. + AddressRangeMap m_regions {}; + // Describes the images of the cache. + std::unordered_map m_images {}; + // All the symbols for this cache. Both mapped and unmapped (not in the view). + std::unordered_map m_symbols {}; - public: - std::shared_ptr GetVMMap(); - std::shared_ptr GetVMMap(const CacheInfo& staticState); + bool ProcessEntryImage(const std::string& path, const dyld_cache_image_info& info); - static SharedCache* GetFromDSCView(BinaryNinja::Ref dscView); - static uint64_t FastGetBackingCacheCount(BinaryNinja::Ref dscView); - bool SaveCacheInfoToDSCView(std::lock_guard&); - bool SaveModifiedStateToDSCView(std::lock_guard&); + // Add a region known not to overlap with another, otherwise use AddRegion. + // returns whether the region was inserted. + bool AddNonOverlappingRegion(CacheRegion region); - static void ParseAndApplySlideInfoForFile(std::shared_ptr file, uint64_t baseAddress, Ref logger); - std::optional 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 GetAvailableImages(); +public: + explicit SharedCache(uint64_t addressSize); - std::vector GetMappedRegions() const; - bool IsMemoryMapped(uint64_t address); + uint64_t GetBaseAddress() const { return m_baseAddress; } + std::shared_ptr GetVirtualMemory() { return m_vm; } + const std::unordered_map& GetEntries() const { return m_entries; } + const AddressRangeMap& GetRegions() const { return m_regions; } + const std::unordered_map& GetImages() const { return m_images; } + const std::unordered_map& GetSymbols() const { return m_symbols; } - std::unordered_map>> LoadAllSymbolsAndWait(); + void AddImage(CacheImage image); - const std::unordered_map& AllImageStarts() const; - const std::unordered_map& AllImageHeaders() const; + // Add a region that may overlap with another. + void AddRegion(CacheRegion region); - std::string SerializedImageHeaderForAddress(uint64_t address); - std::string SerializedImageHeaderForName(std::string name); + void AddSymbol(CacheSymbol symbol); - void FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis); + void AddSymbols(std::vector symbols); - const std::vector& BackingCaches() const; + // 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); - DSCViewState ViewState() const; + void ProcessEntryImages(const CacheEntry& entry); - explicit SharedCache(BinaryNinja::Ref rawView); - virtual ~SharedCache(); + void ProcessEntryRegions(const CacheEntry& entry); - uint64_t GetObjCRelativeMethodBaseAddress(const VMReader& reader) const; + void ProcessEntrySlideInfo(const CacheEntry& entry); -private: - std::optional LoadHeaderForAddress( - std::shared_ptr vm, uint64_t address, std::string installName); - void InitializeHeader( - std::lock_guard&, Ref view, VM* vm, const SharedCacheMachOHeader& header, - std::vector regionsToLoad); - void ReadExportNode(std::vector>& 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> ParseExportTrie( - std::shared_ptr linkeditFile, const SharedCacheMachOHeader& header); - std::shared_ptr>> GetExportListForHeader(std::lock_guard&, const SharedCacheMachOHeader& header, - std::function()> provideLinkeditFile, bool* didModifyExportList = nullptr); - std::shared_ptr>> GetExistingExportListForBaseAddress(std::lock_guard&, uint64_t baseAddress) const; - void ProcessSymbols(std::shared_ptr file, const SharedCacheMachOHeader& header, - uint64_t stringsOffset, size_t stringsSize, uint64_t nlistEntriesOffset, uint32_t nlistCount, uint32_t nlistStartIndex = 0); - void ApplySymbol(Ref view, Ref typeLib, Ref symbol); + std::optional GetEntryContaining(uint64_t address) const; - void ProcessAllObjCSections(std::lock_guard&); - bool LoadImageWithInstallName(std::lock_guard&, std::string installName, bool skipObjC); + std::optional GetEntryWithImage(const CacheImage& image) const; - bool MemoryRegionIsLoaded(std::lock_guard&, const MemoryRegion& region) const; - void SetMemoryRegionIsLoaded(std::lock_guard&, const MemoryRegion& region); - bool MemoryRegionIsHeaderInitialized(std::lock_guard&, const MemoryRegion& region) const; - void SetMemoryRegionHeaderInitialized(std::lock_guard&, const MemoryRegion& region); + std::optional GetRegionAt(uint64_t address) const; - Ref TypeLibraryForImage(const std::string& installName); + std::optional GetRegionContaining(uint64_t address) const; - std::optional GetObjCOptimizationHeader(VMReader reader) const; - - std::shared_ptr MapFile(const std::string& path); - static std::shared_ptr MapFileWithoutApplyingSlide(const std::string& path); - }; - - class SharedCacheMetadata - { - public: - static std::optional LoadFromView(BinaryView*); - static bool ViewHasMetadata(BinaryView*); - static std::optional ViewMetadataVersion(BinaryView*); + std::optional GetImageAt(uint64_t address) const; - const std::unordered_map>>>& ExportInfos() const; - std::string InstallNameForImageBaseAddress(uint64_t baseAddress) const; + std::optional 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 GetImageWithName(const std::string& name) const; - private: - SharedCacheMetadata(SharedCache::CacheInfo, SharedCache::ModifiedState); + std::optional GetSymbolAt(uint64_t address) const; - std::unique_ptr cacheInfo; - std::unique_ptr state; + std::optional 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 m_view; + BinaryNinja::Ref 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 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 + +#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>& GlobalControllers() +{ + // To make initialization order consistent we place the static in a function. + static std::map> 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 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) : m_cache(std::move(cache)) +{ + INIT_DSC_API_OBJECT(); + m_logger = std::move(logger); + m_loadedRegions = {}; + m_loadedImages = {}; +} + +DSCRef SharedCacheController::Initialize(BinaryView& view, SharedCache cache) +{ + auto id = GetViewIdFromView(view); + std::unique_lock lock(GlobalControllersMutex); + auto logger = new Logger("SharedCacheController", view.GetFile()->GetSessionId()); + DSCRef dscView = new SharedCacheController(std::move(cache), logger); + + // Pull the settings from the view. + if (Ref settings = view.GetLoadSettings(VIEW_NAME)) + { + if (settings->Contains("loader.dsc.processObjC")) + dscView->m_processObjC = settings->Get("loader.dsc.processObjC", &view); + if (settings->Contains("loader.dsc.processCFStrings")) + dscView->m_processCFStrings = settings->Get("loader.dsc.processCFStrings", &view); + if (settings->Contains("loader.dsc.regionFilter")) + dscView->m_regionFilter = std::regex(settings->Get("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::FromView(BinaryView& view) +{ + auto id = GetViewIdFromView(view); + std::shared_lock 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 SharedCacheController::GetMetadata() const +{ + std::map> controllerMeta; + + std::vector loadedImages; + std::vector 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 + +#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 m_logger; + + SharedCache m_cache; + // Store the open images. + // Things other than the cache here will be serialized. + std::unordered_set m_loadedRegions; + std::unordered_set m_loadedImages; + + // Settings from the view. + std::regex m_regionFilter; + bool m_processObjC; + bool m_processCFStrings; + + explicit SharedCacheController(SharedCache cache, Ref logger); + + public: + // Initialize the DSCacheView, this should be called from the view initialize function only! + static DSCRef 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 FromView(BinaryView& view); + + SharedCache& GetCache() { return m_cache; }; + const std::unordered_set& GetLoadedRegions() { return m_loadedRegions; }; + const std::unordered_set& 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 GetMetadata() const; + + void LoadMetadata(const Metadata& metadata); + }; +} // namespace BinaryNinja::DSC diff --git a/view/sharedcache/core/SharedCacheView.cpp b/view/sharedcache/core/SharedCacheView.cpp new file mode 100644 index 00000000..bf80c6eb --- /dev/null +++ b/view/sharedcache/core/SharedCacheView.cpp @@ -0,0 +1,854 @@ +#include "SharedCacheView.h" +#include +#include "SharedCacheController.h" +#include "FileAccessorCache.h" +#include "MappedFileAccessor.h" + +using namespace BinaryNinja; +using namespace BinaryNinja::DSC; + +SharedCacheViewType::SharedCacheViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME) {} + +// 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 SharedCacheViewType::Create(BinaryView* data) +{ + return new SharedCacheView(VIEW_NAME, data, false); +} + +Ref SharedCacheViewType::GetLoadSettingsForData(BinaryView* data) +{ + Ref 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 = GetDefaultLoadSettingsForData(viewRef); + + // specify default load settings that can be overridden + std::vector 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 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 loadSettings = viewRef->GetLoadSettings(GetName()); + if (loadSettings && !loadSettings->IsEmpty()) + settings->DeserializeSchema(loadSettings->SerializeSchema()); + + return settings; +} + +Ref 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"); +} + +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. + // DSCPlatformMacCatalyst = 6, + DSCPlatformiOSSimulator = 7, + DSCPlatformTVOSSimulator = 8, + DSCPlatformWatchOSSimulator = 9, + DSCPlatformVisionOS = 11, // Apple Vision Pro + DSCPlatformVisionOSSimulator = 12 // Apple Vision Pro Simulator +}; + +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: + 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") + { + arch = "aarch64"; + } + else if (std::string(magic) == "dyld_v1 x86_64" || std::string(magic) == "dyld_v1 x86_64h") + { + arch = "x86_64"; + } + else + { + logger->LogError("Unknown magic: %s", magic); + return false; + } + + SetDefaultPlatform(Platform::GetByName(os + "-" + arch)); + SetDefaultArchitecture(Architecture::GetByName(arch)); + + Ref settings = GetLoadSettings(GetTypeName()); + + if (!settings) + { + Ref programSettings = Settings::Instance(); + programSettings->Set("analysis.workflows.functionWorkflow", "core.function.sharedCache", this); + } + + if (m_parseOnly) + return true; + + QualifiedNameAndType headerType; + std::string err; + + 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\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" + "\t\tuint64_t slideInfoOffsetUnused;\t // unused. Used to be file offset of kernel slid info\n" + "\t\tuint64_t slideInfoSizeUnused;\t // unused. Used to be size of kernel slid info\n" + "\t\tuint64_t localSymbolsOffset;\t // file offset of where local symbols are stored\n" + "\t\tuint64_t localSymbolsSize;\t\t // size of local symbols information\n" + "\t\tuint8_t uuid[16];\t\t\t\t // unique value for each shared cache file\n" + "\t\tuint64_t cacheType;\t\t\t\t // 0 for development, 1 for production // Kat: , 2 for iOS 16?\n" + "\t\tuint32_t branchPoolsOffset;\t\t // file offset to table of uint64_t pool addresses\n" + "\t\tuint32_t branchPoolsCount;\t\t // number of uint64_t entries\n" + "\t\tuint64_t accelerateInfoAddr;\t // (unslid) address of optimization info\n" + "\t\tuint64_t accelerateInfoSize;\t // size of optimization info\n" + "\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 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" + "\t\tuint64_t progClosuresSize;\t\t\t // size of list of program launch closures\n" + "\t\tuint64_t progClosuresTrieAddr;\t\t // (unslid) address of trie of indexes into program launch closures\n" + "\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\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\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" + "\t\tuint64_t maxSlide;\t\t\t\t // runtime slide of cache can be between zero and this value\n" + "\t\tuint64_t dylibsImageArrayAddr;\t // (unslid) address of ImageArray for dylibs in this cache\n" + "\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 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 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" + "\t\tuint64_t dylibsPBLStateArrayAddrUnused;\t // unused\n" + "\t\tuint64_t dylibsPBLSetAddr;\t\t\t\t // (unslid) address of PrebuiltLoaderSet of all cached dylibs\n" + "\t\tuint64_t programsPBLSetPoolAddr;\t\t // (unslid) address of pool of PrebuiltLoaderSet for each program\n" + "\t\tuint64_t programsPBLSetPoolSize;\t\t // size of pool of PrebuiltLoaderSet for each program\n" + "\t\tuint64_t programTrieAddr;\t\t\t\t // (unslid) address of trie mapping program path to PrebuiltLoaderSet\n" + "\t\tuint32_t programTrieSize;\n" + "\t\tuint32_t osVersion;\t\t\t\t// OS Version of dylibs in this cache for the main platform\n" + "\t\tuint32_t altPlatform;\t\t\t// e.g. iOSMac on macOS\n" + "\t\tuint32_t altOsVersion;\t\t\t// e.g. 14.0 for iOSMac\n" + "\t\tuint64_t swiftOptsOffset;\t\t// file offset to Swift optimizations header\n" + "\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\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 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\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 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 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); + + if (!err.empty() || !headerType.type) + { + logger->LogError("Failed to parse header type: %s", err.c_str()); + return false; + } + + // Add Mach-O file header type info + EnumerationBuilder cpuTypeBuilder; + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ANY", MACHO_CPU_TYPE_ANY); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_VAX", MACHO_CPU_TYPE_VAX); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC680x0", MACHO_CPU_TYPE_MC680x0); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_X86", MACHO_CPU_TYPE_X86); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_X86_64", MACHO_CPU_TYPE_X86_64); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MIPS", MACHO_CPU_TYPE_MIPS); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC98000", MACHO_CPU_TYPE_MC98000); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_HPPA", MACHO_CPU_TYPE_HPPA); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM", MACHO_CPU_TYPE_ARM); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM64", MACHO_CPU_TYPE_ARM64); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ARM64_32", MACHO_CPU_TYPE_ARM64_32); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_MC88000", MACHO_CPU_TYPE_MC88000); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_SPARC", MACHO_CPU_TYPE_SPARC); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_I860", MACHO_CPU_TYPE_I860); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ALPHA", MACHO_CPU_TYPE_ALPHA); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_POWERPC", MACHO_CPU_TYPE_POWERPC); + cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_POWERPC64", MACHO_CPU_TYPE_POWERPC64); + Ref cpuTypeEnum = cpuTypeBuilder.Finalize(); + + Ref cpuTypeEnumType = Type::EnumerationType(nullptr, cpuTypeEnum, 4, false); + std::string cpuTypeEnumName = "cpu_type_t"; + std::string cpuTypeEnumId = Type::GenerateAutoTypeId("macho", cpuTypeEnumName); + DefineType(cpuTypeEnumId, cpuTypeEnumName, cpuTypeEnumType); + + EnumerationBuilder fileTypeBuilder; + fileTypeBuilder.AddMemberWithValue("MH_OBJECT", MH_OBJECT); + fileTypeBuilder.AddMemberWithValue("MH_EXECUTE", MH_EXECUTE); + fileTypeBuilder.AddMemberWithValue("MH_FVMLIB", MH_FVMLIB); + fileTypeBuilder.AddMemberWithValue("MH_CORE", MH_CORE); + fileTypeBuilder.AddMemberWithValue("MH_PRELOAD", MH_PRELOAD); + fileTypeBuilder.AddMemberWithValue("MH_DYLIB", MH_DYLIB); + fileTypeBuilder.AddMemberWithValue("MH_DYLINKER", MH_DYLINKER); + fileTypeBuilder.AddMemberWithValue("MH_BUNDLE", MH_BUNDLE); + fileTypeBuilder.AddMemberWithValue("MH_DYLIB_STUB", MH_DYLIB_STUB); + fileTypeBuilder.AddMemberWithValue("MH_DSYM", MH_DSYM); + fileTypeBuilder.AddMemberWithValue("MH_KEXT_BUNDLE", MH_KEXT_BUNDLE); + fileTypeBuilder.AddMemberWithValue("MH_FILESET", MH_FILESET); + Ref fileTypeEnum = fileTypeBuilder.Finalize(); + + Ref fileTypeEnumType = Type::EnumerationType(nullptr, fileTypeEnum, 4, false); + std::string fileTypeEnumName = "file_type_t"; + std::string fileTypeEnumId = Type::GenerateAutoTypeId("macho", fileTypeEnumName); + DefineType(fileTypeEnumId, fileTypeEnumName, fileTypeEnumType); + + EnumerationBuilder flagsTypeBuilder; + flagsTypeBuilder.AddMemberWithValue("MH_NOUNDEFS", MH_NOUNDEFS); + flagsTypeBuilder.AddMemberWithValue("MH_INCRLINK", MH_INCRLINK); + flagsTypeBuilder.AddMemberWithValue("MH_DYLDLINK", MH_DYLDLINK); + flagsTypeBuilder.AddMemberWithValue("MH_BINDATLOAD", MH_BINDATLOAD); + flagsTypeBuilder.AddMemberWithValue("MH_PREBOUND", MH_PREBOUND); + flagsTypeBuilder.AddMemberWithValue("MH_SPLIT_SEGS", MH_SPLIT_SEGS); + flagsTypeBuilder.AddMemberWithValue("MH_LAZY_INIT", MH_LAZY_INIT); + flagsTypeBuilder.AddMemberWithValue("MH_TWOLEVEL", MH_TWOLEVEL); + flagsTypeBuilder.AddMemberWithValue("MH_FORCE_FLAT", MH_FORCE_FLAT); + flagsTypeBuilder.AddMemberWithValue("MH_NOMULTIDEFS", MH_NOMULTIDEFS); + flagsTypeBuilder.AddMemberWithValue("MH_NOFIXPREBINDING", MH_NOFIXPREBINDING); + flagsTypeBuilder.AddMemberWithValue("MH_PREBINDABLE", MH_PREBINDABLE); + flagsTypeBuilder.AddMemberWithValue("MH_ALLMODSBOUND", MH_ALLMODSBOUND); + flagsTypeBuilder.AddMemberWithValue("MH_SUBSECTIONS_VIA_SYMBOLS", MH_SUBSECTIONS_VIA_SYMBOLS); + flagsTypeBuilder.AddMemberWithValue("MH_CANONICAL", MH_CANONICAL); + flagsTypeBuilder.AddMemberWithValue("MH_WEAK_DEFINES", MH_WEAK_DEFINES); + flagsTypeBuilder.AddMemberWithValue("MH_BINDS_TO_WEAK", MH_BINDS_TO_WEAK); + flagsTypeBuilder.AddMemberWithValue("MH_ALLOW_STACK_EXECUTION", MH_ALLOW_STACK_EXECUTION); + flagsTypeBuilder.AddMemberWithValue("MH_ROOT_SAFE", MH_ROOT_SAFE); + flagsTypeBuilder.AddMemberWithValue("MH_SETUID_SAFE", MH_SETUID_SAFE); + flagsTypeBuilder.AddMemberWithValue("MH_NO_REEXPORTED_DYLIBS", MH_NO_REEXPORTED_DYLIBS); + flagsTypeBuilder.AddMemberWithValue("MH_PIE", MH_PIE); + flagsTypeBuilder.AddMemberWithValue("MH_DEAD_STRIPPABLE_DYLIB", MH_DEAD_STRIPPABLE_DYLIB); + flagsTypeBuilder.AddMemberWithValue("MH_HAS_TLV_DESCRIPTORS", MH_HAS_TLV_DESCRIPTORS); + flagsTypeBuilder.AddMemberWithValue("MH_NO_HEAP_EXECUTION", MH_NO_HEAP_EXECUTION); + flagsTypeBuilder.AddMemberWithValue("MH_APP_EXTENSION_SAFE", _MH_APP_EXTENSION_SAFE); + flagsTypeBuilder.AddMemberWithValue("MH_NLIST_OUTOFSYNC_WITH_DYLDINFO", _MH_NLIST_OUTOFSYNC_WITH_DYLDINFO); + flagsTypeBuilder.AddMemberWithValue("MH_SIM_SUPPORT", _MH_SIM_SUPPORT); + flagsTypeBuilder.AddMemberWithValue("MH_DYLIB_IN_CACHE", _MH_DYLIB_IN_CACHE); + Ref flagsTypeEnum = flagsTypeBuilder.Finalize(); + + Ref flagsTypeEnumType = Type::EnumerationType(nullptr, flagsTypeEnum, 4, false); + std::string flagsTypeEnumName = "flags_type_t"; + std::string flagsTypeEnumId = Type::GenerateAutoTypeId("macho", flagsTypeEnumName); + DefineType(flagsTypeEnumId, flagsTypeEnumName, flagsTypeEnumType); + + StructureBuilder machoHeaderBuilder; + machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "magic"); + machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("cpu_type_t")), "cputype"); + machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "cpusubtype"); + machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("file_type_t")), "filetype"); + machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "ncmds"); + machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "sizeofcmds"); + machoHeaderBuilder.AddMember(Type::NamedType(this, QualifiedName("flags_type_t")), "flags"); + if (GetAddressSize() == 8) + machoHeaderBuilder.AddMember(Type::IntegerType(4, false), "reserved"); + Ref machoHeaderStruct = machoHeaderBuilder.Finalize(); + QualifiedName headerName = (GetAddressSize() == 8) ? std::string("mach_header_64") : std::string("mach_header"); + + std::string headerTypeId = Type::GenerateAutoTypeId("macho", headerName); + Ref machoHeaderType = Type::StructureType(machoHeaderStruct); + DefineType(headerTypeId, headerName, machoHeaderType); + + EnumerationBuilder cmdTypeBuilder; + cmdTypeBuilder.AddMemberWithValue("LC_REQ_DYLD", LC_REQ_DYLD); + cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT", LC_SEGMENT); + cmdTypeBuilder.AddMemberWithValue("LC_SYMTAB", LC_SYMTAB); + cmdTypeBuilder.AddMemberWithValue("LC_SYMSEG", LC_SYMSEG); + cmdTypeBuilder.AddMemberWithValue("LC_THREAD", LC_THREAD); + cmdTypeBuilder.AddMemberWithValue("LC_UNIXTHREAD", LC_UNIXTHREAD); + cmdTypeBuilder.AddMemberWithValue("LC_LOADFVMLIB", LC_LOADFVMLIB); + cmdTypeBuilder.AddMemberWithValue("LC_IDFVMLIB", LC_IDFVMLIB); + cmdTypeBuilder.AddMemberWithValue("LC_IDENT", LC_IDENT); + cmdTypeBuilder.AddMemberWithValue("LC_FVMFILE", LC_FVMFILE); + cmdTypeBuilder.AddMemberWithValue("LC_PREPAGE", LC_PREPAGE); + cmdTypeBuilder.AddMemberWithValue("LC_DYSYMTAB", LC_DYSYMTAB); + cmdTypeBuilder.AddMemberWithValue("LC_LOAD_DYLIB", LC_LOAD_DYLIB); + cmdTypeBuilder.AddMemberWithValue("LC_ID_DYLIB", LC_ID_DYLIB); + cmdTypeBuilder.AddMemberWithValue("LC_LOAD_DYLINKER", LC_LOAD_DYLINKER); + cmdTypeBuilder.AddMemberWithValue("LC_ID_DYLINKER", LC_ID_DYLINKER); + cmdTypeBuilder.AddMemberWithValue("LC_PREBOUND_DYLIB", LC_PREBOUND_DYLIB); + cmdTypeBuilder.AddMemberWithValue("LC_ROUTINES", LC_ROUTINES); + cmdTypeBuilder.AddMemberWithValue("LC_SUB_FRAMEWORK", LC_SUB_FRAMEWORK); + cmdTypeBuilder.AddMemberWithValue("LC_SUB_UMBRELLA", LC_SUB_UMBRELLA); + cmdTypeBuilder.AddMemberWithValue("LC_SUB_CLIENT", LC_SUB_CLIENT); + cmdTypeBuilder.AddMemberWithValue("LC_SUB_LIBRARY", LC_SUB_LIBRARY); + cmdTypeBuilder.AddMemberWithValue("LC_TWOLEVEL_HINTS", LC_TWOLEVEL_HINTS); + cmdTypeBuilder.AddMemberWithValue("LC_PREBIND_CKSUM", LC_PREBIND_CKSUM); + cmdTypeBuilder.AddMemberWithValue("LC_LOAD_WEAK_DYLIB", LC_LOAD_WEAK_DYLIB); // (0x18 | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_64", LC_SEGMENT_64); + cmdTypeBuilder.AddMemberWithValue("LC_ROUTINES_64", LC_ROUTINES_64); + cmdTypeBuilder.AddMemberWithValue("LC_UUID", LC_UUID); + 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_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_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_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); + cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO_64", _LC_ENCRYPTION_INFO_64); + cmdTypeBuilder.AddMemberWithValue("LC_LINKER_OPTION", _LC_LINKER_OPTION); + cmdTypeBuilder.AddMemberWithValue("LC_LINKER_OPTIMIZATION_HINT", _LC_LINKER_OPTIMIZATION_HINT); + cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_TVOS", _LC_VERSION_MIN_TVOS); + cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_WATCHOS", LC_VERSION_MIN_WATCHOS); + cmdTypeBuilder.AddMemberWithValue("LC_NOTE", LC_NOTE); + cmdTypeBuilder.AddMemberWithValue("LC_BUILD_VERSION", LC_BUILD_VERSION); + cmdTypeBuilder.AddMemberWithValue("LC_DYLD_EXPORTS_TRIE", LC_DYLD_EXPORTS_TRIE); + cmdTypeBuilder.AddMemberWithValue("LC_DYLD_CHAINED_FIXUPS", LC_DYLD_CHAINED_FIXUPS); + cmdTypeBuilder.AddMemberWithValue("LC_FILESET_ENTRY", LC_FILESET_ENTRY); + Ref cmdTypeEnum = cmdTypeBuilder.Finalize(); + + Ref cmdTypeEnumType = Type::EnumerationType(nullptr, cmdTypeEnum, 4, false); + std::string cmdTypeEnumName = "load_command_type_t"; + std::string cmdTypeEnumId = Type::GenerateAutoTypeId("macho", cmdTypeEnumName); + DefineType(cmdTypeEnumId, cmdTypeEnumName, cmdTypeEnumType); + + StructureBuilder loadCommandBuilder; + loadCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + loadCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + Ref loadCommandStruct = loadCommandBuilder.Finalize(); + QualifiedName loadCommandName = std::string("load_command"); + std::string loadCommandTypeId = Type::GenerateAutoTypeId("macho", loadCommandName); + Ref loadCommandType = Type::StructureType(loadCommandStruct); + DefineType(loadCommandTypeId, loadCommandName, loadCommandType); + + EnumerationBuilder protTypeBuilder; + protTypeBuilder.AddMemberWithValue("VM_PROT_NONE", MACHO_VM_PROT_NONE); + protTypeBuilder.AddMemberWithValue("VM_PROT_READ", MACHO_VM_PROT_READ); + protTypeBuilder.AddMemberWithValue("VM_PROT_WRITE", MACHO_VM_PROT_WRITE); + protTypeBuilder.AddMemberWithValue("VM_PROT_EXECUTE", MACHO_VM_PROT_EXECUTE); + // protTypeBuilder.AddMemberWithValue("VM_PROT_DEFAULT", MACHO_VM_PROT_DEFAULT); + // protTypeBuilder.AddMemberWithValue("VM_PROT_ALL", MACHO_VM_PROT_ALL); + protTypeBuilder.AddMemberWithValue("VM_PROT_NO_CHANGE", MACHO_VM_PROT_NO_CHANGE); + protTypeBuilder.AddMemberWithValue("VM_PROT_COPY_OR_WANTS_COPY", MACHO_VM_PROT_COPY); + // protTypeBuilder.AddMemberWithValue("VM_PROT_WANTS_COPY", MACHO_VM_PROT_WANTS_COPY); + Ref protTypeEnum = protTypeBuilder.Finalize(); + + Ref protTypeEnumType = Type::EnumerationType(nullptr, protTypeEnum, 4, false); + std::string protTypeEnumName = "vm_prot_t"; + std::string protTypeEnumId = Type::GenerateAutoTypeId("macho", protTypeEnumName); + DefineType(protTypeEnumId, protTypeEnumName, protTypeEnumType); + + EnumerationBuilder segFlagsTypeBuilder; + segFlagsTypeBuilder.AddMemberWithValue("SG_HIGHVM", SG_HIGHVM); + segFlagsTypeBuilder.AddMemberWithValue("SG_FVMLIB", SG_FVMLIB); + segFlagsTypeBuilder.AddMemberWithValue("SG_NORELOC", SG_NORELOC); + segFlagsTypeBuilder.AddMemberWithValue("SG_PROTECTED_VERSION_1", SG_PROTECTED_VERSION_1); + Ref segFlagsTypeEnum = segFlagsTypeBuilder.Finalize(); + + Ref segFlagsTypeEnumType = Type::EnumerationType(nullptr, segFlagsTypeEnum, 4, false); + std::string segFlagsTypeEnumName = "sg_flags_t"; + std::string segFlagsTypeEnumId = Type::GenerateAutoTypeId("macho", segFlagsTypeEnumName); + DefineType(segFlagsTypeEnumId, segFlagsTypeEnumName, segFlagsTypeEnumType); + + StructureBuilder loadSegmentCommandBuilder; + loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + loadSegmentCommandBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "vmaddr"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "vmsize"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "fileoff"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "filesize"); + loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "maxprot"); + loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "initprot"); + loadSegmentCommandBuilder.AddMember(Type::IntegerType(4, false), "nsects"); + loadSegmentCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("sg_flags_t")), "flags"); + Ref loadSegmentCommandStruct = loadSegmentCommandBuilder.Finalize(); + QualifiedName loadSegmentCommandName = std::string("segment_command"); + std::string loadSegmentCommandTypeId = Type::GenerateAutoTypeId("macho", loadSegmentCommandName); + Ref loadSegmentCommandType = Type::StructureType(loadSegmentCommandStruct); + DefineType(loadSegmentCommandTypeId, loadSegmentCommandName, loadSegmentCommandType); + + StructureBuilder loadSegmentCommand64Builder; + loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(4, false), "cmdsize"); + loadSegmentCommand64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "vmaddr"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "vmsize"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "fileoff"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(8, false), "filesize"); + loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "maxprot"); + loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("vm_prot_t")), "initprot"); + loadSegmentCommand64Builder.AddMember(Type::IntegerType(4, false), "nsects"); + loadSegmentCommand64Builder.AddMember(Type::NamedType(this, QualifiedName("sg_flags_t")), "flags"); + Ref loadSegmentCommand64Struct = loadSegmentCommand64Builder.Finalize(); + QualifiedName loadSegment64CommandName = std::string("segment_command_64"); + std::string loadSegment64CommandTypeId = Type::GenerateAutoTypeId("macho", loadSegment64CommandName); + Ref loadSegment64CommandType = Type::StructureType(loadSegmentCommand64Struct); + DefineType(loadSegment64CommandTypeId, loadSegment64CommandName, loadSegment64CommandType); + + StructureBuilder sectionBuilder; + sectionBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "sectname"); + sectionBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "addr"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "size"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "offset"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "align"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "reloff"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "nreloc"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "flags"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "reserved1"); + sectionBuilder.AddMember(Type::IntegerType(4, false), "reserved2"); + Ref sectionStruct = sectionBuilder.Finalize(); + QualifiedName sectionName = std::string("section"); + std::string sectionTypeId = Type::GenerateAutoTypeId("macho", sectionName); + Ref sectionType = Type::StructureType(sectionStruct); + DefineType(sectionTypeId, sectionName, sectionType); + + StructureBuilder section64Builder; + section64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "sectname"); + section64Builder.AddMember(Type::ArrayType(Type::IntegerType(1, true), 16), "segname"); + section64Builder.AddMember(Type::IntegerType(8, false), "addr"); + section64Builder.AddMember(Type::IntegerType(8, false), "size"); + section64Builder.AddMember(Type::IntegerType(4, false), "offset"); + section64Builder.AddMember(Type::IntegerType(4, false), "align"); + section64Builder.AddMember(Type::IntegerType(4, false), "reloff"); + section64Builder.AddMember(Type::IntegerType(4, false), "nreloc"); + section64Builder.AddMember(Type::IntegerType(4, false), "flags"); + section64Builder.AddMember(Type::IntegerType(4, false), "reserved1"); + section64Builder.AddMember(Type::IntegerType(4, false), "reserved2"); + section64Builder.AddMember(Type::IntegerType(4, false), "reserved3"); + Ref section64Struct = section64Builder.Finalize(); + QualifiedName section64Name = std::string("section_64"); + std::string section64TypeId = Type::GenerateAutoTypeId("macho", section64Name); + Ref section64Type = Type::StructureType(section64Struct); + DefineType(section64TypeId, section64Name, section64Type); + + StructureBuilder symtabBuilder; + symtabBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + symtabBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + symtabBuilder.AddMember(Type::IntegerType(4, false), "symoff"); + symtabBuilder.AddMember(Type::IntegerType(4, false), "nsyms"); + symtabBuilder.AddMember(Type::IntegerType(4, false), "stroff"); + symtabBuilder.AddMember(Type::IntegerType(4, false), "strsize"); + Ref symtabStruct = symtabBuilder.Finalize(); + QualifiedName symtabName = std::string("symtab"); + std::string symtabTypeId = Type::GenerateAutoTypeId("macho", symtabName); + Ref symtabType = Type::StructureType(symtabStruct); + DefineType(symtabTypeId, symtabName, symtabType); + + StructureBuilder dynsymtabBuilder; + dynsymtabBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "ilocalsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nlocalsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "iextdefsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextdefsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "iundefsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nundefsym"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "tocoff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "ntoc"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "modtaboff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nmodtab"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "extrefsymoff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextrefsyms"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "indirectsymoff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nindirectsyms"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "extreloff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nextrel"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "locreloff"); + dynsymtabBuilder.AddMember(Type::IntegerType(4, false), "nlocrel"); + Ref dynsymtabStruct = dynsymtabBuilder.Finalize(); + QualifiedName dynsymtabName = std::string("dynsymtab"); + std::string dynsymtabTypeId = Type::GenerateAutoTypeId("macho", dynsymtabName); + Ref dynsymtabType = Type::StructureType(dynsymtabStruct); + DefineType(dynsymtabTypeId, dynsymtabName, dynsymtabType); + + StructureBuilder uuidBuilder; + uuidBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + uuidBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + uuidBuilder.AddMember(Type::ArrayType(Type::IntegerType(1, false), 16), "uuid"); + Ref uuidStruct = uuidBuilder.Finalize(); + QualifiedName uuidName = std::string("uuid"); + std::string uuidTypeId = Type::GenerateAutoTypeId("macho", uuidName); + Ref uuidType = Type::StructureType(uuidStruct); + DefineType(uuidTypeId, uuidName, uuidType); + + StructureBuilder linkeditDataBuilder; + linkeditDataBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "dataoff"); + linkeditDataBuilder.AddMember(Type::IntegerType(4, false), "datasize"); + Ref linkeditDataStruct = linkeditDataBuilder.Finalize(); + QualifiedName linkeditDataName = std::string("linkedit_data"); + std::string linkeditDataTypeId = Type::GenerateAutoTypeId("macho", linkeditDataName); + Ref linkeditDataType = Type::StructureType(linkeditDataStruct); + DefineType(linkeditDataTypeId, linkeditDataName, linkeditDataType); + + StructureBuilder encryptionInfoBuilder; + encryptionInfoBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptoff"); + encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptsize"); + encryptionInfoBuilder.AddMember(Type::IntegerType(4, false), "cryptid"); + Ref encryptionInfoStruct = encryptionInfoBuilder.Finalize(); + QualifiedName encryptionInfoName = std::string("encryption_info"); + std::string encryptionInfoTypeId = Type::GenerateAutoTypeId("macho", encryptionInfoName); + Ref encryptionInfoType = Type::StructureType(encryptionInfoStruct); + DefineType(encryptionInfoTypeId, encryptionInfoName, encryptionInfoType); + + StructureBuilder versionMinBuilder; + versionMinBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + versionMinBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + versionMinBuilder.AddMember(Type::IntegerType(4, false), "version"); + versionMinBuilder.AddMember(Type::IntegerType(4, false), "sdk"); + Ref versionMinStruct = versionMinBuilder.Finalize(); + QualifiedName versionMinName = std::string("version_min"); + std::string versionMinTypeId = Type::GenerateAutoTypeId("macho", versionMinName); + Ref versionMinType = Type::StructureType(versionMinStruct); + DefineType(versionMinTypeId, versionMinName, versionMinType); + + StructureBuilder dyldInfoBuilder; + dyldInfoBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "rebase_off"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "rebase_size"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "bind_off"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "bind_size"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "weak_bind_off"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "weak_bind_size"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "lazy_bind_off"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "lazy_bind_size"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "export_off"); + dyldInfoBuilder.AddMember(Type::IntegerType(4, false), "export_size"); + Ref dyldInfoStruct = dyldInfoBuilder.Finalize(); + QualifiedName dyldInfoName = std::string("dyld_info"); + std::string dyldInfoTypeId = Type::GenerateAutoTypeId("macho", dyldInfoName); + Ref dyldInfoType = Type::StructureType(dyldInfoStruct); + DefineType(dyldInfoTypeId, dyldInfoName, dyldInfoType); + + StructureBuilder dylibBuilder; + dylibBuilder.AddMember(Type::IntegerType(4, false), "name"); + dylibBuilder.AddMember(Type::IntegerType(4, false), "timestamp"); + dylibBuilder.AddMember(Type::IntegerType(4, false), "current_version"); + dylibBuilder.AddMember(Type::IntegerType(4, false), "compatibility_version"); + Ref dylibStruct = dylibBuilder.Finalize(); + QualifiedName dylibName = std::string("dylib"); + std::string dylibTypeId = Type::GenerateAutoTypeId("macho", dylibName); + Ref dylibType = Type::StructureType(dylibStruct); + DefineType(dylibTypeId, dylibName, dylibType); + + StructureBuilder dylibCommandBuilder; + dylibCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + dylibCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + dylibCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("dylib")), "dylib"); + Ref dylibCommandStruct = dylibCommandBuilder.Finalize(); + QualifiedName dylibCommandName = std::string("dylib_command"); + std::string dylibCommandTypeId = Type::GenerateAutoTypeId("macho", dylibCommandName); + Ref dylibCommandType = Type::StructureType(dylibCommandStruct); + DefineType(dylibCommandTypeId, dylibCommandName, dylibCommandType); + + StructureBuilder filesetEntryCommandBuilder; + filesetEntryCommandBuilder.AddMember(Type::NamedType(this, QualifiedName("load_command_type_t")), "cmd"); + filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "cmdsize"); + filesetEntryCommandBuilder.AddMember(Type::IntegerType(8, false), "vmaddr"); + filesetEntryCommandBuilder.AddMember(Type::IntegerType(8, false), "fileoff"); + filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "entry_id"); + filesetEntryCommandBuilder.AddMember(Type::IntegerType(4, false), "reserved"); + Ref filesetEntryCommandStruct = filesetEntryCommandBuilder.Finalize(); + QualifiedName filesetEntryCommandName = std::string("fileset_entry_command"); + std::string filesetEntryCommandTypeId = Type::GenerateAutoTypeId("macho", filesetEntryCommandName); + Ref filesetEntryCommandType = Type::StructureType(filesetEntryCommandStruct); + DefineType(filesetEntryCommandTypeId, filesetEntryCommandName, filesetEntryCommandType); + + // uint32_t at 0x10 is offset to mapping table. + // first mapping struct in that table is base of primary + // first uint64_t in that struct is the base address of the primary + // double gpv here because DSCRaw explicitly stops at the start of this mapping table + uint64_t basePointer = 0; + GetParentView()->Read(&basePointer, 16, 4); + if (basePointer == 0) + { + logger->LogError("Failed to read base pointer"); + return false; + } + uint64_t primaryBase = 0; + GetParentView()->Read(&primaryBase, basePointer, 8); + if (primaryBase == 0) + { + logger->LogError("Failed to read primary base at 0x%llx", basePointer); + return false; + } + + uint64_t headerSize = std::min(basePointer, headerType.type->GetWidth()); + // Truncate the `dyld_cache_header` structure to the structure present in the cache file. + auto newStructure = StructureBuilder(headerType.type->GetStructure()).SetWidth(headerSize).Finalize(); + headerType.type = TypeBuilder::StructureType(newStructure).Finalize(); + + 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); + + auto sharedCache = SharedCache(GetAddressSize()); + + { + // 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 elapsed = endTime - startTime; + logger->LogInfo("Processing %zu entries took %.3f seconds", sharedCache.GetEntries().size(), elapsed.count()); + } + + { + // 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 elapsed = endTime - startTime; + logger->LogInfo("Processing slide info took %.3f seconds", elapsed.count()); + } + + { + // 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 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."); + } + + { + // 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 elapsed = endTime - startTime; + logger->LogInfo("Processing %zu regions took %.3f seconds", sharedCache.GetRegions().size(), elapsed.count()); + } + + auto cacheController = SharedCacheController::Initialize(*this, std::move(sharedCache)); + + // 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("loader.dsc.autoLoadPattern", this); + logger->LogDebug("Loading images using pattern: %s", autoLoadPattern.c_str()); + + std::regex autoLoadRegex(autoLoadPattern); + for (const auto& [_, image] : cacheController->GetCache().GetImages()) + if (std::regex_match(image.GetName(), autoLoadRegex)) + cacheController->ApplyImage(*this, image); + + 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 + + +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 Create(BinaryNinja::BinaryView* data) override; + + BinaryNinja::Ref Parse(BinaryNinja::BinaryView* data) override; + + bool IsTypeValidForData(BinaryNinja::BinaryView* data) override; + + bool IsDeprecated() override { return false; } + + BinaryNinja::Ref 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 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 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& 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 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 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& 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(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 view, Ref typeLib, Ref symbol) +{ + Ref func = nullptr; + auto symbolAddress = symbol->GetAddress(); + auto symbolName = symbol->GetFullName(); + + if (symbol->GetType() == FunctionSymbol) + { + Ref 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 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 +#include + +#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 view, BinaryNinja::Ref typeLib, + BinaryNinja::Ref symbol); + +// Returns the on disk file for a given path, this is required to support project files. +std::string ResolveFilePath(BinaryNinja::Ref 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 +using AddressRangeMap = std::map>; + +// TODO: Document this! +template +class WeakAllocPtr +{ +protected: + std::weak_ptr m_weakPtr; // Weak reference to the object + std::function()> m_allocator; // Function to recreate the object + +public: + explicit WeakAllocPtr(std::function()> allocator) : m_allocator(allocator) {} + + WeakAllocPtr(std::weak_ptr weakPtr, std::function()> allocator) : m_allocator(allocator) + { + if (weakPtr == nullptr) + { + m_weakPtr = {}; + } + else + { + m_weakPtr = weakPtr; + } + } + + std::shared_ptr lock() + { + std::shared_ptr sharedPtr = m_weakPtr.lock(); + if (!sharedPtr) + { + sharedPtr = m_allocator(); + m_weakPtr = sharedPtr; + } + return sharedPtr; + } + + std::shared_ptr 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 -#include -#include -#include -#include -#include - -#ifdef _MSC_VER - #include -#else - #include - #include - #include - #include -#endif - -static std::atomic mmapCount = 0; - -uint64_t MMapCount() -{ - return mmapCount; -} - -void VMShutdown() {} - - -std::string ResolveFilePath(BinaryNinja::Ref 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 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(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(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(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 FileAccessorCache::OpenLazily( - const uint64_t sessionID, const std::string& path, - std::function)> 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(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 FileAccessorCache::Open( - const uint64_t sessionID, const std::string& path) -{ - EvictFromCacheIfNeeded(); - - mmapCount++; - auto accessor = std::shared_ptr(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 MMappedFileAccessor::Open( - const uint64_t sessionID, const std::string& path, - std::function)> 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 -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(address); -} - -int8_t MMappedFileAccessor::ReadChar(size_t address) -{ - return Read(address); -} - -uint16_t MMappedFileAccessor::ReadUShort(size_t address) -{ - return Read(address); -} - -int16_t MMappedFileAccessor::ReadShort(size_t address) -{ - return Read(address); -} - -uint32_t MMappedFileAccessor::ReadUInt32(size_t address) -{ - return Read(address); -} - -int32_t MMappedFileAccessor::ReadInt32(size_t address) -{ - return Read(address); -} - -uint64_t MMappedFileAccessor::ReadULong(size_t address) -{ - return Read(address); -} - -int64_t MMappedFileAccessor::ReadLong(size_t address) -{ - return Read(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 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 dscView, uint64_t sessionID, size_t vm_address, size_t fileoff, size_t size, const std::string& filePath, std::function)> 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 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, 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 - -#include -#include -#include - -void VMShutdown(); - -std::string ResolveFilePath(BinaryNinja::Ref dscView, const std::string& path); - -template -class SelfAllocatingWeakPtr { -public: - SelfAllocatingWeakPtr(std::function()> allocator) : allocator(allocator) {} - - std::shared_ptr lock() { - std::shared_ptr sharedPtr = weakPtr.lock(); - if (!sharedPtr) { - sharedPtr = allocator(); - weakPtr = sharedPtr; - } - return sharedPtr; - } - - std::shared_ptr lock_no_allocate() { - return weakPtr.lock(); - } - -private: - std::weak_ptr weakPtr; // Weak reference to the object - std::function()> 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 { -public: - LazyMappedFileAccessor( - std::string filePath, std::function(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 Open(const uint64_t sessionID, const std::string &path, std::function)> 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 once the minimum supported - // C++ version supports it. - std::pair ReadSpan(size_t addr, size_t length); - - void Read(void *dest, size_t addr, size_t length); - - template - T Read(size_t address); -}; - -class FileAccessorCache -{ -public: - static FileAccessorCache& Shared(); - - std::shared_ptr OpenLazily( - const uint64_t sessionID, const std::string& path, - std::function)> postAllocationRoutine); - - void SetCacheSize(uint64_t size); - -private: - FileAccessorCache(); - - std::shared_ptr 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> m_lazyAccessors; - - // Ordered from least recently opened (front) to most recently opened (end). - std::list m_leastRecentlyOpened; - std::unordered_map, std::list::iterator>> - m_accessors; - - uint64_t m_cacheSize = 8; -}; - -struct PageMapping { - std::shared_ptr fileAccessor; - size_t fileOffset; - PageMapping(std::shared_ptr 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 -using AddressRangeMap = std::map>; - -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 m_map; - size_t m_pageSize; - bool m_safe; - - friend VMReader; - -public: - - VM(size_t pageSize, bool safe = true); - - ~VM(); - - void MapPages(BinaryNinja::Ref dscView, uint64_t sessionID, size_t vm_address, size_t fileoff, size_t size, const std::string& filePath, std::function)> postAllocationRoutine); - - bool AddressIsMapped(uint64_t address); - - std::pair 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 m_vm; - size_t m_cursor; - size_t m_addressSize; - - BNEndianness m_endianness = LittleEndian; - -public: - VMReader(std::shared_ptr 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 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 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 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 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(fileAccessor->Data()); + + // Loop through the SLEB128 encoded bytes + while (offset < fileLimit) + { + uint8_t currentByte = fileBuffer[offset++]; + value |= (static_cast(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 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 GetRegionAtAddress(uint64_t address, uint64_t& addressOffset); + + std::optional 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 ReadSpan(size_t address, size_t length); + + void Read(void* dest, uint64_t address, size_t length); +}; + +class VirtualMemoryReader +{ + std::shared_ptr m_memory; + uint64_t m_cursor; + uint64_t m_addressSize; + BNEndianness m_endianness = LittleEndian; + +public: + explicit VirtualMemoryReader(std::shared_ptr 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 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 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 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 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 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 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 +#endif +#include +#include +#include + +namespace BinaryNinja::DSC { + class DSCRefCountObject + { + // CORE_ALLOCATED_CLASS(RefCountObject) + + public: + std::atomic 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 DSCRef + { + T* m_obj; +#ifdef BN_REF_COUNT_DEBUG + void* m_assignmentTrace = nullptr; +#endif + + 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 + } + } + + DSCRef(const DSCRef& 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() + { + if (m_obj) + { + m_obj->Release(); +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif + } + } + + // move constructor + DSCRef(DSCRef&& 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& operator=(Ref&& 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& operator=(const DSCRef& 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& 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& obj) const { return m_obj == obj.m_obj; } + + bool operator!=(const DSCRef& obj) const { return m_obj != obj.m_obj; } + + bool operator<(const DSCRef& obj) const { return m_obj < obj.m_obj; } + + template + friend H AbslHashValue(H h, const DSCRef& value) + { + return AbslHashValue(std::move(h), value.m_obj); + } + }; + + + // Macro-like functions to manage referenced objects for the external API + template + static typename T::APIHandle DSC_API_OBJECT_REF(T* obj) + { + if (obj == nullptr) + return nullptr; + obj->AddAPIRef(); + return obj->GetAPIObject(); + } + + template + static typename T::APIHandle DSC_API_OBJECT_REF(const DSCRef& obj) + { + if (!obj) + return nullptr; + obj->AddAPIRef(); + return obj->GetAPIObject(); + } + + // template + // static typename T::APIHandle DSC_API_OBJECT_REF(const APIRef& obj) + //{ + // if (!obj) + // return nullptr; + // obj->AddAPIRef(); + // return obj->GetAPIObject(); + // } + + template + static T* DSC_API_OBJECT_NEW_REF(T* obj) + { + if (obj) + obj->object->AddAPIRef(); + return obj; + } + + template + 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[A-Z]+)\s+(?P[\dMKB]+)\s+0x(?P[a-fA-F0-9]+)\s+->\s+0x(?P[a-fA-F0-9]+)" + ) + library_pattern = re.compile( + r"^(?P.+)$" + ) + section_pattern = re.compile( + r"\s+(?P
[^\s]+)\s+0x(?P[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 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 -#include "ui/uicontext.h" -#include "SharedCacheUINotifications.h" - -using namespace BinaryNinja; - -class SharedCacheBDNotifications : public BinaryDataNotification -{ -public: - SharedCacheBDNotifications(Ref 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 #include #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, uint64_t)> isAddrMapped = [](Ref 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 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 view = ctx.binaryView; - Ref 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 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 view = ctx.binaryView; - Ref 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 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 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 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(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(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 #include "progresstask.h" -#include - using namespace BinaryNinja; +using namespace SharedCacheAPI; void DisplayDSCPicker(UIContext* ctx, Ref dscView) { - BackgroundThread::create(ctx ? ctx->mainWindow() : nullptr)->thenBackground( - [dscView=dscView](QVariant var) { - QStringList entries; - Ref cache = new SharedCacheAPI::SharedCache(dscView); - - for (const auto& img : cache->GetAvailableImages()) - entries.push_back(QString::fromStdString(img)); - - return entries; - })->thenMainThread([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(QString::fromStdString(entries.at((qsizetype)choiceDialog->GetChosenEntry().value().idx).toStdString())); - else - return QVariant(""); - })->thenBackground([dscView=dscView](QVariant var){ - if (var.toString().isEmpty()) - return; - Ref cache = new SharedCacheAPI::SharedCache(dscView); - cache->LoadImageWithInstallName(var.toString().toStdString()); - })->start(); + 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())); + }; + + static auto loadSelectedImage = [dscView](QVariant var) { + auto selectedImageName = var.toString().toStdString(); + if (selectedImageName.empty()) + return; + + if (auto controller = SharedCacheController::GetController(*dscView)) + { + if (const auto selectedImage = controller->GetImageWithName( selectedImageName)) + { + controller->ApplyImage(*dscView, *selectedImage); + dscView->AddAnalysisOption("linearsweep"); + dscView->UpdateAnalysis(); + } + } + }; + + + 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 -#include #include "tabwidget.h" #include "globalarea.h" #include "progresstask.h" - -#include #include +#include +#include "dsctriage.h" +#include +#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 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 originalWidths; - originalWidths.resize(m_blockSizeRatios.size(), (availableWidth / m_blockSizeRatios.size())); - - - // Calculate center points for each block - std::vector 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(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 originalWidths; - originalWidths.resize(m_blockSizeRatios.size(), (availableWidth / m_blockSizeRatios.size())); - - std::vector 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(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(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); -} - +DSCTriageViewType::DSCTriageViewType() + : ViewType("DSCTriage", "Dyld Shared Cache Triage") +{} -void DSCCacheBlocksView::resizeEvent(QResizeEvent* event) +int DSCTriageViewType::getPriority(BinaryViewRef data, const QString& filename) { - QWidget::resizeEvent(event); + if (data->GetTypeName() == VIEW_NAME) + return 100; + return 0; } - -QSize DSCCacheBlocksView::sizeHint() const +QWidget* DSCTriageViewType::create(BinaryViewRef data, ViewFrame* viewFrame) { - return QWidget::sizeHint(); + if (data->GetTypeName() != VIEW_NAME) + return nullptr; + // TODO: Check for dyld start. Then continue. + return new DSCTriageView(viewFrame, data); } - -QSize DSCCacheBlocksView::minimumSizeHint() const +void DSCTriageViewType::Register() { - 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(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(); - } + registerViewType(new DSCTriageViewType()); } -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(m_symbols.size())) { - return m_symbols.at(0); - } - 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, Ref 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); -} - - -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& 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) - { - sizeStr = std::to_string(sizeInBits / (1024 * 1024)) + " MB"; - } - else + std::map images = {}; + for (const uint64_t& addr : addresses) { - 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 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 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 navigateToAddress = [=](uint64_t addr){ - ExecuteOnMainThread([addr, this](){ - if (BinaryNinja::Settings::Instance()->Get("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"( -

Dyld Shared Cache Alpha

- -

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.

- -

Platforms

-
-
iOS 11-17
Full Support
-
iOS 18
Partial support, may experience issues -- specifically, Objective-C optimization parsing is not implemented
-
macOS x86/arm64e
Partial support, may experience issues
-
- -

iOS parsing should work fairly well for now. macOS parsing should be usable, but both are still a work in progress.

-

Getting the latest version of the plugin

-

We frequently release "dev" builds which will contain the latest version of the dyld shared cache plugin (and many other things).

-

You can find instructions on how to install these builds here.

- -

Reading / building the source

-

Like most of our platforms, architectures, debug information parsing, and the entire API and documentation, this plugin is open source.

-

You can read the source and find instructions for building it here.

-

Contributions are always welcome!

-)"; - tb->setHtml(alphaHtml); - - m_triageTabs->addTab(tb, "Dyld Shared Cache Alpha"); - m_triageTabs->setCanCloseTab(tb, false); + regionTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + regionTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + regionTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Interactive); + regionTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); - } - 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; - } + regionTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + + regionTable->setSelectionBehavior(QAbstractItemView::SelectRows); + regionTable->setSelectionMode(QAbstractItemView::ExtendedSelection); - containerWidget->addWidget(m_bottomRegionTabs); + regionTable->setSortingEnabled(true); + + regionTable->verticalHeader()->setVisible(false); + + auto mappingLabel = new QLabel("Mappings"); + + 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); + + 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) -{ - 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); -} - - -void CollapsibleSection::setSubtitleRight(const QString& subtitle) -{ - m_subtitleRightLabel->setVisible(subtitle != ""); - m_subtitleRightLabel->setText(subtitle); -} - - -void CollapsibleSection::setContentWidget(QWidget* contentWidget) +void DSCTriageView::OnAfterOpenFile(UIContext *context, FileContext *file, ViewFrame *frame) { - m_contentWidget = contentWidget; - m_contentWidgetContainer->layout()->addWidget(contentWidget); + RefreshData(); + UIContextNotification::OnAfterOpenFile(context, file, frame); } - -QSize CollapsibleSection::sizeHint() const +// Called when shared cache information has changed. +void DSCTriageView::RefreshData() { - 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 #include +#include + #include "uitypes.h" #include "viewframe.h" #include "animation.h" #include "uicontext.h" -#include -#include -#include -#include #include "filter.h" +#include "symboltable.h" #ifndef BINARYNINJA_DSCTRIAGE_H #define BINARYNINJA_DSCTRIAGE_H - -class DSCCacheBlocksView : public QWidget -{ - Q_OBJECT - - BinaryViewRef m_data; - Ref m_cache; - - uint64_t m_backingCacheCount = 0; - std::vector m_backingCaches; - - std::atomic m_currentProgress; - std::vector m_blockSizeRatios; - std::vector m_targetBlockSizeForAnimation; - uint64_t m_averageBlockSizeForAnimationInterp = 0; - std::vector 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 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 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 m_symbols; - - SymbolTableModel* m_model; - -public: - SymbolTableView(QWidget* parent, Ref 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 m_cache; SplitTabWidget* m_triageTabs; DockableTabCollection* m_triageCollection; - SplitTabWidget* m_bottomRegionTabs; - DockableTabCollection* m_bottomRegionCollection; + QStandardItemModel* m_imageModel; + + SymbolTableView* m_symbolTable; - std::vector 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 +#include "symboltable.h" + +#include + +#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(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 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 +#include "viewframe.h" +#include "animation.h" + +#include +#include +#include "filter.h" + +class SymbolTableView; + +class SymbolTableModel : public QAbstractTableModel { + Q_OBJECT + + SymbolTableView* m_parent; + std::string m_filter; + std::vector 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 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 splitSelector(const std::string& selector) { + std::vector components; + std::istringstream stream(selector); + std::string component; + + while (std::getline(stream, component, ':')) { + if (!component.empty()) { + components.push_back(component); + } + } + + return components; +} + +std::vector generateArgumentNames(const std::vector& components) { + std::vector 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 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(); + if (auto symbol = bv->GetSymbolByAddress(callExpr.GetValue().value)) + if (symbol->GetRawName() != "_objc_msgSend") + return; + + const auto params = insn.GetParameterExprs(); + // 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(); + rawSelector = ssa->GetSSARegisterValue(selectorRegister).value; + } + else if (params[0].operation == LLIL_SEPARATE_PARAM_LIST_SSA) + { + if (params[0].GetParameterExprs().size() == 0) + return; + const auto selectorRegister = params[0].GetParameterExprs()[1].GetSourceSSARegister(); + 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 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 selectorComponents = splitSelector(selector); + std::vector 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 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 +#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> globalWorkflowState; -std::shared_mutex globalWorkflowStateMutex; - std::shared_ptr GetGlobalWorkflowState(Ref view) { + static std::shared_mutex globalWorkflowStateMutex; + static std::unordered_map> globalWorkflowState; + std::shared_lock 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 GetGlobalWorkflowState(Ref view std::unique_lock writeLock(globalWorkflowStateMutex); globalWorkflowState[viewId] = std::make_shared(); Ref settings = view->GetLoadSettings(VIEW_NAME); + bool autoLoadStubsAndDyldData = true; if (settings && settings->Contains("loader.dsc.autoLoadStubsAndDyldData")) - { autoLoadStubsAndDyldData = settings->Get("loader.dsc.autoLoadStubsAndDyldData", view); - } globalWorkflowState[viewId]->autoLoadStubsAndDyldData = autoLoadStubsAndDyldData; + bool autoLoadObjC = true; if (settings && settings->Contains("loader.dsc.autoLoadObjCStubRequirements")) - { autoLoadObjC = settings->Get("loader.dsc.autoLoadObjCStubRequirements", view); - } globalWorkflowState[viewId]->autoLoadObjCStubRequirements = autoLoadObjC; - return globalWorkflowState[viewId]; -} - -std::vector splitSelector(const std::string& selector) { - std::vector 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 generateArgumentNames(const std::vector& components) { - std::vector 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; +// TODO: Add a type library cache to this workflow. (so we dont take global file lock) +Ref 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; + + // 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 &workflowState, Ref cache, Ref func, - const Ref
& 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(); -} - - -void SharedCacheWorkflow::ProcessOffImageCall(Ref ctx, Ref cache, Ref func, - Ref 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 - ) - ) + // No existing symbol located, try and search through the symbols of the cache. + auto symbol = controller.GetSymbolAt(symbolAddr); + if (!symbol.has_value()) + return; - { - 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); - } - }); + // 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 = nullptr; + if (const auto image = controller.GetImageContaining(symbolAddr)) + if (auto typeLib = TypeLibraryFromName(view, image->name)) + type = view.ImportTypeLibraryObject(typeLib, {symbol->name}); + + // 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 ctx) +void FixupStubs(Ref 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 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(); - 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(); + 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().operation == MLIL_VAR_SSA) - { - auto dest = instr.GetDestExpr(); - 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().operation == MLIL_CONST_PTR) - { - auto dest = instr.GetDestExpr(); - 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(); - 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(); - 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().operation == MLIL_VAR_SSA) { - dest = dest.GetSourceExpr(); - if (!bv->IsValidOffset(dest.GetConstant())) + auto dest = instr.GetDestExpr(); + 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().operation == MLIL_CONST_PTR) { - auto instr = mssa->GetInstruction(i); - if (instr.operation == MLIL_JUMP) - { - if (instr.GetDestExpr().operation == MLIL_VAR_SSA) - { - auto dest = instr.GetDestExpr(); - 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(); + 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().operation == MLIL_CONST_PTR) + if (instr.GetDestExpr().operation == MLIL_VAR_SSA) { - auto dest = instr.GetDestExpr(); - if (!bv->IsValidOffset(dest.GetConstant())) + auto dest = instr.GetDestExpr(); + 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 ctx) +// TODO: FixupOffImageAccess +void FixupOffImageCalls(Ref 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(); - 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(); + // 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(); - 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().size() == 0) + if (instr.GetDestExpr().operation == MLIL_CONST_PTR) { - return; + auto targetAddr = instr.GetDestExpr().GetConstant(); + if (!view->IsValidOffset(targetAddr)) + { + processOffImageCall(targetAddr, targetAddr); + } } - const auto selectorRegister = params[0].GetParameterExprs()[1].GetSourceSSARegister(); - 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 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 selectorComponents = splitSelector(selector); - std::vector 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(); + if (destExpr.operation == MLIL_VAR_SSA) + { + // 1 @ 180359b5c (MLIL_JUMP jump((MLIL_VAR.q x16))) + auto dest = instr.GetDestExpr(); + 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(); + if (loadExpr.operation == MLIL_LOAD_SSA) + { + auto ptrExpr = loadExpr.GetSourceExpr(); + 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(); + 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 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::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 ctx, Ref cache, Ref func, Ref il, const MediumLevelILInstruction instr, bool applySymbolIfFoundToCurrentFunction = false); - static void FixupStubs(Ref ctx); static void Register(); }; @@ -19,5 +13,3 @@ extern "C" { #ifdef __cplusplus } #endif - -#endif //SHAREDCACHE_SHAREDCACHEWORKFLOW_H -- cgit v1.3.1