diff options
Diffstat (limited to 'view/kernelcache/api')
| -rw-r--r-- | view/kernelcache/api/CMakeLists.txt | 79 | ||||
| -rw-r--r-- | view/kernelcache/api/kernelcache.cpp | 191 | ||||
| -rw-r--r-- | view/kernelcache/api/kernelcacheapi.h | 272 | ||||
| -rw-r--r-- | view/kernelcache/api/kernelcachecore.h | 133 | ||||
| -rw-r--r-- | view/kernelcache/api/python/CMakeLists.txt | 50 | ||||
| -rw-r--r-- | view/kernelcache/api/python/__init__.py | 8 | ||||
| -rw-r--r-- | view/kernelcache/api/python/_kernelcachecore.py | 515 | ||||
| -rw-r--r-- | view/kernelcache/api/python/_kernelcachecore_template.py | 43 | ||||
| -rw-r--r-- | view/kernelcache/api/python/generator.cpp | 617 | ||||
| -rw-r--r-- | view/kernelcache/api/python/kernelcache.py | 238 | ||||
| -rw-r--r-- | view/kernelcache/api/python/kernelcache_enums.py | 14 |
11 files changed, 2160 insertions, 0 deletions
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<BinaryView> view) { + m_object = BNGetKernelCache(view->GetObject()); + } + + BNKCViewLoadProgress KernelCache::GetLoadProgress(Ref<BinaryView> view) + { + return BNKCViewGetLoadProgress(view->GetFile()->GetSessionId()); + } + + uint64_t KernelCache::FastGetImageCount(Ref<BinaryView> 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<std::string> KernelCache::GetAvailableImages() + { + 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; + } + + std::vector<KCImage> KernelCache::GetImages() + { + size_t count; + BNKCImage* value = BNKCViewGetAllImages(m_object, &count); + if (value == nullptr) + { + return {}; + } + + 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); + } + + BNKCViewFreeAllImages(value, count); + 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); + + return result; + } + + std::vector<KCSymbol> KernelCache::LoadAllSymbolsAndWait() + { + size_t count; + BNKCSymbolRep* value = BNKCViewLoadAllSymbolsAndWait(m_object, &count); + if (value == nullptr) + { + return {}; + } + + 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); + } + + 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<KernelCacheMachOHeader> 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<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); + } + +} // 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 <binaryninjaapi.h> +#include "../core/MetadataSerializable.hpp" +#include "../api/view/macho/machoview.h" +#include "kernelcachecore.h" + +using namespace BinaryNinja; + +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; + } + + public: + std::atomic<int> 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 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; + } + } + + public: + std::atomic<int> 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<BackingCacheMapping> 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<KCImageMemoryMapping> mappings; + }; + + struct KCSymbol { + uint64_t address; + std::string name; + std::string image; + }; + + 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; + + 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; + 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; + + std::vector<section_64> symbolStubSections; + std::vector<section_64> symbolPointerSections; + + std::vector<std::string> dylibs; + + 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); + } + + 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<BNKernelCache, BNNewKernelCacheReference, BNFreeKernelCacheReference> { + public: + KernelCache(Ref<BinaryView> view); + + BNKCViewState GetState(); + static BNKCViewLoadProgress GetLoadProgress(Ref<BinaryView> view); + static uint64_t FastGetImageCount(Ref<BinaryView> view); + + bool LoadImageWithInstallName(std::string installName); + bool LoadImageContainingAddress(uint64_t addr); + std::vector<std::string> GetAvailableImages(); + + std::vector<KCSymbol> LoadAllSymbolsAndWait(); + + std::string GetNameForAddress(uint64_t address); + std::string GetImageNameForAddress(uint64_t address); + + std::vector<KCImage> GetImages(); + std::vector<KCImage> GetLoadedImages(); + + std::optional<KernelCacheMachOHeader> GetMachOHeaderForImage(std::string name); + std::optional<KernelCacheMachOHeader> 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 $<TARGET_FILE:kernelcache_generator> + 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 $<TARGET_FILE:kernelcache_generator> + ${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 <stdio.h> +#include <inttypes.h> +#include "binaryninjaapi.h" + +using namespace BinaryNinja; +using namespace std; + + +map<string, string> 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 <header> <output> <output_template> <output_enum>\n"); + return 1; + } + + // Parse API header to get type and function information + map<QualifiedName, Ref<Type>> 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<QualifiedName> structsToProcess; + set<QualifiedName> finishedStructs; + for (auto& i : types) + structsToProcess.insert(i.first); + while (structsToProcess.size() != 0) + { + set<QualifiedName> currentStructList = structsToProcess; + structsToProcess.clear(); + bool processedSome = false; + for (auto& i : currentStructList) + { + string name; + if (i.size() != 1) + continue; + Ref<Type> 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"<KCImageMemoryMapping '{self.name}' {self.vmAddress:x}+{self.size:x} " + f"raw<{self.rawViewOffset:x}>>") + + +@dataclasses.dataclass +class KCImage: + name: str + headerFileAddress: int + mappings: list[KCImageMemoryMapping] + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<KCImage {self.name} @ {self.headerFileAddress:x}>" + + +@dataclasses.dataclass +class KCSymbol: + name: str + image: str + address: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<KCSymbol {self.name} @ {self.address:x} ({self.image})>" + + +@dataclasses.dataclass +class KernelCacheMachOHeader: + header: str + + @classmethod + def LoadFromString(cls, s: str): + return cls(header=s) + + def __repr__(self): + return f"<KernelCacheMachOHeader {self.header!r}>" + + +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 |
