diff options
| author | kat <kat@vector35.com> | 2025-03-19 09:12:52 -0400 |
|---|---|---|
| committer | kat <kat@vector35.com> | 2025-03-19 15:04:23 -0400 |
| commit | 7f3f394c8bb50c987a5237bc74aa503486571058 (patch) | |
| tree | 9f5ada34f6226562b79f0dce70bfebab124a1bdb /view/kernelcache/core/KernelCache.cpp | |
| parent | e6d4d38e2a5dc66d3c8004cc917bc08e39337482 (diff) | |
Add iOS/macOS MH_FILESET KernelCache View and loader.
This loader is inspired by/based on our dyld_shared_cache loader, following the same design language. It targets primarily the latest kernels, but should support any with the MH_FILESET format.
It allows you to decide which images you would like to map in (the kernel itself included), resulting in targeted analysis when you may only need to load a singular image.
It also supports dropping in compressed KernelCaches, directly from the ipsw.
This is an early solution and we have many more changes and improvements planned. We look forward to your feedback
Diffstat (limited to 'view/kernelcache/core/KernelCache.cpp')
| -rw-r--r-- | view/kernelcache/core/KernelCache.cpp | 2709 |
1 files changed, 2709 insertions, 0 deletions
diff --git a/view/kernelcache/core/KernelCache.cpp b/view/kernelcache/core/KernelCache.cpp new file mode 100644 index 00000000..41577c0b --- /dev/null +++ b/view/kernelcache/core/KernelCache.cpp @@ -0,0 +1,2709 @@ +// +// Created by kat on 5/19/23. +// + +#include "binaryninjaapi.h" + +/* --- + * This is the primary image loader logic for Kernelcaches. + * */ + +#include "KernelCache.h" +#include <filesystem> +#include <utility> +#include <fcntl.h> +#include <memory> +#include <chrono> +#include <thread> + + +using namespace BinaryNinja; +using namespace KernelCacheCore; + +namespace KernelCacheCore { + + + +#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 + +// State that does not change after `PerformInitialLoad`. +struct KernelCache::CacheInfo : + public MetadataSerializable<KernelCache::CacheInfo, std::optional<KernelCache::CacheInfo>> +{ + // std::unordered_map<uint64_t, KernelCacheMachOHeader> headers; + std::vector<KernelCacheImage> images; + std::unordered_map<std::string, uint64_t> imageStarts; + + KernelCacheFormat cacheFormat = FilesetCacheFormat; + +#ifndef NDEBUG + void Verify() const; +#endif + + uint64_t BaseAddress() const; + + void Store(SerializationContext&) const; + static std::optional<KernelCache::CacheInfo> Load(DeserializationContext&); +}; + +struct State : public MetadataSerializable<State> +{ + std::unordered_map<uint64_t, KernelCacheImage> loadedImages; + // Store only. Loading is done via `ModifiedState`. + void Store(SerializationContext&, std::optional<KCViewState> viewState) const; +}; + +struct KernelCache::ModifiedState : public State, public MetadataSerializable<KernelCache::ModifiedState> +{ + std::optional<KCViewState> viewState; + + using Base = MetadataSerializable<KernelCache::ModifiedState>; + using Base::AsMetadata; + using Base::LoadFromString; + + void Store(SerializationContext&) const; + static KernelCache::ModifiedState Load(DeserializationContext&); + static KernelCache::ModifiedState LoadAll(BinaryNinja::BinaryView*, const CacheInfo&); + + void Merge(KernelCache::ModifiedState&& other); +}; + +struct KernelCache::ViewSpecificState +{ + std::mutex typeLibraryMutex; + std::unordered_map<std::string, Ref<TypeLibrary>> typeLibraries; + + std::mutex viewOperationsThatInfluenceMetadataMutex; + + std::atomic<BNKCViewLoadProgress> progress; + + std::mutex cacheInfoMutex; + std::shared_ptr<const KernelCache::CacheInfo> cacheInfo; + + std::mutex stateMutex; + struct State state; + + std::atomic<KCViewState> viewState; + uint64_t savedModifications = 0; +}; + +namespace { + + std::shared_ptr<KernelCache::ViewSpecificState> ViewSpecificStateForId( + uint64_t viewIdentifier, bool insertIfNeeded = true) + { + static std::mutex viewSpecificStateMutex; + static std::unordered_map<uint64_t, std::weak_ptr<KernelCache::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<KernelCache::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<KernelCache::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); + } + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-function" + static int64_t readSLEB128(DataBuffer& buffer, size_t length, size_t& offset) + { + uint8_t cur; + int64_t value = 0; + size_t shift = 0; + while (offset < length) + { + cur = buffer[offset++]; + 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(DataBuffer& p, size_t end, size_t& offset) + { + uint64_t result = 0; + int bit = 0; + do + { + if (offset >= end) + return -1; + + uint64_t slice = p[offset] & 0x7f; + + if (bit > 63) + return -1; + else + { + result |= (slice << bit); + bit += 7; + } + } while (p[offset++] & 0x80); + return result; + } + + + uint64_t readValidULEB128(DataBuffer& buffer, size_t& cursor) + { + uint64_t value = readLEB128(buffer, buffer.GetLength(), cursor); + if ((int64_t)value == -1) + throw ReadException(); + return value; + } + +} // namespace + +void KernelCache::PerformInitialLoad(std::lock_guard<std::mutex>& lock) +{ + bool is64 = m_kcView->GetAddressSize() == 8; + + m_logger->LogInfo("Performing initial load of Kernel Cache"); + + m_viewSpecificState->progress = LoadProgressLoadingCaches; + + CacheInfo initialState; + initialState.cacheFormat = FilesetCacheFormat; + + m_viewSpecificState->progress = LoadProgressLoadingImages; + + // We have set up enough metadata to map VM now. + // Iterate load comands: + BinaryReader reader(m_kcView->GetParentView()); + + uint32_t magic = reader.Read32(); + if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit + { + m_logger->LogError("Invalid magic number in KernelCache"); + return; + } + uint32_t cpuType = reader.Read32(); + uint32_t cpuSubtype = reader.Read32(); + uint32_t fileType = reader.Read32(); + if (fileType != MH_FILESET) + { + m_logger->LogError("Invalid file type in KernelCache"); + return; + } + uint32_t ncmds = reader.Read32(); + uint32_t sizeofcmds = reader.Read32(); + uint32_t flags = reader.Read32(); + if ((cpuType & MachOABIMask) != MachOABI64) + { + m_logger->LogError("Invalid ABI in KernelCache. 32 bit not yet supported."); + return; + } + if (is64) + reader.SeekRelative(4); + + uint64_t off = reader.GetOffset(); + + while (ncmds--) + { + reader.Seek(off); + uint32_t cmd = reader.Read32(); + uint32_t cmdsize = reader.Read32(); + if (cmd == LC_FILESET_ENTRY) + { + // uint64_t vmAddr =reader.Read64(); + reader.SeekRelative(m_kcView->GetAddressSize()); + uint64_t fileOff = reader.Read64(); + uint32_t entryID = reader.Read32(); + reader.Seek(entryID + off); + std::string installName = reader.ReadCString(0x1000); + initialState.imageStarts[installName] = fileOff; + } + off += cmdsize; + } + + for (const auto& start : initialState.imageStarts) + { + try { + auto imageHeader = KernelCache::LoadHeaderForAddress(m_kcView, start.second, start.first); + if (imageHeader) + { + KernelCacheImage image; + image.installName = start.first; + image.headerFileLocation = start.second; + for (const auto& segment : imageHeader->segments) + { + char segName[17]; + memcpy(segName, segment.segname, 16); + segName[16] = 0; + MemoryRegion sectionRegion; + sectionRegion.prettyName = imageHeader.value().identifierPrefix + "::" + std::string(segName); + sectionRegion.start = segment.vmaddr; + sectionRegion.size = segment.vmsize; + sectionRegion.fileOffset = segment.fileoff; + uint32_t flags = 0; + if (segment.initprot & MACHO_VM_PROT_READ) + flags |= SegmentReadable; + if (segment.initprot & MACHO_VM_PROT_WRITE) + flags |= SegmentWritable; + if (segment.initprot & MACHO_VM_PROT_EXECUTE) + flags |= SegmentExecutable; + if (((segment.initprot & MACHO_VM_PROT_WRITE) == 0) && + ((segment.maxprot & MACHO_VM_PROT_WRITE) == 0)) + flags |= SegmentDenyWrite; + if (((segment.initprot & MACHO_VM_PROT_EXECUTE) == 0) && + ((segment.maxprot & MACHO_VM_PROT_EXECUTE) == 0)) + flags |= SegmentDenyExecute; + + // 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; + image.regions.push_back(sectionRegion); + } + initialState.images.push_back(image); + } + else + { + m_logger->LogError("Failed to load Mach-O header for %s", start.first.c_str()); + } + } + catch (std::exception& ex) + { + m_logger->LogError("Failed to load Mach-O header for %s: %s", start.first.c_str(), ex.what()); + } + } + + m_cacheInfo = std::make_shared<CacheInfo>(std::move(initialState)); + m_modifiedState->viewState = KCViewStateLoaded; + SaveCacheInfoToKCView(lock); + SaveModifiedStateToKCView(lock); + + m_logger->LogDebug("Finished initial load of KernelCache"); + + m_viewSpecificState->progress = LoadProgressFinished; +} + +void KernelCache::DeserializeFromRawView(std::lock_guard<std::mutex>& lock) +{ + std::lock_guard cacheInfoLock(m_viewSpecificState->cacheInfoMutex); + if (m_viewSpecificState->cacheInfo) + { + m_cacheInfo = m_viewSpecificState->cacheInfo; + m_modifiedState = std::make_unique<ModifiedState>(); + m_metadataValid = true; + return; + } + + if (KernelCacheMetadata::ViewHasMetadata(m_kcView)) + { + auto metadata = KernelCacheMetadata::LoadFromView(m_kcView); + if (!metadata) + { + m_metadataValid = false; + m_logger->LogError("Failed to deserialize Shared Cache metadata"); + return; + } + + m_viewSpecificState->viewState = metadata->state->viewState.value_or(KCViewStateUnloaded); + m_viewSpecificState->state = std::move(*metadata->state); + m_viewSpecificState->cacheInfo = std::move(metadata->cacheInfo); + + m_cacheInfo = m_viewSpecificState->cacheInfo; + m_modifiedState = std::make_unique<ModifiedState>(); + m_metadataValid = true; + return; + } + + m_cacheInfo = nullptr; + m_modifiedState = std::make_unique<ModifiedState>(); + m_modifiedState->viewState = KCViewStateUnloaded; + m_metadataValid = true; +} + + +std::string to_hex_string(uint64_t value) +{ + std::stringstream ss; + ss << std::hex << value; + return ss.str(); +} + + +KernelCache::KernelCache(BinaryNinja::Ref<BinaryNinja::BinaryView> kcView) : m_kcView(kcView), + m_viewSpecificState(ViewSpecificStateForView(kcView)) +{ + std::lock_guard lock(m_mutex); + m_logger = LogRegistry::GetLogger("KernelCache", kcView->GetFile()->GetSessionId()); + if (kcView->GetTypeName() != KC_VIEW_NAME) + { + // Unreachable? + m_logger->LogError("Attempted to create KernelCache object from non-KernelCache view"); + return; + } + + INIT_KERNELCACHE_API_OBJECT() + DeserializeFromRawView(lock); + if (!m_metadataValid) + return; + + if (m_modifiedState->viewState.value_or(m_viewSpecificState->viewState) != KCViewStateUnloaded) + { + m_viewSpecificState->progress = LoadProgressFinished; + return; + } + + std::unique_lock viewOperationsLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); + + try { + PerformInitialLoad(lock); + } + catch (...) + { + m_logger->LogError("Failed to perform initial load of KernelCache"); + } +} + +KernelCache::~KernelCache() { +} + +KernelCache* KernelCache::GetFromKCView(BinaryNinja::Ref<BinaryNinja::BinaryView> kcView) +{ + if (kcView->GetTypeName() != KC_VIEW_NAME) + return nullptr; + try { + return new KernelCache(kcView); + } + catch (...) + { + return nullptr; + } +} + + +uint64_t KernelCache::FastGetImageCount(BinaryNinja::Ref<BinaryNinja::BinaryView> kcView) +{ + if (!kcView->GetParentView()) + return 0; + auto reader = BinaryReader(kcView->GetParentView()); + uint32_t magic = reader.Read32(); + if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit + { + return 0; + } + reader.Seek(0xc); + uint32_t fileType = reader.Read32(); + if (fileType != MH_FILESET) + { + return 0; + } + uint32_t ncmds = reader.Read32(); + + uint64_t imageCount = 0; + + uint64_t off = 0x20; // FIXME 32 bit + while (ncmds--) + { + reader.Seek(off); + uint32_t cmd = reader.Read32(); + uint32_t cmdsize = reader.Read32(); + if (cmd == LC_FILESET_ENTRY) + { + imageCount++; + } + off += cmdsize; + } + + return imageCount; +} + + +const std::unordered_map<std::string, uint64_t>& KernelCache::AllImageStarts() const +{ + return m_cacheInfo->imageStarts; +} + + +const std::unordered_map<uint64_t, KernelCacheMachOHeader> KernelCache::AllImageHeaders() const +{ + std::unordered_map<uint64_t, KernelCacheMachOHeader> headers; + for (const auto& start : m_cacheInfo->imageStarts) + { + auto header = LoadHeaderForAddress(m_kcView, start.second, start.first); + if (header) + { + headers[start.second] = *header; + } + } + return headers; +} + + +KCViewState KernelCache::ViewState() const +{ + return m_viewSpecificState->viewState; +} + +std::optional<uint64_t> KernelCache::GetImageStart(std::string installName) +{ + for (const auto& [name, start] : m_cacheInfo->imageStarts) + { + if (name == installName) + { + return start; + } + } + return {}; +} + +std::optional<KernelCacheMachOHeader> KernelCache::HeaderForVMAddress(uint64_t address) +{ + for (const auto& img : m_cacheInfo->images) + { + for (const auto& region : img.regions) + { + if (region.start <= address && region.start + region.size > address) + { + return LoadHeaderForAddress(m_kcView, img.headerFileLocation, img.installName); + } + } + } + return {}; +} + +std::optional<KernelCacheMachOHeader> KernelCache::HeaderForFileAddress(uint64_t address) +{ + for (const auto& img : m_cacheInfo->images) + { + for (const auto& region : img.regions) + { + if (region.fileOffset <= address && region.fileOffset + region.size > address) + { + return LoadHeaderForAddress(m_kcView, img.headerFileLocation, img.installName); + } + } + } + return {}; +} + +std::string KernelCache::NameForAddress(uint64_t address) +{ + if (auto header = HeaderForVMAddress(address)) + { + for (const auto& section : header->sections) + { + if (section.addr <= address && section.addr + section.size > address) + { + char sectionName[17]; + strncpy(sectionName, section.sectname, 16); + sectionName[16] = '\0'; + return header->identifierPrefix + "::" + sectionName; + } + } + } + return ""; +} + +std::string KernelCache::ImageNameForAddress(uint64_t address) +{ + if (auto header = HeaderForVMAddress(address)) + { + return header->identifierPrefix; + } + return ""; +} + +bool KernelCache::LoadImageContainingAddress(uint64_t address) +{ + std::lock_guard<std::mutex> lock(m_mutex); + return LoadImageContainingAddress(lock, address); +} + +bool KernelCache::LoadImageContainingAddress(std::lock_guard<std::mutex>& lock, uint64_t address) +{ + for (const auto& img : m_cacheInfo->images) + { + for (const auto& region : img.regions) + { + if (region.start <= address && region.start + region.size > address) + { + return LoadImageWithInstallName(lock, img.installName); + } + } + } + return false; +} + + +bool KernelCache::LoadImageWithInstallName(std::string installName) +{ + std::lock_guard<std::mutex> lock(m_mutex); + return LoadImageWithInstallName(lock, installName); +} + + +bool KernelCache::LoadImageWithInstallName(std::lock_guard<std::mutex>& lock, std::string installName) +{ + auto settings = m_kcView->GetLoadSettings(KC_VIEW_NAME); + + std::lock_guard viewSpecificStateLock(m_viewSpecificState->viewOperationsThatInfluenceMetadataMutex); + + m_logger->LogInfo("Loading image %s", installName.c_str()); + const KernelCacheImage* targetImage = nullptr; + + // FIXME at some point the logic func should be one with targetImage passed, and an installName wrapper can be added. + // In many use cases of this function we already have our target image. + for (auto& cacheImage : m_cacheInfo->images) + { + if (cacheImage.installName == installName) + { + targetImage = &cacheImage; + break; + } + } + const auto header = LoadHeaderForAddress(m_kcView, targetImage->headerFileLocation, targetImage->installName); + + if (!header) + { + return false; + } + + m_modifiedState->viewState = KCViewStateLoadedWithImages; + + InitializeSegmentsForHeader(m_kcView, *header, *targetImage); + + // Optimize relocations we just added en masse. + m_kcView->FinalizeNewSegments(); + + // FIXME Load TypeLibrary here when we have those for the kernel. + + m_modifiedState->loadedImages[targetImage->headerFileLocation] = *targetImage; + + SaveModifiedStateToKCView(lock); + + auto h = KernelCache::LoadHeaderForAddress(m_kcView, targetImage->headerFileLocation, installName); + if (!h.has_value()) + { + return false; + } + + KernelCache::InitializeHeader(m_kcView, *h); + + m_kcView->AddAnalysisOption("linearsweep"); + m_kcView->UpdateAnalysis(); + + return true; +} + +std::optional<KernelCacheMachOHeader> KernelCache::LoadHeaderForAddress(Ref<BinaryView> view, uint64_t address, std::string installName) +{ + KernelCacheMachOHeader header; + + header.installName = installName; + header.identifierPrefix = base_name(installName); + + std::string errorMsg; + // address is a Raw file offset + BinaryReader reader(view->GetParentView()); + 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(); + + // Parse segment commands + try + { + for (size_t i = 0; i < header.ident.ncmds; i++) + { + // BNLogInfo("of 0x%llx", reader.GetOffset()); + load_command load; + segment_command_64 segment64; + section_64 sect; + memset(§, 0, sizeof(sect)); + size_t curOffset = reader.GetOffset(); + load.cmd = reader.Read32(); + load.cmdsize = reader.Read32(); + size_t nextOffset = curOffset + load.cmdsize; + if (load.cmdsize < sizeof(load_command)) + return {}; + + switch (load.cmd) + { + case LC_MAIN: + { + uint64_t entryPoint = reader.Read64(); + header.entryPoints.push_back({entryPoint, true}); + (void)reader.Read64(); // Stack start + break; + } + case LC_SEGMENT: // map the 32bit version to 64 bits + segment64.cmd = LC_SEGMENT_64; + reader.Read(&segment64.segname, 16); + segment64.vmaddr = reader.Read32(); + segment64.vmsize = reader.Read32(); + segment64.fileoff = reader.Read32(); + segment64.filesize = reader.Read32(); + segment64.maxprot = reader.Read32(); + segment64.initprot = reader.Read32(); + segment64.nsects = reader.Read32(); + segment64.flags = reader.Read32(); + if (strncmp(segment64.segname, "__TEXT\0", 7) == 0) + { + header.relocationBase = segment64.vmaddr; + header.textBase = segment64.vmaddr; + header.textBaseFileOffset = segment64.fileoff; + } + 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 (!strncmp(sect.sectname, "__mod_term_func", 15)) + header.moduleTermSections.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 (strncmp(segment64.segname, "__TEXT\0", 7) == 0) + { + header.relocationBase = segment64.vmaddr; + header.textBase = segment64.vmaddr; + header.textBaseFileOffset = segment64.fileoff; + } + 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; + char segmentName[17]; + memcpy(segmentName, section.segname, sizeof(section.segname)); + segmentName[16] = 0; + if (header.identifierPrefix.empty()) + header.sectionNames.push_back(sectionName); + else + header.sectionNames.push_back(header.identifierPrefix + "::" + segmentName + ":" + sectionName); + } + } + catch (ReadException&) + { + return {}; + } + + return header; +} + +bool KernelCache::InitializeSegmentsForHeader(Ref<BinaryView> view, const KernelCacheMachOHeader& header, const KernelCacheImage& targetImage) +{ + // FIXME this uselessly loads chained fixups if an image is already loaded. + auto logger = LogRegistry::GetLogger("KernelCache", view->GetFile()->GetSessionId()); + + bool hasRegionsToLoad = false; + + auto reader = BinaryReader(view->GetParentView()); + + reader.Seek(0x10); + uint32_t ncmds = reader.Read32(); + uint64_t off = 0x20; // FIXME 32 bit + + uint64_t kernelBaseAddress = 0; + + uint32_t chainedFixupDataOff = 0; + uint32_t chainedFixupDataSize = 0; + + while (ncmds--) + { + reader.Seek(off); + uint32_t cmd = reader.Read32(); + uint32_t cmdsize = reader.Read32(); + if (cmd == LC_SEGMENT_64) + { + segment_command_64 segment {}; + reader.Read(&segment.segname, 16); + if (strncmp(segment.segname, "__TEXT\0", 7) == 0) + { + kernelBaseAddress = reader.Read64(); + } + } + if (cmd == LC_DYLD_CHAINED_FIXUPS) + { + chainedFixupDataOff = reader.Read32(); + chainedFixupDataSize = reader.Read32(); + } + off += cmdsize; + } + + BNRelocationInfo reloc; + memset(&reloc, 0, sizeof(BNRelocationInfo)); + reloc.type = StandardRelocationType; + reloc.size = 8; + reloc.nativeType = BINARYNINJA_MANUAL_RELOCATION; + logger->LogDebug("Processing Chained Fixups"); + + std::vector<std::pair<uint64_t, uint64_t>> relocations; + if (chainedFixupDataOff && chainedFixupDataSize) + { + BinaryReader parentReader(view->GetParentView()); + + try { + dyld_chained_fixups_header fixupsHeader {}; + uint64_t fixupHeaderAddress = chainedFixupDataOff; + parentReader.Seek(fixupHeaderAddress); + fixupsHeader.fixups_version = parentReader.Read32(); + fixupsHeader.starts_offset = parentReader.Read32(); + fixupsHeader.imports_offset = parentReader.Read32(); + fixupsHeader.symbols_offset = parentReader.Read32(); + fixupsHeader.imports_count = parentReader.Read32(); + fixupsHeader.imports_format = parentReader.Read32(); + fixupsHeader.symbols_format = parentReader.Read32(); + + logger->LogDebug("Chained Fixups: Header @ %llx // Fixups version %lx", fixupHeaderAddress, fixupsHeader.fixups_version); + + if (fixupsHeader.fixups_version > 0) + { + logger->LogError("Chained Fixup parsing failed. Unknown Fixups Version"); + throw ReadException(); + } + + uint64_t fixupStartsAddress = fixupHeaderAddress + fixupsHeader.starts_offset; + parentReader.Seek(fixupStartsAddress); + dyld_chained_starts_in_image segs {}; + segs.seg_count = parentReader.Read32(); + std::vector<uint32_t> segInfoOffsets {}; + for (size_t i = 0; i < segs.seg_count; i++) + { + segInfoOffsets.push_back(parentReader.Read32()); + } + for (auto offset : segInfoOffsets) + { + if (!offset) + continue; + + dyld_chained_starts_in_segment starts {}; + uint64_t startsAddr = fixupStartsAddress + offset; + parentReader.Seek(startsAddr); + starts.size = parentReader.Read32(); + starts.page_size = parentReader.Read16(); + starts.pointer_format = parentReader.Read16(); + starts.segment_offset = parentReader.Read64(); + starts.max_valid_pointer = parentReader.Read32(); + starts.page_count = parentReader.Read16(); + + uint8_t strideSize; + ChainedFixupPointerGeneric format; + + // Firmware formats will require digging up whatever place they're being used and reversing it. + // They are not handled by dyld. + switch (starts.pointer_format) { + case DYLD_CHAINED_PTR_ARM64E: + case DYLD_CHAINED_PTR_ARM64E_USERLAND: + case DYLD_CHAINED_PTR_ARM64E_USERLAND24: + strideSize = 8; + format = GenericArm64eFixupFormat; + break; + case DYLD_CHAINED_PTR_ARM64E_KERNEL: + strideSize = 4; + format = GenericArm64eFixupFormat; + break; + // case DYLD_CHAINED_PTR_ARM64E_FIRMWARE: Unsupported. + case DYLD_CHAINED_PTR_64: + case DYLD_CHAINED_PTR_64_OFFSET: + strideSize = 4; + format = Generic64FixupFormat; + break; + case DYLD_CHAINED_PTR_32: + case DYLD_CHAINED_PTR_32_CACHE: + strideSize = 4; + format = Generic32FixupFormat; + break; + case DYLD_CHAINED_PTR_32_FIRMWARE: + strideSize = 4; + format = Firmware32FixupFormat; + break; + case DYLD_CHAINED_PTR_64_KERNEL_CACHE: + strideSize = 4; + format = Kernel64Format; + break; + case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE: + strideSize = 1; + format = Kernel64Format; + break; + default: + { + logger->LogError("Chained Fixups: Unknown or unsupported pointer format %d, " + "unable to process chains for segment at @llx", starts.pointer_format, starts.segment_offset); + continue; + } + } + + uint16_t fmt = starts.pointer_format; + logger->LogDebug("Chained Fixups: Segment start @ %llx, fmt %d", starts.segment_offset, fmt); + + uint64_t pageStartsTableStartAddress = parentReader.GetOffset(); + std::vector<std::vector<uint16_t>> pageStartOffsets {}; + for (size_t i = 0; i < starts.page_count; i++) + { + // On armv7, Chained pointers here can have multiple starts. + // And if so, there's another table *overlapping* the table we're currently reading. + // dyld handles this through 'overflow indexing' + // This is technically supported on other archs however is not (currently) used. + parentReader.Seek(pageStartsTableStartAddress + (sizeof(uint16_t) * i)); + uint16_t start = parentReader.Read16(); + if ((start & DYLD_CHAINED_PTR_START_MULTI) && (start != DYLD_CHAINED_PTR_START_NONE)) + { + uint64_t overflowIndex = start & ~DYLD_CHAINED_PTR_START_MULTI; + std::vector<uint16_t> pageStartSubStarts; + parentReader.Seek(pageStartsTableStartAddress + (overflowIndex * sizeof(uint16_t))); + bool done = false; + while (!done) + { + uint16_t subPageStart = parentReader.Read16(); + if ((subPageStart & DYLD_CHAINED_PTR_START_LAST) == 0) + { + pageStartSubStarts.push_back(subPageStart); + } + else + { + pageStartSubStarts.push_back(subPageStart & ~DYLD_CHAINED_PTR_START_LAST); + done = true; + } + } + pageStartOffsets.push_back(pageStartSubStarts); + } + else + { + pageStartOffsets.push_back({start}); + } + } + + int i = -1; + for (auto pageStarts : pageStartOffsets) + { + i++; + uint64_t pageAddress = starts.segment_offset + (i * starts.page_size); + for (uint16_t start : pageStarts) + { + if (start == DYLD_CHAINED_PTR_START_NONE) + continue; + + uint64_t chainEntryAddress = pageAddress + start; + + bool fixupsDone = false; + + while (!fixupsDone) + { + ChainedFixupPointer pointer; + parentReader.Seek(chainEntryAddress); + if (format == Generic32FixupFormat || format == Firmware32FixupFormat) + pointer.raw32 = (uint32_t)(uintptr_t)parentReader.Read32(); + else + pointer.raw64 = (uintptr_t)parentReader.Read64(); + + bool bind = false; + uint64_t nextEntryStrideCount; + + switch (format) + { + case Generic32FixupFormat: + bind = pointer.generic32.bind.bind; + nextEntryStrideCount = pointer.generic32.rebase.next; + break; + case Generic64FixupFormat: + bind = pointer.generic64.bind.bind; + nextEntryStrideCount = pointer.generic64.rebase.next; + break; + case GenericArm64eFixupFormat: + bind = pointer.arm64e.bind.bind; + nextEntryStrideCount = pointer.arm64e.rebase.next; + break; + case Firmware32FixupFormat: + nextEntryStrideCount = pointer.firmware32.next; + bind = false; + break; + case Kernel64Format: + nextEntryStrideCount = pointer.kernel64.next; + bind = false; + } + + logger->LogTrace("Chained Fixups: @ 0x%llx ( 0x%llx ) - %d 0x%llx", chainEntryAddress, + kernelBaseAddress + (chainEntryAddress), + bind, nextEntryStrideCount); + + if (!bind) + { + uint64_t entryOffset; + switch (starts.pointer_format) + { + case DYLD_CHAINED_PTR_ARM64E: + case DYLD_CHAINED_PTR_ARM64E_KERNEL: + case DYLD_CHAINED_PTR_ARM64E_USERLAND: + case DYLD_CHAINED_PTR_ARM64E_USERLAND24: + { + if (pointer.arm64e.bind.auth) + entryOffset = pointer.arm64e.authRebase.target; + else + entryOffset = pointer.arm64e.rebase.target; + + if ( starts.pointer_format != DYLD_CHAINED_PTR_ARM64E || pointer.arm64e.bind.auth) + entryOffset += kernelBaseAddress; + + break; + } + case DYLD_CHAINED_PTR_64: + entryOffset = pointer.generic64.rebase.target; + break; + case DYLD_CHAINED_PTR_64_OFFSET: + entryOffset = pointer.generic64.rebase.target + kernelBaseAddress; + break; + // We expect only cases past this point will be applicable in this context. + case DYLD_CHAINED_PTR_64_KERNEL_CACHE: + case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE: + entryOffset = pointer.kernel64.target + kernelBaseAddress; + break; + case DYLD_CHAINED_PTR_32: + case DYLD_CHAINED_PTR_32_CACHE: + entryOffset = pointer.generic32.rebase.target; + break; + case DYLD_CHAINED_PTR_32_FIRMWARE: + entryOffset = pointer.firmware32.target; + break; + } + + // logger->LogInfo("Chained Fixups: Pointer at 0x%llx -> 0x%llx", view->GetStart() + chainEntryAddress, entryOffset); + + relocations.emplace_back(kernelBaseAddress + chainEntryAddress, entryOffset); + } + + chainEntryAddress += (nextEntryStrideCount * strideSize); + + if (chainEntryAddress > pageAddress + starts.page_size) + { + // Something is seriously wrong here. likely malformed binary, or our parsing failed elsewhere. + // This will log the pointer in mapped memory. + logger->LogError("Chained Fixups: Pointer at 0x%llx left page", + kernelBaseAddress + ((chainEntryAddress - (nextEntryStrideCount * strideSize)))); + fixupsDone = true; + } + + if (nextEntryStrideCount == 0) + fixupsDone = true; + } + } + } + } + } + catch (ReadException&) + { + logger->LogError("Chained Fixup parsing failed"); + } + } + + // Critical that we sort these as this optimization allows us to save a lot of time on image loading when we + // need to apply relocations later. Now is the best time, since we aren't going to find more relocs now. + std::sort(relocations.begin(), relocations.end(), + [](const std::pair<uint64_t, uint64_t>& a, const std::pair<uint64_t, uint64_t>& b) { + return a.first < b.first; + }); + + for (auto& region : targetImage.regions) + { + if (view->IsValidOffset(region.start)) + { + logger->LogDebug("Skipping region %s as it is already loaded.", region.prettyName.c_str()); + continue; + } + + hasRegionsToLoad = true; + + // Considerations at this point in processing: + // * View is already finalized + // * view (shown to user) has only one segment, at the kernel start address + // * All of our relevant data is contained in the Raw view. + view->AddAutoSegment(region.start, region.size, region.fileOffset, region.size, region.flags); + + auto begin = std::lower_bound(relocations.begin(), relocations.end(), region.start, + [](const std::pair<uint64_t, uint64_t>& reloc, uint64_t addr) { + return reloc.first < addr; + }); + + auto arch = view->GetDefaultArchitecture(); + // Process relocations until the VM address is beyond our region + for (auto it = begin; it != relocations.end() && it->first < region.start + region.size; ++it) { + reloc.address = it->first; + view->DefineRelocation(arch, reloc, it->second, reloc.address); + } + } + + if (!hasRegionsToLoad) + { + logger->LogWarn("No regions to load for image %s", header.installName.c_str()); + return false; + } + + return true; +} + +void KernelCache::InitializeHeader(Ref<BinaryView> view, KernelCacheMachOHeader header) +{ + Ref<Settings> settings = view->GetLoadSettings(KC_VIEW_NAME); + bool applyFunctionStarts = true; // FIXME + + view->AddAutoSection(header.identifierPrefix + "::__macho_header", header.textBase, header.ident.sizeofcmds + sizeof(mach_header_64), ReadOnlyDataSectionSemantics); + + for (size_t i = 0; i < header.sections.size(); i++) + { + if (!header.sections[i].size) + 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->AddAutoSection(header.sectionNames[i], header.sections[i].addr, header.sections[i].size, semantics, + type, header.sections[i].align); + } + + auto typeLib = view->GetTypeLibrary(header.installName); + + BinaryReader virtualReader(view->GetParentView()); + + view->DefineDataVariable(header.textBase, Type::NamedType(view, QualifiedName("mach_header_64"))); + view->DefineAutoSymbol( + new Symbol(DataSymbol, "__macho_header::" + header.identifierPrefix, header.textBase, LocalBinding)); + + try + { + virtualReader.Seek(header.textBaseFileOffset + sizeof(mach_header_64)); + size_t sectionNum = 0; + uint64_t curVirtualOffset = header.textBase + sizeof(mach_header_64); + uint64_t curFileOffset = header.textBaseFileOffset + sizeof(mach_header_64); + for (size_t i = 0; i < header.ident.ncmds; i++) + { + load_command load; + curFileOffset = virtualReader.GetOffset(); + load.cmd = virtualReader.Read32(); + load.cmdsize = virtualReader.Read32(); + uint64_t nextOffset = curFileOffset + load.cmdsize; + switch (load.cmd) + { + case LC_SEGMENT: + { + view->DefineDataVariable(curVirtualOffset, 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->DefineAutoSymbol(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(curVirtualOffset, 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->DefineAutoSymbol(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(curVirtualOffset, Type::NamedType(view, QualifiedName("symtab"))); + break; + case LC_DYSYMTAB: + view->DefineDataVariable(curVirtualOffset, Type::NamedType(view, QualifiedName("dysymtab"))); + break; + case LC_UUID: + view->DefineDataVariable(curVirtualOffset, 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(curVirtualOffset, Type::NamedType(view, QualifiedName("dylib_command"))); + if (load.cmdsize - 24 <= 150) + view->DefineDataVariable( + curVirtualOffset + 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(curVirtualOffset, Type::NamedType(view, QualifiedName("linkedit_data"))); + break; + case LC_ENCRYPTION_INFO: + view->DefineDataVariable(curVirtualOffset, Type::NamedType(view, QualifiedName("encryption_info"))); + break; + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + view->DefineDataVariable(curVirtualOffset, Type::NamedType(view, QualifiedName("version_min"))); + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + view->DefineDataVariable(curVirtualOffset, Type::NamedType(view, QualifiedName("dyld_info"))); + break; + default: + view->DefineDataVariable(curVirtualOffset, Type::NamedType(view, QualifiedName("load_command"))); + break; + } + + view->DefineAutoSymbol(new Symbol(DataSymbol, + "__macho_load_command::" + header.identifierPrefix + "_[" + std::to_string(i) + "]", curVirtualOffset, + LocalBinding)); + curVirtualOffset += (nextOffset - curFileOffset); + virtualReader.Seek(nextOffset); + } + } + catch (ReadException&) + { + LogError("Error when applying Mach-O header types at %" PRIx64, header.textBase); + } + + if (applyFunctionStarts && header.functionStartsPresent && header.linkeditPresent) + { + size_t i = 0; + uint64_t curfunc = header.textBase; + uint64_t curOffset; + + auto funcStarts = view->GetParentView()->ReadBuffer(header.functionStarts.funcoff, header.functionStarts.funcsize); + + while (i < header.functionStarts.funcsize) + { + curOffset = readLEB128(funcStarts, header.functionStarts.funcsize, i); + curfunc += curOffset; + // LogError("0x%llx, 0x%llx", header.textBase, curOffset); + if (curOffset == 0) + continue; + uint64_t target = curfunc; + Ref<Platform> targetPlatform = view->GetDefaultPlatform(); + view->AddFunctionForAnalysis(targetPlatform, target); + } + } + + view->BeginBulkModifySymbols(); + ParseSymbolTable(view, header); + + BinaryReader reader(view); + + size_t modInitFuncCnt = 0; + for (const auto& moduleInitSection : header.moduleInitSections) + { + // The mod_init section contains a list of function pointers called at initialization + // if we don't have a defined entrypoint then use the first one in the list as the entrypoint + size_t i = 0; + reader.Seek(moduleInitSection.addr); + for (; i < (moduleInitSection.size / view->GetAddressSize()); i++) + { + uint64_t target = (view->GetAddressSize() == 4) ? reader.Read32() : reader.Read64(); + if (!view->IsValidOffset(target)) + continue; + Ref<Platform> targetPlatform = view->GetDefaultPlatform()->GetAssociatedPlatformByAddress(target); + auto name = header.identifierPrefix + "_init_func_" + std::to_string(modInitFuncCnt++); + view->AddEntryPointForAnalysis(targetPlatform, target); + auto symbol = new Symbol(FunctionSymbol, name, target, GlobalBinding); + view->DefineAutoSymbol(symbol); + } + } + + view->EndBulkModifySymbols(); +} + + +std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> KernelCache::ParseSymbolTable(Ref<BinaryView> view, KernelCacheMachOHeader header, bool defineSymbolsInView) +{ + if (header.symtab.symoff != 0 && header.linkeditPresent) + { + // Mach-O View symtab processing with + // a ton of stuff cut out so it can work + // auto symtab = reader->ReadBuffer(header.symtab.symoff, header.symtab.nsyms * sizeof(nlist_64)); + auto strtab = view->GetParentView()->ReadBuffer(header.symtab.stroff, header.symtab.strsize); + nlist_64 sym; + memset(&sym, 0, sizeof(sym)); + std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> symbolInfos; + for (size_t i = 0; i < header.symtab.nsyms; i++) + { + view->GetParentView()->Read(&sym, header.symtab.symoff + i * sizeof(nlist_64), sizeof(nlist_64)); + if (sym.n_strx >= header.symtab.strsize || ((sym.n_type & N_TYPE) == N_INDR)) + continue; + + std::string symbol((char*)strtab.GetDataAt(sym.n_strx)); + // BNLogError("%s: 0x%llx", symbol.c_str(), sym.n_value); + if (symbol == "<redacted>") + continue; + + BNSymbolType type = DataSymbol; + uint32_t flags; + if ((sym.n_type & N_TYPE) == N_SECT && sym.n_sect > 0 && (size_t)(sym.n_sect - 1) < header.sections.size()) + {} + else if ((sym.n_type & N_TYPE) == N_ABS) + {} + else if ((sym.n_type & 0x1)) + { + type = ExternalSymbol; + } + else + continue; + + for (auto s : header.sections) + { + if (s.addr < sym.n_value) + { + if (s.addr + s.size > sym.n_value) + { + flags = s.flags; + } + } + } + + if (type != ExternalSymbol) + { + if ((flags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS + || (flags & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) + type = FunctionSymbol; + else + type = DataSymbol; + } + if ((sym.n_desc & N_ARM_THUMB_DEF) == N_ARM_THUMB_DEF) + sym.n_value++; + + std::string rawName = symbol; + std::string shortName = symbol; + std::string fullName = symbol; + Ref<Type> typeRef = nullptr; + + if (view->GetDefaultArchitecture()) + { + QualifiedName demangledName; + Ref<Type> demangledType; + bool simplify = Settings::Instance()->Get<bool>("analysis.types.templateSimplifier", view); + if (DemangleGeneric(view->GetDefaultArchitecture(), rawName, demangledType, demangledName, view, simplify)) + { + shortName = demangledName.GetString(); + fullName = shortName; + if (demangledType) + fullName += demangledType->GetStringAfterName(); + if (!typeRef && !view->GetDefaultPlatform()->GetFunctionByName(rawName)) + typeRef = demangledType; + } + else + { + LogDebug("Failed to demangle name: '%s'\n", rawName.c_str()); + } + } + + if (defineSymbolsInView) + { + auto symbolObj = new Symbol(type, shortName, fullName, rawName, sym.n_value, GlobalBinding); + + if (typeRef) + view->DefineAutoSymbolAndVariableOrFunction(view->GetDefaultPlatform(), symbolObj, typeRef); + else + { + view->DefineAutoSymbol(symbolObj); + if (type == FunctionSymbol) + { + Ref<Platform> targetPlatform = view->GetDefaultPlatform(); + view->AddFunctionForAnalysis(targetPlatform, sym.n_value); + } + } + } + symbolInfos.push_back({sym.n_value, {type, fullName}}); + } + return symbolInfos; + } + return {}; +} + + +struct ExportNode +{ + std::string text; + uint64_t offset; + uint64_t flags; +}; + + +void KernelCache::ReadExportNode(Ref<BinaryView> view, std::vector<Ref<Symbol>>& symbolList, KernelCacheMachOHeader& header, DataBuffer& buffer, uint64_t textBase, + const std::string& currentText, size_t cursor, uint32_t endGuard) +{ + + if (cursor > endGuard) + throw ReadException(); + + uint64_t terminalSize = readValidULEB128(buffer, cursor); + uint64_t childOffset = cursor + terminalSize; + if (terminalSize != 0) { + uint64_t imageOffset = 0; + uint64_t flags = readValidULEB128(buffer, cursor); + if (!(flags & EXPORT_SYMBOL_FLAGS_REEXPORT)) + { + imageOffset = readValidULEB128(buffer, cursor); + // auto symbolType = view->GetAnalysisFunctionsForAddress(textBase + imageOffset).size() ? FunctionSymbol : DataSymbol; + { + 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; + } + } + } + if ((flags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS + || (flags & S_ATTR_SOME_INSTRUCTIONS) == S_ATTR_SOME_INSTRUCTIONS) + type = FunctionSymbol; + else + type = DataSymbol; + +#if EXPORT_TRIE_DEBUG + // BNLogInfo("export: %s -> 0x%llx", n.text.c_str(), image.baseAddress + n.offset); +#endif + auto sym = new Symbol(type, currentText, textBase + imageOffset); + symbolList.push_back(sym); + } + } + } + } + cursor = childOffset; + uint8_t childCount = buffer[cursor]; + cursor++; + if (cursor > endGuard) + throw ReadException(); + for (uint8_t i = 0; i < childCount; ++i) + { + std::string childText; + while (buffer[cursor] != 0 & cursor <= endGuard) + childText.push_back(buffer[cursor++]); + cursor++; + if (cursor > endGuard) + throw ReadException(); + auto next = readValidULEB128(buffer, cursor); + if (next == 0) + throw ReadException(); + ReadExportNode(view, symbolList, header, buffer, textBase, currentText + childText, next, endGuard); + } +} + + +std::vector<Ref<Symbol>> KernelCache::ParseExportTrie(Ref<BinaryView> view, KernelCacheMachOHeader header) +{ + if (!header.exportTriePresent || !header.exportTrie.dataoff || !header.exportTrie.datasize) + return {}; + + std::vector<Ref<Symbol>> symbols; + try + { + std::vector<ExportNode> nodes; + + DataBuffer buffer = view->GetParentView()->ReadBuffer(header.exportTrie.dataoff, header.exportTrie.datasize); + ReadExportNode(view, symbols, header, buffer, header.textBase, "", 0, header.exportTrie.datasize); + } + catch (std::exception& e) + { + BNLogError("Failed to load Export Trie"); + } + return symbols; +} + +std::vector<std::string> KernelCache::GetAvailableImages() +{ + std::vector<std::string> installNames; + for (const auto& img : m_cacheInfo->images) + { + installNames.push_back(img.installName); + } + return installNames; +} + + +std::vector<KernelCacheImage> KernelCache::GetLoadedImages() +{ + std::lock_guard lock(m_viewSpecificState->stateMutex); + std::vector<KernelCacheImage> images; + for (const auto& [fileStart, image] : m_viewSpecificState->state.loadedImages) + { + images.push_back(image); + } + return images; +} + + +std::vector<std::pair<uint64_t, std::pair<std::string, std::string>>> KernelCache::LoadAllSymbolsAndWait() +{ + std::vector<std::pair<uint64_t, std::pair<std::string, std::string>>> symbols; + for (const auto& img : m_cacheInfo->images) + { + auto header = HeaderForFileAddress(img.headerFileLocation); + if (header) + { + auto newSymbolList = ParseSymbolTable(m_kcView, *header, false); + for (const auto& symbol : newSymbolList) + { + if (symbol.first != 0) + symbols.push_back({symbol.first, {header->installName, symbol.second.second}}); + } + } + } + return symbols; +} + + +std::string KernelCache::SerializedImageHeaderForVMAddress(uint64_t address) +{ + auto header = HeaderForVMAddress(address); + if (header) + { + return header->AsString(); + } + return ""; +} + + +std::string KernelCache::SerializedImageHeaderForName(std::string name) +{ + if (auto it = m_cacheInfo->imageStarts.find(name); it != m_cacheInfo->imageStarts.end()) + { + if (auto header = HeaderForFileAddress(it->second)) + { + return header->AsString(); + } + } + return ""; +} + + + +bool KernelCache::SaveCacheInfoToKCView(std::lock_guard<std::mutex>&) +{ + if (!m_cacheInfo) + return false; + + // The initial load should only populate `m_cacheInfo` and should not modify any state. + assert(m_modifiedState->loadedImages.size() == 0); + + auto data = m_cacheInfo->AsMetadata(); + m_kcView->StoreMetadata(KernelCacheMetadata::Tag, data); + m_kcView->GetParentView()->StoreMetadata(KernelCacheMetadata::Tag, data); + + { + std::lock_guard lock(m_viewSpecificState->cacheInfoMutex); + if (m_cacheInfo && !m_viewSpecificState->cacheInfo) + m_viewSpecificState->cacheInfo = m_cacheInfo; + else if (m_cacheInfo != m_viewSpecificState->cacheInfo) + abort(); + } + + m_metadataValid = true; + return true; +} + + +bool KernelCache::SaveModifiedStateToKCView(std::lock_guard<std::mutex>&) +{ + if (!m_kcView) + return false; + + { + std::lock_guard lock(m_viewSpecificState->stateMutex); + + uint64_t modificationNumber = m_viewSpecificState->savedModifications++; + if (modificationNumber == 0) + { + // The cached state in the view-specific state has not yet been saved. + // For the initial load of a shared cache this will be empty, but if + // the shared cache has been loaded from a database then this will + // contain the full state that was saved. + std::string metadataKey = KernelCacheMetadata::ModifiedStateTagPrefix + std::to_string(modificationNumber); + auto data = m_viewSpecificState->state.AsMetadata(m_viewSpecificState->viewState); + + m_kcView->StoreMetadata(metadataKey, data); + m_kcView->GetParentView()->StoreMetadata(metadataKey, data); + modificationNumber = m_viewSpecificState->savedModifications++; + } + + std::string metadataKey = KernelCacheMetadata::ModifiedStateTagPrefix + std::to_string(modificationNumber); + auto data = m_modifiedState->AsMetadata(); + + m_kcView->StoreMetadata(metadataKey, data); + m_kcView->GetParentView()->StoreMetadata(metadataKey, data); + + Ref<Metadata> count = new Metadata(m_viewSpecificState->savedModifications); + m_kcView->StoreMetadata(KernelCacheMetadata::ModifiedStateCountTag, count); + m_kcView->GetParentView()->StoreMetadata(KernelCacheMetadata::ModifiedStateCountTag, count); + + m_viewSpecificState->state.loadedImages.merge(m_modifiedState->loadedImages); + // `merge` will move a node to the target map if the corresponding key does not yet exist. + // If we've redundantly loaded images, we may be left with symbols in the source maps. + m_modifiedState->loadedImages.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 = KernelCacheMetadata::ModifiedStateTagPrefix + std::to_string(i); + bool done = true; + if (m_kcView->QueryMetadata(metadataKey)) + { + done = false; + m_kcView->RemoveMetadata(metadataKey); + } + if (m_kcView->GetParentView()->QueryMetadata(metadataKey)) + { + done = false; + m_kcView->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; +} + + +const std::string KernelCacheMetadata::Tag = "KERNELCACHE-KernelCacheData"; +const std::string KernelCacheMetadata::CacheInfoTag = "KERNELCACHE-CacheInfo"; +const std::string KernelCacheMetadata::ModifiedStateTagPrefix = "KERNELCACHE-ModifiedState-"; +const std::string KernelCacheMetadata::ModifiedStateCountTag = "KERNELCACHE-ModifiedState-Count"; + +KernelCacheMetadata::~KernelCacheMetadata() = default; +KernelCacheMetadata::KernelCacheMetadata(KernelCacheMetadata&&) = default; +KernelCacheMetadata& KernelCacheMetadata::operator=(KernelCacheMetadata&&) = default; + +KernelCacheMetadata::KernelCacheMetadata(KernelCache::CacheInfo cacheInfo, KernelCache::ModifiedState state) : + cacheInfo(std::make_unique<KernelCache::CacheInfo>(std::move(cacheInfo))), + state(std::make_unique<KernelCache::ModifiedState>(std::move(state))) +{} + + +// static +bool KernelCacheMetadata::ViewHasMetadata(BinaryView* view) +{ + return view->QueryMetadata(Tag); +} + +// static +std::optional<KernelCacheMetadata> KernelCacheMetadata::LoadFromView(BinaryView* view) +{ + Ref<Metadata> viewMetadata = view->QueryMetadata(Tag); + if (!viewMetadata) + return std::nullopt; + + auto cacheInfo = KernelCache::CacheInfo::LoadFromString(viewMetadata->GetString()); + if (!cacheInfo) + return std::nullopt; + + auto modifiedState = KernelCache::ModifiedState::LoadAll(view, *cacheInfo); + return KernelCacheMetadata(std::move(*cacheInfo), std::move(modifiedState)); +} + +std::string KernelCacheMetadata::InstallNameForImageBaseAddress(uint64_t baseAddress) const +{ + 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; +} + +std::vector<KernelCacheImage> KernelCacheMetadata::LoadedImages() +{ + std::vector<KernelCacheImage> images; + for (const auto& image : state->loadedImages) + { + images.push_back(image.second); + } + return images; +} + +} + +extern "C" +{ + BNKernelCache* BNGetKernelCache(BNBinaryView* data) + { + if (!data) + return nullptr; + + Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); + if (auto cache = KernelCache::GetFromKCView(view)) + { + cache->AddAPIRef(); + return cache->GetAPIObject(); + } + + return nullptr; + } + + BNKernelCache* BNNewKernelCacheReference(BNKernelCache* cache) + { + if (!cache->object) + return nullptr; + + cache->object->AddAPIRef(); + return cache; + } + + void BNFreeKernelCacheReference(BNKernelCache* cache) + { + if (!cache->object) + return; + + cache->object->ReleaseAPIRef(); + } + + bool BNKCViewLoadImageWithInstallName(BNKernelCache* cache, char* name) + { + std::string imageName = std::string(name); + // FIXME !!!!!!!! BNFreeString(name); + + if (cache->object) + return cache->object->LoadImageWithInstallName(imageName); + + return false; + } + + bool BNKCViewLoadImageContainingAddress(BNKernelCache* cache, uint64_t address) + { + if (cache->object) + { + return cache->object->LoadImageContainingAddress(address); + } + + return false; + } + + char** BNKCViewGetInstallNames(BNKernelCache* cache, size_t* count) + { + if (cache->object) + { + auto value = cache->object->GetAvailableImages(); + *count = value.size(); + + std::vector<const char*> cstrings; + 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; + } + + BNKCSymbolRep* BNKCViewLoadAllSymbolsAndWait(BNKernelCache* cache, size_t* count) + { + if (cache->object) + { + auto value = cache->object->LoadAllSymbolsAndWait(); + *count = value.size(); + + BNKCSymbolRep* symbols = (BNKCSymbolRep*)malloc(sizeof(BNKCSymbolRep) * value.size()); + for (size_t i = 0; i < value.size(); i++) + { + symbols[i].address = value[i].first; + symbols[i].name = BNAllocString(value[i].second.second.c_str()); + symbols[i].image = BNAllocString(value[i].second.first.c_str()); + } + return symbols; + } + *count = 0; + return nullptr; + } + + void BNKCViewFreeSymbols(BNKCSymbolRep* symbols, size_t count) + { + for (size_t i = 0; i < count; i++) + { + BNFreeString(symbols[i].name); + BNFreeString(symbols[i].image); + } + delete symbols; + } + + char* BNKCViewGetNameForAddress(BNKernelCache* cache, uint64_t address) + { + if (cache->object) + { + return BNAllocString(cache->object->NameForAddress(address).c_str()); + } + + return nullptr; + } + + char* BNKCViewGetImageNameForAddress(BNKernelCache* cache, uint64_t address) + { + if (cache->object) + { + return BNAllocString(cache->object->ImageNameForAddress(address).c_str()); + } + + return nullptr; + } + + uint64_t BNKCViewLoadedImageCount(BNKernelCache* cache) + { + // FIXME? + return 0; + } + + BNKCViewState BNKCViewGetState(BNKernelCache* cache) + { + if (cache->object) + { + return (BNKCViewState)cache->object->ViewState(); + } + + return BNKCViewState::Unloaded; + } + + void BNKCViewFreeLoadedRegions(BNKCMappedMemoryRegion* images, size_t count) + { + for (size_t i = 0; i < count; i++) + { + BNFreeString(images[i].name); + } + delete images; + } + + BNKCImage* BNKCViewGetAllImages(BNKernelCache* cache, size_t* count) + { + if (cache->object) + { + auto viewImageHeaders = cache->object->AllImageHeaders(); + *count = viewImageHeaders.size(); + BNKCImage* images = (BNKCImage*)malloc(sizeof(BNKCImage) * viewImageHeaders.size()); + size_t i = 0; + for (const auto& [baseAddress, header] : viewImageHeaders) + { + images[i].name = BNAllocString(header.installName.c_str()); + images[i].headerFileAddress = baseAddress; + images[i].mappingCount = header.sections.size(); + images[i].mappings = (BNKCImageMemoryMapping*)malloc(sizeof(BNKCImageMemoryMapping) * header.sections.size()); + for (size_t j = 0; j < header.sections.size(); j++) + { + images[i].mappings[j].rawViewOffset = header.sections[j].offset; + images[i].mappings[j].vmAddress = header.sections[j].addr; + images[i].mappings[j].size = header.sections[j].size; + images[i].mappings[j].name = BNAllocString(header.sectionNames[j].c_str()); + } + i++; + } + return images; + } + *count = 0; + return nullptr; + } + + void BNKCViewFreeAllImages(BNKCImage* 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); + } + delete[] images[i].mappings; + BNFreeString(images[i].name); + } + delete[] images; + } + + char* BNKCViewGetImageHeaderForAddress(BNKernelCache* cache, uint64_t address) + { + if (cache->object) + { + auto header = cache->object->SerializedImageHeaderForVMAddress(address); + return BNAllocString(header.c_str()); + } + + return nullptr; + } + + char* BNKCViewGetImageHeaderForName(BNKernelCache* 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; + } + + BNKCViewLoadProgress BNKCViewGetLoadProgress(uint64_t sessionID) + { + if (auto viewSpecificState = ViewSpecificStateForId(sessionID, false)) + return viewSpecificState->progress; + + return LoadProgressNotStarted; + } + + uint64_t BNKCViewFastGetImageCount(BNBinaryView* data) + { + Ref<BinaryView> view = new BinaryView(BNNewViewReference(data)); + if (view) + return KernelCache::FastGetImageCount(view); + return 0; + } + + BNKCImage* BNKCViewGetLoadedImages(BNKernelCache* cache, size_t* count) + { + if (cache->object) + { + auto images = cache->object->GetLoadedImages(); + *count = images.size(); + BNKCImage* bnImages = new BNKCImage[images.size()]; + for (size_t i = 0; i < images.size(); i++) + { + bnImages[i].name = BNAllocString(images[i].installName.c_str()); + bnImages[i].headerFileAddress = images[i].headerFileLocation; + bnImages[i].mappingCount = images[i].regions.size(); + bnImages[i].mappings = new BNKCImageMemoryMapping[images[i].regions.size()]; + for (size_t j = 0; j < images[i].regions.size(); j++) + { + bnImages[i].mappings[j].rawViewOffset = images[i].regions[j].fileOffset; + bnImages[i].mappings[j].vmAddress = images[i].regions[j].start; + bnImages[i].mappings[j].size = images[i].regions[j].size; + bnImages[i].mappings[j].name = BNAllocString(images[i].regions[j].prettyName.c_str()); + } + } + return bnImages; + } + *count = 0; + return nullptr; + } + + void BNKCViewFreeLoadedImages(BNKCImage* 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); + } + delete[] images[i].mappings; + BNFreeString(images[i].name); + } + delete[] images; + } +} +#ifdef __cplusplus +extern "C" { +#endif + + void RegisterTransformers(); + +#ifdef __cplusplus +} +#endif + +void InitKernelcache() +{ + InitKCViewType(); + RegisterTransformers(); +} + +namespace KernelCacheCore { + +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 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::unordered_map<uint64_t, std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>>& value) +{ + auto array = context.doc[name.data()].GetArray(); + for (auto& pair : array) + { + auto symbols_array = pair[1].GetArray(); + std::unordered_map<uint64_t, Ref<Symbol>> symbols; + for (auto& symbol_value : symbols_array) + { + auto symbol_array = symbol_value.GetArray(); + std::string symbolName = symbol_array[0].GetString(); + uint64_t address = symbol_array[1].GetUint64(); + BNSymbolType type = (BNSymbolType)symbol_array[2].GetUint(); + symbols.insert({address, new Symbol(type, symbolName, address)}); + } + value[pair[0].GetUint64()] = std::make_shared<std::unordered_map<uint64_t, Ref<Symbol>>>(std::move(symbols)); + } +} + +void Deserialize(DeserializationContext& context, std::string_view name, + std::unordered_map<uint64_t, std::shared_ptr<std::vector<Ref<Symbol>>>>& value) +{ + auto array = context.doc[name.data()].GetArray(); + for (auto& pair : array) + { + auto symbols_array = pair[1].GetArray(); + std::vector<Ref<Symbol>> symbols; + symbols.reserve(symbols_array.Size()); + for (auto& symbol_value : symbols_array) + { + auto symbol_array = symbol_value.GetArray(); + std::string symbolName = symbol_array[0].GetString(); + uint64_t address = symbol_array[1].GetUint64(); + BNSymbolType type = (BNSymbolType)symbol_array[2].GetUint(); + symbols.push_back(new Symbol(type, symbolName, address)); + } + value[pair[0].GetUint64()] = std::make_shared<std::vector<Ref<Symbol>>>(std::move(symbols)); + } +} + +void Deserialize(DeserializationContext& context, std::string_view name, std::optional<KCViewState>& viewState) +{ + auto& value = context.doc[name.data()]; + if (value.IsNull()) + viewState = std::nullopt; + else + viewState = (KCViewState)value.GetUint(); +} + + +void Serialize(SerializationContext& context, const std::vector<MemoryRegion>& value) +{ + context.writer.StartArray(); + for (const auto& region : value) + { + context.writer.StartArray(); + Serialize(context, region.prettyName); + Serialize(context, region.start); + Serialize(context, region.size); + Serialize(context, region.fileOffset); + Serialize(context, (uint64_t)region.flags); + Serialize(context, (uint8_t)region.type); + context.writer.EndArray(); + } + context.writer.EndArray(); +} + +void Deserialize(DeserializationContext& context, std::string_view name, std::vector<MemoryRegion>& value) +{ + auto array = context.doc[name.data()].GetArray(); + value.reserve(array.Size()); + for (auto& region : array) + { + auto region_array = region.GetArray(); + MemoryRegion newRegion; + newRegion.prettyName = region_array[0].GetString(); + newRegion.start = region_array[1].GetUint64(); + newRegion.size = region_array[2].GetUint64(); + newRegion.fileOffset = region_array[3].GetUint64(); + newRegion.flags = (BNSegmentFlag)region_array[4].GetUint64(); + newRegion.type = (MemoryRegion::Type)region_array[5].GetUint(); + value.push_back(newRegion); + } +} + +void Serialize(SerializationContext& context, const std::vector<KernelCacheImage>& value) +{ + context.writer.StartArray(); + for (const auto& image : value) + { + Serialize(context, image); + } + context.writer.EndArray(); +} + + +void Deserialize(DeserializationContext& context, std::string_view name, std::vector<KernelCacheImage>& value) +{ + auto array = context.doc[name.data()].GetArray(); + value.reserve(array.Size()); + for (auto& image : array) + { + KernelCacheImage img = KernelCacheImage::LoadFromValue(image); + value.push_back(img); + } +} + + +void Serialize(SerializationContext& context, const std::vector<std::pair<uint64_t, uint64_t>>& value) +{ + context.writer.StartArray(); + for (const auto& pair : value) + { + context.writer.StartArray(); + Serialize(context, pair.first); + Serialize(context, pair.second); + context.writer.EndArray(); + } + context.writer.EndArray(); +} + +void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, uint64_t>>& value) +{ + auto array = context.doc[name.data()].GetArray(); + value.reserve(array.Size()); + for (auto& pair : array) + { + auto pair_array = pair.GetArray(); + value.push_back({pair_array[0].GetUint64(), pair_array[1].GetUint64()}); + } +} + +void Serialize(SerializationContext& context, const std::unordered_map<uint64_t, KernelCacheMachOHeader>& value) +{ + context.writer.StartArray(); + for (const auto& pair : value) + { + context.writer.StartArray(); + Serialize(context, pair.first); + Serialize(context, pair.second); + context.writer.EndArray(); + } + context.writer.EndArray(); +} + +void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, KernelCacheMachOHeader>& value) +{ + auto array = context.doc[name.data()].GetArray(); + for (auto& pair : array) + { + auto pair_array = pair.GetArray(); + uint64_t key = pair_array[0].GetUint64(); + KernelCacheMachOHeader header = KernelCacheMachOHeader::LoadFromValue(pair_array[1]); + value[key] = header; + } +} + + +void Serialize(SerializationContext& context, const std::unordered_map<uint64_t, KernelCacheImage> value) +{ + context.writer.StartArray(); + for (const auto& pair : value) + { + context.writer.StartArray(); + Serialize(context, pair.first); + Serialize(context, pair.second); + context.writer.EndArray(); + } + context.writer.EndArray(); +} + +void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, KernelCacheImage>& value) +{ + auto array = context.doc[name.data()].GetArray(); + for (auto& pair : array) + { + auto pair_array = pair.GetArray(); + uint64_t key = pair_array[0].GetUint64(); + KernelCacheImage image = KernelCacheImage::LoadFromValue(pair_array[1]); + value[key] = image; + } +} + + +void KernelCache::CacheInfo::Store(SerializationContext& context) const +{ + Serialize(context, "metadataVersion", METADATA_VERSION); + + MSS(images); + MSS(imageStarts); + MSS_CAST(cacheFormat, uint8_t); +} + +// static +std::optional<KernelCache::CacheInfo> KernelCache::CacheInfo::Load(DeserializationContext& context) +{ + if (!context.doc.HasMember("metadataVersion")) + { + LogError("Shared Cache metadata version missing"); + return std::nullopt; + } + + if (context.doc["metadataVersion"].GetUint() != METADATA_VERSION) + { + LogError("Shared Cache metadata version mismatch"); + return std::nullopt; + } + + CacheInfo cacheInfo; + cacheInfo.MSL(images); + cacheInfo.MSL(imageStarts); + cacheInfo.MSL_CAST(cacheFormat, uint8_t, KernelCacheFormat); + return cacheInfo; +} +void State::Store(SerializationContext& context, std::optional<KCViewState> viewState) const +{ + MSS(loadedImages); + MSS(viewState); +} + +void KernelCache::ModifiedState::Store(SerializationContext& context) const +{ + State::Store(context, viewState); +} + +KernelCache::ModifiedState KernelCache::ModifiedState::Load(DeserializationContext& context) +{ + KernelCache::ModifiedState state; + state.MSL(loadedImages); + state.MSL(viewState); + return state; +} + +KernelCache::ModifiedState KernelCache::ModifiedState::LoadAll(BinaryNinja::BinaryView *dscView, const CacheInfo& cacheInfo) +{ + uint64_t stateCount = dscView->GetUIntMetadata(KernelCacheMetadata::ModifiedStateCountTag); + KernelCache::ModifiedState state; + for (uint64_t i = 0; i < stateCount; ++i) + { + std::string key = KernelCacheMetadata::ModifiedStateTagPrefix + std::to_string(i); + std::string serialized = dscView->GetStringMetadata(key); + auto thisState = KernelCache::ModifiedState::LoadFromString(serialized); + state.Merge(std::move(thisState)); + } + return state; +} + +void KernelCache::ModifiedState::Merge(KernelCache::ModifiedState&& newer) +{ + loadedImages.merge(newer.loadedImages); + + if (newer.viewState) + viewState = newer.viewState; +} + +void KernelCacheImage::Store(SerializationContext& context) const +{ + MSS(installName); + MSS(headerFileLocation); + MSS(regions); +} + +// static +KernelCacheImage KernelCacheImage::Load(DeserializationContext& context) +{ + KernelCacheImage cacheImage; + cacheImage.MSL(installName); + cacheImage.MSL(headerFileLocation); + cacheImage.MSL(regions); + return cacheImage; +} + + +} // namespace KernelCacheCore |
