diff options
| author | Mason Reed <mason@vector35.com> | 2025-03-10 11:05:40 -0400 |
|---|---|---|
| committer | Mason Reed <mason@vector35.com> | 2025-04-02 05:36:54 -0400 |
| commit | 25cc02431b61097b2adfc2fbc493b648b0300c3b (patch) | |
| tree | a79d9c4f4f67234d3bf9bda413e8608f479a4cc8 /view/sharedcache/core | |
| parent | fa85bf28502286c4821427c5d0ed91a7ed46f8f6 (diff) | |
[SharedCache] Refactor Shared Cache
In absence of a better name, this commit refactors the shared cache code.
Diffstat (limited to 'view/sharedcache/core')
34 files changed, 5196 insertions, 6750 deletions
diff --git a/view/sharedcache/core/CMakeLists.txt b/view/sharedcache/core/CMakeLists.txt index 03766920..a9aaa1bc 100644 --- a/view/sharedcache/core/CMakeLists.txt +++ b/view/sharedcache/core/CMakeLists.txt @@ -9,7 +9,7 @@ if((NOT BN_API_PATH) AND (NOT BN_INTERNAL_BUILD)) endif() endif() -file(GLOB SOURCES *.cpp *.h ../../../objectivec/*) +file(GLOB_RECURSE SOURCES *.cpp *.h ../../../objectivec/*) add_library(sharedcachecore OBJECT ${SOURCES}) diff --git a/view/sharedcache/core/DSCView.h b/view/sharedcache/core/DSCView.h deleted file mode 100644 index bde2f7c2..00000000 --- a/view/sharedcache/core/DSCView.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// Created by kat on 5/23/23. -// - -#ifndef SHAREDCACHE_DSCVIEW_H -#define SHAREDCACHE_DSCVIEW_H - -#include <binaryninjaapi.h> - - -class DSCView : public BinaryNinja::BinaryView { - bool m_parseOnly; -public: - - DSCView(const std::string &typeName, BinaryView *data, bool parseOnly = false); - - ~DSCView() override; - - bool Init() override; -}; - - -class DSCViewType : public BinaryNinja::BinaryViewType { - -public: - DSCViewType(); - - BinaryNinja::Ref<BinaryNinja::BinaryView> Create(BinaryNinja::BinaryView *data) override; - - BinaryNinja::Ref<BinaryNinja::BinaryView> Parse(BinaryNinja::BinaryView *data) override; - - bool IsTypeValidForData(BinaryNinja::BinaryView *data) override; - - bool IsDeprecated() override { return false; } - - BinaryNinja::Ref<BinaryNinja::Settings> GetLoadSettingsForData(BinaryNinja::BinaryView *data) override; -}; - - -#endif //SHAREDCACHE_DSCVIEW_H diff --git a/view/sharedcache/core/Dyld.h b/view/sharedcache/core/Dyld.h new file mode 100644 index 00000000..70397877 --- /dev/null +++ b/view/sharedcache/core/Dyld.h @@ -0,0 +1,267 @@ +#pragma once + +// These types will be parsed directly from the files. + +#include <stdint.h> + +#if defined(__GNUC__) || defined(__clang__) + #define PACKED_STRUCT __attribute__((packed)) +#else + #define PACKED_STRUCT +#endif + +struct PACKED_STRUCT dyld_cache_mapping_info +{ + uint64_t address; + uint64_t size; + uint64_t fileOffset; + uint32_t maxProt; + uint32_t initProt; +}; + +struct dyld_cache_slide_info +{ + uint32_t version; + uint32_t toc_offset; + uint32_t toc_count; + uint32_t entries_offset; + uint32_t entries_count; + uint32_t entries_size; + // uint16_t toc[toc_count]; + // entrybitmap entries[entries_count]; +}; + +struct dyld_cache_slide_info_entry +{ + uint8_t bits[4096 / (8 * 4)]; // 128-byte bitmap +}; + +struct PACKED_STRUCT dyld_cache_mapping_and_slide_info +{ + uint64_t address; + uint64_t size; + uint64_t fileOffset; + uint64_t slideInfoFileOffset; + uint64_t slideInfoFileSize; + uint64_t flags; + uint32_t maxProt; + uint32_t initProt; +}; + +struct PACKED_STRUCT dyld_cache_slide_info_v2 +{ + uint32_t version; + uint32_t page_size; + uint32_t page_starts_offset; + uint32_t page_starts_count; + uint32_t page_extras_offset; + uint32_t page_extras_count; + uint64_t delta_mask; + uint64_t value_add; +}; +#define DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA 0x8000 // index is into extras array (not starts array) +#define DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE 0x4000 // page has no rebasing +#define DYLD_CACHE_SLIDE_PAGE_ATTR_END 0x8000 // last chain entry for page + +#define DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing + +struct PACKED_STRUCT dyld_cache_slide_info_v3 +{ + uint32_t version; + uint32_t page_size; + uint32_t page_starts_count; + uint32_t pad_i_guess; + uint64_t auth_value_add; +}; + + +// DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE +struct dyld_chained_ptr_arm64e_shared_cache_rebase +{ + uint64_t runtimeOffset : 34, // offset from the start of the shared cache + high8 : 8, unused : 10, + next : 11, // 8-byte stide + auth : 1; // == 0 +}; + +// DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE +struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase +{ + uint64_t runtimeOffset : 34, // offset from the start of the shared cache + diversity : 16, addrDiv : 1, + keyIsData : 1, // implicitly always the 'A' key. 0 -> IA. 1 -> DA + next : 11, // 8-byte stide + auth : 1; // == 1 +}; + +// TODO: dyld_cache_slide_info4 is used in watchOS which we are not close to supporting right now. + +#define DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing + +struct PACKED_STRUCT dyld_cache_slide_info_v5 +{ + uint32_t version; // currently 5 + uint32_t page_size; // currently 4096 (may also be 16384) + uint32_t page_starts_count; + uint32_t pad; // padding to ensure the value below is on an 8-byte boundary + uint64_t value_add; + // uint16_t page_starts[/* page_starts_count */]; +}; + + +struct PACKED_STRUCT dyld_cache_image_info +{ + uint64_t address; + uint64_t modTime; + uint64_t inode; + uint32_t pathFileOffset; + uint32_t pad; + + bool operator==(const dyld_cache_image_info& image) const; +}; + +union dyld_cache_slide_pointer5 +{ + uint64_t raw; + struct dyld_chained_ptr_arm64e_shared_cache_rebase regular; + struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase auth; +}; + + +struct PACKED_STRUCT dyld_cache_local_symbols_info +{ + uint32_t nlistOffset; // offset into this chunk of nlist entries + uint32_t nlistCount; // count of nlist entries + uint32_t stringsOffset; // offset into this chunk of string pool + uint32_t stringsSize; // byte count of string pool + uint32_t entriesOffset; // offset into this chunk of array of dyld_cache_local_symbols_entry + uint32_t entriesCount; // number of elements in dyld_cache_local_symbols_entry array +}; + +struct PACKED_STRUCT dyld_cache_local_symbols_entry +{ + uint32_t dylibOffset; // offset in cache file of start of dylib + uint32_t nlistStartIndex; // start index of locals for this dylib + uint32_t nlistCount; // number of local symbols for this dylib +}; + +struct PACKED_STRUCT dyld_cache_local_symbols_entry_64 +{ + uint64_t dylibOffset; // offset in cache buffer of start of dylib + uint32_t nlistStartIndex; // start index of locals for this dylib + uint32_t nlistCount; // number of local symbols for this dylib +}; + +union dyld_cache_slide_pointer3 +{ + uint64_t raw; + struct + { + uint64_t pointerValue : 51, offsetToNextPointer : 11, unused : 2; + } plain; + + struct + { + uint64_t offsetFromSharedCacheBase : 32, diversityData : 16, hasAddressDiversity : 1, key : 2, + offsetToNextPointer : 11, unused : 1, + authenticated : 1; // = 1; + } auth; +}; + + +struct PACKED_STRUCT dyld_cache_header +{ + char magic[16]; // e.g. "dyld_v0 i386" + uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info + uint32_t mappingCount; // number of dyld_cache_mapping_info entries + uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing + uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing + uint64_t dyldBaseAddress; // base address of dyld when cache was built + uint64_t codeSignatureOffset; // file offset of code signature blob + uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file) + uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info + uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info + uint64_t localSymbolsOffset; // file offset of where local symbols are stored + uint64_t localSymbolsSize; // size of local symbols information + uint8_t uuid[16]; // unique value for each shared cache file + uint64_t cacheType; // 0 for development, 1 for production, 2 for multi-cache + uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses + uint32_t branchPoolsCount; // number of uint64_t entries + uint64_t dyldInCacheMH; // (unslid) address of mach_header of dyld in cache + uint64_t dyldInCacheEntry; // (unslid) address of entry point (_dyld_start) of dyld in cache + uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info + uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries + uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info + uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info + uint64_t otherImageGroupAddrUnused; // unused + uint64_t otherImageGroupSizeUnused; // unused + uint64_t progClosuresAddr; // (unslid) address of list of program launch closures + uint64_t progClosuresSize; // size of list of program launch closures + uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures + uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures + uint32_t platform; // platform number (macOS=1, etc) + uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion + dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if + // cache is valid + simulator : 1, // for simulator of specified platform + locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache + builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used + // for overrides + padding : 20; // TBD + uint64_t sharedRegionStart; // base load address of cache if not slid + uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any + uint64_t maxSlide; // runtime slide of cache can be between zero and this value + uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache + uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache + uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs + uint64_t dylibsTrieSize; // size of trie of cached dylib paths + uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures + uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures + uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures + uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info + uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries + uint64_t dylibsPBLStateArrayAddrUnused; // unused + uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs + uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program + uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program + uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet + uint32_t programTrieSize; + uint32_t osVersion; // OS Version of dylibs in this cache for the main platform + uint32_t altPlatform; // e.g. iOSMac on macOS + uint32_t altOsVersion; // e.g. 14.0 for iOSMac + uint64_t swiftOptsOffset; // VM offset from cache_header* to Swift optimizations header + uint64_t swiftOptsSize; // size of Swift optimizations header + uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry + uint32_t subCacheArrayCount; // number of subCache entries + uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols + uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data + uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region + uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data + uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region + uint32_t imagesOffset; // file offset to first dyld_cache_image_info + uint32_t imagesCount; // number of dyld_cache_image_info entries + uint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2) + uint32_t padding2; + uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header + uint64_t objcOptsSize; // size of ObjC optimizations header + uint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection + uint64_t cacheAtlasSize; // size of embedded cache atlas + uint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header + uint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data + uint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info + uint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries +}; + +struct PACKED_STRUCT dyld_subcache_entry +{ + char uuid[16]; + uint64_t address; +}; + +struct PACKED_STRUCT dyld_subcache_entry2 +{ + char uuid[16]; + uint64_t address; + char fileExtension[32]; +};
\ No newline at end of file diff --git a/view/sharedcache/core/FileAccessorCache.cpp b/view/sharedcache/core/FileAccessorCache.cpp new file mode 100644 index 00000000..047bbe19 --- /dev/null +++ b/view/sharedcache/core/FileAccessorCache.cpp @@ -0,0 +1,112 @@ +#include "FileAccessorCache.h" + +#include <cassert> + +CacheAccessorID GetCacheAccessorID(const std::string& filePath) +{ + constexpr std::hash<std::string> hasher; + return static_cast<CacheAccessorID>(hasher(filePath)); +} + +FileAccessorCache::FileAccessorCache(size_t cacheSize) +{ + m_cacheSize = cacheSize; + m_accessors = {}; +} + +void FileAccessorCache::EvictLastUsed() +{ + if (m_cache.empty()) + return; + // Evict the least recently used element. + const auto lruID = m_cache.front(); + m_cache.pop_front(); + // Ensure the least recently used ID actually exists in the accessors map. + assert(m_accessors.find(lruID) != m_accessors.end() && "Evicting non-existent ID from accessors map"); + m_accessors.erase(lruID); +} + +FileAccessorCache& FileAccessorCache::Global() +{ + static FileAccessorCache cache {}; + return cache; +} + + +WeakFileAccessor FileAccessorCache::Open(const std::string& filePath) +{ + const auto id = GetCacheAccessorID(filePath); + std::unique_lock lock(m_mutex); + + // Check if the file is already in the cache. + if (const auto it = m_accessors.find(id); it != m_accessors.end()) + { + // Move the accessed ID to the back so we keep it in the cache. + auto pos = std::find(m_cache.begin(), m_cache.end(), id); + if (pos != m_cache.end()) + m_cache.erase(pos); + m_cache.push_back(id); + + return WeakFileAccessor(it->second, filePath); + } + + // Evict if we are going to go above the limit. + if (m_cache.size() >= m_cacheSize) + EvictLastUsed(); + + // Create a new file accessor and add it to the cache. + auto accessor = MappedFileAccessor::Open(filePath); + if (accessor == nullptr) + { + // We failed to open the file, we must throw hard! + // TODO: Make this mechanism more thought out... + throw std::runtime_error("Failed to open file: " + filePath); + } + auto sharedAccessor = std::make_shared<MappedFileAccessor>(std::move(*accessor)); + m_accessors.insert_or_assign(id, sharedAccessor); + m_cache.push_back(id); + + return WeakFileAccessor(sharedAccessor, filePath); +} + +void FileAccessorWriteLog::AddPointer(const size_t address, const size_t pointer) +{ + // TODO: A sharded map here would be better. see: rust dashmap + std::unique_lock<std::shared_mutex> lock(m_persistedMutex); + m_persistedPointers[address] = pointer; +} + +void FileAccessorWriteLog::ApplyWrites(MappedFileAccessor& accessor) +{ + std::shared_lock<std::shared_mutex> lock(m_persistedMutex); + for (const auto& [address, pointer] : m_persistedPointers) + accessor.WritePointer(address, pointer); +} + +std::shared_ptr<MappedFileAccessor> WeakFileAccessor::lock() +{ + auto sharedPtr = m_weakPtr.lock(); + if (!sharedPtr) + { + // This will revive other weak pointers to the same shared ptr. + // Update the weak pointer to the newly created shared instance + m_weakPtr = FileAccessorCache::Global().Open(m_filePath).m_weakPtr; + sharedPtr = m_weakPtr.lock(); + + // Apply any previously written pointers back. + if (sharedPtr) + m_writeLog->ApplyWrites(*sharedPtr); + } + + return sharedPtr; +} + +void WeakFileAccessor::WritePointer(const size_t address, const size_t pointer) +{ + // Persist the pointer after the file accessor is revived. + m_writeLog->AddPointer(address, pointer); + + // And then actually apply the written pointer... + if (auto sharedPtr = m_weakPtr.lock()) + sharedPtr->WritePointer(address, pointer); +} diff --git a/view/sharedcache/core/FileAccessorCache.h b/view/sharedcache/core/FileAccessorCache.h new file mode 100644 index 00000000..bb2f8084 --- /dev/null +++ b/view/sharedcache/core/FileAccessorCache.h @@ -0,0 +1,84 @@ +#pragma once + +#include <shared_mutex> + +#include "MappedFileAccessor.h" + +typedef uint32_t CacheAccessorID; + +// TODO: We might want to make this more than just the path, for example +// TODO: We might want to make it unique to a view session (session id). +// Get a unique entry id for the given file path. +CacheAccessorID GetCacheAccessorID(const std::string& filePath); + +class WeakFileAccessor; + +class FileAccessorCache +{ + size_t m_cacheSize; + std::mutex m_mutex; + // NOTE: If we end up wanting to handle 1000's of files we should consider std::list. + std::deque<CacheAccessorID> m_cache; + std::unordered_map<CacheAccessorID, std::shared_ptr<MappedFileAccessor>> m_accessors; + + explicit FileAccessorCache(size_t cacheSize = 8); + + void EvictLastUsed(); + +public: + static FileAccessorCache& Global(); + + // Get a weak reference to a file accessor, the reference at this point is alive. + // The reference is always alive at this point either because it is in the cache or it has been inserted in. + // Subsequent calls to this might kill the backing file accessor resulting in the weak ref recreating the file + // accessor and inserting itself back into its related cache. + WeakFileAccessor Open(const std::string& filePath); + + // Adjust the cache size limit. + // This will NOT evict current cache entries, as they are already available. + // Any subsequent call to `Open` will assume this cache size, evicting until the size is equal to the cache size. + void SetCacheSize(uint64_t size) { m_cacheSize = size; }; +}; + +// Write log to be used in conjunction with `WeakFileAccessor` to re-apply written data to a "revived" file. +struct FileAccessorWriteLog +{ + // To persist writes to a file accessor being revived (within the lock() function) + // we keep a list of writes that will be re-applied in the lock function. + std::shared_mutex m_persistedMutex; + std::unordered_map<size_t, uint64_t> m_persistedPointers; + + FileAccessorWriteLog() = default; + + // Add the pointer to the persisted pointers. + void AddPointer(size_t address, size_t pointer); + + // Apply all logged writes to the given accessor. + void ApplyWrites(MappedFileAccessor& accessor); +}; + +class WeakFileAccessor +{ + // Weak pointer to the mapped file accessor, once this is expired we will re-open. + std::weak_ptr<MappedFileAccessor> m_weakPtr; + // File path for re-opening if needed + std::string m_filePath; + + // Used to re-add writes once the file accessor is "revived". + std::shared_ptr<FileAccessorWriteLog> m_writeLog; + + // TODO: Store a weak_ptr/shared_ptr to FileAccessorCache? That way we dont access Global() + // TODO: Only need to do the above if we want multiple caches. + +public: + explicit WeakFileAccessor(std::weak_ptr<MappedFileAccessor> weakPtr, std::string filePath) : + m_weakPtr(std::move(weakPtr)), m_filePath(std::move(filePath)), + m_writeLog(std::make_shared<FileAccessorWriteLog>()) + {} + + std::shared_ptr<MappedFileAccessor> lock(); + + // Persists the written pointer within this weak file accessor. + // This works as we expect the weak file accessor to be stored per virtual memory region. + void WritePointer(size_t address, size_t pointer); +}; diff --git a/view/sharedcache/core/MachO.cpp b/view/sharedcache/core/MachO.cpp new file mode 100644 index 00000000..14fffefd --- /dev/null +++ b/view/sharedcache/core/MachO.cpp @@ -0,0 +1,667 @@ +#include "MachO.h" +#include "Utility.h" + +#include "SharedCache.h" +#include "VirtualMemory.h" + +using namespace BinaryNinja; + +std::vector<uint64_t> SharedCacheMachOHeader::ReadFunctionTable(VirtualMemory& vm) const +{ + // NOTE: The funcoff is relative to the file of the linkedit segment. + uint64_t funcStartsAddress = GetLinkEditFileBase() + functionStarts.funcoff; + auto funcStarts = vm.ReadBuffer(funcStartsAddress, functionStarts.funcsize); + uint64_t curfunc = textBase; + uint64_t curOffset = 0; + + std::vector<uint64_t> functionTable = {}; + auto current = static_cast<const uint8_t*>(funcStarts.GetData()); + auto end = current + funcStarts.GetLength(); + while (current != end) + { + curOffset = readLEB128(current, end); + // TODO: Verify this is the correct behavior. + // Skip unmapped functions. + if (curOffset == 0 || !vm.IsAddressMapped(curfunc)) + continue; + curfunc += curOffset; + uint64_t target = curfunc; + functionTable.push_back(target); + } + return functionTable; +} + +std::optional<SharedCacheMachOHeader> SharedCacheMachOHeader::ParseHeaderForAddress( + std::shared_ptr<VirtualMemory> vm, uint64_t address, const std::string& imagePath) +{ + // Sanity check to make sure that the header is mapped. + // This should really only fail if we didn't grab all the required entries. + if (!vm->IsAddressMapped(address)) + return std::nullopt; + + SharedCacheMachOHeader header; + + header.textBase = address; + header.installName = imagePath; + // The identifierPrefix is used for the display of the image name in the sections and segments. + header.identifierPrefix = BaseFileName(imagePath); + + std::string errorMsg; + VirtualMemoryReader reader(vm); + reader.Seek(address); + + header.ident.magic = reader.ReadUInt32(); + + BNEndianness endianness; + switch (header.ident.magic) + { + case MH_MAGIC: + case MH_MAGIC_64: + endianness = LittleEndian; + break; + case MH_CIGAM: + case MH_CIGAM_64: + endianness = BigEndian; + break; + default: + return {}; + } + + reader.SetEndianness(endianness); + header.ident.cputype = reader.ReadUInt32(); + header.ident.cpusubtype = reader.ReadUInt32(); + header.ident.filetype = reader.ReadUInt32(); + header.ident.ncmds = reader.ReadUInt32(); + header.ident.sizeofcmds = reader.ReadUInt32(); + header.ident.flags = reader.ReadUInt32(); + if ((header.ident.cputype & MachOABIMask) == MachOABI64) // address size == 8 + { + header.ident.reserved = reader.ReadUInt32(); + } + header.loadCommandOffset = reader.GetOffset(); + + bool first = true; + // Parse segment commands + try + { + for (size_t i = 0; i < header.ident.ncmds; i++) + { + // BNLogInfo("of 0x%llx", reader.GetOffset()); + load_command load; + segment_command_64 segment64; + section_64 sect = {}; + size_t curOffset = reader.GetOffset(); + load.cmd = reader.ReadUInt32(); + load.cmdsize = reader.ReadUInt32(); + size_t nextOffset = curOffset + load.cmdsize; + if (load.cmdsize < sizeof(load_command)) + return {}; + + switch (load.cmd) + { + case LC_MAIN: + { + uint64_t entryPoint = reader.ReadUInt64(); + header.entryPoints.push_back({entryPoint, true}); + (void)reader.ReadUInt64(); // Stack start + break; + } + case LC_SEGMENT: // map the 32bit version to 64 bits + segment64.cmd = LC_SEGMENT_64; + reader.Read(&segment64.segname, 16); + segment64.vmaddr = reader.ReadUInt32(); + segment64.vmsize = reader.ReadUInt32(); + segment64.fileoff = reader.ReadUInt32(); + segment64.filesize = reader.ReadUInt32(); + segment64.maxprot = reader.ReadUInt32(); + segment64.initprot = reader.ReadUInt32(); + segment64.nsects = reader.ReadUInt32(); + segment64.flags = reader.ReadUInt32(); + if (first) + { + if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) + || (segment64.flags & MACHO_VM_PROT_WRITE)) + { + header.relocationBase = segment64.vmaddr; + first = false; + } + } + for (size_t j = 0; j < segment64.nsects; j++) + { + reader.Read(§.sectname, 16); + reader.Read(§.segname, 16); + sect.addr = reader.ReadUInt32(); + sect.size = reader.ReadUInt32(); + sect.offset = reader.ReadUInt32(); + sect.align = reader.ReadUInt32(); + sect.reloff = reader.ReadUInt32(); + sect.nreloc = reader.ReadUInt32(); + sect.flags = reader.ReadUInt32(); + sect.reserved1 = reader.ReadUInt32(); + sect.reserved2 = reader.ReadUInt32(); + // if the segment isn't mapped into virtual memory don't add the corresponding sections. + if (segment64.vmsize > 0) + { + header.sections.push_back(sect); + } + if (!strncmp(sect.sectname, "__mod_init_func", 15)) + header.moduleInitSections.push_back(sect); + if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + header.symbolStubSections.push_back(sect); + if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + } + header.segments.push_back(segment64); + break; + case LC_SEGMENT_64: + segment64.cmd = LC_SEGMENT_64; + reader.Read(&segment64.segname, 16); + segment64.vmaddr = reader.ReadUInt64(); + segment64.vmsize = reader.ReadUInt64(); + segment64.fileoff = reader.ReadUInt64(); + segment64.filesize = reader.ReadUInt64(); + segment64.maxprot = reader.ReadUInt32(); + segment64.initprot = reader.ReadUInt32(); + segment64.nsects = reader.ReadUInt32(); + segment64.flags = reader.ReadUInt32(); + if (strncmp(segment64.segname, "__LINKEDIT", 10) == 0) + { + header.linkeditSegment = segment64; + header.linkeditPresent = true; + } + if (first) + { + if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) + || (segment64.flags & MACHO_VM_PROT_WRITE)) + { + header.relocationBase = segment64.vmaddr; + first = false; + } + } + for (size_t j = 0; j < segment64.nsects; j++) + { + reader.Read(§.sectname, 16); + reader.Read(§.segname, 16); + sect.addr = reader.ReadUInt64(); + sect.size = reader.ReadUInt64(); + sect.offset = reader.ReadUInt32(); + sect.align = reader.ReadUInt32(); + sect.reloff = reader.ReadUInt32(); + sect.nreloc = reader.ReadUInt32(); + sect.flags = reader.ReadUInt32(); + sect.reserved1 = reader.ReadUInt32(); + sect.reserved2 = reader.ReadUInt32(); + sect.reserved3 = reader.ReadUInt32(); + // if the segment isn't mapped into virtual memory don't add the corresponding sections. + if (segment64.vmsize > 0) + { + header.sections.push_back(sect); + } + + if (!strncmp(sect.sectname, "__mod_init_func", 15)) + header.moduleInitSections.push_back(sect); + if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + header.symbolStubSections.push_back(sect); + if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + } + header.segments.push_back(segment64); + break; + case LC_ROUTINES: // map the 32bit version to 64bits + header.routines64.cmd = LC_ROUTINES_64; + header.routines64.init_address = reader.ReadUInt32(); + header.routines64.init_module = reader.ReadUInt32(); + header.routines64.reserved1 = reader.ReadUInt32(); + header.routines64.reserved2 = reader.ReadUInt32(); + header.routines64.reserved3 = reader.ReadUInt32(); + header.routines64.reserved4 = reader.ReadUInt32(); + header.routines64.reserved5 = reader.ReadUInt32(); + header.routines64.reserved6 = reader.ReadUInt32(); + header.routinesPresent = true; + break; + case LC_ROUTINES_64: + header.routines64.cmd = LC_ROUTINES_64; + header.routines64.init_address = reader.ReadUInt64(); + header.routines64.init_module = reader.ReadUInt64(); + header.routines64.reserved1 = reader.ReadUInt64(); + header.routines64.reserved2 = reader.ReadUInt64(); + header.routines64.reserved3 = reader.ReadUInt64(); + header.routines64.reserved4 = reader.ReadUInt64(); + header.routines64.reserved5 = reader.ReadUInt64(); + header.routines64.reserved6 = reader.ReadUInt64(); + header.routinesPresent = true; + break; + case LC_FUNCTION_STARTS: + header.functionStarts.funcoff = reader.ReadUInt32(); + header.functionStarts.funcsize = reader.ReadUInt32(); + header.functionStartsPresent = true; + break; + case LC_SYMTAB: + header.symtab.symoff = reader.ReadUInt32(); + header.symtab.nsyms = reader.ReadUInt32(); + header.symtab.stroff = reader.ReadUInt32(); + header.symtab.strsize = reader.ReadUInt32(); + break; + case LC_DYSYMTAB: + header.dysymtab.ilocalsym = reader.ReadUInt32(); + header.dysymtab.nlocalsym = reader.ReadUInt32(); + header.dysymtab.iextdefsym = reader.ReadUInt32(); + header.dysymtab.nextdefsym = reader.ReadUInt32(); + header.dysymtab.iundefsym = reader.ReadUInt32(); + header.dysymtab.nundefsym = reader.ReadUInt32(); + header.dysymtab.tocoff = reader.ReadUInt32(); + header.dysymtab.ntoc = reader.ReadUInt32(); + header.dysymtab.modtaboff = reader.ReadUInt32(); + header.dysymtab.nmodtab = reader.ReadUInt32(); + header.dysymtab.extrefsymoff = reader.ReadUInt32(); + header.dysymtab.nextrefsyms = reader.ReadUInt32(); + header.dysymtab.indirectsymoff = reader.ReadUInt32(); + header.dysymtab.nindirectsyms = reader.ReadUInt32(); + header.dysymtab.extreloff = reader.ReadUInt32(); + header.dysymtab.nextrel = reader.ReadUInt32(); + header.dysymtab.locreloff = reader.ReadUInt32(); + header.dysymtab.nlocrel = reader.ReadUInt32(); + header.dysymPresent = true; + break; + case LC_DYLD_CHAINED_FIXUPS: + header.chainedFixups.dataoff = reader.ReadUInt32(); + header.chainedFixups.datasize = reader.ReadUInt32(); + header.chainedFixupsPresent = true; + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + header.dyldInfo.rebase_off = reader.ReadUInt32(); + header.dyldInfo.rebase_size = reader.ReadUInt32(); + header.dyldInfo.bind_off = reader.ReadUInt32(); + header.dyldInfo.bind_size = reader.ReadUInt32(); + header.dyldInfo.weak_bind_off = reader.ReadUInt32(); + header.dyldInfo.weak_bind_size = reader.ReadUInt32(); + header.dyldInfo.lazy_bind_off = reader.ReadUInt32(); + header.dyldInfo.lazy_bind_size = reader.ReadUInt32(); + header.dyldInfo.export_off = reader.ReadUInt32(); + header.dyldInfo.export_size = reader.ReadUInt32(); + header.exportTrie.dataoff = header.dyldInfo.export_off; + header.exportTrie.datasize = header.dyldInfo.export_size; + header.exportTriePresent = true; + header.dyldInfoPresent = true; + break; + case LC_DYLD_EXPORTS_TRIE: + header.exportTrie.dataoff = reader.ReadUInt32(); + header.exportTrie.datasize = reader.ReadUInt32(); + header.exportTriePresent = true; + break; + case LC_THREAD: + case LC_UNIXTHREAD: + /*while (reader.GetOffset() < nextOffset) + { + + thread_command thread; + thread.flavor = reader.ReadUInt32(); + thread.count = reader.ReadUInt32(); + switch (m_archId) + { + case MachOx64: + m_logger->LogDebug("x86_64 Thread state\n"); + if (thread.flavor != X86_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statex64, sizeof(thread.statex64)); + header.entryPoints.push_back({thread.statex64.rip, false}); + break; + case MachOx86: + m_logger->LogDebug("x86 Thread state\n"); + if (thread.flavor != X86_THREAD_STATE32) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statex86, sizeof(thread.statex86)); + header.entryPoints.push_back({thread.statex86.eip, false}); + break; + case MachOArm: + m_logger->LogDebug("Arm Thread state\n"); + if (thread.flavor != _ARM_THREAD_STATE) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statearmv7, sizeof(thread.statearmv7)); + header.entryPoints.push_back({thread.statearmv7.r15, false}); + break; + case MachOAarch64: + case MachOAarch6432: + m_logger->LogDebug("Aarch64 Thread state\n"); + if (thread.flavor != _ARM_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + reader.Read(&thread.stateaarch64, sizeof(thread.stateaarch64)); + header.entryPoints.push_back({thread.stateaarch64.pc, false}); + break; + case MachOPPC: + m_logger->LogDebug("PPC Thread state\n"); + if (thread.flavor != PPC_THREAD_STATE) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //Read individual entries for endian reasons + header.entryPoints.push_back({reader.ReadUInt32(), false}); + (void)reader.ReadUInt32(); + (void)reader.ReadUInt32(); + //Read the rest of the structure + (void)reader.Read(&thread.stateppc.r1, sizeof(thread.stateppc) - (3 * 4)); + break; + case MachOPPC64: + m_logger->LogDebug("PPC64 Thread state\n"); + if (thread.flavor != PPC_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + header.entryPoints.push_back({reader.ReadUInt64(), false}); + (void)reader.ReadUInt64(); + (void)reader.ReadUInt64(); // Stack start + (void)reader.Read(&thread.stateppc64.r1, sizeof(thread.stateppc64) - (3 * 8)); + break; + default: + m_logger->LogError("Unknown archid: %x", m_archId); + } + + }*/ + break; + case LC_LOAD_DYLIB: + { + uint32_t offset = reader.ReadUInt32(); + if (offset < nextOffset) + { + reader.Seek(curOffset + offset); + std::string libname = reader.ReadCString(reader.GetOffset()); + header.dylibs.push_back(libname); + } + } + break; + case LC_BUILD_VERSION: + { + // m_logger->LogDebug("LC_BUILD_VERSION:"); + header.buildVersion.platform = reader.ReadUInt32(); + header.buildVersion.minos = reader.ReadUInt32(); + header.buildVersion.sdk = reader.ReadUInt32(); + header.buildVersion.ntools = reader.ReadUInt32(); + // m_logger->LogDebug("Platform: %s", BuildPlatformToString(header.buildVersion.platform).c_str()); + // m_logger->LogDebug("MinOS: %s", BuildToolVersionToString(header.buildVersion.minos).c_str()); + // m_logger->LogDebug("SDK: %s", BuildToolVersionToString(header.buildVersion.sdk).c_str()); + for (uint32_t j = 0; (i < header.buildVersion.ntools) && (j < 10); j++) + { + uint32_t tool = reader.ReadUInt32(); + uint32_t version = reader.ReadUInt32(); + header.buildToolVersions.push_back({tool, version}); + // m_logger->LogDebug("Build Tool: %s: %s", BuildToolToString(tool).c_str(), + // BuildToolVersionToString(version).c_str()); + } + break; + } + case LC_FILESET_ENTRY: + { + throw ReadException(); + } + default: + // m_logger->LogDebug("Unhandled command: %s : %" PRIu32 "\n", CommandToString(load.cmd).c_str(), + // load.cmdsize); + break; + } + if (reader.GetOffset() != nextOffset) + { + // m_logger->LogDebug("Didn't parse load command: %s fully %" PRIx64 ":%" PRIxPTR, + // CommandToString(load.cmd).c_str(), reader.GetOffset(), nextOffset); + } + reader.Seek(nextOffset); + } + + for (auto& section : header.sections) + { + char sectionName[17]; + memcpy(sectionName, section.sectname, sizeof(section.sectname)); + sectionName[16] = 0; + header.sectionNames.push_back(header.identifierPrefix + "::" + sectionName); + } + } + catch (ReadException&) + { + return {}; + } + + return header; +} + +// TODO: Support reading from .symbols file. +// TODO: Replace view with address size? +std::vector<CacheSymbol> SharedCacheMachOHeader::ReadSymbolTable(BinaryView& view, VirtualMemory& vm) const +{ + auto addressSize = view.GetAddressSize(); + // NOTE: The symbol table will exist within the link edit segment, the table offsets are relative to the file not + // the linkedit segment. + uint64_t symbolsAddress = GetLinkEditFileBase() + symtab.symoff; + uint64_t stringsAddress = GetLinkEditFileBase() + symtab.stroff; + + // TODO: This needs to be passed in as an optional argument. + // TODO: Sometimes symbol tables are shared and we have to offset into the table for a specific header. + // TODO: The "shared" symbol tables are stored in .symbols files. + int nlistStartIndex = 0; + + std::vector<CacheSymbol> symbolList; + for (uint64_t i = 0; i < symtab.nsyms; i++) + { + uint64_t entryIndex = (nlistStartIndex + i); + + nlist_64 nlist = {}; + if (addressSize == 4) + { + // 32-bit DSC + struct nlist nlist32 = {}; + vm.Read(&nlist, symbolsAddress + (entryIndex * sizeof(nlist32)), sizeof(nlist32)); + nlist.n_strx = nlist32.n_strx; + nlist.n_type = nlist32.n_type; + nlist.n_sect = nlist32.n_sect; + nlist.n_desc = nlist32.n_desc; + nlist.n_value = nlist32.n_value; + } + else + { + // 64-bit DSC + vm.Read(&nlist, symbolsAddress + (entryIndex * sizeof(nlist)), sizeof(nlist)); + } + + auto symbolAddress = nlist.n_value; + if (((nlist.n_type & N_TYPE) == N_INDR) || symbolAddress == 0) + continue; + + if (nlist.n_strx >= symtab.strsize) + { + // TODO: where logger? + LogError( + "Symbol entry at index %llu has a string offset of %u which is outside the strings buffer of size %u " + "for symbol table %x", + entryIndex, nlist.n_strx, symtab.strsize, symtab.stroff); + continue; + } + + std::string symbolName = vm.ReadCString(stringsAddress + nlist.n_strx); + if (symbolName == "<redacted>") + continue; + + std::optional<BNSymbolType> symbolType; + if ((nlist.n_type & N_TYPE) == N_SECT && nlist.n_sect > 0 && (size_t)(nlist.n_sect - 1) < sections.size()) + symbolType = DataSymbol; + else if ((nlist.n_type & N_TYPE) == N_ABS) + symbolType = DataSymbol; + else if ((nlist.n_type & N_EXT)) + symbolType = ExternalSymbol; + + if (!symbolType.has_value()) + { + // TODO: Where logger? + LogError("Symbol %s at address %llx has unknown symbol type", symbolName.c_str(), symbolAddress); + continue; + } + + std::optional<uint32_t> flags; + for (auto s : sections) + { + if (s.addr <= symbolAddress && symbolAddress < s.addr + s.size) + { + // First section to contain the address we will use its flags. + flags = s.flags; + break; + } + } + + if (symbolType != ExternalSymbol) + { + if (!flags.has_value()) + { + // TODO: where logger? + LogError("Symbol %s at address %llx is not in any section", symbolName.c_str(), symbolAddress); + continue; + } + + if ((flags.value() & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS + || (flags.value() & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) + symbolType = FunctionSymbol; + else + symbolType = DataSymbol; + } + if ((nlist.n_desc & N_ARM_THUMB_DEF) == N_ARM_THUMB_DEF) + symbolAddress++; + + CacheSymbol symbol; + symbol.address = symbolAddress; + symbol.name = std::move(symbolName); + symbol.type = symbolType.value(); + symbolList.emplace_back(symbol); + } + + return symbolList; +} + +std::optional<CacheSymbol> SharedCacheMachOHeader::AddExportTerminalSymbol( + const std::string& symbolName, const uint8_t* current, const uint8_t* end) const +{ + uint64_t symbolFlags = readValidULEB128(current, end); + if (symbolFlags & EXPORT_SYMBOL_FLAGS_REEXPORT) + return std::nullopt; + + uint64_t imageOffset = readValidULEB128(current, end); + uint64_t symbolAddress = textBase + imageOffset; + if (symbolName.empty() || symbolAddress == 0) + return std::nullopt; + + // Tries to get the symbol type based off the section containing it. + auto sectionSymbolType = [&]() -> BNSymbolType { + uint32_t sectionFlags = 0; + for (const auto& section : sections) + { + if (symbolAddress >= section.addr && symbolAddress < section.addr + section.size) + { + // Take the flags from the first containing section. + sectionFlags = section.flags; + break; + } + } + + // TODO: Is this enough to determine a function symbol? + // TODO: Might be the cause of https://github.com/Vector35/binaryninja-api/issues/6526 + // Check the sections flags to see if we actually have a function symbol instead. + if (sectionFlags & S_ATTR_PURE_INSTRUCTIONS || sectionFlags & S_ATTR_SOME_INSTRUCTIONS) + return FunctionSymbol; + + // By default, just return data symbol. + return DataSymbol; + }; + + switch (symbolFlags & EXPORT_SYMBOL_FLAGS_KIND_MASK) + { + case EXPORT_SYMBOL_FLAGS_KIND_REGULAR: + case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL: + return CacheSymbol(sectionSymbolType(), symbolAddress, symbolName); + case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE: + return CacheSymbol(DataSymbol, symbolAddress, symbolName); + default: + LogWarn("Unhandled export symbol kind: %llx", symbolFlags & EXPORT_SYMBOL_FLAGS_KIND_MASK); + return std::nullopt; + } +} + +// TODO: This is like 90% of the runtime. +bool SharedCacheMachOHeader::ProcessLinkEditTrie(std::vector<CacheSymbol>& symbols, const std::string& currentText, + const uint8_t* begin, const uint8_t* current, const uint8_t* end) const +{ + if (current >= end) + return false; + + uint64_t terminalSize = readValidULEB128(current, end); + const uint8_t* child = current + terminalSize; + + // The terminal is an export symbol. + if (terminalSize != 0) + { + // Add the export symbol is applicable. + auto symbol = AddExportTerminalSymbol(currentText, current, end); + if (symbol.has_value()) + symbols.push_back(*symbol); + } + + // TODO: Make this look better + current = child; + uint8_t childCount = *current++; + std::string childText = currentText; + for (uint8_t i = 0; i < childCount; ++i) + { + if (current >= end) + return false; + const auto it = std::find(current, end, 0); + childText.append(current, it); + current = it + 1; + if (current >= end) + return false; + const auto next = readValidULEB128(current, end); + if (next == 0) + return false; + if (!ProcessLinkEditTrie(symbols, childText, begin, begin + next, end)) + return false; + childText.resize(currentText.size()); + } + + return true; +} + +std::vector<CacheSymbol> SharedCacheMachOHeader::ReadExportSymbolTrie(VirtualMemory& vm) const +{ + if (exportTrie.datasize == 0) + return {}; + + uint64_t exportTrieAddress = GetLinkEditFileBase() + exportTrie.dataoff; + std::vector<CacheSymbol> symbols = {}; + try + { + auto [begin, end] = vm.ReadSpan(exportTrieAddress, exportTrie.datasize); + ProcessLinkEditTrie(symbols, "", begin, begin, end); + } + catch (std::exception& e) + { + BNLogError("Failed to read Export Trie: %s", e.what()); + } + return symbols; +} diff --git a/view/sharedcache/core/MachO.h b/view/sharedcache/core/MachO.h new file mode 100644 index 00000000..37b82915 --- /dev/null +++ b/view/sharedcache/core/MachO.h @@ -0,0 +1,76 @@ +#pragma once + +#include "VirtualMemory.h" + +// TODO: Including this adds a bunch of binary ninja specific stuff :ugh: +#include "view/macho/machoview.h" + +struct CacheSymbol; + +struct SharedCacheMachOHeader +{ + uint64_t textBase = 0; + uint64_t loadCommandOffset = 0; + BinaryNinja::mach_header_64 ident; + // NOTE: This should never be empty. + std::string identifierPrefix; + std::string installName; + + std::vector<std::pair<uint64_t, bool>> entryPoints; + std::vector<uint64_t> m_entryPoints; // list of entrypoints + + BinaryNinja::symtab_command symtab; + BinaryNinja::dysymtab_command dysymtab; + BinaryNinja::dyld_info_command dyldInfo; + BinaryNinja::routines_command_64 routines64; + BinaryNinja::function_starts_command functionStarts; + std::vector<BinaryNinja::section_64> moduleInitSections; + BinaryNinja::linkedit_data_command exportTrie; + BinaryNinja::linkedit_data_command chainedFixups {}; + + uint64_t relocationBase; + // Section and program headers, internally use 64-bit form as it is a superset of 32-bit + std::vector<BinaryNinja::segment_command_64> segments; // only three types of sections __TEXT, __DATA, __IMPORT + BinaryNinja::segment_command_64 linkeditSegment; + std::vector<BinaryNinja::section_64> sections; + std::vector<std::string> sectionNames; + + std::vector<BinaryNinja::section_64> symbolStubSections; + std::vector<BinaryNinja::section_64> symbolPointerSections; + + std::vector<std::string> dylibs; + + BinaryNinja::build_version_command buildVersion; + std::vector<BinaryNinja::build_tool_version> buildToolVersions; + + std::string exportTriePath; + + bool linkeditPresent = false; + bool dysymPresent = false; + bool dyldInfoPresent = false; + bool exportTriePresent = false; + bool chainedFixupsPresent = false; + bool routinesPresent = false; + bool functionStartsPresent = false; + bool relocatable = false; + + // The base address of the link edit file. + // Use this if you want to read offsets relative to the file containing the link edit segment. + uint64_t GetLinkEditFileBase() const { return linkeditSegment.vmaddr - linkeditSegment.fileoff; }; + + static std::optional<SharedCacheMachOHeader> ParseHeaderForAddress( + std::shared_ptr<VirtualMemory> vm, uint64_t address, const std::string& imagePath); + + // TODO: Replace view with address size? + std::vector<CacheSymbol> ReadSymbolTable(BinaryNinja::BinaryView& view, VirtualMemory& vm) const; + + std::optional<CacheSymbol> AddExportTerminalSymbol( + const std::string& symbolName, const uint8_t* current, const uint8_t* end) const; + + bool ProcessLinkEditTrie(std::vector<CacheSymbol>& symbols, const std::string& currentText, const uint8_t* begin, + const uint8_t* current, const uint8_t* end) const; + + std::vector<CacheSymbol> ReadExportSymbolTrie(VirtualMemory& vm) const; + + std::vector<uint64_t> ReadFunctionTable(VirtualMemory& vm) const; +}; diff --git a/view/sharedcache/core/MachOProcessor.cpp b/view/sharedcache/core/MachOProcessor.cpp new file mode 100644 index 00000000..be2e9007 --- /dev/null +++ b/view/sharedcache/core/MachOProcessor.cpp @@ -0,0 +1,320 @@ +#include "MachOProcessor.h" +#include "SharedCache.h" + +using namespace BinaryNinja; + +SharedCacheMachOProcessor::SharedCacheMachOProcessor(Ref<BinaryView> view, std::shared_ptr<VirtualMemory> vm) +{ + m_view = view; + m_logger = new Logger("SharedCacheMachOProcessor", view->GetFile()->GetSessionId()); + m_vm = std::move(vm); + + // Adjust processor settings. + if (Ref<Settings> settings = m_view->GetLoadSettings(VIEW_NAME)) + { + if (settings->Contains("loader.dsc.processFunctionStarts")) + m_applyFunctions = settings->Get<bool>("loader.dsc.processFunctionStarts", m_view); + } +} + +void SharedCacheMachOProcessor::ApplyHeader(SharedCacheMachOHeader& header) +{ + // Add a section for the header itself. + std::string headerSection = fmt::format("{}::__macho_header", header.identifierPrefix); + // TODO: Support mach_header (non 64bit) + uint64_t headerSectionSize = sizeof(mach_header_64) + header.ident.sizeofcmds; + m_view->AddUserSection(headerSection, header.textBase, headerSectionSize, ReadOnlyDataSectionSemantics); + + ApplyHeaderSections(header); + ApplyHeaderDataVariables(header); + + if (header.linkeditPresent && m_vm->IsAddressMapped(header.linkeditSegment.vmaddr)) + { + if (m_applyFunctions && header.functionStartsPresent) + { + auto targetPlatform = m_view->GetDefaultPlatform(); + auto functions = header.ReadFunctionTable(*m_vm); + for (const auto& func : functions) + { + // TODO: Check to make sure the func exists in a loaded region? + // TODO: ^ this check existed prior so we should add it back. + m_view->AddFunctionForAnalysis(targetPlatform, func); + } + } + + auto typeLib = m_view->GetTypeLibrary(header.installName); + m_view->BeginBulkModifySymbols(); + + // TODO: Why does this need to only happen in linkeditSegment? + // Apply symbols from symbol table. + if (header.symtab.symoff != 0) + { + // Mach-O View symtab processing with + // a ton of stuff cut out so it can work + // NOTE: This table is read relative to the link edit segment file base. + // TODO: Remove this and use the m_symbols in the cache? + const auto symbols = header.ReadSymbolTable(*m_view, *m_vm); + for (const auto& sym : symbols) + ApplySymbol(m_view, typeLib, sym.ToBNSymbol()); + } + + // Apply symbols from export trie. + if (header.exportTriePresent) + { + // NOTE: This table is read relative to the link edit segment file base. + // TODO: Remove this and use the m_symbols in the cache? + const auto exportSymbols = header.ReadExportSymbolTrie(*m_vm); + for (const auto& sym : exportSymbols) + ApplySymbol(m_view, typeLib, sym.ToBNSymbol()); + } + m_view->EndBulkModifySymbols(); + } +} + +uint64_t SharedCacheMachOProcessor::ApplyHeaderSections(SharedCacheMachOHeader& header) +{ + auto initSection = [&](const section_64& section, const std::string& sectionName) { + if (!section.size) + return false; + + std::string type; + BNSectionSemantics semantics = DefaultSectionSemantics; + switch (section.flags & 0xff) + { + case S_REGULAR: + if (section.flags & S_ATTR_PURE_INSTRUCTIONS) + { + type = "PURE_CODE"; + semantics = ReadOnlyCodeSectionSemantics; + } + else if (section.flags & S_ATTR_SOME_INSTRUCTIONS) + { + type = "CODE"; + semantics = ReadOnlyCodeSectionSemantics; + } + else + { + type = "REGULAR"; + } + break; + case S_ZEROFILL: + type = "ZEROFILL"; + semantics = ReadWriteDataSectionSemantics; + break; + case S_CSTRING_LITERALS: + type = "CSTRING_LITERALS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_4BYTE_LITERALS: + type = "4BYTE_LITERALS"; + break; + case S_8BYTE_LITERALS: + type = "8BYTE_LITERALS"; + break; + case S_LITERAL_POINTERS: + type = "LITERAL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_NON_LAZY_SYMBOL_POINTERS: + type = "NON_LAZY_SYMBOL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_LAZY_SYMBOL_POINTERS: + type = "LAZY_SYMBOL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_SYMBOL_STUBS: + type = "SYMBOL_STUBS"; + semantics = ReadOnlyCodeSectionSemantics; + break; + case S_MOD_INIT_FUNC_POINTERS: + type = "MOD_INIT_FUNC_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_MOD_TERM_FUNC_POINTERS: + type = "MOD_TERM_FUNC_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_COALESCED: + type = "COALESCED"; + break; + case S_GB_ZEROFILL: + type = "GB_ZEROFILL"; + semantics = ReadWriteDataSectionSemantics; + break; + case S_INTERPOSING: + type = "INTERPOSING"; + break; + case S_16BYTE_LITERALS: + type = "16BYTE_LITERALS"; + break; + case S_DTRACE_DOF: + type = "DTRACE_DOF"; + break; + case S_LAZY_DYLIB_SYMBOL_POINTERS: + type = "LAZY_DYLIB_SYMBOL_POINTERS"; + semantics = ReadOnlyDataSectionSemantics; + break; + case S_THREAD_LOCAL_REGULAR: + type = "THREAD_LOCAL_REGULAR"; + break; + case S_THREAD_LOCAL_ZEROFILL: + type = "THREAD_LOCAL_ZEROFILL"; + break; + case S_THREAD_LOCAL_VARIABLES: + type = "THREAD_LOCAL_VARIABLES"; + break; + case S_THREAD_LOCAL_VARIABLE_POINTERS: + type = "THREAD_LOCAL_VARIABLE_POINTERS"; + break; + case S_THREAD_LOCAL_INIT_FUNCTION_POINTERS: + type = "THREAD_LOCAL_INIT_FUNCTION_POINTERS"; + break; + default: + type = "UNKNOWN"; + break; + } + + if (strncmp(section.sectname, "__text", sizeof(section.sectname)) == 0) + semantics = ReadOnlyCodeSectionSemantics; + if (strncmp(section.sectname, "__const", sizeof(section.sectname)) == 0) + semantics = ReadOnlyDataSectionSemantics; + if (strncmp(section.sectname, "__data", sizeof(section.sectname)) == 0) + semantics = ReadWriteDataSectionSemantics; + if (strncmp(section.segname, "__DATA_CONST", sizeof(section.segname)) == 0) + semantics = ReadOnlyDataSectionSemantics; + if (strncmp(section.segname, "__auth_got", sizeof(section.segname)) == 0) + semantics = ReadOnlyDataSectionSemantics; + + m_view->AddUserSection(sectionName, section.addr, section.size, semantics, type, section.align); + + return true; + }; + + uint64_t addedSections = 0; + for (size_t i = 0; i < header.sections.size() && i < header.sectionNames.size(); i++) + { + if (initSection(header.sections[i], header.sectionNames[i])) + addedSections++; + } + return addedSections; +} + +void SharedCacheMachOProcessor::ApplyHeaderDataVariables(SharedCacheMachOHeader& header) +{ + // TODO: By using a binary reader we assume the sections have all been mapped. + // TODO: Maybe we should just use the virtual memory reader... + // TODO: We can define symbols and data variables even if there is no backing region FWIW + BinaryReader reader(m_view); + // TODO: Do we support non 64 bit header? + reader.Seek(header.textBase + sizeof(mach_header_64)); + + m_view->DefineDataVariable(header.textBase, Type::NamedType(m_view, QualifiedName("mach_header_64"))); + m_view->DefineAutoSymbol( + new Symbol(DataSymbol, "__macho_header::" + header.identifierPrefix, header.textBase, LocalBinding)); + + auto applyLoadCommand = [&](uint64_t cmdAddr, const load_command& load) { + switch (load.cmd) + { + case LC_SEGMENT: + { + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("segment_command"))); + reader.SeekRelative(5 * 8); + size_t numSections = reader.Read32(); + reader.SeekRelative(4); + for (size_t j = 0; j < numSections; j++) + { + m_view->DefineDataVariable(reader.GetOffset(), Type::NamedType(m_view, QualifiedName("section"))); + auto sectionSymName = + fmt::format("__macho_section::{}_[{}]", header.identifierPrefix, std::to_string(j)); + auto sectionSym = new Symbol(DataSymbol, sectionSymName, reader.GetOffset(), LocalBinding); + m_view->DefineAutoSymbol(sectionSym); + reader.SeekRelative((8 * 8) + 4); + } + break; + } + case LC_SEGMENT_64: + { + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("segment_command_64"))); + reader.SeekRelative(7 * 8); + size_t numSections = reader.Read32(); + reader.SeekRelative(4); + for (size_t j = 0; j < numSections; j++) + { + m_view->DefineDataVariable(reader.GetOffset(), Type::NamedType(m_view, QualifiedName("section_64"))); + auto sectionSymName = + fmt::format("__macho_section_64::{}_[{}]", header.identifierPrefix, std::to_string(j)); + auto sectionSym = new Symbol(DataSymbol, sectionSymName, reader.GetOffset(), LocalBinding); + m_view->DefineAutoSymbol(sectionSym); + reader.SeekRelative(10 * 8); + } + break; + } + case LC_SYMTAB: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("symtab"))); + break; + case LC_DYSYMTAB: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("dysymtab"))); + break; + case LC_UUID: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("uuid"))); + break; + case LC_ID_DYLIB: + case LC_LOAD_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("dylib_command"))); + if (load.cmdsize - 24 <= 150) + m_view->DefineDataVariable( + cmdAddr + 24, Type::ArrayType(Type::IntegerType(1, true), load.cmdsize - 24)); + break; + case LC_CODE_SIGNATURE: + case LC_SEGMENT_SPLIT_INFO: + case LC_FUNCTION_STARTS: + case LC_DATA_IN_CODE: + case LC_DYLIB_CODE_SIGN_DRS: + case LC_DYLD_EXPORTS_TRIE: + case LC_DYLD_CHAINED_FIXUPS: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("linkedit_data"))); + break; + case LC_ENCRYPTION_INFO: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("encryption_info"))); + break; + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("version_min"))); + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("dyld_info"))); + break; + default: + m_view->DefineDataVariable(cmdAddr, Type::NamedType(m_view, QualifiedName("load_command"))); + break; + } + }; + + try + { + for (size_t i = 0; i < header.ident.ncmds; i++) + { + load_command load {}; + uint64_t curOffset = reader.GetOffset(); + load.cmd = reader.Read32(); + load.cmdsize = reader.Read32(); + + applyLoadCommand(curOffset, load); + m_view->DefineAutoSymbol(new Symbol(DataSymbol, + "__macho_load_command::" + header.identifierPrefix + "_[" + std::to_string(i) + "]", curOffset, + LocalBinding)); + + uint64_t nextOffset = curOffset + load.cmdsize; + reader.Seek(nextOffset); + } + } + catch (ReadException&) + { + m_logger->LogError("Error when applying Mach-O header types at %llx", header.textBase); + } +} diff --git a/view/sharedcache/core/MachOProcessor.h b/view/sharedcache/core/MachOProcessor.h new file mode 100644 index 00000000..0564a831 --- /dev/null +++ b/view/sharedcache/core/MachOProcessor.h @@ -0,0 +1,23 @@ +#pragma once +#include "MachO.h" + +// Process `SharedCacheMachOHeader`. +class SharedCacheMachOProcessor +{ + BinaryNinja::Ref<BinaryNinja::BinaryView> m_view; + BinaryNinja::Ref<BinaryNinja::Logger> m_logger; + std::shared_ptr<VirtualMemory> m_vm; + + bool m_applyFunctions = true; + +public: + explicit SharedCacheMachOProcessor( + BinaryNinja::Ref<BinaryNinja::BinaryView> view, std::shared_ptr<VirtualMemory> vm); + + // Initialize header information such as sections and symbols. + void ApplyHeader(SharedCacheMachOHeader& header); + + uint64_t ApplyHeaderSections(SharedCacheMachOHeader& header); + + void ApplyHeaderDataVariables(SharedCacheMachOHeader& header); +}; diff --git a/view/sharedcache/core/MappedFile.cpp b/view/sharedcache/core/MappedFile.cpp new file mode 100644 index 00000000..e0b805bc --- /dev/null +++ b/view/sharedcache/core/MappedFile.cpp @@ -0,0 +1,193 @@ +#ifdef _MSC_VER + #include <windows.h> +#else + #include <sys/mman.h> + #include <fcntl.h> + #include <cstdlib> + #include <sys/resource.h> +#endif + +#include "MappedFile.h" +#include "binaryninjaapi.h" + +#ifndef _MSC_VER +uint64_t AdjustFileDescriptorLimit() +{ + // The soft file descriptor limit on Linux and Mac is a lot lower than + // on Windows (1024 for Linux, 256 for Mac). Recent iOS shared caches + // have 60+ files which may not leave much headroom if a user opens + // more than one at a time. Attempt to increase the file descriptor + // limit to 1024, and limit ourselves to caching half of them as a + // memory vs performance trade-off (closing and re-opening a file + // requires parsing and applying the slide information again). + uint64_t maxFPLimit = 1024; + + // check for BN_SHAREDCACHE_FP_MAX + // if it exists, set maxFPLimit to that value + if (auto env = getenv("BN_SHAREDCACHE_FP_MAX"); env) + { + // FIXME behav on 0 here is unintuitive, '0123' will interpret as octal and be 83 according to manpage. meh. + maxFPLimit = strtoull(env, nullptr, 0); + if (maxFPLimit < 10) + { + BinaryNinja::LogWarn( + "BN_SHAREDCACHE_FP_MAX set to below 10. A value of at least 10 is recommended for performant analysis " + "on SharedCache Binaries."); + } + if (maxFPLimit == 0) + { + BinaryNinja::LogError("BN_SHAREDCACHE_FP_MAX set to 0. Adjusting to 1"); + maxFPLimit = 1; + } + } + + rlimit rlim {}; + getrlimit(RLIMIT_NOFILE, &rlim); + uint64_t previousLimit = rlim.rlim_cur; + uint64_t targetLimit = std::min(maxFPLimit, rlim.rlim_max); + if (rlim.rlim_cur < targetLimit) + { + rlim.rlim_cur = targetLimit; + if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) + { + perror("setrlimit(RLIMIT_NOFILE)"); + rlim.rlim_cur = previousLimit; + } + } + + maxFPLimit = rlim.rlim_cur / 2; + return maxFPLimit; +} + +MappedFile::~MappedFile() +{ + Unmap(); + if (fd) + fclose(fd); +} + +std::optional<MappedFile> MappedFile::OpenFile(const std::string& path) +{ + MappedFile file; + file._mmap = nullptr; + file.fd = fopen(path.c_str(), "r"); + if (file.fd == nullptr) + return std::nullopt; + + fseek(file.fd, 0L, SEEK_END); + file.len = ftell(file.fd); + fseek(file.fd, 0L, SEEK_SET); + + return file; +} + +MapStatus MappedFile::Map() +{ + if (_mmap) + return MapStatus::Success; + + void* result = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(fd), 0u); + if (result == MAP_FAILED) + { + BinaryNinja::LogError("mmap failed: %s", strerror(errno)); // Use errno to log the reason + return MapStatus::Error; + } + _mmap = static_cast<uint8_t*>(result); + + return MapStatus::Success; +} + +MapStatus MappedFile::Unmap() +{ + if (_mmap) + { + munmap(_mmap, len); + _mmap = nullptr; + } + return MapStatus::Success; +} +#else +uint64_t AdjustFileDescriptorLimit() +{ + return 0x1000000; +} + +MappedFile::~MappedFile() +{ + Unmap(); + if (hFile) + CloseHandle(hFile); +} + +std::optional<MappedFile> MappedFile::OpenFile(const std::string& path) +{ + MappedFile file; + file._mmap = nullptr; + file.hFile = CreateFile(path.c_str(), // file name + GENERIC_READ, // desired access (read-only) + FILE_SHARE_READ, // share mode + NULL, // security attributes + OPEN_EXISTING, // creation disposition + FILE_ATTRIBUTE_NORMAL, // flags and attributes + NULL); // template file + + if (file.hFile == INVALID_HANDLE_VALUE) + return std::nullopt; + + LARGE_INTEGER fileSize; + if (!GetFileSizeEx(file.hFile, &fileSize)) + { + CloseHandle(file.hFile); + return std::nullopt; + } + file.len = static_cast<size_t>(fileSize.QuadPart); + + return file; +} + +MapStatus MappedFile::Map() +{ + if (_mmap) + return MapStatus::Success; + + HANDLE hMapping = CreateFileMapping(hFile, // file handle + NULL, // security attributes + PAGE_WRITECOPY, // protection + 0, // maximum size (high-order DWORD) + 0, // maximum size (low-order DWORD) + NULL); // name of the mapping object + + if (hMapping == NULL) + { + CloseHandle(hFile); + return MapStatus::Error; + } + + _mmap = static_cast<uint8_t*>(MapViewOfFile(hMapping, // handle to the file mapping object + FILE_MAP_COPY, // desired access + 0, // file offset (high-order DWORD) + 0, // file offset (low-order DWORD) + 0)); // number of bytes to map (0 = entire file) + + if (_mmap == nullptr) + { + CloseHandle(hMapping); + CloseHandle(hFile); + return MapStatus::Error; + } + + CloseHandle(hMapping); + CloseHandle(hFile); + + return MapStatus::Success; +} + +MapStatus MappedFile::Unmap() +{ + if (_mmap) + { + UnmapViewOfFile(_mmap); + } + return MapStatus::Success; +} +#endif diff --git a/view/sharedcache/core/MappedFile.h b/view/sharedcache/core/MappedFile.h new file mode 100644 index 00000000..ced79f52 --- /dev/null +++ b/view/sharedcache/core/MappedFile.h @@ -0,0 +1,86 @@ +#pragma once + +#include <string> +#include <cstdint> +#include <cstdio> +#include <optional> + +// Call this when initializing the plugin so that the process file descriptor limit is raised. +uint64_t AdjustFileDescriptorLimit(); + +enum MapStatus : int +{ + Success, + // TODO: Split this error out into more contextual errors. + Error, +}; + +struct MappedFile +{ + uint8_t* _mmap = nullptr; + size_t len = 0; + +#ifdef _MSC_VER + HANDLE hFile = INVALID_HANDLE_VALUE; +#else + FILE* fd = nullptr; +#endif + + MappedFile() = default; + + ~MappedFile(); + + MappedFile(const MappedFile&) = delete; + + MappedFile& operator=(const MappedFile&) = delete; + + MappedFile(MappedFile&& other) noexcept : _mmap(other._mmap), len(other.len) + { +#ifdef _MSC_VER + hFile = other.hFile; + // Don't close the hFile in the move. + other.hFile = nullptr; +#else + fd = other.fd; + // Don't close the fd in the move. + other.fd = nullptr; +#endif + other._mmap = nullptr; + } + + // I hate C++ + MappedFile& operator=(MappedFile&& other) noexcept + { + if (this != &other) + { + Unmap(); +#ifdef _MSC_VER + if (hFile != nullptr) + { + CloseHandle(hFile); + } + hFile = other.hFile; + // Don't close the hFile in the move. + other.hFile = nullptr; +#else + if (fd != nullptr) + { + fclose(fd); + } + fd = other.fd; + // Don't close the fd in the move. + other.fd = nullptr; +#endif + len = other.len; + _mmap = other._mmap; + other._mmap = nullptr; + } + return *this; + } + + static std::optional<MappedFile> OpenFile(const std::string& path); + + MapStatus Map(); + + MapStatus Unmap(); +}; diff --git a/view/sharedcache/core/MappedFileAccessor.cpp b/view/sharedcache/core/MappedFileAccessor.cpp new file mode 100644 index 00000000..ddd6ce9b --- /dev/null +++ b/view/sharedcache/core/MappedFileAccessor.cpp @@ -0,0 +1,101 @@ +#include "MappedFileAccessor.h" + + +std::shared_ptr<MappedFileAccessor> MappedFileAccessor::Open(const std::string& filePath) +{ + auto file = MappedFile::OpenFile(filePath); + if (!file.has_value()) + return nullptr; + if (file->Map() != MapStatus::Success) + return nullptr; + return std::make_shared<MappedFileAccessor>(std::move(*file)); +} + +// TODO: Will obviously not work on 32bit binaries, need to make WritePointer64 and 32 equiv. +void MappedFileAccessor::WritePointer(size_t address, size_t pointer) +{ + m_dirty = true; + *reinterpret_cast<size_t*>(&m_file._mmap[address]) = pointer; +} + +std::string MappedFileAccessor::ReadNullTermString(size_t address, size_t maxLength) const +{ + if (address > Length()) + return ""; + auto start = m_file._mmap + address; + auto endLen = (maxLength > 0) ? maxLength : m_file.len; + auto end = start + endLen; + auto nul = std::find(start, end, 0); + return {start, nul}; +} + +uint8_t MappedFileAccessor::ReadUInt8(size_t address) +{ + return Read<uint8_t>(address); +} + +int8_t MappedFileAccessor::ReadInt8(size_t address) +{ + return Read<int8_t>(address); +} + +uint16_t MappedFileAccessor::ReadUInt16(size_t address) +{ + return Read<uint16_t>(address); +} + +int16_t MappedFileAccessor::ReadInt16(size_t address) +{ + return Read<int16_t>(address); +} + + +uint32_t MappedFileAccessor::ReadUInt32(size_t address) +{ + return Read<uint32_t>(address); +} + +int32_t MappedFileAccessor::ReadInt32(size_t address) +{ + return Read<int32_t>(address); +} + +uint64_t MappedFileAccessor::ReadUInt64(size_t address) +{ + return Read<uint64_t>(address); +} + +int64_t MappedFileAccessor::ReadInt64(size_t address) +{ + return Read<int64_t>(address); +} + +BinaryNinja::DataBuffer MappedFileAccessor::ReadBuffer(size_t addr, size_t length) +{ + if (addr + length > Length()) + throw UnmappedAccessException(addr + length, Length()); + return {&m_file._mmap[addr], length}; +} + +std::pair<const uint8_t*, const uint8_t*> MappedFileAccessor::ReadSpan(size_t addr, size_t length) +{ + if (addr + length > Length()) + throw UnmappedAccessException(addr + length, Length()); + const uint8_t* data = &m_file._mmap[addr]; + return {data, data + length}; +} + +void MappedFileAccessor::Read(void* dest, size_t addr, size_t length) const +{ + if (addr + length > Length()) + throw UnmappedAccessException(addr + length, Length()); + memcpy(dest, &m_file._mmap[addr], length); +} + +template <typename T> +T MappedFileAccessor::Read(size_t address) +{ + T result; + Read(&result, address, sizeof(T)); + return result; +} diff --git a/view/sharedcache/core/MappedFileAccessor.h b/view/sharedcache/core/MappedFileAccessor.h new file mode 100644 index 00000000..7b01cb0d --- /dev/null +++ b/view/sharedcache/core/MappedFileAccessor.h @@ -0,0 +1,103 @@ +#pragma once + +#include "binaryninjaapi.h" +#include "MappedFile.h" + +#include <cstdint> +#include <list> + +class UnmappedAccessException : public std::exception +{ + uint64_t m_address; + uint64_t m_fileLen; + +public: + explicit UnmappedAccessException(uint64_t address, uint64_t fileLength) : m_address(address), m_fileLen(fileLength) + {} + + virtual const char* what() const throw() + { + thread_local std::string message; + message = + fmt::format("Tried to access unmapped address {0:x} for file with length of {0:x}", m_address, m_fileLen); + return message.c_str(); + } +}; + +// std::enable_shared_from_this allows weak pointers to "revive" the shared pointer in the FileAccessorCache. +class MappedFileAccessor : public std::enable_shared_from_this<MappedFileAccessor> +{ + MappedFile m_file; + bool m_dirty = false; + +public: + explicit MappedFileAccessor(MappedFile file) : m_file(std::move(file)) {} + + ~MappedFileAccessor() = default; + + MappedFileAccessor(const MappedFileAccessor&) = delete; + + MappedFileAccessor& operator=(const MappedFileAccessor&) = delete; + + MappedFileAccessor(MappedFileAccessor&&) noexcept = default; + + MappedFileAccessor& operator=(MappedFileAccessor&&) noexcept = default; + + static std::shared_ptr<MappedFileAccessor> Open(const std::string& filePath); + + size_t Length() const { return m_file.len; }; + + void* Data() const { return m_file._mmap; }; + + bool IsDirty() const { return m_dirty; } + + /** + * Writes to files are implemented for performance reasons and should be treated with utmost care + * + * They _MAY_ disappear as _soon_ as you release the lock on the file. + * They may also NOT disappear for the lifetime of the application. + * + * The former is more likely to occur when concurrent DSC processing is happening. The latter is the typical + * scenario. + * + * This is used explicitly for slide information in a locked scope and _NOTHING_ else. It should probably not be + * used for anything else. + * + * \param address The address to write the pointer to + * \param pointer The pointer to be written + */ + void WritePointer(size_t address, size_t pointer); + + std::string ReadNullTermString(size_t address, size_t maxLength = -1) const; + + uint8_t ReadUInt8(size_t address); + + int8_t ReadInt8(size_t address); + + uint16_t ReadUInt16(size_t address); + + int16_t ReadInt16(size_t address); + + uint32_t ReadUInt32(size_t address); + + int32_t ReadInt32(size_t address); + + uint64_t ReadUInt64(size_t address); + + int64_t ReadInt64(size_t address); + + BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length); + + // Returns a range of pointers within the mapped memory region corresponding to + // {addr, length}. + // WARNING: The pointers returned by this method is only valid for the lifetime + // of this file accessor. + // TODO: This should use std::span<const uint8_t> once the minimum supported + // C++ version supports it. + std::pair<const uint8_t*, const uint8_t*> ReadSpan(size_t addr, size_t length); + + void Read(void* dest, size_t addr, size_t length) const; + + template <typename T> + T Read(size_t address); +}; diff --git a/view/sharedcache/core/MetadataSerializable.cpp b/view/sharedcache/core/MetadataSerializable.cpp deleted file mode 100644 index eaf82c8b..00000000 --- a/view/sharedcache/core/MetadataSerializable.cpp +++ /dev/null @@ -1,552 +0,0 @@ -#include "MetadataSerializable.hpp" - -namespace SharedCacheCore { - -void Serialize(SerializationContext& context, std::string_view str) { - context.writer.String(str.data(), str.length()); -} - -void Serialize(SerializationContext& context, const char* value) { - Serialize(context, std::string_view(value)); -} - -void Serialize(SerializationContext& context, bool b) -{ - context.writer.Bool(b); -} - -void Serialize(SerializationContext& context, int8_t value) { - context.writer.Int(value); -} - -void Serialize(SerializationContext& context, uint8_t value) { - context.writer.Uint(value); -} - -void Serialize(SerializationContext& context, int16_t value) { - context.writer.Int(value); -} - -void Serialize(SerializationContext& context, uint16_t value) { - context.writer.Uint(value); -} - -void Serialize(SerializationContext& context, int32_t value) { - context.writer.Int(value); -} - -void Serialize(SerializationContext& context, uint32_t value) { - context.writer.Uint(value); -} - -void Serialize(SerializationContext& context, int64_t value) { - context.writer.Int64(value); -} - -void Serialize(SerializationContext& context, uint64_t value) { - context.writer.Uint64(value); -} - -void Deserialize(DeserializationContext& context, std::string_view name, bool& b) { - b = context.doc[name.data()].GetBool(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, uint8_t& b) -{ - b = static_cast<uint8_t>(context.doc[name.data()].GetUint64()); -} - -void Deserialize(DeserializationContext& context, std::string_view name, uint16_t& b) -{ - b = static_cast<uint16_t>(context.doc[name.data()].GetUint64()); -} - -void Deserialize(DeserializationContext& context, std::string_view name, uint32_t& b) -{ - b = static_cast<uint32_t>(context.doc[name.data()].GetUint64()); -} - -void Deserialize(DeserializationContext& context, std::string_view name, uint64_t& b) -{ - b = context.doc[name.data()].GetUint64(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, int8_t& b) -{ - b = context.doc[name.data()].GetInt64(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, int16_t& b) -{ - b = context.doc[name.data()].GetInt64(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, int32_t& b) -{ - b = context.doc[name.data()].GetInt(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, int64_t& b) -{ - b = context.doc[name.data()].GetInt64(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::string& b) -{ - b = context.doc[name.data()].GetString(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::map<uint64_t, std::string>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetString(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, std::string>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetString(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, uint64_t>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - b[i.GetArray()[0].GetUint64()] = i.GetArray()[1].GetUint64(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::string key = i.GetArray()[0].GetString(); - std::unordered_map<uint64_t, uint64_t> memArray; - for (auto& member : i.GetArray()[1].GetArray()) - { - memArray[member.GetArray()[0].GetUint64()] = member.GetArray()[1].GetUint64(); - } - b[key] = memArray; - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::string>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - b[i.GetArray()[0].GetString()] = i.GetArray()[1].GetString(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::string>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - b.emplace_back(i.GetString()); -} - -// Note: This flattens the pair into [first, second.first, second.second] with no nested arrays. -void Serialize(SerializationContext& context, const std::pair<uint64_t, std::pair<uint64_t, uint64_t>>& value) -{ - context.writer.StartArray(); - Serialize(context, value.first); - Serialize(context, value.second.first); - Serialize(context, value.second.second); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::pair<uint64_t, std::pair<uint64_t, uint64_t>> j; - j.first = i.GetArray()[0].GetUint64(); - j.second.first = i.GetArray()[1].GetUint64(); - j.second.second = i.GetArray()[2].GetUint64(); - b.push_back(j); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, bool>>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::pair<uint64_t, bool> j; - j.first = i.GetArray()[0].GetUint64(); - j.second = i.GetArray()[1].GetBool(); - b.push_back(j); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<uint64_t>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - b.push_back(i.GetUint64()); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, uint64_t>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - b[i.GetArray()[0].GetString()] = i.GetArray()[1].GetUint64(); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>>>& b) -{ - for (auto& i : context.doc[name.data()].GetArray()) - { - std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>> j; - j.first = i.GetArray()[0].GetUint64(); - for (auto& k : i.GetArray()[1].GetArray()) - { - j.second.push_back({k.GetArray()[0].GetUint64(), k.GetArray()[1].GetString()}); - } - b.push_back(j); - } -} - -void Serialize(SerializationContext& context, const mach_header_64& value) { - context.writer.StartArray(); - Serialize(context, value.magic); - // cputype and cpusubtype are signed but were serialized as unsigned in - // v4.2 (metadata version 2). We continue serializing them as unsigned - // so we don't need to bump the metadata version. - Serialize(context, static_cast<uint32_t>(value.cputype)); - Serialize(context, static_cast<uint32_t>(value.cpusubtype)); - Serialize(context, value.filetype); - Serialize(context, value.ncmds); - Serialize(context, value.sizeofcmds); - Serialize(context, value.flags); - Serialize(context, value.reserved); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, mach_header_64& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.magic = bArr[0].GetUint(); - b.cputype = bArr[1].GetUint(); - b.cpusubtype = bArr[2].GetUint(); - b.filetype = bArr[3].GetUint(); - b.ncmds = bArr[4].GetUint(); - b.sizeofcmds = bArr[5].GetUint(); - b.flags = bArr[6].GetUint(); - b.reserved = bArr[7].GetUint(); -} - -void Serialize(SerializationContext& context, const symtab_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.symoff); - Serialize(context, value.nsyms); - Serialize(context, value.stroff); - Serialize(context, value.strsize); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, symtab_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.symoff = bArr[2].GetUint(); - b.nsyms = bArr[3].GetUint(); - b.stroff = bArr[4].GetUint(); - b.strsize = bArr[5].GetUint(); -} - -void Serialize(SerializationContext& context, const dysymtab_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.ilocalsym); - Serialize(context, value.nlocalsym); - Serialize(context, value.iextdefsym); - Serialize(context, value.nextdefsym); - Serialize(context, value.iundefsym); - Serialize(context, value.nundefsym); - Serialize(context, value.tocoff); - Serialize(context, value.ntoc); - Serialize(context, value.modtaboff); - Serialize(context, value.nmodtab); - Serialize(context, value.extrefsymoff); - Serialize(context, value.nextrefsyms); - Serialize(context, value.indirectsymoff); - Serialize(context, value.nindirectsyms); - Serialize(context, value.extreloff); - Serialize(context, value.nextrel); - Serialize(context, value.locreloff); - Serialize(context, value.nlocrel); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, dysymtab_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.ilocalsym = bArr[2].GetUint(); - b.nlocalsym = bArr[3].GetUint(); - b.iextdefsym = bArr[4].GetUint(); - b.nextdefsym = bArr[5].GetUint(); - b.iundefsym = bArr[6].GetUint(); - b.nundefsym = bArr[7].GetUint(); - b.tocoff = bArr[8].GetUint(); - b.ntoc = bArr[9].GetUint(); - b.modtaboff = bArr[10].GetUint(); - b.nmodtab = bArr[11].GetUint(); - b.extrefsymoff = bArr[12].GetUint(); - b.nextrefsyms = bArr[13].GetUint(); - b.indirectsymoff = bArr[14].GetUint(); - b.nindirectsyms = bArr[15].GetUint(); - b.extreloff = bArr[16].GetUint(); - b.nextrel = bArr[17].GetUint(); - b.locreloff = bArr[18].GetUint(); - b.nlocrel = bArr[19].GetUint(); -} - -void Serialize(SerializationContext& context, const dyld_info_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.rebase_off); - Serialize(context, value.rebase_size); - Serialize(context, value.bind_off); - Serialize(context, value.bind_size); - Serialize(context, value.weak_bind_off); - Serialize(context, value.weak_bind_size); - Serialize(context, value.lazy_bind_off); - Serialize(context, value.lazy_bind_size); - Serialize(context, value.export_off); - Serialize(context, value.export_size); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, dyld_info_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.rebase_off = bArr[2].GetUint(); - b.rebase_size = bArr[3].GetUint(); - b.bind_off = bArr[4].GetUint(); - b.bind_size = bArr[5].GetUint(); - b.weak_bind_off = bArr[6].GetUint(); - b.weak_bind_size = bArr[7].GetUint(); - b.lazy_bind_off = bArr[8].GetUint(); - b.lazy_bind_size = bArr[9].GetUint(); - b.export_off = bArr[10].GetUint(); - b.export_size = bArr[11].GetUint(); -} - -void Serialize(SerializationContext& context, const routines_command_64& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.init_address); - Serialize(context, value.init_module); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, routines_command_64& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - // Because we might open databases that had not previously serialized this, we must allow - // an empty array, otherwise we will crash! - if (bArr.Size() < 4) - return; - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.init_address = bArr[2].GetUint64(); - b.init_module = bArr[3].GetUint64(); -} - -void Serialize(SerializationContext& context, const function_starts_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.funcoff); - Serialize(context, value.funcsize); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, function_starts_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.funcoff = bArr[2].GetUint(); - b.funcsize = bArr[3].GetUint(); -} - -void Serialize(SerializationContext& context, const section_64& value) -{ - context.writer.StartArray(); - - std::string_view sectname(value.sectname, 16); - std::string_view segname(value.segname, 16); - - Serialize(context, sectname.substr(0, sectname.find('\0'))); - Serialize(context, segname.substr(0, segname.find('\0'))); - Serialize(context, value.addr); - Serialize(context, value.size); - Serialize(context, value.offset); - Serialize(context, value.align); - Serialize(context, value.reloff); - Serialize(context, value.nreloc); - Serialize(context, value.flags); - Serialize(context, value.reserved1); - Serialize(context, value.reserved2); - Serialize(context, value.reserved3); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<section_64>& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - for (auto& s : bArr) - { - section_64 sec; - auto s2 = s.GetArray(); - std::string sectNameStr = s2[0].GetString(); - memset(sec.sectname, 0, 16); - memcpy(sec.sectname, sectNameStr.c_str(), sectNameStr.size()); - std::string segNameStr = s2[1].GetString(); - memset(sec.segname, 0, 16); - memcpy(sec.segname, segNameStr.c_str(), segNameStr.size()); - sec.addr = s2[2].GetUint64(); - sec.size = s2[3].GetUint64(); - sec.offset = s2[4].GetUint(); - sec.align = s2[5].GetUint(); - sec.reloff = s2[6].GetUint(); - sec.nreloc = s2[7].GetUint(); - sec.flags = s2[8].GetUint(); - sec.reserved1 = s2[9].GetUint(); - sec.reserved2 = s2[10].GetUint(); - sec.reserved3 = s2[11].GetUint(); - b.push_back(std::move(sec)); - } -} - -void Serialize(SerializationContext& context, const linkedit_data_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.dataoff); - Serialize(context, value.datasize); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, linkedit_data_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.dataoff = bArr[2].GetUint(); - b.datasize = bArr[3].GetUint(); -} - -void Serialize(SerializationContext& context, const segment_command_64& value) -{ - context.writer.StartArray(); - std::string_view segname(value.segname, 16); - Serialize(context, segname.substr(0, segname.find('\0'))); - Serialize(context, value.vmaddr); - Serialize(context, value.vmsize); - Serialize(context, value.fileoff); - Serialize(context, value.filesize); - Serialize(context, value.maxprot); - Serialize(context, value.initprot); - Serialize(context, value.nsects); - Serialize(context, value.flags); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, segment_command_64& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - std::string segNameStr = bArr[0].GetString(); - memset(b.segname, 0, 16); - memcpy(b.segname, segNameStr.c_str(), segNameStr.size()); - b.vmaddr = bArr[1].GetUint64(); - b.vmsize = bArr[2].GetUint64(); - b.fileoff = bArr[3].GetUint64(); - b.filesize = bArr[4].GetUint64(); - b.maxprot = bArr[5].GetUint(); - b.initprot = bArr[6].GetUint(); - b.nsects = bArr[7].GetUint(); - b.flags = bArr[8].GetUint(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<segment_command_64>& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - for (auto& s : bArr) - { - segment_command_64 sec; - auto s2 = s.GetArray(); - std::string segNameStr = s2[0].GetString(); - memset(sec.segname, 0, 16); - memcpy(sec.segname, segNameStr.c_str(), segNameStr.size()); - sec.vmaddr = s2[1].GetUint64(); - sec.vmsize = s2[2].GetUint64(); - sec.fileoff = s2[3].GetUint64(); - sec.filesize = s2[4].GetUint64(); - sec.maxprot = s2[5].GetUint(); - sec.initprot = s2[6].GetUint(); - sec.nsects = s2[7].GetUint(); - sec.flags = s2[8].GetUint(); - b.push_back(std::move(sec)); - } -} - -void Serialize(SerializationContext& context, const build_version_command& value) -{ - context.writer.StartArray(); - Serialize(context, value.cmd); - Serialize(context, value.cmdsize); - Serialize(context, value.platform); - Serialize(context, value.minos); - Serialize(context, value.sdk); - Serialize(context, value.ntools); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, build_version_command& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - b.cmd = bArr[0].GetUint(); - b.cmdsize = bArr[1].GetUint(); - b.platform = bArr[2].GetUint(); - b.minos = bArr[3].GetUint(); - b.sdk = bArr[4].GetUint(); - b.ntools = bArr[5].GetUint(); -} - -void Serialize(SerializationContext& context, const build_tool_version& value) -{ - context.writer.StartArray(); - Serialize(context, value.tool); - Serialize(context, value.version); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<build_tool_version>& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - for (auto& s : bArr) - { - build_tool_version sec; - auto s2 = s.GetArray(); - sec.tool = s2[0].GetUint(); - sec.version = s2[1].GetUint(); - b.push_back(sec); - } -} - -} // namespace SharedCacheCore diff --git a/view/sharedcache/core/MetadataSerializable.hpp b/view/sharedcache/core/MetadataSerializable.hpp deleted file mode 100644 index 24d8c3fd..00000000 --- a/view/sharedcache/core/MetadataSerializable.hpp +++ /dev/null @@ -1,254 +0,0 @@ -// -// Created by kat on 5/31/23. -// - -/* - * Welcome to, this file. - * - * This is a metadata serialization helper. - * - * Have you ever wished turning a complex datastructure into a Metadata object was as easy in C++ as it is in python? - * Do you like macros and templates? - * - * Great news. - * - * Implement these on your `public MetadataSerializable<T>` subclass: - * ``` - class MyClass : public MetadataSerializable<MyClass> { - void Store(SerializationContext& context) const { - MSS(m_someVariable); - MSS(m_someOtherVariable); - } - void Load(DeserializationContext& context) { - MSL(m_someVariable); - MSL(m_someOtherVariable); - } - } - ``` - * Then, you can turn your object into a Metadata object with `AsMetadata()`, and load it back with - `LoadFromMetadata()`. - * - * Serialized fields will be automatically repopulated. - * - * Other ser/deser formats (rapidjson objects, strings) also exist. You can use these to achieve nesting, but probably - avoid that. - * */ - -#ifndef SHAREDCACHE_CORE_METADATASERIALIZABLE_HPP -#define SHAREDCACHE_CORE_METADATASERIALIZABLE_HPP - -#include <cassert> -#include "binaryninjaapi.h" -#include "rapidjson/document.h" -#include "rapidjson/stringbuffer.h" -#include "rapidjson/prettywriter.h" -#include "../api/sharedcachecore.h" -#include "view/macho/machoview.h" - -using namespace BinaryNinja; - -namespace SharedCacheCore { - -#define MSS(name) context.store(#name, name) -#define MSS_CAST(name, type) context.store(#name, (type) name) -#define MSS_SUBCLASS(name) Serialize(context, #name, name) -#define MSL(name) name = context.load<decltype(name)>(#name) -#define MSL_CAST(name, storedType, type) name = (type)context.load<storedType>(#name) - -struct DeserializationContext; - -struct SerializationContext { - rapidjson::StringBuffer buffer; - rapidjson::Writer<rapidjson::StringBuffer> writer; - - SerializationContext() : buffer(), writer(buffer) { - } - - template <typename T> - void store(std::string_view x, const T& y) - { - Serialize(*this, x, y); - } -}; - -struct DeserializationContext { - rapidjson::Document doc; - - template <typename T> - T load(std::string_view x) - { - T value; - Deserialize(*this, x, value); - return value; - } -}; - -template <typename Derived, typename LoadResult = Derived> -class MetadataSerializable { -public: - template <typename... Args> - std::string AsString(Args&&... args) const { - SerializationContext context; - Store(context, std::forward<Args>(args)...); - - return context.buffer.GetString(); - } - - static LoadResult LoadFromString(const std::string& s) { - DeserializationContext context; - [[maybe_unused]] rapidjson::ParseResult result = context.doc.Parse(s.c_str()); - assert(result); - return Derived::Load(context); - } - - static LoadResult LoadFromValue(rapidjson::Value& s) { - DeserializationContext context; - context.doc.CopyFrom(s, context.doc.GetAllocator()); - return Derived::Load(context); - } - - template <typename... Args> - Ref<Metadata> AsMetadata(Args&&... args) const { - return new Metadata(AsString(std::forward<Args>(args)...)); - } - - template <typename... Args> - void Store(SerializationContext& context, Args&&... args) const { - context.writer.StartObject(); - AsDerived().Store(context, std::forward<Args>(args)...); - context.writer.EndObject(); - } - -private: - const Derived& AsDerived() const { return static_cast<const Derived&>(*this); } - Derived& AsDerived() { return static_cast<Derived&>(*this); } -}; - -// The functions below are not part of the FFI API, but are exported so they can be shared with sharedcacheui. - -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, std::string_view str); - -template <typename T> -inline void Serialize(SerializationContext& context, const MetadataSerializable<T>& value) -{ - value.Store(context); -} - -template <typename T> -inline void Serialize(SerializationContext& context, std::string_view name, const T& value) -{ - Serialize(context, name); - Serialize(context, value); -} - -template <typename First, typename Second> -void Serialize(SerializationContext& context, const std::pair<First, Second>& value) -{ - context.writer.StartArray(); - Serialize(context, value.first); - Serialize(context, value.second); - context.writer.EndArray(); -} - -template <typename K, typename V, typename L> -void Serialize(SerializationContext& context, const std::map<K, V, L>& value) -{ - context.writer.StartArray(); - for (auto& pair : value) - { - Serialize(context, pair); - } - context.writer.EndArray(); -} - -template <typename K, typename V> -void Serialize(SerializationContext& context, const std::unordered_map<K, V>& value) -{ - context.writer.StartArray(); - for (auto& pair : value) - { - Serialize(context, pair); - } - context.writer.EndArray(); -} - -template <typename T> -void Serialize(SerializationContext& context, const std::vector<T>& values) -{ - context.writer.StartArray(); - for (const auto& value : values) - { - Serialize(context, value); - } - context.writer.EndArray(); -} - -template <typename T> -void Serialize(SerializationContext& context, const std::optional<T>& value) -{ - if (value.has_value()) - Serialize(context, *value); - else - context.writer.Null(); -} - -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, const char*); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, bool b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, bool& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint8_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint8_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint16_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint16_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint32_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint32_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, uint64_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint64_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int8_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int8_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int16_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int16_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int32_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int32_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, int64_t b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int64_t& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, std::string_view b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext& context, const std::pair<uint64_t, std::pair<uint64_t, uint64_t>>& value); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::string& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::map<uint64_t, std::string>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, std::string>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, uint64_t>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::string>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::string>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, bool>>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<uint64_t>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, uint64_t>& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::vector<std::pair<uint64_t, std::string>>>>& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const mach_header_64& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, mach_header_64& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const symtab_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, symtab_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const dysymtab_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, dysymtab_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const dyld_info_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, dyld_info_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const routines_command_64& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, routines_command_64& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const function_starts_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, function_starts_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const section_64& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<section_64>& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const linkedit_data_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, linkedit_data_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const segment_command_64& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, segment_command_64& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<segment_command_64>& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const build_version_command& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, build_version_command& b); -SHAREDCACHE_FFI_API void Serialize(SerializationContext&, const build_tool_version& b); -SHAREDCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<build_tool_version>& b); - -} // namespace SharedCacheCore - -#endif // SHAREDCACHE_METADATASERIALIZABLE_HPP diff --git a/view/sharedcache/core/ObjC.cpp b/view/sharedcache/core/ObjC.cpp index c763b321..bb483542 100644 --- a/view/sharedcache/core/ObjC.cpp +++ b/view/sharedcache/core/ObjC.cpp @@ -1,95 +1,98 @@ #include "ObjC.h" +#include "SharedCacheController.h" + using namespace BinaryNinja; using namespace DSCObjC; -using namespace SharedCacheCore; -DSCObjCReader::DSCObjCReader(SharedCache* cache, size_t addressSize) : - m_reader(VMReader(cache->GetVMMap())), m_addressSize(addressSize) -{ -} +SharedCacheObjCReader::SharedCacheObjCReader(VirtualMemoryReader reader) : m_reader(reader) {} -void DSCObjCReader::Read(void* dest, size_t len) +void SharedCacheObjCReader::Read(void* dest, size_t len) { m_reader.Read(dest, len); } -std::string DSCObjCReader::ReadCString() +std::string SharedCacheObjCReader::ReadCString(size_t maxLength) { - return m_reader.ReadCString(m_reader.GetOffset()); + return m_reader.ReadCString(m_reader.GetOffset(), maxLength); } -uint8_t DSCObjCReader::Read8() +uint8_t SharedCacheObjCReader::Read8() { - return m_reader.Read8(); + return m_reader.ReadUInt8(); } -uint16_t DSCObjCReader::Read16() +uint16_t SharedCacheObjCReader::Read16() { - return m_reader.Read16(); + return m_reader.ReadUInt16(); } -uint32_t DSCObjCReader::Read32() +uint32_t SharedCacheObjCReader::Read32() { - return m_reader.Read32(); + return m_reader.ReadUInt32(); } -uint64_t DSCObjCReader::Read64() +uint64_t SharedCacheObjCReader::Read64() { - return m_reader.Read64(); + return m_reader.ReadUInt64(); } -int8_t DSCObjCReader::ReadS8() +int8_t SharedCacheObjCReader::ReadS8() { - return m_reader.ReadS8(); + return m_reader.ReadInt8(); } -int16_t DSCObjCReader::ReadS16() +int16_t SharedCacheObjCReader::ReadS16() { - return m_reader.ReadS16(); + return m_reader.ReadInt16(); } -int32_t DSCObjCReader::ReadS32() +int32_t SharedCacheObjCReader::ReadS32() { - return m_reader.ReadS32(); + return m_reader.ReadInt32(); } -int64_t DSCObjCReader::ReadS64() +int64_t SharedCacheObjCReader::ReadS64() { - return m_reader.ReadS64(); + return m_reader.ReadInt64(); } -uint64_t DSCObjCReader::ReadPointer() +uint64_t SharedCacheObjCReader::ReadPointer() { return m_reader.ReadPointer(); } -uint64_t DSCObjCReader::GetOffset() const +uint64_t SharedCacheObjCReader::GetOffset() const { return m_reader.GetOffset(); } -void DSCObjCReader::Seek(uint64_t offset) +void SharedCacheObjCReader::Seek(uint64_t offset) { m_reader.Seek(offset); } -void DSCObjCReader::SeekRelative(int64_t offset) +void SharedCacheObjCReader::SeekRelative(int64_t offset) { m_reader.SeekRelative(offset); } -VMReader& DSCObjCReader::GetVMReader() +VirtualMemoryReader& SharedCacheObjCReader::GetVMReader() { return m_reader; } -std::shared_ptr<ObjCReader> DSCObjCProcessor::GetReader() +std::shared_ptr<ObjCReader> SharedCacheObjCProcessor::GetReader() { - return std::make_shared<DSCObjCReader>(m_cache, m_data->GetAddressSize()); + const auto controller = DSC::SharedCacheController::FromView(*m_data); + // TODO: This should never happen. + if (!controller) + throw std::runtime_error("SharedCacheController not found for SharedCacheObjCProcessor::GetReader!"); + auto reader = VirtualMemoryReader(controller->GetCache().GetVirtualMemory(), m_data->GetAddressSize()); + return std::make_shared<SharedCacheObjCReader>(reader); } -void DSCObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth) +void SharedCacheObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth) { if (m_customRelativeMethodSelectorBase.has_value()) { @@ -103,14 +106,48 @@ void DSCObjCProcessor::GetRelativeMethod(ObjCReader* reader, method_t& meth) } } -uint64_t DSCObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader) +std::optional<ObjCOptimizationHeader> GetObjCOptimizationHeader(SharedCache& cache, VirtualMemoryReader& reader) { - auto objCRelativeMethodsBaseAddr = m_cache->GetObjCRelativeMethodBaseAddress(static_cast<DSCObjCReader*>(reader)->GetVMReader()); - m_customRelativeMethodSelectorBase = objCRelativeMethodsBaseAddr; - return objCRelativeMethodsBaseAddr; + // Find the first primary entry and use that header to read the obj opt header. + // Don't ask me why this is done like this... + std::optional<dyld_cache_header> primaryCacheHeader = std::nullopt; + for (const auto& [_, entry] : cache.GetEntries()) + { + if (entry.GetType() == CacheEntryType::Primary) + { + primaryCacheHeader = entry.GetHeader(); + break; + } + } + + // Check if we even have the obj opt stuff. + if (!primaryCacheHeader || !primaryCacheHeader->objcOptsOffset || !primaryCacheHeader->objcOptsSize) + return std::nullopt; + + ObjCOptimizationHeader header = {}; + // Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior. + // TODO: The base address is the lowest region, however is that going to be where the primary cache header resides? + reader.Read(&header, cache.GetBaseAddress() + primaryCacheHeader->objcOptsOffset, sizeof(ObjCOptimizationHeader)); + + return header; } -DSCObjCProcessor::DSCObjCProcessor(BinaryView* data, SharedCache* cache, bool isBackedByDatabase) : - ObjCProcessor(data, "SharedCache.ObjC", isBackedByDatabase, true), m_cache(cache) +uint64_t SharedCacheObjCProcessor::GetObjCRelativeMethodBaseAddress(ObjCReader* reader) { + // Try and retrieve the base address of the selector stuff. + if (const auto controller = DSC::SharedCacheController::FromView(*m_data)) + { + auto baseAddress = controller->GetCache().GetBaseAddress(); + auto dangerReader = dynamic_cast<SharedCacheObjCReader*>(reader)->GetVMReader(); + if (const auto header = GetObjCOptimizationHeader(controller->GetCache(), dangerReader); header.has_value()) + { + m_customRelativeMethodSelectorBase = baseAddress + header->relativeMethodSelectorBaseAddressOffset; + } + } + + return m_customRelativeMethodSelectorBase.value_or(0); } + +SharedCacheObjCProcessor::SharedCacheObjCProcessor(BinaryView* data, bool isBackedByDatabase) : + ObjCProcessor(data, "SharedCache.ObjC", isBackedByDatabase, true) +{} diff --git a/view/sharedcache/core/ObjC.h b/view/sharedcache/core/ObjC.h index 13790364..6576564b 100644 --- a/view/sharedcache/core/ObjC.h +++ b/view/sharedcache/core/ObjC.h @@ -1,52 +1,71 @@ -// -// Created by kat on 5/23/23. -// - -#ifndef SHAREDCACHE_OBJC_H -#define SHAREDCACHE_OBJC_H +#pragma once #include <binaryninjaapi.h> #include <objectivec/objc.h> #include "SharedCache.h" +struct ObjCOptimizationHeader +{ + uint32_t version; + uint32_t flags; + uint64_t headerInfoROCacheOffset; + uint64_t headerInfoRWCacheOffset; + uint64_t selectorHashTableCacheOffset; + uint64_t classHashTableCacheOffset; + uint64_t protocolHashTableCacheOffset; + uint64_t relativeMethodSelectorBaseAddressOffset; +}; + namespace DSCObjC { - class DSCObjCReader : public ObjCReader { - private: - VMReader m_reader; - size_t m_addressSize; + class SharedCacheObjCReader : public BinaryNinja::ObjCReader + { + VirtualMemoryReader m_reader; public: void Read(void* dest, size_t len) override; - std::string ReadCString() override; + + std::string ReadCString(size_t maxLength = -1) override; + uint8_t Read8() override; + uint16_t Read16() override; + uint32_t Read32() override; + uint64_t Read64() override; + int8_t ReadS8() override; + int16_t ReadS16() override; + int32_t ReadS32() override; + int64_t ReadS64() override; + uint64_t ReadPointer() override; + uint64_t GetOffset() const override; + void Seek(uint64_t offset) override; + void SeekRelative(int64_t offset) override; - VMReader& GetVMReader(); + VirtualMemoryReader& GetVMReader(); - DSCObjCReader(SharedCacheCore::SharedCache* cache, size_t addressSize); + SharedCacheObjCReader(VirtualMemoryReader reader); }; - class DSCObjCProcessor : public ObjCProcessor { + class SharedCacheObjCProcessor : public BinaryNinja::ObjCProcessor + { std::optional<uint64_t> m_customRelativeMethodSelectorBase = std::nullopt; - SharedCacheCore::SharedCache* m_cache; - std::shared_ptr<ObjCReader> GetReader() override; - void GetRelativeMethod(ObjCReader* reader, method_t& meth) override; - + std::shared_ptr<BinaryNinja::ObjCReader> GetReader() override; + + void GetRelativeMethod(BinaryNinja::ObjCReader* reader, BinaryNinja::method_t& meth) override; + public: - DSCObjCProcessor(BinaryView* data, SharedCacheCore::SharedCache* cache, bool isBackedByDatabase); - - uint64_t GetObjCRelativeMethodBaseAddress(ObjCReader* reader) override; + SharedCacheObjCProcessor(BinaryNinja::BinaryView* data, bool isBackedByDatabase); + + uint64_t GetObjCRelativeMethodBaseAddress(BinaryNinja::ObjCReader* reader) override; }; -} -#endif //SHAREDCACHE_OBJC_H +} // namespace DSCObjC diff --git a/view/sharedcache/core/SharedCache.cpp b/view/sharedcache/core/SharedCache.cpp index 27367a45..24bc4619 100644 --- a/view/sharedcache/core/SharedCache.cpp +++ b/view/sharedcache/core/SharedCache.cpp @@ -1,3975 +1,617 @@ -// -// Created by kat on 5/19/23. -// - -/* --- - * This is the primary image loader logic for Shared Caches - * - * It is standalone code that operates on a DSCView. - * - * This has to recreate _all_ of the Mach-O View logic, but slightly differently, as everything is spicy and weird and - * different enough that it's not worth trying to make a shared base class. - * - * The SharedCache api object is a 'Controller' that serializes its own state in view metadata. - * - * It is multithreading capable (multiple SharedCache objects can exist and do things on different threads, it will manage) - * - * View state is saved to BinaryView any time it changes, however due to json deser speed we must also cache it on heap. - * This cache is 'load bearing' and controllers on other threads may serialize it back to view after making changes, so it - * must be kept up to date. - * - * - * - * */ - #include "SharedCache.h" -#include "binaryninjaapi.h" -#include "DSCView.h" -#include "ObjC.h" -#include <algorithm> -#include <fcntl.h> +#include <regex> #include <filesystem> -#include <limits> -#include <memory> -#include <mutex> -#include <optional> -#include <unordered_map> -#include <utility> -#include <vector> +#include "MachO.h" +#include "SlideInfo.h" using namespace BinaryNinja; -using namespace SharedCacheCore; - -namespace SharedCacheCore { - -namespace { - -#ifdef _MSC_VER - -int count_trailing_zeros(uint64_t value) { - unsigned long index; // 32-bit long on Windows - if (_BitScanForward64(&index, value)) { - return index; - } else { - return 64; // If the value is 0, return 64. - } -} -#else -int count_trailing_zeros(uint64_t value) { - return value == 0 ? 64 : __builtin_ctzll(value); -} -#endif - -struct MemoryRegionStatus -{ - bool loaded = false; - bool headerInitialized = false; -}; - -} // unnamed namespace - -// State that does not change after `PerformInitialLoad`. -struct SharedCache::CacheInfo : - public MetadataSerializable<SharedCache::CacheInfo, std::optional<SharedCache::CacheInfo>> -{ - std::vector<BackingCache> backingCaches; - std::unordered_map<uint64_t, SharedCacheMachOHeader> headers; - std::vector<CacheImage> images; - std::unordered_map<std::string, uint64_t> imageStarts; - AddressRangeMap<MemoryRegion> memoryRegions; - - std::optional<std::pair<uint64_t, uint64_t>> objcOptimizationDataRange; - - std::string baseFilePath; - SharedCacheFormat cacheFormat = RegularCacheFormat; - - MemoryRegion* AddMemoryRegion(MemoryRegion region); - void AddPotentiallyOverlappingMemoryRegion(MemoryRegion region); -#ifndef NDEBUG - void Verify() const; -#endif - - uint64_t BaseAddress() const; - - void Store(SerializationContext&) const; - static std::optional<SharedCache::CacheInfo> Load(DeserializationContext&); -}; - -struct State : public MetadataSerializable<State> -{ - // Map from start address of a region to the region's status. - std::unordered_map<uint64_t, MemoryRegionStatus> memoryRegionStatus; - std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>> - exportInfos; - std::unordered_map<uint64_t, std::shared_ptr<std::vector<Ref<Symbol>>>> symbolInfos; - - // Store only. Loading is done via `ModifiedState`. - void Store(SerializationContext&, std::optional<DSCViewState> viewState) const; -}; - -struct SharedCache::ModifiedState : public State, public MetadataSerializable<SharedCache::ModifiedState> -{ - std::optional<DSCViewState> viewState; - - using Base = MetadataSerializable<SharedCache::ModifiedState>; - using Base::AsMetadata; - using Base::LoadFromString; - - void Store(SerializationContext&) const; - static SharedCache::ModifiedState Load(DeserializationContext&); - static SharedCache::ModifiedState LoadAll(BinaryNinja::BinaryView*, const CacheInfo&); - - void Merge(SharedCache::ModifiedState&& other); -}; - -struct SharedCache::ViewSpecificState -{ - std::mutex typeLibraryMutex; - std::unordered_map<std::string, Ref<TypeLibrary>> typeLibraries; - - std::mutex viewOperationsThatInfluenceMetadataMutex; - std::atomic<BNDSCViewLoadProgress> progress; +// The next id to use when calling Cache::AddEntry +static CacheEntryId nextId = 1; - std::mutex cacheInfoMutex; - std::shared_ptr<const SharedCache::CacheInfo> cacheInfo; - - std::mutex stateMutex; - struct State state; - - std::atomic<DSCViewState> viewState; - uint64_t savedModifications = 0; -}; - -namespace { - -std::shared_ptr<SharedCache::ViewSpecificState> ViewSpecificStateForId(uint64_t viewIdentifier, bool insertIfNeeded = true) +Ref<Symbol> CacheSymbol::ToBNSymbol() const { - static std::mutex viewSpecificStateMutex; - static std::unordered_map<uint64_t, std::weak_ptr<SharedCache::ViewSpecificState>> viewSpecificState; - - std::lock_guard lock(viewSpecificStateMutex); - - if (auto it = viewSpecificState.find(viewIdentifier); it != viewSpecificState.end()) - { - if (auto statePtr = it->second.lock()) - return statePtr; - } - - if (!insertIfNeeded) - return nullptr; - - auto statePtr = std::make_shared<SharedCache::ViewSpecificState>(); - viewSpecificState[viewIdentifier] = statePtr; - - // Prune entries for any views that are no longer in use. - for (auto it = viewSpecificState.begin(); it != viewSpecificState.end(); ) - { - if (it->second.expired()) - it = viewSpecificState.erase(it); - else - ++it; - } - - return statePtr; -} - -std::shared_ptr<SharedCache::ViewSpecificState> ViewSpecificStateForView(Ref<BinaryNinja::BinaryView> view) -{ - return ViewSpecificStateForId(view->GetFile()->GetSessionId()); -} - -std::string base_name(std::string const& path) -{ - return path.substr(path.find_last_of("/\\") + 1); + QualifiedName qname; + std::string shortName = name; + if (DemangleLLVM(name, qname, true)) + shortName = qname.GetString(); + return new Symbol(type, shortName, shortName, name, address, nullptr); } -BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt) +std::vector<std::string> CacheImage::GetDependencies() const { - - uint32_t flags = 0; - if (initProt & MACHO_VM_PROT_READ) - flags |= SegmentReadable; - if (initProt & MACHO_VM_PROT_WRITE) - flags |= SegmentWritable; - if (initProt & MACHO_VM_PROT_EXECUTE) - flags |= SegmentExecutable; - if (((initProt & MACHO_VM_PROT_WRITE) == 0) && - ((maxProt & MACHO_VM_PROT_WRITE) == 0)) - flags |= SegmentDenyWrite; - if (((initProt & MACHO_VM_PROT_EXECUTE) == 0) && - ((maxProt & MACHO_VM_PROT_EXECUTE) == 0)) - flags |= SegmentDenyExecute; - return (BNSegmentFlag)flags; -} - -BNSectionSemantics SectionSemanticsForRegion(const MemoryRegion& region) -{ - if ((region.flags & SegmentExecutable) && (region.flags & SegmentDenyWrite)) - return ReadOnlyCodeSectionSemantics; - - if (region.flags & SegmentExecutable) - return DefaultSectionSemantics; - - if (region.flags & SegmentDenyWrite) - return ReadOnlyDataSectionSemantics; - - return ReadWriteDataSectionSemantics; -} - - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-function" -static int64_t readSLEB128(const uint8_t*& current, const uint8_t* end) -{ - uint8_t cur; - int64_t value = 0; - size_t shift = 0; - while (current != end) - { - cur = *current++; - value |= (cur & 0x7f) << shift; - shift += 7; - if ((cur & 0x80) == 0) - break; - } - value = (value << (64 - shift)) >> (64 - shift); - return value; -} -#pragma clang diagnostic pop - - -static uint64_t readLEB128(const uint8_t*& current, const uint8_t* end) -{ - uint64_t result = 0; - int bit = 0; - do - { - if (current >= end) - return -1; - - uint64_t slice = *current & 0x7f; - - if (bit > 63) - return -1; - else - { - result |= (slice << bit); - bit += 7; - } - } while (*current++ & 0x80); - return result; -} - - -uint64_t readValidULEB128(const uint8_t*& current, const uint8_t* end) -{ - uint64_t value = readLEB128(current, end); - if ((int64_t)value == -1) - throw ReadException(); - return value; -} - -} // unnamed namespace - -uint64_t SharedCache::FastGetBackingCacheCount(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) -{ - std::shared_ptr<MMappedFileAccessor> baseFile; - try { - baseFile = MapFileWithoutApplyingSlide(dscView->GetFile()->GetOriginalFilename()); - } - catch (...){ - LogError("Shared Cache preload: Failed to open file %s", dscView->GetFile()->GetOriginalFilename().c_str()); - return 0; - } - - dyld_cache_header header {}; - size_t header_size = baseFile->ReadUInt32(16); - baseFile->Read(&header, 0, std::min(header_size, sizeof(dyld_cache_header))); - - SharedCacheFormat cacheFormat; - - if (header.imagesCountOld != 0) - cacheFormat = RegularCacheFormat; - - size_t subCacheOff = offsetof(struct dyld_cache_header, subCacheArrayOffset); - size_t headerEnd = header.mappingOffset; - if (headerEnd > subCacheOff) - { - if (header.cacheType != 2) - { - if (std::filesystem::exists(ResolveFilePath(dscView, baseFile->Path() + ".01"))) - cacheFormat = LargeCacheFormat; - else - cacheFormat = SplitCacheFormat; - } - else - cacheFormat = iOS16CacheFormat; - } - - switch (cacheFormat) - { - case RegularCacheFormat: - { - return 1; - } - case LargeCacheFormat: - { - auto subCacheCount = header.subCacheArrayCount; - return subCacheCount + 1; - } - case SplitCacheFormat: - { - auto subCacheCount = header.subCacheArrayCount; - return subCacheCount + 2; - } - case iOS16CacheFormat: - { - auto subCacheCount = header.subCacheArrayCount; - return subCacheCount + 2; - } - } -} - -MemoryRegion* SharedCache::CacheInfo::AddMemoryRegion(MemoryRegion region) -{ - if (!region.size) - return nullptr; - - auto [it, inserted] = memoryRegions.insert(std::make_pair(region.AsAddressRange(), std::move(region))); - assert(inserted); -#ifndef NDEBUG - Verify(); -#endif - return &it->second; -} - -void SharedCache::CacheInfo::AddPotentiallyOverlappingMemoryRegion(MemoryRegion region) -{ - if (!region.size) - return; - - // First region at or past the start of the region. - auto begin = memoryRegions.lower_bound(region.AsAddressRange().start); - if (begin == memoryRegions.end()) - { - AddMemoryRegion(std::move(region)); - return; - } - - // First region past the end of the region. - auto end = memoryRegions.lower_bound(region.AsAddressRange().end); - - for (auto it = begin; it != end; ++it) - { - uint64_t newRegionSize = it->second.start - region.start; - if (newRegionSize) - { - MemoryRegion newRegion(region); - newRegion.size = newRegionSize; - AddMemoryRegion(std::move(newRegion)); - } - - region.start = it->second.start + it->second.size; - region.size -= (newRegionSize + it->second.size); - } - - AddMemoryRegion(std::move(region)); -} - -#ifndef NDEBUG -void SharedCache::CacheInfo::Verify() const -{ - if (memoryRegions.size() < 2) - { - return; - } - - auto it = memoryRegions.begin(); - auto lastIt = it++; - for (auto lastIt = it++; it != memoryRegions.end(); lastIt = it++) - { - const auto& [lastAddress, lastRegion] = *lastIt; - const auto& [address, region] = *it; - assert(lastRegion.start == lastAddress.start && lastRegion.start + lastRegion.size == lastAddress.end); - assert(region.start == address.start && region.start + region.size == address.end); - assert(lastAddress.start < address.start); - assert(lastAddress.end <= address.start); - } -} -#endif - -static MemoryRegionStatus* StatusForMemoryRegion( - std::unordered_map<uint64_t, MemoryRegionStatus>& memoryRegionStatus, const MemoryRegion& region) -{ - auto it = memoryRegionStatus.find(region.start); - if (it == memoryRegionStatus.end()) - return nullptr; - - return &it->second; -} - -bool SharedCache::MemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region) const -{ - if (auto status = StatusForMemoryRegion(m_modifiedState->memoryRegionStatus, region)) - return status->loaded; - - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - return status->loaded; - - return false; -} - -void SharedCache::SetMemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region) -{ - auto [it, inserted] = m_modifiedState->memoryRegionStatus.insert({region.start, {}}); - if (inserted) - { - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - it->second = *status; - } - it->second.loaded = true; -} - -bool SharedCache::MemoryRegionIsHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region) const -{ - if (auto status = StatusForMemoryRegion(m_modifiedState->memoryRegionStatus, region)) - return status->headerInitialized; - - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - return status->headerInitialized; - - return false; + if (header) + return header->dylibs; + return {}; } -void SharedCache::SetMemoryRegionHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region) +CacheEntry::CacheEntry(std::string filePath, std::string fileName, CacheEntryType type, dyld_cache_header header, + std::vector<dyld_cache_mapping_info> mappings, std::unordered_map<std::string, dyld_cache_image_info> images) { - auto [it, inserted] = m_modifiedState->memoryRegionStatus.insert({region.start, {}}); - if (inserted) - { - std::lock_guard lock(m_viewSpecificState->stateMutex); - if (auto status = StatusForMemoryRegion(m_viewSpecificState->state.memoryRegionStatus, region)) - it->second = *status; - } - it->second.headerInitialized = true; + m_filePath = std::move(filePath); + m_fileName = std::move(fileName); + m_type = type; + m_header = header; + m_mappings = std::move(mappings); + m_images = std::move(images); } -void SharedCache::PerformInitialLoad(std::lock_guard<std::mutex>& lock) +std::optional<CacheEntry> CacheEntry::FromFile(const std::string& filePath, const std::string& fileName, CacheEntryType type) { - std::unique_lock viewOperationsLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - auto path = m_dscView->GetFile()->GetOriginalFilename(); - auto baseFile = MapFileWithoutApplyingSlide(path); - - m_logger->LogInfo("Loading caches..."); - m_viewSpecificState->progress = LoadProgressLoadingCaches; - - CacheInfo initialState; - initialState.baseFilePath = path; - initialState.cacheFormat = RegularCacheFormat; + auto file = FileAccessorCache::Global().Open(filePath).lock(); - // TODO: If this fails we should prompt the user to select there file, this probably means that - // TODO: They are opening a database with no original file path. - DataBuffer sig = baseFile->ReadBuffer(0, 4); + // TODO: Pull this out into another function so we can do IsValidDSCFile or something. + // We first want to make sure that the base file is dyld. + // All entries must start with "dyld". + DataBuffer sig = file->ReadBuffer(0, 4); if (sig.GetLength() != 4) - throw std::runtime_error("Not a valid dyld_shared_cache file"); + return std::nullopt; const char* magic = (char*)sig.GetData(); if (strncmp(magic, "dyld", 4) != 0) - throw std::runtime_error("Not a valid dyld_shared_cache file"); - - dyld_cache_header primaryCacheHeader {}; - size_t header_size = baseFile->ReadUInt32(16); - baseFile->Read(&primaryCacheHeader, 0, std::min(header_size, sizeof(dyld_cache_header))); - - if (primaryCacheHeader.imagesCountOld != 0) - initialState.cacheFormat = RegularCacheFormat; - - size_t subCacheOff = offsetof(struct dyld_cache_header, subCacheArrayOffset); - size_t headerEnd = primaryCacheHeader.mappingOffset; - if (headerEnd > subCacheOff) - { - if (primaryCacheHeader.cacheType != 2) - { - if (std::filesystem::exists(ResolveFilePath(m_dscView, baseFile->Path() + ".01"))) - initialState.cacheFormat = LargeCacheFormat; - else - initialState.cacheFormat = SplitCacheFormat; - } - else - initialState.cacheFormat = iOS16CacheFormat; - } - - if (primaryCacheHeader.objcOptsOffset && primaryCacheHeader.objcOptsSize) - { - uint64_t objcOptsOffset = primaryCacheHeader.objcOptsOffset; - uint64_t objcOptsSize = primaryCacheHeader.objcOptsSize; - initialState.objcOptimizationDataRange = {objcOptsOffset, objcOptsSize}; - } - - std::vector<MemoryRegion> nonImageMemoryRegions; - switch (initialState.cacheFormat) - { - case RegularCacheFormat: - { - dyld_cache_mapping_info mapping {}; - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCountOld; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffsetOld + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - m_logger->LogInfo("Found %d images in the shared cache", primaryCacheHeader.imagesCountOld); - - if (primaryCacheHeader.branchPoolsCount) - { - std::vector<uint64_t> addresses; - addresses.reserve(primaryCacheHeader.branchPoolsCount); - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - addresses.push_back(baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize()))); - } - baseFile.reset(); // No longer needed, we're about to remap this file into VM space so we can load these. - uint64_t i = 0; - for (auto address : addresses) - { - i++; - auto vm = GetVMMap(); - auto machoHeader = SharedCache::LoadHeaderForAddress(vm, address, "dyld_shared_cache_branch_islands_" + std::to_string(i)); - if (machoHeader) - { - for (const auto& segment : machoHeader->segments) - { - MemoryRegion stubIslandRegion; - stubIslandRegion.start = segment.vmaddr; - stubIslandRegion.size = segment.filesize; - char segName[17]; - memcpy(segName, segment.segname, 16); - segName[16] = 0; - std::string segNameStr = std::string(segName); - stubIslandRegion.prettyName = "dyld_shared_cache_branch_islands_" + std::to_string(i) + "::" + segNameStr; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - } - } - } - - m_logger->LogInfo("Found %d branch pools in the shared cache", primaryCacheHeader.branchPoolsCount); - - break; - } - case LargeCacheFormat: - { - dyld_cache_mapping_info mapping {}; // We're going to reuse this for all of the mappings. We only need it - // briefly. - - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - if (primaryCacheHeader.branchPoolsCount) - { - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = - baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); - } - } - std::string mainFileName = base_name(path); - if (auto projectFile = m_dscView->GetFile()->GetProjectFile()) - mainFileName = projectFile->GetName(); - auto subCacheCount = primaryCacheHeader.subCacheArrayCount; - - dyld_subcache_entry2 _entry {}; - std::vector<dyld_subcache_entry2> subCacheEntries; - subCacheEntries.reserve(subCacheCount); - for (size_t i = 0; i < subCacheCount; i++) - { - baseFile->Read(&_entry, primaryCacheHeader.subCacheArrayOffset + (i * sizeof(dyld_subcache_entry2)), - sizeof(dyld_subcache_entry2)); - subCacheEntries.push_back(_entry); - } - - baseFile.reset(); - for (const auto& entry : subCacheEntries) - { - std::string subCachePath; - std::string subCacheFilename; - if (std::string(entry.fileExtension).find('.') != std::string::npos) - { - subCachePath = path + entry.fileExtension; - subCacheFilename = mainFileName + entry.fileExtension; - } - else - { - subCachePath = path + "." + entry.fileExtension; - subCacheFilename = mainFileName + "." + entry.fileExtension; - } - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - dyld_cache_mapping_info subCacheMapping {}; - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSecondary; - subCache.path = subCachePath; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 - && subCacheHeader.imagesTextOffset == 0) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion stubIslandRegion; - stubIslandRegion.start = address; - stubIslandRegion.size = size; - stubIslandRegion.prettyName = subCacheFilename + "::_stubs"; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - - initialState.backingCaches.push_back(std::move(subCache)); - } - break; - } - case SplitCacheFormat: - { - dyld_cache_mapping_info mapping {}; // We're going to reuse this for all of the mappings. We only need it - // briefly. - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - if (primaryCacheHeader.branchPoolsCount) - { - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = - baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); - } - } - - std::string mainFileName = base_name(path); - if (auto projectFile = m_dscView->GetFile()->GetProjectFile()) - mainFileName = projectFile->GetName(); - auto subCacheCount = primaryCacheHeader.subCacheArrayCount; - - baseFile.reset(); - - for (size_t i = 1; i <= subCacheCount; i++) - { - auto subCachePath = path + "." + std::to_string(i); - auto subCacheFilename = mainFileName + "." + std::to_string(i); - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSecondary; - subCache.path = subCachePath; - - dyld_cache_mapping_info subCacheMapping {}; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - initialState.backingCaches.push_back(std::move(subCache)); - - if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 - && subCacheHeader.imagesTextOffset == 0) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion stubIslandRegion; - stubIslandRegion.start = address; - stubIslandRegion.size = size; - stubIslandRegion.prettyName = subCacheFilename + "::_stubs"; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - } - - // Load .symbols subcache - try { - auto subCachePath = path + ".symbols"; - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - dyld_cache_mapping_info subCacheMapping {}; - BackingCache subCache; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - initialState.backingCaches.push_back(std::move(subCache)); - } - catch (...) - { - m_logger->LogWarn("Failed to locate .symbols subcache. Non-exported symbol information may be missing."); - } - break; - } - case iOS16CacheFormat: - { - dyld_cache_mapping_info mapping {}; - - BackingCache cache; - cache.cacheType = BackingCacheTypePrimary; - cache.path = path; - - for (size_t i = 0; i < primaryCacheHeader.mappingCount; i++) - { - baseFile->Read(&mapping, primaryCacheHeader.mappingOffset + (i * sizeof(mapping)), sizeof(mapping)); - cache.mappings.push_back(mapping); - } - - initialState.backingCaches.push_back(std::move(cache)); - - dyld_cache_image_info img {}; - - for (size_t i = 0; i < primaryCacheHeader.imagesCount; i++) - { - baseFile->Read(&img, primaryCacheHeader.imagesOffset + (i * sizeof(img)), sizeof(img)); - auto iname = baseFile->ReadNullTermString(img.pathFileOffset); - initialState.imageStarts[iname] = img.address; - } - - if (primaryCacheHeader.branchPoolsCount) - { - for (size_t i = 0; i < primaryCacheHeader.branchPoolsCount; i++) - { - initialState.imageStarts["dyld_shared_cache_branch_islands_" + std::to_string(i)] = - baseFile->ReadULong(primaryCacheHeader.branchPoolsOffset + (i * m_dscView->GetAddressSize())); - } - } - - std::string mainFileName = base_name(path); - if (auto projectFile = m_dscView->GetFile()->GetProjectFile()) - mainFileName = projectFile->GetName(); - auto subCacheCount = primaryCacheHeader.subCacheArrayCount; - - dyld_subcache_entry2 _entry {}; - - std::vector<dyld_subcache_entry2> subCacheEntries; - subCacheEntries.reserve(subCacheCount); - for (size_t i = 0; i < subCacheCount; i++) - { - baseFile->Read(&_entry, primaryCacheHeader.subCacheArrayOffset + (i * sizeof(dyld_subcache_entry2)), - sizeof(dyld_subcache_entry2)); - subCacheEntries.push_back(_entry); - } - - baseFile.reset(); - - for (const auto& entry : subCacheEntries) - { - std::string subCachePath; - std::string subCacheFilename; - if (std::string(entry.fileExtension).find('.') != std::string::npos) - { - subCachePath = path + entry.fileExtension; - subCacheFilename = mainFileName + entry.fileExtension; - } - else - { - subCachePath = path + "." + entry.fileExtension; - subCacheFilename = mainFileName + "." + entry.fileExtension; - } - - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (headerSize > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected (0x%llx), using default size (0x%llx)", headerSize, - sizeof(dyld_cache_header)); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - dyld_cache_mapping_info subCacheMapping {}; - - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSecondary; - subCache.path = subCachePath; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - - if (subCachePath.find(".dylddata") != std::string::npos) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion dyldDataRegion; - dyldDataRegion.start = address; - dyldDataRegion.size = size; - dyldDataRegion.prettyName = subCacheFilename + "::_data" + std::to_string(j); - dyldDataRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable); - dyldDataRegion.type = MemoryRegion::Type::DyldData; - nonImageMemoryRegions.push_back(std::move(dyldDataRegion)); - } - } - - initialState.backingCaches.push_back(std::move(subCache)); - - if (subCacheHeader.mappingCount == 1 && subCacheHeader.imagesCountOld == 0 && subCacheHeader.imagesCount == 0 - && subCacheHeader.imagesTextOffset == 0) - { - auto pathBasename = subCachePath.substr(subCachePath.find_last_of("/\\") + 1); - uint64_t address = subCacheMapping.address; - uint64_t size = subCacheMapping.size; - MemoryRegion stubIslandRegion; - stubIslandRegion.start = address; - stubIslandRegion.size = size; - stubIslandRegion.prettyName = subCacheFilename + "::_stubs"; - stubIslandRegion.flags = (BNSegmentFlag)(BNSegmentFlag::SegmentReadable | BNSegmentFlag::SegmentExecutable); - stubIslandRegion.type = MemoryRegion::Type::StubIsland; - nonImageMemoryRegions.push_back(std::move(stubIslandRegion)); - } - } - - // Load .symbols subcache - try - { - auto subCachePath = path + ".symbols"; - auto subCacheFile = MapFileWithoutApplyingSlide(ResolveFilePath(m_dscView, subCachePath)); - dyld_cache_header subCacheHeader {}; - uint64_t headerSize = subCacheFile->ReadUInt32(16); - if (subCacheFile->ReadUInt32(16) > sizeof(dyld_cache_header)) - { - m_logger->LogDebug("Header size is larger than expected, using default size"); - headerSize = sizeof(dyld_cache_header); - } - subCacheFile->Read(&subCacheHeader, 0, headerSize); - - BackingCache subCache; - subCache.cacheType = BackingCacheTypeSymbols; - subCache.path = subCachePath; - - dyld_cache_mapping_info subCacheMapping {}; - - for (size_t j = 0; j < subCacheHeader.mappingCount; j++) - { - subCacheFile->Read(&subCacheMapping, subCacheHeader.mappingOffset + (j * sizeof(subCacheMapping)), - sizeof(subCacheMapping)); - subCache.mappings.push_back(subCacheMapping); - } - - initialState.backingCaches.push_back(std::move(subCache)); - } - catch (...) - { - m_logger->LogWarn("Failed to load the symbols cache"); - } - break; - } - } - baseFile.reset(); - - m_logger->LogInfo("Loading images..."); - m_viewSpecificState->progress = LoadProgressLoadingImages; + return std::nullopt; - // We have set up enough metadata to map VM now. + // Read the header, this _should_ be compatible with all known DSC formats. + // Mason: the above is not true! https://github.com/Vector35/binaryninja-api/issues/6073 + dyld_cache_header header = {}; + file->Read(&header, 0, sizeof(header)); - auto vm = GetVMMap(initialState); - if (!vm) + // Read the mappings using the headers `mappingCount` and `mappingOffset`. + dyld_cache_mapping_info currentMapping = {}; + std::vector<dyld_cache_mapping_info> mappings; + for (size_t i = 0; i < header.mappingCount; i++) { - m_logger->LogError("Failed to map VM pages for Shared Cache on initial load, this is fatal."); - return; + file->Read(¤tMapping, header.mappingOffset + (i * sizeof(currentMapping)), sizeof(currentMapping)); + mappings.push_back(currentMapping); } - for (const auto& start : initialState.imageStarts) - { - try - { - auto imageHeader = SharedCache::LoadHeaderForAddress(vm, start.second, start.first); - if (!imageHeader) - { - m_logger->LogError("Failed to load Mach-O header for %s", start.first.c_str()); - continue; - } - if (imageHeader->linkeditPresent && vm->AddressIsMapped(imageHeader->linkeditSegment.vmaddr)) - { - auto mapping = vm->MappingAtAddress(imageHeader->linkeditSegment.vmaddr); - imageHeader->exportTriePath = mapping.first.fileAccessor->filePath(); - } - initialState.headers[start.second] = imageHeader.value(); - CacheImage image; - image.installName = start.first; - image.headerLocation = start.second; - for (const auto& segment : imageHeader->segments) - { - char segName[17]; - memcpy(segName, segment.segname, 16); - segName[16] = 0; - - // Many images include a __LINKEDIT segment that share a single region in the shared cache. - // Reuse the same `MemoryRegion` to represent all of these linkedit regions. - if (std::string(segName) == "__LINKEDIT") - { - if (auto it = initialState.memoryRegions.find(segment.vmaddr); - it != initialState.memoryRegions.end()) - { - image.regionStarts.push_back(it->second.start); - continue; - } - } - MemoryRegion sectionRegion; - sectionRegion.prettyName = imageHeader.value().identifierPrefix + "::" + std::string(segName); - sectionRegion.start = segment.vmaddr; - sectionRegion.size = segment.vmsize; - sectionRegion.imageStart = start.second; - uint32_t flags = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot); - - // if we're positive we have an entry point for some reason, force the segment - // executable. this helps with kernel images. - for (auto &entryPoint : imageHeader->m_entryPoints) - if (segment.vmaddr <= entryPoint && (entryPoint < (segment.vmaddr + segment.filesize))) - flags |= SegmentExecutable; - - sectionRegion.flags = (BNSegmentFlag)flags; - sectionRegion.type = MemoryRegion::Type::Image; - if (auto region = initialState.AddMemoryRegion(std::move(sectionRegion))) - image.regionStarts.push_back(region->start); - } - initialState.images.push_back(image); - } - catch (std::exception& ex) - { - m_logger->LogError("Failed to load Mach-O header for %s: %s", start.first.c_str(), ex.what()); - } - } - - m_logger->LogInfo("Loaded %d Mach-O headers", initialState.headers.size()); - - for (auto& memoryRegion : nonImageMemoryRegions) + // Handle special entry types. + if (fileName.find(".dylddata") != std::string::npos) { - initialState.AddPotentiallyOverlappingMemoryRegion(std::move(memoryRegion)); + // We found a single dyld data cache entry file. Mark it as such! + type = CacheEntryType::DyldData; } - m_logger->LogInfo("Loaded %zu stub island or dyld memory regions", nonImageMemoryRegions.size()); - - for (const auto& cache : initialState.backingCaches) + else if (fileName.find(".symbols") != std::string::npos) { - size_t i = 0; - for (const auto& mapping : cache.mappings) - { - MemoryRegion region; - region.start = mapping.address; - region.size = mapping.size; - region.prettyName = base_name(cache.path) + "::" + std::to_string(i++); - region.flags = SegmentFlagsFromMachOProtections(mapping.initProt, mapping.maxProt); - region.type = MemoryRegion::Type::NonImage; - initialState.AddPotentiallyOverlappingMemoryRegion(std::move(region)); - } + // We found a single symbols cache entry file. Mark it as such! + type = CacheEntryType::Symbols; } - - m_cacheInfo = std::make_shared<CacheInfo>(std::move(initialState)); - m_modifiedState->viewState = DSCViewStateLoaded; - SaveCacheInfoToDSCView(lock); - - m_logger->LogInfo("Finished loading..."); - m_viewSpecificState->progress = LoadProgressFinished; -} - -std::shared_ptr<VM> SharedCache::GetVMMap() -{ - return GetVMMap(*m_cacheInfo); -} - -std::shared_ptr<VM> SharedCache::GetVMMap(const CacheInfo& cacheInfo) -{ - std::shared_ptr<VM> vm = std::make_shared<VM>(0x1000); - - uint64_t baseAddress = cacheInfo.BaseAddress(); - Ref<Logger> logger = m_logger; - for (const auto& cache : cacheInfo.backingCaches) + else if (mappings.size() == 1 && header.imagesCountOld == 0 && header.imagesCount == 0 + && header.imagesTextOffset == 0) { - for (const auto& mapping : cache.mappings) - { - vm->MapPages(m_dscView, m_dscView->GetFile()->GetSessionId(), mapping.address, mapping.fileOffset, mapping.size, cache.path, - [vm, baseAddress, logger](std::shared_ptr<MMappedFileAccessor> mmap){ - ParseAndApplySlideInfoForFile(mmap, baseAddress, logger); - }); - } + // Stub entry file, should only have a single mapping and no images. + // NOTE: If we end up identifying something incorrectly as a stub we need to restrict this further. + // We found a single stub cache entry file. Mark it as such! + type = CacheEntryType::Stub; } - return vm; -} - - -bool SharedCache::DeserializeFromRawView(std::lock_guard<std::mutex>& lock) -{ - std::lock_guard cacheInfoLock(m_viewSpecificState->cacheInfoMutex); - - m_cacheInfo = std::make_shared<CacheInfo>(); - m_modifiedState = std::make_unique<ModifiedState>(); - m_modifiedState->viewState = DSCViewStateUnloaded; - - // First try and load from the view itself. - if (m_viewSpecificState->cacheInfo) + // Gather all images for the entry. + std::unordered_map<std::string, dyld_cache_image_info> images; + dyld_cache_image_info currentImg {}; + for (size_t i = 0; i < header.imagesCount; i++) { - m_cacheInfo = m_viewSpecificState->cacheInfo; - m_modifiedState->viewState = DSCViewStateLoaded; - return true; + file->Read( + ¤tImg, header.imagesOffset + (i * sizeof(dyld_cache_image_info)), sizeof(dyld_cache_image_info)); + auto imagePath = file->ReadNullTermString(currentImg.pathFileOffset); + images.insert_or_assign(imagePath, currentImg); } - // Second try and load from the view metadata. - if (SharedCacheMetadata::ViewHasMetadata(m_dscView)) + // Handle old dyld format that uses old images field. + for (size_t i = 0; i < header.imagesCountOld; i++) { - auto metadata = SharedCacheMetadata::LoadFromView(m_dscView); - if (!metadata) - return false; - - m_viewSpecificState->viewState = metadata->state->viewState.value_or(DSCViewStateUnloaded); - m_viewSpecificState->state = static_cast<State>(std::move(*metadata->state)); - m_viewSpecificState->cacheInfo = std::move(metadata->cacheInfo); - - m_cacheInfo = m_viewSpecificState->cacheInfo; - m_modifiedState->viewState = DSCViewStateLoaded; + file->Read( + ¤tImg, header.imagesOffsetOld + (i * sizeof(dyld_cache_image_info)), sizeof(dyld_cache_image_info)); + auto imagePath = file->ReadNullTermString(currentImg.pathFileOffset); + images.insert_or_assign(imagePath, currentImg); } - return true; -} - - -// static -void SharedCache::ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAccessor> file, uint64_t base, Ref<Logger> logger) -{ - if (file->SlideInfoWasApplied()) - return; - - dyld_cache_header baseHeader; - file->Read(&baseHeader, 0, sizeof(dyld_cache_header)); - - std::vector<std::pair<uint64_t, MappingInfo>> mappings; - - if (baseHeader.slideInfoOffsetUnused) + // NOTE: I am not sure how the header type has changed over time but if apple is replacing fields with other ones + // NOTE: And branchPoolsCount is not zero for earlier shared caches (non split cache ones) than we need to check + // this! Also make pseudo-image for the branch pools, so we can map them in to the binary view. + for (size_t i = 0; i < header.branchPoolsCount; i++) { - // Legacy - - auto slideInfoOff = baseHeader.slideInfoOffsetUnused; - auto slideInfoVersion = file->ReadUInt32(slideInfoOff); - if (slideInfoVersion != 2 && slideInfoVersion != 3) - { - logger->LogError("Unsupported slide info version %d", slideInfoVersion); - throw std::runtime_error("Unsupported slide info version"); - } - - MappingInfo map; - - file->Read(&map.mappingInfo, baseHeader.mappingOffset + sizeof(dyld_cache_mapping_info), sizeof(dyld_cache_mapping_info)); - map.slideInfoVersion = slideInfoVersion; - if (map.slideInfoVersion == 2) - file->Read(&map.slideInfoV2, slideInfoOff, sizeof(dyld_cache_slide_info_v2)); - else if (map.slideInfoVersion == 3) - file->Read(&map.slideInfoV3, slideInfoOff, sizeof(dyld_cache_slide_info_v3)); - - mappings.emplace_back(slideInfoOff, map); + dyld_cache_image_info branchIslandImg = {}; + // TODO: uint64_t means this only works on 64bit... tbh tho this is fine this is a new addition so 32bit doesnt + // apply here. + // TODO: If we want to make this work for other addr sizes we need the binary view in this function. + branchIslandImg.address = header.branchPoolsOffset + (i * sizeof(uint64_t)); + // Mason: why such a long name for the image??? + auto imageName = fmt::format("dyld_shared_cache_branch_islands_{}", i); + images.insert_or_assign(imageName, branchIslandImg); } - else - { - dyld_cache_header targetHeader; - file->Read(&targetHeader, 0, sizeof(dyld_cache_header)); - - if (targetHeader.mappingWithSlideCount == 0) - { - logger->LogDebug("No mappings with slide info found"); - } - - for (auto i = 0; i < targetHeader.mappingWithSlideCount; i++) - { - dyld_cache_mapping_and_slide_info mappingAndSlideInfo; - file->Read(&mappingAndSlideInfo, targetHeader.mappingWithSlideOffset + (i * sizeof(dyld_cache_mapping_and_slide_info)), sizeof(dyld_cache_mapping_and_slide_info)); - if (mappingAndSlideInfo.slideInfoFileOffset) - { - MappingInfo map; - if (mappingAndSlideInfo.size == 0) - continue; - map.slideInfoVersion = file->ReadUInt32(mappingAndSlideInfo.slideInfoFileOffset); - logger->LogDebug("Slide Info Version: %d", map.slideInfoVersion); - map.mappingInfo.address = mappingAndSlideInfo.address; - map.mappingInfo.size = mappingAndSlideInfo.size; - map.mappingInfo.fileOffset = mappingAndSlideInfo.fileOffset; - if (map.slideInfoVersion == 2) - { - file->Read( - &map.slideInfoV2, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info_v2)); - } - else if (map.slideInfoVersion == 3) - { - file->Read( - &map.slideInfoV3, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info_v3)); - map.slideInfoV3.auth_value_add = base; - } - else if (map.slideInfoVersion == 5) - { - file->Read( - &map.slideInfoV5, mappingAndSlideInfo.slideInfoFileOffset, sizeof(dyld_cache_slide_info5)); - map.slideInfoV5.value_add = base; - } - else - { - logger->LogError("Unknown slide info version: %d", map.slideInfoVersion); - continue; - } - - uint64_t slideInfoOffset = mappingAndSlideInfo.slideInfoFileOffset; - mappings.emplace_back(slideInfoOffset, map); - logger->LogDebug("Filename: %s", file->Path().c_str()); - logger->LogDebug("Slide Info Offset: 0x%llx", slideInfoOffset); - logger->LogDebug("Mapping Address: 0x%llx", map.mappingInfo.address); - logger->LogDebug("Slide Info v", map.slideInfoVersion); - } - } - } - - if (mappings.empty()) - { - logger->LogDebug("No slide info found"); - file->SetSlideInfoWasApplied(true); - return; - } - - for (const auto& [off, mapping] : mappings) - { - logger->LogDebug("Slide Info Version: %d", mapping.slideInfoVersion); - uint64_t extrasOffset = off; - uint64_t pageStartsOffset = off; - uint64_t pageStartCount; - uint64_t pageSize; - - if (mapping.slideInfoVersion == 2) - { - pageStartsOffset += mapping.slideInfoV2.page_starts_offset; - pageStartCount = mapping.slideInfoV2.page_starts_count; - pageSize = mapping.slideInfoV2.page_size; - extrasOffset += mapping.slideInfoV2.page_extras_offset; - auto cursor = pageStartsOffset; - - for (size_t i = 0; i < pageStartCount; i++) - { - try - { - uint16_t start = file->ReadUShort(cursor); - cursor += sizeof(uint16_t); - if (start == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE) - continue; - - auto rebaseChain = [&](const dyld_cache_slide_info_v2& slideInfo, uint64_t pageContent, uint16_t startOffset) - { - uintptr_t slideAmount = 0; - - auto deltaMask = slideInfo.delta_mask; - auto valueMask = ~deltaMask; - auto valueAdd = slideInfo.value_add; - - auto deltaShift = count_trailing_zeros(deltaMask) - 2; - - uint32_t pageOffset = startOffset; - uint32_t delta = 1; - while ( delta != 0 ) - { - uint64_t loc = pageContent + pageOffset; - try - { - uintptr_t rawValue = file->ReadULong(loc); - delta = (uint32_t)((rawValue & deltaMask) >> deltaShift); - uintptr_t value = (rawValue & valueMask); - if (value != 0) - { - value += valueAdd; - value += slideAmount; - } - pageOffset += delta; - file->WritePointer(loc, value); - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v2 slide pointer at 0x%llx\n", loc); - break; - } - } - }; - - if (start & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) - { - int j=(start & 0x3FFF); - bool done = false; - do - { - uint64_t extraCursor = extrasOffset + (j * sizeof(uint16_t)); - try - { - auto extra = file->ReadUShort(extraCursor); - uint16_t aStart = extra; - uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i); - uint16_t pageStartOffset = (aStart & 0x3FFF)*4; - rebaseChain(mapping.slideInfoV2, page, pageStartOffset); - done = (extra & DYLD_CACHE_SLIDE_PAGE_ATTR_END); - ++j; - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v2 slide extra at 0x%llx\n", cursor); - break; - } - } while (!done); - } - else - { - uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i); - uint16_t pageStartOffset = start*4; - rebaseChain(mapping.slideInfoV2, page, pageStartOffset); - } - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v2 slide info at 0x%llx\n", cursor); - } - } - } - else if (mapping.slideInfoVersion == 3) { - // Slide Info Version 3 Logic - pageStartsOffset += sizeof(dyld_cache_slide_info_v3); - pageStartCount = mapping.slideInfoV3.page_starts_count; - pageSize = mapping.slideInfoV3.page_size; - auto cursor = pageStartsOffset; - - for (size_t i = 0; i < pageStartCount; i++) - { - try - { - uint16_t delta = file->ReadUShort(cursor); - cursor += sizeof(uint16_t); - if (delta == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE) - continue; - - delta = delta/sizeof(uint64_t); // initial offset is byte based - uint64_t loc = mapping.mappingInfo.fileOffset + (pageSize * i); - do - { - loc += delta * sizeof(dyld_cache_slide_pointer3); - try - { - dyld_cache_slide_pointer3 slideInfo = { file->ReadULong(loc) }; - delta = slideInfo.plain.offsetToNextPointer; - if (slideInfo.auth.authenticated) - { - uint64_t value = slideInfo.auth.offsetFromSharedCacheBase; - value += mapping.slideInfoV3.auth_value_add; - file->WritePointer(loc, value); - } - else - { - uint64_t value51 = slideInfo.plain.pointerValue; - uint64_t top8Bits = value51 & 0x0007F80000000000; - uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFF; - uint64_t value = (uint64_t)top8Bits << 13 | bottom43Bits; - file->WritePointer(loc, value); - } - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v3 slide pointer at 0x%llx\n", loc); - break; - } - } while (delta != 0); - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v3 slide info at 0x%llx\n", cursor); - } - } - } - else if (mapping.slideInfoVersion == 5) - { - pageStartsOffset += sizeof(dyld_cache_slide_info5); - pageStartCount = mapping.slideInfoV5.page_starts_count; - pageSize = mapping.slideInfoV5.page_size; - auto cursor = pageStartsOffset; - - for (size_t i = 0; i < pageStartCount; i++) - { - try - { - uint16_t delta = file->ReadUShort(cursor); - cursor += sizeof(uint16_t); - if (delta == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE) - continue; - - delta = delta/sizeof(uint64_t); // initial offset is byte based - uint64_t loc = mapping.mappingInfo.fileOffset + (pageSize * i); - do - { - loc += delta * sizeof(dyld_cache_slide_pointer5); - try - { - dyld_cache_slide_pointer5 slideInfo = { file->ReadULong(loc) }; - delta = slideInfo.regular.next; - if (slideInfo.auth.auth) - { - uint64_t value = mapping.slideInfoV5.value_add + slideInfo.auth.runtimeOffset; - file->WritePointer(loc, value); - } - else - { - uint64_t value = mapping.slideInfoV5.value_add + slideInfo.regular.runtimeOffset; - file->WritePointer(loc, value); - } - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v5 slide pointer at 0x%llx\n", loc); - break; - } - } while (delta != 0); - } - catch (MappingReadException& ex) - { - logger->LogError("Failed to read v5 slide info at 0x%llx\n", cursor); - } - } - } - } - // logger->LogDebug("Applied slide info for %s (0x%llx rewrites)", file->Path().c_str(), rewrites.size()); - file->SetSlideInfoWasApplied(true); + return CacheEntry(filePath, fileName, type, header, mappings, images); } - -SharedCache::SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) : - m_dscView(dscView), m_viewSpecificState(ViewSpecificStateForView(dscView)) +WeakFileAccessor CacheEntry::GetAccessor() const { - std::lock_guard lock(m_mutex); - m_logger = LogRegistry::GetLogger("SharedCache", dscView->GetFile()->GetSessionId()); - if (dscView->GetTypeName() != VIEW_NAME) - { - // Unreachable? - m_logger->LogError("Attempted to create SharedCache object from non-Shared Cache view"); - return; - } - - ++sharedCacheReferences; - INIT_SHAREDCACHE_API_OBJECT() - if (!DeserializeFromRawView(lock)) - { - // TODO: We need a way to prompt the user and ask if they want to continue (like BNDB version upgrades) - // TODO: To do that we really need to consolidate _where_ SharedCache is called. - // TODO: Specifically the **first** call to this must originate in DSCView, which is NOT the case currently. - m_logger->LogWarn("Metadata was invalid, recreating initial load of shared cache information..."); - } - - if (m_modifiedState->viewState.value_or(m_viewSpecificState->viewState) != DSCViewStateUnloaded) - { - m_viewSpecificState->progress = LoadProgressFinished; - return; - } - - try { - PerformInitialLoad(lock); - } - catch (std::exception& e) - { - m_logger->LogError("Failed to perform initial load of Shared Cache: %s", e.what()); - } - - auto settings = m_dscView->GetLoadSettings(VIEW_NAME); - bool autoLoadLibsystem = true; - if (settings && settings->Contains("loader.dsc.autoLoadLibSystem")) - { - autoLoadLibsystem = settings->Get<bool>("loader.dsc.autoLoadLibSystem", m_dscView); - } - if (autoLoadLibsystem) - { - for (const auto& [_, header] : m_cacheInfo->headers) - { - if (header.installName.find("libsystem_c.dylib") != std::string::npos) - { - m_logger->LogInfo("Loading core libsystem_c.dylib library"); - LoadImageWithInstallName(lock, header.installName, false); - break; - } - } - } -} - -SharedCache::~SharedCache() { - --sharedCacheReferences; + return FileAccessorCache::Global().Open(m_filePath); } -SharedCache* SharedCache::GetFromDSCView(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView) +std::optional<uint64_t> CacheEntry::GetHeaderAddress() const { - if (dscView->GetTypeName() != VIEW_NAME) - return nullptr; - try { - return new SharedCache(dscView); - } - catch (...) - { - return nullptr; - } + // The mapping at file offset 0 will contain the header (duh). + return GetMappedAddress(0); } -std::optional<uint64_t> SharedCache::GetImageStart(const std::string_view installName) +std::optional<uint64_t> CacheEntry::GetMappedAddress(uint64_t fileOffset) const { - const auto& imageStarts = m_cacheInfo->imageStarts; - auto it = std::find_if(imageStarts.begin(), imageStarts.end(), [&] (auto image) { - return image.first == installName; - }); - - if (it != imageStarts.end()) - return it->second; - + for (const auto& mapping : m_mappings) + if (mapping.fileOffset <= fileOffset && mapping.fileOffset + mapping.size > fileOffset) + return mapping.address + (fileOffset - mapping.fileOffset); return std::nullopt; } -const SharedCacheMachOHeader* SharedCache::HeaderForAddress(uint64_t address) -{ - // It is very common for `HeaderForAddress` to be called with an address corresponding to a header. - if (auto it = m_cacheInfo->headers.find(address); it != m_cacheInfo->headers.end()) - return &it->second; - - auto it = m_cacheInfo->memoryRegions.find(address); - if (it == m_cacheInfo->memoryRegions.end()) - return nullptr; - - if (auto headerAddress = it->second.imageStart) - return HeaderForAddress(headerAddress); - - // Found a region, but its `imageStart` was 0. This should mean it doesn't belong to an image. - assert(it->second.type != MemoryRegion::Type::Image); - return nullptr; -} - -std::string SharedCache::NameForAddress(uint64_t address) +SharedCache::SharedCache(uint64_t addressSize) { - if (auto it = m_cacheInfo->memoryRegions.find(address); it != m_cacheInfo->memoryRegions.end()) - return it->second.prettyName; - - return ""; + m_addressSize = addressSize; + m_vm = std::make_shared<VirtualMemory>(); } -std::string SharedCache::ImageNameForAddress(uint64_t address) -{ - if (auto header = HeaderForAddress(address)) - return header->identifierPrefix; - - return ""; -} -bool SharedCache::LoadImageContainingAddress(uint64_t address, bool skipObjC) +void SharedCache::AddImage(CacheImage image) { - if (auto header = HeaderForAddress(address)) { - std::lock_guard lock(m_mutex); - return LoadImageWithInstallName(lock, header->installName, skipObjC); - } - - return false; + m_images.insert({image.headerAddress, std::move(image)}); } -bool SharedCache::LoadSectionAtAddress(uint64_t address) +void SharedCache::AddRegion(CacheRegion region) { - std::lock(m_mutex, m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex, std::adopt_lock); - std::lock_guard lock(m_mutex, std::adopt_lock); - - auto vm = GetVMMap(); - if (!vm) { - m_logger->LogError("Failed to map VM pages for Shared Cache."); - return false; - } - - SharedCacheMachOHeader targetHeader; - const CacheImage* targetImage = nullptr; - const MemoryRegion* targetSegment = nullptr; - - auto it = m_cacheInfo->memoryRegions.find(address); - if (it != m_cacheInfo->memoryRegions.end()) + // Handle overlapping regions here. + const auto regionRange = region.AsAddressRange(); + // First region at or past the start of the region. + const auto begin = m_regions.lower_bound(regionRange.start); + if (begin == m_regions.end()) { - const MemoryRegion* region = &it->second; - for (auto& image : m_cacheInfo->images) - { - if (std::find(image.regionStarts.begin(), image.regionStarts.end(), region->start) == image.regionStarts.end()) - continue; - - targetHeader = m_cacheInfo->headers.at(image.headerLocation); - targetImage = ℑ - targetSegment = region; - break; - } + AddNonOverlappingRegion(std::move(region)); + return; } - if (!targetSegment) + // First region past the end of the region. + const auto end = m_regions.lower_bound(regionRange.end); + + for (auto it = begin; it != end; ++it) { - auto regionIt = m_cacheInfo->memoryRegions.find(address); - if (regionIt == m_cacheInfo->memoryRegions.end()) + const uint64_t newRegionSize = it->second.start - region.start; + if (newRegionSize) { - m_logger->LogError("Failed to find a segment containing address 0x%llx", address); - return false; + CacheRegion newRegion(region); + newRegion.size = newRegionSize; + AddNonOverlappingRegion(std::move(newRegion)); } - auto& region = regionIt->second; - if (MemoryRegionIsLoaded(lock, region)) - return true; - - m_logger->LogInfo( - "Loading region of type %d named %s @ 0x%llx", region.type, region.prettyName.c_str(), region.start); - auto targetFile = vm->MappingAtAddress(region.start).first.fileAccessor->lock(); - - auto reader = VMReader(vm); - auto buff = reader.ReadBuffer(region.start, region.size); - - m_dscView->GetMemoryMap()->AddDataMemoryRegion(region.prettyName, region.start, buff, region.flags); - m_dscView->AddUserSection(region.prettyName, region.start, region.size, SectionSemanticsForRegion(region)); - - SetMemoryRegionIsLoaded(lock, region); - - SaveModifiedStateToDSCView(lock); - - m_dscView->AddAnalysisOption("linearsweep"); - m_dscView->UpdateAnalysis(); - - return true; + region.start = it->second.start + it->second.size; + region.size -= (newRegionSize + it->second.size); } - auto id = m_dscView->BeginUndoActions(); - auto reader = VMReader(vm); - - m_logger->LogDebug("Partial loading image %s", targetHeader.installName.c_str()); - - auto targetFile = vm->MappingAtAddress(targetSegment->start).first.fileAccessor->lock(); - auto buff = reader.ReadBuffer(targetSegment->start, targetSegment->size); - m_dscView->GetMemoryMap()->AddDataMemoryRegion(targetSegment->prettyName, targetSegment->start, buff, targetSegment->flags); - - SetMemoryRegionIsLoaded(lock, *targetSegment); - - if (!MemoryRegionIsHeaderInitialized(lock, *targetSegment)) - SharedCache::InitializeHeader(lock, m_dscView, vm.get(), targetHeader, {targetSegment}); - - SaveModifiedStateToDSCView(lock); - - m_dscView->AddAnalysisOption("linearsweep"); - m_dscView->UpdateAnalysis(); - - m_dscView->CommitUndoActions(id); - - return true; + // Add remaining region. + if (region.size > 0) + AddNonOverlappingRegion(std::move(region)); } -static void GetObjCSettings(Ref<BinaryView> view, bool* processObjCMetadata, bool* processCFStrings) +bool SharedCache::AddNonOverlappingRegion(CacheRegion region) { - auto settings = view->GetLoadSettings(VIEW_NAME); - *processCFStrings = true; - *processObjCMetadata = true; - if (settings && settings->Contains("loader.dsc.processCFStrings")) - *processCFStrings = settings->Get<bool>("loader.dsc.processCFStrings", view); - if (settings && settings->Contains("loader.dsc.processObjC")) - *processObjCMetadata = settings->Get<bool>("loader.dsc.processObjC", view); + auto [_, inserted] = m_regions.insert(std::make_pair(region.AsAddressRange(), std::move(region))); + return inserted; } -static void ProcessObjCSectionsForImageWithName(std::string baseName, std::shared_ptr<VM> vm, std::shared_ptr<DSCObjC::DSCObjCProcessor> objc, bool processCFStrings, bool processObjCMetadata, Ref<Logger> logger) +void SharedCache::AddSymbol(CacheSymbol symbol) { - try - { - if (processObjCMetadata) - objc->ProcessObjCData(baseName); - if (processCFStrings) - objc->ProcessCFStrings(baseName); - } - catch (const std::exception& ex) - { - logger->LogWarn("Error processing ObjC data for image %s: %s", baseName.c_str(), ex.what()); - } - catch (...) - { - logger->LogWarn("Error processing ObjC data for image %s", baseName.c_str()); - } + m_symbols.insert({symbol.address, std::move(symbol)}); } -void SharedCache::ProcessObjCSectionsForImageWithInstallName(std::string installName) +void SharedCache::AddSymbols(std::vector<CacheSymbol> symbols) { - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - if (!processObjCMetadata && !processCFStrings) - return; - - auto objc = std::make_shared<DSCObjC::DSCObjCProcessor>(m_dscView, this, false); - auto vm = GetVMMap(); - - ProcessObjCSectionsForImageWithName(base_name(installName), vm, objc, processCFStrings, processObjCMetadata, m_logger); -} - -void SharedCache::ProcessAllObjCSections() -{ - std::lock_guard lock(m_mutex); - ProcessAllObjCSections(lock); + for (auto& symbol : symbols) + m_symbols.insert({symbol.address, std::move(symbol)}); } -void SharedCache::ProcessAllObjCSections(std::lock_guard<std::mutex>& lock) +CacheEntryId SharedCache::AddEntry(CacheEntry entry) { - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - if (!processObjCMetadata && !processCFStrings) - return; + // TODO: Maybe check to see if we already added the file? + // TODO: I doubt we will ever accidentally call this for the same entry... + // This is monotonically increasing so you can tell how many times we have called this function :) + CacheEntryId id = nextId++; - auto objc = std::make_shared<DSCObjC::DSCObjCProcessor>(m_dscView, this, false); - auto vm = GetVMMap(); + // Get the file accessor to associate with the virtual memory region. + auto fileAccessor = FileAccessorCache::Global().Open(entry.GetFilePath()); - std::set<uint64_t> processedImageHeaders; - for (auto region : GetMappedRegions()) + // Populate virtual memory using the entry mappings, by doing so we can now + // read the memory of the mapped regions of the cache entry file. + const auto& mappings = entry.GetMappings(); + for (const auto& mapping : mappings) { - // Don't repeat the same images multiple times - auto header = HeaderForAddress(region->start); - if (!header) - continue; - if (processedImageHeaders.find(header->textBase) != processedImageHeaders.end()) - continue; - processedImageHeaders.insert(header->textBase); + m_vm->MapRegion(fileAccessor, {mapping.address, mapping.address + mapping.size}, mapping.fileOffset); - ProcessObjCSectionsForImageWithName(header->identifierPrefix, vm, objc, processCFStrings, processObjCMetadata, m_logger); + // Recalculate the base address. + if (mapping.address < m_baseAddress || m_baseAddress == 0) + m_baseAddress = mapping.address; } -} -bool SharedCache::LoadImageWithInstallName(std::string installName, bool skipObjC) -{ - std::lock_guard lock(m_mutex); - return LoadImageWithInstallName(lock, installName, skipObjC); + // We are done and can make the entry visible to the entire cache. + m_entries.insert({id, std::move(entry)}); + return id; } -bool SharedCache::LoadImageWithInstallName(std::lock_guard<std::mutex>& lock, std::string installName, bool skipObjC) +bool SharedCache::ProcessEntryImage(const std::string& path, const dyld_cache_image_info& info) { - auto settings = m_dscView->GetLoadSettings(VIEW_NAME); - - std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - - m_logger->LogInfo("Loading image %s", installName.c_str()); - - auto vm = GetVMMap(); - const CacheImage* targetImage = nullptr; - - for (auto& cacheImage : m_cacheInfo->images) - { - if (cacheImage.installName == installName) - { - targetImage = &cacheImage; - break; - } - } - - if (!targetImage) - { - m_logger->LogError("Failed to find target image %s", installName.c_str()); - return false; - } - - auto it = m_cacheInfo->headers.find(targetImage->headerLocation); - if (it == m_cacheInfo->headers.end()) - { - m_logger->LogError("Failed to find target image header %s", installName.c_str()); - return false; - } - - const auto& header = it->second; - - auto id = m_dscView->BeginUndoActions(); - m_modifiedState->viewState = DSCViewStateLoadedWithImages; - - auto reader = VMReader(vm); - reader.Seek(targetImage->headerLocation); - - std::vector<const MemoryRegion*> regionsToLoad; - regionsToLoad.reserve(targetImage->regionStarts.size()); - - for (auto regionStart : targetImage->regionStarts) - { - const auto& region = m_cacheInfo->memoryRegions.find(regionStart)->second; - bool allowLoadingLinkedit = false; - if (settings && settings->Contains("loader.dsc.allowLoadingLinkeditSegments")) - allowLoadingLinkedit = settings->Get<bool>("loader.dsc.allowLoadingLinkeditSegments", m_dscView); - if ((region.prettyName.find("__LINKEDIT") != std::string::npos) && !allowLoadingLinkedit) - continue; - - if (MemoryRegionIsLoaded(lock, region)) - { - m_logger->LogDebug("Skipping region %s as it is already loaded.", region.prettyName.c_str()); - continue; - } - - auto targetFile = vm->MappingAtAddress(region.start).first.fileAccessor->lock(); - auto buff = reader.ReadBuffer(region.start, region.size); - m_dscView->GetMemoryMap()->AddDataMemoryRegion(region.prettyName, region.start, buff, region.flags); - - SetMemoryRegionIsLoaded(lock, region); - regionsToLoad.push_back(®ion); - } - - // Regions for this image are already loaded, skip un-needed analysis! - if (regionsToLoad.empty()) + auto imageHeader = SharedCacheMachOHeader::ParseHeaderForAddress(m_vm, info.address, path); + if (!imageHeader.has_value()) return false; - auto typeLib = TypeLibraryForImage(header.installName); - - auto h = SharedCache::LoadHeaderForAddress(vm, targetImage->headerLocation, installName); - if (!h) - { - SaveModifiedStateToDSCView(lock); - return false; - } - - SharedCache::InitializeHeader(lock, m_dscView, vm.get(), *h, regionsToLoad); - SaveModifiedStateToDSCView(lock); + // Add the image to the cache. + CacheImage image; + image.headerAddress = info.address; + image.path = path; - if (!skipObjC) + // Add all image regions. + for (const auto& segment : imageHeader->segments) { - bool processCFStrings; - bool processObjCMetadata; - GetObjCSettings(m_dscView, &processCFStrings, &processObjCMetadata); - - ProcessObjCSectionsForImageWithName(h->identifierPrefix, vm, std::make_shared<DSCObjC::DSCObjCProcessor>(m_dscView, this, false), processCFStrings, processObjCMetadata, m_logger); - } - - m_dscView->AddAnalysisOption("linearsweep"); - m_dscView->UpdateAnalysis(); - - m_dscView->CommitUndoActions(id); - - return true; -} - -std::optional<SharedCacheMachOHeader> SharedCache::LoadHeaderForAddress(std::shared_ptr<VM> vm, uint64_t address, std::string installName) -{ - SharedCacheMachOHeader header; - - header.textBase = address; - header.installName = installName; - header.identifierPrefix = base_name(installName); + char segName[17]; + memcpy(segName, segment.segname, 16); + segName[16] = 0; - std::string errorMsg; - // address is a Raw file offset - VMReader reader(vm); - reader.Seek(address); - - header.ident.magic = reader.Read32(); - - BNEndianness endianness; - if (header.ident.magic == MH_MAGIC || header.ident.magic == MH_MAGIC_64) - endianness = LittleEndian; - else if (header.ident.magic == MH_CIGAM || header.ident.magic == MH_CIGAM_64) - endianness = BigEndian; - else - { - return {}; - } - - reader.SetEndianness(endianness); - header.ident.cputype = reader.Read32(); - header.ident.cpusubtype = reader.Read32(); - header.ident.filetype = reader.Read32(); - header.ident.ncmds = reader.Read32(); - header.ident.sizeofcmds = reader.Read32(); - header.ident.flags = reader.Read32(); - if ((header.ident.cputype & MachOABIMask) == MachOABI64) // address size == 8 - { - header.ident.reserved = reader.Read32(); - } - header.loadCommandOffset = reader.GetOffset(); - - bool first = true; - // Parse segment commands - try - { - for (size_t i = 0; i < header.ident.ncmds; i++) + // Many images include a __LINKEDIT segment that share a single region in the shared cache. + // Reuse the same `MemoryRegion` to represent all of these link edit regions. + // Check to see if we have a shared region, if so skip it. + if (std::string(segName) == "__LINKEDIT") { - // BNLogInfo("of 0x%llx", reader.GetOffset()); - load_command load; - segment_command_64 segment64; - section_64 sect; - memset(§, 0, sizeof(sect)); - size_t curOffset = reader.GetOffset(); - load.cmd = reader.Read32(); - load.cmdsize = reader.Read32(); - size_t nextOffset = curOffset + load.cmdsize; - if (load.cmdsize < sizeof(load_command)) - return {}; - - switch (load.cmd) - { - case LC_MAIN: + // TODO: Loosen this to any shared region? + if (const auto linkEditRegion = GetRegionAt(segment.vmaddr)) { - uint64_t entryPoint = reader.Read64(); - header.entryPoints.push_back({entryPoint, true}); - (void)reader.Read64(); // Stack start - break; - } - case LC_SEGMENT: // map the 32bit version to 64 bits - segment64.cmd = LC_SEGMENT_64; - reader.Read(&segment64.segname, 16); - segment64.vmaddr = reader.Read32(); - segment64.vmsize = reader.Read32(); - segment64.fileoff = reader.Read32(); - segment64.filesize = reader.Read32(); - segment64.maxprot = reader.Read32(); - segment64.initprot = reader.Read32(); - segment64.nsects = reader.Read32(); - segment64.flags = reader.Read32(); - if (first) - { - if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) - || (segment64.flags & MACHO_VM_PROT_WRITE)) - { - header.relocationBase = segment64.vmaddr; - first = false; - } - } - for (size_t j = 0; j < segment64.nsects; j++) - { - reader.Read(§.sectname, 16); - reader.Read(§.segname, 16); - sect.addr = reader.Read32(); - sect.size = reader.Read32(); - sect.offset = reader.Read32(); - sect.align = reader.Read32(); - sect.reloff = reader.Read32(); - sect.nreloc = reader.Read32(); - sect.flags = reader.Read32(); - sect.reserved1 = reader.Read32(); - sect.reserved2 = reader.Read32(); - // if the segment isn't mapped into virtual memory don't add the corresponding sections. - if (segment64.vmsize > 0) - { - header.sections.push_back(sect); - } - if (!strncmp(sect.sectname, "__mod_init_func", 15)) - header.moduleInitSections.push_back(sect); - if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - header.symbolStubSections.push_back(sect); - if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - } - header.segments.push_back(segment64); - break; - case LC_SEGMENT_64: - segment64.cmd = LC_SEGMENT_64; - reader.Read(&segment64.segname, 16); - segment64.vmaddr = reader.Read64(); - segment64.vmsize = reader.Read64(); - segment64.fileoff = reader.Read64(); - segment64.filesize = reader.Read64(); - segment64.maxprot = reader.Read32(); - segment64.initprot = reader.Read32(); - segment64.nsects = reader.Read32(); - segment64.flags = reader.Read32(); - if (strncmp(segment64.segname, "__LINKEDIT", 10) == 0) - { - header.linkeditSegment = segment64; - header.linkeditPresent = true; - } - if (first) - { - if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) - || (segment64.flags & MACHO_VM_PROT_WRITE)) - { - header.relocationBase = segment64.vmaddr; - first = false; - } - } - for (size_t j = 0; j < segment64.nsects; j++) - { - reader.Read(§.sectname, 16); - reader.Read(§.segname, 16); - sect.addr = reader.Read64(); - sect.size = reader.Read64(); - sect.offset = reader.Read32(); - sect.align = reader.Read32(); - sect.reloff = reader.Read32(); - sect.nreloc = reader.Read32(); - sect.flags = reader.Read32(); - sect.reserved1 = reader.Read32(); - sect.reserved2 = reader.Read32(); - sect.reserved3 = reader.Read32(); - // if the segment isn't mapped into virtual memory don't add the corresponding sections. - if (segment64.vmsize > 0) - { - header.sections.push_back(sect); - } - - if (!strncmp(sect.sectname, "__mod_init_func", 15)) - header.moduleInitSections.push_back(sect); - if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - header.symbolStubSections.push_back(sect); - if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - } - header.segments.push_back(segment64); - break; - case LC_ROUTINES: // map the 32bit version to 64bits - header.routines64.cmd = LC_ROUTINES_64; - header.routines64.init_address = reader.Read32(); - header.routines64.init_module = reader.Read32(); - header.routines64.reserved1 = reader.Read32(); - header.routines64.reserved2 = reader.Read32(); - header.routines64.reserved3 = reader.Read32(); - header.routines64.reserved4 = reader.Read32(); - header.routines64.reserved5 = reader.Read32(); - header.routines64.reserved6 = reader.Read32(); - header.routinesPresent = true; - break; - case LC_ROUTINES_64: - header.routines64.cmd = LC_ROUTINES_64; - header.routines64.init_address = reader.Read64(); - header.routines64.init_module = reader.Read64(); - header.routines64.reserved1 = reader.Read64(); - header.routines64.reserved2 = reader.Read64(); - header.routines64.reserved3 = reader.Read64(); - header.routines64.reserved4 = reader.Read64(); - header.routines64.reserved5 = reader.Read64(); - header.routines64.reserved6 = reader.Read64(); - header.routinesPresent = true; - break; - case LC_FUNCTION_STARTS: - header.functionStarts.funcoff = reader.Read32(); - header.functionStarts.funcsize = reader.Read32(); - header.functionStartsPresent = true; - break; - case LC_SYMTAB: - header.symtab.symoff = reader.Read32(); - header.symtab.nsyms = reader.Read32(); - header.symtab.stroff = reader.Read32(); - header.symtab.strsize = reader.Read32(); - break; - case LC_DYSYMTAB: - header.dysymtab.ilocalsym = reader.Read32(); - header.dysymtab.nlocalsym = reader.Read32(); - header.dysymtab.iextdefsym = reader.Read32(); - header.dysymtab.nextdefsym = reader.Read32(); - header.dysymtab.iundefsym = reader.Read32(); - header.dysymtab.nundefsym = reader.Read32(); - header.dysymtab.tocoff = reader.Read32(); - header.dysymtab.ntoc = reader.Read32(); - header.dysymtab.modtaboff = reader.Read32(); - header.dysymtab.nmodtab = reader.Read32(); - header.dysymtab.extrefsymoff = reader.Read32(); - header.dysymtab.nextrefsyms = reader.Read32(); - header.dysymtab.indirectsymoff = reader.Read32(); - header.dysymtab.nindirectsyms = reader.Read32(); - header.dysymtab.extreloff = reader.Read32(); - header.dysymtab.nextrel = reader.Read32(); - header.dysymtab.locreloff = reader.Read32(); - header.dysymtab.nlocrel = reader.Read32(); - header.dysymPresent = true; - break; - case LC_DYLD_CHAINED_FIXUPS: - header.chainedFixups.dataoff = reader.Read32(); - header.chainedFixups.datasize = reader.Read32(); - header.chainedFixupsPresent = true; - break; - case LC_DYLD_INFO: - case LC_DYLD_INFO_ONLY: - header.dyldInfo.rebase_off = reader.Read32(); - header.dyldInfo.rebase_size = reader.Read32(); - header.dyldInfo.bind_off = reader.Read32(); - header.dyldInfo.bind_size = reader.Read32(); - header.dyldInfo.weak_bind_off = reader.Read32(); - header.dyldInfo.weak_bind_size = reader.Read32(); - header.dyldInfo.lazy_bind_off = reader.Read32(); - header.dyldInfo.lazy_bind_size = reader.Read32(); - header.dyldInfo.export_off = reader.Read32(); - header.dyldInfo.export_size = reader.Read32(); - header.exportTrie.dataoff = header.dyldInfo.export_off; - header.exportTrie.datasize = header.dyldInfo.export_size; - header.exportTriePresent = true; - header.dyldInfoPresent = true; - break; - case LC_DYLD_EXPORTS_TRIE: - header.exportTrie.dataoff = reader.Read32(); - header.exportTrie.datasize = reader.Read32(); - header.exportTriePresent = true; - break; - case LC_THREAD: - case LC_UNIXTHREAD: - /*while (reader.GetOffset() < nextOffset) - { - - thread_command thread; - thread.flavor = reader.Read32(); - thread.count = reader.Read32(); - switch (m_archId) - { - case MachOx64: - m_logger->LogDebug("x86_64 Thread state\n"); - if (thread.flavor != X86_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statex64, sizeof(thread.statex64)); - header.entryPoints.push_back({thread.statex64.rip, false}); - break; - case MachOx86: - m_logger->LogDebug("x86 Thread state\n"); - if (thread.flavor != X86_THREAD_STATE32) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statex86, sizeof(thread.statex86)); - header.entryPoints.push_back({thread.statex86.eip, false}); - break; - case MachOArm: - m_logger->LogDebug("Arm Thread state\n"); - if (thread.flavor != _ARM_THREAD_STATE) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statearmv7, sizeof(thread.statearmv7)); - header.entryPoints.push_back({thread.statearmv7.r15, false}); - break; - case MachOAarch64: - case MachOAarch6432: - m_logger->LogDebug("Aarch64 Thread state\n"); - if (thread.flavor != _ARM_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - reader.Read(&thread.stateaarch64, sizeof(thread.stateaarch64)); - header.entryPoints.push_back({thread.stateaarch64.pc, false}); - break; - case MachOPPC: - m_logger->LogDebug("PPC Thread state\n"); - if (thread.flavor != PPC_THREAD_STATE) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //Read individual entries for endian reasons - header.entryPoints.push_back({reader.Read32(), false}); - (void)reader.Read32(); - (void)reader.Read32(); - //Read the rest of the structure - (void)reader.Read(&thread.stateppc.r1, sizeof(thread.stateppc) - (3 * 4)); - break; - case MachOPPC64: - m_logger->LogDebug("PPC64 Thread state\n"); - if (thread.flavor != PPC_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - header.entryPoints.push_back({reader.Read64(), false}); - (void)reader.Read64(); - (void)reader.Read64(); // Stack start - (void)reader.Read(&thread.stateppc64.r1, sizeof(thread.stateppc64) - (3 * 8)); - break; - default: - m_logger->LogError("Unknown archid: %x", m_archId); - } - - }*/ - break; - case LC_LOAD_DYLIB: - { - uint32_t offset = reader.Read32(); - if (offset < nextOffset) - { - reader.Seek(curOffset + offset); - std::string libname = reader.ReadCString(reader.GetOffset()); - header.dylibs.push_back(libname); - } - } - break; - case LC_BUILD_VERSION: - { - // m_logger->LogDebug("LC_BUILD_VERSION:"); - header.buildVersion.platform = reader.Read32(); - header.buildVersion.minos = reader.Read32(); - header.buildVersion.sdk = reader.Read32(); - header.buildVersion.ntools = reader.Read32(); - // m_logger->LogDebug("Platform: %s", BuildPlatformToString(header.buildVersion.platform).c_str()); - // m_logger->LogDebug("MinOS: %s", BuildToolVersionToString(header.buildVersion.minos).c_str()); - // m_logger->LogDebug("SDK: %s", BuildToolVersionToString(header.buildVersion.sdk).c_str()); - for (uint32_t j = 0; (i < header.buildVersion.ntools) && (j < 10); j++) - { - uint32_t tool = reader.Read32(); - uint32_t version = reader.Read32(); - header.buildToolVersions.push_back({tool, version}); - // m_logger->LogDebug("Build Tool: %s: %s", BuildToolToString(tool).c_str(), - // BuildToolVersionToString(version).c_str()); - } - break; - } - case LC_FILESET_ENTRY: - { - throw ReadException(); - } - default: - // m_logger->LogDebug("Unhandled command: %s : %" PRIu32 "\n", CommandToString(load.cmd).c_str(), - // load.cmdsize); - break; - } - if (reader.GetOffset() != nextOffset) - { - // m_logger->LogDebug("Didn't parse load command: %s fully %" PRIx64 ":%" PRIxPTR, - // CommandToString(load.cmd).c_str(), reader.GetOffset(), nextOffset); - } - reader.Seek(nextOffset); - } - - for (auto& section : header.sections) - { - char sectionName[17]; - memcpy(sectionName, section.sectname, sizeof(section.sectname)); - sectionName[16] = 0; - if (header.identifierPrefix.empty()) - header.sectionNames.push_back(sectionName); - else - header.sectionNames.push_back(header.identifierPrefix + "::" + sectionName); - } - } - catch (ReadException&) - { - return {}; - } - - return header; -} - - -void SharedCache::ProcessSymbols(std::shared_ptr<MMappedFileAccessor> file, const SharedCacheMachOHeader& header, uint64_t stringsOffset, size_t stringsSize, uint64_t nlistEntriesOffset, uint32_t nlistCount, uint32_t nlistStartIndex) -{ - auto addressSize = m_dscView->GetAddressSize(); - auto strings = file->ReadBuffer(stringsOffset, stringsSize); - - std::vector<Ref<Symbol>> symbolList; - for (uint64_t i = 0; i < nlistCount; i++) - { - uint64_t entryIndex = (nlistStartIndex + i); - - nlist_64 nlist = {}; - if (addressSize == 4) - { - // 32-bit DSC - struct nlist nlist32 = {}; - file->Read(&nlist, nlistEntriesOffset + (entryIndex * sizeof(nlist32)), sizeof(nlist32)); - nlist.n_strx = nlist32.n_strx; - nlist.n_type = nlist32.n_type; - nlist.n_sect = nlist32.n_sect; - nlist.n_desc = nlist32.n_desc; - nlist.n_value = nlist32.n_value; - } - else - { - // 64-bit DSC - file->Read(&nlist, nlistEntriesOffset + (entryIndex * sizeof(nlist)), sizeof(nlist)); - } - - auto symbolAddress = nlist.n_value; - if (((nlist.n_type & N_TYPE) == N_INDR) || symbolAddress == 0) - continue; - - if (nlist.n_strx >= stringsSize) - { - m_logger->LogError("Symbol entry at index %llu has a string offset of %u which is outside the strings buffer of size %llu for file %s", entryIndex, nlist.n_strx, stringsSize, file->Path().c_str()); - continue; - } - - std::string symbolName((char*)strings.GetDataAt(nlist.n_strx)); - if (symbolName == "<redacted>") - continue; - - std::optional<BNSymbolType> symbolType; - if ((nlist.n_type & N_TYPE) == N_SECT && nlist.n_sect > 0 && (size_t)(nlist.n_sect - 1) < header.sections.size()) - { - symbolType = DataSymbol; - } - else if ((nlist.n_type & N_TYPE) == N_ABS) - { - symbolType = DataSymbol; - } - else if ((nlist.n_type & N_EXT)) - { - symbolType = ExternalSymbol; - } - - if (!symbolType.has_value()) - { - m_logger->LogError("Symbol %s at address %" PRIx64 " has unknown symbol type", symbolName.c_str(), symbolAddress); - continue; - } - - std::optional<uint32_t> flags; - for (auto s : header.sections) - { - if (s.addr <= symbolAddress && symbolAddress < s.addr + s.size) - { - flags = s.flags; - } - } - - if (symbolType != ExternalSymbol) - { - if (!flags.has_value()) - { - m_logger->LogError("Symbol %s at address %" PRIx64 " is not in any section", symbolName.c_str(), symbolAddress); + image.regionStarts.push_back(linkEditRegion->start); continue; } - - if ((flags.value() & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS - || (flags.value() & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) - symbolType = FunctionSymbol; - else - symbolType = DataSymbol; } - if ((nlist.n_desc & N_ARM_THUMB_DEF) == N_ARM_THUMB_DEF) - symbolAddress++; - QualifiedName demangledName = {}; - std::string shortName = symbolName; - if (DemangleLLVM(symbolName, demangledName, true)) - shortName = demangledName.GetString(); - Ref<Symbol> sym = new Symbol(symbolType.value(), shortName, shortName, symbolName, symbolAddress, nullptr); - symbolList.emplace_back(sym); - } + CacheRegion sectionRegion; + sectionRegion.type = CacheRegionType::Image; + sectionRegion.name = imageHeader->identifierPrefix + "::" + std::string(segName); + sectionRegion.start = segment.vmaddr; + sectionRegion.size = segment.vmsize; + // Associate this region with this image, this makes it easier to identify what image owns this region. + sectionRegion.imageStart = image.headerAddress; - auto symListPtr = std::make_shared<std::vector<Ref<Symbol>>>(std::move(symbolList)); - m_modifiedState->symbolInfos.emplace(header.textBase, symListPtr); -} + uint32_t flags = SegmentFlagsFromMachOProtections(segment.initprot, segment.maxprot); + // if we're positive we have an entry point for some reason, force the segment + // executable. this helps with kernel images. + for (const auto& entryPoint : imageHeader->m_entryPoints) + if (segment.vmaddr <= entryPoint && (entryPoint < (segment.vmaddr + segment.filesize))) + flags |= SegmentExecutable; + sectionRegion.flags = static_cast<BNSegmentFlag>(flags); -void SharedCache::ApplySymbol(Ref<BinaryView> view, Ref<TypeLibrary> typeLib, Ref<Symbol> symbol) -{ - Ref<Function> func = nullptr; - auto symbolAddress = symbol->GetAddress(); - - if (symbol->GetType() == FunctionSymbol) - { - Ref<Platform> targetPlatform = view->GetDefaultPlatform(); - func = view->AddFunctionForAnalysis(targetPlatform, symbolAddress); + // Add the image section to the cache and also to the image region starts + AddRegion(sectionRegion); + image.regionStarts.push_back(sectionRegion.start); } - if (typeLib) - { - auto type = m_dscView->ImportTypeLibraryObject(typeLib, {symbol->GetFullName()}); - // TODO: This is still auto - if (type) - view->DefineAutoSymbolAndVariableOrFunction(view->GetDefaultPlatform(), symbol, type); - else - view->DefineAutoUserSymbol(symbol); - } - else - { - view->DefineAutoUserSymbol(symbol); - } + // Add the exported symbols to the available symbols. + std::vector<CacheSymbol> exportSymbols = imageHeader->ReadExportSymbolTrie(*m_vm); + AddSymbols(std::move(exportSymbols)); - if (!func) - func = view->GetAnalysisFunction(view->GetDefaultPlatform(), symbolAddress); - if (func) - { - if (symbol->GetFullName() == "_objc_msgSend") - { - func->SetHasVariableArguments(false); - } - else if (symbol->GetFullName().find("_objc_retain_x") != std::string::npos || symbol->GetFullName().find("_objc_release_x") != std::string::npos) - { - auto x = symbol->GetFullName().rfind("x"); - auto num = symbol->GetFullName().substr(x + 1); - - std::vector<BinaryNinja::FunctionParameter> callTypeParams; - auto cc = m_dscView->GetDefaultArchitecture()->GetCallingConventionByName("apple-arm64-objc-fast-arc-" + num); + // This is behind a shared pointer as the header itself is very large. + // TODO: Make this a unique pointer? I think the image should own the header at this point? + image.header = std::make_shared<SharedCacheMachOHeader>(*imageHeader); - callTypeParams.push_back({"obj", m_dscView->GetTypeByName({ "id" }), true, BinaryNinja::Variable()}); - - auto funcType = BinaryNinja::Type::FunctionType(m_dscView->GetTypeByName({ "id" }), cc, callTypeParams); - func->SetUserType(funcType); - } - } + AddImage(std::move(image)); + return true; } - -void SharedCache::InitializeHeader( - std::lock_guard<std::mutex>& lock, - Ref<BinaryView> view, VM* vm, const SharedCacheMachOHeader& header, std::vector<const MemoryRegion*> regionsToLoad) +// At this point all relevant mapping should be loaded in the virtual memory. +void SharedCache::ProcessEntryImages(const CacheEntry& entry) { - Ref<Settings> settings = view->GetLoadSettings(VIEW_NAME); - bool applyFunctionStarts = true; - if (settings && settings->Contains("loader.dsc.processFunctionStarts")) - applyFunctionStarts = settings->Get<bool>("loader.dsc.processFunctionStarts", view); - - for (size_t i = 0; i < header.sections.size(); i++) - { - bool skip = true; - for (const auto& region : regionsToLoad) - { - if (header.sections[i].addr >= region->start && header.sections[i].addr < region->start + region->size) - { - if (!MemoryRegionIsHeaderInitialized(lock, *region)) - skip = false; - break; - } - } - if (!header.sections[i].size || skip) - continue; - - std::string type; - BNSectionSemantics semantics = DefaultSectionSemantics; - switch (header.sections[i].flags & 0xff) - { - case S_REGULAR: - if (header.sections[i].flags & S_ATTR_PURE_INSTRUCTIONS) - { - type = "PURE_CODE"; - semantics = ReadOnlyCodeSectionSemantics; - } - else if (header.sections[i].flags & S_ATTR_SOME_INSTRUCTIONS) - { - type = "CODE"; - semantics = ReadOnlyCodeSectionSemantics; - } - else - { - type = "REGULAR"; - } - break; - case S_ZEROFILL: - type = "ZEROFILL"; - semantics = ReadWriteDataSectionSemantics; - break; - case S_CSTRING_LITERALS: - type = "CSTRING_LITERALS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_4BYTE_LITERALS: - type = "4BYTE_LITERALS"; - break; - case S_8BYTE_LITERALS: - type = "8BYTE_LITERALS"; - break; - case S_LITERAL_POINTERS: - type = "LITERAL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_NON_LAZY_SYMBOL_POINTERS: - type = "NON_LAZY_SYMBOL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_LAZY_SYMBOL_POINTERS: - type = "LAZY_SYMBOL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_SYMBOL_STUBS: - type = "SYMBOL_STUBS"; - semantics = ReadOnlyCodeSectionSemantics; - break; - case S_MOD_INIT_FUNC_POINTERS: - type = "MOD_INIT_FUNC_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_MOD_TERM_FUNC_POINTERS: - type = "MOD_TERM_FUNC_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_COALESCED: - type = "COALESCED"; - break; - case S_GB_ZEROFILL: - type = "GB_ZEROFILL"; - semantics = ReadWriteDataSectionSemantics; - break; - case S_INTERPOSING: - type = "INTERPOSING"; - break; - case S_16BYTE_LITERALS: - type = "16BYTE_LITERALS"; - break; - case S_DTRACE_DOF: - type = "DTRACE_DOF"; - break; - case S_LAZY_DYLIB_SYMBOL_POINTERS: - type = "LAZY_DYLIB_SYMBOL_POINTERS"; - semantics = ReadOnlyDataSectionSemantics; - break; - case S_THREAD_LOCAL_REGULAR: - type = "THREAD_LOCAL_REGULAR"; - break; - case S_THREAD_LOCAL_ZEROFILL: - type = "THREAD_LOCAL_ZEROFILL"; - break; - case S_THREAD_LOCAL_VARIABLES: - type = "THREAD_LOCAL_VARIABLES"; - break; - case S_THREAD_LOCAL_VARIABLE_POINTERS: - type = "THREAD_LOCAL_VARIABLE_POINTERS"; - break; - case S_THREAD_LOCAL_INIT_FUNCTION_POINTERS: - type = "THREAD_LOCAL_INIT_FUNCTION_POINTERS"; - break; - default: - type = "UNKNOWN"; - break; - } - if (i >= header.sectionNames.size()) - break; - if (strncmp(header.sections[i].sectname, "__text", sizeof(header.sections[i].sectname)) == 0) - semantics = ReadOnlyCodeSectionSemantics; - if (strncmp(header.sections[i].sectname, "__const", sizeof(header.sections[i].sectname)) == 0) - semantics = ReadOnlyDataSectionSemantics; - if (strncmp(header.sections[i].sectname, "__data", sizeof(header.sections[i].sectname)) == 0) - semantics = ReadWriteDataSectionSemantics; - if (strncmp(header.sections[i].segname, "__DATA_CONST", sizeof(header.sections[i].segname)) == 0) - semantics = ReadOnlyDataSectionSemantics; - - view->AddUserSection(header.sectionNames[i], header.sections[i].addr, header.sections[i].size, semantics, - type, header.sections[i].align); - } - - auto typeLib = view->GetTypeLibrary(header.installName); + for (const auto& [imagePath, imageInfo] : entry.GetImages()) + ProcessEntryImage(imagePath, imageInfo); +} - BinaryReader virtualReader(view); +// At this point all relevant mapping should be loaded in the virtual memory. +void SharedCache::ProcessEntryRegions(const CacheEntry& entry) +{ + auto entryHeader = entry.GetHeader(); - bool applyHeaderTypes = false; - for (const auto& region : regionsToLoad) + // Collect pool addresses as non image memory regions. + for (size_t i = 0; i < entryHeader.branchPoolsCount; i++) { - if (header.textBase >= region->start && header.textBase < region->start + region->size) - { - if (!MemoryRegionIsHeaderInitialized(lock, *region)) - applyHeaderTypes = true; - + auto branchPoolAddr = entryHeader.branchPoolsOffset + (i * m_addressSize); + auto header = SharedCacheMachOHeader::ParseHeaderForAddress( + m_vm, branchPoolAddr, "dyld_shared_cache_branch_islands_" + std::to_string(i)); + // Stop processing branch pools if a header fails to parse. + if (!header.has_value()) break; - } - } - if (applyHeaderTypes) - { - view->DefineDataVariable(header.textBase, Type::NamedType(view, QualifiedName("mach_header_64"))); - view->DefineAutoSymbol( - new Symbol(DataSymbol, "__macho_header::" + header.identifierPrefix, header.textBase, LocalBinding)); - try + // Gather all non image regions from the branch islands. + for (const auto& segment : header->segments) { - virtualReader.Seek(header.textBase + sizeof(mach_header_64)); - size_t sectionNum = 0; - for (size_t i = 0; i < header.ident.ncmds; i++) - { - load_command load; - uint64_t curOffset = virtualReader.GetOffset(); - load.cmd = virtualReader.Read32(); - load.cmdsize = virtualReader.Read32(); - uint64_t nextOffset = curOffset + load.cmdsize; - switch (load.cmd) - { - case LC_SEGMENT: - { - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("segment_command"))); - virtualReader.SeekRelative(5 * 8); - size_t numSections = virtualReader.Read32(); - virtualReader.SeekRelative(4); - for (size_t j = 0; j < numSections; j++) - { - view->DefineDataVariable( - virtualReader.GetOffset(), Type::NamedType(view, QualifiedName("section"))); - view->DefineUserSymbol(new Symbol(DataSymbol, - "__macho_section::" + header.identifierPrefix + "_[" + std::to_string(sectionNum++) + "]", - virtualReader.GetOffset(), LocalBinding)); - virtualReader.SeekRelative((8 * 8) + 4); - } - break; - } - case LC_SEGMENT_64: - { - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("segment_command_64"))); - virtualReader.SeekRelative(7 * 8); - size_t numSections = virtualReader.Read32(); - virtualReader.SeekRelative(4); - for (size_t j = 0; j < numSections; j++) - { - view->DefineDataVariable( - virtualReader.GetOffset(), Type::NamedType(view, QualifiedName("section_64"))); - view->DefineUserSymbol(new Symbol(DataSymbol, - "__macho_section_64::" + header.identifierPrefix + "_[" + std::to_string(sectionNum++) + "]", - virtualReader.GetOffset(), LocalBinding)); - virtualReader.SeekRelative(10 * 8); - } - break; - } - case LC_SYMTAB: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("symtab"))); - break; - case LC_DYSYMTAB: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dysymtab"))); - break; - case LC_UUID: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("uuid"))); - break; - case LC_ID_DYLIB: - case LC_LOAD_DYLIB: - case LC_REEXPORT_DYLIB: - case LC_LOAD_WEAK_DYLIB: - case LC_LOAD_UPWARD_DYLIB: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dylib_command"))); - if (load.cmdsize - 24 <= 150) - view->DefineDataVariable( - curOffset + 24, Type::ArrayType(Type::IntegerType(1, true), load.cmdsize - 24)); - break; - case LC_CODE_SIGNATURE: - case LC_SEGMENT_SPLIT_INFO: - case LC_FUNCTION_STARTS: - case LC_DATA_IN_CODE: - case LC_DYLIB_CODE_SIGN_DRS: - case LC_DYLD_EXPORTS_TRIE: - case LC_DYLD_CHAINED_FIXUPS: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("linkedit_data"))); - break; - case LC_ENCRYPTION_INFO: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("encryption_info"))); - break; - case LC_VERSION_MIN_MACOSX: - case LC_VERSION_MIN_IPHONEOS: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("version_min"))); - break; - case LC_DYLD_INFO: - case LC_DYLD_INFO_ONLY: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("dyld_info"))); - break; - default: - view->DefineDataVariable(curOffset, Type::NamedType(view, QualifiedName("load_command"))); - break; - } + CacheRegion stubIslandRegion; + stubIslandRegion.start = segment.vmaddr; + stubIslandRegion.size = segment.filesize; + char segName[17]; + memcpy(segName, segment.segname, 16); + segName[16] = 0; + std::string segNameStr = std::string(segName); + stubIslandRegion.name = fmt::format("dyld_shared_cache_branch_islands_{}::{}", i, segNameStr); + stubIslandRegion.flags = static_cast<BNSegmentFlag>(SegmentReadable | SegmentExecutable); + stubIslandRegion.type = CacheRegionType::StubIsland; - view->DefineAutoSymbol(new Symbol(DataSymbol, - "__macho_load_command::" + header.identifierPrefix + "_[" + std::to_string(i) + "]", curOffset, - LocalBinding)); - virtualReader.Seek(nextOffset); - } - } - catch (ReadException&) - { - LogError("Error when applying Mach-O header types at %" PRIx64, header.textBase); - } - } - - if (applyFunctionStarts && header.functionStartsPresent && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) - { - auto funcStarts = - vm->MappingAtAddress(header.linkeditSegment.vmaddr) - .first.fileAccessor->lock() - ->ReadBuffer(header.functionStarts.funcoff, header.functionStarts.funcsize); - uint64_t curfunc = header.textBase; - uint64_t curOffset; - - auto current = static_cast<const uint8_t*>(funcStarts.GetData()); - auto end = current + funcStarts.GetLength(); - while (current != end) - { - curOffset = readLEB128(current, end); - bool addFunction = false; - for (const auto& region : regionsToLoad) - { - if (curfunc >= region->start && curfunc < region->start + region->size) - { - if (!MemoryRegionIsHeaderInitialized(lock, *region)) - addFunction = true; - } - } - // LogError("0x%llx, 0x%llx", header.textBase, curOffset); - if (curOffset == 0 || !addFunction) - continue; - curfunc += curOffset; - uint64_t target = curfunc; - Ref<Platform> targetPlatform = view->GetDefaultPlatform(); - view->AddFunctionForAnalysis(targetPlatform, target); + // Add the stub islands to the cache. + AddRegion(std::move(stubIslandRegion)); } } - if (header.symtab.symoff != 0 && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) - { - // Mach-O View symtab processing with - // a ton of stuff cut out so it can work - auto reader = vm->MappingAtAddress(header.linkeditSegment.vmaddr).first.fileAccessor->lock(); - ProcessSymbols( - reader, - header, - header.symtab.stroff, - header.symtab.strsize, - header.symtab.symoff, - header.symtab.nsyms - ); - } - - view->BeginBulkModifySymbols(); - for (const auto& symbol : *m_modifiedState->symbolInfos[header.textBase]) - ApplySymbol(view, typeLib, symbol); + // Get the mapping. + const auto& entryMappings = entry.GetMappings(); - if (header.exportTriePresent && header.linkeditPresent && vm->AddressIsMapped(header.linkeditSegment.vmaddr)) + // Add the mapping regions for the given entry type. + // By default, we will just add all the mappings as read-write. + switch (entry.GetType()) { - auto symbols = GetExportListForHeader(lock, header, [&]() { - return vm->MappingAtAddress(header.linkeditSegment.vmaddr).first.fileAccessor->lock(); - }); - - for (const auto& [symbolAddress, symbol] : *symbols) - ApplySymbol(view, typeLib, symbol); - } - view->EndBulkModifySymbols(); - - for (auto region : regionsToLoad) - { - SetMemoryRegionHeaderInitialized(lock, *region); - } -} - - -void SharedCache::ReadExportNode(std::vector<Ref<Symbol>>& symbolList, const SharedCacheMachOHeader& header, - const uint8_t* begin, const uint8_t* end, const uint8_t* current, uint64_t textBase, const std::string& currentText) -{ - if (current >= end) - throw ReadException(); - - uint64_t terminalSize = readValidULEB128(current, end); - const uint8_t* child = current + terminalSize; - if (terminalSize != 0) + case CacheEntryType::DyldData: { - uint64_t flags = readValidULEB128(current, end); - if (!(flags & EXPORT_SYMBOL_FLAGS_REEXPORT)) + size_t lastMappingIndex = 0; + for (const auto& mapping : entryMappings) { - uint64_t imageOffset = readValidULEB128(current, end); - if (!currentText.empty() && textBase + imageOffset) - { - uint32_t flags; - BNSymbolType type; - for (auto s : header.sections) - { - if (s.addr < textBase + imageOffset) - { - if (s.addr + s.size > textBase + imageOffset) - { - flags = s.flags; - break; - } - } - } - if ((flags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS - || (flags & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) - type = FunctionSymbol; - else - type = DataSymbol; + CacheRegion mappingRegion; + mappingRegion.start = mapping.address; + mappingRegion.size = mapping.size; + mappingRegion.name = fmt::format("{}::_data_{}", entry.GetFileName(), lastMappingIndex++); + mappingRegion.flags = SegmentReadable; + mappingRegion.type = CacheRegionType::DyldData; -#if EXPORT_TRIE_DEBUG - // BNLogInfo("export: %s -> 0x%llx", n.text.c_str(), image.baseAddress + n.offset); -#endif - auto symbol = new Symbol(type, currentText, textBase + imageOffset, nullptr); - symbolList.emplace_back(symbol); - } + // Add the dyld data mapping as a region to the cache. + AddRegion(std::move(mappingRegion)); } + break; } - current = child; - uint8_t childCount = *current++; - std::string childText = currentText; - for (uint8_t i = 0; i < childCount; ++i) - { - if (current >= end) - throw ReadException(); - auto it = std::find(current, end, 0); - childText.append(current, it); - current = it + 1; - if (current >= end) - throw ReadException(); - auto next = readValidULEB128(current, end); - if (next == 0) - throw ReadException(); - ReadExportNode(symbolList, header, begin, end, begin + next, textBase, childText); - childText.resize(currentText.size()); - } -} - - -std::vector<Ref<Symbol>> SharedCache::ParseExportTrie(std::shared_ptr<MMappedFileAccessor> linkeditFile, const SharedCacheMachOHeader& header) -{ - if (!header.exportTrie.datasize) - return {}; - - try - { - std::vector<Ref<Symbol>> symbols; - auto [begin, end] = linkeditFile->ReadSpan(header.exportTrie.dataoff, header.exportTrie.datasize); - ReadExportNode(symbols, header, begin, end, begin, header.textBase, ""); - return symbols; - } - catch (std::exception& e) - { - BNLogError("Failed to load Export Trie"); - return {}; - } -} - -std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> SharedCache::GetExistingExportListForBaseAddress(std::lock_guard<std::mutex>&, uint64_t baseAddress) const { - if (auto it = m_modifiedState->exportInfos.find(baseAddress); it != m_modifiedState->exportInfos.end()) - return it->second; - - std::lock_guard viewSpecificStateLock(m_viewSpecificState->stateMutex); - if (auto it = m_viewSpecificState->state.exportInfos.find(baseAddress); it != m_viewSpecificState->state.exportInfos.end()) - return it->second; - - return nullptr; -} - - -std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> SharedCache::GetExportListForHeader( - std::lock_guard<std::mutex>& lock, const SharedCacheMachOHeader& header, - std::function<std::shared_ptr<MMappedFileAccessor>()> provideLinkeditFile, bool* didModifyExportList) -{ - if (auto exportList = GetExistingExportListForBaseAddress(lock, header.textBase)) - { - if (didModifyExportList) - *didModifyExportList = false; - - return exportList; - } - - std::shared_ptr<MMappedFileAccessor> linkeditFile = provideLinkeditFile(); - if (!linkeditFile) + case CacheEntryType::Stub: { - if (didModifyExportList) - *didModifyExportList = false; - - return nullptr; - } + // Stub entry file, should only have a single mapping and no images. + auto stubMapping = entryMappings[0]; + CacheRegion stubIslandRegion; + stubIslandRegion.start = stubMapping.address; + stubIslandRegion.size = stubMapping.size; + stubIslandRegion.name = fmt::format("{}::_stubs", entry.GetFileName()); + stubIslandRegion.flags = static_cast<BNSegmentFlag>(SegmentReadable | SegmentExecutable); + stubIslandRegion.type = CacheRegionType::StubIsland; - // FIXME: This is the only place ParseExportTrie is used, it can be optimized for the output we need here. - std::vector<Ref<Symbol>> exportList = SharedCache::ParseExportTrie(linkeditFile, header); - auto exportMapping = std::make_shared<std::unordered_map<uint64_t, Ref<Symbol>>>(exportList.size()); - for (auto& sym : exportList) - { - exportMapping->insert_or_assign(sym->GetAddress(), std::move(sym)); + // Add the stub island to the cache. + AddRegion(std::move(stubIslandRegion)); } - - m_modifiedState->exportInfos.emplace(header.textBase, exportMapping); - if (didModifyExportList) - *didModifyExportList = true; - - return exportMapping; -} - - -std::vector<std::string> SharedCache::GetAvailableImages() -{ - std::vector<std::string> installNames; - installNames.reserve(m_cacheInfo->headers.size()); - for (const auto& header : m_cacheInfo->headers) + default: { - installNames.push_back(header.second.installName); - } - return installNames; -} - - -std::unordered_map<std::string, std::vector<Ref<Symbol>>> SharedCache::LoadAllSymbolsAndWait() -{ - std::lock(m_mutex, m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); - std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex, std::adopt_lock); - std::lock_guard lock(m_mutex, std::adopt_lock); - - bool doSave = false; - std::unordered_map<std::string, std::vector<Ref<Symbol>>> symbolsByImageName(m_cacheInfo->images.size()); - - for (const auto& img : m_cacheInfo->images) - { - auto header = HeaderForAddress(img.headerLocation); - auto exportList = GetExportListForHeader(lock, *header, [&]() { - try { - return MapFile(header->exportTriePath); - } - catch (...) - { - m_logger->LogWarn("Serious Error: Failed to open export trie %s for %s", - header->exportTriePath.c_str(), - header->installName.c_str()); - return std::shared_ptr<MMappedFileAccessor>(nullptr); - } - }, &doSave); - - if (!exportList) - continue; - - auto& symbols = symbolsByImageName[img.installName]; - symbols.reserve(exportList->size()); - for (const auto& [_, symbol] : *exportList) + // Fill in all the gaps in the mapping with non image regions. + size_t lastMappingIndex = 0; + for (const auto& mapping : entryMappings) { - symbols.push_back(symbol); + // Add the remaining gap. + CacheRegion nonImageRegion; + nonImageRegion.start = mapping.address; + nonImageRegion.size = mapping.size; + nonImageRegion.name = fmt::format("{}::{}", entry.GetFileName(), lastMappingIndex++); + nonImageRegion.flags = static_cast<BNSegmentFlag>(SegmentReadable | SegmentWritable); + nonImageRegion.type = CacheRegionType::NonImage; + AddRegion(std::move(nonImageRegion)); } + break; } - - // Only save to DSC view if a header was actually loaded - if (doSave) - SaveModifiedStateToDSCView(lock); - - return symbolsByImageName; -} - - -std::string SharedCache::SerializedImageHeaderForAddress(uint64_t address) -{ - auto header = HeaderForAddress(address); - if (header) - { - return header->AsString(); - } - return ""; -} - - -std::string SharedCache::SerializedImageHeaderForName(std::string name) -{ - if (auto it = m_cacheInfo->imageStarts.find(name); it != m_cacheInfo->imageStarts.end()) - { - if (auto header = HeaderForAddress(it->second)) - return header->AsString(); - } - return ""; -} - -Ref<TypeLibrary> SharedCache::TypeLibraryForImage(const std::string& installName) -{ - std::lock_guard lock(m_viewSpecificState->typeLibraryMutex); - if (auto it = m_viewSpecificState->typeLibraries.find(installName); it != m_viewSpecificState->typeLibraries.end()) - return it->second; - - auto typeLib = m_dscView->GetTypeLibrary(installName); - if (!typeLib) - { - auto typeLibs = m_dscView->GetDefaultPlatform()->GetTypeLibrariesByName(installName); - if (!typeLibs.empty()) - { - typeLib = typeLibs[0]; - m_dscView->AddTypeLibrary(typeLib); - } } - - m_viewSpecificState->typeLibraries[installName] = typeLib; - return typeLib; } -void SharedCache::FindSymbolAtAddrAndApplyToAddr( - uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) +void SharedCache::ProcessEntrySlideInfo(const CacheEntry& entry) { - std::lock_guard lock(m_mutex); - - std::string prefix = ""; - if (symbolLocation != targetLocation) - prefix = "j_"; - - if (auto targetSymbol = m_dscView->GetSymbolByAddress(targetLocation)) - { - // A symbol already exists at the target location. If the source and target address are the same, - // there's nothing more to do. If they're different but the symbol has the `j_` prefix that is added - // to stubs, there's also nothing more to do. - if (symbolLocation == targetLocation || targetSymbol->GetFullName().find("j_") != std::string::npos) - return; - } - - if (symbolLocation != targetLocation) - { - if (auto symbol = m_dscView->GetSymbolByAddress(symbolLocation)) - { - // A symbol already exists at the source location. Add a stub symbol at `targetLocation` based on the existing symbol. - auto id = m_dscView->BeginUndoActions(); - if (m_dscView->GetAnalysisFunction(m_dscView->GetDefaultPlatform(), targetLocation)) - m_dscView->DefineUserSymbol(new Symbol(FunctionSymbol, prefix + symbol->GetFullName(), targetLocation)); - else - m_dscView->DefineUserSymbol(new Symbol(symbol->GetType(), prefix + symbol->GetFullName(), targetLocation)); - m_dscView->ForgetUndoActions(id); - return; - } - } - - // No existing symbol was found at `symbolLocation` or `targetLocation`. Search the export list - // for the image containing `symbolLocation` to find a symbol corresponding to that address. - - auto header = HeaderForAddress(symbolLocation); - if (!header) - return; - - auto exportList = GetExportListForHeader(lock, *header, [&]() { - try { - return MapFile(header->exportTriePath); - } catch (...) { - m_logger->LogWarn("Serious Error: Failed to open export trie %s for %s", header->exportTriePath.c_str(), header->installName.c_str()); - return std::shared_ptr<MMappedFileAccessor>(nullptr); - } - }); - - if (!exportList) - return; - - auto it = exportList->find(symbolLocation); - if (it == exportList->end()) - return; - - const auto& symbol = it->second; - auto id = m_dscView->BeginUndoActions(); - auto typeLib = TypeLibraryForImage(header->installName); - auto type = typeLib ? m_dscView->ImportTypeLibraryObject(typeLib, {symbol->GetFullName()}) : nullptr; - - if (auto func = m_dscView->GetAnalysisFunction(m_dscView->GetDefaultPlatform(), targetLocation)) - { - m_dscView->DefineUserSymbol( - new Symbol(FunctionSymbol, prefix + symbol->GetFullName(), targetLocation)); - if (type) - func->SetUserType(type); - if (triggerReanalysis) - func->Reanalyze(); - } - else - { - m_dscView->DefineUserSymbol( - new Symbol(symbol->GetType(), prefix + symbol->GetFullName(), targetLocation)); - if (type) - m_dscView->DefineUserDataVariable(targetLocation, type); - } - - m_dscView->ForgetUndoActions(id); + auto slideInfoProcessor = SlideInfoProcessor(GetBaseAddress()); + slideInfoProcessor.ProcessEntry(*m_vm, entry); } - -bool SharedCache::SaveCacheInfoToDSCView(std::lock_guard<std::mutex>&) +std::optional<CacheEntry> SharedCache::GetEntryContaining(const uint64_t address) const { - if (!m_dscView) - return false; - - // The initial load should only populate `m_cacheInfo` and should not modify any state. - assert(m_modifiedState->exportInfos.size() == 0); - assert(m_modifiedState->symbolInfos.size() == 0); - assert(m_modifiedState->memoryRegionStatus.size() == 0); - - auto data = m_cacheInfo->AsMetadata(); - m_dscView->StoreMetadata(SharedCacheMetadata::Tag, data); - + for (const auto& [_, entry] : m_entries) { - std::lock_guard lock(m_viewSpecificState->cacheInfoMutex); - if (m_cacheInfo) + for (const auto& mapping : entry.GetMappings()) { - if (!m_viewSpecificState->cacheInfo) - m_viewSpecificState->cacheInfo = m_cacheInfo; - - // At this point we expect cache info and view state cache to be the same. - assert(m_viewSpecificState->cacheInfo == m_cacheInfo); + if (address >= mapping.address && address < mapping.address + mapping.size) + return entry; } } - m_metadataValid = true; - return true; + return std::nullopt; } -bool SharedCache::SaveModifiedStateToDSCView(std::lock_guard<std::mutex>&) +std::optional<CacheEntry> SharedCache::GetEntryWithImage(const CacheImage& image) const { - if (!m_dscView) - return false; - + for (const auto& [_, entry] : m_entries) { - std::lock_guard lock(m_viewSpecificState->stateMutex); - - uint64_t modificationNumber = m_viewSpecificState->savedModifications++; - if (modificationNumber == 0) - { - // The cached state in the view-specific state has not yet been saved. - // For the initial load of a shared cache this will be empty, but if - // the shared cache has been loaded from a database then this will - // contain the full state that was saved. - std::string metadataKey = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(modificationNumber); - auto data = m_viewSpecificState->state.AsMetadata(m_viewSpecificState->viewState); - - m_dscView->StoreMetadata(metadataKey, data); - modificationNumber = m_viewSpecificState->savedModifications++; - } - - std::string metadataKey = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(modificationNumber); - auto data = m_modifiedState->AsMetadata(); - - m_dscView->StoreMetadata(metadataKey, data); - - Ref<Metadata> count = new Metadata(m_viewSpecificState->savedModifications); - m_dscView->StoreMetadata(SharedCacheMetadata::ModifiedStateCountTag, count); - - m_viewSpecificState->state.exportInfos.merge(m_modifiedState->exportInfos); - m_viewSpecificState->state.symbolInfos.merge(m_modifiedState->symbolInfos); - // `merge` will move a node to the target map if the corresponding key does not yet exist. - // If we've redundantly loaded symbols, we may be left with symbols in the source maps. - m_modifiedState->exportInfos.clear(); - m_modifiedState->symbolInfos.clear(); - - for (auto& [region, status] : m_modifiedState->memoryRegionStatus) + for (const auto& [_, currentImage] : entry.GetImages()) { - m_viewSpecificState->state.memoryRegionStatus[region] = status; + if (currentImage.address == image.headerAddress) + return entry; } - m_modifiedState->memoryRegionStatus.clear(); - - // Clean up any metadata entries past the current modification number. - // These can happen after being loaded from a database as all modifications are - // merged into a single state object and the modification count is reset to zero. - for (size_t i = modificationNumber + 1; i < std::numeric_limits<size_t>::max(); ++i) - { - std::string metadataKey = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(i); - bool done = true; - if (m_dscView->QueryMetadata(metadataKey)) - { - done = false; - m_dscView->RemoveMetadata(metadataKey); - } - if (m_dscView->GetParentView()->QueryMetadata(metadataKey)) - { - done = false; - m_dscView->GetParentView()->RemoveMetadata(metadataKey); - } - if (done) - break; - } - } - - if (m_modifiedState->viewState) - { - m_viewSpecificState->viewState = m_modifiedState->viewState.value(); - m_modifiedState->viewState = std::nullopt; } - m_metadataValid = true; - - return true; -} - - -std::vector<const MemoryRegion*> SharedCache::GetMappedRegions() const -{ - std::scoped_lock lock(m_mutex, m_viewSpecificState->stateMutex); - - std::vector<const MemoryRegion*> regions; - regions.reserve(m_viewSpecificState->state.memoryRegionStatus.size() + m_modifiedState->memoryRegionStatus.size()); - for (auto& [regionStart, status] : m_viewSpecificState->state.memoryRegionStatus) - { - if (status.loaded) - { - const auto* region = &m_cacheInfo->memoryRegions.find(regionStart)->second; - regions.push_back(region); - } - } - for (auto& [regionStart, status] : m_modifiedState->memoryRegionStatus) - { - if (status.loaded) - { - const auto* region = &m_cacheInfo->memoryRegions.find(regionStart)->second; - regions.push_back(region); - } - } - std::sort(regions.begin(), regions.end()); - regions.erase(std::unique(regions.begin(), regions.end()), regions.end()); - return regions; -} - -bool SharedCache::IsMemoryMapped(uint64_t address) -{ - return m_dscView->IsValidOffset(address); -} - -void Serialize(SerializationContext& context, const dyld_cache_mapping_info& value) -{ - context.writer.StartArray(); - Serialize(context, value.address); - Serialize(context, value.size); - Serialize(context, value.fileOffset); - Serialize(context, value.maxProt); - Serialize(context, value.initProt); - context.writer.EndArray(); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<dyld_cache_mapping_info>& b) -{ - auto bArr = context.doc[name.data()].GetArray(); - for (auto& s : bArr) - { - dyld_cache_mapping_info mapping; - auto s2 = s.GetArray(); - mapping.address = s2[0].GetUint64(); - mapping.size = s2[1].GetUint64(); - mapping.fileOffset = s2[2].GetUint64(); - mapping.maxProt = s2[3].GetUint(); - mapping.initProt = s2[4].GetUint(); - b.push_back(mapping); - } -} - -void Deserialize( - DeserializationContext& context, std::string_view name, std::optional<std::pair<uint64_t, uint64_t>>& value) -{ - if (!context.doc.HasMember(name.data())) - { - value = std::nullopt; - return; - } - - auto array = context.doc[name.data()].GetArray(); - value = {array[0].GetUint64(), array[1].GetUint64()}; -} - -void Serialize(SerializationContext& context, const AddressRange& value) -{ - Serialize(context, std::make_pair(value.start, value.end)); -} - -void Deserialize(DeserializationContext& context, std::string_view name, AddressRange& value) -{ - auto array = context.doc[name.data()].GetArray(); - value = {array[0].GetUint64(), array[1].GetUint64()}; -} - -void Serialize(SerializationContext& context, const MemoryRegionStatus& status) -{ - context.writer.StartArray(); - Serialize(context, status.loaded); - Serialize(context, status.headerInitialized); - context.writer.EndArray(); -} - -void Deserialize( - DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, MemoryRegionStatus>& statuses) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& pair : array) - { - auto statusArray = pair[1].GetArray(); - MemoryRegionStatus status; - status.loaded = statusArray[0].GetBool(); - status.headerInitialized = statusArray[1].GetBool(); - statuses[pair[0].GetUint64()] = std::move(status); - } -} - -void Serialize(SerializationContext& context, const Ref<Symbol>& value) -{ - context.writer.StartArray(); - Serialize(context, value->GetRawNameRef()); - Serialize(context, value->GetAddress()); - Serialize(context, value->GetType()); - context.writer.EndArray(); -} - -void Serialize(SerializationContext& context, const std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>& value) -{ - context.writer.StartArray(); - for (const auto& [_, symbol] : *value) - { - Serialize(context, symbol); - } - context.writer.EndArray(); -} - -void Serialize(SerializationContext& context, const std::shared_ptr<std::vector<Ref<Symbol>>>& value) -{ - Serialize(context, *value); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::optional<DSCViewState>& viewState) -{ - auto& value = context.doc[name.data()]; - if (value.IsNull()) - viewState = std::nullopt; - else - viewState = (DSCViewState)value.GetUint(); -} - -void SharedCache::CacheInfo::Store(SerializationContext& context) const -{ - Serialize(context, "metadataVersion", METADATA_VERSION); - - MSS(backingCaches); - MSS(headers); - MSS(images); - MSS(imageStarts); - MSS(memoryRegions); - MSS(objcOptimizationDataRange); - MSS(baseFilePath); - MSS_CAST(cacheFormat, uint8_t); + return std::nullopt; } -// static -std::optional<SharedCache::CacheInfo> SharedCache::CacheInfo::Load(DeserializationContext& context) +std::optional<CacheRegion> SharedCache::GetRegionAt(const uint64_t address) const { - if (!context.doc.HasMember("metadataVersion")) - { - LogError("Shared Cache metadata version missing"); + const auto it = m_regions.find(address); + if (it == m_regions.end()) return std::nullopt; - } - - if (context.doc["metadataVersion"].GetUint() != METADATA_VERSION) - return std::nullopt; - - CacheInfo cacheInfo; - cacheInfo.MSL(backingCaches); - cacheInfo.MSL(headers); - cacheInfo.MSL(images); - cacheInfo.MSL(imageStarts); - cacheInfo.MSL(memoryRegions); - cacheInfo.MSL(objcOptimizationDataRange); - cacheInfo.MSL(baseFilePath); - cacheInfo.MSL_CAST(cacheFormat, uint8_t, SharedCacheFormat); - - // Older metadata may be missing the `imageStart` field on `MemoryRegion`. - bool regionsMissingImageStart = - std::any_of(cacheInfo.memoryRegions.begin(), cacheInfo.memoryRegions.end(), [](const auto& pair) { - const auto& region = pair.second; - return region.type == MemoryRegion::Type::Image && region.imageStart == 0; - }); - - if (regionsMissingImageStart) - { - for (const auto& [start, header] : cacheInfo.headers) - { - for (const auto& segment : header.segments) - { - const auto regionIt = cacheInfo.memoryRegions.find(segment.vmaddr); - assert(regionIt != cacheInfo.memoryRegions.end()); - auto& region = regionIt->second; - assert(!region.imageStart || region.imageStart == start); - region.imageStart = start; - } - } - } - - return cacheInfo; -} -void State::Store(SerializationContext& context, std::optional<DSCViewState> viewState) const -{ - MSS(memoryRegionStatus); - MSS(viewState); + return it->second; } -void SharedCache::ModifiedState::Store(SerializationContext& context) const +std::optional<CacheRegion> SharedCache::GetRegionContaining(const uint64_t address) const { - State::Store(context, viewState); -} - -SharedCache::ModifiedState SharedCache::ModifiedState::Load(DeserializationContext& context) -{ - SharedCache::ModifiedState state; - state.MSL(memoryRegionStatus); - state.MSL(viewState); - return state; -} - -SharedCache::ModifiedState SharedCache::ModifiedState::LoadAll(BinaryNinja::BinaryView *dscView, const CacheInfo& cacheInfo) -{ - uint64_t stateCount = dscView->GetUIntMetadata(SharedCacheMetadata::ModifiedStateCountTag); - SharedCache::ModifiedState state; - for (uint64_t i = 0; i < stateCount; ++i) - { - std::string key = SharedCacheMetadata::ModifiedStateTagPrefix + std::to_string(i); - std::string serialized = dscView->GetStringMetadata(key); - auto thisState = SharedCache::ModifiedState::LoadFromString(serialized); - state.Merge(std::move(thisState)); - } - return state; -} - -void SharedCache::ModifiedState::Merge(SharedCache::ModifiedState&& newer) -{ - memoryRegionStatus.merge(newer.memoryRegionStatus); - exportInfos.merge(newer.exportInfos); - symbolInfos.merge(newer.symbolInfos); - - if (newer.viewState) - viewState = newer.viewState; -} - -void BackingCache::Store(SerializationContext& context) const -{ - MSS(path); - MSS_CAST(cacheType, uint32_t); - MSS(mappings); -} - -BackingCache BackingCache::Load(DeserializationContext& context) -{ - BackingCache cache; - cache.MSL(path); - cache.MSL_CAST(cacheType, uint32_t, BNBackingCacheType); - cache.MSL(mappings); - return cache; -} - -void CacheImage::Store(SerializationContext& context) const -{ - MSS(installName); - MSS(headerLocation); - MSS(regionStarts); -} - -// static -CacheImage CacheImage::Load(DeserializationContext& context) -{ - CacheImage cacheImage; - cacheImage.MSL(installName); - cacheImage.MSL(headerLocation); - cacheImage.MSL(regionStarts); - return cacheImage; -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<BackingCache>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& value: array) - b.push_back(BackingCache::LoadFromValue(value)); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::vector<CacheImage>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& value: array) - b.push_back(CacheImage::LoadFromValue(value)); -} - -void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, SharedCacheMachOHeader>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& pair_value : array) - { - auto pair = pair_value.GetArray(); - b[pair[0].GetUint64()] = SharedCacheMachOHeader::LoadFromValue(pair[1]); - } -} - -void Deserialize(DeserializationContext& context, std::string_view name, AddressRangeMap<MemoryRegion>& b) -{ - auto array = context.doc[name.data()].GetArray(); - for (auto& key_value : array) - { - auto key_value_pair = key_value.GetArray(); - auto key_pair = key_value_pair[0].GetArray(); - AddressRange key = {key_pair[0].GetUint64(), key_pair[1].GetUint64()}; - b[key] = MemoryRegion::LoadFromValue(key_value_pair[1]); - } -} - -const std::vector<BackingCache>& SharedCache::BackingCaches() const -{ - return m_cacheInfo->backingCaches; -} - -DSCViewState SharedCache::ViewState() const { - { - std::lock_guard lock(m_mutex); - if (auto& viewState = m_modifiedState->viewState) - return *viewState; - } - - return m_viewSpecificState->viewState; -} - -const std::unordered_map<std::string, uint64_t>& SharedCache::AllImageStarts() const -{ - return m_cacheInfo->imageStarts; -} - -const std::unordered_map<uint64_t, SharedCacheMachOHeader>& SharedCache::AllImageHeaders() const -{ - return m_cacheInfo->headers; -} - -uint64_t SharedCache::CacheInfo::BaseAddress() const -{ - uint64_t base = std::numeric_limits<uint64_t>::max(); - for (const auto& backingCache : backingCaches) - { - for (const auto& mapping : backingCache.mappings) - { - if (mapping.address < base) - { - base = mapping.address; - break; - } - } - } - return base; -} - -// Intentionally takes a copy to avoid modifying the cursor position in the original reader. -std::optional<ObjCOptimizationHeader> SharedCache::GetObjCOptimizationHeader(VMReader reader) const -{ - if (!m_cacheInfo->objcOptimizationDataRange) - return {}; - - ObjCOptimizationHeader header{}; - // Ignoring `objcOptsSize` in favor of `sizeof(ObjCOptimizationHeader)` matches dyld's behavior. - reader.Read(&header, m_cacheInfo->BaseAddress() + m_cacheInfo->objcOptimizationDataRange->first, sizeof(ObjCOptimizationHeader)); - - return header; -} - -uint64_t SharedCache::GetObjCRelativeMethodBaseAddress(const VMReader& reader) const -{ - if (auto header = GetObjCOptimizationHeader(reader); header.has_value()) - return m_cacheInfo->BaseAddress() + header->relativeMethodSelectorBaseAddressOffset; - return 0; + for (const auto& [range, region] : m_regions) + if (address >= range.start && address < range.end) + return region; + return std::nullopt; } -std::shared_ptr<MMappedFileAccessor> SharedCache::MapFile(const std::string& path) +std::optional<CacheImage> SharedCache::GetImageAt(const uint64_t address) const { - uint64_t baseAddress = m_cacheInfo->BaseAddress(); - return MMappedFileAccessor::Open(m_dscView->GetFile()->GetSessionId(), path, - [baseAddress, logger = m_logger](std::shared_ptr<MMappedFileAccessor> mmap) { - ParseAndApplySlideInfoForFile(mmap, baseAddress, logger); - }) - ->lock(); + const auto it = m_images.find(address); + if (it == m_images.end()) + return std::nullopt; + return it->second; } -std::shared_ptr<MMappedFileAccessor> SharedCache::MapFileWithoutApplyingSlide(const std::string& path) +std::optional<CacheImage> SharedCache::GetImageContaining(const uint64_t address) const { - return std::make_shared<MMappedFileAccessor>(path); + // TODO: What if we are using this on a shared region? Return a list of images? + auto region = GetRegionContaining(address); + if (region.has_value()) + return GetImageAt(*region->imageStart); + return std::nullopt; } -const std::string SharedCacheMetadata::Tag = "SHAREDCACHE-SharedCacheData"; -const std::string SharedCacheMetadata::CacheInfoTag = "SHAREDCACHE-CacheInfo"; -const std::string SharedCacheMetadata::ModifiedStateTagPrefix = "SHAREDCACHE-ModifiedState-"; -const std::string SharedCacheMetadata::ModifiedStateCountTag = "SHAREDCACHE-ModifiedState-Count"; - -SharedCacheMetadata::~SharedCacheMetadata() = default; -SharedCacheMetadata::SharedCacheMetadata(SharedCacheMetadata&&) = default; -SharedCacheMetadata& SharedCacheMetadata::operator=(SharedCacheMetadata&&) = default; - -SharedCacheMetadata::SharedCacheMetadata(SharedCache::CacheInfo cacheInfo, SharedCache::ModifiedState state) : - cacheInfo(std::make_unique<SharedCache::CacheInfo>(std::move(cacheInfo))), - state(std::make_unique<SharedCache::ModifiedState>(std::move(state))) -{} - - -// static -bool SharedCacheMetadata::ViewHasMetadata(BinaryView* view) +std::optional<CacheImage> SharedCache::GetImageWithName(const std::string& name) const { - return view->QueryMetadata(Tag); + for (const auto& [address, image] : m_images) + if (image.path == name) + return image; + return std::nullopt; } -std::optional<unsigned int> SharedCacheMetadata::ViewMetadataVersion(BinaryView* view) +std::optional<CacheSymbol> SharedCache::GetSymbolAt(uint64_t address) const { - // Whether the view has compatible metadata. I.e. if the version differs. - Ref<Metadata> viewMetadata = view->QueryMetadata(Tag); - if (!viewMetadata) - return std::nullopt; - DeserializationContext context; - rapidjson::ParseResult result = context.doc.Parse(viewMetadata->GetString().c_str()); - if (!result) - return std::nullopt; - if (!context.doc.HasMember("metadataVersion")) + const auto it = m_symbols.find(address); + if (it == m_symbols.end()) return std::nullopt; - return context.doc["metadataVersion"].GetUint(); + return it->second; } -// static -std::optional<SharedCacheMetadata> SharedCacheMetadata::LoadFromView(BinaryView* view) +std::optional<CacheSymbol> SharedCache::GetSymbolWithName(const std::string& name) const { - Ref<Metadata> viewMetadata = view->QueryMetadata(Tag); - if (!viewMetadata) - return std::nullopt; - - auto cacheInfo = SharedCache::CacheInfo::LoadFromString(viewMetadata->GetString()); - if (!cacheInfo) - return std::nullopt; - - auto modifiedState = SharedCache::ModifiedState::LoadAll(view, *cacheInfo); - return SharedCacheMetadata(std::move(*cacheInfo), std::move(modifiedState)); + for (const auto& [address, symbol] : m_symbols) + if (symbol.name == name) + return symbol; + return std::nullopt; } -const std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>>& SharedCacheMetadata::ExportInfos() const +CacheProcessor::CacheProcessor(Ref<BinaryView> view) { - return state->exportInfos; + m_view = std::move(view); + m_logger = new Logger("CacheProcessor", m_view->GetFile()->GetSessionId()); } -std::string SharedCacheMetadata::InstallNameForImageBaseAddress(uint64_t baseAddress) const +bool CacheProcessor::ProcessCache(SharedCache& cache) { - auto it = std::find_if(cacheInfo->imageStarts.begin(), cacheInfo->imageStarts.end(), [=](auto& pair) { - return pair.second == baseAddress; - }); - - if (it == cacheInfo->imageStarts.end()) - return ""; - - return it->first; -} - -} // namespace SharedCacheCore - -void InitDSCViewType() { - MMappedFileAccessor::InitialVMSetup(); - std::atexit(VMShutdown); - - static DSCViewType type; - BinaryViewType::Register(&type); + // If we are in a project, use the project cache processor. + if (m_view->GetFile()->GetProjectFile()) + return ProcessProjectCache(cache); + return ProcessFileCache(cache); } -extern "C" +bool CacheProcessor::ProcessFileCache(SharedCache& cache) { - BNSharedCache* BNGetSharedCache(BNBinaryView* data) - { - if (!data) - return nullptr; - - Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); - if (auto cache = SharedCache::GetFromDSCView(view)) - { - cache->AddAPIRef(); - return cache->GetAPIObject(); - } - - return nullptr; - } - - BNSharedCache* BNNewSharedCacheReference(BNSharedCache* cache) - { - if (!cache->object) - return nullptr; - - cache->object->AddAPIRef(); - return cache; - } + // We assume that the binary view location has all the files we need. + // If we ever want to allow users to override the shared cache file location + // we should really make a cache processor constructor with entry file paths. + std::string baseFilePath = m_view->GetFile()->GetOriginalFilename(); + std::string baseFileName = BaseFileName(baseFilePath); - void BNFreeSharedCacheReference(BNSharedCache* cache) - { - if (!cache->object) - return; - - cache->object->ReleaseAPIRef(); - } - - bool BNDSCViewLoadImageWithInstallName(BNSharedCache* cache, char* name, bool skipObjC) - { - std::string imageName = std::string(name); - BNFreeString(name); - - if (cache->object) - return cache->object->LoadImageWithInstallName(imageName, skipObjC); - - return false; - } - - bool BNDSCViewLoadSectionAtAddress(BNSharedCache* cache, uint64_t addr) + // Add this file to the entries + try { - if (cache->object) - { - return cache->object->LoadSectionAtAddress(addr); - } + auto baseEntry = CacheEntry::FromFile(baseFilePath, baseFileName, CacheEntryType::Primary); + if (!baseEntry.has_value()) + return false; - return false; + // Before we do anything else, add this to the cache so it's available to other entries. + cache.AddEntry(std::move(*baseEntry)); } - - bool BNDSCViewLoadImageContainingAddress(BNSharedCache* cache, uint64_t address, bool skipObjC) + catch (const std::exception& e) { - if (cache->object) - { - return cache->object->LoadImageContainingAddress(address, skipObjC); - } - + // Just return false so the view init can continue. return false; } - void BNDSCViewProcessObjCSectionsForImageWithInstallName(BNSharedCache* cache, char* name, bool deallocName) - { - std::string imageName = std::string(name); - if (deallocName) - BNFreeString(name); - - if (cache->object) - cache->object->ProcessObjCSectionsForImageWithInstallName(imageName); - } - - void BNDSCViewProcessAllObjCSections(BNSharedCache* cache) - { - if (cache->object) - cache->object->ProcessAllObjCSections(); - } - - char** BNDSCViewGetInstallNames(BNSharedCache* cache, size_t* count) - { - if (cache->object) - { - auto value = cache->object->GetAvailableImages(); - *count = value.size(); - - std::vector<const char*> cstrings; - cstrings.reserve(value.size()); - for (size_t i = 0; i < value.size(); i++) - { - cstrings.push_back(value[i].c_str()); - } - return BNAllocStringList(cstrings.data(), cstrings.size()); - } - *count = 0; - return nullptr; - } - - BNDSCSymbolRep* BNDSCViewLoadAllSymbolsAndWait(BNSharedCache* cache, size_t* count) + // Locate all possible related entry files and add them to the cache. + std::filesystem::path basePath = std::filesystem::path(baseFilePath).parent_path(); + for (const auto& entry : std::filesystem::directory_iterator(basePath)) { - if (cache->object) + if (!entry.is_regular_file()) + continue; + auto currentFilePath = entry.path().string(); + auto currentFileName = BaseFileName(currentFilePath); + // Skip our base file, obviously. + if (currentFilePath== baseFilePath) + continue; + // Filter files that don't contain the base file name i.e. "dyld_shared_cache_arm64e" + if (currentFilePath.find(baseFileName) == std::string::npos) + continue; + // Skip map files, they contain some nice information... we don't use. + if (entry.path().extension() == ".map") + continue; + try { - auto symbolsByImageName = cache->object->LoadAllSymbolsAndWait(); - size_t totalSymbolCount = 0; - for (const auto& [_, symbols] : symbolsByImageName) + auto additionalEntry = CacheEntry::FromFile(currentFilePath, currentFileName, CacheEntryType::Secondary); + if (!additionalEntry.has_value()) { - totalSymbolCount += symbols.size(); - } - *count = totalSymbolCount; - - BNDSCSymbolRep* outputSymbols = new BNDSCSymbolRep[totalSymbolCount]; - size_t i = 0; - for (const auto& [imageName, symbols] : symbolsByImageName) - { - for (const auto& symbol : symbols) - { - outputSymbols[i].address = symbol->GetAddress(); - outputSymbols[i].name = BNDuplicateStringRef(symbol->GetRawNameRef().GetObject()); - outputSymbols[i].image = BNAllocStringWithLength(imageName.c_str(), imageName.length()); - ++i; - } + m_logger->LogErrorF("Failed to load entry {}...", currentFileName); + continue; } - assert(i == totalSymbolCount); - return outputSymbols; - } - *count = 0; - return nullptr; - } - - void BNDSCViewFreeSymbols(BNDSCSymbolRep* symbols, size_t count) - { - for (size_t i = 0; i < count; i++) - { - BNFreeStringRef(symbols[i].name); - BNFreeString(symbols[i].image); - } - delete symbols; - } - - char* BNDSCViewGetNameForAddress(BNSharedCache* cache, uint64_t address) - { - if (cache->object) - { - return BNAllocString(cache->object->NameForAddress(address).c_str()); - } - - return nullptr; - } - - char* BNDSCViewGetImageNameForAddress(BNSharedCache* cache, uint64_t address) - { - if (cache->object) - { - return BNAllocString(cache->object->ImageNameForAddress(address).c_str()); - } - - return nullptr; - } - uint64_t BNDSCViewLoadedImageCount(BNSharedCache* cache) - { - // FIXME? - return 0; - } - - BNDSCViewState BNDSCViewGetState(BNSharedCache* cache) - { - if (cache->object) - { - return (BNDSCViewState)cache->object->ViewState(); + // Add this file as an entry to the cache + cache.AddEntry(std::move(*additionalEntry)); } - - return BNDSCViewState::Unloaded; + catch (const std::exception& e) + {} } + return true; +} - BNDSCMappedMemoryRegion* BNDSCViewGetLoadedRegions(BNSharedCache* cache, size_t* count) - { - if (cache->object) - { - auto regions = cache->object->GetMappedRegions(); - *count = regions.size(); - BNDSCMappedMemoryRegion* mappedRegions = new BNDSCMappedMemoryRegion[regions.size()]; - for (size_t i = 0; i < regions.size(); i++) - { - mappedRegions[i].vmAddress = regions[i]->start; - mappedRegions[i].size = regions[i]->size; - mappedRegions[i].name = - BNAllocStringWithLength(regions[i]->prettyName.c_str(), regions[i]->prettyName.length()); - } - return mappedRegions; - } - *count = 0; - return nullptr; - } +bool CacheProcessor::ProcessProjectCache(SharedCache& cache) +{ + auto baseProjectFile = m_view->GetFile()->GetProjectFile(); + std::string baseFilePath = baseProjectFile->GetPathOnDisk(); + // TODO: I dont think the project file name will have anything other than the base name so this might be redundant. + std::string baseFileName = BaseFileName(baseProjectFile->GetName()); - void BNDSCViewFreeLoadedRegions(BNDSCMappedMemoryRegion* images, size_t count) - { - for (size_t i = 0; i < count; i++) - { - BNFreeString(images[i].name); - } - delete images; - } + // Add this file to the entries + auto baseEntry = CacheEntry::FromFile(baseFilePath, baseFileName, CacheEntryType::Primary); + if (!baseEntry.has_value()) + return false; + // Before we do anything else, add this to the cache so it's available to other entries. + cache.AddEntry(std::move(*baseEntry)); - BNDSCBackingCache* BNDSCViewGetBackingCaches(BNSharedCache* cache, size_t* count) + // Enumerate the project files folder to gather the necessary sub caches. + const auto project = baseProjectFile->GetProject(); + const auto folder = baseProjectFile->GetFolder(); + for (const auto& projectFile : project->GetFiles()) { - BNDSCBackingCache* caches = nullptr; - - if (cache->object) + auto projectFilePath = projectFile->GetPathOnDisk(); + auto projectFileName = projectFile->GetName(); + auto currentFolder = projectFile->GetFolder(); + // Skip our base project file, obviously. + if (projectFile->GetId() == baseProjectFile->GetId()) + continue; + // Filter files that don't contain the base file name i.e. "dyld_shared_cache_arm64e" + if (projectFileName.find(baseFileName) == std::string::npos) + continue; + // Filter out .map files, they contain some nice info for rebasing... that we don't do. + if (projectFileName.find(".map") != std::string::npos) + continue; + // If both top level, or we are in the same folder as the base project file add it. + if ((!folder && !currentFolder) || (folder && currentFolder)) { - auto viewCaches = cache->object->BackingCaches(); - *count = viewCaches.size(); - caches = new BNDSCBackingCache[viewCaches.size()]; - for (size_t i = 0; i < viewCaches.size(); i++) + try { - caches[i].path = BNAllocString(viewCaches[i].path.c_str()); - caches[i].cacheType = viewCaches[i].cacheType; - - BNDSCBackingCacheMapping* mappings; - mappings = new BNDSCBackingCacheMapping[viewCaches[i].mappings.size()]; - - size_t j = 0; - for (const auto& mapping : viewCaches[i].mappings) - { - mappings[j].vmAddress = mapping.address; - mappings[j].size = mapping.size; - mappings[j].fileOffset = mapping.fileOffset; - j++; - } - caches[i].mappings = mappings; - caches[i].mappingCount = viewCaches[i].mappings.size(); - } - } - - return caches; - } - - void BNDSCViewFreeBackingCaches(BNDSCBackingCache* caches, size_t count) - { - for (size_t i = 0; i < count; i++) - { - delete[] caches[i].mappings; - BNFreeString(caches[i].path); - } - delete[] caches; - } - - void BNDSCFindSymbolAtAddressAndApplyToAddress(BNSharedCache* cache, uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis) - { - if (cache->object) - { - cache->object->FindSymbolAtAddrAndApplyToAddr(symbolLocation, targetLocation, triggerReanalysis); - } - } - - BNDSCImage* BNDSCViewGetAllImages(BNSharedCache* cache, size_t* count) - { - if (cache->object) - { - try { - auto vm = cache->object->GetVMMap(); - auto viewImageHeaders = cache->object->AllImageHeaders(); - *count = viewImageHeaders.size(); - BNDSCImage* images = new BNDSCImage[viewImageHeaders.size()]; - size_t i = 0; - for (const auto& [baseAddress, header] : viewImageHeaders) + auto additionalEntry = CacheEntry::FromFile(projectFilePath, projectFileName, CacheEntryType::Secondary); + if (!additionalEntry.has_value()) { - images[i].name = BNAllocString(header.installName.c_str()); - images[i].headerAddress = baseAddress; - images[i].mappingCount = header.sections.size(); - images[i].mappings = new BNDSCImageMemoryMapping[header.sections.size()]; - for (size_t j = 0; j < header.sections.size(); j++) - { - const auto sectionStart = header.sections[j].addr; - images[i].mappings[j].rawViewOffset = header.sections[j].offset; - images[i].mappings[j].vmAddress = sectionStart; - images[i].mappings[j].size = header.sections[j].size; - images[i].mappings[j].name = BNAllocString(header.sectionNames[j].c_str()); - auto fileAccessor = vm->MappingAtAddress(sectionStart).first.fileAccessor; - images[i].mappings[j].filePath = BNAllocStringWithLength(fileAccessor->filePath().data(), fileAccessor->filePath().length()); - images[i].mappings[j].loaded = cache->object->IsMemoryMapped(sectionStart); - } - i++; + m_logger->LogErrorF("Failed to load entry {}...", projectFileName); + continue; } - return images; - } - catch (...) - { - LogError("SharedCache: Failed to load image listing. Likely caused by a ser/deserialization error or load failure"); - *count = 0; - return nullptr; - } - } - *count = 0; - return nullptr; - } - void BNDSCViewFreeAllImages(BNDSCImage* images, size_t count) - { - for (size_t i = 0; i < count; i++) - { - for (size_t j = 0; j < images[i].mappingCount; j++) - { - BNFreeString(images[i].mappings[j].name); - BNFreeString(images[i].mappings[j].filePath); + // Add this file as an entry to the cache + cache.AddEntry(std::move(*additionalEntry)); } - delete[] images[i].mappings; - BNFreeString(images[i].name); + catch (const std::exception& e) + {} } - delete[] images; } - char* BNDSCViewGetImageHeaderForAddress(BNSharedCache* cache, uint64_t address) - { - if (cache->object) - { - auto header = cache->object->SerializedImageHeaderForAddress(address); - return BNAllocString(header.c_str()); - } - - return nullptr; - } - - char* BNDSCViewGetImageHeaderForName(BNSharedCache* cache, char* name) - { - std::string imageName = std::string(name); - BNFreeString(name); - if (cache->object) - { - auto header = cache->object->SerializedImageHeaderForName(imageName); - return BNAllocString(header.c_str()); - } - - return nullptr; - } - - BNDSCMemoryUsageInfo BNDSCViewGetMemoryUsageInfo() - { - BNDSCMemoryUsageInfo info; - info.mmapRefs = MMapCount(); - info.sharedCacheRefs = sharedCacheReferences.load(); - return info; - } - - BNDSCViewLoadProgress BNDSCViewGetLoadProgress(uint64_t sessionID) - { - if (auto viewSpecificState = ViewSpecificStateForId(sessionID, false)) - return viewSpecificState->progress; - - return LoadProgressNotStarted; - } - - uint64_t BNDSCViewFastGetBackingCacheCount(BNBinaryView* data) - { - Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); - return SharedCache::FastGetBackingCacheCount(view); - } -}
\ No newline at end of file + return true; +} diff --git a/view/sharedcache/core/SharedCache.h b/view/sharedcache/core/SharedCache.h index fadcdd3a..54ec9dfd 100644 --- a/view/sharedcache/core/SharedCache.h +++ b/view/sharedcache/core/SharedCache.h @@ -1,705 +1,278 @@ -// -// Created by kat on 5/19/23. -// +#pragma once -#ifndef SHAREDCACHE_SHAREDCACHE_H -#define SHAREDCACHE_SHAREDCACHE_H +#include <vector> +#include <Dyld.h> -#include <binaryninjaapi.h> -#include <cstdint> -#include <memory> -#include <mutex> -#include <unordered_map> -#include "VM.h" -#include "view/macho/machoview.h" -#include "MetadataSerializable.hpp" -#include "../api/sharedcachecore.h" +#include "binaryninjaapi.h" +#include "MachO.h" +#include "VirtualMemory.h" -#include <optional> +class SharedCache; -DECLARE_SHAREDCACHE_API_OBJECT(BNSharedCache, SharedCache); +struct CacheSymbol +{ + BNSymbolType type; + uint64_t address; + std::string name; -namespace SharedCacheCore { + CacheSymbol() = default; - enum DSCViewState - { - DSCViewStateUnloaded, - DSCViewStateLoaded, - DSCViewStateLoadedWithImages, - }; - - struct MemoryRegion : public MetadataSerializable<MemoryRegion> - { - enum class Type - { - Image, - StubIsland, - DyldData, - NonImage, - }; - - std::string prettyName; - uint64_t start; - uint64_t size; - // Start address of the image this region belongs to. - // 0 if the region does not belong to any image. - uint64_t imageStart = 0; - BNSegmentFlag flags; - Type type; + CacheSymbol(BNSymbolType type, uint64_t address, std::string name) : + type(type), address(address), name(std::move(name)) + {} + ~CacheSymbol() = default; - AddressRange AsAddressRange() const - { - return {start, start + size}; - } + CacheSymbol(const CacheSymbol& other) = default; - void Store(SerializationContext& context) const - { - MSS(prettyName); - MSS(start); - MSS(size); - MSS(imageStart); - MSS_CAST(flags, uint64_t); - MSS_CAST(type, uint8_t); - } + CacheSymbol& operator=(const CacheSymbol& other) = default; - static MemoryRegion Load(DeserializationContext& context) - { - MemoryRegion region; - region.MSL(prettyName); - region.MSL(start); - region.MSL(size); - region.MSL_CAST(flags, uint64_t, BNSegmentFlag); - region.MSL_CAST(type, uint8_t, Type); - if (context.doc.HasMember("imageStart")) - region.MSL(imageStart); + CacheSymbol(CacheSymbol&& other) noexcept = default; - return region; - } - }; + CacheSymbol& operator=(CacheSymbol&& other) noexcept = default; - struct CacheImage : public MetadataSerializable<CacheImage> { - std::string installName; - uint64_t headerLocation; - // Start addresses of the memory regions in this image. - std::vector<uint64_t> regionStarts; + // NOTE: you should really only call this when adding the symbol to the view. + BinaryNinja::Ref<BinaryNinja::Symbol> ToBNSymbol() const; +}; - void Store(SerializationContext& context) const; - static CacheImage Load(DeserializationContext& context); - }; +enum class CacheRegionType +{ + Image, + StubIsland, + DyldData, + NonImage, +}; - #if defined(__GNUC__) || defined(__clang__) - #define PACKED_STRUCT __attribute__((packed)) - #else - #define PACKED_STRUCT - #endif +struct CacheRegion +{ + CacheRegionType type; + std::string name; + uint64_t start; + uint64_t size; + // Associate this region with this image, this makes it easier to identify what image owns this region. + std::optional<uint64_t> imageStart; + BNSegmentFlag flags; - #if defined(_MSC_VER) - #pragma pack(push, 1) - #else + CacheRegion() = default; - #endif + ~CacheRegion() = default; - struct PACKED_STRUCT dyld_cache_mapping_info - { - uint64_t address; - uint64_t size; - uint64_t fileOffset; - uint32_t maxProt; - uint32_t initProt; - }; - - struct BackingCache : public MetadataSerializable<BackingCache> { - std::string path; - BNBackingCacheType cacheType = BackingCacheTypeSecondary; - std::vector<dyld_cache_mapping_info> mappings; + CacheRegion(const CacheRegion& other) = default; - void Store(SerializationContext& context) const; - static BackingCache Load(DeserializationContext& context); - }; + CacheRegion& operator=(const CacheRegion& other) = default; - struct LoadedMapping - { - std::shared_ptr<MMappedFileAccessor> backingFile; - dyld_cache_mapping_info mappingInfo; - }; + CacheRegion(CacheRegion&& other) noexcept = default; - struct dyld_cache_slide_info - { - uint32_t version; - uint32_t toc_offset; - uint32_t toc_count; - uint32_t entries_offset; - uint32_t entries_count; - uint32_t entries_size; - // uint16_t toc[toc_count]; - // entrybitmap entries[entries_count]; - }; + CacheRegion& operator=(CacheRegion&& other) noexcept = default; - struct dyld_cache_slide_info_entry { - uint8_t bits[4096/(8*4)]; // 128-byte bitmap - }; + AddressRange AsAddressRange() const { return {start, start + size}; } - struct PACKED_STRUCT dyld_cache_mapping_and_slide_info + BNSectionSemantics SectionSemanticsForRegion() const { - uint64_t address; - uint64_t size; - uint64_t fileOffset; - uint64_t slideInfoFileOffset; - uint64_t slideInfoFileSize; - uint64_t flags; - uint32_t maxProt; - uint32_t initProt; - }; + if ((flags & SegmentExecutable) && (flags & SegmentDenyWrite)) + return ReadOnlyCodeSectionSemantics; - struct PACKED_STRUCT dyld_cache_slide_info_v2 - { - uint32_t version; - uint32_t page_size; - uint32_t page_starts_offset; - uint32_t page_starts_count; - uint32_t page_extras_offset; - uint32_t page_extras_count; - uint64_t delta_mask; - uint64_t value_add; - }; - #define DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA 0x8000 // index is into extras array (not starts array) - #define DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE 0x4000 // page has no rebasing - #define DYLD_CACHE_SLIDE_PAGE_ATTR_END 0x8000 // last chain entry for page - - #define DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing - - struct PACKED_STRUCT dyld_cache_slide_info_v3 - { - uint32_t version; - uint32_t page_size; - uint32_t page_starts_count; - uint32_t pad_i_guess; - uint64_t auth_value_add; - }; - - - // DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE - struct dyld_chained_ptr_arm64e_shared_cache_rebase - { - uint64_t runtimeOffset : 34, // offset from the start of the shared cache - high8 : 8, - unused : 10, - next : 11, // 8-byte stide - auth : 1; // == 0 - }; + if (flags & SegmentExecutable) + return DefaultSectionSemantics; - // DYLD_CHAINED_PTR_ARM64E_SHARED_CACHE - struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase - { - uint64_t runtimeOffset : 34, // offset from the start of the shared cache - diversity : 16, - addrDiv : 1, - keyIsData : 1, // implicitly always the 'A' key. 0 -> IA. 1 -> DA - next : 11, // 8-byte stide - auth : 1; // == 1 - }; + if (flags & SegmentDenyWrite) + return ReadOnlyDataSectionSemantics; - // dyld_cache_slide_info4 is used in watchOS which we are not close to supporting right now. + return ReadWriteDataSectionSemantics; + } +}; - #define DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE 0xFFFF // page has no rebasing +// Represents a single image and its associated memory regions. +struct CacheImage +{ + uint64_t headerAddress; + std::string path; + // A list to the start of memory regions associated with the image. + // This lets us load all regions for a given image easily. + std::vector<uint64_t> regionStarts; + std::shared_ptr<SharedCacheMachOHeader> header; - struct PACKED_STRUCT dyld_cache_slide_info5 - { - uint32_t version; // currently 5 - uint32_t page_size; // currently 4096 (may also be 16384) - uint32_t page_starts_count; - uint32_t pad; // padding to ensure the value below is on an 8-byte boundary - uint64_t value_add; - // uint16_t page_starts[/* page_starts_count */]; - }; + CacheImage() = default; + ~CacheImage() = default; - struct PACKED_STRUCT dyld_cache_image_info - { - uint64_t address; - uint64_t modTime; - uint64_t inode; - uint32_t pathFileOffset; - uint32_t pad; - }; + CacheImage(const CacheImage& other) = default; - union dyld_cache_slide_pointer5 - { - uint64_t raw; - struct dyld_chained_ptr_arm64e_shared_cache_rebase regular; - struct dyld_chained_ptr_arm64e_shared_cache_auth_rebase auth; - }; + CacheImage& operator=(const CacheImage& other) = default; + CacheImage(CacheImage&& other) noexcept = default; - struct PACKED_STRUCT dyld_cache_local_symbols_info - { - uint32_t nlistOffset; // offset into this chunk of nlist entries - uint32_t nlistCount; // count of nlist entries - uint32_t stringsOffset; // offset into this chunk of string pool - uint32_t stringsSize; // byte count of string pool - uint32_t entriesOffset; // offset into this chunk of array of dyld_cache_local_symbols_entry - uint32_t entriesCount; // number of elements in dyld_cache_local_symbols_entry array - }; + CacheImage& operator=(CacheImage&& other) noexcept = default; - struct PACKED_STRUCT dyld_cache_local_symbols_entry - { - uint32_t dylibOffset; // offset in cache file of start of dylib - uint32_t nlistStartIndex; // start index of locals for this dylib - uint32_t nlistCount; // number of local symbols for this dylib - }; + // Get the file name from the path. + std::string GetName() const { return BaseFileName(path); } - struct PACKED_STRUCT dyld_cache_local_symbols_entry_64 - { - uint64_t dylibOffset; // offset in cache buffer of start of dylib - uint32_t nlistStartIndex; // start index of locals for this dylib - uint32_t nlistCount; // number of local symbols for this dylib - }; + // Get the names of the dependencies. + std::vector<std::string> GetDependencies() const; +}; - union dyld_cache_slide_pointer3 - { - uint64_t raw; - struct - { - uint64_t pointerValue : 51, offsetToNextPointer : 11, unused : 2; - } plain; +enum class CacheEntryType +{ + Primary, + Secondary, + // A special entry that holds symbols for other cache entries. + // TODO: We dont need this i think. + Symbols, + // If the type is marked as this then all mappings will be marked as such. + DyldData, + // A single stub mapping file. + Stub, +}; - struct - { - uint64_t offsetFromSharedCacheBase : 32, diversityData : 16, hasAddressDiversity : 1, key : 2, - offsetToNextPointer : 11, unused : 1, - authenticated : 1; // = 1; - } auth; - }; +// Describes a single files cache information +class CacheEntry +{ + CacheEntryType m_type; + std::string m_filePath; + std::string m_fileName; + dyld_cache_header m_header {}; + // Mappings tell us _where_ to map the regions within the flat address space. + // Without this we wouldn't know where the entry is supposed to exist in the address space. + std::vector<dyld_cache_mapping_info> m_mappings {}; + // TODO: We really should remove this methinks. + // TODO: Storing this here is basically useless? IDK + // Mapping of image path to image info, used within ProcessImagesAndRegions to add them to the cache. + std::unordered_map<std::string, dyld_cache_image_info> m_images {}; +public: + CacheEntry(std::string filePath, std::string fileName, CacheEntryType type, dyld_cache_header header, + std::vector<dyld_cache_mapping_info> mappings, std::unordered_map<std::string, dyld_cache_image_info> images); - struct PACKED_STRUCT dyld_cache_header - { - char magic[16]; // e.g. "dyld_v0 i386" - uint32_t mappingOffset; // file offset to first dyld_cache_mapping_info - uint32_t mappingCount; // number of dyld_cache_mapping_info entries - uint32_t imagesOffsetOld; // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing - uint32_t imagesCountOld; // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing - uint64_t dyldBaseAddress; // base address of dyld when cache was built - uint64_t codeSignatureOffset; // file offset of code signature blob - uint64_t codeSignatureSize; // size of code signature blob (zero means to end of file) - uint64_t slideInfoOffsetUnused; // unused. Used to be file offset of kernel slid info - uint64_t slideInfoSizeUnused; // unused. Used to be size of kernel slid info - uint64_t localSymbolsOffset; // file offset of where local symbols are stored - uint64_t localSymbolsSize; // size of local symbols information - uint8_t uuid[16]; // unique value for each shared cache file - uint64_t cacheType; // 0 for development, 1 for production, 2 for multi-cache - uint32_t branchPoolsOffset; // file offset to table of uint64_t pool addresses - uint32_t branchPoolsCount; // number of uint64_t entries - uint64_t dyldInCacheMH; // (unslid) address of mach_header of dyld in cache - uint64_t dyldInCacheEntry; // (unslid) address of entry point (_dyld_start) of dyld in cache - uint64_t imagesTextOffset; // file offset to first dyld_cache_image_text_info - uint64_t imagesTextCount; // number of dyld_cache_image_text_info entries - uint64_t patchInfoAddr; // (unslid) address of dyld_cache_patch_info - uint64_t patchInfoSize; // Size of all of the patch information pointed to via the dyld_cache_patch_info - uint64_t otherImageGroupAddrUnused; // unused - uint64_t otherImageGroupSizeUnused; // unused - uint64_t progClosuresAddr; // (unslid) address of list of program launch closures - uint64_t progClosuresSize; // size of list of program launch closures - uint64_t progClosuresTrieAddr; // (unslid) address of trie of indexes into program launch closures - uint64_t progClosuresTrieSize; // size of trie of indexes into program launch closures - uint32_t platform; // platform number (macOS=1, etc) - uint32_t formatVersion : 8, // dyld3::closure::kFormatVersion - dylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid - simulator : 1, // for simulator of specified platform - locallyBuiltCache : 1, // 0 for B&I built cache, 1 for locally built cache - builtFromChainedFixups : 1, // some dylib in cache was built using chained fixups, so patch tables must be used for overrides - padding : 20; // TBD - uint64_t sharedRegionStart; // base load address of cache if not slid - uint64_t sharedRegionSize; // overall size required to map the cache and all subCaches, if any - uint64_t maxSlide; // runtime slide of cache can be between zero and this value - uint64_t dylibsImageArrayAddr; // (unslid) address of ImageArray for dylibs in this cache - uint64_t dylibsImageArraySize; // size of ImageArray for dylibs in this cache - uint64_t dylibsTrieAddr; // (unslid) address of trie of indexes of all cached dylibs - uint64_t dylibsTrieSize; // size of trie of cached dylib paths - uint64_t otherImageArrayAddr; // (unslid) address of ImageArray for dylibs and bundles with dlopen closures - uint64_t otherImageArraySize; // size of ImageArray for dylibs and bundles with dlopen closures - uint64_t otherTrieAddr; // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures - uint64_t otherTrieSize; // size of trie of dylibs and bundles with dlopen closures - uint32_t mappingWithSlideOffset; // file offset to first dyld_cache_mapping_and_slide_info - uint32_t mappingWithSlideCount; // number of dyld_cache_mapping_and_slide_info entries - uint64_t dylibsPBLStateArrayAddrUnused; // unused - uint64_t dylibsPBLSetAddr; // (unslid) address of PrebuiltLoaderSet of all cached dylibs - uint64_t programsPBLSetPoolAddr; // (unslid) address of pool of PrebuiltLoaderSet for each program - uint64_t programsPBLSetPoolSize; // size of pool of PrebuiltLoaderSet for each program - uint64_t programTrieAddr; // (unslid) address of trie mapping program path to PrebuiltLoaderSet - uint32_t programTrieSize; - uint32_t osVersion; // OS Version of dylibs in this cache for the main platform - uint32_t altPlatform; // e.g. iOSMac on macOS - uint32_t altOsVersion; // e.g. 14.0 for iOSMac - uint64_t swiftOptsOffset; // VM offset from cache_header* to Swift optimizations header - uint64_t swiftOptsSize; // size of Swift optimizations header - uint32_t subCacheArrayOffset; // file offset to first dyld_subcache_entry - uint32_t subCacheArrayCount; // number of subCache entries - uint8_t symbolFileUUID[16]; // unique value for the shared cache file containing unmapped local symbols - uint64_t rosettaReadOnlyAddr; // (unslid) address of the start of where Rosetta can add read-only/executable data - uint64_t rosettaReadOnlySize; // maximum size of the Rosetta read-only/executable region - uint64_t rosettaReadWriteAddr; // (unslid) address of the start of where Rosetta can add read-write data - uint64_t rosettaReadWriteSize; // maximum size of the Rosetta read-write region - uint32_t imagesOffset; // file offset to first dyld_cache_image_info - uint32_t imagesCount; // number of dyld_cache_image_info entries - uint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2) - uint32_t padding2; - uint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header - uint64_t objcOptsSize; // size of ObjC optimizations header - uint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection - uint64_t cacheAtlasSize; // size of embedded cache atlas - uint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header - uint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data - uint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info - uint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries - }; + CacheEntry() = default; - struct PACKED_STRUCT dyld_subcache_entry - { - char uuid[16]; - uint64_t address; - }; + CacheEntry(const CacheEntry&) = default; - struct PACKED_STRUCT dyld_subcache_entry2 - { - char uuid[16]; - uint64_t address; - char fileExtension[32]; - }; + CacheEntry(CacheEntry&&) = default; - struct ObjCOptimizationHeader - { - uint32_t version; - uint32_t flags; - uint64_t headerInfoROCacheOffset; - uint64_t headerInfoRWCacheOffset; - uint64_t selectorHashTableCacheOffset; - uint64_t classHashTableCacheOffset; - uint64_t protocolHashTableCacheOffset; - uint64_t relativeMethodSelectorBaseAddressOffset; - }; + CacheEntry& operator=(CacheEntry&&) = default; - #if defined(_MSC_VER) - #pragma pack(pop) - #else + // Construct a cache entry from the file on disk. + // TODO: Seperate this out a bit more. + static std::optional<CacheEntry> FromFile(const std::string& filePath, const std::string& fileName, CacheEntryType type); - #endif - - struct SharedCacheMachOHeader : public MetadataSerializable<SharedCacheMachOHeader> - { - uint64_t textBase = 0; - uint64_t loadCommandOffset = 0; - mach_header_64 ident; - std::string identifierPrefix; - std::string installName; + // TODO: From Project file? - std::vector<std::pair<uint64_t, bool>> entryPoints; - std::vector<uint64_t> m_entryPoints; // list of entrypoints + WeakFileAccessor GetAccessor() const; - symtab_command symtab; - dysymtab_command dysymtab; - dyld_info_command dyldInfo; - routines_command_64 routines64; - function_starts_command functionStarts; - std::vector<section_64> moduleInitSections; - linkedit_data_command exportTrie; - linkedit_data_command chainedFixups {}; + // Get the headers virtual address. + // This is useful if you need to read relative to the start of the entry file. + std::optional<uint64_t> GetHeaderAddress() const; - uint64_t relocationBase; - // Section and program headers, internally use 64-bit form as it is a superset of 32-bit - std::vector<segment_command_64> segments; // only three types of sections __TEXT, __DATA, __IMPORT - segment_command_64 linkeditSegment; - std::vector<section_64> sections; - std::vector<std::string> sectionNames; + // Get the mapped address for a given file offset. + // Ex. passing 0x0 will retrieve the mapped address for the start of the file (i.e. the header) + std::optional<uint64_t> GetMappedAddress(uint64_t fileOffset) const; - std::vector<section_64> symbolStubSections; - std::vector<section_64> symbolPointerSections; + CacheEntryType GetType() const { return m_type; } + // Ex. "/myuser/mypath/dyld_shared_cache_arm64e" + const std::string& GetFilePath() const { return m_filePath; } + // Ex. "dyld_shared_cache_arm64e" + const std::string GetFileName() const { return m_fileName; } + const dyld_cache_header& GetHeader() const { return m_header; } + const std::vector<dyld_cache_mapping_info>& GetMappings() const { return m_mappings; } + const std::unordered_map<std::string, dyld_cache_image_info>& GetImages() const { return m_images; } +}; - std::vector<std::string> dylibs; +// The ID for a given CacheEntry, use this instead of passing a pointer around to avoid complexity :V +typedef uint32_t CacheEntryId; - build_version_command buildVersion; - std::vector<build_tool_version> buildToolVersions; +// TODO: Add a "ViewCache" that keeps track of what has been added to the view. - std::string exportTriePath; +// The C in DSC. +// This represents the entire cache, all regions and images are visible from here. +// This is the dump for all the information, and what the workflow activities and the UI want. +// Creating this is expensive, both in actual processing and just copying, so we only generate this +// once every time the database is open. +class SharedCache +{ + uint64_t m_addressSize = 8; + uint64_t m_baseAddress = 0; + // TODO: Figure out when to lock the mutex on this shit lmfao + // The shared cache can own the virtual memory, this is fine... + std::shared_ptr<VirtualMemory> m_vm; + std::unordered_map<CacheEntryId, CacheEntry> m_entries {}; + // This information is used in tandem with the cache images to load memory regions into the binary view. + AddressRangeMap<CacheRegion> m_regions {}; + // Describes the images of the cache. + std::unordered_map<uint64_t, CacheImage> m_images {}; + // All the symbols for this cache. Both mapped and unmapped (not in the view). + std::unordered_map<uint64_t, CacheSymbol> m_symbols {}; - bool linkeditPresent = false; - bool dysymPresent = false; - bool dyldInfoPresent = false; - bool exportTriePresent = false; - bool chainedFixupsPresent = false; - bool routinesPresent = false; - bool functionStartsPresent = false; - bool relocatable = false; + bool ProcessEntryImage(const std::string& path, const dyld_cache_image_info& info); - void Store(SerializationContext& context) const { - MSS(textBase); - MSS(loadCommandOffset); - MSS_SUBCLASS(ident); - MSS(identifierPrefix); - MSS(installName); - MSS(entryPoints); - MSS(m_entryPoints); - MSS_SUBCLASS(symtab); - MSS_SUBCLASS(dysymtab); - MSS_SUBCLASS(dyldInfo); - MSS_SUBCLASS(routines64); - MSS_SUBCLASS(functionStarts); - MSS_SUBCLASS(moduleInitSections); - MSS_SUBCLASS(exportTrie); - MSS_SUBCLASS(chainedFixups); - MSS(relocationBase); - MSS_SUBCLASS(segments); - MSS_SUBCLASS(linkeditSegment); - MSS_SUBCLASS(sections); - MSS(sectionNames); - MSS_SUBCLASS(symbolStubSections); - MSS_SUBCLASS(symbolPointerSections); - MSS(dylibs); - MSS_SUBCLASS(buildVersion); - MSS_SUBCLASS(buildToolVersions); - MSS(exportTriePath); - MSS(linkeditPresent); - MSS(dysymPresent); - MSS(dyldInfoPresent); - MSS(exportTriePresent); - MSS(chainedFixupsPresent); - MSS(routinesPresent); - MSS(functionStartsPresent); - MSS(relocatable); - } + // Add a region known not to overlap with another, otherwise use AddRegion. + // returns whether the region was inserted. + bool AddNonOverlappingRegion(CacheRegion region); - static SharedCacheMachOHeader Load(DeserializationContext& context) { - SharedCacheMachOHeader header; - header.MSL(textBase); - header.MSL(loadCommandOffset); - header.MSL(ident); - header.MSL(identifierPrefix); - header.MSL(installName); - header.MSL(entryPoints); - header.MSL(m_entryPoints); - header.MSL(symtab); - header.MSL(dysymtab); - header.MSL(dyldInfo); - header.MSL(routines64); - header.MSL(functionStarts); - header.MSL(moduleInitSections); - header.MSL(exportTrie); - header.MSL(chainedFixups); - header.MSL(relocationBase); - header.MSL(segments); - header.MSL(linkeditSegment); - header.MSL(sections); - header.MSL(sectionNames); - header.MSL(symbolStubSections); - header.MSL(symbolPointerSections); - header.MSL(dylibs); - header.MSL(buildVersion); - header.MSL(buildToolVersions); - header.MSL(exportTriePath); - header.MSL(linkeditPresent); - header.MSL(dysymPresent); - header.MSL(dyldInfoPresent); - header.MSL(exportTriePresent); - header.MSL(chainedFixupsPresent); - header.MSL(routinesPresent); - header.MSL(functionStartsPresent); - header.MSL(relocatable); - return header; - } - }; +public: + explicit SharedCache(uint64_t addressSize); - struct MappingInfo - { - dyld_cache_mapping_info mappingInfo; - uint32_t slideInfoVersion; - dyld_cache_slide_info_v2 slideInfoV2; - dyld_cache_slide_info_v3 slideInfoV3; - dyld_cache_slide_info5 slideInfoV5; - }; + uint64_t GetBaseAddress() const { return m_baseAddress; } + std::shared_ptr<VirtualMemory> GetVirtualMemory() { return m_vm; } + const std::unordered_map<CacheEntryId, CacheEntry>& GetEntries() const { return m_entries; } + const AddressRangeMap<CacheRegion>& GetRegions() const { return m_regions; } + const std::unordered_map<uint64_t, CacheImage>& GetImages() const { return m_images; } + const std::unordered_map<uint64_t, CacheSymbol>& GetSymbols() const { return m_symbols; } + void AddImage(CacheImage image); - class ScopedVMMapSession; + // Add a region that may overlap with another. + void AddRegion(CacheRegion region); - static std::atomic<uint64_t> sharedCacheReferences = 0; + void AddSymbol(CacheSymbol symbol); - class SharedCache - { - IMPLEMENT_SHAREDCACHE_API_OBJECT(BNSharedCache); + void AddSymbols(std::vector<CacheSymbol> symbols); - std::atomic<int> m_refs = 0; + // Adds the cache entry and populates the virtual memory using the mapping information. + // After being added the entry is read only, there is nothing that can modify it. + CacheEntryId AddEntry(CacheEntry entry); - public: - virtual void AddRef() { m_refs.fetch_add(1); } + void ProcessEntryImages(const CacheEntry& entry); - virtual void Release() - { - // undo actions will lock a file lock we hold and then wait for main thread - // so we need to release the ref later. - WorkerPriorityEnqueue([this]() { - if (m_refs.fetch_sub(1) == 1) - delete this; - }); - } + void ProcessEntryRegions(const CacheEntry& entry); - virtual void AddAPIRef() { AddRef(); } + void ProcessEntrySlideInfo(const CacheEntry& entry); - virtual void ReleaseAPIRef() { Release(); } + std::optional<CacheEntry> GetEntryContaining(uint64_t address) const; - public: - enum SharedCacheFormat - { - RegularCacheFormat, - SplitCacheFormat, - LargeCacheFormat, - iOS16CacheFormat, - }; + std::optional<CacheEntry> GetEntryWithImage(const CacheImage& image) const; - struct CacheInfo; - struct ModifiedState; + std::optional<CacheRegion> GetRegionAt(uint64_t address) const; - struct ViewSpecificState; + std::optional<CacheRegion> GetRegionContaining(uint64_t address) const; + std::optional<CacheImage> GetImageAt(uint64_t address) const; - private: - Ref<Logger> m_logger; - /* VIEW STATE BEGIN -- SERIALIZE ALL OF THIS AND STORE IT IN RAW VIEW */ - - // State that is initialized during `PerformInitialLoad` and does - // not change thereafter. - std::shared_ptr<const CacheInfo> m_cacheInfo; - - // Protects member variables below. - mutable std::mutex m_mutex; - - // State that has been modified since this instance was created - // or last saved to the view-specific state. - // To get an accurate view of the current state, both these modifications - // and the view-specific state must be consulted. - std::unique_ptr<ModifiedState> m_modifiedState; - - // Serialized once by PerformInitialLoad and available after m_viewState == Loaded - bool m_metadataValid = false; - - /* VIEWSTATE END -- NOTHING PAST THIS IS SERIALIZED */ - - /* API VIEW START */ - BinaryNinja::Ref<BinaryNinja::BinaryView> m_dscView; - /* API VIEW END */ - - std::shared_ptr<ViewSpecificState> m_viewSpecificState; - - private: - void PerformInitialLoad(std::lock_guard<std::mutex>&); - bool DeserializeFromRawView(std::lock_guard<std::mutex>&); - - public: - std::shared_ptr<VM> GetVMMap(); - std::shared_ptr<VM> GetVMMap(const CacheInfo& staticState); - - static SharedCache* GetFromDSCView(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView); - static uint64_t FastGetBackingCacheCount(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView); - bool SaveCacheInfoToDSCView(std::lock_guard<std::mutex>&); - bool SaveModifiedStateToDSCView(std::lock_guard<std::mutex>&); - - static void ParseAndApplySlideInfoForFile(std::shared_ptr<MMappedFileAccessor> file, uint64_t baseAddress, Ref<Logger> logger); - std::optional<uint64_t> GetImageStart(std::string_view installName); - const SharedCacheMachOHeader* HeaderForAddress(uint64_t); - bool LoadImageWithInstallName(std::string installName, bool skipObjC); - bool LoadSectionAtAddress(uint64_t address); - bool LoadImageContainingAddress(uint64_t address, bool skipObjC); - void ProcessObjCSectionsForImageWithInstallName(std::string installName); - void ProcessAllObjCSections(); - std::string NameForAddress(uint64_t address); - std::string ImageNameForAddress(uint64_t address); - std::vector<std::string> GetAvailableImages(); - - std::vector<const MemoryRegion*> GetMappedRegions() const; - bool IsMemoryMapped(uint64_t address); - - std::unordered_map<std::string, std::vector<Ref<Symbol>>> LoadAllSymbolsAndWait(); - - const std::unordered_map<std::string, uint64_t>& AllImageStarts() const; - const std::unordered_map<uint64_t, SharedCacheMachOHeader>& AllImageHeaders() const; - - std::string SerializedImageHeaderForAddress(uint64_t address); - std::string SerializedImageHeaderForName(std::string name); - - void FindSymbolAtAddrAndApplyToAddr(uint64_t symbolLocation, uint64_t targetLocation, bool triggerReanalysis); - - const std::vector<BackingCache>& BackingCaches() const; - - DSCViewState ViewState() const; - - explicit SharedCache(BinaryNinja::Ref<BinaryNinja::BinaryView> rawView); - virtual ~SharedCache(); - - uint64_t GetObjCRelativeMethodBaseAddress(const VMReader& reader) const; - -private: - std::optional<SharedCacheMachOHeader> LoadHeaderForAddress( - std::shared_ptr<VM> vm, uint64_t address, std::string installName); - void InitializeHeader( - std::lock_guard<std::mutex>&, Ref<BinaryView> view, VM* vm, const SharedCacheMachOHeader& header, - std::vector<const MemoryRegion*> regionsToLoad); - void ReadExportNode(std::vector<Ref<Symbol>>& symbolList, const SharedCacheMachOHeader& header, const uint8_t* begin, - const uint8_t *end, const uint8_t* current, uint64_t textBase, const std::string& currentText); - std::vector<Ref<Symbol>> ParseExportTrie( - std::shared_ptr<MMappedFileAccessor> linkeditFile, const SharedCacheMachOHeader& header); - std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> GetExportListForHeader(std::lock_guard<std::mutex>&, const SharedCacheMachOHeader& header, - std::function<std::shared_ptr<MMappedFileAccessor>()> provideLinkeditFile, bool* didModifyExportList = nullptr); - std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>> GetExistingExportListForBaseAddress(std::lock_guard<std::mutex>&, uint64_t baseAddress) const; - void ProcessSymbols(std::shared_ptr<MMappedFileAccessor> file, const SharedCacheMachOHeader& header, - uint64_t stringsOffset, size_t stringsSize, uint64_t nlistEntriesOffset, uint32_t nlistCount, uint32_t nlistStartIndex = 0); - void ApplySymbol(Ref<BinaryView> view, Ref<TypeLibrary> typeLib, Ref<Symbol> symbol); - - void ProcessAllObjCSections(std::lock_guard<std::mutex>&); - bool LoadImageWithInstallName(std::lock_guard<std::mutex>&, std::string installName, bool skipObjC); - - bool MemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region) const; - void SetMemoryRegionIsLoaded(std::lock_guard<std::mutex>&, const MemoryRegion& region); - bool MemoryRegionIsHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region) const; - void SetMemoryRegionHeaderInitialized(std::lock_guard<std::mutex>&, const MemoryRegion& region); - - Ref<TypeLibrary> TypeLibraryForImage(const std::string& installName); - - std::optional<ObjCOptimizationHeader> GetObjCOptimizationHeader(VMReader reader) const; - - std::shared_ptr<MMappedFileAccessor> MapFile(const std::string& path); - static std::shared_ptr<MMappedFileAccessor> MapFileWithoutApplyingSlide(const std::string& path); - }; - - class SharedCacheMetadata - { - public: - static std::optional<SharedCacheMetadata> LoadFromView(BinaryView*); - static bool ViewHasMetadata(BinaryView*); - static std::optional<unsigned int> ViewMetadataVersion(BinaryView*); - - const std::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>>& ExportInfos() const; - std::string InstallNameForImageBaseAddress(uint64_t baseAddress) const; + std::optional<CacheImage> GetImageContaining(uint64_t address) const; - ~SharedCacheMetadata(); - SharedCacheMetadata(SharedCacheMetadata&&); - SharedCacheMetadata& operator=(SharedCacheMetadata&&); + // TODO: Rename to GetImageWithPath and then make another one for the image name. + std::optional<CacheImage> GetImageWithName(const std::string& name) const; - private: - SharedCacheMetadata(SharedCache::CacheInfo, SharedCache::ModifiedState); + std::optional<CacheSymbol> GetSymbolAt(uint64_t address) const; - std::unique_ptr<SharedCache::CacheInfo> cacheInfo; - std::unique_ptr<SharedCache::ModifiedState> state; + std::optional<CacheSymbol> GetSymbolWithName(const std::string& name) const; +}; - friend struct SharedCache::ModifiedState; - friend class SharedCache; +// This constructs a Cache, give it a file path, and it will add all relevant cache entries. +class CacheProcessor +{ + BinaryNinja::Ref<BinaryNinja::BinaryView> m_view; + BinaryNinja::Ref<BinaryNinja::Logger> m_logger; - static const std::string Tag; - static const std::string CacheInfoTag; - static const std::string ModifiedStateTagPrefix; - static const std::string ModifiedStateCountTag; - }; -} +public: + explicit CacheProcessor(BinaryNinja::Ref<BinaryNinja::BinaryView> view); -void InitDSCViewType(); + // Construct a cache from the root file, this will parse the cache header and locate all + // applicable cache entries and add them as well. + bool ProcessCache(SharedCache& cache); -#endif //SHAREDCACHE_SHAREDCACHE_H + // Process a cache on the file system, this is for when not using a project. + bool ProcessFileCache(SharedCache& cache); + // Process a cache using Binary Ninja's project system. + bool ProcessProjectCache(SharedCache& cache); +}; diff --git a/view/sharedcache/core/SharedCacheController.cpp b/view/sharedcache/core/SharedCacheController.cpp new file mode 100644 index 00000000..29c1192a --- /dev/null +++ b/view/sharedcache/core/SharedCacheController.cpp @@ -0,0 +1,261 @@ +#include "SharedCacheController.h" + +#include <utility> + +#include "MachOProcessor.h" +#include "ObjC.h" +#include "SlideInfo.h" + +using namespace BinaryNinja; +using namespace BinaryNinja::DSC; + +// Unique ID for a given Binary View. +typedef uint64_t ViewId; + +std::shared_mutex GlobalControllersMutex; + +std::map<ViewId, DSCRef<SharedCacheController>>& GlobalControllers() +{ + // To make initialization order consistent we place the static in a function. + static std::map<ViewId, DSCRef<SharedCacheController>> g_dscViews = {}; + return g_dscViews; +} + +ViewId GetViewIdFromView(BinaryView& view) +{ + // Currently the view id is just the views session id. + // NOTE: If we want more than one shared cache controller per view we would need to make this more unique. + return view.GetFile()->GetSessionId(); +} + +void DeleteController(BinaryView& view) +{ + const auto id = GetViewIdFromView(view); + std::unique_lock<std::shared_mutex> lock(GlobalControllersMutex); + auto& dscViews = GlobalControllers(); + if (auto it = dscViews.find(id); it != dscViews.end()) + { + // Someone is still holding the controller, lets warn about this. + if (it->second->m_refs > 1) + LogWarn("Deleting SharedCacheController for view %llx, but there are still %d references", id, + it->second->m_refs.load()); + dscViews.erase(it); + LogDebug("Deleted SharedCacheController for view %s", view.GetFile()->GetFilename().c_str()); + } +} + +void RegisterSharedCacheControllerDestructor() +{ + BNObjectDestructionCallbacks callbacks = {}; + callbacks.destructBinaryView = [](void* ctx, BNBinaryView* obj) -> void { + auto view = BinaryView(obj); + DeleteController(view); + }; + BNRegisterObjectDestructionCallbacks(&callbacks); +} + +SharedCacheController::SharedCacheController(SharedCache cache, Ref<Logger> logger) : m_cache(std::move(cache)) +{ + INIT_DSC_API_OBJECT(); + m_logger = std::move(logger); + m_loadedRegions = {}; + m_loadedImages = {}; +} + +DSCRef<SharedCacheController> SharedCacheController::Initialize(BinaryView& view, SharedCache cache) +{ + auto id = GetViewIdFromView(view); + std::unique_lock<std::shared_mutex> lock(GlobalControllersMutex); + auto logger = new Logger("SharedCacheController", view.GetFile()->GetSessionId()); + DSCRef<SharedCacheController> dscView = new SharedCacheController(std::move(cache), logger); + + // Pull the settings from the view. + if (Ref<Settings> settings = view.GetLoadSettings(VIEW_NAME)) + { + if (settings->Contains("loader.dsc.processObjC")) + dscView->m_processObjC = settings->Get<bool>("loader.dsc.processObjC", &view); + if (settings->Contains("loader.dsc.processCFStrings")) + dscView->m_processCFStrings = settings->Get<bool>("loader.dsc.processCFStrings", &view); + if (settings->Contains("loader.dsc.regionFilter")) + dscView->m_regionFilter = std::regex(settings->Get<std::string>("loader.dsc.regionFilter", &view)); + } + + // Check the view auto metadata for shared cache information. + // This effectively restores the state of the opened database to when it was last saved. + auto metadata = view.GetAutoMetadata()->GetKeyValueStore(); + if (metadata.find(METADATA_KEY) != metadata.end()) + dscView->LoadMetadata(*metadata[METADATA_KEY]); + + GlobalControllers().insert({id, dscView}); + return dscView; +} + +DSCRef<SharedCacheController> SharedCacheController::FromView(BinaryView& view) +{ + auto id = GetViewIdFromView(view); + std::shared_lock<std::shared_mutex> lock(GlobalControllersMutex); + auto& dscViews = GlobalControllers(); + auto dscView = dscViews.find(id); + if (dscView == dscViews.end()) + return nullptr; + return dscView->second; +} + +bool SharedCacheController::ApplyRegionAtAddress(BinaryView& view, const uint64_t address) +{ + auto region = m_cache.GetRegionAt(address); + if (!region) + return false; + return ApplyRegion(view, *region); +} + +bool SharedCacheController::ApplyRegion(BinaryView& view, const CacheRegion& region) +{ + // TODO: Check m_loadedRegions? This seems redundant... + // Loads the given region into the BinaryView and marks it as loaded. + // First check to make sure there isn't already a segment at the address in the view. + if (view.GetSegmentAt(region.start)) + return false; + + // Skip filtered regions, this defaults to just LINKEDIT regions. + if (std::regex_match(region.name, m_regionFilter)) + { + LogDebug("Skipping filtered region at %llx", region.start); + return false; + } + + auto vm = m_cache.GetVirtualMemory(); + auto reader = VirtualMemoryReader(vm); + DataBuffer buffer = {}; + try + { + buffer = reader.ReadBuffer(region.start, region.size); + } + catch (std::exception& e) + { + // This happens if we have not mapped in all the relevant entries. + m_logger->LogError("Failed to read region: %s", e.what()); + return false; + } + + // Unique memory region name so that we don't cause collisions. + // TODO: Better name? I dont really think so... + const auto memoryRegionName = fmt::format("{}_0x{:x}", region.name, region.start); + + // NOTE: Adding a data memory region will store the entire contents of the region in the BNDB. + // TODO: We can use the AddRemoteMemoryRegion if we want to reload on view init. + // TODO: ^ The above is only useful if we assume that all files will be available across database loads. + // TODO: we might allow a user to select non-persisted memory regions as an option. + view.GetMemoryMap()->AddDataMemoryRegion(memoryRegionName, region.start, buffer, region.flags); + // TODO: We might want to make this auto if we decide to "reload" all loaded region in view init. + // If we are not associated with an image we can create a section here to set the semantics. + // This is important for stub regions, as they will deref non image data that we want to retrieve the value of. + if (region.type != CacheRegionType::Image) + view.AddUserSection(memoryRegionName, region.start, region.size, region.SectionSemanticsForRegion()); + + m_loadedRegions.insert(region.start); + + // TODO: This needs to be done in a "database save" callback. + view.StoreMetadata(METADATA_KEY, GetMetadata()); + + return true; +} + +bool SharedCacheController::IsRegionLoaded(const CacheRegion& region) const +{ + return std::any_of(m_loadedRegions.begin(), m_loadedRegions.end(), [&](const auto& loadedRegion) { + return loadedRegion == region.start; + }); +} + +bool SharedCacheController::ApplyImage(BinaryView& view, const CacheImage& image) +{ + // TODO: Check m_loadedImages? This seems redundant... + // Load all regions of an image and mark the image as loaded. + bool loadedRegion = false; + for (const auto& regionStart : image.regionStarts) + if (ApplyRegionAtAddress(view, regionStart)) + loadedRegion = true; + + // If there was no loaded regions than we just want to forgo loading the image. + if (!loadedRegion) + return false; + + if (image.header) + { + // Header information is applied to the view here, such as sections. + auto machoProcessor = SharedCacheMachOProcessor(&view, m_cache.GetVirtualMemory()); + machoProcessor.ApplyHeader(*image.header); + + // TODO: Make this optional. + // Load objective-c information. + auto objcProcessor = DSCObjC::SharedCacheObjCProcessor(&view, false); + // TODO: Passing in an image name here is weird considering this is shared with the MACHO view. + // TODO: We should abstract out the "image" into an objc image type that represents what is required, which ig is the name? + try + { + if (m_processObjC) + objcProcessor.ProcessObjCData(image.GetName()); + if (m_processObjC) + objcProcessor.ProcessCFStrings(image.GetName()); + } + catch (std::exception& e) + { + // Let the user know there was an error in processing the objc stuff but let the image load + // regardless, as its non-critical. + m_logger->LogError("Failed to process ObjC information: %s", e.what()); + } + } + + m_loadedImages.insert(image.headerAddress); + + // TODO: This needs to be done in a "database save" callback. + view.StoreMetadata(METADATA_KEY, GetMetadata()); + + // TODO: Partial failure state (i.e. 2 regions loaded, one failed) + return true; +} + +bool SharedCacheController::IsImageLoaded(const CacheImage& image) const +{ + return std::any_of(m_loadedImages.begin(), m_loadedImages.end(), [&](const auto& loadedImage) { + return loadedImage == image.headerAddress; + }); +} + +Ref<Metadata> SharedCacheController::GetMetadata() const +{ + std::map<std::string, Ref<Metadata>> controllerMeta; + + std::vector<uint64_t> loadedImages; + std::vector<uint64_t> loadedRegions; + loadedImages.reserve(m_loadedImages.size()); + loadedRegions.reserve(m_loadedRegions.size()); + for (const auto& loadedImage : m_loadedImages) + loadedImages.push_back(loadedImage); + for (const auto& loadedRegion : m_loadedRegions) + loadedRegions.push_back(loadedRegion); + + controllerMeta["loadedImages"] = new Metadata(loadedImages); + controllerMeta["loadedRegions"] = new Metadata(loadedRegions); + + return new Metadata(controllerMeta); +} + +void SharedCacheController::LoadMetadata(const Metadata& metadata) +{ + auto controllerMeta = metadata.GetKeyValueStore(); + if (controllerMeta.find("loadedImages") != controllerMeta.end()) + { + const auto loadedImages = controllerMeta["loadedImages"]->GetUnsignedIntegerList(); + for (const auto& image : loadedImages) + m_loadedImages.insert(image); + } + + if (controllerMeta.find("loadedRegions") != controllerMeta.end()) + { + const auto loadedRegions = controllerMeta["loadedRegions"]->GetUnsignedIntegerList(); + for (const auto& region : loadedRegions) + m_loadedImages.insert(region); + } +} diff --git a/view/sharedcache/core/SharedCacheController.h b/view/sharedcache/core/SharedCacheController.h new file mode 100644 index 00000000..354762eb --- /dev/null +++ b/view/sharedcache/core/SharedCacheController.h @@ -0,0 +1,65 @@ +#pragma once + +#include <regex> + +#include "SharedCache.h" +#include "refcountobject.h" +#include "ffi_global.h" + +DECLARE_DSC_API_OBJECT(BNSharedCacheController, SharedCacheController); + +void RegisterSharedCacheControllerDestructor(); + +namespace BinaryNinja::DSC { + static const char* METADATA_KEY = "shared_cache"; + + // Represents the view state for a given `DSCache` + class SharedCacheController : public DSCRefCountObject + { + IMPLEMENT_DSC_API_OBJECT(BNSharedCacheController); + Ref<Logger> m_logger; + + SharedCache m_cache; + // Store the open images. + // Things other than the cache here will be serialized. + std::unordered_set<uint64_t> m_loadedRegions; + std::unordered_set<uint64_t> m_loadedImages; + + // Settings from the view. + std::regex m_regionFilter; + bool m_processObjC; + bool m_processCFStrings; + + explicit SharedCacheController(SharedCache cache, Ref<Logger> logger); + + public: + // Initialize the DSCacheView, this should be called from the view initialize function only! + static DSCRef<SharedCacheController> Initialize(BinaryView& view, SharedCache cache); + + // NOTE: This will not create one if it does not exist. To create one for the view call `Initialize`. + static DSCRef<SharedCacheController> FromView(BinaryView& view); + + SharedCache& GetCache() { return m_cache; }; + const std::unordered_set<uint64_t>& GetLoadedRegions() { return m_loadedRegions; }; + const std::unordered_set<uint64_t>& GetLoadedImages() { return m_loadedImages; }; + + // TODO: LoadResult type? AlreadyLoaded, Loaded, NotLoaded. + // NOTE: `address` should be the start of a region, not containing the address. + bool ApplyRegionAtAddress(BinaryView& view, uint64_t address); + + bool ApplyRegion(BinaryView& view, const CacheRegion& region); + + bool IsRegionLoaded(const CacheRegion& region) const; + + // Loads the relevant image info into the view. This does not update analysis so if you + // call this make sure at some point you update analysis and likely with linear sweep. + bool ApplyImage(BinaryView& view, const CacheImage& image); + + bool IsImageLoaded(const CacheImage& image) const; + + // Get the metadata for saving the state of the shared cache. + Ref<Metadata> GetMetadata() const; + + void LoadMetadata(const Metadata& metadata); + }; +} // namespace BinaryNinja::DSC diff --git a/view/sharedcache/core/DSCView.cpp b/view/sharedcache/core/SharedCacheView.cpp index add2faff..bf80c6eb 100644 --- a/view/sharedcache/core/DSCView.cpp +++ b/view/sharedcache/core/SharedCacheView.cpp @@ -1,82 +1,208 @@ -// -// Created by kat on 5/23/23. -// +#include "SharedCacheView.h" +#include <regex> +#include "SharedCacheController.h" +#include "FileAccessorCache.h" +#include "MappedFileAccessor.h" -/* - * If you're here looking for the code to load caches, out of luck. - * - * The VIEW_NAME is essentially a blank slate that only knows how to deserialize and reserialize itself - * based on some metadata encoded in it. - * - * The actual controller logic that does _all_ of the image loading is invoked via API -> SharedCache.cpp - * - * */ +using namespace BinaryNinja; +using namespace BinaryNinja::DSC; -#include "DSCView.h" -#include "view/macho/machoview.h" -#include "SharedCache.h" +SharedCacheViewType::SharedCacheViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME) {} -using namespace BinaryNinja; +// We register all our one-shot stuff here, such as the object destructor. +void SharedCacheViewType::Register() +{ + auto fdLimit = AdjustFileDescriptorLimit(); + LogDebug("Shared Cache processing initialized with a max file descriptor limit of %lld", fdLimit); + + // Adjust the global accessor cache to the fdlimit. + FileAccessorCache::Global().SetCacheSize(fdLimit); + + // TODO: Register object destructor to clear accessor cache + RegisterSharedCacheControllerDestructor(); + + static SharedCacheViewType type; + BinaryViewType::Register(&type); +} + +Ref<BinaryView> SharedCacheViewType::Create(BinaryView* data) +{ + return new SharedCacheView(VIEW_NAME, data, false); +} + +Ref<Settings> SharedCacheViewType::GetLoadSettingsForData(BinaryView* data) +{ + Ref<BinaryView> viewRef = Parse(data); + if (!viewRef || !viewRef->Init()) + { + LogWarn("Failed to initialize view of type '%s'. Generating default load settings.", GetName().c_str()); + viewRef = data; + } + + Ref<Settings> settings = GetDefaultLoadSettingsForData(viewRef); + + // specify default load settings that can be overridden + std::vector<std::string> overrides = {"loader.imageBase", "loader.platform"}; + settings->UpdateProperty("loader.imageBase", "message", "Note: File indicates image is not relocatable."); + + for (const auto& override : overrides) + { + if (settings->Contains(override)) + settings->UpdateProperty(override, "readOnly", false); + } + + Ref<Settings> programSettings = Settings::Instance(); + programSettings->Set("analysis.workflows.functionWorkflow", "core.function.sharedCache", viewRef); + + settings->RegisterSetting("loader.dsc.processCFStrings", + R"({ + "title" : "Process CFString Metadata", + "type" : "boolean", + "default" : true, + "description" : "Processes CoreFoundation strings, applying string values from encoded metadata" + })"); + + settings->RegisterSetting("loader.dsc.autoLoadPattern", + R"({ + "title" : "Image Auto-Load Regex Pattern", + "type" : "string", + "default" : ".*libsystem_c.dylib", + "description" : "A regex pattern to auto load matching images at the end of view init, defaults to the system_c image only." + })"); + + settings->RegisterSetting("loader.dsc.processObjC", + R"({ + "title" : "Process Objective-C Metadata", + "type" : "boolean", + "default" : true, + "description" : "Processes Objective-C metadata, applying class and method names from encoded metadata" + })"); + + settings->RegisterSetting("loader.dsc.regionFilter", + R"({ + "title" : "Region Regex Filter", + "type" : "string", + "default" : "LINKEDIT", + "description" : "Regex filter for region names to skip loading, by default this filters out the link edit region which is not necessary to be applied to the view." + })"); + + settings->RegisterSetting("loader.dsc.autoLoadObjCStubRequirements", + R"({ + "title" : "Auto-Load Objective-C Stub Requirements", + "type" : "boolean", + "default" : true, + "description" : "Automatically loads segments required for inlining Objective-C stubs. Recommended you keep this on." + })"); + + settings->RegisterSetting("loader.dsc.autoLoadStubsAndDyldData", + R"({ + "title" : "Auto-Load Stub Islands", + "type" : "boolean", + "default" : true, + "description" : "Automatically loads stub and dylddata regions that contain just branches and pointers. These are required for resolving stub names, and performance impact is minimal. Recommended you keep this on." + })"); + + settings->RegisterSetting("loader.dsc.processFunctionStarts", + R"({ + "title" : "Process Mach-O Function Starts Tables", + "type" : "boolean", + "default" : true, + "description" : "Add function starts sourced from the Function Starts tables to the core for analysis." + })"); + // Merge existing load settings if they exist. This allows for the selection of a specific object file from a Mach-O + // Universal file. The 'Universal' BinaryViewType generates a schema with 'loader.universal.architectures'. This + // schema contains an appropriate 'Mach-O' load schema for selecting a specific object file. The embedded schema + // contains 'loader.macho.universalImageOffset'. + Ref<Settings> loadSettings = viewRef->GetLoadSettings(GetName()); + if (loadSettings && !loadSettings->IsEmpty()) + settings->DeserializeSchema(loadSettings->SerializeSchema()); -DSCView::DSCView(const std::string& typeName, BinaryView* data, bool parseOnly) : + return settings; +} + +Ref<BinaryView> SharedCacheViewType::Parse(BinaryView* data) +{ + return new SharedCacheView(VIEW_NAME, data, true); +} + +bool SharedCacheViewType::IsTypeValidForData(BinaryView* data) +{ + if (!data) + return false; + + DataBuffer sig = data->ReadBuffer(data->GetStart(), 4); + if (sig.GetLength() != 4) + return false; + + const char* magic = (char*)sig.GetData(); + if (strncmp(magic, "dyld", 4) == 0) + return true; + + return false; +} + +SharedCacheView::SharedCacheView(const std::string& typeName, BinaryView* data, bool parseOnly) : BinaryView(typeName, data->GetFile(), data), m_parseOnly(parseOnly) { CreateLogger("SharedCache"); CreateLogger("SharedCache.ObjC"); } -DSCView::~DSCView() +enum DSCPlatform { -} - -enum DSCPlatform { DSCPlatformMacOS = 1, DSCPlatformiOS = 2, DSCPlatformTVOS = 3, DSCPlatformWatchOS = 4, - DSCPlatformBridgeOS = 5, // T1/T2 APL1023/T8012, this is your touchbar/touchid in intel macs. Similar to watchOS. + DSCPlatformBridgeOS = 5, // T1/T2 APL1023/T8012, this is your touchbar/touchid in intel macs. Similar to watchOS. // DSCPlatformMacCatalyst = 6, DSCPlatformiOSSimulator = 7, DSCPlatformTVOSSimulator = 8, DSCPlatformWatchOSSimulator = 9, - DSCPlatformVisionOS = 11, // Apple Vision Pro - DSCPlatformVisionOSSimulator = 12 // Apple Vision Pro Simulator + DSCPlatformVisionOS = 11, // Apple Vision Pro + DSCPlatformVisionOSSimulator = 12 // Apple Vision Pro Simulator }; -bool DSCView::Init() +bool SharedCacheView::Init() { std::string os; std::string arch; + auto logger = new Logger("SharedCacheView", GetFile()->GetSessionId()); + uint32_t platform; GetParentView()->Read(&platform, 0xd8, 4); char magic[17]; GetParentView()->Read(&magic, 0, 16); magic[16] = 0; + + // TODO: Do we want to add any warnings about platform support here? + // TODO: Do we still consider macos experimental? switch (platform) { - case DSCPlatformMacOS: - case DSCPlatformTVOS: - case DSCPlatformTVOSSimulator: - os = "mac"; - break; - case DSCPlatformiOS: - case DSCPlatformiOSSimulator: - case DSCPlatformVisionOS: - case DSCPlatformVisionOSSimulator: - os = "ios"; - break; - // armv7 or slide info v1 (unsupported) - case DSCPlatformWatchOS: - case DSCPlatformWatchOSSimulator: - case DSCPlatformBridgeOS: - default: - LogError("Unknown platform: %d", platform); - return false; + case DSCPlatformMacOS: + case DSCPlatformTVOS: + case DSCPlatformTVOSSimulator: + os = "mac"; + break; + case DSCPlatformiOS: + case DSCPlatformiOSSimulator: + case DSCPlatformVisionOS: + case DSCPlatformVisionOSSimulator: + os = "ios"; + break; + // armv7 or slide info v1 (unsupported) + case DSCPlatformWatchOS: + case DSCPlatformWatchOSSimulator: + case DSCPlatformBridgeOS: + default: + logger->LogError("Unknown platform: %d", platform); + return false; } - if (std::string(magic) == "dyld_v1 arm64" || std::string(magic) == "dyld_v1 arm64e" || std::string(magic) == "dyld_v1arm64_32") + if (std::string(magic) == "dyld_v1 arm64" || std::string(magic) == "dyld_v1 arm64e" + || std::string(magic) == "dyld_v1arm64_32") { arch = "aarch64"; } @@ -86,24 +212,38 @@ bool DSCView::Init() } else { - LogError("Unknown magic: %s", magic); + logger->LogError("Unknown magic: %s", magic); return false; } SetDefaultPlatform(Platform::GetByName(os + "-" + arch)); SetDefaultArchitecture(Architecture::GetByName(arch)); + Ref<Settings> settings = GetLoadSettings(GetTypeName()); + + if (!settings) + { + Ref<Settings> programSettings = Settings::Instance(); + programSettings->Set("analysis.workflows.functionWorkflow", "core.function.sharedCache", this); + } + + if (m_parseOnly) + return true; + QualifiedNameAndType headerType; std::string err; - ParseTypeString("\n" + ParseTypeString( + "\n" "\tstruct dyld_cache_header\n" "\t{\n" "\t\tchar magic[16];\t\t\t\t\t // e.g. \"dyld_v0 i386\"\n" "\t\tuint32_t mappingOffset;\t\t\t // file offset to first dyld_cache_mapping_info\n" "\t\tuint32_t mappingCount;\t\t\t // number of dyld_cache_mapping_info entries\n" - "\t\tuint32_t imagesOffsetOld;\t\t // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing\n" - "\t\tuint32_t imagesCountOld;\t\t // UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing\n" + "\t\tuint32_t imagesOffsetOld;\t\t // UNUSED: moved to imagesOffset to prevent older dsc_extarctors from " + "crashing\n" + "\t\tuint32_t imagesCountOld;\t\t // UNUSED: moved to imagesCount to prevent older dsc_extarctors from " + "crashing\n" "\t\tuint64_t dyldBaseAddress;\t\t // base address of dyld when cache was built\n" "\t\tuint64_t codeSignatureOffset;\t // file offset of code signature blob\n" "\t\tuint64_t codeSignatureSize;\t\t // size of code signature blob (zero means to end of file)\n" @@ -120,7 +260,8 @@ bool DSCView::Init() "\t\tuint64_t imagesTextOffset;\t\t // file offset to first dyld_cache_image_text_info\n" "\t\tuint64_t imagesTextCount;\t\t // number of dyld_cache_image_text_info entries\n" "\t\tuint64_t patchInfoAddr;\t\t\t // (unslid) address of dyld_cache_patch_info\n" - "\t\tuint64_t patchInfoSize;\t // Size of all of the patch information pointed to via the dyld_cache_patch_info\n" + "\t\tuint64_t patchInfoSize;\t // Size of all of the patch information pointed to via the " + "dyld_cache_patch_info\n" "\t\tuint64_t otherImageGroupAddrUnused;\t // unused\n" "\t\tuint64_t otherImageGroupSizeUnused;\t // unused\n" "\t\tuint64_t progClosuresAddr;\t\t\t // (unslid) address of list of program launch closures\n" @@ -129,10 +270,12 @@ bool DSCView::Init() "\t\tuint64_t progClosuresTrieSize;\t\t // size of trie of indexes into program launch closures\n" "\t\tuint32_t platform;\t\t\t\t\t // platform number (macOS=1, etc)\n" "\t\tuint32_t formatVersion : 8,\t\t\t // dyld3::closure::kFormatVersion\n" - "\t\t\tdylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to see if cache is valid\n" + "\t\t\tdylibsExpectedOnDisk : 1, // dyld should expect the dylib exists on disk and to compare inode/mtime to " + "see if cache is valid\n" "\t\t\tsimulator : 1,\t\t\t // for simulator of specified platform\n" "\t\t\tlocallyBuiltCache : 1,\t // 0 for B&I built cache, 1 for locally built cache\n" - "\t\t\tbuiltFromChainedFixups : 1,\t // some dylib in cache was built using chained fixups, so patch tables must be used for overrides\n" + "\t\t\tbuiltFromChainedFixups : 1,\t // some dylib in cache was built using chained fixups, so patch tables " + "must be used for overrides\n" "\t\t\tpadding : 20;\t\t\t\t // TBD\n" "\t\tuint64_t sharedRegionStart;\t\t // base load address of cache if not slid\n" "\t\tuint64_t sharedRegionSize;\t\t // overall size required to map the cache and all subCaches, if any\n" @@ -141,9 +284,11 @@ bool DSCView::Init() "\t\tuint64_t dylibsImageArraySize;\t // size of ImageArray for dylibs in this cache\n" "\t\tuint64_t dylibsTrieAddr;\t\t // (unslid) address of trie of indexes of all cached dylibs\n" "\t\tuint64_t dylibsTrieSize;\t\t // size of trie of cached dylib paths\n" - "\t\tuint64_t otherImageArrayAddr;\t // (unslid) address of ImageArray for dylibs and bundles with dlopen closures\n" + "\t\tuint64_t otherImageArrayAddr;\t // (unslid) address of ImageArray for dylibs and bundles with dlopen " + "closures\n" "\t\tuint64_t otherImageArraySize;\t // size of ImageArray for dylibs and bundles with dlopen closures\n" - "\t\tuint64_t otherTrieAddr;\t // (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures\n" + "\t\tuint64_t otherTrieAddr;\t // (unslid) address of trie of indexes of all dylibs and bundles with dlopen " + "closures\n" "\t\tuint64_t otherTrieSize;\t // size of trie of dylibs and bundles with dlopen closures\n" "\t\tuint32_t mappingWithSlideOffset;\t\t // file offset to first dyld_cache_mapping_and_slide_info\n" "\t\tuint32_t mappingWithSlideCount;\t\t\t // number of dyld_cache_mapping_and_slide_info entries\n" @@ -160,49 +305,37 @@ bool DSCView::Init() "\t\tuint64_t swiftOptsSize;\t\t\t// size of Swift optimizations header\n" "\t\tuint32_t subCacheArrayOffset;\t// file offset to first dyld_subcache_entry\n" "\t\tuint32_t subCacheArrayCount;\t// number of subCache entries\n" - "\t\tuint8_t symbolFileUUID[16];\t\t// unique value for the shared cache file containing unmapped local symbols\n" - "\t\tuint64_t rosettaReadOnlyAddr;\t// (unslid) address of the start of where Rosetta can add read-only/executable data\n" + "\t\tuint8_t symbolFileUUID[16];\t\t// unique value for the shared cache file containing unmapped local " + "symbols\n" + "\t\tuint64_t rosettaReadOnlyAddr;\t// (unslid) address of the start of where Rosetta can add " + "read-only/executable data\n" "\t\tuint64_t rosettaReadOnlySize;\t// maximum size of the Rosetta read-only/executable region\n" - "\t\tuint64_t rosettaReadWriteAddr;\t// (unslid) address of the start of where Rosetta can add read-write data\n" + "\t\tuint64_t rosettaReadWriteAddr;\t// (unslid) address of the start of where Rosetta can add read-write " + "data\n" "\t\tuint64_t rosettaReadWriteSize;\t// maximum size of the Rosetta read-write region\n" "\t\tuint32_t imagesOffset;\t\t\t// file offset to first dyld_cache_image_info\n" "\t\tuint32_t imagesCount;\t\t\t// number of dyld_cache_image_info entries\n" - "\t\tuint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is multi-cache(2)\n" + "\t\tuint32_t cacheSubType; // 0 for development, 1 for production, when cacheType is " + "multi-cache(2)\n" "\t\tuint64_t objcOptsOffset; // VM offset from cache_header* to ObjC optimizations header\n" "\t\tuint64_t objcOptsSize; // size of ObjC optimizations header\n" - "\t\tuint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process introspection\n" + "\t\tuint64_t cacheAtlasOffset; // VM offset from cache_header* to embedded cache atlas for process " + "introspection\n" "\t\tuint64_t cacheAtlasSize; // size of embedded cache atlas\n" - "\t\tuint64_t dynamicDataOffset; // VM offset from cache_header* to the location of dyld_cache_dynamic_data_header\n" + "\t\tuint64_t dynamicDataOffset; // VM offset from cache_header* to the location of " + "dyld_cache_dynamic_data_header\n" "\t\tuint64_t dynamicDataMaxSize; // maximum size of space reserved from dynamic data\n" "\t\tuint32_t tproMappingsOffset; // file offset to first dyld_cache_tpro_mapping_info\n" "\t\tuint32_t tproMappingsCount; // number of dyld_cache_tpro_mapping_info entries\n" - "\t};", headerType, err); + "\t};", + headerType, err); if (!err.empty() || !headerType.type) { - LogError("Failed to parse header type: %s", err.c_str()); + logger->LogError("Failed to parse header type: %s", err.c_str()); return false; } - Ref<Settings> settings = GetLoadSettings(GetTypeName()); - - if (!settings) - { - Ref<Settings> programSettings = Settings::Instance(); - programSettings->Set("analysis.workflows.functionWorkflow", "core.function.dsc", this); - } - - if (m_parseOnly) - return true; - - // Check the metadata version if there is one, so we can alert the user to why they have no loaded images. - // TODO: Once the view is able to be exited early we should offer to close the view if the user does not want to upgrade. - auto metadataVersion = SharedCacheCore::SharedCacheMetadata::ViewMetadataVersion(GetParentView()); - if (metadataVersion.has_value() && metadataVersion.value() != METADATA_VERSION) - { - ShowMessageBox("Invalid Shared Cache Metadata!", "The BNDB shared cache metadata was created with a different version of the Shared Cache view, to continue the metadata has to be recreated. You will need to add your images back again."); - } - // Add Mach-O file header type info EnumerationBuilder cpuTypeBuilder; cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ANY", MACHO_CPU_TYPE_ANY); @@ -335,17 +468,17 @@ bool DSCView::Init() cmdTypeBuilder.AddMemberWithValue("LC_RPATH", LC_RPATH); // (0x1c | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_CODE_SIGNATURE", LC_CODE_SIGNATURE); cmdTypeBuilder.AddMemberWithValue("LC_SEGMENT_SPLIT_INFO", LC_SEGMENT_SPLIT_INFO); - cmdTypeBuilder.AddMemberWithValue("LC_REEXPORT_DYLIB", LC_REEXPORT_DYLIB); // (0x1f | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_REEXPORT_DYLIB", LC_REEXPORT_DYLIB); // (0x1f | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_LAZY_LOAD_DYLIB", LC_LAZY_LOAD_DYLIB); cmdTypeBuilder.AddMemberWithValue("LC_ENCRYPTION_INFO", LC_ENCRYPTION_INFO); cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO", LC_DYLD_INFO); - cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO_ONLY", LC_DYLD_INFO_ONLY); // (0x22 | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_DYLD_INFO_ONLY", LC_DYLD_INFO_ONLY); // (0x22 | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_LOAD_UPWARD_DYLIB", LC_LOAD_UPWARD_DYLIB); // (0x23 | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_MACOSX", LC_VERSION_MIN_MACOSX); cmdTypeBuilder.AddMemberWithValue("LC_VERSION_MIN_IPHONEOS", LC_VERSION_MIN_IPHONEOS); cmdTypeBuilder.AddMemberWithValue("LC_FUNCTION_STARTS", LC_FUNCTION_STARTS); cmdTypeBuilder.AddMemberWithValue("LC_DYLD_ENVIRONMENT", LC_DYLD_ENVIRONMENT); - cmdTypeBuilder.AddMemberWithValue("LC_MAIN", LC_MAIN); // (0x28 | LC_REQ_DYLD) + cmdTypeBuilder.AddMemberWithValue("LC_MAIN", LC_MAIN); // (0x28 | LC_REQ_DYLD) cmdTypeBuilder.AddMemberWithValue("LC_DATA_IN_CODE", LC_DATA_IN_CODE); cmdTypeBuilder.AddMemberWithValue("LC_SOURCE_VERSION", LC_SOURCE_VERSION); cmdTypeBuilder.AddMemberWithValue("LC_DYLIB_CODE_SIGN_DRS", LC_DYLIB_CODE_SIGN_DRS); @@ -622,14 +755,14 @@ bool DSCView::Init() GetParentView()->Read(&basePointer, 16, 4); if (basePointer == 0) { - LogError("Failed to read base pointer"); + logger->LogError("Failed to read base pointer"); return false; } uint64_t primaryBase = 0; GetParentView()->Read(&primaryBase, basePointer, 8); if (primaryBase == 0) { - LogError("Failed to read primary base at 0x%llx", basePointer); + logger->LogError("Failed to read primary base at 0x%llx", basePointer); return false; } @@ -641,131 +774,81 @@ bool DSCView::Init() AddAutoSegment(primaryBase, headerSize, 0, headerSize, SegmentReadable); AddAutoSection("__dsc_header", primaryBase, headerSize, ReadOnlyDataSectionSemantics); DefineType("dyld_cache_header", headerType.name, headerType.type); - DefineAutoSymbolAndVariableOrFunction(GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type); + DefineAutoSymbolAndVariableOrFunction( + GetDefaultPlatform(), new Symbol(DataSymbol, "primary_cache_header", primaryBase), headerType.type); - return true; -} - - -DSCViewType::DSCViewType() : BinaryViewType(VIEW_NAME, VIEW_NAME) -{ -} + auto sharedCache = SharedCache(GetAddressSize()); -BinaryNinja::Ref<BinaryNinja::BinaryView> DSCViewType::Create(BinaryNinja::BinaryView* data) -{ - return new DSCView(VIEW_NAME, data, false); -} - - -Ref<Settings> DSCViewType::GetLoadSettingsForData(BinaryView* data) -{ - Ref<BinaryView> viewRef = Parse(data); - if (!viewRef || !viewRef->Init()) { - LogWarn("Failed to initialize view of type '%s'. Generating default load settings.", GetName().c_str()); - viewRef = data; + // Add up all the cache entries using the cache processor. + // After this we should have all the mappings available as well. + CacheProcessor cacheProcessor = CacheProcessor(this); + auto startTime = std::chrono::high_resolution_clock::now(); + if (!cacheProcessor.ProcessCache(sharedCache)) + { + // Oh no, we failed to process the cache, this likely means the primary on-disk file was not able to be + // found. + // TODO: Prompt the user to select the primary cache file? We can still recover from here. + logger->LogError("Failed to process cache, likely missing cache files."); + // NOTE: An interaction handler headlessly could select yes to this, however for the purposes of this we will just let them continue by defualt. + if (IsUIEnabled() && ShowMessageBox("Continue opening", "This shared cache file was unable to be processed, would you like to open without shared cache information?", YesNoButtonSet, QuestionIcon) != YesButton) + return false; + } + auto endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double> elapsed = endTime - startTime; + logger->LogInfo("Processing %zu entries took %.3f seconds", sharedCache.GetEntries().size(), elapsed.count()); } - Ref<Settings> settings = GetDefaultLoadSettingsForData(viewRef); - - // specify default load settings that can be overridden - std::vector<std::string> overrides = {"loader.imageBase", "loader.platform"}; - settings->UpdateProperty("loader.imageBase", "message", "Note: File indicates image is not relocatable."); - - for (const auto& override : overrides) { - if (settings->Contains(override)) - settings->UpdateProperty(override, "readOnly", false); + // Write all the slide info pointers to the virtual memory. + // This should be done before any other work begins, as the backing data will be altered by this process. + auto startTime = std::chrono::high_resolution_clock::now(); + for (const auto& [_, entry] : sharedCache.GetEntries()) + sharedCache.ProcessEntrySlideInfo(entry); + auto endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double> elapsed = endTime - startTime; + logger->LogInfo("Processing slide info took %.3f seconds", elapsed.count()); } - Ref<Settings> programSettings = Settings::Instance(); - programSettings->Set("analysis.workflows.functionWorkflow", "core.function.dsc", viewRef); - - settings->RegisterSetting("loader.dsc.processCFStrings", - R"({ - "title" : "Process CFString Metadata", - "type" : "boolean", - "default" : true, - "description" : "Processes CoreFoundation strings, applying string values from encoded metadata" - })"); - - settings->RegisterSetting("loader.dsc.autoLoadLibSystem", - R"({ - "title" : "Auto-Load libSystem", - "type" : "boolean", - "default" : true, - "description" : "Whether to automatically load libsystem_c.dylib. This image contains frequently used noreturn symbols, and not loading it will result in frequently incorrect control flows." - })"); - - settings->RegisterSetting("loader.dsc.processObjC", - R"({ - "title" : "Process Objective-C Metadata", - "type" : "boolean", - "default" : true, - "description" : "Processes Objective-C metadata, applying class and method names from encoded metadata" - })"); - - settings->RegisterSetting("loader.dsc.autoLoadObjCStubRequirements", - R"({ - "title" : "Auto-Load Objective-C Stub Requirements", - "type" : "boolean", - "default" : true, - "description" : "Automatically loads segments required for inlining Objective-C stubs. Recommended you keep this on." - })"); - - settings->RegisterSetting("loader.dsc.autoLoadStubsAndDyldData", - R"({ - "title" : "Auto-Load Stub Islands", - "type" : "boolean", - "default" : true, - "description" : "Automatically loads stub and dylddata regions that contain just branches and pointers. These are required for resolving stub names, and performance impact is minimal. Recommended you keep this on." - })"); - - settings->RegisterSetting("loader.dsc.allowLoadingLinkeditSegments", - R"({ - "title" : "Allow Loading __LINKEDIT Segments", - "type" : "boolean", - "default" : false, - "description" : "Allow mapping __LINKEDIT segments. These are large regions of symbol data that are automatically processed by BinaryNinja without the need for mapping. On newer caches, __LINKEDIT for all images may end up merged and be >300MB in size. This will likely cause severe performance degradation with _zero_ benefit." - })"); - - settings->RegisterSetting("loader.dsc.processFunctionStarts", - R"({ - "title" : "Process Mach-O Function Starts Tables", - "type" : "boolean", - "default" : true, - "description" : "Add function starts sourced from the Function Starts tables to the core for analysis." - })"); - - // Merge existing load settings if they exist. This allows for the selection of a specific object file from a Mach-O - // Universal file. The 'Universal' BinaryViewType generates a schema with 'loader.universal.architectures'. This - // schema contains an appropriate 'Mach-O' load schema for selecting a specific object file. The embedded schema - // contains 'loader.macho.universalImageOffset'. - Ref<Settings> loadSettings = viewRef->GetLoadSettings(GetName()); - if (loadSettings && !loadSettings->IsEmpty()) - settings->DeserializeSchema(loadSettings->SerializeSchema()); - - return settings; -} - + { + // Load up all images into the cache before adding extra regions. + // Currently, it is expected that a primary entry contain all relevant images, so we only check that one. + auto startTime = std::chrono::high_resolution_clock::now(); + for (const auto& [_, entry] : sharedCache.GetEntries()) + if (entry.GetType() == CacheEntryType::Primary) + sharedCache.ProcessEntryImages(entry); + auto endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double> elapsed = endTime - startTime; + auto images = sharedCache.GetImages(); + logger->LogInfo("Processing %zu images took %.3f seconds", images.size(), elapsed.count()); + // Warn if we found no images, and provide the likely explanation + if (images.empty()) + logger->LogWarn("Failed to process any images, likely missing cache files."); + } -BinaryNinja::Ref<BinaryNinja::BinaryView> DSCViewType::Parse(BinaryNinja::BinaryView* data) -{ - return new DSCView(VIEW_NAME, data, true); -} + { + // TODO: Run this up on a separate thread maybe and have callback notifications? + // Load up all the regions into the cache. + auto startTime = std::chrono::high_resolution_clock::now(); + for (const auto& [_, entry] : sharedCache.GetEntries()) + sharedCache.ProcessEntryRegions(entry); + auto endTime = std::chrono::high_resolution_clock::now(); + std::chrono::duration<double> elapsed = endTime - startTime; + logger->LogInfo("Processing %zu regions took %.3f seconds", sharedCache.GetRegions().size(), elapsed.count()); + } -bool DSCViewType::IsTypeValidForData(BinaryNinja::BinaryView* data) -{ - if (!data) - return false; + auto cacheController = SharedCacheController::Initialize(*this, std::move(sharedCache)); - DataBuffer sig = data->ReadBuffer(data->GetStart(), 4); - if (sig.GetLength() != 4) - return false; + // Users can adjust which images are loaded by default using the `loader.dsc.autoLoadPattern` setting. + std::string autoLoadPattern = ".*libsystem_c.dylib"; + if (settings && settings->Contains("loader.dsc.autoLoadPattern")) + autoLoadPattern = settings->Get<std::string>("loader.dsc.autoLoadPattern", this); + logger->LogDebug("Loading images using pattern: %s", autoLoadPattern.c_str()); - const char* magic = (char*)sig.GetData(); - if (strncmp(magic, "dyld", 4) == 0) - return true; + std::regex autoLoadRegex(autoLoadPattern); + for (const auto& [_, image] : cacheController->GetCache().GetImages()) + if (std::regex_match(image.GetName(), autoLoadRegex)) + cacheController->ApplyImage(*this, image); - return false; + return true; } diff --git a/view/sharedcache/core/SharedCacheView.h b/view/sharedcache/core/SharedCacheView.h new file mode 100644 index 00000000..1ead0b33 --- /dev/null +++ b/view/sharedcache/core/SharedCacheView.h @@ -0,0 +1,43 @@ +// +// Created by kat on 5/23/23. +// + +#ifndef SHAREDCACHE_DSCVIEW_H +#define SHAREDCACHE_DSCVIEW_H + +#include <binaryninjaapi.h> + + +class SharedCacheView : public BinaryNinja::BinaryView +{ + bool m_parseOnly; + +public: + SharedCacheView(const std::string& typeName, BinaryView* data, bool parseOnly = false); + + ~SharedCacheView() override = default; + + bool Init() override; +}; + + +class SharedCacheViewType : public BinaryNinja::BinaryViewType +{ +public: + SharedCacheViewType(); + + static void Register(); + + BinaryNinja::Ref<BinaryNinja::BinaryView> Create(BinaryNinja::BinaryView* data) override; + + BinaryNinja::Ref<BinaryNinja::BinaryView> Parse(BinaryNinja::BinaryView* data) override; + + bool IsTypeValidForData(BinaryNinja::BinaryView* data) override; + + bool IsDeprecated() override { return false; } + + BinaryNinja::Ref<BinaryNinja::Settings> GetLoadSettingsForData(BinaryNinja::BinaryView* data) override; +}; + + +#endif // SHAREDCACHE_DSCVIEW_H diff --git a/view/sharedcache/core/SlideInfo.cpp b/view/sharedcache/core/SlideInfo.cpp new file mode 100644 index 00000000..e258a90c --- /dev/null +++ b/view/sharedcache/core/SlideInfo.cpp @@ -0,0 +1,292 @@ +#include "SlideInfo.h" + +#include "Dyld.h" +#include "SharedCache.h" +#include "Utility.h" + +SlideInfoProcessor::SlideInfoProcessor(uint64_t baseAddress) +{ + m_logger = new BinaryNinja::Logger("SlideInfoProcessor"); + m_baseAddress = baseAddress; +} + +void ApplySlideInfoV5(VirtualMemory& vm, const SlideMappingInfo& mapping) +{ + uint64_t pageStartsOffset = mapping.address + sizeof(dyld_cache_slide_info_v5); + uint64_t pageStartCount = mapping.slideInfoV5.page_starts_count; + uint64_t pageSize = mapping.slideInfoV5.page_size; + + uint64_t offset; + // Retrieve this once so we don't need to keep querying the region through the VM. + auto region = vm.GetRegionAtAddress(mapping.address, offset); + if (!region.has_value()) + throw UnmappedRegionException(mapping.address); + + auto cursor = pageStartsOffset; + for (size_t i = 0; i < pageStartCount; i++) + { + uint16_t delta = vm.ReadUInt16(cursor); + cursor += sizeof(uint16_t); + if (delta == DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE) + continue; + + delta = delta / sizeof(uint64_t); // initial offset is byte based + uint64_t loc = mapping.mappingInfo.fileOffset + (pageSize * i); + do + { + loc += delta * sizeof(dyld_cache_slide_pointer5); + dyld_cache_slide_pointer5 slideInfo = {vm.ReadUInt64(loc)}; + delta = slideInfo.regular.next; + if (slideInfo.auth.auth) + { + uint64_t value = mapping.slideInfoV5.value_add + slideInfo.auth.runtimeOffset; + vm.WritePointer(loc, value); + } + else + { + uint64_t value = mapping.slideInfoV5.value_add + slideInfo.regular.runtimeOffset; + vm.WritePointer(loc, value); + } + } while (delta != 0); + } +} + +void ApplySlideInfoV3(VirtualMemory& vm, const SlideMappingInfo& mapping) +{ + uint64_t pageStartsOffset = mapping.address + sizeof(dyld_cache_slide_info_v3); + uint64_t pageStartCount = mapping.slideInfoV3.page_starts_count; + uint64_t pageSize = mapping.slideInfoV3.page_size; + + auto cursor = pageStartsOffset; + for (size_t i = 0; i < pageStartCount; i++) + { + uint16_t delta = vm.ReadUInt16(cursor); + cursor += sizeof(uint16_t); + if (delta == DYLD_CACHE_SLIDE_V3_PAGE_ATTR_NO_REBASE) + continue; + + delta = delta / sizeof(uint64_t); // initial offset is byte based + uint64_t loc = mapping.mappingInfo.fileOffset + (pageSize * i); + do + { + loc += delta * sizeof(dyld_cache_slide_pointer3); + dyld_cache_slide_pointer3 slideInfo = {vm.ReadUInt64(loc)}; + delta = slideInfo.plain.offsetToNextPointer; + + if (slideInfo.auth.authenticated) + { + uint64_t value = slideInfo.auth.offsetFromSharedCacheBase; + value += mapping.slideInfoV3.auth_value_add; + vm.WritePointer(loc, value); + } + else + { + uint64_t value51 = slideInfo.plain.pointerValue; + uint64_t top8Bits = value51 & 0x0007F80000000000; + uint64_t bottom43Bits = value51 & 0x000007FFFFFFFFFF; + uint64_t value = (uint64_t)top8Bits << 13 | bottom43Bits; + vm.WritePointer(loc, value); + } + } while (delta != 0); + } +} + +void ApplySlideInfoV2(VirtualMemory& vm, const SlideMappingInfo& mapping) +{ + auto rebaseChain = [&](const dyld_cache_slide_info_v2& slideInfo, uint64_t pageContent, uint16_t startOffset) { + // TODO: This is always zero? + // TODO: This is probably something for runtime offsets provided to the shared cache. + uintptr_t slideAmount = 0; + + auto deltaMask = slideInfo.delta_mask; + auto valueMask = ~deltaMask; + auto valueAdd = slideInfo.value_add; + + auto deltaShift = CountTrailingZeros(deltaMask) - 2; + + uint32_t pageOffset = startOffset; + uint32_t delta = 1; + while (delta != 0) + { + uint64_t loc = pageContent + pageOffset; + uintptr_t rawValue = vm.ReadUInt64(loc); + delta = (uint32_t)((rawValue & deltaMask) >> deltaShift); + uintptr_t value = (rawValue & valueMask); + if (value != 0) + { + value += valueAdd; + // TODO: slideAmount += value? + // TODO: slideAmount is always zero? What is this suppose to do? + value += slideAmount; + } + pageOffset += delta; + vm.WritePointer(loc, value); + } + }; + + uint64_t extrasOffset = mapping.address + mapping.slideInfoV2.page_extras_offset; + uint64_t pageStartsOffset = mapping.address + sizeof(dyld_cache_slide_info_v2); + uint64_t pageStartCount = mapping.slideInfoV2.page_starts_count; + uint64_t pageSize = mapping.slideInfoV2.page_size; + + auto cursor = pageStartsOffset; + for (size_t i = 0; i < pageStartCount; i++) + { + uint16_t start = vm.ReadUInt16(cursor); + cursor += sizeof(uint16_t); + if (start == DYLD_CACHE_SLIDE_PAGE_ATTR_NO_REBASE) + continue; + + if (start & DYLD_CACHE_SLIDE_PAGE_ATTR_EXTRA) + { + int j = (start & 0x3FFF); + bool done = false; + do + { + uint64_t extraCursor = extrasOffset + (j * sizeof(uint16_t)); + auto extra = vm.ReadUInt16(extraCursor); + uint16_t aStart = extra; + uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i); + uint16_t pageStartOffset = (aStart & 0x3FFF) * 4; + rebaseChain(mapping.slideInfoV2, page, pageStartOffset); + done = (extra & DYLD_CACHE_SLIDE_PAGE_ATTR_END); + ++j; + } while (!done); + } + else + { + uint64_t page = mapping.mappingInfo.fileOffset + (pageSize * i); + uint16_t pageStartOffset = start * 4; + rebaseChain(mapping.slideInfoV2, page, pageStartOffset); + } + } +} + +std::vector<SlideMappingInfo> SlideInfoProcessor::ReadEntryInfo(VirtualMemory& vm, const CacheEntry& entry) const +{ + auto& baseHeader = entry.GetHeader(); + + // Handle legacy, single mapping slide info. + if (baseHeader.slideInfoOffsetUnused) + { + auto slideInfoAddress = entry.GetMappedAddress(baseHeader.slideInfoOffsetUnused); + if (!slideInfoAddress.has_value()) + { + m_logger->LogError("Unmapped slide info address %llx", slideInfoAddress); + return {}; + } + auto slideInfoVersion = vm.ReadUInt32(*slideInfoAddress); + if (slideInfoVersion != 2 && slideInfoVersion != 3) + { + m_logger->LogError("Unsupported slide info version %d", slideInfoVersion); + return {}; + } + + SlideMappingInfo singleMapping = {}; + singleMapping.address = *slideInfoAddress; + + auto mappingAddress = entry.GetMappedAddress(baseHeader.mappingOffset + sizeof(dyld_cache_mapping_info)); + if (!mappingAddress.has_value()) + { + m_logger->LogError("Unmapped mapping address %llx", mappingAddress); + return {}; + } + vm.Read(&singleMapping.mappingInfo, *mappingAddress, sizeof(dyld_cache_mapping_info)); + singleMapping.slideInfoVersion = slideInfoVersion; + if (singleMapping.slideInfoVersion == 2) + vm.Read(&singleMapping.slideInfoV2, *slideInfoAddress, sizeof(dyld_cache_slide_info_v2)); + else if (singleMapping.slideInfoVersion == 3) + vm.Read(&singleMapping.slideInfoV3, *slideInfoAddress, sizeof(dyld_cache_slide_info_v3)); + + return {singleMapping}; + } + + std::vector<SlideMappingInfo> mappings = {}; + for (auto i = 0; i < baseHeader.mappingWithSlideCount; i++) + { + dyld_cache_mapping_and_slide_info mappingAndSlideInfo = {}; + auto mappingAndSlideInfoAddress = + entry.GetMappedAddress(baseHeader.mappingWithSlideOffset + (i * sizeof(dyld_cache_mapping_and_slide_info))); + if (!mappingAndSlideInfoAddress.has_value()) + { + m_logger->LogError("Unmapped mapping and slide info address %llx", mappingAndSlideInfoAddress); + continue; + } + + vm.Read(&mappingAndSlideInfo, *mappingAndSlideInfoAddress, sizeof(dyld_cache_mapping_and_slide_info)); + if (mappingAndSlideInfo.size == 0 || mappingAndSlideInfo.slideInfoFileOffset == 0) + continue; + + SlideMappingInfo map = {}; + map.address = *entry.GetMappedAddress(mappingAndSlideInfo.slideInfoFileOffset); + map.slideInfoVersion = vm.ReadUInt32(map.address); + map.mappingInfo.address = mappingAndSlideInfo.address; + map.mappingInfo.size = mappingAndSlideInfo.size; + map.mappingInfo.fileOffset = *entry.GetMappedAddress(mappingAndSlideInfo.fileOffset); + if (map.slideInfoVersion == 2) + { + vm.Read(&map.slideInfoV2, map.address, sizeof(dyld_cache_slide_info_v2)); + } + else if (map.slideInfoVersion == 3) + { + vm.Read(&map.slideInfoV3, map.address, sizeof(dyld_cache_slide_info_v3)); + map.slideInfoV3.auth_value_add = m_baseAddress; + } + else if (map.slideInfoVersion == 5) + { + vm.Read(&map.slideInfoV5, map.address, sizeof(dyld_cache_slide_info_v5)); + map.slideInfoV5.value_add = m_baseAddress; + } + else + { + m_logger->LogError("Unknown slide info version: %d", map.slideInfoVersion); + continue; + } + + mappings.emplace_back(map); + m_logger->LogDebug("File: %s", entry.GetFilePath().c_str()); + m_logger->LogDebug("Slide Info Address: 0x%llx", map.address); + m_logger->LogDebug("Mapping Address: 0x%llx", map.mappingInfo.address); + m_logger->LogDebug("Slide Info Version: %d", map.slideInfoVersion); + } + + return mappings; +} + +void SlideInfoProcessor::ApplyMappings(VirtualMemory& vm, const std::vector<SlideMappingInfo>& mappings) +{ + // Apply the slide information to the mapped file. + for (const auto& mapping : mappings) + { + switch (mapping.slideInfoVersion) + { + case 2: + ApplySlideInfoV2(vm, mapping); + break; + case 3: + ApplySlideInfoV3(vm, mapping); + break; + case 5: + ApplySlideInfoV5(vm, mapping); + break; + default: + m_logger->LogError( + "Cannot apply slide info version: %d @ %llx", mapping.slideInfoVersion, mapping.mappingInfo.address); + break; + } + } +} + +void SlideInfoProcessor::ProcessEntry(VirtualMemory& vm, const CacheEntry& entry) +{ + try + { + ApplyMappings(vm, ReadEntryInfo(vm, entry)); + } + catch (const std::exception& e) + { + // Just log an error, we technically can continue as if the slide info is not applied for a given entry it does + // not necessarily mean we cannot do analysis on others. + m_logger->LogError("Error processing slide info for entry `%s`: %s", entry.GetFileName().c_str(), e.what()); + } +} diff --git a/view/sharedcache/core/SlideInfo.h b/view/sharedcache/core/SlideInfo.h new file mode 100644 index 00000000..bc165589 --- /dev/null +++ b/view/sharedcache/core/SlideInfo.h @@ -0,0 +1,42 @@ +#pragma once + +#include "binaryninjaapi.h" +#include "SharedCache.h" + +// Information required to apply slide (relocation) info to a mapping +struct SlideMappingInfo +{ + dyld_cache_mapping_info mappingInfo; + // NOTE: Offset is relative to the beginning of the entry file. + uint64_t address; + uint16_t slideInfoVersion; + + union + { + dyld_cache_slide_info_v2 slideInfoV2; + dyld_cache_slide_info_v3 slideInfoV3; + dyld_cache_slide_info_v5 slideInfoV5; + }; +}; + +// Current usages of the slide info are: +// - Reading export symbols requires slide info to be processed. +// - Reading objc stuff +// - Loading an image or region +// - Reading branch island mappings???? +class SlideInfoProcessor +{ + BinaryNinja::Ref<BinaryNinja::Logger> m_logger; + // Base address of the shared cache, NOT the base address of the entry. + uint64_t m_baseAddress; + +public: + explicit SlideInfoProcessor(uint64_t baseAddress); + + std::vector<SlideMappingInfo> ReadEntryInfo(VirtualMemory& vm, const CacheEntry& entry) const; + + // Write the slide information back to the entries memory mapped regions. + void ApplyMappings(VirtualMemory& vm, const std::vector<SlideMappingInfo>& mappings); + + void ProcessEntry(VirtualMemory& vm, const CacheEntry& entry); +}; diff --git a/view/sharedcache/core/Utility.cpp b/view/sharedcache/core/Utility.cpp new file mode 100644 index 00000000..91729578 --- /dev/null +++ b/view/sharedcache/core/Utility.cpp @@ -0,0 +1,131 @@ +#include "Utility.h" +#include "binaryninjaapi.h" +#include "view/macho/machoview.h" + +using namespace BinaryNinja; + +BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt) +{ + uint32_t flags = 0; + if (initProt & MACHO_VM_PROT_READ) + flags |= SegmentReadable; + if (initProt & MACHO_VM_PROT_WRITE) + flags |= SegmentWritable; + if (initProt & MACHO_VM_PROT_EXECUTE) + flags |= SegmentExecutable; + if (((initProt & MACHO_VM_PROT_WRITE) == 0) && ((maxProt & MACHO_VM_PROT_WRITE) == 0)) + flags |= SegmentDenyWrite; + if (((initProt & MACHO_VM_PROT_EXECUTE) == 0) && ((maxProt & MACHO_VM_PROT_EXECUTE) == 0)) + flags |= SegmentDenyExecute; + return static_cast<BNSegmentFlag>(flags); +} + +int64_t readSLEB128(const uint8_t*& current, const uint8_t* end) +{ + uint8_t cur; + int64_t value = 0; + size_t shift = 0; + while (current != end) + { + cur = *current++; + value |= (cur & 0x7f) << shift; + shift += 7; + if ((cur & 0x80) == 0) + break; + } + value = (value << (64 - shift)) >> (64 - shift); + return value; +} + +uint64_t readLEB128(const uint8_t*& current, const uint8_t* end) +{ + uint64_t result = 0; + int bit = 0; + do + { + if (current >= end) + return -1; + + uint64_t slice = *current & 0x7f; + + if (bit > 63) + return -1; + result |= (slice << bit); + bit += 7; + } while (*current++ & 0x80); + return result; +} + + +uint64_t readValidULEB128(const uint8_t*& current, const uint8_t* end) +{ + uint64_t value = readLEB128(current, end); + if ((int64_t)value == -1) + throw ReadException(); + return value; +} + +void ApplySymbol(Ref<BinaryView> view, Ref<TypeLibrary> typeLib, Ref<Symbol> symbol) +{ + Ref<Function> func = nullptr; + auto symbolAddress = symbol->GetAddress(); + auto symbolName = symbol->GetFullName(); + + if (symbol->GetType() == FunctionSymbol) + { + Ref<Platform> targetPlatform = view->GetDefaultPlatform(); + func = view->AddFunctionForAnalysis(targetPlatform, symbolAddress); + } + + if (typeLib) + { + auto type = view->ImportTypeLibraryObject(typeLib, {symbolName}); + // TODO: This is still auto + if (type) + view->DefineAutoSymbolAndVariableOrFunction(view->GetDefaultPlatform(), symbol, type); + else + view->DefineAutoSymbol(symbol); + } + else + { + view->DefineAutoSymbol(symbol); + } + + if (!func) + func = view->GetAnalysisFunction(view->GetDefaultPlatform(), symbolAddress); + if (func) + { + if (symbolName == "_objc_msgSend") + { + func->SetHasVariableArguments(false); + } + else if (symbolName.find("_objc_retain_x") != std::string::npos + || symbolName.find("_objc_release_x") != std::string::npos) + { + auto x = symbolName.rfind('x'); + auto num = symbolName.substr(x + 1); + + std::vector<FunctionParameter> callTypeParams; + auto cc = view->GetDefaultArchitecture()->GetCallingConventionByName("apple-arm64-objc-fast-arc-" + num); + + if (auto idType = view->GetTypeByName({"id"})) + { + callTypeParams.emplace_back("obj", idType, true, Variable()); + auto funcType = Type::FunctionType(idType, cc, callTypeParams); + func->SetUserType(funcType); + } + else + { + LogWarn("Failed to find id type for %llx, objective-c processor not ran?", func->GetStart()); + } + } + } +} + +std::string BaseFileName(const std::string& path) +{ + auto lastSlashPos = path.find_last_of("/\\"); + if (lastSlashPos != std::string::npos) + return path.substr(lastSlashPos + 1); + return path; +} diff --git a/view/sharedcache/core/Utility.h b/view/sharedcache/core/Utility.h new file mode 100644 index 00000000..86715557 --- /dev/null +++ b/view/sharedcache/core/Utility.h @@ -0,0 +1,106 @@ +#pragma once + +#include <cstdint> +#include <map> + +#include "binaryninjaapi.h" + +#ifdef _MSC_VER +inline int CountTrailingZeros(uint64_t value) +{ + unsigned long index; // 32-bit long on Windows + if (_BitScanForward64(&index, value)) + { + return index; + } + else + { + return 64; // If the value is 0, return 64. + } +} +#else +inline int CountTrailingZeros(uint64_t value) +{ + return value == 0 ? 64 : __builtin_ctzll(value); +} +#endif + +BNSegmentFlag SegmentFlagsFromMachOProtections(int initProt, int maxProt); + +int64_t readSLEB128(const uint8_t*& current, const uint8_t* end); + +uint64_t readLEB128(const uint8_t*& current, const uint8_t* end); + +uint64_t readValidULEB128(const uint8_t*& current, const uint8_t* end); + +void ApplySymbol(BinaryNinja::Ref<BinaryNinja::BinaryView> view, BinaryNinja::Ref<BinaryNinja::TypeLibrary> typeLib, + BinaryNinja::Ref<BinaryNinja::Symbol> symbol); + +// Returns the on disk file for a given path, this is required to support project files. +std::string ResolveFilePath(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, const std::string& path); + +// Returns the "image name" for a given path. +// /blah/foo/bar/libObjCThing.dylib -> libObjCThing.dylib +std::string BaseFileName(const std::string& path); + +// Represents a range of addresses [start, end). +// Note that `end` is not included within the range. +struct AddressRange +{ + uint64_t start; + uint64_t end; + + AddressRange(uint64_t start, uint64_t end) : start(start), end(end) {} + + AddressRange() : start(0), end(0) {} + + bool Overlaps(const AddressRange& b) const { return start < b.end && b.start < end; } + + bool operator<(const AddressRange& b) const { return start < b.start || (start == b.start && end < b.end); } + + friend bool operator<(const AddressRange& range, uint64_t address) { return range.end <= address; } + + friend bool operator<(uint64_t address, const AddressRange& range) { return address < range.start; } +}; + +// A map keyed by address ranges that can be looked up via any +// address within a range thanks to C++14's transparent comparators. +template <typename Value> +using AddressRangeMap = std::map<AddressRange, Value, std::less<>>; + +// TODO: Document this! +template <typename T> +class WeakAllocPtr +{ +protected: + std::weak_ptr<T> m_weakPtr; // Weak reference to the object + std::function<std::shared_ptr<T>()> m_allocator; // Function to recreate the object + +public: + explicit WeakAllocPtr(std::function<std::shared_ptr<T>()> allocator) : m_allocator(allocator) {} + + WeakAllocPtr(std::weak_ptr<T> weakPtr, std::function<std::shared_ptr<T>()> allocator) : m_allocator(allocator) + { + if (weakPtr == nullptr) + { + m_weakPtr = {}; + } + else + { + m_weakPtr = weakPtr; + } + } + + std::shared_ptr<T> lock() + { + std::shared_ptr<T> sharedPtr = m_weakPtr.lock(); + if (!sharedPtr) + { + sharedPtr = m_allocator(); + m_weakPtr = sharedPtr; + } + return sharedPtr; + } + + std::shared_ptr<T> lock_no_allocate() { return m_weakPtr.lock(); } +}; diff --git a/view/sharedcache/core/VM.cpp b/view/sharedcache/core/VM.cpp deleted file mode 100644 index 711c2a88..00000000 --- a/view/sharedcache/core/VM.cpp +++ /dev/null @@ -1,891 +0,0 @@ -// -// Created by kat on 5/23/23. -// - -/* - This is the cross-plat file buffering logic used for SharedCache processing. - This is used for reading large amounts of large files in a performant manner. - - Here be no dragons, but this code is very complex, beware. - - We in _all_ cases memory map the files, as we hardly ever need more than a few pages per file for most intensive operations. - - Memory Map Implementation: - Of interest is that on several platforms we have to account for very low file pointer limits, and when mapping - 40+ files, these are trivially reachable. - - We handle this with a "SelfAllocatingWeakPtr": - - Calling .lock() ALWAYS delivers a shared_ptr guaranteed to stay valid. This may block waiting for a free pointer - - As soon as that lock is released, that file pointer MAY be freed if another thread wants to open a new one, and we are at our limit. - - Calling .lock() again on this same theoretical object will then wait for another file pointer to be freeable. - - VM Implementation: - - - Since the caches we're operating on are by nature page aligned, we are able to use nice optimizations under the hood to translate - "VM Addresses" to their actual in-memory counterparts. - - We do this with a page table, which is a map of page -> file offset. - - We also implement a "VMReader" here, which is a drop-in replacement for BinaryReader that operates on the VM. - see "ObjC.cpp" for where this is used. - -*/ - - -#include "VM.h" -#include <utility> -#include <memory> -#include <cstring> -#include <stdio.h> -#include <filesystem> -#include <binaryninjaapi.h> - -#ifdef _MSC_VER - #include <windows.h> -#else - #include <sys/mman.h> - #include <fcntl.h> - #include <stdlib.h> - #include <sys/resource.h> -#endif - -static std::atomic<uint64_t> mmapCount = 0; - -uint64_t MMapCount() -{ - return mmapCount; -} - -void VMShutdown() {} - - -std::string ResolveFilePath(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, const std::string& path) -{ - auto dscProjectFile = dscView->GetFile()->GetProjectFile(); - BinaryNinja::LogDebugF( - "ResolveFilePath:\n of: {}\n path: {}\n pfn: {}\n pfp: {}", - dscView->GetFile()->GetOriginalFilename(), - path, - dscProjectFile ? dscProjectFile->GetName() : "", - dscProjectFile ? dscProjectFile->GetPathOnDisk() : "" - ); - - // If we're not in a project, just return the path we were given - if (!dscProjectFile) - { - return path; - } - - // TODO: do we need to support looking in subfolders? - // Replace project file path on disk with project file name for resolution - std::string projectFilePathOnDisk = dscProjectFile->GetPathOnDisk(); - std::string originalFilePathOnDisk = dscView->GetFile()->GetOriginalFilename(); - std::string cleanPath = path; - if (auto pindex = cleanPath.find(projectFilePathOnDisk); pindex != std::string::npos) - { - cleanPath.replace(pindex, projectFilePathOnDisk.size(), dscProjectFile->GetName()); - } - else if (auto oindex = cleanPath.find(originalFilePathOnDisk); oindex != std::string::npos) - { - BinaryNinja::Ref<BinaryNinja::ProjectFile> originalProjectFile = dscProjectFile->GetProject()->GetFileByPathOnDisk(originalFilePathOnDisk); - if (!originalProjectFile) - { - BinaryNinja::LogErrorF("Failed to resolve file path for {}: original file {} not found", path, originalFilePathOnDisk); - return path; - } - cleanPath.replace(oindex, originalFilePathOnDisk.size(), originalProjectFile->GetName()); - } - - size_t lastSlashPos = cleanPath.find_last_of("/\\"); - std::string fileName; - - if (lastSlashPos != std::string::npos) { - fileName = cleanPath.substr(lastSlashPos + 1); - } else { - fileName = cleanPath; - } - - auto project = dscProjectFile->GetProject(); - auto dscProjectFolder = dscProjectFile->GetFolder(); - for (const auto& file : project->GetFiles()) - { - auto fileFolder = file->GetFolder(); - bool isSibling = false; - if (!dscProjectFolder && !fileFolder) - { - // Both top-level - isSibling = true; - } - else if (dscProjectFolder && fileFolder) - { - // Have same parent folder - isSibling = dscProjectFolder->GetId() == fileFolder->GetId(); - } - - if (isSibling && file->GetName() == fileName) - { - return file->GetPathOnDisk(); - } - } - - BinaryNinja::LogErrorF("Failed to resolve file path for {}", path); - - // If we couldn't find a sibling filename, just return the path we were given - return path; -} - - -void MMAP::Map() -{ - if (mapped) - return; -#ifdef _MSC_VER - LARGE_INTEGER fileSize; - if (!GetFileSizeEx(hFile, &fileSize)) - { - // Handle error - CloseHandle(hFile); - return; - } - len = static_cast<size_t>(fileSize.QuadPart); - - HANDLE hMapping = CreateFileMapping( - hFile, // file handle - NULL, // security attributes - PAGE_WRITECOPY, // protection - 0, // maximum size (high-order DWORD) - 0, // maximum size (low-order DWORD) - NULL); // name of the mapping object - - if (hMapping == NULL) - { - // Handle error - CloseHandle(hFile); - return; - } - - _mmap = static_cast<uint8_t*>(MapViewOfFile( - hMapping, // handle to the file mapping object - FILE_MAP_COPY, // desired access - 0, // file offset (high-order DWORD) - 0, // file offset (low-order DWORD) - 0)); // number of bytes to map (0 = entire file) - - if (_mmap == nullptr) - { - // Handle error - CloseHandle(hMapping); - CloseHandle(hFile); - return; - } - - mapped = true; - - CloseHandle(hMapping); - CloseHandle(hFile); - -#else - fseek(fd, 0L, SEEK_END); - len = ftell(fd); - fseek(fd, 0L, SEEK_SET); - - void *result = mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fileno(fd), 0u); - if (result == MAP_FAILED) - { - // Handle error - return; - } - - _mmap = static_cast<uint8_t*>(result); - mapped = true; -#endif -} - -void MMAP::Unmap() -{ -#ifdef _MSC_VER - if (_mmap) - { - UnmapViewOfFile(_mmap); - mapped = false; - } -#else - if (mapped) - { - munmap(_mmap, len); - mapped = false; - } -#endif -} - -FileAccessorCache& FileAccessorCache::Shared() -{ - static FileAccessorCache& shared = *new FileAccessorCache; - return shared; -}; - -FileAccessorCache::FileAccessorCache() = default; - -void FileAccessorCache::SetCacheSize(uint64_t cacheSize) -{ - m_cacheSize = cacheSize; - EvictFromCacheIfNeeded(); -} - -void FileAccessorCache::RecordAccess(const std::string& path) -{ - auto it = m_accessors.find(path); - if (it == m_accessors.end()) - return; - - // Move the entry for `path` to the end of `m_leastRecentlyOpened`. - m_leastRecentlyOpened.splice(m_leastRecentlyOpened.end(), m_leastRecentlyOpened, it->second.second); -} - -void FileAccessorCache::EvictFromCacheIfNeeded() -{ - std::lock_guard lock(m_mutex); - while (m_accessors.size() > m_cacheSize) - { - m_accessors.erase(m_leastRecentlyOpened.front()); - m_leastRecentlyOpened.pop_front(); - } -} - -std::shared_ptr<LazyMappedFileAccessor> FileAccessorCache::OpenLazily( - const uint64_t sessionID, const std::string& path, - std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine) -{ - std::lock_guard lock(m_mutex); - if (auto it = m_lazyAccessors.find(path); it != m_lazyAccessors.end()) - { - RecordAccess(path); - return it->second; - } - - auto accessor = std::make_shared<LazyMappedFileAccessor>(path, - [=, postAllocationRoutine = std::move(postAllocationRoutine)]( - const std::string& path) { - auto accessor = Open(sessionID, path); - if (postAllocationRoutine) - { - postAllocationRoutine(accessor); - } - return accessor; - }); - - m_lazyAccessors.insert_or_assign(path, accessor); - return accessor; -} - -std::shared_ptr<MMappedFileAccessor> FileAccessorCache::Open( - const uint64_t sessionID, const std::string& path) -{ - EvictFromCacheIfNeeded(); - - mmapCount++; - auto accessor = std::shared_ptr<MMappedFileAccessor>(new MMappedFileAccessor(path), - [this](MMappedFileAccessor* accessor) { Close(accessor); }); - - std::lock_guard lock(m_mutex); - auto [it, inserted] = m_accessors.insert({path, {accessor, m_leastRecentlyOpened.end()}}); - if (inserted) - { - m_leastRecentlyOpened.push_back(path); - it->second.second = std::prev(it->second.second); - } - else - { - RecordAccess(path); - } - - return accessor; -} - -void FileAccessorCache::Close(MMappedFileAccessor* accessor) -{ - mmapCount--; - delete accessor; -} - -std::shared_ptr<LazyMappedFileAccessor> MMappedFileAccessor::Open( - const uint64_t sessionID, const std::string& path, - std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine) -{ - return FileAccessorCache::Shared().OpenLazily(sessionID, path, std::move(postAllocationRoutine)); -} - -void MMappedFileAccessor::InitialVMSetup() -{ - static std::once_flag once; - std::call_once(once, []{ - // check for BN_SHAREDCACHE_FP_MAX - // if it exists, set maxFPLimit to that value - unsigned long long maxFPLimit = 0; - if (auto env = getenv("BN_SHAREDCACHE_FP_MAX"); env) - { - // FIXME behav on 0 here is unintuitive, '0123' will interpret as octal and be 83 according to manpage. meh. - maxFPLimit = strtoull(env, nullptr, 0); - if (maxFPLimit < 10) - { - BinaryNinja::LogWarn("BN_SHAREDCACHE_FP_MAX set to below 10. A value of at least 10 is recommended for performant analysis on SharedCache Binaries."); - } - if (maxFPLimit == 0) - { - BinaryNinja::LogError("BN_SHAREDCACHE_FP_MAX set to 0. Adjusting to 1"); - maxFPLimit = 1; - } - } - else - { -#ifdef _MSC_VER - // It is not _super_ clear what the max file pointer limit is on windows, - // but to my understanding, we are using the windows API to map files, - // so we should have at least 2^24; - // kind of funny to me that windows would be the most effecient OS to - // parallelize sharedcache processing on in terms of FP usage concerns - maxFPLimit = 0x1000000; -#else - // The soft file descriptor limit on Linux and Mac is a lot lower than - // on Windows (1024 for Linux, 256 for Mac). Recent iOS shared caches - // have 60+ files which may not leave much headroom if a user opens - // more than one at a time. Attempt to increase the file descriptor - // limit to 1024, and limit ourselves to caching half of them as a - // memory vs performance trade-off (closing and re-opening a file - // requires parsing and applying the slide information again). - constexpr rlim_t TargetFileDescriptorLimit = 1024; - struct rlimit rlim; - getrlimit(RLIMIT_NOFILE, &rlim); - unsigned long long previousLimit = rlim.rlim_cur; - if (rlim.rlim_cur < TargetFileDescriptorLimit) - { - rlim.rlim_cur = std::min(TargetFileDescriptorLimit, rlim.rlim_max); - if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) - { - perror("setrlimit(RLIMIT_NOFILE)"); - rlim.rlim_cur = previousLimit; - } - } - maxFPLimit = rlim.rlim_cur / 2; -#endif - } - BinaryNinja::LogInfo("Shared Cache processing initialized with a max file descriptor limit of %lld", maxFPLimit); - FileAccessorCache::Shared().SetCacheSize(maxFPLimit); - }); -} - - -MMappedFileAccessor::MMappedFileAccessor(const std::string& path) : m_path(path) -{ -#ifdef _MSC_VER - m_mmap.hFile = CreateFile( - path.c_str(), // file name - GENERIC_READ, // desired access (read-only) - FILE_SHARE_READ, // share mode - NULL, // security attributes - OPEN_EXISTING, // creation disposition - FILE_ATTRIBUTE_NORMAL, // flags and attributes - NULL); // template file - - if (m_mmap.hFile == INVALID_HANDLE_VALUE) - { - // BNLogInfo("Couldn't read file at %s", path.c_str()); - throw MissingFileException(); - } - -#else -#ifdef ABORT_FAILURES - if (path.empty()) - { - cerr << "Path is empty." << endl; - abort(); - } -#endif - m_mmap.fd = fopen(path.c_str(), "r"); - if (m_mmap.fd == nullptr) - { - BNLogError("Serious VM Error: Couldn't read file at %s", path.c_str()); - -#ifndef _MSC_VER - try { - throw BinaryNinja::ExceptionWithStackTrace("Unable to Read file"); - } - catch (ExceptionWithStackTrace &ex) - { - BNLogError("%s", ex.m_stackTrace.c_str()); - BNLogError("Error: %d (%s)", errno, strerror(errno)); - } -#endif - throw MissingFileException(); - } -#endif - - m_mmap.Map(); -} - -MMappedFileAccessor::~MMappedFileAccessor() -{ - // BNLogInfo("Unmapping %s", m_path.c_str()); - m_mmap.Unmap(); - -#ifdef _MSC_VER - if (m_mmap.hFile != INVALID_HANDLE_VALUE) - { - CloseHandle(m_mmap.hFile); - } -#else - if (m_mmap.fd != nullptr) - { - fclose(m_mmap.fd); - } -#endif -} - -void MMappedFileAccessor::WritePointer(size_t address, size_t pointer) -{ - *(size_t*)&m_mmap._mmap[address] = pointer; -} - -template <typename T> -T MMappedFileAccessor::Read(size_t address) -{ - T result; - Read(&result, address, sizeof(T)); - return result; -} - -std::string MMappedFileAccessor::ReadNullTermString(size_t address) -{ - if (address > m_mmap.len) - return ""; - auto start = m_mmap._mmap + address; - auto end = m_mmap._mmap + m_mmap.len; - auto nul = std::find(start, end, 0); - return std::string(start, nul); -} - -uint8_t MMappedFileAccessor::ReadUChar(size_t address) -{ - return Read<uint8_t>(address); -} - -int8_t MMappedFileAccessor::ReadChar(size_t address) -{ - return Read<int8_t>(address); -} - -uint16_t MMappedFileAccessor::ReadUShort(size_t address) -{ - return Read<uint16_t>(address); -} - -int16_t MMappedFileAccessor::ReadShort(size_t address) -{ - return Read<int16_t>(address); -} - -uint32_t MMappedFileAccessor::ReadUInt32(size_t address) -{ - return Read<uint32_t>(address); -} - -int32_t MMappedFileAccessor::ReadInt32(size_t address) -{ - return Read<int32_t>(address); -} - -uint64_t MMappedFileAccessor::ReadULong(size_t address) -{ - return Read<uint64_t>(address); -} - -int64_t MMappedFileAccessor::ReadLong(size_t address) -{ - return Read<int64_t>(address); -} - -BinaryNinja::DataBuffer MMappedFileAccessor::ReadBuffer(size_t address, size_t length) -{ - if (m_mmap.len <= length || address > m_mmap.len - length) - throw MappingReadException(); - - return BinaryNinja::DataBuffer(&m_mmap._mmap[address], length); -} - -std::pair<const uint8_t*, const uint8_t*> MMappedFileAccessor::ReadSpan(size_t address, size_t length) -{ - if (address > m_mmap.len) - throw MappingReadException(); - if (address + length > m_mmap.len) - throw MappingReadException(); - const uint8_t* data = (&(((uint8_t*)m_mmap._mmap)[address])); - return {data, data + length}; -} - - -void MMappedFileAccessor::Read(void* dest, size_t address, size_t length) -{ - if (m_mmap.len <= length || address > m_mmap.len - length) - throw MappingReadException(); - - memcpy(dest, &m_mmap._mmap[address], length); -} - - -VM::VM(size_t pageSize, bool safe) : m_pageSize(pageSize), m_safe(safe) -{ -} - -VM::~VM() -{ -} - - -void VM::MapPages(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, uint64_t sessionID, size_t vm_address, size_t fileoff, size_t size, const std::string& filePath, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine) -{ - // The mappings provided for shared caches will always be page aligned. - // We can use this to our advantage and gain considerable performance via page tables. - // This could probably be sped up if c++ were avoided? - // We want to create a map of page -> file offset - - if (vm_address % m_pageSize != 0 || size % m_pageSize != 0) - { - throw MappingPageAlignmentException(); - } - - auto accessor = - MMappedFileAccessor::Open(sessionID, ResolveFilePath(dscView, filePath), std::move(postAllocationRoutine)); - auto [it, inserted] = m_map.insert_or_assign({vm_address, vm_address + size}, PageMapping(std::move(accessor), fileoff)); - if (m_safe && !inserted) - { - BNLogWarn("Remapping page 0x%zx (f: 0x%zx)", vm_address, fileoff); - throw MappingCollisionException(); - } -} - -std::pair<PageMapping, size_t> VM::MappingAtAddress(size_t address) -{ - if (auto it = m_map.find(address); it != m_map.end()) - { - // The PageMapping object returned contains the page, and more importantly, the file pointer (there can be - // multiple in newer caches) This is relevant for reading out the data in the rest of this file. - // The second item in the returned pair is the offset of `address` within the file. - auto& range = it->first; - auto& mapping = it->second; - return {mapping, mapping.fileOffset + (address - range.start)}; - } - - throw MappingReadException(); -} - - -bool VM::AddressIsMapped(uint64_t address) -{ - auto it = m_map.find(address); - return it != m_map.end(); -} - - -uint64_t VMReader::ReadULEB128(size_t limit) -{ - uint64_t result = 0; - int bit = 0; - auto mapping = m_vm->MappingAtAddress(m_cursor); - auto fileCursor = mapping.second; - auto fileLimit = fileCursor + (limit - m_cursor); - auto fa = mapping.first.fileAccessor->lock(); - auto* fileBuff = (uint8_t*)fa->Data(); - do - { - if (fileCursor >= fileLimit) - return -1; - uint64_t slice = ((uint64_t*)&((fileBuff)[fileCursor]))[0] & 0x7f; - if (bit > 63) - return -1; - else - { - result |= (slice << bit); - bit += 7; - } - } while (((uint64_t*)&(fileBuff[fileCursor++]))[0] & 0x80); - fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer - return result; -} - - -int64_t VMReader::ReadSLEB128(size_t limit) -{ - uint8_t cur; - int64_t value = 0; - size_t shift = 0; - - auto mapping = m_vm->MappingAtAddress(m_cursor); - auto fileCursor = mapping.second; - auto fileLimit = fileCursor + (limit - m_cursor); - auto fa = mapping.first.fileAccessor->lock(); - auto* fileBuff = (uint8_t*)fa->Data(); - - while (fileCursor < fileLimit) - { - cur = ((uint64_t*)&((fileBuff)[fileCursor]))[0]; - fileCursor++; - value |= (cur & 0x7f) << shift; - shift += 7; - if ((cur & 0x80) == 0) - break; - } - value = (value << (64 - shift)) >> (64 - shift); - fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer - return value; -} - -std::string VM::ReadNullTermString(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadNullTermString(mapping.second); -} - -uint8_t VM::ReadUChar(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second); -} - -int8_t VM::ReadChar(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadChar(mapping.second); -} - -uint16_t VM::ReadUShort(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second); -} - -int16_t VM::ReadShort(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadShort(mapping.second); -} - -uint32_t VM::ReadUInt32(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second); -} - -int32_t VM::ReadInt32(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second); -} - -uint64_t VM::ReadULong(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadULong(mapping.second); -} - -int64_t VM::ReadLong(size_t address) -{ - auto mapping = MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadLong(mapping.second); -} - -BinaryNinja::DataBuffer VM::ReadBuffer(size_t addr, size_t length) -{ - auto mapping = MappingAtAddress(addr); - return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length); -} - - -void VM::Read(void* dest, size_t addr, size_t length) -{ - auto mapping = MappingAtAddress(addr); - mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length); -} - -VMReader::VMReader(std::shared_ptr<VM> vm, size_t addressSize) : m_vm(vm), m_cursor(0), m_addressSize(addressSize) {} - - -void VMReader::Seek(size_t address) -{ - m_cursor = address; -} - -void VMReader::SeekRelative(size_t offset) -{ - m_cursor += offset; -} - -std::string VMReader::ReadCString(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - return mapping.first.fileAccessor->lock()->ReadNullTermString(mapping.second); -} - -uint8_t VMReader::ReadUChar(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 1; - return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second); -} - -int8_t VMReader::ReadChar(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 1; - return mapping.first.fileAccessor->lock()->ReadChar(mapping.second); -} - -uint16_t VMReader::ReadUShort(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 2; - return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second); -} - -int16_t VMReader::ReadShort(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 2; - return mapping.first.fileAccessor->lock()->ReadShort(mapping.second); -} - -uint32_t VMReader::ReadUInt32(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 4; - return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second); -} - -int32_t VMReader::ReadInt32(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 4; - return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second); -} - -uint64_t VMReader::ReadULong(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 8; - return mapping.first.fileAccessor->lock()->ReadULong(mapping.second); -} - -int64_t VMReader::ReadLong(size_t address) -{ - auto mapping = m_vm->MappingAtAddress(address); - m_cursor = address + 8; - return mapping.first.fileAccessor->lock()->ReadLong(mapping.second); -} - - -size_t VMReader::ReadPointer(size_t address) -{ - if (m_addressSize == 8) - return ReadULong(address); - else if (m_addressSize == 4) - return ReadUInt32(address); - - // no idea what horrible arch we have, should probably die here. - return 0; -} - - -size_t VMReader::ReadPointer() -{ - if (m_addressSize == 8) - return Read64(); - else if (m_addressSize == 4) - return Read32(); - - return 0; -} - -BinaryNinja::DataBuffer VMReader::ReadBuffer(size_t length) -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += length; - return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length); -} - -BinaryNinja::DataBuffer VMReader::ReadBuffer(size_t addr, size_t length) -{ - auto mapping = m_vm->MappingAtAddress(addr); - m_cursor = addr + length; - return mapping.first.fileAccessor->lock()->ReadBuffer(mapping.second, length); -} - -void VMReader::Read(void* dest, size_t length) -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += length; - mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length); -} - -void VMReader::Read(void* dest, size_t addr, size_t length) -{ - auto mapping = m_vm->MappingAtAddress(addr); - m_cursor = addr + length; - mapping.first.fileAccessor->lock()->Read(dest, mapping.second, length); -} - - -uint8_t VMReader::Read8() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 1; - return mapping.first.fileAccessor->lock()->ReadUChar(mapping.second); -} - -int8_t VMReader::ReadS8() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 1; - return mapping.first.fileAccessor->lock()->ReadChar(mapping.second); -} - -uint16_t VMReader::Read16() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 2; - return mapping.first.fileAccessor->lock()->ReadUShort(mapping.second); -} - -int16_t VMReader::ReadS16() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 2; - return mapping.first.fileAccessor->lock()->ReadShort(mapping.second); -} - -uint32_t VMReader::Read32() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 4; - return mapping.first.fileAccessor->lock()->ReadUInt32(mapping.second); -} - -int32_t VMReader::ReadS32() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 4; - return mapping.first.fileAccessor->lock()->ReadInt32(mapping.second); -} - -uint64_t VMReader::Read64() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 8; - return mapping.first.fileAccessor->lock()->ReadULong(mapping.second); -} - -int64_t VMReader::ReadS64() -{ - auto mapping = m_vm->MappingAtAddress(m_cursor); - m_cursor += 8; - return mapping.first.fileAccessor->lock()->ReadLong(mapping.second); -} diff --git a/view/sharedcache/core/VM.h b/view/sharedcache/core/VM.h deleted file mode 100644 index e245751a..00000000 --- a/view/sharedcache/core/VM.h +++ /dev/null @@ -1,368 +0,0 @@ -// -// Created by kat on 5/23/23. -// - -#ifndef SHAREDCACHE_VM_H -#define SHAREDCACHE_VM_H - -#include <binaryninjaapi.h> - -#include <list> -#include <mutex> -#include <unordered_map> - -void VMShutdown(); - -std::string ResolveFilePath(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, const std::string& path); - -template <typename T> -class SelfAllocatingWeakPtr { -public: - SelfAllocatingWeakPtr(std::function<std::shared_ptr<T>()> allocator) : allocator(allocator) {} - - std::shared_ptr<T> lock() { - std::shared_ptr<T> sharedPtr = weakPtr.lock(); - if (!sharedPtr) { - sharedPtr = allocator(); - weakPtr = sharedPtr; - } - return sharedPtr; - } - - std::shared_ptr<T> lock_no_allocate() { - return weakPtr.lock(); - } - -private: - std::weak_ptr<T> weakPtr; // Weak reference to the object - std::function<std::shared_ptr<T>()> allocator; // Function to recreate the object -}; - - -class MissingFileException : public std::exception -{ - virtual const char* what() const throw() - { - return "Missing File."; - } -}; - -class MMappedFileAccessor; - -class MMAP { - friend MMappedFileAccessor; - - uint8_t *_mmap; - FILE *fd; - size_t len; - -#ifdef _MSC_VER - HANDLE hFile = INVALID_HANDLE_VALUE; // For Windows -#endif - - bool mapped = false; - - void Map(); - - void Unmap(); -}; - -class LazyMappedFileAccessor : public SelfAllocatingWeakPtr<MMappedFileAccessor> { -public: - LazyMappedFileAccessor( - std::string filePath, std::function<std::shared_ptr<MMappedFileAccessor>(const std::string&)> allocator) : - SelfAllocatingWeakPtr([this, allocator = std::move(allocator)] { return allocator(m_filePath); }), - m_filePath(std::move(filePath)) - {} - ~LazyMappedFileAccessor() = default; - - std::string_view filePath() const { return m_filePath; } - -private: - std::string m_filePath; -}; - -uint64_t MMapCount(); - -class MMappedFileAccessor { - std::string m_path; - MMAP m_mmap; - bool m_slideInfoWasApplied = false; - -public: - MMappedFileAccessor(const std::string &path); - ~MMappedFileAccessor(); - - static std::shared_ptr<LazyMappedFileAccessor> Open(const uint64_t sessionID, const std::string &path, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine = nullptr); - - static void CloseAll(const uint64_t sessionID); - - static void InitialVMSetup(); - - const std::string& Path() const { return m_path; }; - - size_t Length() const { return m_mmap.len; }; - - void *Data() const { return m_mmap._mmap; }; - - bool SlideInfoWasApplied() const { return m_slideInfoWasApplied; } - - void SetSlideInfoWasApplied(bool slideInfoWasApplied) { m_slideInfoWasApplied = slideInfoWasApplied; } - - /** - * Writes to files are implemented for performance reasons and should be treated with utmost care - * - * They _MAY_ disappear as _soon_ as you release the lock on the this file. - * They may also NOT disappear for the lifetime of the application. - * - * The former is more likely to occur when concurrent DSC processing is happening. The latter is the typical scenario. - * - * This is used explicitly for slide information in a locked scope and _NOTHING_ else. It should probably not be used for anything else. - * - * \param address - * \param pointer - */ - void WritePointer(size_t address, size_t pointer); - - std::string ReadNullTermString(size_t address); - - uint8_t ReadUChar(size_t address); - - int8_t ReadChar(size_t address); - - uint16_t ReadUShort(size_t address); - - int16_t ReadShort(size_t address); - - uint32_t ReadUInt32(size_t address); - - int32_t ReadInt32(size_t address); - - uint64_t ReadULong(size_t address); - - int64_t ReadLong(size_t address); - - BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length); - - // Returns a range of pointers within the mapped memory region corresponding to - // {addr, length}. - // WARNING: The pointers returned by this method is only valid for the lifetime - // of this file accessor. - // TODO: This should use std::span<const uint8_t> once the minimum supported - // C++ version supports it. - std::pair<const uint8_t*, const uint8_t*> ReadSpan(size_t addr, size_t length); - - void Read(void *dest, size_t addr, size_t length); - - template <typename T> - T Read(size_t address); -}; - -class FileAccessorCache -{ -public: - static FileAccessorCache& Shared(); - - std::shared_ptr<LazyMappedFileAccessor> OpenLazily( - const uint64_t sessionID, const std::string& path, - std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine); - - void SetCacheSize(uint64_t size); - -private: - FileAccessorCache(); - - std::shared_ptr<MMappedFileAccessor> Open( - const uint64_t sessionID, const std::string& path); - - void Close(MMappedFileAccessor* accessor); - - void RecordAccess(const std::string& path); - void EvictFromCacheIfNeeded(); - - std::mutex m_mutex; - std::unordered_map<std::string, std::shared_ptr<LazyMappedFileAccessor>> m_lazyAccessors; - - // Ordered from least recently opened (front) to most recently opened (end). - std::list<std::string> m_leastRecentlyOpened; - std::unordered_map<std::string, std::pair<std::shared_ptr<MMappedFileAccessor>, std::list<std::string>::iterator>> - m_accessors; - - uint64_t m_cacheSize = 8; -}; - -struct PageMapping { - std::shared_ptr<LazyMappedFileAccessor> fileAccessor; - size_t fileOffset; - PageMapping(std::shared_ptr<LazyMappedFileAccessor> fileAccessor, size_t fileOffset) - : fileAccessor(std::move(fileAccessor)), fileOffset(fileOffset) {} -}; - - -class VMException : public std::exception { - virtual const char *what() const throw() { - return "Generic VM Exception"; - } -}; - -class MappingPageAlignmentException : public VMException { - virtual const char *what() const throw() { - return "Tried to create a mapping not aligned to given page size"; - } -}; - -class MappingReadException : VMException { - virtual const char *what() const throw() { - return "Tried to access unmapped page"; - } -}; - -class MappingCollisionException : VMException { - virtual const char *what() const throw() { - return "Tried to remap a page"; - } -}; - -class VMReader; - -// Represents a range of addresses [start, end). -// Note that `end` is not included within the range. -struct AddressRange { - uint64_t start; - uint64_t end; - - bool operator<(const AddressRange& b) const { - return start < b.start || (start == b.start && end < b.end); - } - - friend bool operator<(const AddressRange& range, uint64_t address) { - return range.end <= address; - } - - friend bool operator<(uint64_t address, const AddressRange& range) { - return address < range.start; - } -}; - -// A map keyed by address ranges that can be looked up via any -// address within a range thanks to C++14's transparent comparators. -template <typename Value> -using AddressRangeMap = std::map<AddressRange, Value, std::less<>>; - -class VM { - // A map keyed by address ranges that can be looked up via any - // address within a range thanks to C++14's transparent comparators. - AddressRangeMap<PageMapping> m_map; - size_t m_pageSize; - bool m_safe; - - friend VMReader; - -public: - - VM(size_t pageSize, bool safe = true); - - ~VM(); - - void MapPages(BinaryNinja::Ref<BinaryNinja::BinaryView> dscView, uint64_t sessionID, size_t vm_address, size_t fileoff, size_t size, const std::string& filePath, std::function<void(std::shared_ptr<MMappedFileAccessor>)> postAllocationRoutine); - - bool AddressIsMapped(uint64_t address); - - std::pair<PageMapping, size_t> MappingAtAddress(size_t address); - - std::string ReadNullTermString(size_t address); - - uint8_t ReadUChar(size_t address); - - int8_t ReadChar(size_t address); - - uint16_t ReadUShort(size_t address); - - int16_t ReadShort(size_t address); - - uint32_t ReadUInt32(size_t address); - - int32_t ReadInt32(size_t address); - - uint64_t ReadULong(size_t address); - - int64_t ReadLong(size_t address); - - BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length); - - void Read(void *dest, size_t addr, size_t length); -}; - - -class VMReader { - std::shared_ptr<VM> m_vm; - size_t m_cursor; - size_t m_addressSize; - - BNEndianness m_endianness = LittleEndian; - -public: - VMReader(std::shared_ptr<VM> vm, size_t addressSize = 8); - - void SetEndianness(BNEndianness endianness) { m_endianness = endianness; } - - BNEndianness GetEndianness() const { return m_endianness; } - - void Seek(size_t address); - - void SeekRelative(size_t offset); - - [[nodiscard]] size_t GetOffset() const { return m_cursor; } - - std::string ReadCString(size_t address); - - uint64_t ReadULEB128(size_t cursorLimit); - - int64_t ReadSLEB128(size_t cursorLimit); - - uint8_t Read8(); - - int8_t ReadS8(); - - uint16_t Read16(); - - int16_t ReadS16(); - - uint32_t Read32(); - - int32_t ReadS32(); - - uint64_t Read64(); - - int64_t ReadS64(); - - size_t ReadPointer(); - - uint8_t ReadUChar(size_t address); - - int8_t ReadChar(size_t address); - - uint16_t ReadUShort(size_t address); - - int16_t ReadShort(size_t address); - - uint32_t ReadUInt32(size_t address); - - int32_t ReadInt32(size_t address); - - uint64_t ReadULong(size_t address); - - int64_t ReadLong(size_t address); - - size_t ReadPointer(size_t address); - - BinaryNinja::DataBuffer ReadBuffer(size_t length); - - BinaryNinja::DataBuffer ReadBuffer(size_t addr, size_t length); - - void Read(void *dest, size_t length); - - void Read(void *dest, size_t addr, size_t length); -}; - -#endif //SHAREDCACHE_VM_H diff --git a/view/sharedcache/core/VirtualMemory.cpp b/view/sharedcache/core/VirtualMemory.cpp new file mode 100644 index 00000000..b55ddefe --- /dev/null +++ b/view/sharedcache/core/VirtualMemory.cpp @@ -0,0 +1,375 @@ +#include "VirtualMemory.h" + +// TODO: Add back the ability to do relocations on this stuff. +// TODO: ^ the above is currently handled only by the consumers of the VirtualMemory +// TODO: however we might still want to have this be done here as well for persistence. + +void VirtualMemory::MapRegion(WeakFileAccessor fileAccessor, AddressRange mappedRange, uint64_t fileOffset) +{ + // Create a new VirtualMemoryRegion object + VirtualMemoryRegion region {fileOffset, std::move(fileAccessor)}; + + // TODO: How to handle overlapping regions? + for (const auto& [existingRange, existingRegion] : m_regions) + { + if (existingRange.Overlaps(mappedRange)) + { + // Handle overlapping regions, e.g., throw an exception or skip the mapping + BinaryNinja::LogError("Overlapping memory region %llx", existingRange.start); + } + } + + // Insert the region into the map + m_regions.insert_or_assign(mappedRange, region); +} + +std::optional<VirtualMemoryRegion> VirtualMemory::GetRegionAtAddress(uint64_t address, uint64_t& addressOffset) +{ + if (const auto& it = m_regions.find(address); it != m_regions.end()) + { + // The VirtualMemoryRegion object returned contains the page, and more importantly, the file pointer (there can + // be multiple in newer caches) This is relevant for reading out the data in the rest of this file. The second + // item in the returned pair is the offset of `address` within the file. + const auto& range = it->first; + auto mapping = it->second; + addressOffset = mapping.fileOffset + (address - range.start); + return mapping; + } + + return std::nullopt; +} + +std::optional<VirtualMemoryRegion> VirtualMemory::GetRegionAtAddress(uint64_t address) +{ + uint64_t offset; + return GetRegionAtAddress(address, offset); +} + +bool VirtualMemory::IsAddressMapped(uint64_t address) +{ + return m_regions.find(address) != m_regions.end(); +} + +void VirtualMemory::WritePointer(size_t address, size_t pointer) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + // Unique to the `VirtualMemory::WritePointer` we actually have an interface on the WeakFileAccessor to use. + // this is the mechanism for persisting our written pointers. + region->fileAccessor.WritePointer(offset, pointer); +} + +std::string VirtualMemory::ReadCString(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadNullTermString(offset); +} + +uint8_t VirtualMemory::ReadUInt8(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadUInt8(offset); +} + +int8_t VirtualMemory::ReadInt8(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadInt8(offset); +} + +uint16_t VirtualMemory::ReadUInt16(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadUInt16(offset); +} + +int16_t VirtualMemory::ReadInt16(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadInt16(offset); +} + +uint32_t VirtualMemory::ReadUInt32(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadUInt32(offset); +} + +int32_t VirtualMemory::ReadInt32(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadInt32(offset); +} + +uint64_t VirtualMemory::ReadUInt64(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadUInt64(offset); +} + +int64_t VirtualMemory::ReadInt64(uint64_t address) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadInt64(offset); +} + +BinaryNinja::DataBuffer VirtualMemory::ReadBuffer(uint64_t address, size_t length) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadBuffer(offset, length); +} + +std::pair<const uint8_t*, const uint8_t*> VirtualMemory::ReadSpan(size_t address, size_t length) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + return region->fileAccessor.lock()->ReadSpan(offset, length); +} + +void VirtualMemory::Read(void* dest, uint64_t address, size_t length) +{ + uint64_t offset; + auto region = GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + region->fileAccessor.lock()->Read(dest, offset, length); +} + +VirtualMemoryReader::VirtualMemoryReader(std::shared_ptr<VirtualMemory> memory, uint64_t addressSize) +{ + m_memory = memory; + m_addressSize = addressSize; + m_cursor = 0; +} + +std::string VirtualMemoryReader::ReadCString(uint64_t address, size_t maxLength) +{ + uint64_t offset; + auto region = m_memory->GetRegionAtAddress(address, offset); + if (!region.has_value()) + throw UnmappedRegionException(address); + // TODO: Advance cursor? + return region->fileAccessor.lock()->ReadNullTermString(offset); +} + +uint64_t VirtualMemoryReader::ReadULEB128(size_t cursorLimit) +{ + uint64_t result = 0; + int bit = 0; + uint64_t offset; + auto mapping = m_memory->GetRegionAtAddress(m_cursor, offset); + auto fileLimit = offset + (cursorLimit - m_cursor); + auto fa = mapping->fileAccessor.lock(); + auto* fileBuff = (uint8_t*)fa->Data(); + do + { + if (offset >= fileLimit) + return -1; + uint64_t slice = ((uint64_t*)&((fileBuff)[offset]))[0] & 0x7f; + if (bit > 63) + return -1; + else + { + result |= (slice << bit); + bit += 7; + } + } while (((uint64_t*)&(fileBuff[offset++]))[0] & 0x80); + // TODO: There has got to be a better way to prevent this... + fa->Data(); // prevent deallocation of the fileAccessor as we're operating on the raw data buffer + return result; +} + +int64_t VirtualMemoryReader::ReadSLEB128(size_t cursorLimit) +{ + constexpr size_t BYTE_SIZE = 7; // Number of bits in each SLEB128 byte + constexpr size_t INT64_BITS = 64; // Total number of bits in an int64_t + + int64_t value = 0; + size_t shift = 0; + uint64_t offset; + + // Retrieve associated memory region and the file buffer + auto mapping = m_memory->GetRegionAtAddress(m_cursor, offset); + auto fileLimit = offset + (cursorLimit - m_cursor); + auto fileAccessor = mapping->fileAccessor.lock(); + auto* fileBuffer = static_cast<uint8_t*>(fileAccessor->Data()); + + // Loop through the SLEB128 encoded bytes + while (offset < fileLimit) + { + uint8_t currentByte = fileBuffer[offset++]; + value |= (static_cast<int64_t>(currentByte & 0x7F) << shift); + shift += BYTE_SIZE; + + if ((currentByte & 0x80) == 0) // If MSB is not set, we're done + break; + } + + // Properly sign-extend the value according to its size + value = (value << (INT64_BITS - shift)) >> (INT64_BITS - shift); + + // TODO: There has got to be a better way to prevent this... + // Prevent deallocation of the fileAccessor + fileAccessor->Data(); + + return value; +} + +uint64_t VirtualMemoryReader::ReadPointer() +{ + return ReadPointer(m_cursor); +} + +uint64_t VirtualMemoryReader::ReadPointer(uint64_t address) +{ + if (m_addressSize == 8) + return ReadUInt64(address); + if (m_addressSize == 4) + return ReadUInt32(address); + // TODO: Throw here or assert. + return 0; +} + +uint8_t VirtualMemoryReader::ReadUInt8() +{ + return ReadUInt8(m_cursor); +} + +uint8_t VirtualMemoryReader::ReadUInt8(uint64_t address) +{ + m_cursor = address + 1; + return m_memory->ReadUInt8(address); +} + +int8_t VirtualMemoryReader::ReadInt8() +{ + return ReadUInt8(m_cursor); +} + +int8_t VirtualMemoryReader::ReadInt8(uint64_t address) +{ + m_cursor = address + 1; + return m_memory->ReadInt8(address); +} + +uint16_t VirtualMemoryReader::ReadUInt16() +{ + return ReadUInt16(m_cursor); +} + +uint16_t VirtualMemoryReader::ReadUInt16(uint64_t address) +{ + m_cursor = address + 2; + return m_memory->ReadUInt16(address); +} + +int16_t VirtualMemoryReader::ReadInt16() +{ + return ReadInt16(m_cursor); +} + +int16_t VirtualMemoryReader::ReadInt16(uint64_t address) +{ + m_cursor = address + 2; + return m_memory->ReadInt16(address); +} + +uint32_t VirtualMemoryReader::ReadUInt32() +{ + return ReadUInt32(m_cursor); +} + +uint32_t VirtualMemoryReader::ReadUInt32(uint64_t address) +{ + m_cursor = address + 4; + return m_memory->ReadUInt32(address); +} + +int32_t VirtualMemoryReader::ReadInt32() +{ + return ReadInt32(m_cursor); +} + +int32_t VirtualMemoryReader::ReadInt32(uint64_t address) +{ + m_cursor = address + 4; + return m_memory->ReadInt32(address); +} + +uint64_t VirtualMemoryReader::ReadUInt64() +{ + return ReadUInt64(m_cursor); +} + +uint64_t VirtualMemoryReader::ReadUInt64(uint64_t address) +{ + m_cursor = address + 8; + return m_memory->ReadUInt64(address); +} + +int64_t VirtualMemoryReader::ReadInt64() +{ + return ReadInt64(m_cursor); +} + +int64_t VirtualMemoryReader::ReadInt64(uint64_t address) +{ + m_cursor = address + 8; + return m_memory->ReadInt64(address); +} + +BinaryNinja::DataBuffer VirtualMemoryReader::ReadBuffer(size_t length) +{ + return ReadBuffer(m_cursor, length); +} + +BinaryNinja::DataBuffer VirtualMemoryReader::ReadBuffer(uint64_t address, size_t length) +{ + m_cursor = address + length; + return m_memory->ReadBuffer(address, length); +} + +void VirtualMemoryReader::Read(void* dest, size_t length) +{ + Read(dest, m_cursor, length); +} + +void VirtualMemoryReader::Read(void* dest, uint64_t address, size_t length) +{ + m_cursor = address + length; + m_memory->Read(dest, address, length); +} diff --git a/view/sharedcache/core/VirtualMemory.h b/view/sharedcache/core/VirtualMemory.h new file mode 100644 index 00000000..fb00ebaf --- /dev/null +++ b/view/sharedcache/core/VirtualMemory.h @@ -0,0 +1,157 @@ +#pragma once +#include "FileAccessorCache.h" +#include "MappedFileAccessor.h" +#include "Utility.h" + +class UnmappedRegionException : public std::exception +{ + uint64_t m_address; + +public: + explicit UnmappedRegionException(uint64_t address) : m_address(address) {} + + virtual const char* what() const throw() + { + thread_local std::string message; + message = fmt::format("Tried to access unmapped region using address {0:x}", m_address); + return message.c_str(); + } +}; + +// A region within the virtual memory +struct VirtualMemoryRegion +{ + uint64_t fileOffset; + // Access the memory regions contents through this. + // NOTE: Any read through this should be seeked to `fileOffset` + WeakFileAccessor fileAccessor; + + VirtualMemoryRegion(const VirtualMemoryRegion&) = default; + + VirtualMemoryRegion& operator=(const VirtualMemoryRegion&) = default; + + VirtualMemoryRegion(VirtualMemoryRegion&&) = default; + + VirtualMemoryRegion& operator=(VirtualMemoryRegion&&) = default; +}; + +// Contains information to handle mapping of multiple mapped files into a single memory space. +// This models how the loader of DYLD shared caches would operate, so that we can effectively query memory regions +// and map them into Binary Ninja. +class VirtualMemory +{ + std::shared_mutex m_regionMutex; + AddressRangeMap<VirtualMemoryRegion> m_regions; + +public: + // At no point do we ever store a strong pointer to a file accessor, that is the job of the `FileAccessorCache`. + void MapRegion(WeakFileAccessor fileAccessor, AddressRange mappedRange, uint64_t fileOffset); + + // Returns the region in virtual memory, along with the offset into that region where the address is located. + // Using the regions file accessor and the address offset you can read a regions content. + std::optional<VirtualMemoryRegion> GetRegionAtAddress(uint64_t address, uint64_t& addressOffset); + + std::optional<VirtualMemoryRegion> GetRegionAtAddress(uint64_t address); + + bool IsAddressMapped(uint64_t address); + + // Write a pointer at a given address. This pointer will be persisted + // for a given `VirtualMemoryRegion` region, unlike using the MappedFileAccessor directly. + // The persistence is provided through the WeakFileAccessor itself and thus is unique to the construction. + void WritePointer(size_t address, size_t pointer); + + std::string ReadCString(uint64_t address); + + uint8_t ReadUInt8(uint64_t address); + + int8_t ReadInt8(uint64_t address); + + uint16_t ReadUInt16(uint64_t address); + + int16_t ReadInt16(uint64_t address); + + uint32_t ReadUInt32(uint64_t address); + + int32_t ReadInt32(uint64_t address); + + uint64_t ReadUInt64(uint64_t address); + + int64_t ReadInt64(uint64_t address); + + BinaryNinja::DataBuffer ReadBuffer(uint64_t address, size_t length); + + std::pair<const uint8_t*, const uint8_t*> ReadSpan(size_t address, size_t length); + + void Read(void* dest, uint64_t address, size_t length); +}; + +class VirtualMemoryReader +{ + std::shared_ptr<VirtualMemory> m_memory; + uint64_t m_cursor; + uint64_t m_addressSize; + BNEndianness m_endianness = LittleEndian; + +public: + explicit VirtualMemoryReader(std::shared_ptr<VirtualMemory> memory, uint64_t addressSize = 8); + + void SetEndianness(BNEndianness endianness) { m_endianness = endianness; } + + BNEndianness GetEndianness() const { return m_endianness; } + + void Seek(const uint64_t address) { m_cursor = address; }; + + void SeekRelative(const size_t offset) { m_cursor += offset; }; + + size_t GetOffset() const { return m_cursor; } + + std::string ReadCString(uint64_t address, size_t maxLength = -1); + + uint64_t ReadULEB128(size_t cursorLimit); + + int64_t ReadSLEB128(size_t cursorLimit); + + uint64_t ReadPointer(); + + uint64_t ReadPointer(uint64_t address); + + uint8_t ReadUInt8(); + + uint8_t ReadUInt8(uint64_t address); + + int8_t ReadInt8(); + + int8_t ReadInt8(uint64_t address); + + uint16_t ReadUInt16(); + + uint16_t ReadUInt16(uint64_t address); + + int16_t ReadInt16(); + + int16_t ReadInt16(uint64_t address); + + uint32_t ReadUInt32(); + + uint32_t ReadUInt32(uint64_t address); + + int32_t ReadInt32(); + + int32_t ReadInt32(uint64_t address); + + uint64_t ReadUInt64(); + + uint64_t ReadUInt64(uint64_t address); + + int64_t ReadInt64(); + + int64_t ReadInt64(uint64_t address); + + BinaryNinja::DataBuffer ReadBuffer(size_t length); + + BinaryNinja::DataBuffer ReadBuffer(uint64_t address, size_t length); + + void Read(void* dest, size_t length); + + void Read(void* dest, uint64_t address, size_t length); +}; diff --git a/view/sharedcache/core/ffi.cpp b/view/sharedcache/core/ffi.cpp new file mode 100644 index 00000000..0b37196f --- /dev/null +++ b/view/sharedcache/core/ffi.cpp @@ -0,0 +1,429 @@ +#include "SharedCacheController.h" +#include "../api/sharedcachecore.h" + +using namespace BinaryNinja; +using namespace BinaryNinja::DSC; + +BNSharedCacheImage ImageToApi(const CacheImage& image) +{ + BNSharedCacheImage apiImage; + apiImage.name = BNAllocString(image.path.c_str()); + apiImage.headerAddress = image.headerAddress; + apiImage.regionStartCount = image.regionStarts.size(); + uint64_t* regionStarts = new uint64_t[image.regionStarts.size()]; + for (size_t i = 0; i < image.regionStarts.size(); i++) + regionStarts[i] = image.regionStarts[i]; + apiImage.regionStarts = regionStarts; + return apiImage; +} + +CacheImage ImageFromApi(const BNSharedCacheImage& image) +{ + CacheImage apiImage; + apiImage.path = image.name; + apiImage.headerAddress = image.headerAddress; + apiImage.regionStarts.reserve(image.regionStartCount); + for (size_t i = 0; i < image.regionStartCount; i++) + apiImage.regionStarts.push_back(image.regionStarts[i]); + apiImage.header = nullptr; + return apiImage; +} + +BNSharedCacheRegionType RegionTypeToApi(const CacheRegionType& regionType) +{ + switch (regionType) + { + case CacheRegionType::Image: + return SharedCacheRegionTypeImage; + case CacheRegionType::StubIsland: + return SharedCacheRegionTypeStubIsland; + case CacheRegionType::DyldData: + return SharedCacheRegionTypeDyldData; + default: + case CacheRegionType::NonImage: + return SharedCacheRegionTypeNonImage; + } +} + +CacheRegionType RegionTypeFromApi(const BNSharedCacheRegionType regionType) +{ + switch (regionType) + { + case SharedCacheRegionTypeImage: + return CacheRegionType::Image; + case SharedCacheRegionTypeStubIsland: + return CacheRegionType::StubIsland; + case SharedCacheRegionTypeDyldData: + return CacheRegionType::DyldData; + default: + case SharedCacheRegionTypeNonImage: + return CacheRegionType::NonImage; + } +} + +BNSharedCacheRegion RegionToApi(const CacheRegion& region) +{ + BNSharedCacheRegion apiRegion; + apiRegion.vmAddress = region.start; + apiRegion.name = BNAllocString(region.name.c_str()); + apiRegion.size = region.size; + apiRegion.flags = region.flags; + apiRegion.regionType = RegionTypeToApi(region.type); + // If not associated with image this will be zeroed. + apiRegion.imageStart = region.imageStart.value_or(0); + return apiRegion; +} + +CacheRegion RegionFromApi(const BNSharedCacheRegion& apiRegion) +{ + CacheRegion region; + region.start = apiRegion.vmAddress; + region.name = apiRegion.name; + region.size = apiRegion.size; + region.flags = apiRegion.flags; + region.type = RegionTypeFromApi(apiRegion.regionType); + return region; +} + +BNSharedCacheSymbol SymbolToApi(const CacheSymbol& symbol) +{ + BNSharedCacheSymbol apiSymbol; + apiSymbol.name = BNAllocString(symbol.name.c_str()); + apiSymbol.address = symbol.address; + apiSymbol.symbolType = symbol.type; + return apiSymbol; +} + +CacheSymbol SymbolFromApi(const BNSharedCacheSymbol& apiSymbol) +{ + CacheSymbol symbol; + symbol.name = apiSymbol.name; + symbol.address = apiSymbol.address; + symbol.type = apiSymbol.symbolType; + return symbol; +} + +BNSharedCacheEntryType EntryTypeToApi(const CacheEntryType& entryType) +{ + switch (entryType) + { + case CacheEntryType::Primary: + return SharedCacheEntryTypePrimary; + case CacheEntryType::Stub: + return SharedCacheEntryTypeStub; + case CacheEntryType::Symbols: + return SharedCacheEntryTypeSymbols; + case CacheEntryType::DyldData: + return SharedCacheEntryTypeDyldData; + default: + case CacheEntryType::Secondary: + return SharedCacheEntryTypeSecondary; + } +} + +BNSharedCacheMappingInfo MappingToApi(const dyld_cache_mapping_info& mapping) +{ + BNSharedCacheMappingInfo apiMapping; + apiMapping.vmAddress = mapping.address; + apiMapping.size = mapping.size; + apiMapping.fileOffset = mapping.fileOffset; + return apiMapping; +} + +BNSharedCacheEntry EntryToApi(const CacheEntry& entry) +{ + BNSharedCacheEntry apiEntry; + apiEntry.path = BNAllocString(entry.GetFilePath().c_str()); + apiEntry.entryType = EntryTypeToApi(entry.GetType()); + const auto& mappings = entry.GetMappings(); + apiEntry.mappingCount = mappings.size(); + apiEntry.mappings = new BNSharedCacheMappingInfo[mappings.size()]; + for (size_t i = 0; i < mappings.size(); i++) + apiEntry.mappings[i] = MappingToApi(mappings[i]); + return apiEntry; +} + +extern "C" +{ + BNSharedCacheController* BNGetSharedCacheController(BNBinaryView* data) + { + Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); + auto controller = SharedCacheController::FromView(*view); + if (!controller) + return nullptr; + return DSC_API_OBJECT_REF(controller); + } + + BNSharedCacheController* BNNewSharedCacheControllerReference(BNSharedCacheController* controller) + { + return DSC_API_OBJECT_NEW_REF(controller); + } + + void BNFreeSharedCacheControllerReference(BNSharedCacheController* controller) + { + DSC_API_OBJECT_FREE(controller); + } + + bool BNSharedCacheControllerApplyImage( + BNSharedCacheController* controller, BNBinaryView* data, BNSharedCacheImage* image) + { + Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); + // LoadImage will use the header, lets do everyone a favor and use the existing image! + if (const auto realImage = controller->object->GetCache().GetImageAt(image->headerAddress)) + return controller->object->ApplyImage(*view, *realImage); + // They gave us an unknown image, we will not have header information. + return controller->object->ApplyImage(*view, ImageFromApi(*image)); + } + + bool BNSharedCacheControllerApplyRegion( + BNSharedCacheController* controller, BNBinaryView* data, BNSharedCacheRegion* region) + { + Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); + return controller->object->ApplyRegion(*view, RegionFromApi(*region)); + } + + bool BNSharedCacheControllerIsRegionLoaded(BNSharedCacheController* controller, BNSharedCacheRegion* region) + { + return controller->object->IsRegionLoaded(RegionFromApi(*region)); + } + + bool BNSharedCacheControllerIsImageLoaded(BNSharedCacheController* controller, BNSharedCacheImage* image) + { + return controller->object->IsImageLoaded(ImageFromApi(*image)); + } + + bool BNSharedCacheControllerGetRegionAt( + BNSharedCacheController* controller, uint64_t address, BNSharedCacheRegion* outRegion) + { + auto region = controller->object->GetCache().GetRegionAt(address); + if (!region) + return false; + *outRegion = RegionToApi(*region); + return true; + } + + bool BNSharedCacheControllerGetRegionContaining( + BNSharedCacheController* controller, uint64_t address, BNSharedCacheRegion* outRegion) + { + auto region = controller->object->GetCache().GetRegionContaining(address); + if (!region) + return false; + *outRegion = RegionToApi(*region); + return true; + } + + BNSharedCacheRegion* BNSharedCacheControllerGetRegions(BNSharedCacheController* controller, size_t* count) + { + auto regions = controller->object->GetCache().GetRegions(); + *count = regions.size(); + BNSharedCacheRegion* apiRegions = new BNSharedCacheRegion[*count]; + int idx = 0; + for (const auto& [_, region] : regions) + apiRegions[idx++] = RegionToApi(region); + return apiRegions; + } + + BNSharedCacheRegion* BNSharedCacheControllerGetLoadedRegions(BNSharedCacheController* controller, size_t* count) + { + auto loadedRegionStarts = controller->object->GetLoadedRegions(); + + // TODO: This translation should likely exist in the core cache controller class? + std::vector<CacheRegion> loadedRegions; + for (auto start : loadedRegionStarts) + { + auto region = controller->object->GetCache().GetRegionAt(start); + if (region) + loadedRegions.push_back(*region); + } + + *count = loadedRegions.size(); + BNSharedCacheRegion* apiRegions = new BNSharedCacheRegion[*count]; + // I am too lazy to add a real conversion here. + int idx = 0; + for (const auto& region : loadedRegions) + { + apiRegions[idx] = RegionToApi(region); + idx++; + } + return apiRegions; + } + + uint64_t* BNSharedCacheAllocRegionList(uint64_t* list, size_t count) + { + uint64_t* newList = new uint64_t[count]; + for (size_t i = 0; i < count; i++) + newList[i] = list[i]; + return newList; + } + + void BNSharedCacheFreeRegion(BNSharedCacheRegion region) + { + BNFreeString(region.name); + } + + void BNSharedCacheFreeRegionList(BNSharedCacheRegion* regions, size_t count) + { + for (size_t i = 0; i < count; i++) + BNSharedCacheFreeRegion(regions[i]); + delete[] regions; + } + + bool BNSharedCacheControllerGetImageAt( + BNSharedCacheController* controller, uint64_t address, BNSharedCacheImage* outImage) + { + auto image = controller->object->GetCache().GetImageAt(address); + if (!image) + return false; + *outImage = ImageToApi(*image); + return true; + } + + bool BNSharedCacheControllerGetImageContaining( + BNSharedCacheController* controller, uint64_t address, BNSharedCacheImage* outImage) + { + auto image = controller->object->GetCache().GetImageContaining(address); + if (!image) + return false; + *outImage = ImageToApi(*image); + return true; + } + + bool BNSharedCacheControllerGetImageWithName( + BNSharedCacheController* controller, const char* name, BNSharedCacheImage* outImage) + { + auto image = controller->object->GetCache().GetImageWithName(name); + if (!image) + return false; + *outImage = ImageToApi(*image); + return true; + } + + char** BNSharedCacheControllerGetImageDependencies( + BNSharedCacheController* controller, BNSharedCacheImage* image, size_t* count) + { + // GetDependencies will use the header, lets do everyone a favor and use the existing image! + const auto realImage = controller->object->GetCache().GetImageAt(image->headerAddress); + if (!realImage.has_value()) + return nullptr; + const auto dependencies = realImage->GetDependencies(); + + std::vector<const char*> dependencyPtrs; + dependencyPtrs.reserve(dependencies.size()); + for (const auto& dependency : dependencies) + dependencyPtrs.push_back(dependency.c_str()); + *count = dependencyPtrs.size(); + return BNAllocStringList(dependencyPtrs.data(), dependencyPtrs.size()); + } + + BNSharedCacheImage* BNSharedCacheControllerGetImages(BNSharedCacheController* controller, size_t* count) + { + auto images = controller->object->GetCache().GetImages(); + *count = images.size(); + BNSharedCacheImage* apiImages = new BNSharedCacheImage[*count]; + size_t idx = 0; + for (const auto& [_, image] : images) + apiImages[idx++] = ImageToApi(image); + return apiImages; + } + + BNSharedCacheImage* BNSharedCacheControllerGetLoadedImages(BNSharedCacheController* controller, size_t* count) + { + auto loadedImageStarts = controller->object->GetLoadedImages(); + + // TODO: This translation should likely exist in the core cache controller class? + std::vector<CacheImage> loadedImages; + for (auto start : loadedImageStarts) + { + auto image = controller->object->GetCache().GetImageAt(start); + if (image) + loadedImages.push_back(*image); + } + + *count = loadedImages.size(); + BNSharedCacheImage* apiImages = new BNSharedCacheImage[*count]; + for (size_t i = 0; i < *count; i++) + apiImages[i] = ImageToApi(loadedImages[i]); + return apiImages; + } + + void BNSharedCacheFreeImage(BNSharedCacheImage image) + { + BNFreeString(image.name); + delete[] image.regionStarts; + } + + void BNSharedCacheFreeImageList(BNSharedCacheImage* images, size_t count) + { + for (size_t i = 0; i < count; i++) + BNSharedCacheFreeImage(images[i]); + delete[] images; + } + + bool BNSharedCacheControllerGetSymbolAt( + BNSharedCacheController* controller, uint64_t address, BNSharedCacheSymbol* outSymbol) + { + auto symbol = controller->object->GetCache().GetSymbolAt(address); + if (!symbol) + return false; + *outSymbol = SymbolToApi(*symbol); + return true; + } + + bool BNSharedCacheControllerGetSymbolWithName( + BNSharedCacheController* controller, const char* name, BNSharedCacheSymbol* outSymbol) + { + auto symbol = controller->object->GetCache().GetSymbolWithName(name); + if (!symbol) + return false; + *outSymbol = SymbolToApi(*symbol); + return true; + } + + BNSharedCacheSymbol* BNSharedCacheControllerGetSymbols(BNSharedCacheController* controller, size_t* count) + { + auto symbols = controller->object->GetCache().GetSymbols(); + *count = symbols.size(); + BNSharedCacheSymbol* apiSymbols = new BNSharedCacheSymbol[*count]; + size_t idx = 0; + for (const auto& [_, symbol] : symbols) + apiSymbols[idx++] = SymbolToApi(symbol); + return apiSymbols; + } + + + void BNSharedCacheFreeSymbol(BNSharedCacheSymbol symbol) + { + BNFreeString(symbol.name); + } + + void BNSharedCacheFreeSymbolList(BNSharedCacheSymbol* symbols, size_t count) + { + for (size_t i = 0; i < count; i++) + BNSharedCacheFreeSymbol(symbols[i]); + delete[] symbols; + } + + BNSharedCacheEntry* BNSharedCacheControllerGetEntries(BNSharedCacheController* controller, size_t* count) + { + auto entries = controller->object->GetCache().GetEntries(); + *count = entries.size(); + BNSharedCacheEntry* apiEntries = new BNSharedCacheEntry[*count]; + size_t idx = 0; + for (const auto& [_, entry] : entries) + apiEntries[idx++] = EntryToApi(entry); + return apiEntries; + } + + void BNSharedCacheFreeEntry(BNSharedCacheEntry entry) + { + BNFreeString(entry.path); + delete[] entry.mappings; + } + + void BNSharedCacheFreeEntryList(BNSharedCacheEntry* entries, size_t count) + { + for (size_t i = 0; i < count; i++) + BNSharedCacheFreeEntry(entries[i]); + delete[] entries; + } +}; diff --git a/view/sharedcache/core/ffi_global.h b/view/sharedcache/core/ffi_global.h new file mode 100644 index 00000000..cd2727b9 --- /dev/null +++ b/view/sharedcache/core/ffi_global.h @@ -0,0 +1,41 @@ +/* +Copyright 2020-2024 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once + +// Define macros for defining objects exposed by the API +#define DECLARE_DSC_API_OBJECT(handle, cls) \ + namespace BinaryNinja::DSC { \ + class cls; \ + } \ + struct handle \ + { \ + BinaryNinja::DSC::cls* object; \ + } +#define IMPLEMENT_DSC_API_OBJECT(handle) \ +\ +private: \ + handle m_apiObject; \ +\ +public: \ + typedef handle* APIHandle; \ + handle* GetAPIObject() \ + { \ + return &m_apiObject; \ + } \ +\ +private: +#define INIT_DSC_API_OBJECT() m_apiObject.object = this; diff --git a/view/sharedcache/core/refcountobject.h b/view/sharedcache/core/refcountobject.h new file mode 100644 index 00000000..1b79f12f --- /dev/null +++ b/view/sharedcache/core/refcountobject.h @@ -0,0 +1,223 @@ +/* +Copyright 2020-2024 Vector 35 Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +#pragma once + +#ifdef WIN32 + #include <windows.h> +#endif +#include <stddef.h> +#include <vector> +#include <atomic> + +namespace BinaryNinja::DSC { + class DSCRefCountObject + { + // CORE_ALLOCATED_CLASS(RefCountObject) + + public: + std::atomic<int> m_refs; + + DSCRefCountObject() : m_refs(0) {} + + virtual ~DSCRefCountObject() {} + + virtual void AddRef() { m_refs.fetch_add(1); } + + virtual void Release() + { + if (m_refs.fetch_sub(1) == 1) + delete this; + } + + virtual void AddAPIRef() { AddRef(); } + + virtual void ReleaseAPIRef() { Release(); } + }; + + + template <class T> + class DSCRef + { + T* m_obj; +#ifdef BN_REF_COUNT_DEBUG + void* m_assignmentTrace = nullptr; +#endif + + public: + DSCRef<T>() : m_obj(NULL) {} + + DSCRef<T>(T* obj) : m_obj(obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + } + } + + DSCRef<T>(const DSCRef<T>& obj) : m_obj(obj.m_obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + } + } + + ~DSCRef<T>() + { + if (m_obj) + { + m_obj->Release(); +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif + } + } + + // move constructor + DSCRef<T>(DSCRef<T>&& other) : m_obj(other.m_obj) + { + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + } + + // move assignment (inefficient in this case) + // Ref<T>& operator=(Ref<T>&& other) + // { + // if (m_obj) + // { + // #ifdef BN_REF_COUNT_DEBUG + // BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + // #endif + // m_obj->Release(); + // } + // m_obj = other.m_obj; + // other.m_obj = 0; + // #ifdef BN_REF_COUNT_DEBUG + // m_assignmentTrace = other.m_assignmentTrace; + // #endif + // return *this; + // } + + DSCRef<T>& operator=(const DSCRef<T>& obj) + { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj.m_obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T* oldObj = m_obj; + m_obj = obj.m_obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } + + DSCRef<T>& operator=(T* obj) + { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T* oldObj = m_obj; + m_obj = obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } + + operator T*() const { return m_obj; } + + T* operator->() const { return m_obj; } + + T& operator*() const { return *m_obj; } + + bool operator!() const { return m_obj == NULL; } + + T* GetPtr() const { return m_obj; } + + bool operator==(const DSCRef<T>& obj) const { return m_obj == obj.m_obj; } + + bool operator!=(const DSCRef<T>& obj) const { return m_obj != obj.m_obj; } + + bool operator<(const DSCRef<T>& obj) const { return m_obj < obj.m_obj; } + + template <typename H> + friend H AbslHashValue(H h, const DSCRef<T>& value) + { + return AbslHashValue(std::move(h), value.m_obj); + } + }; + + + // Macro-like functions to manage referenced objects for the external API + template <class T> + static typename T::APIHandle DSC_API_OBJECT_REF(T* obj) + { + if (obj == nullptr) + return nullptr; + obj->AddAPIRef(); + return obj->GetAPIObject(); + } + + template <class T> + static typename T::APIHandle DSC_API_OBJECT_REF(const DSCRef<T>& obj) + { + if (!obj) + return nullptr; + obj->AddAPIRef(); + return obj->GetAPIObject(); + } + + // template <class T> + // static typename T::APIHandle DSC_API_OBJECT_REF(const APIRef<T>& obj) + //{ + // if (!obj) + // return nullptr; + // obj->AddAPIRef(); + // return obj->GetAPIObject(); + // } + + template <class T> + static T* DSC_API_OBJECT_NEW_REF(T* obj) + { + if (obj) + obj->object->AddAPIRef(); + return obj; + } + + template <class T> + static void DSC_API_OBJECT_FREE(T* obj) + { + if (obj) + obj->object->ReleaseAPIRef(); + } +}; // namespace BinaryNinja::DSC |
