diff options
| author | kat <kat@vector35.com> | 2025-07-06 15:05:01 -0400 |
|---|---|---|
| committer | kat <kat@vector35.com> | 2025-07-07 07:37:23 -0400 |
| commit | 768f7c78465fb93936e5ca50a0ca712664fe54e7 (patch) | |
| tree | 78c73e0022d6006882e365a257595a5b55a37432 | |
| parent | 8f3e251c42169fb4fe8db9403d900571434d88ff (diff) | |
KernelCache rewrite
33 files changed, 4039 insertions, 4768 deletions
diff --git a/python/scriptingprovider.py b/python/scriptingprovider.py index e3ca09f6..3b7747bb 100644 --- a/python/scriptingprovider.py +++ b/python/scriptingprovider.py @@ -901,6 +901,8 @@ from binaryninja import * settings = Settings() if settings.contains('corePlugins.view.sharedCache') and settings.get_bool('corePlugins.view.sharedCache'): from .sharedcache import SharedCacheController + if settings.contains('corePlugins.view.kernelCache') and settings.get_bool('corePlugins.view.kernelCache'): + from .kernelcache import KernelCacheController if os.environ.get('BN_STANDALONE_DEBUGGER'): # By the time this scriptingprovider.py file is imported, the user plugins are not loaded yet. # So `from debugger import DebuggerController` would not work. diff --git a/view/kernelcache/HeadlessPlugin.cpp b/view/kernelcache/HeadlessPlugin.cpp index d14ccd6d..63f82d4d 100644 --- a/view/kernelcache/HeadlessPlugin.cpp +++ b/view/kernelcache/HeadlessPlugin.cpp @@ -1,11 +1,10 @@ #include <binaryninjaapi.h> -#include "KCView.h" -#include "KernelCache.h" +#include "KernelCacheView.h" +#include "transformers/KernelCacheTransforms.h" #ifdef __cplusplus extern "C" { #endif - // extern void RegisterSharedCacheWorkflow(); #ifdef __cplusplus } #endif @@ -16,8 +15,8 @@ extern "C" BINARYNINJAPLUGIN bool CorePluginInit() { - InitKernelcache(); - // RegisterSharedCacheWorkflow(); + KernelCacheViewType::Register(); + RegisterTransformers(); return true; } }
\ No newline at end of file diff --git a/view/kernelcache/api/kernelcache.cpp b/view/kernelcache/api/kernelcache.cpp index 7a650660..30f40656 100644 --- a/view/kernelcache/api/kernelcache.cpp +++ b/view/kernelcache/api/kernelcache.cpp @@ -4,191 +4,192 @@ #include "kernelcacheapi.h" -namespace KernelCacheAPI { +using namespace BinaryNinja; +using namespace KernelCacheAPI; - KernelCache::KernelCache(Ref<BinaryView> view) { - m_object = BNGetKernelCache(view->GetObject()); - } +BNKernelCacheImage ImageToApi(CacheImage image) +{ + BNKernelCacheImage apiImage {}; + apiImage.name = BNAllocString(image.name.c_str()); + apiImage.headerFileAddress = image.headerFileAddress; + apiImage.headerVirtualAddress = image.headerVirtualAddress; + return apiImage; +} - BNKCViewLoadProgress KernelCache::GetLoadProgress(Ref<BinaryView> view) - { - return BNKCViewGetLoadProgress(view->GetFile()->GetSessionId()); - } +CacheImage ImageFromApi(BNKernelCacheImage image) +{ + CacheImage apiImage {}; + apiImage.name = image.name; + apiImage.headerVirtualAddress = image.headerVirtualAddress; + apiImage.headerFileAddress = image.headerFileAddress; + return apiImage; +} - uint64_t KernelCache::FastGetImageCount(Ref<BinaryView> view) - { - return BNKCViewFastGetImageCount(view->GetObject()); - } +CacheSymbol SymbolFromApi(BNKernelCacheSymbol apiSymbol) +{ + CacheSymbol symbol; + symbol.name = apiSymbol.name; + symbol.address = apiSymbol.address; + symbol.type = apiSymbol.symbolType; + return symbol; +} - bool KernelCache::LoadImageWithInstallName(const std::string& installName) - { - return BNKCViewLoadImageWithInstallName(m_object, installName.c_str()); - } +std::pair<std::string, Ref<Type>> CacheSymbol::DemangledName(BinaryView &view) const +{ + QualifiedName qname; + Ref<Type> outType = nullptr; + std::string shortName = name; + if (DemangleGeneric(view.GetDefaultArchitecture(), name, outType, qname, &view, true)) + shortName = qname.GetString(); + return {shortName, outType}; +} - bool KernelCache::LoadImageContainingAddress(uint64_t addr) - { - return BNKCViewLoadImageContainingAddress(m_object, addr); - } +Ref<Symbol> CacheSymbol::GetBNSymbol(BinaryView &view) const +{ + auto [shortName, _] = DemangledName(view); + return new Symbol(type, shortName, shortName, name, address, nullptr); +} - std::vector<std::string> KernelCache::GetAvailableImages() +std::string KernelCacheAPI::GetSymbolTypeAsString(const BNSymbolType &type) +{ + // NOTE: We currently only use the function and data symbol for cache symbols. + // update this if that changes. + switch (type) { - size_t count; - char** value = BNKCViewGetInstallNames(m_object, &count); - if (value == nullptr) - { - return {}; - } - - std::vector<std::string> result; - for (size_t i = 0; i < count; i++) - { - result.push_back(value[i]); - } - - BNFreeStringList(value, count); - return result; + case FunctionSymbol: + return "Function"; + case DataSymbol: + return "Data"; + default: + return "Unknown"; } +} - bool KernelCache::IsImageLoaded(const uint64_t address) const - { - return BNKCViewIsImageLoaded(m_object, address); - } +KernelCacheController::KernelCacheController(BNKernelCacheController *controller) +{ + m_object = controller; +} - std::vector<KCImage> KernelCache::GetImages() - { - size_t count; - BNKCImage* value = BNKCViewGetAllImages(m_object, &count); - if (value == nullptr) - { - return {}; - } +KCRef<KernelCacheController> KernelCacheController::GetController(BinaryView &view) +{ + BNKernelCacheController *controller = BNGetKernelCacheController(view.GetObject()); + if (controller == nullptr) + return nullptr; + return new KernelCacheController(controller); +} - std::vector<KCImage> result; - for (size_t i = 0; i < count; i++) - { - KCImage img; - img.name = value[i].name; - img.headerFileAddress = value[i].headerFileAddress; - for (size_t j = 0; j < value[i].mappingCount; j++) - { - KCImageMemoryMapping mapping; - mapping.name = value[i].mappings[j].name; - mapping.vmAddress = value[i].mappings[j].vmAddress; - mapping.rawViewOffset = value[i].mappings[j].rawViewOffset; - mapping.size = value[i].mappings[j].size; - mapping.loaded = value[i].mappings[j].loaded; - img.mappings.push_back(mapping); - } - result.push_back(img); - } +bool KernelCacheController::ApplyImage(BinaryView &view, const CacheImage &image) +{ + auto apiImage = ImageToApi(image); + bool result = BNKernelCacheControllerApplyImage(m_object, view.GetObject(), &apiImage); + BNKernelCacheFreeImage(apiImage); + return result; +} - BNKCViewFreeAllImages(value, count); - return result; - } +bool KernelCacheController::IsImageLoaded(const CacheImage &image) const +{ + auto apiImage = ImageToApi(image); + bool result = BNKernelCacheControllerIsImageLoaded(m_object, &apiImage); + BNKernelCacheFreeImage(apiImage); + return result; +} - std::vector<KCImage> KernelCache::GetLoadedImages() - { - size_t count; - auto images = BNKCViewGetLoadedImages(m_object, &count); - if (images == nullptr) - { - return {}; - } - std::vector<KCImage> result; - for (size_t i = 0; i < count; i++) - { - KCImage img; - img.name = images[i].name; - img.headerFileAddress = images[i].headerFileAddress; - for (size_t j = 0; j < images[i].mappingCount; j++) - { - KCImageMemoryMapping mapping; - mapping.name = images[i].mappings[j].name; - mapping.vmAddress = images[i].mappings[j].vmAddress; - mapping.rawViewOffset = images[i].mappings[j].rawViewOffset; - mapping.size = images[i].mappings[j].size; - mapping.loaded = images[i].mappings[j].loaded; - img.mappings.push_back(mapping); - } - result.push_back(img); - } - BNKCViewFreeAllImages(images, count); +std::optional<CacheImage> KernelCacheController::GetImageAt(uint64_t address) const +{ + BNKernelCacheImage apiImage; + if (!BNKernelCacheControllerGetImageAt(m_object, address, &apiImage)) + return std::nullopt; + CacheImage image = ImageFromApi(apiImage); + BNKernelCacheFreeImage(apiImage); + return image; +} - return result; - } +std::optional<CacheImage> KernelCacheController::GetImageContaining(uint64_t address) const +{ + BNKernelCacheImage apiImage; + if (!BNKernelCacheControllerGetImageContaining(m_object, address, &apiImage)) + return std::nullopt; + CacheImage image = ImageFromApi(apiImage); + BNKernelCacheFreeImage(apiImage); + return image; +} - std::vector<KCSymbol> KernelCache::LoadAllSymbolsAndWait() - { - size_t count; - BNKCSymbolRep* value = BNKCViewLoadAllSymbolsAndWait(m_object, &count); - if (value == nullptr) - { - return {}; - } +std::optional<CacheImage> KernelCacheController::GetImageWithName(const std::string &name) const +{ + BNKernelCacheImage apiImage; + if (!BNKernelCacheControllerGetImageWithName(m_object, name.c_str(), &apiImage)) + return std::nullopt; + CacheImage image = ImageFromApi(apiImage); + BNKernelCacheFreeImage(apiImage); + return image; +} - std::vector<KCSymbol> result; - for (size_t i = 0; i < count; i++) - { - KCSymbol sym; - sym.address = value[i].address; - sym.name = value[i].name; - sym.image = value[i].image; - result.push_back(sym); - } +std::vector<std::string> KernelCacheController::GetImageDependencies(const CacheImage &image) const +{ + size_t count; + BNKernelCacheImage apiImage = ImageToApi(image); + char **dependencies = BNKernelCacheControllerGetImageDependencies(m_object, &apiImage, &count); + BNKernelCacheFreeImage(apiImage); + std::vector<std::string> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(dependencies[i]); + BNFreeStringList(dependencies, count); + return result; +} - BNKCViewFreeSymbols(value, count); - return result; - } +std::optional<CacheSymbol> KernelCacheController::GetSymbolAt(uint64_t address) const +{ + BNKernelCacheSymbol apiSymbol; + if (!BNKernelCacheControllerGetSymbolAt(m_object, address, &apiSymbol)) + return std::nullopt; + CacheSymbol symbol = SymbolFromApi(apiSymbol); + BNKernelCacheFreeSymbol(apiSymbol); + return symbol; +} - std::string KernelCache::GetNameForAddress(uint64_t address) - { - char* name = BNKCViewGetNameForAddress(m_object, address); - if (name == nullptr) - return {}; - std::string result = name; - BNFreeString(name); - return result; - } +std::optional<CacheSymbol> KernelCacheController::GetSymbolWithName(const std::string &name) const +{ + BNKernelCacheSymbol apiSymbol; + if (!BNKernelCacheControllerGetSymbolWithName(m_object, name.c_str(), &apiSymbol)) + return std::nullopt; + CacheSymbol symbol = SymbolFromApi(apiSymbol); + BNKernelCacheFreeSymbol(apiSymbol); + return symbol; +} - std::string KernelCache::GetImageNameForAddress(uint64_t address) - { - char* name = BNKCViewGetImageNameForAddress(m_object, address); - if (name == nullptr) - return {}; - std::string result = name; - BNFreeString(name); - return result; - } +std::vector<CacheImage> KernelCacheController::GetImages() const +{ + size_t count; + BNKernelCacheImage *images = BNKernelCacheControllerGetImages(m_object, &count); + std::vector<CacheImage> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(ImageFromApi(images[i])); + BNKernelCacheFreeImageList(images, count); + return result; +} - std::optional<KernelCacheMachOHeader> KernelCache::GetMachOHeaderForImage(const std::string& name) - { - char* outputStr = BNKCViewGetImageHeaderForName(m_object, name.c_str()); - if (outputStr == nullptr) - return {}; - std::string output = outputStr; - BNFreeString(outputStr); - if (output.empty()) - return {}; - KernelCacheMachOHeader header = KernelCacheMachOHeader::LoadFromString(output); - return header; - } - - std::optional<KernelCacheMachOHeader> KernelCache::GetMachOHeaderForAddress(uint64_t address) - { - char* outputStr = BNKCViewGetImageHeaderForAddress(m_object, address); - if (outputStr == nullptr) - return {}; - std::string output = outputStr; - BNFreeString(outputStr); - if (output.empty()) - return {}; - KernelCacheMachOHeader header = KernelCacheMachOHeader::LoadFromString(output); - return header; - } - - BNKCViewState KernelCache::GetState() - { - return BNKCViewGetState(m_object); - } +std::vector<CacheImage> KernelCacheController::GetLoadedImages() const +{ + size_t count; + BNKernelCacheImage *images = BNKernelCacheControllerGetLoadedImages(m_object, &count); + std::vector<CacheImage> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(ImageFromApi(images[i])); + BNKernelCacheFreeImageList(images, count); + return result; +} -} // namespace KernelCacheAPI +std::vector<CacheSymbol> KernelCacheController::GetSymbols() const +{ + size_t count; + BNKernelCacheSymbol *symbols = BNKernelCacheControllerGetSymbols(m_object, &count); + std::vector<CacheSymbol> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.emplace_back(SymbolFromApi(symbols[i])); + BNKernelCacheFreeSymbolList(symbols, count); + return result; +} diff --git a/view/kernelcache/api/kernelcacheapi.h b/view/kernelcache/api/kernelcacheapi.h index 35c53d78..aceb30a5 100644 --- a/view/kernelcache/api/kernelcacheapi.h +++ b/view/kernelcache/api/kernelcacheapi.h @@ -1,274 +1,311 @@ #pragma once #include <binaryninjaapi.h> -#include "../core/MetadataSerializable.hpp" -#include "../api/view/macho/machoview.h" #include "kernelcachecore.h" -using namespace BinaryNinja; +template<class T> +class KCRefCountObject { + void AddRefInternal() { m_refs.fetch_add(1); } -namespace KernelCacheAPI { - template<class T> - class KCRefCountObject { - void AddRefInternal() { m_refs.fetch_add(1); } + void ReleaseInternal() { + if (m_refs.fetch_sub(1) == 1) + delete this; + } - void ReleaseInternal() { - if (m_refs.fetch_sub(1) == 1) - delete this; - } +public: + std::atomic<int> m_refs; + T *m_object; - public: - std::atomic<int> m_refs; - T *m_object; + KCRefCountObject() : m_refs(0), m_object(nullptr) {} + + virtual ~KCRefCountObject() = default; + + T *GetObject() const { return m_object; } + + static T *GetObject(KCRefCountObject *obj) { + if (!obj) + return nullptr; + return obj->GetObject(); + } - KCRefCountObject() : m_refs(0), m_object(nullptr) {} + void AddRef() { AddRefInternal(); } - virtual ~KCRefCountObject() {} + void Release() { ReleaseInternal(); } - T *GetObject() const { return m_object; } + void AddRefForRegistration() { AddRefInternal(); } +}; - static T *GetObject(KCRefCountObject *obj) { - if (!obj) - return nullptr; - return obj->GetObject(); + +template<class T, T *(*AddObjectReference)(T *), void (*FreeObjectReference)(T *)> +class KCCoreRefCountObject { + void AddRefInternal() { m_refs.fetch_add(1); } + + void ReleaseInternal() { + if (m_refs.fetch_sub(1) == 1) { + if (!m_registeredRef) + delete this; } + } - void AddRef() { AddRefInternal(); } +public: + std::atomic<int> m_refs; + bool m_registeredRef = false; + T *m_object; - void Release() { ReleaseInternal(); } + KCCoreRefCountObject() : m_refs(0), m_object(nullptr) {} - void AddRefForRegistration() { AddRefInternal(); } - }; + virtual ~KCCoreRefCountObject() = default; + T *GetObject() const { return m_object; } - template<class T, T *(*AddObjectReference)(T *), void (*FreeObjectReference)(T *)> - class KCCoreRefCountObject { - void AddRefInternal() { m_refs.fetch_add(1); } + static T *GetObject(KCCoreRefCountObject *obj) { + if (!obj) + return nullptr; + return obj->GetObject(); + } - void ReleaseInternal() { - if (m_refs.fetch_sub(1) == 1) { - if (!m_registeredRef) - delete this; - } - } + void AddRef() { + if (m_object && (m_refs != 0)) + AddObjectReference(m_object); + AddRefInternal(); + } - public: - std::atomic<int> m_refs; - bool m_registeredRef = false; - T *m_object; + void Release() { + if (m_object) + FreeObjectReference(m_object); + ReleaseInternal(); + } + + void AddRefForRegistration() { m_registeredRef = true; } - KCCoreRefCountObject() : m_refs(0), m_object(nullptr) {} + void ReleaseForRegistration() { + m_object = nullptr; + m_registeredRef = false; + if (m_refs == 0) + delete this; + } +}; - virtual ~KCCoreRefCountObject() {} +template <class T> +class KCRef +{ + T* m_obj; +#ifdef BN_REF_COUNT_DEBUG + void* m_assignmentTrace = nullptr; +#endif - T *GetObject() const { return m_object; } +public: + KCRef() : m_obj(NULL) {} - static T *GetObject(KCCoreRefCountObject *obj) { - if (!obj) - return nullptr; - return obj->GetObject(); + KCRef(T* obj) : m_obj(obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif } + } - void AddRef() { - if (m_object && (m_refs != 0)) - AddObjectReference(m_object); - AddRefInternal(); + KCRef(const KCRef<T>& obj) : m_obj(obj.m_obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif } + } - void Release() { - if (m_object) - FreeObjectReference(m_object); - ReleaseInternal(); + KCRef(KCRef<T>&& other) : m_obj(other.m_obj) + { + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + } + + ~KCRef() + { + if (m_obj) + { + m_obj->Release(); +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif } + } - void AddRefForRegistration() { m_registeredRef = true; } + KCRef<T>& operator=(const BinaryNinja::Ref<T>& obj) + { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj.m_obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T* oldObj = m_obj; + m_obj = obj.m_obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } - void ReleaseForRegistration() { - m_object = nullptr; - m_registeredRef = false; - if (m_refs == 0) - delete this; + KCRef<T>& operator=(KCRef<T>&& other) + { + if (m_obj) + { +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif + m_obj->Release(); } - }; + m_obj = other.m_obj; + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + return *this; + } - struct KCMemoryRegion { - uint64_t vmAddress; - uint64_t size; - std::string prettyName; - }; + 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; + } - struct BackingCacheMapping { - uint64_t vmAddress; - uint64_t size; - uint64_t fileOffset; - }; + operator T*() const + { + return m_obj; + } - struct BackingCache { - std::string path; - bool isPrimary; - std::vector<BackingCacheMapping> mappings; - }; + T* operator->() const + { + return m_obj; + } - struct KCImageMemoryMapping { - std::string name; - uint64_t vmAddress; - uint64_t size; - bool loaded; - uint64_t rawViewOffset; - }; + T& operator*() const + { + return *m_obj; + } - struct KCImage { - std::string name; - uint64_t headerFileAddress; - std::vector<KCImageMemoryMapping> mappings; - }; + bool operator!() const + { + return m_obj == NULL; + } - struct KCSymbol { - uint64_t address; - std::string name; - std::string image; - }; + bool operator==(const T* obj) const + { + return T::GetObject(m_obj) == T::GetObject(obj); + } - using namespace BinaryNinja; - struct KernelCacheMachOHeader : public KernelCacheCore::MetadataSerializable<KernelCacheMachOHeader> { - uint64_t textBase = 0; - uint64_t loadCommandOffset = 0; - mach_header_64 ident; - std::string identifierPrefix; - std::string installName; + bool operator==(const KCRef<T>& obj) const + { + return T::GetObject(m_obj) == T::GetObject(obj.m_obj); + } - std::vector<std::pair<uint64_t, bool>> entryPoints; - std::vector<uint64_t> m_entryPoints; //list of entrypoints + bool operator!=(const T* obj) const + { + return T::GetObject(m_obj) != T::GetObject(obj); + } - symtab_command symtab; - dysymtab_command dysymtab; - dyld_info_command dyldInfo; - routines_command_64 routines64; - function_starts_command functionStarts; - std::vector<section_64> moduleInitSections; - linkedit_data_command exportTrie; - linkedit_data_command chainedFixups {}; + bool operator!=(const KCRef<T>& obj) const + { + return T::GetObject(m_obj) != T::GetObject(obj.m_obj); + } - uint64_t relocationBase; - // Section and program headers, internally use 64-bit form as it is a superset of 32-bit - std::vector<segment_command_64> segments; //only three types of sections __TEXT, __DATA, __IMPORT - segment_command_64 linkeditSegment; - std::vector<section_64> sections; - std::vector<std::string> sectionNames; + bool operator<(const T* obj) const + { + return T::GetObject(m_obj) < T::GetObject(obj); + } - std::vector<section_64> symbolStubSections; - std::vector<section_64> symbolPointerSections; + bool operator<(const KCRef<T>& obj) const + { + return T::GetObject(m_obj) < T::GetObject(obj.m_obj); + } - std::vector<std::string> dylibs; + T* GetPtr() const + { + return m_obj; + } +}; - build_version_command buildVersion; - std::vector<build_tool_version> buildToolVersions; - bool dysymPresent = false; - bool dyldInfoPresent = false; - bool exportTriePresent = false; - bool chainedFixupsPresent = false; - bool routinesPresent = false; - bool functionStartsPresent = false; - bool relocatable = false; - void Store(KernelCacheCore::SerializationContext& context) const { - MSS(textBase); - MSS(loadCommandOffset); - MSS_SUBCLASS(ident); - MSS(identifierPrefix); - MSS(installName); - MSS(entryPoints); - MSS(m_entryPoints); - MSS_SUBCLASS(symtab); - MSS_SUBCLASS(dysymtab); - MSS_SUBCLASS(dyldInfo); - MSS_SUBCLASS(routines64); - MSS_SUBCLASS(functionStarts); - MSS_SUBCLASS(moduleInitSections); - MSS_SUBCLASS(exportTrie); - MSS_SUBCLASS(chainedFixups); - MSS(relocationBase); - MSS_SUBCLASS(segments); - MSS_SUBCLASS(linkeditSegment); - MSS_SUBCLASS(sections); - MSS(sectionNames); - MSS_SUBCLASS(symbolStubSections); - MSS_SUBCLASS(symbolPointerSections); - MSS(dylibs); - MSS_SUBCLASS(buildVersion); - MSS_SUBCLASS(buildToolVersions); - MSS(dysymPresent); - MSS(dyldInfoPresent); - MSS(exportTriePresent); - MSS(chainedFixupsPresent); - MSS(routinesPresent); - MSS(functionStartsPresent); - MSS(relocatable); - } +namespace KernelCacheAPI { + struct CacheMappingInfo + { + uint64_t vmAddress; + uint64_t size; + uint64_t fileOffset; + }; - static KernelCacheMachOHeader Load(KernelCacheCore::DeserializationContext& context) { - KernelCacheMachOHeader header; - header.MSL(textBase); - header.MSL(loadCommandOffset); - header.MSL(ident); - header.MSL(identifierPrefix); - header.MSL(installName); - header.MSL(entryPoints); - header.MSL(m_entryPoints); - header.MSL(symtab); - header.MSL(dysymtab); - header.MSL(dyldInfo); - header.MSL(routines64); - header.MSL(functionStarts); - header.MSL(moduleInitSections); - header.MSL(exportTrie); - header.MSL(chainedFixups); - header.MSL(relocationBase); - header.MSL(segments); - header.MSL(linkeditSegment); - header.MSL(sections); - header.MSL(sectionNames); - header.MSL(symbolStubSections); - header.MSL(symbolPointerSections); - header.MSL(dylibs); - header.MSL(buildVersion); - header.MSL(buildToolVersions); - header.MSL(dysymPresent); - header.MSL(dyldInfoPresent); - header.MSL(exportTriePresent); - header.MSL(chainedFixupsPresent); - header.MSL(routinesPresent); - header.MSL(functionStartsPresent); - header.MSL(relocatable); - return header; - } + struct CacheImage + { + uint64_t headerFileAddress; + uint64_t headerVirtualAddress; + std::string name; }; + struct CacheEntry + { + std::string path; + std::string name; + BNKernelCacheEntryType entryType; + std::vector<CacheMappingInfo> mappings; + }; - class KernelCache : public KCCoreRefCountObject<BNKernelCache, BNNewKernelCacheReference, BNFreeKernelCacheReference> { - public: - KernelCache(Ref<BinaryView> view); + struct CacheSymbol + { + BNSymbolType type; + uint64_t address; + std::string name; + + std::pair<std::string, BinaryNinja::Ref<BinaryNinja::Type>> DemangledName(BinaryNinja::BinaryView &view) const; + BinaryNinja::Ref<BinaryNinja::Symbol> GetBNSymbol(BinaryNinja::BinaryView& view) const; + }; + + std::string GetSymbolTypeAsString(const BNSymbolType& type); - BNKCViewState GetState(); - static BNKCViewLoadProgress GetLoadProgress(Ref<BinaryView> view); - static uint64_t FastGetImageCount(Ref<BinaryView> view); + class KernelCacheController : public KCCoreRefCountObject<BNKernelCacheController, BNNewKernelCacheControllerReference, BNFreeKernelCacheControllerReference> { + public: + explicit KernelCacheController(BNKernelCacheController* controller); + static KCRef<KernelCacheController> GetController(BinaryNinja::BinaryView& view); - bool LoadImageWithInstallName(const std::string& installName); - bool LoadImageContainingAddress(uint64_t addr); - std::vector<std::string> GetAvailableImages(); + // Attempt to load the given image into the view. + // + // It is the callers responsibility to run linear sweep and update analysis, as you might want to add + // multiple images at a time. + bool ApplyImage(BinaryNinja::BinaryView& view, const CacheImage& image); - bool IsImageLoaded(const uint64_t address) const; + bool IsImageLoaded(const CacheImage& image) const; - std::vector<KCSymbol> LoadAllSymbolsAndWait(); + std::optional<CacheImage> GetImageAt(uint64_t address) const; + std::optional<CacheImage> GetImageContaining(uint64_t address) const; + std::optional<CacheImage> GetImageWithName(const std::string& name) const; - std::string GetNameForAddress(uint64_t address); - std::string GetImageNameForAddress(uint64_t address); + std::vector<std::string> GetImageDependencies(const CacheImage& image) const; - std::vector<KCImage> GetImages(); - std::vector<KCImage> GetLoadedImages(); + std::optional<CacheSymbol> GetSymbolAt(uint64_t address) const; + std::optional<CacheSymbol> GetSymbolWithName(const std::string& name) const; - std::optional<KernelCacheMachOHeader> GetMachOHeaderForImage(const std::string& name); - std::optional<KernelCacheMachOHeader> GetMachOHeaderForAddress(uint64_t address); + std::vector<CacheImage> GetImages() const; + std::vector<CacheImage> GetLoadedImages() const; + std::vector<CacheSymbol> GetSymbols() const; }; -}
\ No newline at end of file +} diff --git a/view/kernelcache/api/kernelcachecore.h b/view/kernelcache/api/kernelcachecore.h index 2f158131..02451a3e 100644 --- a/view/kernelcache/api/kernelcachecore.h +++ b/view/kernelcache/api/kernelcachecore.h @@ -28,6 +28,34 @@ extern "C" #endif // _MSC_VER #endif // __GNUC__C + +// binaryninjacore.h is not included so we must duplicate enum types here. +#ifdef BN_TYPE_PARSER +typedef enum BNSegmentFlag +{ + SegmentExecutable = 1, + SegmentWritable = 2, + SegmentReadable = 4, + SegmentContainsData = 8, + SegmentContainsCode = 0x10, + SegmentDenyWrite = 0x20, + SegmentDenyExecute = 0x40 +} BNSegmentFlag; + +typedef enum BNSymbolType +{ + FunctionSymbol = 0, + ImportAddressSymbol = 1, + ImportedFunctionSymbol = 2, + DataSymbol = 3, + ImportedDataSymbol = 4, + ExternalSymbol = 5, + LibraryFunctionSymbol = 6, + SymbolicFunctionSymbol = 7, + LocalLabelSymbol = 8, +} BNSymbolType; +#endif + #define CORE_ALLOCATED_STRUCT(T) #define CORE_ALLOCATED_CLASS(T) \ @@ -35,101 +63,80 @@ extern "C" CORE_ALLOCATED_STRUCT(T) \ private: -#define DECLARE_KERNELCACHE_API_OBJECT_INTERNAL(handle, cls, ns) \ - namespace ns { class cls; } struct handle { ns::cls* object; } - -#define DECLARE_KERNELCACHE_API_OBJECT(handle, cls) DECLARE_KERNELCACHE_API_OBJECT_INTERNAL(handle, cls, KernelCacheCore) - -#define IMPLEMENT_KERNELCACHE_API_OBJECT(handle) \ - CORE_ALLOCATED_CLASS(handle) \ - private: \ - handle m_apiObject; \ - public: \ - typedef handle* APIHandle; \ - handle* GetAPIObject() { return &m_apiObject; } \ - private: -#define INIT_KERNELCACHE_API_OBJECT() \ - m_apiObject.object = this; + typedef struct BNBinaryView BNBinaryView; + typedef struct BNKernelCacheController BNKernelCacheController; - typedef enum BNKCViewState { - Unloaded, - Loaded, - LoadedWithImages, - } BNKCViewState; + typedef enum BNKernelCacheEntryType { + KernelCacheEntryTypePrimary, + KernelCacheEntryTypeSecondary, + KernelCacheEntryTypeSymbols, + KernelCacheEntryTypeDyldData, + KernelCacheEntryTypeStub, + } BNKernelCacheEntryType; - typedef enum BNKCViewLoadProgress { - LoadProgressNotStarted, - LoadProgressLoadingCaches, - LoadProgressLoadingImages, - LoadProgressFinished, - } BNKCViewLoadProgress; + typedef enum BNKernelCacheRegionType { + KernelCacheRegionTypeImage, + KernelCacheRegionTypeStubIsland, + KernelCacheRegionTypeDyldData, + KernelCacheRegionTypeNonImage, + } BNKernelCacheRegionType; - typedef struct BNBinaryView BNBinaryView; - typedef struct BNKernelCache BNKernelCache; + typedef struct BNKernelCacheImage { + char* name; + uint64_t headerVirtualAddress; + uint64_t headerFileAddress; + } BNKernelCacheImage; - typedef struct BNKCImageMemoryMapping { + typedef struct BNKernelCacheRegion { + BNKernelCacheRegionType regionType; char* name; uint64_t vmAddress; uint64_t size; - bool loaded; - uint64_t rawViewOffset; - } BNKCImageMemoryMapping; - - typedef struct BNKCImage { - char* name; - uint64_t headerFileAddress; - BNKCImageMemoryMapping* mappings; - size_t mappingCount; - } BNKCImage; + // NOTE: If not associated with an image this will be zero. + uint64_t imageStart; + BNSegmentFlag flags; + } BNKernelCacheRegion; - typedef struct BNKCMappedMemoryRegion { + typedef struct BNKernelCacheMappingInfo { uint64_t vmAddress; uint64_t size; - char* name; - } BNKCMappedMemoryRegion; + uint64_t fileOffset; + } BNKernelCacheMappingInfo; - typedef struct BNKCMemoryUsageInfo { - uint64_t sharedCacheRefs; - uint64_t mmapRefs; - } BNKCMemoryUsageInfo; - - typedef struct BNKCSymbolRep { + typedef struct BNKernelCacheSymbol { + BNSymbolType symbolType; uint64_t address; char* name; - char* image; - } BNKCSymbolRep; - - KERNELCACHE_FFI_API BNKernelCache* BNGetKernelCache(BNBinaryView* data); + } BNKernelCacheSymbol; - KERNELCACHE_FFI_API BNKernelCache* BNNewKernelCacheReference(BNKernelCache* cache); - KERNELCACHE_FFI_API void BNFreeKernelCacheReference(BNKernelCache* cache); + KERNELCACHE_FFI_API BNKernelCacheController* BNGetKernelCacheController(BNBinaryView* data); - KERNELCACHE_FFI_API char** BNKCViewGetInstallNames(BNKernelCache* cache, size_t* count); + KERNELCACHE_FFI_API BNKernelCacheController* BNNewKernelCacheControllerReference(BNKernelCacheController* controller); + KERNELCACHE_FFI_API void BNFreeKernelCacheControllerReference(BNKernelCacheController* controller); - KERNELCACHE_FFI_API bool BNKCViewLoadImageWithInstallName(BNKernelCache* cache, const char* name); - KERNELCACHE_FFI_API bool BNKCViewLoadImageContainingAddress(BNKernelCache* cache, uint64_t address); + KERNELCACHE_FFI_API bool BNKernelCacheControllerApplyImage(BNKernelCacheController* controller, BNBinaryView* view, BNKernelCacheImage* image); - KERNELCACHE_FFI_API bool BNKCViewIsImageLoaded(BNKernelCache* cache, uint64_t address); + KERNELCACHE_FFI_API bool BNKernelCacheControllerIsImageLoaded(BNKernelCacheController* controller, BNKernelCacheImage* image); - KERNELCACHE_FFI_API char* BNKCViewGetNameForAddress(BNKernelCache* cache, uint64_t address); - KERNELCACHE_FFI_API char* BNKCViewGetImageNameForAddress(BNKernelCache* cache, uint64_t address); + KERNELCACHE_FFI_API bool BNKernelCacheControllerGetImageAt(BNKernelCacheController* controller, uint64_t address, BNKernelCacheImage* image); + KERNELCACHE_FFI_API bool BNKernelCacheControllerGetImageContaining(BNKernelCacheController* controller, uint64_t address, BNKernelCacheImage* image); + KERNELCACHE_FFI_API bool BNKernelCacheControllerGetImageWithName(BNKernelCacheController* controller, const char* name, BNKernelCacheImage* image); - KERNELCACHE_FFI_API BNKCViewState BNKCViewGetState(BNKernelCache* cache); - KERNELCACHE_FFI_API BNKCViewLoadProgress BNKCViewGetLoadProgress(uint64_t sessionID); - KERNELCACHE_FFI_API uint64_t BNKCViewFastGetImageCount(BNBinaryView* view); + KERNELCACHE_FFI_API char** BNKernelCacheControllerGetImageDependencies(BNKernelCacheController* controller, BNKernelCacheImage* image, size_t* count); - KERNELCACHE_FFI_API BNKCSymbolRep* BNKCViewLoadAllSymbolsAndWait(BNKernelCache* cache, size_t* count); - KERNELCACHE_FFI_API void BNKCViewFreeSymbols(BNKCSymbolRep* symbols, size_t count); + KERNELCACHE_FFI_API BNKernelCacheImage* BNKernelCacheControllerGetImages(BNKernelCacheController* controller, size_t* count); + KERNELCACHE_FFI_API BNKernelCacheImage* BNKernelCacheControllerGetLoadedImages(BNKernelCacheController* controller, size_t* count); - KERNELCACHE_FFI_API BNKCImage* BNKCViewGetAllImages(BNKernelCache* cache, size_t* count); - KERNELCACHE_FFI_API void BNKCViewFreeAllImages(BNKCImage* images, size_t count); + KERNELCACHE_FFI_API void BNKernelCacheFreeImage(BNKernelCacheImage image); + KERNELCACHE_FFI_API void BNKernelCacheFreeImageList(BNKernelCacheImage* images, size_t count); - KERNELCACHE_FFI_API BNKCImage* BNKCViewGetLoadedImages(BNKernelCache* cache, size_t* count); - KERNELCACHE_FFI_API void BNKCViewFreeLoadedImages(BNKCImage* images, size_t count); + KERNELCACHE_FFI_API bool BNKernelCacheControllerGetSymbolAt(BNKernelCacheController* controller, uint64_t address, BNKernelCacheSymbol* symbol); + KERNELCACHE_FFI_API bool BNKernelCacheControllerGetSymbolWithName(BNKernelCacheController* controller, const char* name, BNKernelCacheSymbol* symbol); - KERNELCACHE_FFI_API char* BNKCViewGetImageHeaderForAddress(BNKernelCache* cache, uint64_t address); - KERNELCACHE_FFI_API char* BNKCViewGetImageHeaderForName(BNKernelCache* cache, const char* name); + KERNELCACHE_FFI_API BNKernelCacheSymbol* BNKernelCacheControllerGetSymbols(BNKernelCacheController* controller, size_t* count); + KERNELCACHE_FFI_API void BNKernelCacheFreeSymbol(BNKernelCacheSymbol symbol); + KERNELCACHE_FFI_API void BNKernelCacheFreeSymbolList(BNKernelCacheSymbol* symbols, size_t count); #ifdef __cplusplus } #endif diff --git a/view/kernelcache/api/python/kernelcache.py b/view/kernelcache/api/python/kernelcache.py index 9425ef9c..25360213 100644 --- a/view/kernelcache/api/python/kernelcache.py +++ b/view/kernelcache/api/python/kernelcache.py @@ -1,229 +1,210 @@ -import os import ctypes import dataclasses -import traceback +from typing import Optional import binaryninja +from binaryninja import BinaryView from binaryninja._binaryninjacore import BNFreeStringList, BNAllocString, BNFreeString from . import _kernelcachecore as kccore from .kernelcache_enums import * - @dataclasses.dataclass -class KCImageMemoryMapping: +class CacheImage: name: str - vmAddress: int - rawViewOffset: int - size: int + header_virtual_address: int + header_file_address: int def __str__(self): return repr(self) def __repr__(self): - return (f"<KCImageMemoryMapping '{self.name}' {self.vmAddress:x}+{self.size:x} " - f"raw<{self.rawViewOffset:x}>>") - + return f"<CacheImage '{self.name}': 0x{self.header_virtual_address:x} (@0x{self.header_file_address:x})>" @dataclasses.dataclass -class KCImage: +class CacheSymbol: + symbol_type: kccore.SymbolTypeEnum + address: int name: str - headerFileAddress: int - mappings: list[KCImageMemoryMapping] def __str__(self): return repr(self) def __repr__(self): - return f"<KCImage {self.name} @ {self.headerFileAddress:x}>" + return f"<CacheSymbol '{self.name}': 0x{self.address:x}>" +def image_from_api(image: kccore.BNKernelCacheImage) -> CacheImage: + return CacheImage( + name=image.name, + header_file_address=image.headerFileAddress, + header_virtual_address=image.headerVirtualAddress + ) -@dataclasses.dataclass -class KCSymbol: - name: str - image: str - address: int +def image_to_api(image: CacheImage) -> kccore.BNKernelCacheImage: + return kccore.BNKernelCacheImage( + _name=BNAllocString(image.name), + headerFileAddress=image.header_file_address, + headerVirtualAddress=image.header_virtual_address, + ) - def __str__(self): - return repr(self) +def symbol_from_api(symbol: kccore.BNKernelCacheSymbol) -> CacheSymbol: + return CacheSymbol( + symbol_type=symbol.symbolType, + address=symbol.address, + name=symbol.name + ) - def __repr__(self): - return f"<KCSymbol {self.name} @ {self.address:x} ({self.image})>" +def symbol_to_api(symbol: CacheSymbol) -> kccore.BNKernelCacheSymbol: + return kccore.BNKernelCacheSymbol( + symbolType=symbol.symbol_type, + address=symbol.address, + _name=BNAllocString(symbol.name) + ) +class KernelCacheController: + def __init__(self, view: BinaryView): + """ + Retrieve the shared cache controller for a given view. + Call `is_valid` to check if the controller is valid. + """ + self.handle = kccore.BNGetKernelCacheController(view.handle) -@dataclasses.dataclass -class KernelCacheMachOHeader: - header: str + def __del__(self): + if self.handle is not None: + kccore.BNFreeKernelCacheControllerReference(self.handle) - @classmethod - def LoadFromString(cls, s: str): - return cls(header=s) + def __str__(self): + return repr(self) def __repr__(self): - return f"<KernelCacheMachOHeader {self.header!r}>" + return f"<KernelCacheController: {len(self.images)} images>" + def is_valid(self) -> bool: + return self.handle is not None -class KernelCache: - def __init__(self, view): - # Create a KernelCache object from a BinaryView. - self.handle = kccore.BNGetKernelCache(view.handle) + def apply_image(self, view: BinaryView, image: CacheImage) -> bool: + api_image: kccore.BNKernelCacheImage = image_to_api(image) + result = kccore.BNKernelCacheControllerApplyImage(self.handle, view.handle, api_image) + kccore.BNKernelCacheFreeImage(api_image) + return result - @staticmethod - def get_load_progress(view) -> int: - """ - Returns the current load progress. - Note: In the C++ FFI this function takes the BinaryView's file session ID. - """ - return KCViewLoadProgress(kccore.BNKCViewGetLoadProgress(view.file.session_id)) + def is_image_loaded(self, image: CacheImage) -> bool: + api_image: kccore.BNKernelCacheImage = image_to_api(image) + result = kccore.BNKernelCacheControllerIsImageLoaded(self.handle, api_image) + kccore.BNKernelCacheFreeImage(api_image) + return result - @staticmethod - def fast_get_image_count(view) -> int: - """ - Quickly returns the number of images in the kernel cache. - """ - return kccore.BNKCViewFastGetImageCount(view.handle) + def get_image_at(self, address: int) -> Optional[CacheImage]: + api_image = kccore.BNKernelCacheImage() + if not kccore.BNKernelCacheControllerGetImageAt(self.handle, address, api_image): + return None + image = image_from_api(api_image) + kccore.BNKernelCacheFreeImage(api_image) + return image - def load_image_with_install_name(self, install_name: str) -> bool: - """ - Load a kernel cache image by its install name. - """ - return kccore.BNKCViewLoadImageWithInstallName(self.handle, install_name) + def get_image_containing(self, address: int) -> Optional[CacheImage]: + api_image = kccore.BNKernelCacheImage() + if not kccore.BNKernelCacheControllerGetImageContaining(self.handle, address, api_image): + return None + image = image_from_api(api_image) + kccore.BNKernelCacheFreeImage(api_image) + return image - def load_image_containing_address(self, addr: int) -> bool: - """ - Load the image that contains the given address. - """ - return kccore.BNKCViewLoadImageContainingAddress(self.handle, addr) + def get_image_with_name(self, name: str) -> Optional[CacheImage]: + api_image = kccore.BNKernelCacheImage() + if not kccore.BNKernelCacheControllerGetImageWithName(self.handle, name, api_image): + return None + image = image_from_api(api_image) + kccore.BNKernelCacheFreeImage(api_image) + return image - @property - def image_names(self) -> list[str]: + def get_image_dependencies(self, image: CacheImage) -> [str]: """ - Return a list of available kernel cache image install names. + Returns a list of image names that this image depends on. """ count = ctypes.c_ulonglong() - value = kccore.BNKCViewGetInstallNames(self.handle, count) + api_image: kccore.BNKernelCacheImage = image_to_api(image) + value = kccore.BNKernelCacheControllerGetImageDependencies(self.handle, api_image, count) + kccore.BNKernelCacheFreeImage(api_image) if value is None: return [] result = [] for i in range(count.value): - result.append(value[i].decode('utf-8')) + result.append(value[i].decode("utf-8")) BNFreeStringList(value, count) return result + def get_symbol_at(self, address: int) -> Optional[CacheSymbol]: + api_symbol = kccore.BNKernelCacheSymbol() + if not kccore.BNKernelCacheControllerGetSymbolAt(self.handle, address, api_symbol): + return None + symbol = symbol_from_api(api_symbol) + kccore.BNKernelCacheFreeSymbol(api_symbol) + return symbol + + def get_symbol_with_name(self, name: str) -> Optional[CacheSymbol]: + api_symbol = kccore.BNKernelCacheSymbol() + if not kccore.BNKernelCacheControllerGetSymbolWithName(self.handle, name, api_symbol): + return None + symbol = symbol_from_api(api_symbol) + kccore.BNKernelCacheFreeSymbol(api_symbol) + return symbol + @property - def images(self) -> list[KCImage]: - """ - Return all kernel cache images. - """ + def images(self) -> [CacheImage]: count = ctypes.c_ulonglong() - value = kccore.BNKCViewGetAllImages(self.handle, count) + value = kccore.BNKernelCacheControllerGetImages(self.handle, count) if value is None: return [] result = [] for i in range(count.value): - mappings = [] - for j in range(value[i].mappingCount): - mapping = KCImageMemoryMapping( - name=value[i].mappings[j].name, - vmAddress=value[i].mappings[j].vmAddress, - rawViewOffset=value[i].mappings[j].rawViewOffset, - size=value[i].mappings[j].size, - ) - mappings.append(mapping) - result.append(KCImage( - name=value[i].name, - headerFileAddress=value[i].headerFileAddress, - mappings=mappings - )) - kccore.BNKCViewFreeAllImages(value, count) + result.append(image_from_api(value[i])) + kccore.BNKernelCacheFreeImageList(value, count) return result @property - def loaded_images(self) -> list[KCImage]: + def loaded_images(self) -> [CacheImage]: """ - Return the kernel cache images that are currently loaded. + Get a list of images that are currently loaded in the view. """ count = ctypes.c_ulonglong() - images = kccore.BNKCViewGetLoadedImages(self.handle, count) - if images is None: + value = kccore.BNKernelCacheControllerGetLoadedImages(self.handle, count) + if value is None: return [] result = [] for i in range(count.value): - mappings = [] - for j in range(images[i].mappingCount): - mapping = KCImageMemoryMapping( - name=images[i].mappings[j].name, - vmAddress=images[i].mappings[j].vmAddress, - rawViewOffset=images[i].mappings[j].rawViewOffset, - size=images[i].mappings[j].size, - ) - mappings.append(mapping) - result.append(KCImage( - name=images[i].name, - headerFileAddress=images[i].headerFileAddress, - mappings=mappings - )) - kccore.BNKCViewFreeAllImages(images, count) + result.append(image_from_api(value[i])) + kccore.BNKernelCacheFreeImageList(value, count) return result - def load_all_symbols_and_wait(self) -> list[KCSymbol]: - """ - Load all symbols from the kernel cache and wait for completion. - """ + @property + def symbols(self) -> [CacheSymbol]: count = ctypes.c_ulonglong() - value = kccore.BNKCViewLoadAllSymbolsAndWait(self.handle, count) + value = kccore.BNKernelCacheControllerGetSymbols(self.handle, count) if value is None: return [] result = [] for i in range(count.value): - sym = KCSymbol( - name=value[i].name, - image=value[i].image, - address=value[i].address - ) - result.append(sym) - kccore.BNKCViewFreeSymbols(value, count) + result.append(symbol_from_api(value[i])) + kccore.BNKernelCacheFreeSymbolList(value, count) return result - def get_name_for_address(self, address: int) -> str: - """ - Return the symbol name for a given address. - """ - name = kccore.BNKCViewGetNameForAddress(self.handle, address) - if name is None: - return "" - return name - def get_image_name_for_address(self, address: int) -> str: - """ - Return the image name for a given address. - """ - name = kccore.BNKCViewGetImageNameForAddress(self.handle, address) - if name is None: - return "" - return name +def _get_kernel_cache(instance: binaryninja.PythonScriptingInstance): + if instance.interpreter.active_view is None: + return None + controller = KernelCacheController(instance.interpreter.active_view) + if not controller.is_valid(): + return None + return controller - def get_macho_header_for_image(self, name: str): - """ - Return a KernelCacheMachOHeader for the image with the given install name. - """ - outputStr = kccore.BNKCViewGetImageHeaderForName(self.handle, name) - if outputStr is None: - return None - return KernelCacheMachOHeader.LoadFromString(outputStr) - def get_macho_header_for_address(self, address: int): - """ - Return a KernelCacheMachOHeader for the image containing the given address. - """ - outputStr = kccore.BNKCViewGetImageHeaderForAddress(self.handle, address) - if outputStr is None: - return None - return KernelCacheMachOHeader.LoadFromString(outputStr) +binaryninja.PythonScriptingProvider.register_magic_variable( + "kc", + _get_kernel_cache +) - @property - def state(self): - """ - Return the current state of the kernel cache. - """ - return KCViewState(kccore.BNKCViewGetState(self.handle)) +binaryninja.PythonScriptingProvider.register_magic_variable( + "kernel_cache", + _get_kernel_cache +) diff --git a/view/kernelcache/api/python/kernelcache_enums.py b/view/kernelcache/api/python/kernelcache_enums.py index e610c527..83cbf027 100644 --- a/view/kernelcache/api/python/kernelcache_enums.py +++ b/view/kernelcache/api/python/kernelcache_enums.py @@ -1,14 +1,38 @@ import enum -class KCViewLoadProgress(enum.IntEnum): - LoadProgressNotStarted = 0 - LoadProgressLoadingCaches = 1 - LoadProgressLoadingImages = 2 - LoadProgressFinished = 3 +class KernelCacheEntryType(enum.IntEnum): + KernelCacheEntryTypePrimary = 0 + KernelCacheEntryTypeSecondary = 1 + KernelCacheEntryTypeSymbols = 2 + KernelCacheEntryTypeDyldData = 3 + KernelCacheEntryTypeStub = 4 -class KCViewState(enum.IntEnum): - Unloaded = 0 - Loaded = 1 - LoadedWithImages = 2 +class KernelCacheRegionType(enum.IntEnum): + KernelCacheRegionTypeImage = 0 + KernelCacheRegionTypeStubIsland = 1 + KernelCacheRegionTypeDyldData = 2 + KernelCacheRegionTypeNonImage = 3 + + +class SegmentFlag(enum.IntEnum): + SegmentExecutable = 1 + SegmentWritable = 2 + SegmentReadable = 4 + SegmentContainsData = 8 + SegmentContainsCode = 16 + SegmentDenyWrite = 32 + SegmentDenyExecute = 64 + + +class SymbolType(enum.IntEnum): + FunctionSymbol = 0 + ImportAddressSymbol = 1 + ImportedFunctionSymbol = 2 + DataSymbol = 3 + ImportedDataSymbol = 4 + ExternalSymbol = 5 + LibraryFunctionSymbol = 6 + SymbolicFunctionSymbol = 7 + LocalLabelSymbol = 8 diff --git a/view/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(§, 0, sizeof(sect)); - size_t curOffset = reader.GetOffset(); - load.cmd = reader.Read32(); - load.cmdsize = reader.Read32(); - size_t nextOffset = curOffset + load.cmdsize; - if (load.cmdsize < sizeof(load_command)) - return {}; - - switch (load.cmd) - { - case LC_MAIN: - { - uint64_t entryPoint = reader.Read64(); - header.entryPoints.push_back({entryPoint, true}); - (void)reader.Read64(); // Stack start - break; - } - case LC_SEGMENT: // map the 32bit version to 64 bits - segment64.cmd = LC_SEGMENT_64; - reader.Read(&segment64.segname, 16); - segment64.vmaddr = reader.Read32(); - segment64.vmsize = reader.Read32(); - segment64.fileoff = reader.Read32(); - segment64.filesize = reader.Read32(); - segment64.maxprot = reader.Read32(); - segment64.initprot = reader.Read32(); - segment64.nsects = reader.Read32(); - segment64.flags = reader.Read32(); - if (strncmp(segment64.segname, "__TEXT\0", 7) == 0) - { - header.relocationBase = segment64.vmaddr; - header.textBase = segment64.vmaddr; - header.textBaseFileOffset = segment64.fileoff; - } - for (size_t j = 0; j < segment64.nsects; j++) - { - reader.Read(§.sectname, 16); - reader.Read(§.segname, 16); - sect.addr = reader.Read32(); - sect.size = reader.Read32(); - sect.offset = reader.Read32(); - sect.align = reader.Read32(); - sect.reloff = reader.Read32(); - sect.nreloc = reader.Read32(); - sect.flags = reader.Read32(); - sect.reserved1 = reader.Read32(); - sect.reserved2 = reader.Read32(); - // if the segment isn't mapped into virtual memory don't add the corresponding sections. - if (segment64.vmsize > 0) - { - header.sections.push_back(sect); - } - if (!strncmp(sect.sectname, "__mod_init_func", 15)) - header.moduleInitSections.push_back(sect); - if (!strncmp(sect.sectname, "__mod_term_func", 15)) - header.moduleTermSections.push_back(sect); - if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - header.symbolStubSections.push_back(sect); - if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - } - header.segments.push_back(segment64); - break; - case LC_SEGMENT_64: - segment64.cmd = LC_SEGMENT_64; - reader.Read(&segment64.segname, 16); - segment64.vmaddr = reader.Read64(); - segment64.vmsize = reader.Read64(); - segment64.fileoff = reader.Read64(); - segment64.filesize = reader.Read64(); - segment64.maxprot = reader.Read32(); - segment64.initprot = reader.Read32(); - segment64.nsects = reader.Read32(); - segment64.flags = reader.Read32(); - if (strncmp(segment64.segname, "__LINKEDIT", 10) == 0) - { - header.linkeditSegment = segment64; - header.linkeditPresent = true; - } - if (strncmp(segment64.segname, "__TEXT\0", 7) == 0) - { - header.relocationBase = segment64.vmaddr; - header.textBase = segment64.vmaddr; - header.textBaseFileOffset = segment64.fileoff; - } - for (size_t j = 0; j < segment64.nsects; j++) - { - reader.Read(§.sectname, 16); - reader.Read(§.segname, 16); - sect.addr = reader.Read64(); - sect.size = reader.Read64(); - sect.offset = reader.Read32(); - sect.align = reader.Read32(); - sect.reloff = reader.Read32(); - sect.nreloc = reader.Read32(); - sect.flags = reader.Read32(); - sect.reserved1 = reader.Read32(); - sect.reserved2 = reader.Read32(); - sect.reserved3 = reader.Read32(); - // if the segment isn't mapped into virtual memory don't add the corresponding sections. - if (segment64.vmsize > 0) - { - header.sections.push_back(sect); - } - - if (!strncmp(sect.sectname, "__mod_init_func", 15)) - header.moduleInitSections.push_back(sect); - if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) - header.symbolStubSections.push_back(sect); - if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) - header.symbolPointerSections.push_back(sect); - } - header.segments.push_back(segment64); - break; - case LC_ROUTINES: // map the 32bit version to 64bits - header.routines64.cmd = LC_ROUTINES_64; - header.routines64.init_address = reader.Read32(); - header.routines64.init_module = reader.Read32(); - header.routines64.reserved1 = reader.Read32(); - header.routines64.reserved2 = reader.Read32(); - header.routines64.reserved3 = reader.Read32(); - header.routines64.reserved4 = reader.Read32(); - header.routines64.reserved5 = reader.Read32(); - header.routines64.reserved6 = reader.Read32(); - header.routinesPresent = true; - break; - case LC_ROUTINES_64: - header.routines64.cmd = LC_ROUTINES_64; - header.routines64.init_address = reader.Read64(); - header.routines64.init_module = reader.Read64(); - header.routines64.reserved1 = reader.Read64(); - header.routines64.reserved2 = reader.Read64(); - header.routines64.reserved3 = reader.Read64(); - header.routines64.reserved4 = reader.Read64(); - header.routines64.reserved5 = reader.Read64(); - header.routines64.reserved6 = reader.Read64(); - header.routinesPresent = true; - break; - case LC_FUNCTION_STARTS: - header.functionStarts.funcoff = reader.Read32(); - header.functionStarts.funcsize = reader.Read32(); - header.functionStartsPresent = true; - break; - case LC_SYMTAB: - header.symtab.symoff = reader.Read32(); - header.symtab.nsyms = reader.Read32(); - header.symtab.stroff = reader.Read32(); - header.symtab.strsize = reader.Read32(); - break; - case LC_DYSYMTAB: - header.dysymtab.ilocalsym = reader.Read32(); - header.dysymtab.nlocalsym = reader.Read32(); - header.dysymtab.iextdefsym = reader.Read32(); - header.dysymtab.nextdefsym = reader.Read32(); - header.dysymtab.iundefsym = reader.Read32(); - header.dysymtab.nundefsym = reader.Read32(); - header.dysymtab.tocoff = reader.Read32(); - header.dysymtab.ntoc = reader.Read32(); - header.dysymtab.modtaboff = reader.Read32(); - header.dysymtab.nmodtab = reader.Read32(); - header.dysymtab.extrefsymoff = reader.Read32(); - header.dysymtab.nextrefsyms = reader.Read32(); - header.dysymtab.indirectsymoff = reader.Read32(); - header.dysymtab.nindirectsyms = reader.Read32(); - header.dysymtab.extreloff = reader.Read32(); - header.dysymtab.nextrel = reader.Read32(); - header.dysymtab.locreloff = reader.Read32(); - header.dysymtab.nlocrel = reader.Read32(); - header.dysymPresent = true; - break; - case LC_DYLD_CHAINED_FIXUPS: - header.chainedFixups.dataoff = reader.Read32(); - header.chainedFixups.datasize = reader.Read32(); - header.chainedFixupsPresent = true; - break; - case LC_DYLD_INFO: - case LC_DYLD_INFO_ONLY: - header.dyldInfo.rebase_off = reader.Read32(); - header.dyldInfo.rebase_size = reader.Read32(); - header.dyldInfo.bind_off = reader.Read32(); - header.dyldInfo.bind_size = reader.Read32(); - header.dyldInfo.weak_bind_off = reader.Read32(); - header.dyldInfo.weak_bind_size = reader.Read32(); - header.dyldInfo.lazy_bind_off = reader.Read32(); - header.dyldInfo.lazy_bind_size = reader.Read32(); - header.dyldInfo.export_off = reader.Read32(); - header.dyldInfo.export_size = reader.Read32(); - header.exportTrie.dataoff = header.dyldInfo.export_off; - header.exportTrie.datasize = header.dyldInfo.export_size; - header.exportTriePresent = true; - header.dyldInfoPresent = true; - break; - case LC_DYLD_EXPORTS_TRIE: - header.exportTrie.dataoff = reader.Read32(); - header.exportTrie.datasize = reader.Read32(); - header.exportTriePresent = true; - break; - case LC_THREAD: - case LC_UNIXTHREAD: - /*while (reader.GetOffset() < nextOffset) - { - - thread_command thread; - thread.flavor = reader.Read32(); - thread.count = reader.Read32(); - switch (m_archId) - { - case MachOx64: - m_logger->LogDebug("x86_64 Thread state\n"); - if (thread.flavor != X86_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statex64, sizeof(thread.statex64)); - header.entryPoints.push_back({thread.statex64.rip, false}); - break; - case MachOx86: - m_logger->LogDebug("x86 Thread state\n"); - if (thread.flavor != X86_THREAD_STATE32) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statex86, sizeof(thread.statex86)); - header.entryPoints.push_back({thread.statex86.eip, false}); - break; - case MachOArm: - m_logger->LogDebug("Arm Thread state\n"); - if (thread.flavor != _ARM_THREAD_STATE) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //This wont be big endian so we can just read the whole thing - reader.Read(&thread.statearmv7, sizeof(thread.statearmv7)); - header.entryPoints.push_back({thread.statearmv7.r15, false}); - break; - case MachOAarch64: - case MachOAarch6432: - m_logger->LogDebug("Aarch64 Thread state\n"); - if (thread.flavor != _ARM_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - reader.Read(&thread.stateaarch64, sizeof(thread.stateaarch64)); - header.entryPoints.push_back({thread.stateaarch64.pc, false}); - break; - case MachOPPC: - m_logger->LogDebug("PPC Thread state\n"); - if (thread.flavor != PPC_THREAD_STATE) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - //Read individual entries for endian reasons - header.entryPoints.push_back({reader.Read32(), false}); - (void)reader.Read32(); - (void)reader.Read32(); - //Read the rest of the structure - (void)reader.Read(&thread.stateppc.r1, sizeof(thread.stateppc) - (3 * 4)); - break; - case MachOPPC64: - m_logger->LogDebug("PPC64 Thread state\n"); - if (thread.flavor != PPC_THREAD_STATE64) - { - reader.SeekRelative(thread.count * sizeof(uint32_t)); - break; - } - header.entryPoints.push_back({reader.Read64(), false}); - (void)reader.Read64(); - (void)reader.Read64(); // Stack start - (void)reader.Read(&thread.stateppc64.r1, sizeof(thread.stateppc64) - (3 * 8)); - break; - default: - m_logger->LogError("Unknown archid: %x", m_archId); - } - - }*/ - break; - case LC_LOAD_DYLIB: - { - uint32_t offset = reader.Read32(); - if (offset < nextOffset) - { - reader.Seek(curOffset + offset); - std::string libname = reader.ReadCString(reader.GetOffset()); - header.dylibs.push_back(libname); - } - } - break; - case LC_BUILD_VERSION: - { - // m_logger->LogDebug("LC_BUILD_VERSION:"); - header.buildVersion.platform = reader.Read32(); - header.buildVersion.minos = reader.Read32(); - header.buildVersion.sdk = reader.Read32(); - header.buildVersion.ntools = reader.Read32(); - // m_logger->LogDebug("Platform: %s", BuildPlatformToString(header.buildVersion.platform).c_str()); - // m_logger->LogDebug("MinOS: %s", BuildToolVersionToString(header.buildVersion.minos).c_str()); - // m_logger->LogDebug("SDK: %s", BuildToolVersionToString(header.buildVersion.sdk).c_str()); - for (uint32_t j = 0; (i < header.buildVersion.ntools) && (j < 10); j++) - { - uint32_t tool = reader.Read32(); - uint32_t version = reader.Read32(); - header.buildToolVersions.push_back({tool, version}); - // m_logger->LogDebug("Build Tool: %s: %s", BuildToolToString(tool).c_str(), - // BuildToolVersionToString(version).c_str()); - } - break; - } - case LC_FILESET_ENTRY: - { - throw ReadException(); - } - default: - // m_logger->LogDebug("Unhandled command: %s : %" PRIu32 "\n", CommandToString(load.cmd).c_str(), - // load.cmdsize); - break; - } - if (reader.GetOffset() != nextOffset) - { - // m_logger->LogDebug("Didn't parse load command: %s fully %" PRIx64 ":%" PRIxPTR, - // CommandToString(load.cmd).c_str(), reader.GetOffset(), nextOffset); - } - reader.Seek(nextOffset); - } - - for (auto& section : header.sections) - { - char sectionName[17]; - memcpy(sectionName, section.sectname, sizeof(section.sectname)); - sectionName[16] = 0; - char segmentName[17]; - memcpy(segmentName, section.segname, sizeof(section.segname)); - segmentName[16] = 0; - if (header.identifierPrefix.empty()) - header.sectionNames.push_back(sectionName); - else - header.sectionNames.push_back(header.identifierPrefix + "::" + segmentName + ":" + sectionName); - } - } - catch (ReadException&) - { - return {}; - } - - return header; + 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(§.sectname, 16); + reader.Read(§.segname, 16); + sect.addr = reader.Read32(); + sect.size = reader.Read32(); + sect.offset = reader.Read32(); + sect.align = reader.Read32(); + sect.reloff = reader.Read32(); + sect.nreloc = reader.Read32(); + sect.flags = reader.Read32(); + sect.reserved1 = reader.Read32(); + sect.reserved2 = reader.Read32(); + // if the segment isn't mapped into virtual memory don't add the corresponding sections. + if (segment64.vmsize > 0) + { + header.sections.push_back(sect); + } + if (!strncmp(sect.sectname, "__mod_init_func", 15)) + header.moduleInitSections.push_back(sect); + if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + header.symbolStubSections.push_back(sect); + if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + } + header.segments.push_back(segment64); + break; + case LC_SEGMENT_64: + segment64.cmd = LC_SEGMENT_64; + reader.Read(&segment64.segname, 16); + segment64.vmaddr = reader.Read64(); + segment64.vmsize = reader.Read64(); + segment64.fileoff = reader.Read64(); + segment64.filesize = reader.Read64(); + segment64.maxprot = reader.Read32(); + segment64.initprot = reader.Read32(); + segment64.nsects = reader.Read32(); + segment64.flags = reader.Read32(); + if (strncmp(segment64.segname, "__LINKEDIT", 10) == 0) + { + header.linkeditSegment = segment64; + header.linkeditPresent = true; + } + if (first) + { + if (!((header.ident.flags & MH_SPLIT_SEGS) || header.ident.cputype == MACHO_CPU_TYPE_X86_64) + || (segment64.flags & MACHO_VM_PROT_WRITE)) + { + header.relocationBase = segment64.vmaddr; + first = false; + } + } + for (size_t j = 0; j < segment64.nsects; j++) + { + reader.Read(§.sectname, 16); + reader.Read(§.segname, 16); + sect.addr = reader.Read64(); + sect.size = reader.Read64(); + sect.offset = reader.Read32(); + sect.align = reader.Read32(); + sect.reloff = reader.Read32(); + sect.nreloc = reader.Read32(); + sect.flags = reader.Read32(); + sect.reserved1 = reader.Read32(); + sect.reserved2 = reader.Read32(); + sect.reserved3 = reader.Read32(); + // if the segment isn't mapped into virtual memory don't add the corresponding sections. + if (segment64.vmsize > 0) + { + header.sections.push_back(sect); + } + + if (!strncmp(sect.sectname, "__mod_init_func", 15)) + header.moduleInitSections.push_back(sect); + if ((sect.flags & (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + == (S_ATTR_SELF_MODIFYING_CODE | S_SYMBOL_STUBS)) + header.symbolStubSections.push_back(sect); + if ((sect.flags & S_NON_LAZY_SYMBOL_POINTERS) == S_NON_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + if ((sect.flags & S_LAZY_SYMBOL_POINTERS) == S_LAZY_SYMBOL_POINTERS) + header.symbolPointerSections.push_back(sect); + } + header.segments.push_back(segment64); + break; + case LC_ROUTINES: // map the 32bit version to 64bits + header.routines64.cmd = LC_ROUTINES_64; + header.routines64.init_address = reader.Read32(); + header.routines64.init_module = reader.Read32(); + header.routines64.reserved1 = reader.Read32(); + header.routines64.reserved2 = reader.Read32(); + header.routines64.reserved3 = reader.Read32(); + header.routines64.reserved4 = reader.Read32(); + header.routines64.reserved5 = reader.Read32(); + header.routines64.reserved6 = reader.Read32(); + header.routinesPresent = true; + break; + case LC_ROUTINES_64: + header.routines64.cmd = LC_ROUTINES_64; + header.routines64.init_address = reader.Read64(); + header.routines64.init_module = reader.Read64(); + header.routines64.reserved1 = reader.Read64(); + header.routines64.reserved2 = reader.Read64(); + header.routines64.reserved3 = reader.Read64(); + header.routines64.reserved4 = reader.Read64(); + header.routines64.reserved5 = reader.Read64(); + header.routines64.reserved6 = reader.Read64(); + header.routinesPresent = true; + break; + case LC_FUNCTION_STARTS: + header.functionStarts.funcoff = reader.Read32(); + header.functionStarts.funcsize = reader.Read32(); + header.functionStartsPresent = true; + break; + case LC_SYMTAB: + header.symtab.symoff = reader.Read32(); + header.symtab.nsyms = reader.Read32(); + header.symtab.stroff = reader.Read32(); + header.symtab.strsize = reader.Read32(); + break; + case LC_DYSYMTAB: + header.dysymtab.ilocalsym = reader.Read32(); + header.dysymtab.nlocalsym = reader.Read32(); + header.dysymtab.iextdefsym = reader.Read32(); + header.dysymtab.nextdefsym = reader.Read32(); + header.dysymtab.iundefsym = reader.Read32(); + header.dysymtab.nundefsym = reader.Read32(); + header.dysymtab.tocoff = reader.Read32(); + header.dysymtab.ntoc = reader.Read32(); + header.dysymtab.modtaboff = reader.Read32(); + header.dysymtab.nmodtab = reader.Read32(); + header.dysymtab.extrefsymoff = reader.Read32(); + header.dysymtab.nextrefsyms = reader.Read32(); + header.dysymtab.indirectsymoff = reader.Read32(); + header.dysymtab.nindirectsyms = reader.Read32(); + header.dysymtab.extreloff = reader.Read32(); + header.dysymtab.nextrel = reader.Read32(); + header.dysymtab.locreloff = reader.Read32(); + header.dysymtab.nlocrel = reader.Read32(); + header.dysymPresent = true; + break; + case LC_DYLD_CHAINED_FIXUPS: + header.chainedFixups.dataoff = reader.Read32(); + header.chainedFixups.datasize = reader.Read32(); + header.chainedFixupsPresent = true; + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + header.dyldInfo.rebase_off = reader.Read32(); + header.dyldInfo.rebase_size = reader.Read32(); + header.dyldInfo.bind_off = reader.Read32(); + header.dyldInfo.bind_size = reader.Read32(); + header.dyldInfo.weak_bind_off = reader.Read32(); + header.dyldInfo.weak_bind_size = reader.Read32(); + header.dyldInfo.lazy_bind_off = reader.Read32(); + header.dyldInfo.lazy_bind_size = reader.Read32(); + header.dyldInfo.export_off = reader.Read32(); + header.dyldInfo.export_size = reader.Read32(); + header.exportTrie.dataoff = header.dyldInfo.export_off; + header.exportTrie.datasize = header.dyldInfo.export_size; + header.exportTriePresent = true; + header.dyldInfoPresent = true; + break; + case LC_DYLD_EXPORTS_TRIE: + header.exportTrie.dataoff = reader.Read32(); + header.exportTrie.datasize = reader.Read32(); + header.exportTriePresent = true; + break; + case LC_THREAD: + case LC_UNIXTHREAD: + /*while (reader.GetOffset() < nextOffset) + { + + thread_command thread; + thread.flavor = reader.Read32(); + thread.count = reader.Read32(); + switch (m_archId) + { + case MachOx64: + m_logger->LogDebug("x86_64 Thread state\n"); + if (thread.flavor != X86_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statex64, sizeof(thread.statex64)); + header.entryPoints.push_back({thread.statex64.rip, false}); + break; + case MachOx86: + m_logger->LogDebug("x86 Thread state\n"); + if (thread.flavor != X86_THREAD_STATE32) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statex86, sizeof(thread.statex86)); + header.entryPoints.push_back({thread.statex86.eip, false}); + break; + case MachOArm: + m_logger->LogDebug("Arm Thread state\n"); + if (thread.flavor != _ARM_THREAD_STATE) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //This wont be big endian so we can just read the whole thing + reader.Read(&thread.statearmv7, sizeof(thread.statearmv7)); + header.entryPoints.push_back({thread.statearmv7.r15, false}); + break; + case MachOAarch64: + case MachOAarch6432: + m_logger->LogDebug("Aarch64 Thread state\n"); + if (thread.flavor != _ARM_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + reader.Read(&thread.stateaarch64, sizeof(thread.stateaarch64)); + header.entryPoints.push_back({thread.stateaarch64.pc, false}); + break; + case MachOPPC: + m_logger->LogDebug("PPC Thread state\n"); + if (thread.flavor != PPC_THREAD_STATE) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + //Read individual entries for endian reasons + header.entryPoints.push_back({reader.Read32(), false}); + (void)reader.Read32(); + (void)reader.Read32(); + //Read the rest of the structure + (void)reader.Read(&thread.stateppc.r1, sizeof(thread.stateppc) - (3 * 4)); + break; + case MachOPPC64: + m_logger->LogDebug("PPC64 Thread state\n"); + if (thread.flavor != PPC_THREAD_STATE64) + { + reader.SeekRelative(thread.count * sizeof(uint32_t)); + break; + } + header.entryPoints.push_back({reader.Read64(), false}); + (void)reader.Read64(); + (void)reader.Read64(); // Stack start + (void)reader.Read(&thread.stateppc64.r1, sizeof(thread.stateppc64) - (3 * 8)); + break; + default: + m_logger->LogError("Unknown archid: %x", m_archId); + } + + }*/ + break; + case LC_LOAD_DYLIB: + { + uint32_t offset = reader.Read32(); + if (offset < nextOffset) + { + reader.Seek(curOffset + offset); + std::string libname = reader.ReadCString(reader.GetOffset()); + header.dylibs.push_back(libname); + } + } + break; + case LC_BUILD_VERSION: + { + // m_logger->LogDebug("LC_BUILD_VERSION:"); + header.buildVersion.platform = reader.Read32(); + header.buildVersion.minos = reader.Read32(); + header.buildVersion.sdk = reader.Read32(); + header.buildVersion.ntools = reader.Read32(); + // m_logger->LogDebug("Platform: %s", BuildPlatformToString(header.buildVersion.platform).c_str()); + // m_logger->LogDebug("MinOS: %s", BuildToolVersionToString(header.buildVersion.minos).c_str()); + // m_logger->LogDebug("SDK: %s", BuildToolVersionToString(header.buildVersion.sdk).c_str()); + for (uint32_t j = 0; (i < header.buildVersion.ntools) && (j < 10); j++) + { + uint32_t tool = reader.Read32(); + uint32_t version = reader.Read32(); + header.buildToolVersions.push_back({tool, version}); + // m_logger->LogDebug("Build Tool: %s: %s", BuildToolToString(tool).c_str(), + // BuildToolVersionToString(version).c_str()); + } + break; + } + case LC_FILESET_ENTRY: + { + throw ReadException(); + } + default: + // m_logger->LogDebug("Unhandled command: %s : %" PRIu32 "\n", CommandToString(load.cmd).c_str(), + // load.cmdsize); + break; + } + if (reader.GetOffset() != nextOffset) + { + // m_logger->LogDebug("Didn't parse load command: %s fully %" PRIx64 ":%" PRIxPTR, + // CommandToString(load.cmd).c_str(), reader.GetOffset(), nextOffset); + } + reader.Seek(nextOffset); + } + + for (auto& section : header.sections) + { + char sectionName[17]; + memcpy(sectionName, section.sectname, sizeof(section.sectname)); + sectionName[16] = 0; + + 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 diff --git a/view/kernelcache/ui/KernelCacheUINotifications.cpp b/view/kernelcache/ui/KernelCacheUINotifications.cpp index 6bd8fdf0..9d7e17a9 100644 --- a/view/kernelcache/ui/KernelCacheUINotifications.cpp +++ b/view/kernelcache/ui/KernelCacheUINotifications.cpp @@ -3,13 +3,15 @@ // #include "KernelCacheUINotifications.h" -#include <QLayout> #include <kernelcacheapi.h> #include "ui/sidebar.h" #include "ui/linearview.h" #include "ui/viewframe.h" #include "progresstask.h" +using namespace BinaryNinja; +using namespace KernelCacheAPI; + UINotifications* UINotifications::m_instance = nullptr; void UINotifications::init() @@ -18,75 +20,94 @@ void UINotifications::init() UIContext::registerNotification(m_instance); } - void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const QString& type) { if (!frame) return; - // FIXME there is a bv func for this - static std::function<bool(Ref<BinaryView>, uint64_t)> isAddrMapped = [](Ref<BinaryView> view, uint64_t addr) { - if (view && view->GetTypeName() == KC_VIEW_NAME) + auto view = frame->getCurrentBinaryView(); + if (!view || view->GetTypeName() != KC_VIEW_NAME) + return; + + auto viewInt = frame->getCurrentViewInterface(); + if (!viewInt) + return; + + auto ah = viewInt->actionHandler(); + // Check to see if we have already bound these actions. + if (ah->isBoundAction("Load Image by Name")) + return; + + static auto loadImageAtAddr = [](BinaryView& view, uint64_t addr) { + auto controller = KernelCacheController::GetController(view); + if (!controller) + return; + if (auto foundImage = controller->GetImageContaining(addr)) { - for (const auto& seg : view->GetSegments()) - { - if (seg->GetStart() <= addr && seg->GetEnd() > addr) - return true; - } + // If we did not load the image, then we don't need to run analysis. + if (!controller->ApplyImage(view, *foundImage)) + return; + view.AddAnalysisOption("linearsweep"); + view.UpdateAnalysis(); } - return false; }; - auto view = frame->getCurrentBinaryView(); - if (view && view->GetTypeName() == KC_VIEW_NAME) - { - if (auto viewInt = frame->getCurrentViewInterface()) + auto loadImageAddrAction = [](const UIActionContext& ctx) { + uint64_t addr = 0; + if (GetAddressInput(addr, "Address", "Address")) { - auto ah = viewInt->actionHandler(); - if (!ah->isBoundAction("KC Load IMGHERE")) - { - ah->bindAction("KC Load IMGHERE", - UIAction( - [](const UIActionContext& ctx) { - Ref<BinaryView> view = ctx.binaryView; - Ref<KernelCacheAPI::KernelCache> cache = new KernelCacheAPI::KernelCache(view); - uint64_t addr = ctx.token.token.value; - if (addr) - { - BackgroundThread::create(ctx.context->mainWindow())->thenBackground( - [cache=cache, addr=addr]() { - cache->LoadImageContainingAddress(addr); - })->start(); - } - }, - [](const UIActionContext& ctx) { - Ref<KernelCacheAPI::KernelCache> cache = new KernelCacheAPI::KernelCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (isAddrMapped(ctx.binaryView, addr)) - return false; - return addr && cache->GetImageNameForAddress(addr) != ""; // bool - })); - ah->setActionDisplayName("KC Load IMGHERE", [](const UIActionContext& ctx) { - Ref<KernelCacheAPI::KernelCache> cache = new KernelCacheAPI::KernelCache(ctx.binaryView); - uint64_t addr = ctx.token.token.value; - if (addr) - return QString("Load ") + cache->GetImageNameForAddress(addr).c_str(); - return QString("Error"); - }); - if (auto linearView = qobject_cast<LinearView*>(viewInt->widget())) - { - linearView->contextMenu().addAction("KC Load IMGHERE", KC_VIEW_NAME); - linearView->contextMenu().setGroupOrdering(KC_VIEW_NAME, 0); - } - } + BackgroundThread::create(ctx.context->mainWindow()) + ->thenBackground([ctx, addr]() { + loadImageAtAddr(*ctx.binaryView, addr); + })->start(); } + }; + + auto loadImageTokenAction = [](const UIActionContext& ctx) { + BackgroundThread::create(ctx.context->mainWindow()) + ->thenBackground([ctx](){ loadImageAtAddr(*ctx.binaryView, ctx.token.token.value); }) + ->start(); + }; + + auto isValidUnloadedImageAction = [](const UIActionContext& ctx) { + uint64_t addr = ctx.token.token.value; + // Check if the image is already loaded in the view. + if (!ctx.binaryView->GetSectionsAt(addr).empty()) + return false; + auto controller = KernelCacheController::GetController(*ctx.binaryView); + if (!controller) + return false; + return controller->GetImageContaining(addr).has_value(); + }; + + ah->bindAction("Load Image by Address", UIAction(loadImageAddrAction)); + + ah->bindAction("Load IMGHERE", UIAction(loadImageTokenAction, isValidUnloadedImageAction)); + + ah->setActionDisplayName("Load IMGHERE", [](const UIActionContext& ctx) { + auto controller = KernelCacheController::GetController(*ctx.binaryView); + if (!controller) + return QString("NO CONTROLLER"); + uint64_t addr = ctx.token.token.value; + auto image = controller->GetImageContaining(addr); + if (!image) + return QString("NO IMAGE"); + return QString("Load ") + image->name.c_str(); + }); + + // Finally add the actions to the context menu. + if (auto linearView = qobject_cast<LinearView*>(viewInt->widget())) + { + constexpr auto groupOneName = KC_VIEW_NAME; + constexpr auto groupTwoName = KC_VIEW_NAME "2"; + linearView->contextMenu().addAction("Load IMGHERE", groupOneName); + linearView->contextMenu().addAction("Load Image by Address", groupTwoName); + linearView->contextMenu().setGroupOrdering(groupOneName, 0); + linearView->contextMenu().setGroupOrdering(groupTwoName, 1); } } + void UINotifications::OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) { - if (frame->getCurrentBinaryView()) - { - // Register BD notifications. We dont use them right now. - } UIContextNotification::OnAfterOpenFile(context, file, frame); } diff --git a/view/kernelcache/ui/kctriage.cpp b/view/kernelcache/ui/kctriage.cpp index c28c323a..da2f2270 100644 --- a/view/kernelcache/ui/kctriage.cpp +++ b/view/kernelcache/ui/kctriage.cpp @@ -1,8 +1,9 @@ +#include <QHeaderView> #include <QMessageBox> -#include <QPainter> -#include <cmath> -#include "globalarea.h" +#include <utility> #include "kctriage.h" +#include "globalarea.h" +#include "symboltable.h" #include "ui/fontsettings.h" using namespace BinaryNinja; @@ -36,7 +37,7 @@ void KCTriageViewType::Register() } -KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), View(), m_data(data), m_cache(new KernelCache(data)) +KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent), m_data(std::move(data)) { setBinaryDataNavigable(false); setupView(this); @@ -56,6 +57,11 @@ KCTriageView::KCTriageView(QWidget* parent, BinaryViewRef data) : QWidget(parent m_layout->addWidget(m_triageTabs); setLayout(m_layout); + // In case we have already initialized the controller (user has opened this view type again) + // we will call refresh data. If this is the first triage view constructed (i.e. before view init) then this + // will do nothing. + RefreshData(); + m_triageTabs->selectWidget(defaultWidget); } @@ -66,43 +72,88 @@ KCTriageView::~KCTriageView() } -void KCTriageView::loadImagesWithAddr(const std::vector<uint64_t>& addresses) { - if (!m_cache) +void KCTriageView::loadImagesWithAddr(const std::vector<uint64_t>& addresses, bool includeDependencies) { + auto controller = KernelCacheController::GetController(*m_data); + if (!controller) return; - std::map<uint64_t, std::string> images; + // TODO: NOTE ABOUT `IsImageLoaded` BEING COMMENTED OUT. PLEASE READ. + // TODO: Because commiting undo actions will use main thread to synchronize we must not be holding any locks + // TODO: This can really only ever be removed if: + // TODO: 1. we can set a user function type without creating an undo action, basically like the rest of shared cache + // TODo: use an auto function or some hack to get the user function but without the undo action. + // TODO: 2. we can use the undo buffer from any thread and not just the main thread + // TODO: I have exhausted all other options, this is a serious issue we should address soon. + typedef std::vector<CacheImage> ImageList; + ImageList images = {}; for (const uint64_t& addr : addresses) { - auto imageName = m_cache->GetImageNameForAddress(addr); - if (!imageName.empty() && !m_cache->IsImageLoaded(addr)) + std::optional<CacheImage> image = controller->GetImageContaining(addr); + if (image.has_value()) { - images.insert({addr, imageName}); + // Only try to load if we have not already. + // if (!controller->IsImageLoaded(*image)) + images.emplace_back(*image); + + // TODO: We currently only add direct dependencies, may want to make the depth configurable? + if (includeDependencies) + { + auto dependencies = controller->GetImageDependencies(*image); + for (const auto& depName : dependencies) + { + auto depImage = controller->GetImageWithName(depName); + if (depImage.has_value()/* && !controller->IsImageLoaded(*depImage) */) + { + images.emplace_back(*depImage); + } + } + } } } // Don't create a worker action if we don't have any images. if (images.empty()) return; + Ref<BackgroundTask> imageLoadTask = new BackgroundTask("Loading images...", true); + + // Apply the images in a future then update the triage view and run analysis. + QPointer<QFutureWatcher<ImageList>> watcher = new QFutureWatcher<ImageList>(this); + connect(watcher, &QFutureWatcher<ImageList>::finished, this, [watcher, this]() { + if (watcher) + { + auto loadedImages = watcher->result(); + if (loadedImages.empty()) + return; - WorkerPriorityEnqueue([this, images]() { - size_t loadedImages = 0; - const std::string initialLoad = fmt::format("Loading images... (0/{})", images.size()); - auto imageLoadTask = BackgroundTask(initialLoad, true); + // Update the triage to display the images as loaded. + for (const auto& image : loadedImages) + setImageLoaded(image.headerVirtualAddress); - for (const auto& [addr, imageName] : images) + // Run analysis. + this->m_data->AddAnalysisOption("linearsweep"); + this->m_data->UpdateAnalysis(); + } + }); + QFuture<ImageList> future = QtConcurrent::run([this, controller, images, imageLoadTask]() { + ImageList loadedImages = {}; + for (const auto& image : images) { - if (imageLoadTask.IsCancelled()) + if (imageLoadTask->IsCancelled() || QThread::currentThread()->isInterruptionRequested()) break; - const std::string newLoad = fmt::format("Loading images... ({}/{})", loadedImages++, images.size()); - imageLoadTask.SetProgressText(newLoad); - if (m_cache->LoadImageWithInstallName(imageName)) - setImageLoaded(addr); + std::string newLoad = fmt::format("Loading images... ({}/{})", loadedImages.size(), images.size()); + imageLoadTask->SetProgressText(newLoad); + if (controller->ApplyImage(*this->m_data, image)) + loadedImages.emplace_back(image); + } + imageLoadTask->Finish(); + return loadedImages; + }); + watcher->setFuture(future); + connect(this, &QObject::destroyed, this, [watcher, imageLoadTask]() { + if (watcher && watcher->isRunning()) { + watcher->cancel(); + imageLoadTask->Cancel(); } - imageLoadTask.Finish(); - - // We have loaded images, lets make sure to update analysis! - this->m_data->AddAnalysisOption("linearsweep"); - this->m_data->UpdateAnalysis(); }); } @@ -131,7 +182,7 @@ QWidget* KCTriageView::initImageTable() m_imageTable = new FilterableTableView(this); m_imageModel = new QStandardItemModel(0, 3, m_imageTable); - m_imageModel->setHorizontalHeaderLabels({"VM Address", "Loaded", "Name"}); + m_imageModel->setHorizontalHeaderLabels({"Address", "Loaded", "Name"}); // Apply custom column styling m_imageTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_imageTable)); @@ -157,6 +208,7 @@ QWidget* KCTriageView::initImageTable() QAction noSelectionAction("No Images Selected", m_imageTable); QAction loadImagesAction("", m_imageTable); + QAction loadImagesWithDepsAction("", m_imageTable); if (selectedCount == 0) { noSelectionAction.setEnabled(false); @@ -168,57 +220,22 @@ QWidget* KCTriageView::initImageTable() QString loadActionText = (selectedCount == 1) ? "Load Selected Image" : QString("Load %1 Selected Images").arg(selectedCount); loadImagesAction.setText(loadActionText); connect(&loadImagesAction, &QAction::triggered, [this, addresses]() { - loadImagesWithAddr(addresses); + loadImagesWithAddr(addresses, false); }); contextMenu.addAction(&loadImagesAction); + + // Format action text for loading selected images with dependencies + QString loadWithDepsActionText = (selectedCount == 1) ? "Load Selected Image and Dependencies" : QString("Load %1 Selected Images and Dependencies").arg(selectedCount); + loadImagesWithDepsAction.setText(loadWithDepsActionText); + connect(&loadImagesWithDepsAction, &QAction::triggered, [this, addresses]() { + this->loadImagesWithAddr(addresses, true); + }); + contextMenu.addAction(&loadImagesWithDepsAction); } contextMenu.exec(m_imageTable->viewport()->mapToGlobal(pos)); }); - BackgroundThread::create(m_imageTable)->thenBackground([this](const QVariant var) { - QVariantList rows; - - auto images = m_cache->GetImages(); - - auto newHeaders = std::make_shared<std::vector<KernelCacheMachOHeader>>(); - newHeaders->reserve(images.size()); - - for (const auto& img : images) - { - if (auto header = m_cache->GetMachOHeaderForImage(img.name); header) - { - newHeaders->push_back(*header); - rows.push_back(QList<QVariant>{ - QString("0x%1").arg(header->textBase, 0, 16), - QString(""), - QString::fromStdString(img.name) - }); - } - } - - std::unique_lock<std::mutex> lock(m_headersMutex); - m_headers.swap(newHeaders); - - return QVariant(rows); - })->thenMainThread([this](const QVariant var) { - QVariantList rows = var.toList(); - - if (m_imageModel->rowCount() > 0) - m_imageModel->removeRows(0, m_imageModel->rowCount()); - - for (const QVariant &rowVariant : rows) { - QVariantList row = rowVariant.toList(); - - QList<QStandardItem*> items; - for (const QVariant &cellValue : row) - items.append(new QStandardItem(cellValue.toString())); - - m_imageModel->appendRow(items); - m_imageTable->resizeColumnsToContents(); - } - })->start(); - auto loadImageButton = new QPushButton(); connect(loadImageButton, &QPushButton::clicked, [this](bool) { // Collect only visible selected rows @@ -239,6 +256,13 @@ QWidget* KCTriageView::initImageTable() }); loadImageButton->setText(" Load Selected "); + auto refreshDataButton = new QPushButton(); + { + // TODO: Might want to introduce a cooldown for this button (if we even keep it) + connect(refreshDataButton, &QPushButton::clicked, [this](bool) { RefreshData(); }); + refreshDataButton->setText("Refresh"); + } // refreshDataButton + auto loadImageFilterEdit = new FilterEdit(m_imageTable); connect(loadImageFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { m_imageTable->setFilter(filter.toStdString()); @@ -255,6 +279,7 @@ QWidget* KCTriageView::initImageTable() auto loadImageFooterLayout = new QHBoxLayout; loadImageFooterLayout->addWidget(loadImageButton); + loadImageFooterLayout->addWidget(refreshDataButton); loadImageFooterLayout->setAlignment(Qt::AlignLeft); loadImageLayout->addLayout(loadImageFooterLayout); @@ -285,7 +310,10 @@ QWidget* KCTriageView::initImageTable() void KCTriageView::initSymbolTable() { - m_symbolTable = new SymbolTableView(this, m_cache); + m_symbolTable = new SymbolTableView(this); + + // Apply custom column styling + m_symbolTable->setItemDelegateForColumn(0, new AddressColorDelegate(m_symbolTable)); auto symbolFilterEdit = new FilterEdit(m_symbolTable); connect(symbolFilterEdit, &FilterEdit::textChanged, [this](const QString& filter) { @@ -308,8 +336,14 @@ void KCTriageView::initSymbolTable() currentImageLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); connect(m_symbolTable->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [this, currentImageLabel](const QModelIndex ¤t, const QModelIndex &) { auto symbol = m_symbolTable->getSymbolAtRow(current.row()); - auto imageName = m_cache->GetImageNameForAddress(symbol.address); - currentImageLabel->setText("Image: " + QString::fromStdString(imageName)); + auto controller = KernelCacheController::GetController(*this->m_data); + if (!controller) + return; + auto image = controller->GetImageContaining(symbol.address); + if (image) + currentImageLabel->setText("Image: " + QString::fromStdString(image->name)); + else + currentImageLabel->setText(""); }); auto symbolFooterLayout = new QHBoxLayout; @@ -325,27 +359,29 @@ void KCTriageView::initSymbolTable() auto symbolWidget = new QWidget; symbolWidget->setLayout(symbolLayout); - std::function<void(uint64_t)> navigateToAddress = [=, this](uint64_t addr) { - ExecuteOnMainThread([addr, this](){ - if (Settings::Instance()->Get<bool>("ui.view.graph.preferred")) - m_data->Navigate("Graph:KCView", addr); - else - m_data->Navigate("Linear:KCView", addr); - }); - }; - connect(m_symbolTable, &SymbolTableView::activated, this, [=, this](const QModelIndex& index) { auto symbol = m_symbolTable->getSymbolAtRow(index.row()); - WorkerPriorityEnqueue([this, symbol, navigateToAddress]() { - if (m_data->IsValidOffset(symbol.address)) - navigateToAddress(symbol.address); - else + auto dialog = new QMessageBox(this); + + auto controller = KernelCacheController::GetController(*this->m_data); + if (!controller) + return; + + auto image = controller->GetImageContaining(symbol.address); + if (!image.has_value()) + return; + + dialog->setText("Load " + QString::fromStdString(image->name) + "?"); + dialog->setStandardButtons(QMessageBox::Yes | QMessageBox::No); + + connect(dialog, &QMessageBox::buttonClicked, this, [=](QAbstractButton* button) { - m_cache->LoadImageWithInstallName(symbol.image); - navigateToAddress(symbol.address); - } - }); + if (button == dialog->button(QMessageBox::Yes)) + loadImagesWithAddr({image->headerFileAddress}); + }); + + dialog->exec(); }); m_triageTabs->addTab(symbolWidget, "Symbols"); @@ -375,3 +411,45 @@ uint64_t KCTriageView::getCurrentOffset() { return 0; } + + +SelectionInfoForXref KCTriageView::getSelectionForXref() +{ + // TODO: If we are in the symbols view we _can_ actually show a useful xref to the selected symbols. + SelectionInfoForXref selection = {}; + selection.addrValid = false; + return selection; +} + + +void KCTriageView::OnAfterOpenFile(UIContext *context, FileContext *file, ViewFrame *frame) +{ + RefreshData(); + UIContextNotification::OnAfterOpenFile(context, file, frame); +} + + +// Called when shared cache information has changed. +void KCTriageView::RefreshData() +{ + // Controller should be available after view init. + auto controller = KernelCacheController::GetController(*m_data); + if (!controller) + return; + + m_imageModel->setRowCount(0); + for (const auto& img : controller->GetImages()) + { + m_imageModel->appendRow({ + new QStandardItem(QString("0x%1").arg(img.headerVirtualAddress, 0, 16)), + new QStandardItem(""), + new QStandardItem(QString::fromStdString(img.name)) + }); + } + + // Set images as loaded (updating the relevant image row) + for (const auto& loadedImg : controller->GetLoadedImages()) + setImageLoaded(loadedImg.headerVirtualAddress); + + m_symbolTable->populateSymbols(*m_data); +} diff --git a/view/kernelcache/ui/kctriage.h b/view/kernelcache/ui/kctriage.h index 2bd106b6..d918cad6 100644 --- a/view/kernelcache/ui/kctriage.h +++ b/view/kernelcache/ui/kctriage.h @@ -6,18 +6,19 @@ #include <QStyledItemDelegate> #include <QTableView> #include <binaryninjaapi.h> -#include <kernelcacheapi.h> #include <progresstask.h> +#include <kernelcacheapi.h> #include "filter.h" +#include "symboltable.h" #include "ui/fontsettings.h" #include "uicontext.h" #include "uitypes.h" #include "viewframe.h" #ifndef BINARYNINJA_KCTRIAGE_H -#define BINARYNINJA_KCTRIAGE_H - + #define BINARYNINJA_KCTRIAGE_H +using namespace BinaryNinja; using namespace KernelCacheAPI; @@ -40,30 +41,15 @@ public: }; -class MonospaceFontDelegate : public QStyledItemDelegate { -public: - explicit MonospaceFontDelegate(QObject* parent = nullptr) : QStyledItemDelegate(parent) {} - - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override { - QStyleOptionViewItem opt = option; - initStyleOption(&opt, index); - - opt.font = getMonospaceFont(qobject_cast<QWidget*>(parent())); - - QStyledItemDelegate::paint(painter, opt, index); - } -}; - - class LoadedDelegate : public QItemDelegate { -Q_OBJECT + Q_OBJECT public: explicit LoadedDelegate(QObject* parent = nullptr) : QItemDelegate(parent) {} void paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const override + const QModelIndex &index) const override { if (!index.isValid()) return; @@ -98,7 +84,7 @@ public: } QSize sizeHint(const QStyleOptionViewItem &option, - const QModelIndex &index) const override + const QModelIndex &index) const override { Q_UNUSED(option); Q_UNUSED(index); @@ -113,7 +99,6 @@ public: }; - class FilterableTableView : public QTableView, public FilterTarget { Q_OBJECT @@ -190,165 +175,11 @@ signals: }; -class SymbolTableProxyModel : public QSortFilterProxyModel -{ -Q_OBJECT - -public: - explicit SymbolTableProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent), m_timer(new QTimer(this)) - { - m_timer->setSingleShot(true); - connect(m_timer, &QTimer::timeout, this, &SymbolTableProxyModel::delayedFilterChanged); - } - - void setFilterString(const QString& filter) - { - QRegularExpression newRegEx(QRegularExpression::escape(filter), QRegularExpression::CaseInsensitiveOption); - if (m_filter != newRegEx) { - m_filter = std::move(newRegEx); - m_timer->start(200); - } - } - -protected: - bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override - { - if (m_filter.pattern().isEmpty()) - return true; - - for (int column = 0; column < sourceModel()->columnCount(source_parent); ++column) - { - QModelIndex index = sourceModel()->index(source_row, column, source_parent); - QString data = sourceModel()->data(index).toString(); - if (m_filter.match(data).hasMatch()) - return true; - } - return false; - } - -private slots: - void delayedFilterChanged() - { - invalidateFilter(); - } - -private: - QRegularExpression m_filter; - QTimer* m_timer; -}; - - -class SymbolTableView : public QTableView, public FilterTarget -{ -Q_OBJECT - friend class SymbolTableModel; - - std::vector<KernelCacheAPI::KCSymbol> m_symbols; - QStandardItemModel* m_model; - SymbolTableProxyModel* m_proxyModel; - -public: - SymbolTableView(QWidget* parent, Ref<KernelCache>& cache) - : QTableView(parent), m_model(new QStandardItemModel(this)), m_proxyModel(new SymbolTableProxyModel(this)) - { - m_proxyModel->setSourceModel(m_model); - setModel(m_proxyModel); - - // Set up the headers - m_model->setColumnCount(3); - m_model->setHorizontalHeaderLabels({"Address", "Name", "Image"}); - setFont(getMonospaceFont(parent)); - setItemDelegateForColumn(0, new AddressColorDelegate(this)); - setItemDelegateForColumn(1, new MonospaceFontDelegate(this)); - setItemDelegateForColumn(2, new MonospaceFontDelegate(this)); - - // Configure view settings - horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); - horizontalHeader()->setSectionResizeMode(1, QHeaderView::Interactive); - horizontalHeader()->resizeSection(1, 400); - horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); - setEditTriggers(QAbstractItemView::NoEditTriggers); - setSelectionBehavior(QAbstractItemView::SelectRows); - setSelectionMode(QAbstractItemView::SingleSelection); - verticalHeader()->setVisible(false); - - setSortingEnabled(true); - - BackgroundThread::create(this)->thenBackground([this, cache](){ - m_symbols = cache->LoadAllSymbolsAndWait(); - })->thenMainThread([this](){ - updateSymbols(); - })->start(); - } - - ~SymbolTableView() override = default; - - void updateSymbols() - { - m_model->removeRows(0, m_model->rowCount()); - for (const auto& symbol : m_symbols) - { - QList<QStandardItem*> row; - row << new QStandardItem(QString("0x%1").arg(symbol.address, 0, 16)) - << new QStandardItem(QString::fromStdString(symbol.name)) - << new QStandardItem(QString::fromStdString(symbol.image)); - m_model->appendRow(row); - } - } - - KernelCacheAPI::KCSymbol getSymbolAtRow(int row) const - { - QModelIndex proxyIndex = m_proxyModel->index(row, 0); - QModelIndex sourceIndex = m_proxyModel->mapToSource(proxyIndex); - return m_symbols[sourceIndex.row()]; - } - - void scrollToFirstItem() override - { - scrollToTop(); - } - - void scrollToCurrentItem() override - { - scrollTo(selectionModel()->currentIndex()); - } - - void selectFirstItem() override - { - if (m_proxyModel->rowCount() > 0) { - QModelIndex idx = m_proxyModel->index(0, 0); - if (idx.isValid()) { - selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); - setCurrentIndex(idx); - } - } - } - - void activateFirstItem() override - { - if (m_proxyModel->rowCount() > 0) { - QModelIndex idx = m_proxyModel->index(0, 0); - if (idx.isValid()) { - setCurrentIndex(idx); - emit activated(idx); - } - } - } - - void setFilter(const std::string& text) override - { - m_proxyModel->setFilterString(QString::fromStdString(text)); - } -}; - - class KCTriageView : public QWidget, public View, public UIContextNotification { BinaryViewRef m_data; QVBoxLayout* m_layout; - Ref<KernelCacheAPI::KernelCache> m_cache; - SplitTabWidget* m_triageTabs; DockableTabCollection* m_triageCollection; @@ -357,8 +188,10 @@ class KCTriageView : public QWidget, public View, public UIContextNotification SymbolTableView* m_symbolTable; - std::mutex m_headersMutex; - std::shared_ptr<std::vector<KernelCacheAPI::KernelCacheMachOHeader>> m_headers; + FilterableTableView* m_mappingTable; + QStandardItemModel* m_mappingModel; + + QStandardItemModel* m_regionModel; public: KCTriageView(QWidget* parent, BinaryViewRef data); @@ -368,10 +201,14 @@ public: QFont getFont() override; bool navigate(uint64_t offset) override; uint64_t getCurrentOffset() override; + SelectionInfoForXref getSelectionForXref() override; + + void OnAfterOpenFile(UIContext* context, FileContext* file, ViewFrame* frame) override; + void RefreshData(); private: - void loadImagesWithAddr(const std::vector<uint64_t>& addresses); - void setImageLoaded(const uint64_t imageHeaderAddr); + void loadImagesWithAddr(const std::vector<uint64_t>& addresses, bool includeDependencies = false); + void setImageLoaded(uint64_t imageHeaderAddr); QWidget* initImageTable(); void initSymbolTable(); }; @@ -386,5 +223,4 @@ public: static void Register(); }; - #endif // BINARYNINJA_KCTRIAGE_H diff --git a/view/kernelcache/ui/symboltable.cpp b/view/kernelcache/ui/symboltable.cpp new file mode 100644 index 00000000..5c86b3b4 --- /dev/null +++ b/view/kernelcache/ui/symboltable.cpp @@ -0,0 +1,216 @@ +#include <progresstask.h> +#include "symboltable.h" + +#include <QHeaderView> + +#include "ui/fontsettings.h" + +#include "binaryninjaapi.h" + +using namespace BinaryNinja; +using namespace KernelCacheAPI; + + +SymbolTableModel::SymbolTableModel(SymbolTableView* parent) + : QAbstractTableModel(parent), m_parent(parent) { + // TODO: Need to implement updating this font if it is changed by the user + m_font = getMonospaceFont(parent); +} + + +int SymbolTableModel::rowCount(const QModelIndex& parent) const { + Q_UNUSED(parent); + return static_cast<int>(m_modelSymbols.size()); +} + + +int SymbolTableModel::columnCount(const QModelIndex& parent) const { + Q_UNUSED(parent); + // We have 3 columns: Address, Type, Name + return 3; +} + + +QVariant SymbolTableModel::data(const QModelIndex& index, int role) const { + if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::FontRole)) { + return QVariant(); + } + + switch (role) + { + case Qt::DisplayRole: + { + auto symbol = symbolAt(index.row()); + auto symbolType = GetSymbolTypeAsString(symbol.type); + + switch (index.column()) + { + case 0: // Address column + return QString("0x%1").arg(symbol.address, 0, 16); // Display address as hexadecimal + case 1: // Type column + return QString::fromUtf8(symbolType.c_str(), symbolType.size()); + case 2: // Name column + return QString::fromUtf8(symbol.name.c_str(), symbol.name.size()); + default: + return QVariant(); + } + } + case Qt::FontRole: + return m_font; + default: + return QVariant(); + } +} + + +QVariant SymbolTableModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (role != Qt::DisplayRole || orientation != Qt::Horizontal) { + return QVariant(); + } + + switch (section) { + case 0: + return QString("Address"); + case 1: + return QString("Type"); + case 2: + return QString("Name"); + default: + return QVariant(); + } +} + + +void SymbolTableModel::sort(int column, Qt::SortOrder order) +{ + beginResetModel(); + + std::function<bool(const CacheSymbol&, const CacheSymbol&)> comparator; + + switch (column) + { + case 0: // Address column + comparator = [](const CacheSymbol& a, const CacheSymbol& b) { + return a.address < b.address; + }; + break; + case 1: // Type column + comparator = [](const CacheSymbol& a, const CacheSymbol& b) { + return GetSymbolTypeAsString(a.type) < GetSymbolTypeAsString(b.type); + }; + break; + case 2: // Name column + comparator = [](const CacheSymbol& a, const CacheSymbol& b) { + return a.name < b.name; + }; + break; + default: + endResetModel(); + return; + } + + if (order == Qt::DescendingOrder) + { + std::sort(m_modelSymbols.begin(), m_modelSymbols.end(), + [&comparator](const CacheSymbol& a, const CacheSymbol& b) { + return comparator(b, a); + }); + } + else + { + std::sort(m_modelSymbols.begin(), m_modelSymbols.end(), comparator); + } + + endResetModel(); +} + + +void SymbolTableModel::updateSymbols(std::vector<CacheSymbol>&& symbols) +{ + m_preparedSymbols = symbols; + setFilter(m_filter); +} + + +const CacheSymbol& SymbolTableModel::symbolAt(int row) const +{ + return m_modelSymbols.at(row); +} + + +void SymbolTableModel::setFilter(std::string text) +{ + beginResetModel(); + + m_filter = text; + m_modelSymbols = {}; + + // Skip filtering if no filter applied. + if (!m_filter.empty()) + { + m_modelSymbols.reserve(m_preparedSymbols.size()); + for (const auto& symbol : m_preparedSymbols) + if (((std::string_view)symbol.name).find(m_filter) != std::string::npos) + m_modelSymbols.push_back(symbol); + m_modelSymbols.shrink_to_fit(); + } + else + { + m_modelSymbols = m_preparedSymbols; + } + + endResetModel(); +} + + +SymbolTableView::SymbolTableView(QWidget* parent) + : QTableView(parent), m_model(new SymbolTableModel(this)) { + + // Set up the filter model + setModel(m_model); + + // Configure view settings + horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); + horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setSelectionBehavior(QAbstractItemView::SelectRows); + setSelectionMode(QAbstractItemView::SingleSelection); + verticalHeader()->setVisible(false); + + setSortingEnabled(true); +} + +SymbolTableView::~SymbolTableView() { + delete m_model; +} + +void SymbolTableView::populateSymbols(BinaryView &view) +{ + if (auto controller = KernelCacheController::GetController(view)) { + typedef std::vector<CacheSymbol> SymbolList; + // Retrieve the symbols from the controller in a future than pass that to the model. + QPointer<QFutureWatcher<SymbolList>> watcher = new QFutureWatcher<SymbolList>(this); + connect(watcher, &QFutureWatcher<SymbolList>::finished, this, [watcher, this]() { + if (watcher) + { + auto symbols = watcher->result(); + m_model->updateSymbols(std::move(symbols)); + } + }); + QFuture<SymbolList> future = QtConcurrent::run([controller]() { + return controller->GetSymbols(); + }); + watcher->setFuture(future); + connect(this, &QObject::destroyed, this, [watcher]() { + if (watcher && watcher->isRunning()) { + watcher->cancel(); + watcher->waitForFinished(); + } + }); + } +} + +void SymbolTableView::setFilter(const std::string& filter) { + m_model->setFilter(filter); +} diff --git a/view/kernelcache/ui/symboltable.h b/view/kernelcache/ui/symboltable.h new file mode 100644 index 00000000..866390c7 --- /dev/null +++ b/view/kernelcache/ui/symboltable.h @@ -0,0 +1,102 @@ +#pragma once + +#include <kernelcacheapi.h> +#include "viewframe.h" + +#include <QTableView> +#include <QStandardItemModel> +#include "filter.h" + +#ifndef BINARYNINJA_KCSYMBOLTABLE_H +#define BINARYNINJA_KCSYMBOLTABLE_H + +class SymbolTableView; + + +class SymbolTableModel : public QAbstractTableModel +{ +Q_OBJECT + SymbolTableView* m_parent; + QFont m_font; + std::string m_filter; + std::vector<KernelCacheAPI::CacheSymbol> m_preparedSymbols{}; + // These are the symbols we actually use + std::vector<KernelCacheAPI::CacheSymbol> m_modelSymbols{}; + +public: + explicit SymbolTableModel(SymbolTableView* parent); + + int rowCount(const QModelIndex& parent) const override; + int columnCount(const QModelIndex& parent) const override; + QVariant data(const QModelIndex& index, int role) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; + void sort(int column, Qt::SortOrder order) override; + void updateSymbols(std::vector<KernelCacheAPI::CacheSymbol>&& symbols); + void setFilter(std::string text); + const KernelCacheAPI::CacheSymbol& symbolAt(int row) const; + +}; + + +class SymbolTableView : public QTableView, public FilterTarget +{ +Q_OBJECT + friend class SymbolTableModel; + + SymbolTableModel* m_model; + +public: + explicit SymbolTableView(QWidget* parent); + ~SymbolTableView() override; + + // Call this to populate the symbols from the given view. + void populateSymbols(BinaryNinja::BinaryView& view); + + void scrollToFirstItem() override + { + if (model()->rowCount() > 0) { + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) + scrollTo(top); + } + } + + void scrollToCurrentItem() override + { + QModelIndex currentIndex = selectionModel()->currentIndex(); + if (currentIndex.isValid()) + scrollTo(currentIndex); + } + + void selectFirstItem() override + { + if (model()->rowCount() > 0) { + QModelIndex top = indexAt(rect().topLeft()); + if (top.isValid()) { + selectionModel()->select(top, QItemSelectionModel::ClearAndSelect); + setCurrentIndex(top); + } + } + } + + void activateFirstItem() override + { + if (model()->rowCount() > 0) { + QModelIndex topLeft = indexAt(rect().topLeft()); + if (topLeft.isValid()) { + setCurrentIndex(topLeft); + emit activated(topLeft); + } + } + } + + KernelCacheAPI::CacheSymbol getSymbolAtRow(int row) const + { + return m_model->symbolAt(row); + } + + void setFilter(const std::string& filter) override; +}; + + +#endif // BINARYNINJA_KCSYMBOLTABLE_H diff --git a/view/macho/machoview.h b/view/macho/machoview.h index 1a715991..dfbb24fe 100644 --- a/view/macho/machoview.h +++ b/view/macho/machoview.h @@ -1366,6 +1366,15 @@ namespace BinaryNinja }; #endif + struct fileset_entry_command { + uint32_t cmd; + uint32_t cmdsize; + uint64_t vmaddr; + uint64_t fileoff; + uint32_t nameEntryOffsetFromBaseOfCommand; + uint32_t reserved; + }; + struct MachOHeader { bool isMainHeader = false; |
