From 7f3f394c8bb50c987a5237bc74aa503486571058 Mon Sep 17 00:00:00 2001 From: kat Date: Wed, 19 Mar 2025 09:12:52 -0400 Subject: Add iOS/macOS MH_FILESET KernelCache View and loader. This loader is inspired by/based on our dyld_shared_cache loader, following the same design language. It targets primarily the latest kernels, but should support any with the MH_FILESET format. It allows you to decide which images you would like to map in (the kernel itself included), resulting in targeted analysis when you may only need to load a singular image. It also supports dropping in compressed KernelCaches, directly from the ipsw. This is an early solution and we have many more changes and improvements planned. We look forward to your feedback --- view/kernelcache/api/CMakeLists.txt | 79 +++ view/kernelcache/api/kernelcache.cpp | 191 +++++++ view/kernelcache/api/kernelcacheapi.h | 272 +++++++++ view/kernelcache/api/kernelcachecore.h | 133 +++++ view/kernelcache/api/python/CMakeLists.txt | 50 ++ view/kernelcache/api/python/__init__.py | 8 + view/kernelcache/api/python/_kernelcachecore.py | 515 +++++++++++++++++ .../api/python/_kernelcachecore_template.py | 43 ++ view/kernelcache/api/python/generator.cpp | 617 +++++++++++++++++++++ view/kernelcache/api/python/kernelcache.py | 238 ++++++++ view/kernelcache/api/python/kernelcache_enums.py | 14 + 11 files changed, 2160 insertions(+) create mode 100644 view/kernelcache/api/CMakeLists.txt create mode 100644 view/kernelcache/api/kernelcache.cpp create mode 100644 view/kernelcache/api/kernelcacheapi.h create mode 100644 view/kernelcache/api/kernelcachecore.h create mode 100644 view/kernelcache/api/python/CMakeLists.txt create mode 100644 view/kernelcache/api/python/__init__.py create mode 100644 view/kernelcache/api/python/_kernelcachecore.py create mode 100644 view/kernelcache/api/python/_kernelcachecore_template.py create mode 100644 view/kernelcache/api/python/generator.cpp create mode 100644 view/kernelcache/api/python/kernelcache.py create mode 100644 view/kernelcache/api/python/kernelcache_enums.py (limited to 'view/kernelcache/api') diff --git a/view/kernelcache/api/CMakeLists.txt b/view/kernelcache/api/CMakeLists.txt new file mode 100644 index 00000000..ae7e454a --- /dev/null +++ b/view/kernelcache/api/CMakeLists.txt @@ -0,0 +1,79 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +project(kernelcacheapi) +file(GLOB BN_MACHO_API_SOURCES *.cpp *.h) +add_library(kernelcacheapi OBJECT ${BN_MACHO_API_SOURCES}) + + +set(COMPILE_DEFS "") + +if (HARD_FAIL_MODE) + set(COMPILE_DEFS "${COMPILE_DEFS} ABORT_FAILURES;") +endif() + +if (BN_REF_COUNT_DEBUG) + set(COMPILE_DEFS "${COMPILE_DEFS} BN_REF_COUNT_DEBUG;") +endif() + +if (SLIDEINFO_DEBUG_TAGS) + set(COMPILE_DEFS "${COMPILE_DEFS} SLIDEINFO_DEBUG_TAGS;") +endif() + +if (METADATA_VERSION) + set(COMPILE_DEFS "${COMPILE_DEFS} METADATA_VERSION=${METADATA_VERSION};") +else() + message(FATAL_ERROR "No metadata version provided. Fatal.") +endif() + +if (KC_VIEW_NAME) + set(COMPILE_DEFS "${COMPILE_DEFS} KC_VIEW_NAME=\"${KC_VIEW_NAME}\";") +else() + message(FATAL_ERROR "No view name provided. Fatal.") +endif() + +target_compile_definitions(kernelcacheapi PRIVATE ${COMPILE_DEFS}) + + +function(get_recursive_include_dirs target result) + # Initialize an empty list to store include directories + set(include_dirs "") + + # Get the include directories of the current target + get_target_property(current_target_includes ${target} INTERFACE_INCLUDE_DIRECTORIES) + if(current_target_includes) + list(APPEND include_dirs ${current_target_includes}) + endif() + + # Get the libraries that this target links to + get_target_property(linked_libraries ${target} INTERFACE_LINK_LIBRARIES) + if(linked_libraries) + foreach(linked_library IN LISTS linked_libraries) + # Skip plain library names (non-target libraries) + if(TARGET ${linked_library}) + # Recursively get include directories from linked libraries + get_recursive_include_dirs(${linked_library} linked_library_includes) + list(APPEND include_dirs ${linked_library_includes}) + endif() + endforeach() + endif() + + # Set the result to the collected include directories + set(${result} ${include_dirs} PARENT_SCOPE) +endfunction() + +get_recursive_include_dirs(binaryninjaapi INCLUDES) + +target_include_directories(kernelcacheapi + PUBLIC ${PROJECT_SOURCE_DIR} ${INCLUDES}) + +set_target_properties(kernelcacheapi PROPERTIES + CXX_STANDARD 17 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/out) + +if (NOT DEMO) + add_subdirectory(python) +endif() \ No newline at end of file diff --git a/view/kernelcache/api/kernelcache.cpp b/view/kernelcache/api/kernelcache.cpp new file mode 100644 index 00000000..6cca9033 --- /dev/null +++ b/view/kernelcache/api/kernelcache.cpp @@ -0,0 +1,191 @@ +// +// Created by kat on 5/21/23. +// + +#include "kernelcacheapi.h" + +namespace KernelCacheAPI { + + KernelCache::KernelCache(Ref view) { + m_object = BNGetKernelCache(view->GetObject()); + } + + BNKCViewLoadProgress KernelCache::GetLoadProgress(Ref view) + { + return BNKCViewGetLoadProgress(view->GetFile()->GetSessionId()); + } + + uint64_t KernelCache::FastGetImageCount(Ref view) + { + return BNKCViewFastGetImageCount(view->GetObject()); + } + + bool KernelCache::LoadImageWithInstallName(std::string installName) + { + char* str = BNAllocString(installName.c_str()); + return BNKCViewLoadImageWithInstallName(m_object, str); + } + + bool KernelCache::LoadImageContainingAddress(uint64_t addr) + { + return BNKCViewLoadImageContainingAddress(m_object, addr); + } + + std::vector KernelCache::GetAvailableImages() + { + size_t count; + char** value = BNKCViewGetInstallNames(m_object, &count); + if (value == nullptr) + { + return {}; + } + + std::vector result; + for (size_t i = 0; i < count; i++) + { + result.push_back(value[i]); + } + + BNFreeStringList(value, count); + return result; + } + + std::vector KernelCache::GetImages() + { + size_t count; + BNKCImage* value = BNKCViewGetAllImages(m_object, &count); + if (value == nullptr) + { + return {}; + } + + std::vector 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); + } + + BNKCViewFreeAllImages(value, count); + return result; + } + + std::vector KernelCache::GetLoadedImages() + { + size_t count; + auto images = BNKCViewGetLoadedImages(m_object, &count); + if (images == nullptr) + { + return {}; + } + std::vector 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); + + return result; + } + + std::vector KernelCache::LoadAllSymbolsAndWait() + { + size_t count; + BNKCSymbolRep* value = BNKCViewLoadAllSymbolsAndWait(m_object, &count); + if (value == nullptr) + { + return {}; + } + + std::vector 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); + } + + BNKCViewFreeSymbols(value, count); + return result; + } + + 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::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::optional KernelCache::GetMachOHeaderForImage(std::string name) + { + char* str = BNAllocString(name.c_str()); + char* outputStr = BNKCViewGetImageHeaderForName(m_object, str); + if (outputStr == nullptr) + return {}; + std::string output = outputStr; + BNFreeString(outputStr); + if (output.empty()) + return {}; + KernelCacheMachOHeader header = KernelCacheMachOHeader::LoadFromString(output); + return header; + } + + std::optional 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); + } + +} // namespace KernelCacheAPI diff --git a/view/kernelcache/api/kernelcacheapi.h b/view/kernelcache/api/kernelcacheapi.h new file mode 100644 index 00000000..149f39bf --- /dev/null +++ b/view/kernelcache/api/kernelcacheapi.h @@ -0,0 +1,272 @@ +#pragma once + +#include +#include "../core/MetadataSerializable.hpp" +#include "../api/view/macho/machoview.h" +#include "kernelcachecore.h" + +using namespace BinaryNinja; + +namespace KernelCacheAPI { + template + class KCRefCountObject { + void AddRefInternal() { m_refs.fetch_add(1); } + + void ReleaseInternal() { + if (m_refs.fetch_sub(1) == 1) + delete this; + } + + public: + std::atomic m_refs; + T *m_object; + + KCRefCountObject() : m_refs(0), m_object(nullptr) {} + + virtual ~KCRefCountObject() {} + + T *GetObject() const { return m_object; } + + static T *GetObject(KCRefCountObject *obj) { + if (!obj) + return nullptr; + return obj->GetObject(); + } + + void AddRef() { AddRefInternal(); } + + void Release() { ReleaseInternal(); } + + void AddRefForRegistration() { AddRefInternal(); } + }; + + + template + class KCCoreRefCountObject { + void AddRefInternal() { m_refs.fetch_add(1); } + + void ReleaseInternal() { + if (m_refs.fetch_sub(1) == 1) { + if (!m_registeredRef) + delete this; + } + } + + public: + std::atomic m_refs; + bool m_registeredRef = false; + T *m_object; + + KCCoreRefCountObject() : m_refs(0), m_object(nullptr) {} + + virtual ~KCCoreRefCountObject() {} + + T *GetObject() const { return m_object; } + + static T *GetObject(KCCoreRefCountObject *obj) { + if (!obj) + return nullptr; + return obj->GetObject(); + } + + void AddRef() { + if (m_object && (m_refs != 0)) + AddObjectReference(m_object); + AddRefInternal(); + } + + void Release() { + if (m_object) + FreeObjectReference(m_object); + ReleaseInternal(); + } + + void AddRefForRegistration() { m_registeredRef = true; } + + void ReleaseForRegistration() { + m_object = nullptr; + m_registeredRef = false; + if (m_refs == 0) + delete this; + } + }; + + struct KCMemoryRegion { + uint64_t vmAddress; + uint64_t size; + std::string prettyName; + }; + + struct BackingCacheMapping { + uint64_t vmAddress; + uint64_t size; + uint64_t fileOffset; + }; + + struct BackingCache { + std::string path; + bool isPrimary; + std::vector mappings; + }; + + struct KCImageMemoryMapping { + std::string name; + uint64_t vmAddress; + uint64_t size; + bool loaded; + uint64_t rawViewOffset; + }; + + struct KCImage { + std::string name; + uint64_t headerFileAddress; + std::vector mappings; + }; + + struct KCSymbol { + uint64_t address; + std::string name; + std::string image; + }; + + using namespace BinaryNinja; + struct KernelCacheMachOHeader : public KernelCacheCore::MetadataSerializable { + uint64_t textBase = 0; + uint64_t loadCommandOffset = 0; + mach_header_64 ident; + std::string identifierPrefix; + std::string installName; + + std::vector> entryPoints; + std::vector m_entryPoints; //list of entrypoints + + symtab_command symtab; + dysymtab_command dysymtab; + dyld_info_command dyldInfo; + routines_command_64 routines64; + function_starts_command functionStarts; + std::vector moduleInitSections; + linkedit_data_command exportTrie; + linkedit_data_command chainedFixups {}; + + uint64_t relocationBase; + // Section and program headers, internally use 64-bit form as it is a superset of 32-bit + std::vector segments; //only three types of sections __TEXT, __DATA, __IMPORT + segment_command_64 linkeditSegment; + std::vector sections; + std::vector sectionNames; + + std::vector symbolStubSections; + std::vector symbolPointerSections; + + std::vector dylibs; + + build_version_command buildVersion; + std::vector buildToolVersions; + + 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); + } + + 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; + } + }; + + + class KernelCache : public KCCoreRefCountObject { + public: + KernelCache(Ref view); + + BNKCViewState GetState(); + static BNKCViewLoadProgress GetLoadProgress(Ref view); + static uint64_t FastGetImageCount(Ref view); + + bool LoadImageWithInstallName(std::string installName); + bool LoadImageContainingAddress(uint64_t addr); + std::vector GetAvailableImages(); + + std::vector LoadAllSymbolsAndWait(); + + std::string GetNameForAddress(uint64_t address); + std::string GetImageNameForAddress(uint64_t address); + + std::vector GetImages(); + std::vector GetLoadedImages(); + + std::optional GetMachOHeaderForImage(std::string name); + std::optional GetMachOHeaderForAddress(uint64_t address); + }; +} \ No newline at end of file diff --git a/view/kernelcache/api/kernelcachecore.h b/view/kernelcache/api/kernelcachecore.h new file mode 100644 index 00000000..d425bf15 --- /dev/null +++ b/view/kernelcache/api/kernelcachecore.h @@ -0,0 +1,133 @@ +#pragma once + + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __GNUC__ + #ifdef KERNELCACHE_LIBRARY + #define KERNELCACHE_FFI_API __attribute__((visibility("default"))) + #else // KERNELCACHE_LIBRARY + #define KERNELCACHE_FFI_API + #endif // KERNELCACHE_LIBRARY +#else // __GNUC__ + #ifdef _MSC_VER + #ifndef DEMO_VERSION + #ifdef KERNELCACHE_LIBRARY + #define KERNELCACHE_FFI_API __declspec(dllexport) + #else // KERNELCACHE_LIBRARY + #define KERNELCACHE_FFI_API __declspec(dllimport) + #endif // KERNELCACHE_LIBRARY + #else + #define KERNELCACHE_FFI_API + #endif + #else // _MSC_VER + #define KERNELCACHE_FFI_API + #endif // _MSC_VER +#endif // __GNUC__C + +#define CORE_ALLOCATED_STRUCT(T) + +#define CORE_ALLOCATED_CLASS(T) \ + public: \ + 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 enum BNKCViewState { + Unloaded, + Loaded, + LoadedWithImages, + } BNKCViewState; + + typedef enum BNKCViewLoadProgress { + LoadProgressNotStarted, + LoadProgressLoadingCaches, + LoadProgressLoadingImages, + LoadProgressFinished, + } BNKCViewLoadProgress; + + typedef struct BNBinaryView BNBinaryView; + typedef struct BNKernelCache BNKernelCache; + + typedef struct BNKCImageMemoryMapping { + 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; + + typedef struct BNKCMappedMemoryRegion { + uint64_t vmAddress; + uint64_t size; + char* name; + } BNKCMappedMemoryRegion; + + typedef struct BNKCMemoryUsageInfo { + uint64_t sharedCacheRefs; + uint64_t mmapRefs; + } BNKCMemoryUsageInfo; + + typedef struct BNKCSymbolRep { + uint64_t address; + char* name; + char* image; + } BNKCSymbolRep; + + KERNELCACHE_FFI_API BNKernelCache* BNGetKernelCache(BNBinaryView* data); + + KERNELCACHE_FFI_API BNKernelCache* BNNewKernelCacheReference(BNKernelCache* cache); + KERNELCACHE_FFI_API void BNFreeKernelCacheReference(BNKernelCache* cache); + + KERNELCACHE_FFI_API char** BNKCViewGetInstallNames(BNKernelCache* cache, size_t* count); + + KERNELCACHE_FFI_API bool BNKCViewLoadImageWithInstallName(BNKernelCache* cache, char* name); + KERNELCACHE_FFI_API bool BNKCViewLoadImageContainingAddress(BNKernelCache* cache, uint64_t address); + + KERNELCACHE_FFI_API char* BNKCViewGetNameForAddress(BNKernelCache* cache, uint64_t address); + KERNELCACHE_FFI_API char* BNKCViewGetImageNameForAddress(BNKernelCache* cache, uint64_t address); + + 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 BNKCSymbolRep* BNKCViewLoadAllSymbolsAndWait(BNKernelCache* cache, size_t* count); + KERNELCACHE_FFI_API void BNKCViewFreeSymbols(BNKCSymbolRep* symbols, 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 BNKCImage* BNKCViewGetLoadedImages(BNKernelCache* cache, size_t* count); + KERNELCACHE_FFI_API void BNKCViewFreeLoadedImages(BNKCImage* images, size_t count); + + KERNELCACHE_FFI_API char* BNKCViewGetImageHeaderForAddress(BNKernelCache* cache, uint64_t address); + KERNELCACHE_FFI_API char* BNKCViewGetImageHeaderForName(BNKernelCache* cache, char* name); + +#ifdef __cplusplus +} +#endif diff --git a/view/kernelcache/api/python/CMakeLists.txt b/view/kernelcache/api/python/CMakeLists.txt new file mode 100644 index 00000000..60fceeeb --- /dev/null +++ b/view/kernelcache/api/python/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(kernelcache-python-api) + +file(GLOB PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/*.py) +list(REMOVE_ITEM PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/_kernelcachecore.py) +list(REMOVE_ITEM PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/enums.py) + +add_executable(kernelcache_generator + ${PROJECT_SOURCE_DIR}/generator.cpp) +target_link_libraries(kernelcache_generator binaryninjaapi) +target_include_directories(kernelcache_generator PUBLIC {PROJECT_SOURCE_DIR}/../../api) + +set_target_properties(kernelcache_generator PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + BUILD_WITH_INSTALL_RPATH OFF + RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) + +if(BN_INTERNAL_BUILD) + set(PYTHON_OUTPUT_DIRECTORY ${BN_RESOURCE_DIR}/python/binaryninja/kernelcache/) +else() + set(PYTHON_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins/kernelcache/) +endif() + +if(WIN32) + if (BN_INTERNAL_BUILD) + add_custom_command(TARGET kernelcache_generator PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BN_CORE_OUTPUT_DIR}/binaryninjacore.dll ${PROJECT_BINARY_DIR}/) + else() + add_custom_command(TARGET kernelcache_generator PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BN_INSTALL_DIR}/binaryninjacore.dll ${PROJECT_BINARY_DIR}/) + endif() +endif() + +add_custom_target(kernelcache_generator_copy ALL + BYPRODUCTS ${PROJECT_SOURCE_DIR}/_kernelcachecore.py ${PROJECT_SOURCE_DIR}/enums.py + DEPENDS ${PYTHON_SOURCES} ${PROJECT_SOURCE_DIR}/../kernelcachecore.h $ + COMMAND ${CMAKE_COMMAND} -E echo "Copying Shared Cache Python Sources" + COMMAND ${CMAKE_COMMAND} -E make_directory ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E env ASAN_OPTIONS=detect_leaks=0 $ + ${PROJECT_SOURCE_DIR}/../kernelcachecore.h + ${PROJECT_SOURCE_DIR}/_kernelcachecore.py + ${PROJECT_SOURCE_DIR}/_kernelcachecore_template.py + ${PROJECT_SOURCE_DIR}/kernelcache_enums.py + + COMMAND ${CMAKE_COMMAND} -E copy ${PYTHON_SOURCES} ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/_kernelcachecore.py ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/kernelcache_enums.py ${PYTHON_OUTPUT_DIRECTORY}) + diff --git a/view/kernelcache/api/python/__init__.py b/view/kernelcache/api/python/__init__.py new file mode 100644 index 00000000..c6f05346 --- /dev/null +++ b/view/kernelcache/api/python/__init__.py @@ -0,0 +1,8 @@ +import os + +from binaryninja._binaryninjacore import BNGetUserPluginDirectory +user_plugin_dir = os.path.realpath(BNGetUserPluginDirectory()) +current_path = os.path.realpath(__file__) + +from .kernelcache import * + diff --git a/view/kernelcache/api/python/_kernelcachecore.py b/view/kernelcache/api/python/_kernelcachecore.py new file mode 100644 index 00000000..6bef0d06 --- /dev/null +++ b/view/kernelcache/api/python/_kernelcachecore.py @@ -0,0 +1,515 @@ +import binaryninja +import ctypes, os + +from typing import Optional +from . import kernelcache_enums +# Load core module +import platform +core = None +core_platform = platform.system() + +# By the time the debugger is loaded, binaryninja has not fully initialized. +# So we cannot call binaryninja.bundled_plugin_path() +from binaryninja._binaryninjacore import BNGetBundledPluginDirectory, BNFreeString +if core_platform == "Darwin": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libkernelcache.dylib")) + +elif core_platform == "Linux": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libkernelcache.so")) + +elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0): + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "kernelcache.dll")) +else: + raise Exception("OS not supported") + +def cstr(var) -> Optional[ctypes.c_char_p]: + if var is None: + return None + if isinstance(var, bytes): + return var + return var.encode("utf-8") + +def pyNativeStr(arg): + if isinstance(arg, str): + return arg + else: + return arg.decode('utf8') + +def free_string(value:ctypes.c_char_p) -> None: + BNFreeString(ctypes.cast(value, ctypes.POINTER(ctypes.c_byte))) + +# Type definitions +from binaryninja._binaryninjacore import BNBinaryView, BNBinaryViewHandle +class BNKCImage(ctypes.Structure): + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) +BNKCImageHandle = ctypes.POINTER(BNKCImage) +class BNKCImageMemoryMapping(ctypes.Structure): + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) +BNKCImageMemoryMappingHandle = ctypes.POINTER(BNKCImageMemoryMapping) +class BNKCMappedMemoryRegion(ctypes.Structure): + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) +BNKCMappedMemoryRegionHandle = ctypes.POINTER(BNKCMappedMemoryRegion) +class BNKCMemoryUsageInfo(ctypes.Structure): + pass +BNKCMemoryUsageInfoHandle = ctypes.POINTER(BNKCMemoryUsageInfo) +class BNKCSymbolRep(ctypes.Structure): + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) + @property + def image(self): + return pyNativeStr(self._image) + @image.setter + def image(self, value): + self._image = cstr(value) +BNKCSymbolRepHandle = ctypes.POINTER(BNKCSymbolRep) +KCViewLoadProgressEnum = ctypes.c_int +KCViewStateEnum = ctypes.c_int +class BNKernelCache(ctypes.Structure): + pass +BNKernelCacheHandle = ctypes.POINTER(BNKernelCache) + +# Structure definitions +BNKCImage._fields_ = [ + ("_name", ctypes.c_char_p), + ("headerFileAddress", ctypes.c_ulonglong), + ("mappings", ctypes.POINTER(BNKCImageMemoryMapping)), + ("mappingCount", ctypes.c_ulonglong), + ] +BNKCImageMemoryMapping._fields_ = [ + ("_name", ctypes.c_char_p), + ("vmAddress", ctypes.c_ulonglong), + ("size", ctypes.c_ulonglong), + ("loaded", ctypes.c_bool), + ("rawViewOffset", ctypes.c_ulonglong), + ] +BNKCMappedMemoryRegion._fields_ = [ + ("vmAddress", ctypes.c_ulonglong), + ("size", ctypes.c_ulonglong), + ("_name", ctypes.c_char_p), + ] +BNKCMemoryUsageInfo._fields_ = [ + ("sharedCacheRefs", ctypes.c_ulonglong), + ("mmapRefs", ctypes.c_ulonglong), + ] +BNKCSymbolRep._fields_ = [ + ("address", ctypes.c_ulonglong), + ("_name", ctypes.c_char_p), + ("_image", ctypes.c_char_p), + ] + +# Function definitions +# ------------------------------------------------------- +# _BNFreeKernelCacheReference + +_BNFreeKernelCacheReference = core.BNFreeKernelCacheReference +_BNFreeKernelCacheReference.restype = None +_BNFreeKernelCacheReference.argtypes = [ + ctypes.POINTER(BNKernelCache), + ] + + +# noinspection PyPep8Naming +def BNFreeKernelCacheReference( + cache: ctypes.POINTER(BNKernelCache) + ) -> None: + return _BNFreeKernelCacheReference(cache) + + +# ------------------------------------------------------- +# _BNGetKernelCache + +_BNGetKernelCache = core.BNGetKernelCache +_BNGetKernelCache.restype = ctypes.POINTER(BNKernelCache) +_BNGetKernelCache.argtypes = [ + ctypes.POINTER(BNBinaryView), + ] + + +# noinspection PyPep8Naming +def BNGetKernelCache( + data: ctypes.POINTER(BNBinaryView) + ) -> Optional[ctypes.POINTER(BNKernelCache)]: + result = _BNGetKernelCache(data) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNKCViewFastGetImageCount + +_BNKCViewFastGetImageCount = core.BNKCViewFastGetImageCount +_BNKCViewFastGetImageCount.restype = ctypes.c_ulonglong +_BNKCViewFastGetImageCount.argtypes = [ + ctypes.POINTER(BNBinaryView), + ] + + +# noinspection PyPep8Naming +def BNKCViewFastGetImageCount( + view: ctypes.POINTER(BNBinaryView) + ) -> int: + return _BNKCViewFastGetImageCount(view) + + +# ------------------------------------------------------- +# _BNKCViewFreeAllImages + +_BNKCViewFreeAllImages = core.BNKCViewFreeAllImages +_BNKCViewFreeAllImages.restype = None +_BNKCViewFreeAllImages.argtypes = [ + ctypes.POINTER(BNKCImage), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNKCViewFreeAllImages( + images: ctypes.POINTER(BNKCImage), + count: int + ) -> None: + return _BNKCViewFreeAllImages(images, count) + + +# ------------------------------------------------------- +# _BNKCViewFreeLoadedImages + +_BNKCViewFreeLoadedImages = core.BNKCViewFreeLoadedImages +_BNKCViewFreeLoadedImages.restype = None +_BNKCViewFreeLoadedImages.argtypes = [ + ctypes.POINTER(BNKCImage), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNKCViewFreeLoadedImages( + images: ctypes.POINTER(BNKCImage), + count: int + ) -> None: + return _BNKCViewFreeLoadedImages(images, count) + + +# ------------------------------------------------------- +# _BNKCViewFreeSymbols + +_BNKCViewFreeSymbols = core.BNKCViewFreeSymbols +_BNKCViewFreeSymbols.restype = None +_BNKCViewFreeSymbols.argtypes = [ + ctypes.POINTER(BNKCSymbolRep), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNKCViewFreeSymbols( + symbols: ctypes.POINTER(BNKCSymbolRep), + count: int + ) -> None: + return _BNKCViewFreeSymbols(symbols, count) + + +# ------------------------------------------------------- +# _BNKCViewGetAllImages + +_BNKCViewGetAllImages = core.BNKCViewGetAllImages +_BNKCViewGetAllImages.restype = ctypes.POINTER(BNKCImage) +_BNKCViewGetAllImages.argtypes = [ + ctypes.POINTER(BNKernelCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNKCViewGetAllImages( + cache: ctypes.POINTER(BNKernelCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNKCImage)]: + result = _BNKCViewGetAllImages(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNKCViewGetImageHeaderForAddress + +_BNKCViewGetImageHeaderForAddress = core.BNKCViewGetImageHeaderForAddress +_BNKCViewGetImageHeaderForAddress.restype = ctypes.POINTER(ctypes.c_byte) +_BNKCViewGetImageHeaderForAddress.argtypes = [ + ctypes.POINTER(BNKernelCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNKCViewGetImageHeaderForAddress( + cache: ctypes.POINTER(BNKernelCache), + address: int + ) -> Optional[Optional[str]]: + result = _BNKCViewGetImageHeaderForAddress(cache, address) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNKCViewGetImageHeaderForName + +_BNKCViewGetImageHeaderForName = core.BNKCViewGetImageHeaderForName +_BNKCViewGetImageHeaderForName.restype = ctypes.POINTER(ctypes.c_byte) +_BNKCViewGetImageHeaderForName.argtypes = [ + ctypes.POINTER(BNKernelCache), + ctypes.c_char_p, + ] + + +# noinspection PyPep8Naming +def BNKCViewGetImageHeaderForName( + cache: ctypes.POINTER(BNKernelCache), + name: Optional[str] + ) -> Optional[Optional[str]]: + result = _BNKCViewGetImageHeaderForName(cache, cstr(name)) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNKCViewGetImageNameForAddress + +_BNKCViewGetImageNameForAddress = core.BNKCViewGetImageNameForAddress +_BNKCViewGetImageNameForAddress.restype = ctypes.POINTER(ctypes.c_byte) +_BNKCViewGetImageNameForAddress.argtypes = [ + ctypes.POINTER(BNKernelCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNKCViewGetImageNameForAddress( + cache: ctypes.POINTER(BNKernelCache), + address: int + ) -> Optional[Optional[str]]: + result = _BNKCViewGetImageNameForAddress(cache, address) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNKCViewGetInstallNames + +_BNKCViewGetInstallNames = core.BNKCViewGetInstallNames +_BNKCViewGetInstallNames.restype = ctypes.POINTER(ctypes.c_char_p) +_BNKCViewGetInstallNames.argtypes = [ + ctypes.POINTER(BNKernelCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNKCViewGetInstallNames( + cache: ctypes.POINTER(BNKernelCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(ctypes.c_char_p)]: + result = _BNKCViewGetInstallNames(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNKCViewGetLoadProgress + +_BNKCViewGetLoadProgress = core.BNKCViewGetLoadProgress +_BNKCViewGetLoadProgress.restype = KCViewLoadProgressEnum +_BNKCViewGetLoadProgress.argtypes = [ + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNKCViewGetLoadProgress( + sessionID: int + ) -> KCViewLoadProgressEnum: + return _BNKCViewGetLoadProgress(sessionID) + + +# ------------------------------------------------------- +# _BNKCViewGetLoadedImages + +_BNKCViewGetLoadedImages = core.BNKCViewGetLoadedImages +_BNKCViewGetLoadedImages.restype = ctypes.POINTER(BNKCImage) +_BNKCViewGetLoadedImages.argtypes = [ + ctypes.POINTER(BNKernelCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNKCViewGetLoadedImages( + cache: ctypes.POINTER(BNKernelCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNKCImage)]: + result = _BNKCViewGetLoadedImages(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNKCViewGetNameForAddress + +_BNKCViewGetNameForAddress = core.BNKCViewGetNameForAddress +_BNKCViewGetNameForAddress.restype = ctypes.POINTER(ctypes.c_byte) +_BNKCViewGetNameForAddress.argtypes = [ + ctypes.POINTER(BNKernelCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNKCViewGetNameForAddress( + cache: ctypes.POINTER(BNKernelCache), + address: int + ) -> Optional[Optional[str]]: + result = _BNKCViewGetNameForAddress(cache, address) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNKCViewGetState + +_BNKCViewGetState = core.BNKCViewGetState +_BNKCViewGetState.restype = KCViewStateEnum +_BNKCViewGetState.argtypes = [ + ctypes.POINTER(BNKernelCache), + ] + + +# noinspection PyPep8Naming +def BNKCViewGetState( + cache: ctypes.POINTER(BNKernelCache) + ) -> KCViewStateEnum: + return _BNKCViewGetState(cache) + + +# ------------------------------------------------------- +# _BNKCViewLoadAllSymbolsAndWait + +_BNKCViewLoadAllSymbolsAndWait = core.BNKCViewLoadAllSymbolsAndWait +_BNKCViewLoadAllSymbolsAndWait.restype = ctypes.POINTER(BNKCSymbolRep) +_BNKCViewLoadAllSymbolsAndWait.argtypes = [ + ctypes.POINTER(BNKernelCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNKCViewLoadAllSymbolsAndWait( + cache: ctypes.POINTER(BNKernelCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNKCSymbolRep)]: + result = _BNKCViewLoadAllSymbolsAndWait(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNKCViewLoadImageContainingAddress + +_BNKCViewLoadImageContainingAddress = core.BNKCViewLoadImageContainingAddress +_BNKCViewLoadImageContainingAddress.restype = ctypes.c_bool +_BNKCViewLoadImageContainingAddress.argtypes = [ + ctypes.POINTER(BNKernelCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNKCViewLoadImageContainingAddress( + cache: ctypes.POINTER(BNKernelCache), + address: int + ) -> bool: + return _BNKCViewLoadImageContainingAddress(cache, address) + + +# ------------------------------------------------------- +# _BNKCViewLoadImageWithInstallName + +_BNKCViewLoadImageWithInstallName = core.BNKCViewLoadImageWithInstallName +_BNKCViewLoadImageWithInstallName.restype = ctypes.c_bool +_BNKCViewLoadImageWithInstallName.argtypes = [ + ctypes.POINTER(BNKernelCache), + ctypes.c_char_p, + ] + + +# noinspection PyPep8Naming +def BNKCViewLoadImageWithInstallName( + cache: ctypes.POINTER(BNKernelCache), + name: Optional[str] + ) -> bool: + return _BNKCViewLoadImageWithInstallName(cache, cstr(name)) + + +# ------------------------------------------------------- +# _BNNewKernelCacheReference + +_BNNewKernelCacheReference = core.BNNewKernelCacheReference +_BNNewKernelCacheReference.restype = ctypes.POINTER(BNKernelCache) +_BNNewKernelCacheReference.argtypes = [ + ctypes.POINTER(BNKernelCache), + ] + + +# noinspection PyPep8Naming +def BNNewKernelCacheReference( + cache: ctypes.POINTER(BNKernelCache) + ) -> Optional[ctypes.POINTER(BNKernelCache)]: + result = _BNNewKernelCacheReference(cache) + if not result: + return None + return result + + + +# Helper functions +def handle_of_type(value, handle_type): + if isinstance(value, ctypes.POINTER(handle_type)) or isinstance(value, ctypes.c_void_p): + return ctypes.cast(value, ctypes.POINTER(handle_type)) + raise ValueError('expected pointer to %s' % str(handle_type)) diff --git a/view/kernelcache/api/python/_kernelcachecore_template.py b/view/kernelcache/api/python/_kernelcachecore_template.py new file mode 100644 index 00000000..c1f05e72 --- /dev/null +++ b/view/kernelcache/api/python/_kernelcachecore_template.py @@ -0,0 +1,43 @@ +import binaryninja +import ctypes, os + +from typing import Optional +from . import kernelcache_enums +# Load core module +import platform +core = None +core_platform = platform.system() + +# By the time the debugger is loaded, binaryninja has not fully initialized. +# So we cannot call binaryninja.bundled_plugin_path() +from binaryninja._binaryninjacore import BNGetBundledPluginDirectory, BNFreeString +if core_platform == "Darwin": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libkernelcache.dylib")) + +elif core_platform == "Linux": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libkernelcache.so")) + +elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0): + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "kernelcache.dll")) +else: + raise Exception("OS not supported") + +def cstr(var) -> Optional[ctypes.c_char_p]: + if var is None: + return None + if isinstance(var, bytes): + return var + return var.encode("utf-8") + +def pyNativeStr(arg): + if isinstance(arg, str): + return arg + else: + return arg.decode('utf8') + +def free_string(value:ctypes.c_char_p) -> None: + BNFreeString(ctypes.cast(value, ctypes.POINTER(ctypes.c_byte))) + diff --git a/view/kernelcache/api/python/generator.cpp b/view/kernelcache/api/python/generator.cpp new file mode 100644 index 00000000..91ff88c0 --- /dev/null +++ b/view/kernelcache/api/python/generator.cpp @@ -0,0 +1,617 @@ +/* +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. +*/ + + + +#include +#include +#include "binaryninjaapi.h" + +using namespace BinaryNinja; +using namespace std; + + +map g_pythonKeywordReplacements = { + {"False", "False_"}, + {"True", "True_"}, + {"None", "None_"}, + {"and", "and_"}, + {"as", "as_"}, + {"assert", "assert_"}, + {"async", "async_"}, + {"await", "await_"}, + {"break", "break_"}, + {"class", "class_"}, + {"continue", "continue_"}, + {"def", "def_"}, + {"del", "del_"}, + {"elif", "elif_"}, + {"else", "else_"}, + {"except", "except_"}, + {"finally", "finally_"}, + {"for", "for_"}, + {"from", "from_"}, + {"global", "global_"}, + {"if", "if_"}, + {"import", "import_"}, + {"in", "in_"}, + {"is", "is_"}, + {"lambda", "lambda_"}, + {"nonlocal", "nonlocal_"}, + {"not", "not_"}, + {"or", "or_"}, + {"pass", "pass_"}, + {"raise", "raise_"}, + {"return", "return_"}, + {"try", "try_"}, + {"while", "while_"}, + {"with", "with_"}, + {"yield", "yield_"}, +}; + + +void OutputType(FILE* out, Type* type, bool isReturnType = false, bool isCallback = false) +{ + switch (type->GetClass()) + { + case BoolTypeClass: + fprintf(out, "ctypes.c_bool"); + break; + case IntegerTypeClass: + switch (type->GetWidth()) + { + case 1: + if (type->IsSigned()) + fprintf(out, "ctypes.c_byte"); + else + fprintf(out, "ctypes.c_ubyte"); + break; + case 2: + if (type->IsSigned()) + fprintf(out, "ctypes.c_short"); + else + fprintf(out, "ctypes.c_ushort"); + break; + case 4: + if (type->IsSigned()) + fprintf(out, "ctypes.c_int"); + else + fprintf(out, "ctypes.c_uint"); + break; + default: + if (type->IsSigned()) + fprintf(out, "ctypes.c_longlong"); + else + fprintf(out, "ctypes.c_ulonglong"); + break; + } + break; + case FloatTypeClass: + if (type->GetWidth() == 4) + fprintf(out, "ctypes.c_float"); + else + fprintf(out, "ctypes.c_double"); + break; + case NamedTypeReferenceClass: + if (type->GetNamedTypeReference()->GetTypeReferenceClass() == EnumNamedTypeClass) + { + string name = type->GetNamedTypeReference()->GetName().GetString(); + if (name.size() > 16 && name.substr(0, 11) == "_BNDebugger") + name = name.substr(3); + else if (name.size() > 15 && name.substr(0, 10) == "BNDebugger") + name = name.substr(2); + else if (name.size() > 15 && name.substr(0, 7) == "BNDebug") + name = name.substr(2); + else if (name.size() > 2 && name.substr(0, 2) == "BN") + name = name.substr(2); + fprintf(out, "%sEnum", name.c_str()); + } + else + { + fprintf(out, "%s", type->GetNamedTypeReference()->GetName().GetString().c_str()); + } + break; + case PointerTypeClass: + if (isCallback || (type->GetChildType()->GetClass() == VoidTypeClass)) + { + fprintf(out, "ctypes.c_void_p"); + break; + } + else if ((type->GetChildType()->GetClass() == IntegerTypeClass) && + (type->GetChildType()->GetWidth() == 1) && (type->GetChildType()->IsSigned())) + { + if (isReturnType) + fprintf(out, "ctypes.POINTER(ctypes.c_byte)"); + else + fprintf(out, "ctypes.c_char_p"); + break; + } + else if (type->GetChildType()->GetClass() == FunctionTypeClass) + { + fprintf(out, "ctypes.CFUNCTYPE("); + OutputType(out, type->GetChildType()->GetChildType(), true, true); + for (auto& i : type->GetChildType()->GetParameters()) + { + fprintf(out, ", "); + OutputType(out, i.type); + } + fprintf(out, ")"); + break; + } + fprintf(out, "ctypes.POINTER("); + OutputType(out, type->GetChildType()); + fprintf(out, ")"); + break; + case ArrayTypeClass: + OutputType(out, type->GetChildType()); + fprintf(out, " * %" PRId64, type->GetElementCount()); + break; + default: + fprintf(out, "None"); + break; + } +} + + +void OutputSwizzledType(FILE* out, Type* type) +{ + switch (type->GetClass()) + { + case BoolTypeClass: + fprintf(out, "bool"); + break; + case IntegerTypeClass: + fprintf(out, "int"); + break; + case FloatTypeClass: + fprintf(out, "float"); + break; + case NamedTypeReferenceClass: + if (type->GetNamedTypeReference()->GetTypeReferenceClass() == EnumNamedTypeClass) + { + string name = type->GetNamedTypeReference()->GetName().GetString(); + if (name.size() > 16 && name.substr(0, 11) == "_BNDebugger") + name = name.substr(3); + else if (name.size() > 15 && name.substr(0, 10) == "BNDebugger") + name = name.substr(2); + else if (name.size() > 15 && name.substr(0, 7) == "BNDebug") + name = name.substr(2); + else if (name.size() > 2 && name.substr(0, 2) == "BN") + name = name.substr(2); + fprintf(out, "%sEnum", name.c_str()); + } + else + { + fprintf(out, "%s", type->GetNamedTypeReference()->GetName().GetString().c_str()); + } + break; + case PointerTypeClass: + if (type->GetChildType()->GetClass() == VoidTypeClass) + { + fprintf(out, "Optional[ctypes.c_void_p]"); + break; + } + else if ((type->GetChildType()->GetClass() == IntegerTypeClass) && + (type->GetChildType()->GetWidth() == 1) && (type->GetChildType()->IsSigned())) + { + fprintf(out, "Optional[str]"); + break; + } + else if (type->GetChildType()->GetClass() == FunctionTypeClass) + { + fprintf(out, "ctypes.CFUNCTYPE("); + OutputType(out, type->GetChildType()->GetChildType(), true, true); + for (auto& i : type->GetChildType()->GetParameters()) + { + fprintf(out, ", "); + OutputType(out, i.type); + } + fprintf(out, ")"); + break; + } + fprintf(out, "ctypes.POINTER("); + OutputType(out, type->GetChildType()); + fprintf(out, ")"); + break; + case ArrayTypeClass: + OutputType(out, type->GetChildType()); + fprintf(out, " * %" PRId64, type->GetElementCount()); + break; + default: + fprintf(out, "None"); + break; + } +} + + +int main(int argc, char* argv[]) +{ + if (argc < 5) + { + fprintf(stderr, "Usage: generator
\n"); + return 1; + } + + // Parse API header to get type and function information + map> types, vars, funcs; + string errors; + auto arch = new CoreArchitecture(BNGetNativeTypeParserArchitecture()); + + // Enable ephemeral settings + Settings::Instance()->LoadSettingsFile(""); + Settings::Instance()->Set("analysis.types.parserName", "ClangTypeParser"); + bool ok = arch->GetStandalonePlatform()->ParseTypesFromSourceFile(argv[1], types, vars, funcs, errors); + + if (!ok) + { + fprintf(stderr, "Errors: %s\n", errors.c_str()); + return 1; + } + + FILE* out = fopen(argv[2], "w"); + FILE* out_template = fopen(argv[3], "r"); + FILE* enums = fopen(argv[4], "w"); + + fprintf(enums, "import enum\n"); + + // Copy the content of the template to the output file + int c; + while((c = fgetc(out_template)) != EOF) + fputc(c, out); + + // Create type objects + fprintf(out, "# Type definitions\n"); + for (auto& i : types) + { + string name; + if (i.first.size() != 1) + continue; + name = i.first[0]; + if (name == "BNBinaryView") + { + fprintf(out, "from binaryninja._binaryninjacore import BNBinaryView, BNBinaryViewHandle\n"); + continue; + } + if (i.second->GetClass() == StructureTypeClass) + { + fprintf(out, "class %s(ctypes.Structure):\n", name.c_str()); + + // python uses str's, C uses byte-arrays + bool stringField = false; + for (auto& arg : i.second->GetStructure()->GetMembers()) + { + if ((arg.type->GetClass() == PointerTypeClass) && + (arg.type->GetChildType()->GetWidth() == 1) && + (arg.type->GetChildType()->IsSigned())) + { + fprintf(out, "\t@property\n\tdef %s(self):\n\t\treturn pyNativeStr(self._%s)\n", arg.name.c_str(), arg.name.c_str()); + fprintf(out, "\t@%s.setter\n\tdef %s(self, value):\n\t\tself._%s = cstr(value)\n", arg.name.c_str(), arg.name.c_str(), arg.name.c_str()); + stringField = true; + } + } + + if (!stringField) + fprintf(out, "\tpass\n"); + + fprintf(out, "%sHandle = ctypes.POINTER(%s)\n", name.c_str(), name.c_str()); + } + else if (i.second->GetClass() == EnumerationTypeClass) + { + bool isBNAPIEnum = false; + if (name.size() > 16 && name.substr(0, 11) == "_BNDebugger") + name = name.substr(3); + else if (name.size() > 15 && name.substr(0, 10) == "BNDebugger") + name = name.substr(2); + else if (name.size() > 15 && name.substr(0, 7) == "BNDebug") + name = name.substr(2); + else if (name.size() > 2 && name.substr(0, 2) == "BN") + { + name = name.substr(2); + isBNAPIEnum = false; + } + else + continue; + + if (isBNAPIEnum) + { + fprintf(out, "from binaryninja._binaryninjacore import %sEnum\n", name.c_str()); + continue; + } + + fprintf(out, "%sEnum = ctypes.c_int\n", name.c_str()); + + fprintf(enums, "\n\nclass %s(enum.IntEnum):\n", name.c_str()); + for (auto& j : i.second->GetEnumeration()->GetMembers()) + { + fprintf(enums, "\t%s = %" PRId64 "\n", j.name.c_str(), j.value); + } + } + else if ((i.second->GetClass() == BoolTypeClass) || (i.second->GetClass() == IntegerTypeClass) || + (i.second->GetClass() == FloatTypeClass) || (i.second->GetClass() == ArrayTypeClass)) + { + fprintf(out, "%s = ", name.c_str()); + OutputType(out, i.second); + fprintf(out, "\n"); + } + } + + + fprintf(out, "\n# Structure definitions\n"); + set structsToProcess; + set finishedStructs; + for (auto& i : types) + structsToProcess.insert(i.first); + while (structsToProcess.size() != 0) + { + set currentStructList = structsToProcess; + structsToProcess.clear(); + bool processedSome = false; + for (auto& i : currentStructList) + { + string name; + if (i.size() != 1) + continue; + Ref type = types[i]; + name = i[0]; + if ((type->GetClass() == StructureTypeClass) && (type->GetStructure()->GetMembers().size() != 0)) + { + bool requiresDependency = false; + for (auto& j : type->GetStructure()->GetMembers()) + { + if ((j.type->GetClass() == NamedTypeReferenceClass) && + (types[j.type->GetNamedTypeReference()->GetName()]->GetClass() == StructureTypeClass) && + (finishedStructs.count(j.type->GetNamedTypeReference()->GetName()) == 0)) + { + // This structure needs another structure that isn't fully defined yet, need to wait + // for the dependencies to be defined + structsToProcess.insert(i); + requiresDependency = true; + break; + } + } + + if (requiresDependency) + continue; + fprintf(out, "%s._fields_ = [\n", name.c_str()); + for (auto& j : type->GetStructure()->GetMembers()) + { + // To help the python->C wrappers + if ((j.type->GetClass() == PointerTypeClass) && + (j.type->GetChildType()->GetWidth() == 1) && + (j.type->GetChildType()->IsSigned())) + { + fprintf(out, "\t\t(\"_%s\", ", j.name.c_str()); + } + else + fprintf(out, "\t\t(\"%s\", ", j.name.c_str()); + OutputType(out, j.type); + fprintf(out, "),\n"); + } + fprintf(out, "\t]\n"); + finishedStructs.insert(i); + processedSome = true; + } + else if (type->GetClass() == NamedTypeReferenceClass) + { + if (type->GetNamedTypeReference()->GetTypeReferenceClass() == StructNamedTypeClass) + { + fprintf(out, "%s = %s\n", name.c_str(), type->GetNamedTypeReference()->GetName().GetString().c_str()); + fprintf(out, "%sHandle = %sHandle\n", name.c_str(), type->GetNamedTypeReference()->GetName().GetString().c_str()); + } + else if (type->GetNamedTypeReference()->GetTypeReferenceClass() == EnumNamedTypeClass) + { + fprintf(out, "%s = ctypes.c_int\n", name.c_str()); + } + finishedStructs.insert(i); + processedSome = true; + } + } + + if (!processedSome && structsToProcess.size() != 0) + { + fprintf(stderr, "Detected dependency cycle in structures\n"); + for (auto& i : structsToProcess) + fprintf(stderr, "%s\n", i.GetString().c_str()); + return 1; + } + } + + fprintf(out, "\n# Function definitions\n"); + for (auto& i : funcs) + { + string name; + if (i.first.size() != 1) + continue; + name = i.first[0]; + + // Check for a string result, these will be automatically wrapped to free the string + // memory and return a Python string + bool stringResult = (i.second->GetChildType()->GetClass() == PointerTypeClass) && + (i.second->GetChildType()->GetChildType()->GetWidth() == 1) && + (i.second->GetChildType()->GetChildType()->IsSigned()); + // Pointer returns will be automatically wrapped to return None on null pointer + bool pointerResult = (i.second->GetChildType()->GetClass() == PointerTypeClass); + + // From python -> C python3 requires str -> str.encode('charmap') + bool swizzleArgs = true; + if (name == "BNFreeString") + swizzleArgs = false; + + bool callbackConvention = false; + if (name == "BNAllocString") + { + // Don't perform automatic wrapping of string allocation, and return a void + // pointer so that callback functions (which is the only valid use of BNDebuggerAllocString) + // can properly return the result + stringResult = false; + callbackConvention = true; + swizzleArgs = false; + } + + string funcName = string("_") + name; + + fprintf(out, "# -------------------------------------------------------\n"); + fprintf(out, "# %s\n\n", funcName.c_str()); + fprintf(out, "%s = core.%s\n", funcName.c_str(), name.c_str()); + fprintf(out, "%s.restype = ", funcName.c_str()); + OutputType(out, i.second->GetChildType(), true, callbackConvention); + fprintf(out, "\n"); + if (!i.second->HasVariableArguments()) + { + fprintf(out, "%s.argtypes = [\n", funcName.c_str()); + for (auto& j : i.second->GetParameters()) + { + fprintf(out, "\t\t"); + if (name == "BNFreeString") + { + // BNDebuggerFreeString expects a pointer to a string allocated by the core, so do not use + // a c_char_p here, as that would be allocated by the Python runtime. This can + // be enforced by outputting like a return value. + OutputType(out, j.type, true); + } + else + { + OutputType(out, j.type); + } + fprintf(out, ",\n"); + } + fprintf(out, "\t]"); + } + else + { + // As of writing this, only BNLog's have variable instruction lengths, but in an attempt not to break in the future: + if (funcName.compare(0, 6, "_BNLog") == 0) + { + if (funcName != "_BNLog") + { + fprintf(out, "def %s(*args):\n", name.c_str()); + fprintf(out, "\treturn %s(*[cstr(arg) for arg in args])\n\n", funcName.c_str()); + continue; + } + else + { + fprintf(out, "def %s(level, *args):\n", name.c_str()); + fprintf(out, "\treturn %s(level, *[cstr(arg) for arg in args])\n\n", funcName.c_str()); + continue; + } + } + } + fprintf(out, "\n\n\n# noinspection PyPep8Naming\n"); + fprintf(out, "def %s(", name.c_str()); + if (!i.second->HasVariableArguments()) + { + size_t argN = 0; + for (auto& arg: i.second->GetParameters()) + { + string argName = arg.name; + if (g_pythonKeywordReplacements.find(argName) != g_pythonKeywordReplacements.end()) + argName = g_pythonKeywordReplacements[argName]; + + if (argName.empty()) + argName = "arg" + to_string(argN); + + if (argN > 0) + fprintf(out, ", "); + fprintf(out, "\n\t\t"); + fprintf(out, "%s: ", argName.c_str()); + if (swizzleArgs) + OutputSwizzledType(out, arg.type); + else + OutputType(out, arg.type); + argN ++; + } + } + fprintf(out, "\n\t\t) -> "); + if (swizzleArgs) + { + if (stringResult || pointerResult) + fprintf(out, "Optional["); + OutputSwizzledType(out, i.second->GetChildType()); + if (stringResult || pointerResult) + fprintf(out, "]"); + } + else + { + OutputType(out, i.second->GetChildType()); + } + fprintf(out, ":\n"); + + string stringArgFuncCall = funcName + "("; + size_t argN = 0; + for (auto& arg : i.second->GetParameters()) + { + string argName = arg.name; + if (g_pythonKeywordReplacements.find(argName) != g_pythonKeywordReplacements.end()) + argName = g_pythonKeywordReplacements[argName]; + + if (argName.empty()) + argName = "arg" + to_string(argN); + + if (swizzleArgs && (arg.type->GetClass() == PointerTypeClass) && + (arg.type->GetChildType()->GetClass() == IntegerTypeClass) && + (arg.type->GetChildType()->GetWidth() == 1) && + (arg.type->GetChildType()->IsSigned())) + { + stringArgFuncCall += string("cstr(") + argName + "), "; + } + else + { + stringArgFuncCall += argName + ", "; + } + argN++; + } + if (argN > 0) + stringArgFuncCall = stringArgFuncCall.substr(0, stringArgFuncCall.size()-2); + stringArgFuncCall += ")"; + + if (stringResult) + { + // Emit wrapper to get Python string and free native memory + fprintf(out, "\tresult = "); + fprintf(out, "%s\n", stringArgFuncCall.c_str()); + fprintf(out, "\tif not result:\n"); + fprintf(out, "\t\treturn None\n"); + fprintf(out, "\tstring = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value))\n"); + fprintf(out, "\tBNFreeString(result)\n"); + fprintf(out, "\treturn string\n"); + } + else if (pointerResult) + { + // Emit wrapper to return None on null pointer + fprintf(out, "\tresult = "); + fprintf(out, "%s\n", stringArgFuncCall.c_str()); + fprintf(out, "\tif not result:\n"); + fprintf(out, "\t\treturn None\n"); + fprintf(out, "\treturn result\n"); + } + else + { + fprintf(out, "\treturn "); + fprintf(out, "%s\n", stringArgFuncCall.c_str()); + } + fprintf(out, "\n\n"); + } + + fprintf(out, "\n# Helper functions\n"); + fprintf(out, "def handle_of_type(value, handle_type):\n"); + fprintf(out, "\tif isinstance(value, ctypes.POINTER(handle_type)) or isinstance(value, ctypes.c_void_p):\n"); + fprintf(out, "\t\treturn ctypes.cast(value, ctypes.POINTER(handle_type))\n"); + fprintf(out, "\traise ValueError('expected pointer to %%s' %% str(handle_type))\n"); + + fclose(out); + fclose(enums); + return 0; +} diff --git a/view/kernelcache/api/python/kernelcache.py b/view/kernelcache/api/python/kernelcache.py new file mode 100644 index 00000000..2148f2a8 --- /dev/null +++ b/view/kernelcache/api/python/kernelcache.py @@ -0,0 +1,238 @@ +import os +import ctypes +import dataclasses +import traceback + +import binaryninja +from binaryninja._binaryninjacore import BNFreeStringList, BNAllocString, BNFreeString + +from . import _kernelcachecore as kccore +from .kernelcache_enums import * + + +@dataclasses.dataclass +class KCImageMemoryMapping: + name: str + vmAddress: int + rawViewOffset: int + size: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return (f">") + + +@dataclasses.dataclass +class KCImage: + name: str + headerFileAddress: int + mappings: list[KCImageMemoryMapping] + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"" + + +@dataclasses.dataclass +class KCSymbol: + name: str + image: str + address: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"" + + +@dataclasses.dataclass +class KernelCacheMachOHeader: + header: str + + @classmethod + def LoadFromString(cls, s: str): + return cls(header=s) + + def __repr__(self): + return f"" + + +class KernelCache: + def __init__(self, view): + # Create a KernelCache object from a BinaryView. + self.handle = kccore.BNGetKernelCache(view.handle) + + @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)) + + @staticmethod + def fast_get_image_count(view) -> int: + """ + Quickly returns the number of images in the kernel cache. + """ + return kccore.BNKCViewFastGetImageCount(view.handle) + + 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 load_image_containing_address(self, addr: int) -> bool: + """ + Load the image that contains the given address. + """ + return kccore.BNKCViewLoadImageContainingAddress(self.handle, addr) + + @property + def image_names(self) -> list[str]: + """ + Return a list of available kernel cache image install names. + """ + count = ctypes.c_ulonglong() + value = kccore.BNKCViewGetInstallNames(self.handle, count) + if value is None: + return [] + result = [] + for i in range(count.value): + result.append(value[i].decode('utf-8')) + BNFreeStringList(value, count) + return result + + @property + def images(self) -> list[KCImage]: + """ + Return all kernel cache images. + """ + count = ctypes.c_ulonglong() + value = kccore.BNKCViewGetAllImages(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) + return result + + @property + def loaded_images(self) -> list[KCImage]: + """ + Return the kernel cache images that are currently loaded. + """ + count = ctypes.c_ulonglong() + images = kccore.BNKCViewGetLoadedImages(self.handle, count) + if images 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) + return result + + def load_all_symbols_and_wait(self) -> list[KCSymbol]: + """ + Load all symbols from the kernel cache and wait for completion. + """ + count = ctypes.c_ulonglong() + value = kccore.BNKCViewLoadAllSymbolsAndWait(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) + 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_macho_header_for_image(self, name: str): + """ + Return a KernelCacheMachOHeader for the image with the given install name. + """ + s = BNAllocString(name) + outputStr = kccore.BNKCViewGetImageHeaderForName(self.handle, s) + if outputStr is None: + return None + output = outputStr + BNFreeString(outputStr) + if output == "": + return None + return KernelCacheMachOHeader.LoadFromString(output) + + 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 + output = outputStr + BNFreeString(outputStr) + if output == "": + return None + return KernelCacheMachOHeader.LoadFromString(output) + + @property + def state(self): + """ + Return the current state of the kernel cache. + """ + return KCViewState(kccore.BNKCViewGetState(self.handle)) diff --git a/view/kernelcache/api/python/kernelcache_enums.py b/view/kernelcache/api/python/kernelcache_enums.py new file mode 100644 index 00000000..e610c527 --- /dev/null +++ b/view/kernelcache/api/python/kernelcache_enums.py @@ -0,0 +1,14 @@ +import enum + + +class KCViewLoadProgress(enum.IntEnum): + LoadProgressNotStarted = 0 + LoadProgressLoadingCaches = 1 + LoadProgressLoadingImages = 2 + LoadProgressFinished = 3 + + +class KCViewState(enum.IntEnum): + Unloaded = 0 + Loaded = 1 + LoadedWithImages = 2 -- cgit v1.3.1