summaryrefslogtreecommitdiff
path: root/view/kernelcache/core
diff options
context:
space:
mode:
authorkat <kat@vector35.com>2025-07-06 15:05:01 -0400
committerkat <kat@vector35.com>2025-07-07 07:37:23 -0400
commit768f7c78465fb93936e5ca50a0ca712664fe54e7 (patch)
tree78c73e0022d6006882e365a257595a5b55a37432 /view/kernelcache/core
parent8f3e251c42169fb4fe8db9403d900571434d88ff (diff)
KernelCache rewrite
Diffstat (limited to 'view/kernelcache/core')
-rw-r--r--view/kernelcache/core/KCView.h48
-rw-r--r--view/kernelcache/core/KernelCache.cpp2520
-rw-r--r--view/kernelcache/core/KernelCache.h433
-rw-r--r--view/kernelcache/core/KernelCacheBuilder.cpp16
-rw-r--r--view/kernelcache/core/KernelCacheBuilder.h20
-rw-r--r--view/kernelcache/core/KernelCacheController.cpp198
-rw-r--r--view/kernelcache/core/KernelCacheController.h49
-rw-r--r--view/kernelcache/core/KernelCacheView.cpp (renamed from view/kernelcache/core/KCView.cpp)562
-rw-r--r--view/kernelcache/core/KernelCacheView.h63
-rw-r--r--view/kernelcache/core/MachO.cpp714
-rw-r--r--view/kernelcache/core/MachO.h79
-rw-r--r--view/kernelcache/core/MachOProcessor.cpp332
-rw-r--r--view/kernelcache/core/MachOProcessor.h22
-rw-r--r--view/kernelcache/core/MetadataSerializable.cpp552
-rw-r--r--view/kernelcache/core/MetadataSerializable.hpp254
-rw-r--r--view/kernelcache/core/Utility.cpp159
-rw-r--r--view/kernelcache/core/Utility.h106
-rw-r--r--view/kernelcache/core/ffi.cpp213
-rw-r--r--view/kernelcache/core/ffi_global.h41
-rw-r--r--view/kernelcache/core/refcountobject.h223
20 files changed, 2781 insertions, 3823 deletions
diff --git a/view/kernelcache/core/KCView.h b/view/kernelcache/core/KCView.h
deleted file mode 100644
index 530e10de..00000000
--- a/view/kernelcache/core/KCView.h
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// Created by kat on 5/23/23.
-//
-
-#ifndef KERNELCACHE_KCVIEW_H
-#define KERNELCACHE_KCVIEW_H
-
-#include <binaryninjaapi.h>
-
-class KCView : public BinaryNinja::BinaryView {
- bool m_parseOnly;
-public:
-
- KCView(const std::string &typeName, BinaryView *data, bool parseOnly = false);
-
- ~KCView() override;
-
- bool Init() override;
-
- virtual bool PerformIsExecutable() const override { return true; }
-};
-
-
-class KCViewType : public BinaryNinja::BinaryViewType {
-
-public:
- KCViewType();
-
- 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;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
- void InitKCViewType();
-#ifdef __cplusplus
-};
-#endif
-
-#endif //KERNELCACHE_KCVIEW_H
diff --git a/view/kernelcache/core/KernelCache.cpp b/view/kernelcache/core/KernelCache.cpp
index 6cb3fca6..36f3331b 100644
--- a/view/kernelcache/core/KernelCache.cpp
+++ b/view/kernelcache/core/KernelCache.cpp
@@ -1,1101 +1,127 @@
-//
-// 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>
+#include <regex>
+#include <filesystem>
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();
- reader.SeekRelative(4);
- uint32_t fileType = reader.Read32();
- if (fileType != MH_FILESET)
- {
- m_logger->LogError("Invalid file type in KernelCache");
- return;
- }
- uint32_t ncmds = reader.Read32();
- reader.SeekRelative(8);
- 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::pair<std::string, Ref<Type>> CacheSymbol::DemangledName(BinaryView &view) const
{
- 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() {
+ QualifiedName qname;
+ Ref<Type> outType;
+ std::string shortName = name;
+ if (DemangleGeneric(view.GetDefaultArchitecture(), name, outType, qname, &view, true))
+ shortName = qname.GetString();
+ return { shortName, outType };
}
-KernelCache* KernelCache::GetFromKCView(BinaryNinja::Ref<BinaryNinja::BinaryView> kcView)
+std::pair<Ref<Symbol>, Ref<Type>> CacheSymbol::GetBNSymbolAndType(BinaryView& view) const
{
- if (kcView->GetTypeName() != KC_VIEW_NAME)
- return nullptr;
- try {
- return new KernelCache(kcView);
- }
- catch (...)
- {
- return nullptr;
- }
+ auto [shortName, demangledType] = DemangledName(view);
+ auto symbol = new Symbol(type, shortName, shortName, name, address, nullptr);
+ return {symbol, demangledType};
}
-
-uint64_t KernelCache::FastGetImageCount(BinaryNinja::Ref<BinaryNinja::BinaryView> kcView)
+std::vector<std::string> CacheImage::GetDependencies() const
{
- 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);
- }
- }
- }
+ if (header)
+ return header->dylibs;
return {};
}
-std::string KernelCache::NameForAddress(uint64_t address)
+KernelCache::KernelCache(uint64_t addressSize)
{
- 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 "";
+ m_namedSymMutex = std::make_unique<std::shared_mutex>();
}
-std::string KernelCache::ImageNameForAddress(uint64_t address)
-{
- if (auto header = HeaderForVMAddress(address))
- {
- return header->identifierPrefix;
- }
- return "";
-}
-bool KernelCache::LoadImageContainingAddress(uint64_t address)
+void KernelCache::AddImage(CacheImage&& image)
{
- std::lock_guard<std::mutex> lock(m_mutex);
- return LoadImageContainingAddress(lock, address);
+ m_images.insert({image.headerVirtualAddress, std::move(image)});
}
-bool KernelCache::LoadImageContainingAddress(std::lock_guard<std::mutex>& lock, uint64_t address)
+void KernelCache::AddSymbol(CacheSymbol symbol)
{
- 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;
+ m_symbols.insert({symbol.address, std::move(symbol)});
}
-
-bool KernelCache::LoadImageWithInstallName(std::string installName)
+void KernelCache::AddSymbols(std::vector<CacheSymbol>&& symbols)
{
- std::lock_guard<std::mutex> lock(m_mutex);
- return LoadImageWithInstallName(lock, installName);
+ for (auto& symbol : symbols)
+ m_symbols.insert({symbol.address, std::move(symbol)});
}
-
-bool KernelCache::LoadImageWithInstallName(std::lock_guard<std::mutex>& lock, std::string installName)
+bool KernelCache::ProcessEntryImage(Ref<BinaryView> bv, const std::string& path, const fileset_entry_command& info)
{
- 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;
- }
- }
- if (!targetImage)
- return false;
-
- const auto header = LoadHeaderForAddress(m_kcView, targetImage->headerFileLocation, targetImage->installName);
- if (!header)
+ auto imageHeader = KernelCacheMachOHeader::ParseHeaderForAddress(bv, info.vmaddr, info.fileoff, path);
+ if (!imageHeader.has_value())
return false;
- m_modifiedState->viewState = KCViewStateLoadedWithImages;
+ // Add the image to the cache.
+ CacheImage image;
+ image.headerFileAddress = info.fileoff;
+ image.headerVirtualAddress = info.vmaddr;
+ image.path = path;
- 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.
+ // Add all image regions.
+ for (const auto& segment : imageHeader->segments)
+ {
+ char segName[17];
+ memcpy(segName, segment.segname, 16);
+ segName[16] = 0;
- m_modifiedState->loadedImages[targetImage->headerFileLocation] = *targetImage;
+ CacheRegion sectionRegion;
+ 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.headerFileAddress;
- SaveModifiedStateToKCView(lock);
+ 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);
- auto h = KernelCache::LoadHeaderForAddress(m_kcView, targetImage->headerFileLocation, installName);
- if (!h.has_value())
- {
- return false;
+ image.regions.push_back(std::move(sectionRegion));
}
- KernelCache::InitializeHeader(m_kcView, *h);
+ // Add the exported symbols to the available symbols.
+ std::vector<CacheSymbol> exportSymbols = imageHeader->ReadExportSymbolTrie(bv);
+ AddSymbols(std::move(exportSymbols));
+ TableInfo symbolInfo = { imageHeader->symtab.symoff, imageHeader->symtab.nsyms };
+ TableInfo stringInfo = { imageHeader->symtab.stroff, imageHeader->symtab.strsize };
+ std::vector<CacheSymbol> symbols = imageHeader->ReadSymbolTable(bv, symbolInfo, stringInfo);
+ AddSymbols(std::move(symbols));
- m_kcView->AddAnalysisOption("linearsweep");
- m_kcView->UpdateAnalysis();
+ // This is behind a shared pointer as the header itself is very large.
+ image.header = std::make_shared<KernelCacheMachOHeader>(std::move(*imageHeader));
+ AddImage(std::move(image));
return true;
}
-std::optional<KernelCacheMachOHeader> KernelCache::LoadHeaderForAddress(Ref<BinaryView> view, uint64_t address, std::string installName)
+void KernelCache::ProcessSymbols()
{
- 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(&sect, 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(&sect.sectname, 16);
- reader.Read(&sect.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(&sect.sectname, 16);
- reader.Read(&sect.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;
+ std::unique_lock<std::shared_mutex> lock(*m_namedSymMutex);
+ // Populate the named symbols from the regular symbols map.
+ m_namedSymbols.reserve(m_symbols.size());
+ for (const auto& [address, symbol] : m_symbols)
+ m_namedSymbols.emplace(symbol.name, address);
}
-bool KernelCache::InitializeSegmentsForHeader(Ref<BinaryView> view, const KernelCacheMachOHeader& header, const KernelCacheImage& targetImage)
+void KernelCache::ProcessRelocations(Ref<BinaryView> view, linkedit_data_command chained_fixup_command)
{
- // 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)
+ m_relocations.clear();
+ if (chained_fixup_command.dataoff && chained_fixup_command.datasize)
{
BinaryReader parentReader(view->GetParentView());
try {
dyld_chained_fixups_header fixupsHeader {};
- uint64_t fixupHeaderAddress = chainedFixupDataOff;
+ uint64_t fixupHeaderAddress = chained_fixup_command.dataoff;
parentReader.Seek(fixupHeaderAddress);
fixupsHeader.fixups_version = parentReader.Read32();
fixupsHeader.starts_offset = parentReader.Read32();
@@ -1105,11 +131,11 @@ bool KernelCache::InitializeSegmentsForHeader(Ref<BinaryView> view, const Kernel
fixupsHeader.imports_format = parentReader.Read32();
fixupsHeader.symbols_format = parentReader.Read32();
- logger->LogDebug("Chained Fixups: Header @ %llx // Fixups version %lx", fixupHeaderAddress, fixupsHeader.fixups_version);
+ 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");
+ LogError("Chained Fixup parsing failed. Unknown Fixups Version");
throw ReadException();
}
@@ -1178,14 +204,14 @@ bool KernelCache::InitializeSegmentsForHeader(Ref<BinaryView> view, const Kernel
break;
default:
{
- logger->LogError("Chained Fixups: Unknown or unsupported pointer format %d, "
+ 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);
+ LogDebug("Chained Fixups: Segment start @ %llx, fmt %d", starts.segment_offset, fmt);
uint64_t pageStartsTableStartAddress = parentReader.GetOffset();
std::vector<std::vector<uint16_t>> pageStartOffsets {};
@@ -1273,8 +299,8 @@ bool KernelCache::InitializeSegmentsForHeader(Ref<BinaryView> view, const Kernel
bind = false;
}
- logger->LogTrace("Chained Fixups: @ 0x%llx ( 0x%llx ) - %d 0x%llx", chainEntryAddress,
- kernelBaseAddress + (chainEntryAddress),
+ LogTrace("Chained Fixups: @ 0x%llx ( 0x%llx ) - %d 0x%llx", chainEntryAddress,
+ view->GetStart() + (chainEntryAddress),
bind, nextEntryStrideCount);
if (!bind)
@@ -1293,7 +319,7 @@ bool KernelCache::InitializeSegmentsForHeader(Ref<BinaryView> view, const Kernel
entryOffset = pointer.arm64e.rebase.target;
if ( starts.pointer_format != DYLD_CHAINED_PTR_ARM64E || pointer.arm64e.bind.auth)
- entryOffset += kernelBaseAddress;
+ entryOffset += view->GetStart();
break;
}
@@ -1301,12 +327,12 @@ bool KernelCache::InitializeSegmentsForHeader(Ref<BinaryView> view, const Kernel
entryOffset = pointer.generic64.rebase.target;
break;
case DYLD_CHAINED_PTR_64_OFFSET:
- entryOffset = pointer.generic64.rebase.target + kernelBaseAddress;
+ entryOffset = pointer.generic64.rebase.target + view->GetStart();
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;
+ entryOffset = pointer.kernel64.target + view->GetStart();
break;
case DYLD_CHAINED_PTR_32:
case DYLD_CHAINED_PTR_32_CACHE:
@@ -1319,7 +345,7 @@ bool KernelCache::InitializeSegmentsForHeader(Ref<BinaryView> view, const Kernel
// logger->LogInfo("Chained Fixups: Pointer at 0x%llx -> 0x%llx", view->GetStart() + chainEntryAddress, entryOffset);
- relocations.emplace_back(kernelBaseAddress + chainEntryAddress, entryOffset);
+ m_relocations.emplace_back(view->GetStart() + chainEntryAddress, entryOffset);
}
chainEntryAddress += (nextEntryStrideCount * strideSize);
@@ -1328,8 +354,8 @@ bool KernelCache::InitializeSegmentsForHeader(Ref<BinaryView> view, const Kernel
{
// 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))));
+ LogError("Chained Fixups: Pointer at 0x%llx left page",
+ view->GetStart() + ((chainEntryAddress - (nextEntryStrideCount * strideSize))));
fixupsDone = true;
}
@@ -1342,1379 +368,59 @@ bool KernelCache::InitializeSegmentsForHeader(Ref<BinaryView> view, const Kernel
}
catch (ReadException&)
{
- logger->LogError("Chained Fixup parsing failed");
+ 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(),
+
+ std::sort(m_relocations.begin(), m_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)
+std::optional<CacheImage> KernelCache::GetImageAt(const uint64_t address) const
{
- 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 sectionFlags;
- BNSymbolType type;
- for (auto s : header.sections)
- {
- if (s.addr < textBase + imageOffset)
- {
- if (s.addr + s.size > textBase + imageOffset)
- {
- sectionFlags = s.flags;
- }
- }
- }
- if ((sectionFlags & S_ATTR_PURE_INSTRUCTIONS) == S_ATTR_PURE_INSTRUCTIONS
- || (sectionFlags & 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;
-}
-
-
-bool KernelCache::IsImageLoaded(uint64_t address)
-{
- std::lock_guard lock(m_viewSpecificState->stateMutex);
- return m_viewSpecificState->state.loadedImages.find(address)!= m_viewSpecificState->state.loadedImages.end();
-}
-
-
-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 modifiedStateMetadataKey = KernelCacheMetadata::ModifiedStateTagPrefix + std::to_string(i);
- bool done = true;
- if (m_kcView->QueryMetadata(modifiedStateMetadataKey))
- {
- done = false;
- m_kcView->RemoveMetadata(modifiedStateMetadataKey);
- }
- if (m_kcView->GetParentView()->QueryMetadata(modifiedStateMetadataKey))
- {
- done = false;
- m_kcView->GetParentView()->RemoveMetadata(modifiedStateMetadataKey);
- }
- 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)
+ const auto it = m_images.find(address);
+ if (it == m_images.end())
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));
+ return it->second;
}
-std::string KernelCacheMetadata::InstallNameForImageBaseAddress(uint64_t baseAddress) const
+std::optional<CacheImage> KernelCache::GetImageContaining(const uint64_t address) 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)
+ for (const auto& [startAddress, image] : m_images)
{
- 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))
+ for (const auto& region : image.regions)
{
- cache->AddAPIRef();
- return cache->GetAPIObject();
+ if (region.AsAddressRange().start <= address && address < region.AsAddressRange().end)
+ return image;
}
-
- 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, const char* name)
- {
- std::string imageName = std::string(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;
- }
-
- bool BNKCViewIsImageLoaded(BNKernelCache* cache, uint64_t address)
- {
- if (cache->object)
- return cache->object->IsImageLoaded(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, const char* name)
- {
- std::string imageName = std::string(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;
}
+ return std::nullopt;
}
-#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)
+std::optional<CacheImage> KernelCache::GetImageWithName(const std::string& name) const
{
- context.writer.StartArray();
- Serialize(context, value->GetRawNameRef());
- Serialize(context, value->GetAddress());
- Serialize(context, value->GetType());
- context.writer.EndArray();
+ for (const auto& [address, image] : m_images)
+ if (image.path == name)
+ return image;
+ return std::nullopt;
}
-void Serialize(SerializationContext& context, const std::shared_ptr<std::unordered_map<uint64_t, Ref<Symbol>>>& value)
+std::optional<CacheSymbol> KernelCache::GetSymbolAt(uint64_t address) const
{
- 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");
+ const auto it = m_symbols.find(address);
+ if (it == m_symbols.end())
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);
+ return it->second;
}
-KernelCache::ModifiedState KernelCache::ModifiedState::Load(DeserializationContext& context)
+std::optional<CacheSymbol> KernelCache::GetSymbolWithName(const std::string& name)
{
- 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;
+ std::shared_lock<std::shared_mutex> lock(*m_namedSymMutex);
+ const auto it = m_namedSymbols.find(name);
+ if (it == m_namedSymbols.end())
+ return std::nullopt;
+ return GetSymbolAt(it->second);
}
-
-
-} // namespace KernelCacheCore
diff --git a/view/kernelcache/core/KernelCache.h b/view/kernelcache/core/KernelCache.h
index e0e80cb6..38974225 100644
--- a/view/kernelcache/core/KernelCache.h
+++ b/view/kernelcache/core/KernelCache.h
@@ -1,361 +1,160 @@
-//
-// Created by kat on 5/19/23.
-//
+#pragma once
-#include <binaryninjaapi.h>
-#include "KCView.h"
-#include "view/macho/machoview.h"
-#include "MetadataSerializable.hpp"
-#include "../api/kernelcachecore.h"
+#include <vector>
-#ifndef KERNELCACHE_KERNELCACHE_H
-#define KERNELCACHE_KERNELCACHE_H
+#include "binaryninjaapi.h"
+#include "MachO.h"
-DECLARE_KERNELCACHE_API_OBJECT(BNKernelCache, KernelCache);
+#include <mutex>
+#include <shared_mutex>
+#include "Utility.h"
-namespace KernelCacheCore {
+struct CacheSymbol
+{
+ BNSymbolType type;
+ uint64_t address;
+ std::string name;
- enum KCViewState
- {
- KCViewStateUnloaded,
- KCViewStateLoaded,
- KCViewStateLoadedWithImages,
- };
-
- const std::string KernelCacheMetadataTag = "KERNELCACHE-KernelCacheData";
-
- struct MemoryRegion : public MetadataSerializable<MemoryRegion>
- {
- enum class Type
- {
- Image,
- NonImage,
- };
-
- std::string prettyName;
- uint64_t start;
- uint64_t size;
- uint64_t fileOffset;
- BNSegmentFlag flags;
- Type type;
+ CacheSymbol() = default;
+ CacheSymbol(BNSymbolType type, uint64_t address, std::string name) :
+ type(type), address(address), name(std::move(name))
+ {}
+ ~CacheSymbol() = default;
- void Store(SerializationContext& context) const
- {
- MSS(prettyName);
- MSS(start);
- MSS(size);
- MSS(fileOffset);
- MSS_CAST(flags, uint64_t);
- MSS_CAST(type, uint8_t);
- }
+ CacheSymbol(const CacheSymbol& other) = default;
+ CacheSymbol& operator=(const CacheSymbol& other) = default;
- static MemoryRegion Load(DeserializationContext& context)
- {
- MemoryRegion region;
- region.MSL(prettyName);
- region.MSL(start);
- region.MSL(size);
- region.MSL(fileOffset);
- region.MSL_CAST(flags, uint64_t, BNSegmentFlag);
- region.MSL_CAST(type, uint8_t, Type);
- return region;
- }
- };
+ CacheSymbol(CacheSymbol&& other) noexcept = default;
+ CacheSymbol& operator=(CacheSymbol&& other) noexcept = default;
- struct KernelCacheImage : public MetadataSerializable<KernelCacheImage> {
- std::string installName;
- uint64_t headerFileLocation;
- std::vector<MemoryRegion> regions;
+ std::pair<std::string, BinaryNinja::Ref<BinaryNinja::Type>> DemangledName(BinaryNinja::BinaryView& view) const;
- void Store(SerializationContext& context) const;
- static KernelCacheImage Load(DeserializationContext& context);
- };
+ // NOTE: you should really only call this when adding the symbol to the view.
+ std::pair<BinaryNinja::Ref<BinaryNinja::Symbol>, BinaryNinja::Ref<BinaryNinja::Type>> GetBNSymbolAndType(BinaryNinja::BinaryView& view) const;
+};
- #if defined(__GNUC__) || defined(__clang__)
- #define PACKED_STRUCT __attribute__((packed))
- #else
- #define PACKED_STRUCT
- #endif
+struct CacheRegion
+{
+ // type is always image
+ 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;
+ ~CacheRegion() = default;
- #endif
+ CacheRegion(const CacheRegion& other) = default;
+ CacheRegion& operator=(const CacheRegion& other) = default;
- #if defined(_MSC_VER)
- #pragma pack(pop)
- #else
+ CacheRegion(CacheRegion&& other) noexcept = default;
+ CacheRegion& operator=(CacheRegion&& other) noexcept = default;
- #endif
+ AddressRange AsAddressRange() const { return {start, start + size}; }
- using namespace BinaryNinja;
- struct KernelCacheMachOHeader : public MetadataSerializable<KernelCacheMachOHeader>
+ BNSectionSemantics SectionSemanticsForRegion() const
{
- uint64_t textBase = 0;
- uint64_t textBaseFileOffset = 0;
- uint64_t loadCommandOffset = 0;
- mach_header_64 ident;
- std::string identifierPrefix;
- std::string installName;
-
- std::vector<std::pair<uint64_t, bool>> entryPoints;
- std::vector<uint64_t> m_entryPoints; // list of entrypoints
-
- symtab_command symtab;
- dysymtab_command dysymtab;
- dyld_info_command dyldInfo;
- routines_command_64 routines64;
- function_starts_command functionStarts;
- std::vector<section_64> moduleInitSections;
- std::vector<section_64> moduleTermSections;
- linkedit_data_command exportTrie;
- linkedit_data_command chainedFixups {};
-
- uint64_t relocationBase;
- // Section and program headers, internally use 64-bit form as it is a superset of 32-bit
- std::vector<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;
+ if ((flags & SegmentExecutable) && (flags & SegmentDenyWrite))
+ return ReadOnlyCodeSectionSemantics;
- std::vector<section_64> symbolStubSections;
- std::vector<section_64> symbolPointerSections;
+ if (flags & SegmentExecutable)
+ return DefaultSectionSemantics;
- std::vector<std::string> dylibs;
-
- build_version_command buildVersion;
- std::vector<build_tool_version> buildToolVersions;
-
- bool linkeditPresent = false;
- bool dysymPresent = false;
- bool dyldInfoPresent = false;
- bool exportTriePresent = false;
- bool chainedFixupsPresent = false;
- bool routinesPresent = false;
- bool functionStartsPresent = false;
- bool relocatable = false;
-
- void Store(SerializationContext& context) const {
- MSS(textBase);
- MSS(textBaseFileOffset);
- 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(moduleTermSections);
- 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(linkeditPresent);
- MSS(dysymPresent);
- MSS(dyldInfoPresent);
- MSS(exportTriePresent);
- MSS(chainedFixupsPresent);
- MSS(routinesPresent);
- MSS(functionStartsPresent);
- MSS(relocatable);
- }
-
- static KernelCacheMachOHeader Load(DeserializationContext& context) {
- KernelCacheMachOHeader header;
- header.MSL(textBase);
- header.MSL(textBaseFileOffset);
- 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(moduleTermSections);
- 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(linkeditPresent);
- header.MSL(dysymPresent);
- header.MSL(dyldInfoPresent);
- header.MSL(exportTriePresent);
- header.MSL(chainedFixupsPresent);
- header.MSL(routinesPresent);
- header.MSL(functionStartsPresent);
- header.MSL(relocatable);
- return header;
- }
- };
-
- class KernelCache : public MetadataSerializable<KernelCache>
- {
- IMPLEMENT_KERNELCACHE_API_OBJECT(BNKernelCache);
+ if (flags & SegmentDenyWrite)
+ return ReadOnlyDataSectionSemantics;
- std::atomic<int> m_refs = 0;
+ return ReadWriteDataSectionSemantics;
+ }
+};
- public:
- virtual void AddRef() { m_refs.fetch_add(1); }
+// Represents a single image and its associated memory regions.
+struct CacheImage
+{
+ uint64_t headerFileAddress;
+ uint64_t headerVirtualAddress;
+ 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<CacheRegion> regions;
+ std::shared_ptr<KernelCacheMachOHeader> header;
- 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;
- });
- }
+ CacheImage() = default;
+ ~CacheImage() = default;
- virtual void AddAPIRef() { AddRef(); }
+ CacheImage(const CacheImage& other) = default;
+ CacheImage& operator=(const CacheImage& other) = default;
- virtual void ReleaseAPIRef() { Release(); }
+ CacheImage(CacheImage&& other) noexcept = default;
+ CacheImage& operator=(CacheImage&& other) noexcept = default;
- public:
- enum KernelCacheFormat
- {
- FilesetCacheFormat,
- PrelinkedCacheFormat,
- };
+ // Get the file name from the path.
+ std::string GetName() const { return BaseFileName(path); }
- struct CacheInfo;
- struct ModifiedState;
+ // Get the names of the dependencies.
+ std::vector<std::string> GetDependencies() const;
+};
- struct ViewSpecificState;
+// The C in KC.
+// 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 KernelCache
+{
+ // Calculated within `AddEntry`, this indicates where the shared cache image is based at.
+ uint64_t m_baseAddress = 0;
- void Store(SerializationContext& context) const;
- void Load(DeserializationContext& context);
+ std::vector<std::pair<uint64_t, uint64_t>> m_relocations {};
- private:
- Ref<Logger> m_logger;
- /* VIEW STATE BEGIN -- SERIALIZE ALL OF THIS AND STORE IT IN RAW VIEW */
+ std::unordered_map<uint64_t, CacheImage> m_images {};
+ // All the external symbols for this cache. Both mapped and unmapped (not in the view).
+ std::unordered_map<uint64_t, CacheSymbol> m_symbols {};
+ // Quickly lookup a symbol by name, populated by `FinalizeSymbols`.
+ // `m_namedSymbols` is modified in a worker thread spawned by view init so we must not get a symbol until its populated.
+ std::unordered_map<std::string, uint64_t> m_namedSymbols {};
+ // Used to guard `m_namedSymbols` as it's accessed on multiple threads.
+ // NOTE: Wrapped in unique_ptr to keep KernelCache movable.
+ std::unique_ptr<std::shared_mutex> m_namedSymMutex;
- // State that is initialized during `PerformInitialLoad` and does
- // not change thereafter.
- std::shared_ptr<const CacheInfo> m_cacheInfo;
+public:
- // Protects member variables below.
- mutable std::mutex m_mutex;
+ bool ProcessEntryImage(BinaryNinja::Ref<BinaryNinja::BinaryView> bv, const std::string& path, const BinaryNinja::fileset_entry_command& info);
+ KernelCache() = default;
+ explicit KernelCache(uint64_t addressSize);
- // 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;
+ KernelCache(const KernelCache &) = delete;
+ KernelCache &operator=(const KernelCache &) = delete;
- // Serialized once by PerformInitialLoad and available after m_viewState == Loaded
- bool m_metadataValid = false;
+ KernelCache(KernelCache &&) noexcept = default;
+ KernelCache &operator=(KernelCache &&) noexcept = default;
- /* API VIEW START */
- BinaryNinja::Ref<BinaryNinja::BinaryView> m_kcView;
- /* API VIEW END */
-
- std::shared_ptr<ViewSpecificState> m_viewSpecificState;
-
- private:
- void PerformInitialLoad(std::lock_guard<std::mutex>&);
- void DeserializeFromRawView(std::lock_guard<std::mutex>&);
-
- public:
- static KernelCache* GetFromKCView(BinaryNinja::Ref<BinaryNinja::BinaryView> kcView);
- static uint64_t FastGetImageCount(BinaryNinja::Ref<BinaryNinja::BinaryView> kcView);
- bool SaveCacheInfoToKCView(std::lock_guard<std::mutex>&);
- bool SaveModifiedStateToKCView(std::lock_guard<std::mutex>&);
- std::optional<uint64_t> GetImageStart(std::string installName);
- std::optional<KernelCacheMachOHeader> HeaderForVMAddress(uint64_t address);
- std::optional<KernelCacheMachOHeader> HeaderForFileAddress(uint64_t address);
- bool LoadImageWithInstallName(std::lock_guard<std::mutex>& lock, std::string installName);
- bool LoadImageWithInstallName(std::string installName);
- bool LoadImageContainingAddress(std::lock_guard<std::mutex>& lock, uint64_t address);
- bool LoadImageContainingAddress(uint64_t address);
- std::string NameForAddress(uint64_t address);
- std::string ImageNameForAddress(uint64_t address);
- std::vector<std::string> GetAvailableImages();
- std::vector<KernelCacheImage> GetLoadedImages();
- bool IsImageLoaded(uint64_t address);
-
- std::vector<std::pair<uint64_t, std::pair<std::string, std::string>>> LoadAllSymbolsAndWait();
-
- const std::unordered_map<std::string, uint64_t>& AllImageStarts() const;
- const std::unordered_map<uint64_t, KernelCacheMachOHeader> AllImageHeaders() const;
-
- std::string SerializedImageHeaderForVMAddress(uint64_t address);
- std::string SerializedImageHeaderForName(std::string name);
-
- KCViewState ViewState() const;
-
- explicit KernelCache(BinaryNinja::Ref<BinaryNinja::BinaryView> rawView);
- virtual ~KernelCache();
-
- static bool InitializeSegmentsForHeader(Ref<BinaryView> view, const KernelCacheMachOHeader& header, const KernelCacheImage& targetImage);
- static std::optional<KernelCacheMachOHeader> LoadHeaderForAddress(Ref<BinaryView> view, uint64_t address, std::string installName);
- static void InitializeHeader(Ref<BinaryView> view, KernelCacheMachOHeader header);
- static void 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);
- static std::vector<Ref<Symbol>> ParseExportTrie(Ref<BinaryView> view, KernelCacheMachOHeader header);
- static std::vector<std::pair<uint64_t, std::pair<BNSymbolType, std::string>>> ParseSymbolTable(Ref<BinaryView> view, KernelCacheMachOHeader header, bool defineSymbolsInView = true);
- };
-
-
- class KernelCacheMetadata
- {
- public:
- static std::optional<KernelCacheMetadata> LoadFromView(BinaryView*);
- static bool ViewHasMetadata(BinaryView*);
+ uint64_t GetBaseAddress() const { return m_baseAddress; }
+ const std::unordered_map<uint64_t, CacheImage>& GetImages() const { return m_images; }
+ const std::unordered_map<uint64_t, CacheSymbol>& GetSymbols() const { return m_symbols; }
- std::string InstallNameForImageBaseAddress(uint64_t baseAddress) const;
+ void AddImage(CacheImage&& image);
- std::vector<KernelCacheImage> LoadedImages();
+ void AddSymbol(CacheSymbol symbol);
- ~KernelCacheMetadata();
- KernelCacheMetadata(KernelCacheMetadata&&);
- KernelCacheMetadata& operator=(KernelCacheMetadata&&);
+ void AddSymbols(std::vector<CacheSymbol>&& symbols);
- private:
- KernelCacheMetadata(KernelCache::CacheInfo, KernelCache::ModifiedState);
+ // Construct the named symbols lookup map for use with `GetSymbolWithName`.
+ void ProcessSymbols();
- std::unique_ptr<KernelCache::CacheInfo> cacheInfo;
- std::unique_ptr<KernelCache::ModifiedState> state;
+ void ProcessRelocations(BinaryNinja::Ref<BinaryNinja::BinaryView> view, BinaryNinja::linkedit_data_command chained_fixup_command);
- friend struct KernelCache::ModifiedState;
- friend class KernelCache;
+ const std::vector<std::pair<uint64_t, uint64_t>>& GetRelocations() const { return m_relocations; }
- static const std::string Tag;
- static const std::string CacheInfoTag;
- static const std::string ModifiedStateTagPrefix;
- static const std::string ModifiedStateCountTag;
- };
+ std::optional<CacheImage> GetImageAt(uint64_t address) const;
-}
+ std::optional<CacheImage> GetImageContaining(uint64_t address) const;
-void InitKernelcache();
+ // TODO: Rename to GetImageWithPath and then make another one for the image name.
+ std::optional<CacheImage> GetImageWithName(const std::string& name) const;
-#endif //KERNELCACHE_KERNELCACHE_H
+ std::optional<CacheSymbol> GetSymbolAt(uint64_t address) const;
+ std::optional<CacheSymbol> GetSymbolWithName(const std::string& name);
+};
diff --git a/view/kernelcache/core/KernelCacheBuilder.cpp b/view/kernelcache/core/KernelCacheBuilder.cpp
new file mode 100644
index 00000000..50c1355f
--- /dev/null
+++ b/view/kernelcache/core/KernelCacheBuilder.cpp
@@ -0,0 +1,16 @@
+#include <filesystem>
+#include "KernelCacheBuilder.h"
+
+using namespace BinaryNinja;
+
+KernelCacheBuilder::KernelCacheBuilder(Ref<BinaryView> view)
+{
+ m_view = std::move(view);
+ m_logger = new Logger("KernelCache.Builder", m_view->GetFile()->GetSessionId());
+ m_cache = KernelCache(m_view->GetAddressSize());
+}
+
+KernelCache KernelCacheBuilder::Finalize()
+{
+ return std::move(m_cache);
+}
diff --git a/view/kernelcache/core/KernelCacheBuilder.h b/view/kernelcache/core/KernelCacheBuilder.h
new file mode 100644
index 00000000..756d069b
--- /dev/null
+++ b/view/kernelcache/core/KernelCacheBuilder.h
@@ -0,0 +1,20 @@
+#pragma once
+
+#include "binaryninjaapi.h"
+#include "KernelCache.h"
+
+// This constructs a Cache
+class KernelCacheBuilder
+{
+ BinaryNinja::Ref<BinaryNinja::BinaryView> m_view;
+ BinaryNinja::Ref<BinaryNinja::Logger> m_logger;
+ // This cache is what is returned via `Finalize`.
+ KernelCache m_cache;
+
+public:
+ explicit KernelCacheBuilder(BinaryNinja::Ref<BinaryNinja::BinaryView> view);
+
+ KernelCache& GetCache() { return m_cache; };
+ // Returns a shared cache that is ready for processing, this should include all the required shared cache entries.
+ KernelCache Finalize();
+};
diff --git a/view/kernelcache/core/KernelCacheController.cpp b/view/kernelcache/core/KernelCacheController.cpp
new file mode 100644
index 00000000..ef2fbd04
--- /dev/null
+++ b/view/kernelcache/core/KernelCacheController.cpp
@@ -0,0 +1,198 @@
+#include "KernelCacheController.h"
+#include "MachOProcessor.h"
+
+using namespace BinaryNinja;
+using namespace BinaryNinja::KC;
+
+typedef uint64_t ViewId;
+
+std::shared_mutex GlobalControllersMutex;
+
+std::map<ViewId, KCRef<KernelCacheController>>& GlobalControllers()
+{
+ static std::map<ViewId, KCRef<KernelCacheController>> g_controllers = {};
+ return g_controllers;
+}
+
+
+ViewId GetViewIdFromFileMetadata(const FileMetadata& file)
+{
+ // 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 file.GetSessionId();
+}
+
+void DeleteController(const FileMetadata& file)
+{
+ const auto id = GetViewIdFromFileMetadata(file);
+ std::unique_lock<std::shared_mutex> lock(GlobalControllersMutex);
+ auto& controllers = GlobalControllers();
+ if (auto it = controllers.find(id); it != controllers.end())
+ {
+ auto controller = it->second;
+ // Someone is still holding the controller, lets warn about this.
+ // 2 is expected here because we have one held in `controllers` and one held by `controller`.
+ if (controller->m_refs > 2)
+ LogWarn("Deleting KernelCacheController for view %llx, but there are still %d references", id,
+ controller->m_refs.load());
+
+ controllers.erase(it);
+ LogDebug("Deleted KernelCacheController for view %s", file.GetFilename().c_str());
+ }
+}
+
+void RegisterKernelCacheControllerDestructor()
+{
+ BNObjectDestructionCallbacks callbacks = {};
+ callbacks.destructFileMetadata = [](void* ctx, BNFileMetadata* obj) -> void {
+ const auto file = FileMetadata(obj);
+ DeleteController(file);
+ };
+ BNRegisterObjectDestructionCallbacks(&callbacks);
+}
+
+KernelCacheController::KernelCacheController(KernelCache&& cache, Ref<Logger> logger) : m_cache(std::move(cache))
+{
+ INIT_KC_API_OBJECT();
+ m_logger = std::move(logger);
+ m_loadedImages = {};
+ m_regionFilter = std::regex(".*LINKEDIT.*");
+}
+
+KCRef<KernelCacheController> KernelCacheController::Initialize(BinaryView& view, KernelCache&& cache)
+{
+ auto id = GetViewIdFromFileMetadata(*view.GetFile());
+ std::unique_lock<std::shared_mutex> lock(GlobalControllersMutex);
+ auto logger = new Logger("KernelCache.Controller", view.GetFile()->GetSessionId());
+ KCRef<KernelCacheController> controller = new KernelCacheController(std::move(cache), logger);
+
+ // Pull the settings from the view.
+ if (Ref<Settings> settings = view.GetLoadSettings(KC_VIEW_NAME))
+ {
+ if (settings->Contains("loader.kc.regionFilter"))
+ controller->m_regionFilter = std::regex(settings->Get<std::string>("loader.kc.regionFilter", &view));
+ }
+
+ // TODO: Support old shared cache metadata
+ // TODO: Not strictly necessary as the user has already loaded the information into the database, this would just
+ // TODO: prevent incidental extra work from being done when loading a region or image.
+ // const uint64_t oldStateCount = view.GetUIntMetadata(OLD_METADATA_KEY_COUNT);
+
+ // Check the view auto metadata for shared cache information.
+ // This effectively restores the state of the opened database to when it was last saved.
+ // NOTE: We store on the parent view because hilariously, the metadata is not present until after view init.
+ if (auto loadedImageMetadata = view.GetParentView()->QueryMetadata("KernelCacheLoadedImages"))
+ {
+ auto loadedImageList = loadedImageMetadata->GetArray();
+ for (const auto & imageAddrMeta : loadedImageList)
+ {
+ auto imageAddr = imageAddrMeta->GetUnsignedInteger();
+ controller->m_loadedImages.insert(imageAddr);
+ }
+ }
+
+ GlobalControllers().insert({id, controller});
+ return controller;
+}
+
+KCRef<KernelCacheController> KernelCacheController::FromView(const BinaryView& view)
+{
+ auto id = GetViewIdFromFileMetadata(*view.GetFile());
+ 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 KernelCacheController::ApplyImage(BinaryView& view, const CacheImage& image)
+{
+ // Load all regions of an image and mark the image as loaded.
+ // NOTE: The regions lock m_loadMutex themselves, so we do not hold it up here.
+ bool loadedRegion = false;
+
+ BNRelocationInfo reloc;
+ memset(&reloc, 0, sizeof(BNRelocationInfo));
+ reloc.type = StandardRelocationType;
+ reloc.size = 8;
+ reloc.nativeType = BINARYNINJA_MANUAL_RELOCATION;
+
+ // We check for a valid offset as we apply auto segments and re-call this function on view init
+ if (!view.IsValidOffset(image.headerVirtualAddress))
+ {
+ loadedRegion = true;
+ for (const auto& segment : image.header->segments)
+ {
+ view.AddAutoSegment(segment.vmaddr, segment.vmsize, segment.fileoff, segment.filesize, segment.flags);
+
+ auto relocations = m_cache.GetRelocations();
+
+ auto begin = std::lower_bound(relocations.begin(), relocations.end(), segment.vmaddr,
+ [](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 < segment.vmaddr + segment.vmsize; ++it) {
+ reloc.address = it->first;
+ view.DefineRelocation(arch, reloc, it->second, reloc.address);
+ }
+ }
+ }
+
+ view.FinalizeNewSegments();
+
+ // The ApplyRegionAtAddress no longer holds the lock, we can take it now.
+ std::unique_lock<std::shared_mutex> lock(m_loadMutex);
+
+
+ // If there was no loaded regions than we just want to forgo loading the image.
+ // We also skip if we already loaded the image itself. We do this after loading regions
+ // as we regions have their own check.
+
+ // On view init, we exit here as we just need to re-add the segments and relocations.
+ if (!loadedRegion || m_loadedImages.find(image.headerVirtualAddress) != m_loadedImages.end())
+ return false;
+
+ if (image.headerVirtualAddress)
+ {
+ // Header information is applied to the view here, such as sections.
+ auto machoProcessor = KernelCacheMachOProcessor(&view);
+
+ // Adding a user section will mark all functions for updates unless we disable this.
+ // Because images are known separate compilation units, we have a real reason to make sure we don't mark all previously
+ // analyzed functions as updated.
+ auto prevDisabledState = view.GetFunctionAnalysisUpdateDisabled();
+ view.SetFunctionAnalysisUpdateDisabled(true);
+ machoProcessor.ApplyHeader(GetCache(), *image.header);
+ view.SetFunctionAnalysisUpdateDisabled(prevDisabledState);
+ }
+
+ m_loadedImages.insert(image.headerVirtualAddress);
+
+ m_logger->LogInfoF("Loaded image: '{}'", image.path);
+
+ // TODO: This needs to be done in a "database save" callback.
+ // NOTE: We store on the parent view because hilariously, the view metadata is not available in view init.
+ std::vector<uint64_t> loadedImages;
+ loadedImages.reserve(m_loadedImages.size());
+ // Store the loaded images in the metadata.
+ for (const uint64_t& addr : m_loadedImages)
+ {
+ loadedImages.push_back(addr);
+ }
+ view.GetParentView()->StoreMetadata("KernelCacheLoadedImages", new Metadata(loadedImages));
+
+ // TODO: Partial failure state (i.e. 2 regions loaded, one failed)
+ return true;
+}
+
+bool KernelCacheController::IsImageLoaded(const CacheImage& image)
+{
+ std::shared_lock<std::shared_mutex> lock(m_loadMutex);
+ return std::any_of(m_loadedImages.begin(), m_loadedImages.end(), [&](const auto& loadedImage) {
+ return loadedImage == image.headerVirtualAddress;
+ });
+}
diff --git a/view/kernelcache/core/KernelCacheController.h b/view/kernelcache/core/KernelCacheController.h
new file mode 100644
index 00000000..71f5e5a9
--- /dev/null
+++ b/view/kernelcache/core/KernelCacheController.h
@@ -0,0 +1,49 @@
+#pragma once
+
+#include <regex>
+
+#include <shared_mutex>
+#include "KernelCache.h"
+#include "refcountobject.h"
+#include "ffi_global.h"
+
+DECLARE_KC_API_OBJECT(BNKernelCacheController, KernelCacheController);
+
+void RegisterKernelCacheControllerDestructor();
+
+namespace BinaryNinja::KC {
+ // Represents the view state for a given `DSCache`
+ class KernelCacheController : public KCRefCountObject
+ {
+ IMPLEMENT_KC_API_OBJECT(BNKernelCacheController);
+ Ref<Logger> m_logger;
+ KernelCache m_cache;
+
+ // Locks on load attempts (region or image).
+ std::shared_mutex m_loadMutex;
+
+ // Store the open images.
+ std::unordered_set<uint64_t> m_loadedImages;
+
+ // Settings from the view.
+ std::regex m_regionFilter;
+
+ explicit KernelCacheController(KernelCache&& cache, Ref<Logger> logger);
+
+ public:
+ // Initialize the DSCacheView, this should be called from the view initialize function only!
+ static KCRef<KernelCacheController> Initialize(BinaryView& view, KernelCache&& cache);
+
+ // NOTE: This will not create one if it does not exist. To create one for the view call `Initialize`.
+ static KCRef<KernelCacheController> FromView(const BinaryView& view);
+
+ KernelCache& GetCache() { return m_cache; };
+ const std::unordered_set<uint64_t>& GetLoadedImages() { return m_loadedImages; };
+
+ // 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);
+ };
+} // namespace BinaryNinja::KC
diff --git a/view/kernelcache/core/KCView.cpp b/view/kernelcache/core/KernelCacheView.cpp
index f166eafd..005a4af2 100644
--- a/view/kernelcache/core/KCView.cpp
+++ b/view/kernelcache/core/KernelCacheView.cpp
@@ -1,49 +1,252 @@
-//
-// Created by kat on 5/23/23.
-//
+#include "KernelCacheView.h"
+#include <regex>
+#include <filesystem>
+#include <MachOProcessor.h>
-/*
- *
- * */
+#include "KernelCacheController.h"
+#include "KernelCacheBuilder.h"
-#include "KCView.h"
-#include "view/macho/machoview.h"
-#include "KernelCache.h"
+using namespace BinaryNinja;
+using namespace BinaryNinja::KC;
-[[maybe_unused]] KCViewType* g_kcViewType;
+[[maybe_unused]] KernelCacheViewType* g_kcViewType;
+KernelCacheViewType::KernelCacheViewType() : BinaryViewType(KC_VIEW_NAME, KC_VIEW_NAME) {}
-#define COMPRESSION_DEBUG 1
+// We register all our one-shot stuff here, such as the object destructor.
+void KernelCacheViewType::Register()
+{
+ RegisterKernelCacheControllerDestructor();
-using namespace BinaryNinja;
+ static KernelCacheViewType kcViewType;
+ BinaryViewType::Register(&kcViewType);
+ g_kcViewType = &kcViewType;
+}
-KCView::KCView(const std::string& typeName, BinaryView* data, bool parseOnly) :
- BinaryView(typeName, data->GetFile(), data), m_parseOnly(parseOnly)
+Ref<BinaryView> KernelCacheViewType::Create(BinaryView* data)
{
- CreateLogger("KernelCache");
+ uint32_t magic;
+ data->Read(&magic, data->GetStart(), 4);
+ if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit
+ {
+ uint32_t im4pMagic;
+ data->Read(&im4pMagic, data->GetStart() + 0x8, 4);
+ if (im4pMagic == 0x50344d49) // P4MI
+ {
+ auto img4 = Transform::GetByName("IMG4-Unencrypted");
+
+ DataBuffer img4Payload;
+ img4->Decode(data->ReadBuffer(data->GetStart(), data->GetLength()), img4Payload);
+
+ DataBuffer machOPayload;
+ uint32_t magic = ((uint32_t*)img4Payload.GetData())[0];
+ if (magic == FAT_MAGIC_64 || magic == MH_MAGIC_64 || magic == MH_MAGIC
+ || magic == MH_CIGAM_64 || magic == MH_CIGAM )
+ {
+ machOPayload = img4Payload;
+ }
+ else if (strncmp((char*)img4Payload.GetData(), "bvx2", 4) == 0)
+ {
+ auto lzfse = Transform::GetByName("LZFSE");
+ if (lzfse)
+ lzfse->Decode(img4Payload, machOPayload);
+ }
+ else
+ {
+#ifdef COMPRESSION_DEBUG
+ LogError("Unknown compression type in IMG4 Payload, writing img4 payload to RAW view for debug purposes.");
+ LogError("KernelCache parsing will now fail to proceed.");
+ data->WriteBuffer(0, img4Payload);
+ return new KernelCacheView(KC_VIEW_NAME, data, false);
+#else
+ LogError("Unknown compression type in IMG4 Payload, unable to proceed.");
+ LogError("You can manually extract the kernelcache using `kerneldec`,`ipsw`, or other tools.");
+ return nullptr;
+#endif
+ }
+
+ if (machOPayload.GetLength() == 0)
+ {
+#ifdef COMPRESSION_DEBUG
+ LogError("Failed to perform extraction on IMG4 Payload, writing img4 payload to RAW view for debug purposes.");
+ LogError("KernelCache parsing will now fail to proceed.");
+ data->WriteBuffer(0, img4Payload);
+ return new KernelCacheView(KC_VIEW_NAME, data, false);
+#else
+ return nullptr;
+#endif
+ }
+
+ uint32_t machoMagic = ((uint32_t*)machOPayload.GetData())[0];
+ if (machoMagic == FAT_MAGIC_64)
+ {
+ DataBuffer output = machOPayload.GetSlice(0x1c, machOPayload.GetLength()-0x1c);
+ data->WriteBuffer(0, output);
+ }
+ else if (machoMagic == MH_MAGIC_64 || machoMagic == MH_MAGIC || machoMagic == MH_CIGAM_64 || machoMagic == MH_CIGAM)
+ {
+ data->WriteBuffer(0, machOPayload);
+ }
+ else
+ {
+#ifdef COMPRESSION_DEBUG
+ LogError("Unknown Mach-O magic in IMG4 Payload, writing img4 payload to RAW view for debug purposes.");
+ LogError("KernelCache parsing will now fail to proceed.");
+ data->WriteBuffer(0, machOPayload);
+ return new KernelCacheView(KC_VIEW_NAME, data, false);
+#else
+ return nullptr;
+#endif
+ }
+
+ return new KernelCacheView(KC_VIEW_NAME, data, false);
+ }
+
+ return nullptr;
+ }
+
+ return new KernelCacheView(KC_VIEW_NAME, data, false);
}
-KCView::~KCView()
+Ref<Settings> KernelCacheViewType::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);
+ }
+
+ settings->RegisterSetting("loader.kc.autoLoadPattern",
+ R"({
+ "title" : "Image Auto-Load Regex Pattern",
+ "type" : "string",
+ "default" : "",
+ "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.kc.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;
+}
+
+Ref<BinaryView> KernelCacheViewType::Parse(BinaryView* data)
+{
+ uint32_t magic;
+ data->Read(&magic, data->GetStart(), 4);
+ if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit
+ {
+ uint32_t im4pMagic;
+ data->Read(&im4pMagic, data->GetStart() + 0x8, 4);
+ if (im4pMagic == 0x50344d49) // P4MI
+ {
+ auto img4 = Transform::GetByName("IMG4-Unencrypted");
+ auto lzfse = Transform::GetByName("LZFSE");
+
+ DataBuffer img4Payload;
+ img4->Decode(data->ReadBuffer(data->GetStart(), data->GetLength()), img4Payload);
+ DataBuffer machOPayload;
+ lzfse->Decode(img4Payload, machOPayload);
+
+ uint32_t magic = ((uint32_t*)machOPayload.GetData())[0];
+ auto id = data->BeginUndoActions();
+ if (magic == FAT_MAGIC_64)
+ {
+ DataBuffer output = machOPayload.GetSlice(0x1c, machOPayload.GetLength()-0x1c);
+ data->WriteBuffer(0, output);
+ }
+ else
+ {
+ data->WriteBuffer(0, machOPayload);
+ }
+ data->ForgetUndoActions(id);
+ return new KernelCacheView(KC_VIEW_NAME, data, true);
+ }
+
+ return nullptr;
+ }
+
+ return new KernelCacheView(KC_VIEW_NAME, data, true);
}
-enum KCPlatform {
- KCPlatformMacOS = 1,
- KCPlatformiOS = 2,
- KCPlatformTVOS = 3,
- KCPlatformWatchOS = 4,
- KCPlatformBridgeOS = 5, // T1/T2 APL1023/T8012, this is your touchbar/touchid in intel macs. Similar to watchOS.
- // KCPlatformMacCatalyst = 6,
- KCPlatformiOSSimulator = 7,
- KCPlatformTVOSSimulator = 8,
- KCPlatformWatchOSSimulator = 9,
- KCPlatformVisionOS = 11, // Apple Vision Pro
- KCPlatformVisionOSSimulator = 12 // Apple Vision Pro Simulator
-};
+bool KernelCacheViewType::IsTypeValidForData(BinaryView* data)
+{
+ if (!data)
+ return false;
+
+ uint32_t magic;
+ data->Read(&magic, data->GetStart(), 4);
+
+ if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit
+ {
+ uint32_t im4pMagic;
+ data->Read(&im4pMagic, data->GetStart() + 0x8, 4);
+ if (im4pMagic == 0x50344d49) // P4MI
+ {
+ auto img4 = Transform::GetByName("IMG4-Unencrypted");
+ auto lzfse = Transform::GetByName("LZFSE");
+
+ DataBuffer img4Payload;
+ img4->Decode(data->ReadBuffer(data->GetStart(), data->GetLength()), img4Payload);
+ DataBuffer machOPayload;
+ lzfse->Decode(img4Payload, machOPayload);
-bool KCView::Init()
+ uint32_t magic = ((uint32_t*)machOPayload.GetData())[0];
+ if (magic == FAT_MAGIC_64 || magic == MH_CIGAM_64 || magic == MH_MAGIC_64)
+ return true;
+
+ return false;
+ }
+
+ return false;
+ }
+
+ uint32_t fileType;
+ data->Read(&fileType, data->GetStart() + 0xc, 4);
+ if (fileType != MH_FILESET)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+KernelCacheView::KernelCacheView(const std::string& typeName, BinaryView* data, bool parseOnly) :
+ BinaryView(typeName, data->GetFile(), data), m_parseOnly(parseOnly)
{
+}
+
+bool KernelCacheView::Init()
+{
+ m_logger = new Logger("KernelCache.View", GetFile()->GetSessionId());
+
BinaryReader reader(GetParentView());
reader.Seek(0x4);
uint32_t cpuType = reader.Read32();
@@ -116,9 +319,16 @@ bool KCView::Init()
}
if (!foundBuildVersion)
{
- LogError("Failed to find LC_BUILD_VERSION in subheader");
- SetDefaultArchitecture(Architecture::GetByName("aarch64"));
- SetDefaultPlatform(Platform::GetByName("macos-kernel-aarch64"));
+ // FIXME: Is it in-spec to have LC_BUILD_VERSION only in the super header?
+ // This is sufficient as a fallback but source should be referenced here.
+ LogWarn("Failed to find LC_BUILD_VERSION in subheader. Assuming macOS platform.");
+ std::map<std::string, Ref<Metadata>> metadataMap = {
+ {"machoplatform", new Metadata((uint64_t) MACHO_PLATFORM_MACOS)},
+ };
+ Ref<Metadata> metadata = new Metadata(metadataMap);
+ Ref<Platform> plat = g_kcViewType->RecognizePlatform(cpuType, GetDefaultEndianness(), this, metadata);
+ SetDefaultArchitecture(plat->GetArchitecture());
+ SetDefaultPlatform(plat);
}
}
else
@@ -127,13 +337,6 @@ bool KCView::Init()
return false;
}
- if (textSegOffset == 0)
- {
- LogError("Failed to find __TEXT segment");
- return false;
- }
-
-
// Add Mach-O file header type info
EnumerationBuilder cpuTypeBuilder;
cpuTypeBuilder.AddMemberWithValue("CPU_TYPE_ANY", MACHO_CPU_TYPE_ANY);
@@ -557,37 +760,17 @@ bool KCView::Init()
Ref<Type> unixThreadCommandType = Type::StructureType(unixThreadCommandStruct);
DefineType(unixThreadCommandTypeId, unixThreadCommandName, unixThreadCommandType);
- std::vector<KernelCacheCore::MemoryRegion> regionsMappedIntoMemory;
- if (auto metadata = KernelCacheCore::KernelCacheMetadata::LoadFromView(GetParentView()))
- {
- for (const auto& image : metadata->LoadedImages())
- {
- auto header = KernelCacheCore::KernelCache::LoadHeaderForAddress(this, image.headerFileLocation, image.installName);
- if (!header)
- {
- LogError("Failed to load header for image %s", image.installName.c_str());
- return false;
- }
- if (!KernelCacheCore::KernelCache::InitializeSegmentsForHeader(this, *header, image))
- {
- LogError("Failed to initialize segments for image %s", image.installName.c_str());
- return false;
- }
- KernelCacheCore::KernelCache::InitializeHeader(this, *header);
- }
- }
-
// Technically the header is executable, but there shouldn't reasonably be code in the header.
AddAutoSegment(textSegOffset, sizeofcmds + 0x20, 0, sizeofcmds + 0x20, SegmentReadable);
AddAutoSection("kernelcache_header", textSegOffset, sizeofcmds + 0x20, ReadOnlyDataSectionSemantics);
if (m_parseOnly)
return true;
-
+
DefineDataVariable(textSegOffset, Type::NamedType(this, QualifiedName("mach_header_64")));
DefineAutoSymbol(
new Symbol(DataSymbol, "kernelcache_header", textSegOffset, LocalBinding));
-
+
try
{
reader.Seek(sizeof(mach_header_64));
@@ -702,220 +885,109 @@ bool KCView::Init()
LogError("Error when applying Mach-O header types at %" PRIx64, textSegOffset);
}
- return true;
+ return InitController();
}
-
-KCViewType::KCViewType() : BinaryViewType(KC_VIEW_NAME, KC_VIEW_NAME)
+bool KernelCacheView::InitController()
{
-}
+ // OK, we have the primary shared cache file, now let's add the entries.
+ auto kernelCacheBuilder = KernelCacheBuilder(this);
+ auto kernelCache = kernelCacheBuilder.Finalize();
+
+ // TODO: Here we should have all the regions and what not for the virtual memory populated, I think it might be a good idea
+ // TODO: To verify the regions are here and pointing at valid file accessor mappings, to make diagnosing issues quicker.
-BinaryNinja::Ref<BinaryNinja::BinaryView> KCViewType::Create(BinaryNinja::BinaryView* data)
-{
- uint32_t magic;
- data->Read(&magic, data->GetStart(), 4);
- if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit
{
- uint32_t im4pMagic;
- data->Read(&im4pMagic, data->GetStart() + 0x8, 4);
- if (im4pMagic == 0x50344d49) // P4MI
+ BinaryReader reader(GetParentView());
+ reader.Seek(0x4);
+ uint32_t cpuType = reader.Read32();
+ reader.Seek(0x10);
+ uint64_t ncmds = reader.Read32();
+ uint64_t sizeofcmds = reader.Read32();
+ uint64_t offset = 0x20;
+ for (uint64_t i = 0; i < ncmds; i++)
{
- auto img4 = Transform::GetByName("IMG4-Unencrypted");
-
- DataBuffer img4Payload;
- img4->Decode(data->ReadBuffer(data->GetStart(), data->GetLength()), img4Payload);
+ reader.Seek(offset);
+ uint64_t cmd = reader.Read32();
+ uint64_t cmdsize = reader.Read32();
- DataBuffer machOPayload;
- uint32_t magic = ((uint32_t*)img4Payload.GetData())[0];
- if (magic == FAT_MAGIC_64 || magic == MH_MAGIC_64 || magic == MH_MAGIC
- || magic == MH_CIGAM_64 || magic == MH_CIGAM )
- {
- machOPayload = img4Payload;
- }
- else if (strncmp((char*)img4Payload.GetData(), "bvx2", 4) == 0)
- {
- auto lzfse = Transform::GetByName("LZFSE");
- if (lzfse)
- lzfse->Decode(img4Payload, machOPayload);
- }
- else
+ if (cmd == LC_FILESET_ENTRY)
{
-#ifdef COMPRESSION_DEBUG
- LogError("Unknown compression type in IMG4 Payload, writing img4 payload to RAW view for debug purposes.");
- LogError("KernelCache parsing will now fail to proceed.");
- data->WriteBuffer(0, img4Payload);
- return new KCView(KC_VIEW_NAME, data, false);
-#else
- LogError("Unknown compression type in IMG4 Payload, unable to proceed.");
- LogError("You can manually extract the kernelcache using `kerneldec`,`ipsw`, or other tools.")
- return nullptr;
-#endif
+ fileset_entry_command fileset_entry;
+ reader.Seek(offset);
+ reader.Read(&fileset_entry, sizeof(fileset_entry_command));
+ reader.Seek(offset + fileset_entry.nameEntryOffsetFromBaseOfCommand);
+ auto name = reader.ReadCString(1000);
+ kernelCache.ProcessEntryImage(this, name, fileset_entry);
}
- if (machOPayload.GetLength() == 0)
+ if (cmd == LC_DYLD_CHAINED_FIXUPS)
{
-#ifdef COMPRESSION_DEBUG
- LogError("Failed to perform extraction on IMG4 Payload, writing img4 payload to RAW view for debug purposes.");
- LogError("KernelCache parsing will now fail to proceed.");
- data->WriteBuffer(0, img4Payload);
- return new KCView(KC_VIEW_NAME, data, false);
-#else
- return nullptr;
-#endif
- }
+ linkedit_data_command chained_fixups;
+ reader.Seek(offset);
+ reader.Read(&chained_fixups, sizeof(linkedit_data_command));
- uint32_t machoMagic = ((uint32_t*)machOPayload.GetData())[0];
- if (machoMagic == FAT_MAGIC_64)
- {
- DataBuffer output = machOPayload.GetSlice(0x1c, machOPayload.GetLength()-0x1c);
- data->WriteBuffer(0, output);
- }
- else if (machoMagic == MH_MAGIC_64 || machoMagic == MH_MAGIC || machoMagic == MH_CIGAM_64 || machoMagic == MH_CIGAM)
- {
- data->WriteBuffer(0, machOPayload);
- }
- else
- {
-#ifdef COMPRESSION_DEBUG
- LogError("Unknown Mach-O magic in IMG4 Payload, writing img4 payload to RAW view for debug purposes.");
- LogError("KernelCache parsing will now fail to proceed.");
- data->WriteBuffer(0, machOPayload);
- return new KCView(KC_VIEW_NAME, data, false);
-#else
- return nullptr;
-#endif
+ kernelCache.ProcessRelocations(this, chained_fixups);
}
- return new KCView(KC_VIEW_NAME, data, false);
+ offset += cmdsize;
}
-
- return nullptr;
}
- return new KCView(KC_VIEW_NAME, data, false);
-}
-
+ auto cacheController = KernelCacheController::Initialize(*this, std::move(kernelCache));
-Ref<Settings> KCViewType::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;
+ auto logger = m_logger;
+ // Load up all the symbols into the named symbols lookup map.
+ // NOTE: We do this on a separate thread as image & region loading does not consult this.
+ WorkerPriorityEnqueue([logger, cacheController]() {
+ auto& kernelCache = cacheController->GetCache();
+ auto startTime = std::chrono::high_resolution_clock::now();
+ kernelCache.ProcessSymbols();
+ auto endTime = std::chrono::high_resolution_clock::now();
+ std::chrono::duration<double> elapsed = endTime - startTime;
+ logger->LogInfo("Processing %zu symbols took %.3f seconds (separate thread)", kernelCache.GetSymbols().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.");
+ Ref<Settings> settings = GetLoadSettings(GetTypeName());
+ // 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.kc.autoLoadPattern"))
+ autoLoadPattern = settings->Get<std::string>("loader.kc.autoLoadPattern", this);
+ m_logger->LogDebug("Loading images using pattern: %s", autoLoadPattern.c_str());
- for (const auto& override : overrides)
{
- if (settings->Contains(override))
- settings->UpdateProperty(override, "readOnly", false);
+ // TODO: Refusing to add undo action "Added section libsystem_c.dylib::__macho_header", there is literally
+ // TODO: no way around this warning as undo actions are implicit with user sections, for more detail see:
+ // TODO: - https://github.com/Vector35/binaryninja-api/issues/6742
+ // TODO: - https://github.com/Vector35/binaryninja-api/issues/6289
+ // Load all images that match the `autoLoadPattern`.
+ auto startTime = std::chrono::high_resolution_clock::now();
+ size_t loadedImages = 0;
+ std::regex autoLoadRegex(autoLoadPattern);
+ for (const auto& [_, image] : cacheController->GetCache().GetImages())
+ if (std::regex_match(image.GetName(), autoLoadRegex))
+ if (cacheController->ApplyImage(*this, image))
+ ++loadedImages;
+ auto endTime = std::chrono::high_resolution_clock::now();
+ std::chrono::duration<double> elapsed = endTime - startTime;
+ m_logger->LogInfo("Automatically loading %zu images took %.3f seconds", loadedImages, elapsed.count());
}
- // 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;
-}
-
-
-BinaryNinja::Ref<BinaryNinja::BinaryView> KCViewType::Parse(BinaryNinja::BinaryView* data)
-{
- uint32_t magic;
- data->Read(&magic, data->GetStart(), 4);
- if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit
+ if (auto loadedImageMetadata = GetParentView()->QueryMetadata("KernelCacheLoadedImages"))
{
- uint32_t im4pMagic;
- data->Read(&im4pMagic, data->GetStart() + 0x8, 4);
- if (im4pMagic == 0x50344d49) // P4MI
+ auto loadedImageList = loadedImageMetadata->GetArray();
+ for (const auto & imageAddrMeta : loadedImageList)
{
- auto img4 = Transform::GetByName("IMG4-Unencrypted");
- auto lzfse = Transform::GetByName("LZFSE");
-
- DataBuffer img4Payload;
- img4->Decode(data->ReadBuffer(data->GetStart(), data->GetLength()), img4Payload);
- DataBuffer machOPayload;
- lzfse->Decode(img4Payload, machOPayload);
-
- uint32_t magic = ((uint32_t*)machOPayload.GetData())[0];
- auto id = data->BeginUndoActions();
- if (magic == FAT_MAGIC_64)
+ auto imageAddr = imageAddrMeta->GetUnsignedInteger();
+ const auto& image = cacheController->GetCache().GetImageAt(imageAddr);
+ if (image)
{
- DataBuffer output = machOPayload.GetSlice(0x1c, machOPayload.GetLength()-0x1c);
- data->WriteBuffer(0, output);
+ cacheController->ApplyImage(*this, *image);
}
- else
- {
- data->WriteBuffer(0, machOPayload);
- }
- data->ForgetUndoActions(id);
- return new KCView(KC_VIEW_NAME, data, true);
}
-
- return nullptr;
- }
-
- return new KCView(KC_VIEW_NAME, data, true);
-}
-
-bool KCViewType::IsTypeValidForData(BinaryNinja::BinaryView* data)
-{
- if (!data)
- return false;
-
- uint32_t magic;
- data->Read(&magic, data->GetStart(), 4);
-
- if (magic != MH_CIGAM_64 && magic != MH_MAGIC_64) // FIXME 32 bit
- {
- uint32_t im4pMagic;
- data->Read(&im4pMagic, data->GetStart() + 0x8, 4);
- if (im4pMagic == 0x50344d49) // P4MI
- {
- auto img4 = Transform::GetByName("IMG4-Unencrypted");
- auto lzfse = Transform::GetByName("LZFSE");
-
- DataBuffer img4Payload;
- img4->Decode(data->ReadBuffer(data->GetStart(), data->GetLength()), img4Payload);
- DataBuffer machOPayload;
- lzfse->Decode(img4Payload, machOPayload);
-
- uint32_t magic = ((uint32_t*)machOPayload.GetData())[0];
- if (magic == FAT_MAGIC_64 || magic == MH_CIGAM_64 || magic == MH_MAGIC_64)
- return true;
-
- return false;
- }
-
- return false;
- }
-
- uint32_t fileType;
- data->Read(&fileType, data->GetStart() + 0xc, 4);
- if (fileType != MH_FILESET)
- {
- return false;
}
return true;
}
-
-extern "C" {
- void InitKCViewType()
- {
- static KCViewType type;
- BinaryViewType::Register(&type);
- g_kcViewType = &type;
- }
-}
-
diff --git a/view/kernelcache/core/KernelCacheView.h b/view/kernelcache/core/KernelCacheView.h
new file mode 100644
index 00000000..9a2baf6f
--- /dev/null
+++ b/view/kernelcache/core/KernelCacheView.h
@@ -0,0 +1,63 @@
+//
+// Created by kat on 5/23/23.
+//
+
+#ifndef KERNELCACHE_KERNELCACHEVIEW_H
+#define KERNELCACHE_KERNELCACHEVIEW_H
+
+#include <binaryninjaapi.h>
+
+static const char* VIEW_METADATA_KEY = "shared_cache_view";
+
+class KernelCacheView : public BinaryNinja::BinaryView
+{
+ bool m_parseOnly;
+ BinaryNinja::Ref<BinaryNinja::Logger> m_logger;
+
+public:
+ KernelCacheView(const std::string& typeName, BinaryView* data, bool parseOnly = false);
+
+ ~KernelCacheView() override = default;
+
+ bool Init() override;
+
+ // Initialized the shared cache controller for this view. This is what allows us to load images and regions.
+ bool InitController();
+
+ void SetPrimaryFileName(std::string primaryFileName);
+
+ // Logs the secondary file name to `m_secondaryFileNames`, see the note on the field about usage.
+ void LogSecondaryFileName(std::string associatedFileName);
+
+ // Get the path to the primary file.
+ std::optional<std::string> GetPrimaryFilePath();
+
+ // Get the metadata for saving the state of the shared cache.
+ BinaryNinja::Ref<BinaryNinja::Metadata> GetMetadata() const;
+
+ void LoadMetadata(const BinaryNinja::Metadata& metadata);
+
+ virtual bool PerformIsExecutable() const override { return true; }
+};
+
+
+class KernelCacheViewType : public BinaryNinja::BinaryViewType
+{
+public:
+ KernelCacheViewType();
+
+ 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 // KERNELCACHE_KERNELCACHEVIEW_H
diff --git a/view/kernelcache/core/MachO.cpp b/view/kernelcache/core/MachO.cpp
new file mode 100644
index 00000000..d3486b67
--- /dev/null
+++ b/view/kernelcache/core/MachO.cpp
@@ -0,0 +1,714 @@
+#include "MachO.h"
+#include "Utility.h"
+
+#include "KernelCache.h"
+
+using namespace BinaryNinja;
+
+std::vector<uint64_t> KernelCacheMachOHeader::ReadFunctionTable(Ref<BinaryView> bv) const
+{
+ // NOTE: The funcoff is relative to the file of the linkedit segment.
+ uint64_t funcStartsAddress = functionStarts.funcoff;
+ auto funcStarts = bv->GetParentView()->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);
+ curfunc += curOffset;
+ uint64_t target = curfunc;
+ functionTable.push_back(target);
+ }
+ return functionTable;
+}
+
+std::optional<KernelCacheMachOHeader> KernelCacheMachOHeader::ParseHeaderForAddress(
+ Ref<BinaryView> bv, uint64_t vmAddress, uint64_t fileAddress, const std::string& imagePath)
+{
+ KernelCacheMachOHeader header;
+
+ header.textBase = vmAddress;
+ 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;
+ BinaryReader reader(bv->GetParentView());
+ reader.Seek(fileAddress);
+
+ header.ident.magic = reader.Read32();
+
+ 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.Read32();
+ header.ident.cpusubtype = reader.Read32();
+ header.ident.filetype = reader.Read32();
+ header.ident.ncmds = reader.Read32();
+ header.ident.sizeofcmds = reader.Read32();
+ header.ident.flags = reader.Read32();
+ if ((header.ident.cputype & MachOABIMask) == MachOABI64) // address size == 8
+ {
+ header.ident.reserved = reader.Read32();
+ }
+ header.loadCommandOffset = reader.GetOffset();
+
+ bool first = true;
+ // Parse segment commands
+ try
+ {
+ for (size_t i = 0; i < header.ident.ncmds; i++)
+ {
+ // BNLogInfo("of 0x%llx", reader.GetOffset());
+ load_command load;
+ segment_command_64 segment64;
+ section_64 sect = {};
+ size_t curOffset = reader.GetOffset();
+ load.cmd = reader.Read32();
+ load.cmdsize = reader.Read32();
+ size_t nextOffset = curOffset + load.cmdsize;
+ if (load.cmdsize < sizeof(load_command))
+ return {};
+
+ switch (load.cmd)
+ {
+ case LC_MAIN:
+ {
+ uint64_t entryPoint = reader.Read64();
+ header.entryPoints.push_back({entryPoint, true});
+ (void)reader.Read64(); // Stack start
+ break;
+ }
+ case LC_SEGMENT: // map the 32bit version to 64 bits
+ segment64.cmd = LC_SEGMENT_64;
+ reader.Read(&segment64.segname, 16);
+ segment64.vmaddr = reader.Read32();
+ segment64.vmsize = reader.Read32();
+ segment64.fileoff = reader.Read32();
+ segment64.filesize = reader.Read32();
+ segment64.maxprot = reader.Read32();
+ segment64.initprot = reader.Read32();
+ segment64.nsects = reader.Read32();
+ segment64.flags = reader.Read32();
+ if (first)
+ {
+ if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64)
+ || (segment64.flags & MACHO_VM_PROT_WRITE))
+ {
+ header.relocationBase = segment64.vmaddr;
+ first = false;
+ }
+ }
+ for (size_t j = 0; j < segment64.nsects; j++)
+ {
+ reader.Read(&sect.sectname, 16);
+ reader.Read(&sect.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(&sect.sectname, 16);
+ reader.Read(&sect.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[sizeof(section.segname)+1];
+ memcpy(segmentName, section.segname, sizeof(section.segname));
+ segmentName[sizeof(segmentName)-1] = 0;
+
+ // Section names used to be image name and section only but some images have duplicate section names
+ // so we now also use the segment name, this also is more close to what is seen with LLVM.
+ // Justification: https://github.com/Vector35/binaryninja-api/pull/6454#issuecomment-2777465476
+ if (header.identifierPrefix.empty())
+ header.sectionNames.push_back(fmt::format("{}.{}", segmentName, sectionName));
+ else
+ header.sectionNames.push_back(fmt::format("{}::{}.{}", header.identifierPrefix, segmentName, sectionName));
+ }
+ }
+ catch (ReadException&)
+ {
+ return {};
+ }
+
+ return header;
+}
+
+std::vector<CacheSymbol> KernelCacheMachOHeader::ReadSymbolTable(Ref<BinaryView> bv, const TableInfo &symbolInfo, const TableInfo &stringInfo) const
+{
+ try {
+ BinaryReader reader(bv->GetParentView());
+ std::vector<CacheSymbol> symbolList;
+ // TODO: This assumes that 95% (or more) are going to be added.
+ symbolList.reserve(symbolInfo.entries);
+ for (uint64_t entryIndex = 0; entryIndex < symbolInfo.entries; entryIndex++)
+ {
+ nlist_64 nlist = {};
+ if (bv->GetAddressSize() == 4)
+ {
+ // 32-bit KC
+ struct nlist nlist32 = {};
+ reader.Seek(symbolInfo.address + (entryIndex * sizeof(nlist32)));
+ reader.Read(&nlist, 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 KC
+ reader.Seek(symbolInfo.address + (entryIndex * sizeof(nlist)));
+ reader.Read(&nlist, sizeof(nlist));
+ }
+
+ auto symbolAddress = nlist.n_value;
+ if (((nlist.n_type & N_TYPE) == N_INDR) || symbolAddress == 0)
+ continue;
+
+ if (nlist.n_strx >= stringInfo.entries)
+ {
+ // TODO: where logger?
+ LogError(
+ "Symbol entry at index %llu has a string offset of %u which is outside the strings buffer of size %llu "
+ "for symbol table %x",
+ entryIndex, nlist.n_strx, stringInfo.address, stringInfo.entries);
+ continue;
+ }
+
+ reader.Seek(stringInfo.address + nlist.n_strx);
+ std::string symbolName = reader.ReadCString();
+ 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;
+ }
+ catch (ReadException& ex) {
+ LogError("Failed to read symbol table: %s", ex.what());
+ return {};
+ }
+
+}
+
+bool KernelCacheMachOHeader::AddExportTerminalSymbol(
+ std::vector<CacheSymbol>& symbols, 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 false;
+
+ uint64_t imageOffset = readValidULEB128(current, end);
+ uint64_t symbolAddress = textBase + imageOffset;
+ if (symbolName.empty() || symbolAddress == 0)
+ return false;
+
+ // 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:
+ symbols.emplace_back(sectionSymbolType(), symbolAddress, symbolName);
+ break;
+ case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE:
+ symbols.emplace_back(DataSymbol, symbolAddress, symbolName);
+ break;
+ default:
+ LogWarn("Unhandled export symbol kind: %llx", symbolFlags & EXPORT_SYMBOL_FLAGS_KIND_MASK);
+ return false;
+ }
+
+ return true;
+}
+
+std::vector<CacheSymbol> KernelCacheMachOHeader::ReadExportSymbolTrie(Ref<BinaryView> bv) const
+{
+ // nothing to do if there’s no export‐trie
+ if (!exportTriePresent || exportTrie.datasize == 0 || exportTrie.dataoff == 0)
+ return {};
+ std::vector<CacheSymbol> symbols = {};
+ try {
+ DataBuffer exportTrieBuffer = bv->GetParentView()->ReadBuffer(exportTrie.dataoff, exportTrie.datasize);
+ const uint8_t* begin = static_cast<const uint8_t*>(exportTrieBuffer.GetData());
+ const uint8_t* end = begin + exportTrieBuffer.GetLength();
+ const uint8_t *cursor = begin;
+
+ struct Node
+ {
+ const uint8_t* cursor;
+ std::string text;
+ };
+ std::vector<Node> stack;
+ stack.reserve(64);
+ stack.push_back({ /* cursor */ begin, /* text */ "" });
+
+ while (!stack.empty())
+ {
+ Node node = std::move(stack.back());
+ stack.pop_back();
+
+ cursor = node.cursor;
+ const std::string currentText = std::move(node.text);
+
+ if (cursor > end)
+ {
+ LogError("Export Trie: Cursor left trie during initial bounds check");
+ throw ReadException();
+ }
+
+ uint64_t terminalSize = readValidULEB128(cursor, end);
+ const uint8_t* childCursor = cursor + terminalSize;
+
+ // If there's terminal data, define the symbol
+ if (terminalSize != 0)
+ {
+ AddExportTerminalSymbol(symbols, currentText, cursor, end);
+ }
+
+ cursor = childCursor;
+ if (cursor > end)
+ {
+ LogError("Export Trie: Cursor left trie while moving to child offset");
+ throw ReadException();
+ }
+
+ uint8_t childCount = *cursor;
+ cursor++;
+ if (cursor > end)
+ {
+ LogError("Export Trie: Cursor left trie while reading child count");
+ throw ReadException();
+ }
+
+ std::vector<Node> children;
+ children.reserve(childCount);
+ for (uint8_t i = 0; i < childCount; ++i)
+ {
+ if (cursor > end)
+ {
+ LogError("Export Trie: Cursor left trie while reading children");
+ throw ReadException();
+ }
+
+ std::string childText;
+ while (cursor <= end && *cursor != 0) {
+ childText.push_back(*cursor);
+ cursor++;
+ }
+ cursor++; // skip the `\0`
+ if (cursor > end)
+ {
+ LogError("Export Trie: Cursor left trie while reading child text");
+ throw ReadException();
+ }
+
+ uint64_t nextOffset = readValidULEB128(cursor, end);
+ if (nextOffset == 0)
+ {
+ LogError("Export Trie: Child offset is zero");
+ throw ReadException();
+ }
+
+ children.push_back({ begin + nextOffset, currentText + childText });
+ }
+
+ // Push in reverse so that the first child is processed next
+ for (auto it = children.rbegin(); it != children.rend(); ++it)
+ {
+ stack.push_back(*it);
+ }
+ }
+ }
+ catch (ReadException&)
+ {
+ LogError("Export trie is malformed. Could not load Exported symbol names.");
+ }
+
+ return symbols;
+}
diff --git a/view/kernelcache/core/MachO.h b/view/kernelcache/core/MachO.h
new file mode 100644
index 00000000..8c2a7709
--- /dev/null
+++ b/view/kernelcache/core/MachO.h
@@ -0,0 +1,79 @@
+#pragma once
+
+// TODO: Including this adds a bunch of binary ninja specific stuff :ugh:
+#include "view/macho/machoview.h"
+
+struct CacheSymbol;
+
+// Used when reading symbol/string table info.
+struct TableInfo
+{
+ // VM address where the reading will begin.
+ uint64_t address;
+ // Number of entries in the table.
+ uint32_t entries;
+};
+
+struct KernelCacheMachOHeader
+{
+ 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;
+
+ static std::optional<KernelCacheMachOHeader> ParseHeaderForAddress(
+ BinaryNinja::Ref<BinaryNinja::BinaryView> bv, uint64_t vmAddress, uint64_t fileAddress, const std::string& imagePath);
+
+ std::vector<CacheSymbol> ReadSymbolTable(BinaryNinja::Ref<BinaryNinja::BinaryView> bv, const TableInfo &symbolInfo, const TableInfo &stringInfo) const;
+
+ bool AddExportTerminalSymbol(
+ std::vector<CacheSymbol>& symbols, 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(BinaryNinja::Ref<BinaryNinja::BinaryView> bv) const;
+
+ std::vector<uint64_t> ReadFunctionTable(BinaryNinja::Ref<BinaryNinja::BinaryView> bv) const;
+};
diff --git a/view/kernelcache/core/MachOProcessor.cpp b/view/kernelcache/core/MachOProcessor.cpp
new file mode 100644
index 00000000..e22ce853
--- /dev/null
+++ b/view/kernelcache/core/MachOProcessor.cpp
@@ -0,0 +1,332 @@
+#include "MachOProcessor.h"
+#include "KernelCache.h"
+
+
+using namespace BinaryNinja;
+
+KernelCacheMachOProcessor::KernelCacheMachOProcessor(Ref<BinaryView> view)
+{
+ m_view = view;
+ m_logger = new Logger("KernelCache.MachOProcessor", view->GetFile()->GetSessionId());
+
+ // Adjust processor settings.
+ if (Ref<Settings> settings = m_view->GetLoadSettings(KC_VIEW_NAME))
+ {
+ if (settings->Contains("loader.kc.processFunctionStarts"))
+ m_applyFunctions = settings->Get<bool>("loader.kc.processFunctionStarts", m_view);
+ }
+}
+
+void KernelCacheMachOProcessor::ApplyHeader(const KernelCache& cache, KernelCacheMachOHeader& header)
+{
+ auto typeLibraryFromName = [&](const std::string& name) -> Ref<TypeLibrary> {
+ // Check to see if we have already loaded the type library.
+ if (auto typeLib = m_view->GetTypeLibrary(name))
+ return typeLib;
+
+ auto typeLibs = m_view->GetDefaultPlatform()->GetTypeLibrariesByName(name);
+ if (!typeLibs.empty())
+ return typeLibs.front();
+ return nullptr;
+ };
+
+ // Add a section for the header itself.
+ std::string headerSection = fmt::format("{}::__macho_header", header.identifierPrefix);
+ uint64_t machHeaderSize = m_view->GetAddressSize() == 8 ? sizeof(mach_header_64) : sizeof(mach_header);
+ uint64_t headerSectionSize = machHeaderSize + header.ident.sizeofcmds;
+ m_view->AddUserSection(headerSection, header.textBase, headerSectionSize, ReadOnlyDataSectionSemantics);
+
+ ApplyHeaderSections(header);
+ ApplyHeaderDataVariables(header);
+
+ // Pull the available type library for the image we are loading, so we can apply known types.
+ auto typeLib = typeLibraryFromName(header.installName);
+
+ if (m_applyFunctions && header.functionStartsPresent)
+ {
+ auto targetPlatform = m_view->GetDefaultPlatform();
+ auto functions = header.ReadFunctionTable(m_view);
+ for (const auto& func : functions)
+ m_view->AddFunctionForAnalysis(targetPlatform, func, false);
+ }
+
+ m_view->BeginBulkModifySymbols();
+
+ // Apply symbols from symbol table.
+ if (header.symtab.symoff != 0)
+ {
+ // NOTE: This table is read relative to the link edit segment file base.
+ // NOTE: This does not handle the shared .symbols cache entry symbols, that is the responsibility of the caller.
+ TableInfo symbolInfo = { header.symtab.symoff, header.symtab.nsyms };
+ TableInfo stringInfo = { header.symtab.stroff, header.symtab.strsize };
+ const auto symbols = header.ReadSymbolTable(m_view, symbolInfo, stringInfo);
+ for (const auto& sym : symbols)
+ {
+ auto [symbol, symbolType] = sym.GetBNSymbolAndType(*m_view);
+ ApplySymbol(m_view, typeLib, symbol, symbolType);
+ }
+ }
+
+ // Apply symbols from export trie.
+ if (header.exportTriePresent)
+ {
+ // NOTE: This table is read relative to the link edit segment file base.
+ const auto exportSymbols = header.ReadExportSymbolTrie(m_view);
+ for (const auto& sym : exportSymbols)
+ {
+ auto [symbol, symbolType] = sym.GetBNSymbolAndType(*m_view);
+ ApplySymbol(m_view, typeLib, symbol, symbolType);
+ }
+ }
+ m_view->EndBulkModifySymbols();
+}
+
+uint64_t KernelCacheMachOProcessor::ApplyHeaderSections(KernelCacheMachOHeader& 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.sectname, "__auth_got", sizeof(section.sectname)) == 0)
+ semantics = ReadOnlyDataSectionSemantics;
+ if (strncmp(section.segname, "__DATA_CONST", sizeof(section.segname)) == 0)
+ semantics = ReadOnlyDataSectionSemantics;
+
+ // Typically a view would add auto sections but those won't persist when loading the BNDB.
+ // if we want to use an auto section here we would need to allow the core to apply auto sections from the database.
+ 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 KernelCacheMachOProcessor::ApplyHeaderDataVariables(KernelCacheMachOHeader& 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/kernelcache/core/MachOProcessor.h b/view/kernelcache/core/MachOProcessor.h
new file mode 100644
index 00000000..3e353a44
--- /dev/null
+++ b/view/kernelcache/core/MachOProcessor.h
@@ -0,0 +1,22 @@
+#pragma once
+#include "MachO.h"
+#include "KernelCache.h"
+
+// Process `KernelCacheMachOHeader`.
+class KernelCacheMachOProcessor
+{
+ BinaryNinja::Ref<BinaryNinja::BinaryView> m_view;
+ BinaryNinja::Ref<BinaryNinja::Logger> m_logger;
+
+ bool m_applyFunctions = true;
+
+public:
+ explicit KernelCacheMachOProcessor(BinaryNinja::Ref<BinaryNinja::BinaryView> view);
+
+ // Initialize header information such as sections and symbols.
+ void ApplyHeader(const KernelCache& cache, KernelCacheMachOHeader& header);
+
+ uint64_t ApplyHeaderSections(KernelCacheMachOHeader& header);
+
+ void ApplyHeaderDataVariables(KernelCacheMachOHeader& header);
+};
diff --git a/view/kernelcache/core/MetadataSerializable.cpp b/view/kernelcache/core/MetadataSerializable.cpp
deleted file mode 100644
index dea6243e..00000000
--- a/view/kernelcache/core/MetadataSerializable.cpp
+++ /dev/null
@@ -1,552 +0,0 @@
-#include "MetadataSerializable.hpp"
-
-namespace KernelCacheCore {
-
- 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 KernelCacheCore
diff --git a/view/kernelcache/core/MetadataSerializable.hpp b/view/kernelcache/core/MetadataSerializable.hpp
deleted file mode 100644
index 5397a56c..00000000
--- a/view/kernelcache/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 KERNELCACHE_CORE_METADATASERIALIZABLE_HPP
-#define KERNELCACHE_CORE_METADATASERIALIZABLE_HPP
-
-#include <cassert>
-#include "binaryninjaapi.h"
-#include "rapidjson/document.h"
-#include "rapidjson/stringbuffer.h"
-#include "rapidjson/prettywriter.h"
-#include "../api/kernelcachecore.h"
-#include "view/macho/machoview.h"
-
-using namespace BinaryNinja;
-
-namespace KernelCacheCore {
-
-#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.
-
- KERNELCACHE_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();
- }
-
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, const char*);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, bool b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, bool& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, uint8_t b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint8_t& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, uint16_t b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint16_t& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, uint32_t b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint32_t& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, uint64_t b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, uint64_t& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, int8_t b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int8_t& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, int16_t b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int16_t& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, int32_t b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int32_t& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, int64_t b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, int64_t& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, std::string_view b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext& context, const std::pair<uint64_t, std::pair<uint64_t, uint64_t>>& value);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::string& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::map<uint64_t, std::string>& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, std::string>& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<uint64_t, uint64_t>& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::unordered_map<uint64_t, uint64_t>>& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, std::string>& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::string>& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, std::pair<uint64_t, uint64_t>>>& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<std::pair<uint64_t, bool>>& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::vector<uint64_t>& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext& context, std::string_view name, std::unordered_map<std::string, uint64_t>& b);
- KERNELCACHE_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);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const mach_header_64& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, mach_header_64& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const symtab_command& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, symtab_command& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const dysymtab_command& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, dysymtab_command& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const dyld_info_command& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, dyld_info_command& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const routines_command_64& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, routines_command_64& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const function_starts_command& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, function_starts_command& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const section_64& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<section_64>& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const linkedit_data_command& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, linkedit_data_command& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const segment_command_64& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, segment_command_64& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<segment_command_64>& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const build_version_command& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, build_version_command& b);
- KERNELCACHE_FFI_API void Serialize(SerializationContext&, const build_tool_version& b);
- KERNELCACHE_FFI_API void Deserialize(DeserializationContext&, std::string_view name, std::vector<build_tool_version>& b);
-
-} // namespace SharedCacheCore
-
-#endif // KERNELCACHE_METADATASERIALIZABLE_HPP
diff --git a/view/kernelcache/core/Utility.cpp b/view/kernelcache/core/Utility.cpp
new file mode 100644
index 00000000..6f8323bf
--- /dev/null
+++ b/view/kernelcache/core/Utility.cpp
@@ -0,0 +1,159 @@
+#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<Type> type)
+{
+ auto symbolAddress = symbol->GetAddress();
+ auto symbolName = symbol->GetFullName();
+
+ // Sometimes the symbol will be duplicated, so lets not do this work again.
+ if (view->GetSymbolByAddress(symbolAddress))
+ return;
+
+ // Define the symbol!
+ view->DefineAutoSymbol(symbol);
+
+ // Try and pull a type from a type library to apply at the symbol location.
+ // The type library type will take precedence over the passed in type.
+ Ref<Type> selectedType = type;
+ if (typeLib)
+ selectedType = view->ImportTypeLibraryObject(typeLib, {symbolName});
+
+ Ref<Function> func = nullptr;
+ if (symbol->GetType() == FunctionSymbol)
+ {
+ Ref<Platform> targetPlatform = view->GetDefaultPlatform();
+ // Make sure to check for already added function from the function table.
+ // Unless we have retrieved a type here we don't need to make a new function.
+ func = view->GetAnalysisFunction(targetPlatform, symbolAddress);
+ if (!func || selectedType != nullptr)
+ func = view->AddFunctionForAnalysis(targetPlatform, symbolAddress, false, selectedType);
+ // The above function might be overwritten so we also want to apply the type here.
+ if (func && selectedType != nullptr)
+ func->ApplyAutoDiscoveredType(selectedType);
+ }
+ else
+ {
+ // Other symbol types can just use this, they don't need to worry about linear sweep removing them.
+ view->DefineAutoSymbolAndVariableOrFunction(view->GetDefaultPlatform(), symbol, selectedType);
+ }
+
+ if (func)
+ {
+ // objective c type adjustment stuff.
+ 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;
+}
+
+bool IsSameFolderForFile(Ref<ProjectFile> a, Ref<ProjectFile> b)
+{
+ if (!a && !b)
+ return true;
+ if (a && b)
+ return IsSameFolder(a->GetFolder(), b->GetFolder());
+ return false;
+}
+
+bool IsSameFolder(Ref<ProjectFolder> a, Ref<ProjectFolder> b)
+{
+ if (!a && !b)
+ return true;
+ if (a && b)
+ return a->GetId() == b->GetId();
+ return false;
+}
diff --git a/view/kernelcache/core/Utility.h b/view/kernelcache/core/Utility.h
new file mode 100644
index 00000000..5664fd9b
--- /dev/null
+++ b/view/kernelcache/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, BinaryNinja::Ref<BinaryNinja::Type> type = nullptr);
+
+// Returns the "image name" for a given path.
+// /blah/foo/bar/libObjCThing.dylib -> libObjCThing.dylib
+std::string BaseFileName(const std::string& path);
+
+bool IsSameFolderForFile(BinaryNinja::Ref<BinaryNinja::ProjectFile> a, BinaryNinja::Ref<BinaryNinja::ProjectFile> b);
+bool IsSameFolder(BinaryNinja::Ref<BinaryNinja::ProjectFolder> a, BinaryNinja::Ref<BinaryNinja::ProjectFolder> b);
+
+// 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/kernelcache/core/ffi.cpp b/view/kernelcache/core/ffi.cpp
new file mode 100644
index 00000000..d7126894
--- /dev/null
+++ b/view/kernelcache/core/ffi.cpp
@@ -0,0 +1,213 @@
+#include "KernelCacheController.h"
+#include "../api/kernelcachecore.h"
+
+using namespace BinaryNinja;
+using namespace BinaryNinja::KC;
+
+BNKernelCacheImage ImageToApi(const CacheImage& image)
+{
+ BNKernelCacheImage apiImage;
+ apiImage.name = BNAllocString(image.path.c_str());
+ apiImage.headerVirtualAddress = image.headerVirtualAddress;
+ apiImage.headerFileAddress = image.headerFileAddress;
+ return apiImage;
+}
+
+CacheImage ImageFromApi(const BNKernelCacheImage& image)
+{
+ CacheImage apiImage;
+ apiImage.path = image.name;
+ apiImage.headerVirtualAddress = image.headerVirtualAddress;
+ apiImage.headerFileAddress = image.headerFileAddress;
+ apiImage.header = nullptr;
+ return apiImage;
+}
+
+BNKernelCacheSymbol SymbolToApi(const CacheSymbol& symbol)
+{
+ BNKernelCacheSymbol apiSymbol;
+ apiSymbol.name = BNAllocString(symbol.name.c_str());
+ apiSymbol.address = symbol.address;
+ apiSymbol.symbolType = symbol.type;
+ return apiSymbol;
+}
+
+CacheSymbol SymbolFromApi(const BNKernelCacheSymbol& apiSymbol)
+{
+ CacheSymbol symbol;
+ symbol.name = apiSymbol.name;
+ symbol.address = apiSymbol.address;
+ symbol.type = apiSymbol.symbolType;
+ return symbol;
+}
+
+extern "C"
+{
+ BNKernelCacheController* BNGetKernelCacheController(BNBinaryView* data)
+ {
+ Ref<BinaryView> view = new BinaryView(BNNewViewReference(data));
+ auto controller = KernelCacheController::FromView(*view);
+ if (!controller)
+ return nullptr;
+ return KC_API_OBJECT_REF(controller);
+ }
+
+ BNKernelCacheController* BNNewKernelCacheControllerReference(BNKernelCacheController* controller)
+ {
+ return KC_API_OBJECT_NEW_REF(controller);
+ }
+
+ void BNFreeKernelCacheControllerReference(BNKernelCacheController* controller)
+ {
+ KC_API_OBJECT_FREE(controller);
+ }
+
+ bool BNKernelCacheControllerApplyImage(
+ BNKernelCacheController* controller, BNBinaryView* data, BNKernelCacheImage* 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->headerVirtualAddress))
+ 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 BNKernelCacheControllerIsImageLoaded(BNKernelCacheController* controller, BNKernelCacheImage* image)
+ {
+ return controller->object->IsImageLoaded(ImageFromApi(*image));
+ }
+
+ bool BNKernelCacheControllerGetImageAt(
+ BNKernelCacheController* controller, uint64_t address, BNKernelCacheImage* outImage)
+ {
+ const auto image = controller->object->GetCache().GetImageAt(address);
+ if (!image)
+ return false;
+ *outImage = ImageToApi(*image);
+ return true;
+ }
+
+ bool BNKernelCacheControllerGetImageContaining(
+ BNKernelCacheController* controller, uint64_t address, BNKernelCacheImage* outImage)
+ {
+ const auto image = controller->object->GetCache().GetImageContaining(address);
+ if (!image)
+ return false;
+ *outImage = ImageToApi(*image);
+ return true;
+ }
+
+ bool BNKernelCacheControllerGetImageWithName(
+ BNKernelCacheController* controller, const char* name, BNKernelCacheImage* outImage)
+ {
+ const auto image = controller->object->GetCache().GetImageWithName(name);
+ if (!image)
+ return false;
+ *outImage = ImageToApi(*image);
+ return true;
+ }
+
+ char** BNKernelCacheControllerGetImageDependencies(
+ BNKernelCacheController* controller, BNKernelCacheImage* 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->headerFileAddress);
+ 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());
+ }
+
+ BNKernelCacheImage* BNKernelCacheControllerGetImages(BNKernelCacheController* controller, size_t* count)
+ {
+ const auto& images = controller->object->GetCache().GetImages();
+ *count = images.size();
+ BNKernelCacheImage* apiImages = new BNKernelCacheImage[*count];
+ size_t idx = 0;
+ for (const auto& [_, image] : images)
+ apiImages[idx++] = ImageToApi(image);
+ return apiImages;
+ }
+
+ BNKernelCacheImage* BNKernelCacheControllerGetLoadedImages(BNKernelCacheController* controller, size_t* count)
+ {
+ const 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();
+ BNKernelCacheImage* apiImages = new BNKernelCacheImage[*count];
+ for (size_t i = 0; i < *count; i++)
+ apiImages[i] = ImageToApi(loadedImages[i]);
+ return apiImages;
+ }
+
+ void BNKernelCacheFreeImage(BNKernelCacheImage image)
+ {
+ BNFreeString(image.name);
+ }
+
+ void BNKernelCacheFreeImageList(BNKernelCacheImage* images, size_t count)
+ {
+ for (size_t i = 0; i < count; i++)
+ BNKernelCacheFreeImage(images[i]);
+ delete[] images;
+ }
+
+ bool BNKernelCacheControllerGetSymbolAt(
+ BNKernelCacheController* controller, uint64_t address, BNKernelCacheSymbol* outSymbol)
+ {
+ const auto symbol = controller->object->GetCache().GetSymbolAt(address);
+ if (!symbol)
+ return false;
+ *outSymbol = SymbolToApi(*symbol);
+ return true;
+ }
+
+ bool BNKernelCacheControllerGetSymbolWithName(
+ BNKernelCacheController* controller, const char* name, BNKernelCacheSymbol* outSymbol)
+ {
+ const auto symbol = controller->object->GetCache().GetSymbolWithName(name);
+ if (!symbol)
+ return false;
+ *outSymbol = SymbolToApi(*symbol);
+ return true;
+ }
+
+ BNKernelCacheSymbol* BNKernelCacheControllerGetSymbols(BNKernelCacheController* controller, size_t* count)
+ {
+ const auto& symbols = controller->object->GetCache().GetSymbols();
+ *count = symbols.size();
+ BNKernelCacheSymbol* apiSymbols = new BNKernelCacheSymbol[*count];
+ size_t idx = 0;
+ for (const auto& [_, symbol] : symbols)
+ apiSymbols[idx++] = SymbolToApi(symbol);
+ return apiSymbols;
+ }
+
+
+ void BNKernelCacheFreeSymbol(BNKernelCacheSymbol symbol)
+ {
+ BNFreeString(symbol.name);
+ }
+
+ void BNKernelCacheFreeSymbolList(BNKernelCacheSymbol* symbols, size_t count)
+ {
+ for (size_t i = 0; i < count; i++)
+ BNKernelCacheFreeSymbol(symbols[i]);
+ delete[] symbols;
+ }
+};
diff --git a/view/kernelcache/core/ffi_global.h b/view/kernelcache/core/ffi_global.h
new file mode 100644
index 00000000..0effdb81
--- /dev/null
+++ b/view/kernelcache/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_KC_API_OBJECT(handle, cls) \
+ namespace BinaryNinja::KC { \
+ class cls; \
+ } \
+ struct handle \
+ { \
+ BinaryNinja::KC::cls* object; \
+ }
+#define IMPLEMENT_KC_API_OBJECT(handle) \
+\
+private: \
+ handle m_apiObject; \
+\
+public: \
+ typedef handle* APIHandle; \
+ handle* GetAPIObject() \
+ { \
+ return &m_apiObject; \
+ } \
+\
+private:
+#define INIT_KC_API_OBJECT() m_apiObject.object = this;
diff --git a/view/kernelcache/core/refcountobject.h b/view/kernelcache/core/refcountobject.h
new file mode 100644
index 00000000..06996da9
--- /dev/null
+++ b/view/kernelcache/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::KC {
+ class KCRefCountObject
+ {
+ // CORE_ALLOCATED_CLASS(RefCountObject)
+
+ public:
+ std::atomic<int> m_refs;
+
+ KCRefCountObject() : m_refs(0) {}
+
+ virtual ~KCRefCountObject() {}
+
+ 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 KCRef
+ {
+ T* m_obj;
+#ifdef BN_REF_COUNT_DEBUG
+ void* m_assignmentTrace = nullptr;
+#endif
+
+ public:
+ KCRef() : m_obj(NULL) {}
+
+ KCRef(T* obj) : m_obj(obj)
+ {
+ if (m_obj)
+ {
+ m_obj->AddRef();
+#ifdef BN_REF_COUNT_DEBUG
+ m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name());
+#endif
+ }
+ }
+
+ KCRef(const KCRef& obj) : m_obj(obj.m_obj)
+ {
+ if (m_obj)
+ {
+ m_obj->AddRef();
+#ifdef BN_REF_COUNT_DEBUG
+ m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name());
+#endif
+ }
+ }
+
+ ~KCRef()
+ {
+ if (m_obj)
+ {
+ m_obj->Release();
+#ifdef BN_REF_COUNT_DEBUG
+ BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace);
+#endif
+ }
+ }
+
+ // move constructor
+ KCRef(KCRef&& 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;
+ // }
+
+ KCRef<T>& operator=(const KCRef<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;
+ }
+
+ KCRef<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 KCRef<T>& obj) const { return m_obj == obj.m_obj; }
+
+ bool operator!=(const KCRef<T>& obj) const { return m_obj != obj.m_obj; }
+
+ bool operator<(const KCRef<T>& obj) const { return m_obj < obj.m_obj; }
+
+ template <typename H>
+ friend H AbslHashValue(H h, const KCRef<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 KC_API_OBJECT_REF(T* obj)
+ {
+ if (obj == nullptr)
+ return nullptr;
+ obj->AddAPIRef();
+ return obj->GetAPIObject();
+ }
+
+ template <class T>
+ static typename T::APIHandle KC_API_OBJECT_REF(const KCRef<T>& obj)
+ {
+ if (!obj)
+ return nullptr;
+ obj->AddAPIRef();
+ return obj->GetAPIObject();
+ }
+
+ // template <class T>
+ // static typename T::APIHandle KC_API_OBJECT_REF(const APIRef<T>& obj)
+ //{
+ // if (!obj)
+ // return nullptr;
+ // obj->AddAPIRef();
+ // return obj->GetAPIObject();
+ // }
+
+ template <class T>
+ static T* KC_API_OBJECT_NEW_REF(T* obj)
+ {
+ if (obj)
+ obj->object->AddAPIRef();
+ return obj;
+ }
+
+ template <class T>
+ static void KC_API_OBJECT_FREE(T* obj)
+ {
+ if (obj)
+ obj->object->ReleaseAPIRef();
+ }
+}; // namespace BinaryNinja::KC