diff options
| author | kat <kat@vector35.com> | 2024-10-23 21:56:29 -0400 |
|---|---|---|
| committer | kat <kat@vector35.com> | 2024-10-23 21:58:47 -0400 |
| commit | 3006c68e108a18f9b8782bc937dc9ec7e2cb3b54 (patch) | |
| tree | f589bb38909349142c09b6215244205aef0a57ac /view/sharedcache/api/python | |
| parent | 80fcccec8f686e0e5c89626b60af121a97baf856 (diff) | |
Initial commit of the alpha dyld_shared_cache view API Plugin.
This is an early release of our DSC processing plugin. We're still hard at work improving this feature. You should be able to just drop in a dyld_shared_cache and use the 'Shared Cache Triage' view to load and analyze images.
Diffstat (limited to 'view/sharedcache/api/python')
| -rw-r--r-- | view/sharedcache/api/python/CMakeLists.txt | 50 | ||||
| -rw-r--r-- | view/sharedcache/api/python/__init__.py | 8 | ||||
| -rw-r--r-- | view/sharedcache/api/python/_sharedcachecore.py | 659 | ||||
| -rw-r--r-- | view/sharedcache/api/python/_sharedcachecore_template.py | 43 | ||||
| -rw-r--r-- | view/sharedcache/api/python/generator.cpp | 617 | ||||
| -rw-r--r-- | view/sharedcache/api/python/sharedcache.py | 260 | ||||
| -rw-r--r-- | view/sharedcache/api/python/sharedcache_enums.py | 14 |
7 files changed, 1651 insertions, 0 deletions
diff --git a/view/sharedcache/api/python/CMakeLists.txt b/view/sharedcache/api/python/CMakeLists.txt new file mode 100644 index 00000000..c04a57ff --- /dev/null +++ b/view/sharedcache/api/python/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(sharedcache-python-api) + +file(GLOB PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/*.py) +list(REMOVE_ITEM PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/_sharedcachecore.py) +list(REMOVE_ITEM PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/enums.py) + +add_executable(sharedcache_generator + ${PROJECT_SOURCE_DIR}/generator.cpp) +target_link_libraries(sharedcache_generator binaryninjaapi) +target_include_directories(sharedcache_generator PUBLIC {PROJECT_SOURCE_DIR}/../../api) + +set_target_properties(sharedcache_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/sharedcache/) +else() + set(PYTHON_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins/sharedcache/) +endif() + +if(WIN32) + if (BN_INTERNAL_BUILD) + add_custom_command(TARGET sharedcache_generator PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BN_CORE_OUTPUT_DIR}/binaryninjacore.dll ${PROJECT_BINARY_DIR}/) + else() + add_custom_command(TARGET sharedcache_generator PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BN_INSTALL_DIR}/binaryninjacore.dll ${PROJECT_BINARY_DIR}/) + endif() +endif() + +add_custom_target(sharedcache_generator_copy ALL + BYPRODUCTS ${PROJECT_SOURCE_DIR}/_sharedcachecore.py ${PROJECT_SOURCE_DIR}/enums.py + DEPENDS ${PYTHON_SOURCES} ${PROJECT_SOURCE_DIR}/../sharedcachecore.h $<TARGET_FILE:sharedcache_generator> + COMMAND ${CMAKE_COMMAND} -E echo "Copying SharedCache Python Sources" + COMMAND ${CMAKE_COMMAND} -E make_directory ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E env ASAN_OPTIONS=detect_leaks=0 $<TARGET_FILE:sharedcache_generator> + ${PROJECT_SOURCE_DIR}/../sharedcachecore.h + ${PROJECT_SOURCE_DIR}/_sharedcachecore.py + ${PROJECT_SOURCE_DIR}/_sharedcachecore_template.py + ${PROJECT_SOURCE_DIR}/sharedcache_enums.py + + COMMAND ${CMAKE_COMMAND} -E copy ${PYTHON_SOURCES} ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/_sharedcachecore.py ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/sharedcache_enums.py ${PYTHON_OUTPUT_DIRECTORY}) + diff --git a/view/sharedcache/api/python/__init__.py b/view/sharedcache/api/python/__init__.py new file mode 100644 index 00000000..1f65af93 --- /dev/null +++ b/view/sharedcache/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 .sharedcache import * + diff --git a/view/sharedcache/api/python/_sharedcachecore.py b/view/sharedcache/api/python/_sharedcachecore.py new file mode 100644 index 00000000..93228468 --- /dev/null +++ b/view/sharedcache/api/python/_sharedcachecore.py @@ -0,0 +1,659 @@ +import binaryninja +import ctypes, os + +from typing import Optional +from . import sharedcache_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, "libsharedcache.dylib")) + +elif core_platform == "Linux": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libsharedcache.so")) + +elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0): + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "sharedcache.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 BNDSCBackingCache(ctypes.Structure): + @property + def path(self): + return pyNativeStr(self._path) + @path.setter + def path(self, value): + self._path = cstr(value) +BNDSCBackingCacheHandle = ctypes.POINTER(BNDSCBackingCache) +class BNDSCBackingCacheMapping(ctypes.Structure): + pass +BNDSCBackingCacheMappingHandle = ctypes.POINTER(BNDSCBackingCacheMapping) +class BNDSCImage(ctypes.Structure): + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) +BNDSCImageHandle = ctypes.POINTER(BNDSCImage) +class BNDSCImageMemoryMapping(ctypes.Structure): + @property + def filePath(self): + return pyNativeStr(self._filePath) + @filePath.setter + def filePath(self, value): + self._filePath = cstr(value) + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) +BNDSCImageMemoryMappingHandle = ctypes.POINTER(BNDSCImageMemoryMapping) +class BNDSCMappedMemoryRegion(ctypes.Structure): + @property + def name(self): + return pyNativeStr(self._name) + @name.setter + def name(self, value): + self._name = cstr(value) +BNDSCMappedMemoryRegionHandle = ctypes.POINTER(BNDSCMappedMemoryRegion) +class BNDSCMemoryUsageInfo(ctypes.Structure): + pass +BNDSCMemoryUsageInfoHandle = ctypes.POINTER(BNDSCMemoryUsageInfo) +class BNDSCSymbolRep(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) +BNDSCSymbolRepHandle = ctypes.POINTER(BNDSCSymbolRep) +DSCViewLoadProgressEnum = ctypes.c_int +DSCViewStateEnum = ctypes.c_int +class BNSharedCache(ctypes.Structure): + pass +BNSharedCacheHandle = ctypes.POINTER(BNSharedCache) + +# Structure definitions +BNDSCBackingCache._fields_ = [ + ("_path", ctypes.c_char_p), + ("isPrimary", ctypes.c_bool), + ("mappings", ctypes.POINTER(BNDSCBackingCacheMapping)), + ("mappingCount", ctypes.c_ulonglong), + ] +BNDSCBackingCacheMapping._fields_ = [ + ("vmAddress", ctypes.c_ulonglong), + ("size", ctypes.c_ulonglong), + ("fileOffset", ctypes.c_ulonglong), + ] +BNDSCImage._fields_ = [ + ("_name", ctypes.c_char_p), + ("headerAddress", ctypes.c_ulonglong), + ("mappings", ctypes.POINTER(BNDSCImageMemoryMapping)), + ("mappingCount", ctypes.c_ulonglong), + ] +BNDSCImageMemoryMapping._fields_ = [ + ("_filePath", ctypes.c_char_p), + ("_name", ctypes.c_char_p), + ("vmAddress", ctypes.c_ulonglong), + ("size", ctypes.c_ulonglong), + ("loaded", ctypes.c_bool), + ("rawViewOffset", ctypes.c_ulonglong), + ] +BNDSCMappedMemoryRegion._fields_ = [ + ("vmAddress", ctypes.c_ulonglong), + ("size", ctypes.c_ulonglong), + ("_name", ctypes.c_char_p), + ] +BNDSCMemoryUsageInfo._fields_ = [ + ("sharedCacheRefs", ctypes.c_ulonglong), + ("mmapRefs", ctypes.c_ulonglong), + ] +BNDSCSymbolRep._fields_ = [ + ("address", ctypes.c_ulonglong), + ("_name", ctypes.c_char_p), + ("_image", ctypes.c_char_p), + ] + +# Function definitions +# ------------------------------------------------------- +# _BNDSCFindSymbolAtAddressAndApplyToAddress + +_BNDSCFindSymbolAtAddressAndApplyToAddress = core.BNDSCFindSymbolAtAddressAndApplyToAddress +_BNDSCFindSymbolAtAddressAndApplyToAddress.restype = None +_BNDSCFindSymbolAtAddressAndApplyToAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ctypes.c_ulonglong, + ctypes.c_bool, + ] + + +# noinspection PyPep8Naming +def BNDSCFindSymbolAtAddressAndApplyToAddress( + cache: ctypes.POINTER(BNSharedCache), + symbolLocation: int, + targetLocation: int, + triggerReanalysis: bool + ) -> None: + return _BNDSCFindSymbolAtAddressAndApplyToAddress(cache, symbolLocation, targetLocation, triggerReanalysis) + + +# ------------------------------------------------------- +# _BNDSCViewFastGetBackingCacheCount + +_BNDSCViewFastGetBackingCacheCount = core.BNDSCViewFastGetBackingCacheCount +_BNDSCViewFastGetBackingCacheCount.restype = ctypes.c_ulonglong +_BNDSCViewFastGetBackingCacheCount.argtypes = [ + ctypes.POINTER(BNBinaryView), + ] + + +# noinspection PyPep8Naming +def BNDSCViewFastGetBackingCacheCount( + view: ctypes.POINTER(BNBinaryView) + ) -> int: + return _BNDSCViewFastGetBackingCacheCount(view) + + +# ------------------------------------------------------- +# _BNDSCViewFreeAllImages + +_BNDSCViewFreeAllImages = core.BNDSCViewFreeAllImages +_BNDSCViewFreeAllImages.restype = None +_BNDSCViewFreeAllImages.argtypes = [ + ctypes.POINTER(BNDSCImage), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewFreeAllImages( + images: ctypes.POINTER(BNDSCImage), + count: int + ) -> None: + return _BNDSCViewFreeAllImages(images, count) + + +# ------------------------------------------------------- +# _BNDSCViewFreeBackingCaches + +_BNDSCViewFreeBackingCaches = core.BNDSCViewFreeBackingCaches +_BNDSCViewFreeBackingCaches.restype = None +_BNDSCViewFreeBackingCaches.argtypes = [ + ctypes.POINTER(BNDSCBackingCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewFreeBackingCaches( + caches: ctypes.POINTER(BNDSCBackingCache), + count: int + ) -> None: + return _BNDSCViewFreeBackingCaches(caches, count) + + +# ------------------------------------------------------- +# _BNDSCViewFreeLoadedRegions + +_BNDSCViewFreeLoadedRegions = core.BNDSCViewFreeLoadedRegions +_BNDSCViewFreeLoadedRegions.restype = None +_BNDSCViewFreeLoadedRegions.argtypes = [ + ctypes.POINTER(BNDSCMappedMemoryRegion), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewFreeLoadedRegions( + images: ctypes.POINTER(BNDSCMappedMemoryRegion), + count: int + ) -> None: + return _BNDSCViewFreeLoadedRegions(images, count) + + +# ------------------------------------------------------- +# _BNDSCViewFreeSymbols + +_BNDSCViewFreeSymbols = core.BNDSCViewFreeSymbols +_BNDSCViewFreeSymbols.restype = None +_BNDSCViewFreeSymbols.argtypes = [ + ctypes.POINTER(BNDSCSymbolRep), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewFreeSymbols( + symbols: ctypes.POINTER(BNDSCSymbolRep), + count: int + ) -> None: + return _BNDSCViewFreeSymbols(symbols, count) + + +# ------------------------------------------------------- +# _BNDSCViewGetAllImages + +_BNDSCViewGetAllImages = core.BNDSCViewGetAllImages +_BNDSCViewGetAllImages.restype = ctypes.POINTER(BNDSCImage) +_BNDSCViewGetAllImages.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetAllImages( + cache: ctypes.POINTER(BNSharedCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNDSCImage)]: + result = _BNDSCViewGetAllImages(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNDSCViewGetBackingCaches + +_BNDSCViewGetBackingCaches = core.BNDSCViewGetBackingCaches +_BNDSCViewGetBackingCaches.restype = ctypes.POINTER(BNDSCBackingCache) +_BNDSCViewGetBackingCaches.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetBackingCaches( + cache: ctypes.POINTER(BNSharedCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNDSCBackingCache)]: + result = _BNDSCViewGetBackingCaches(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNDSCViewGetImageHeaderForAddress + +_BNDSCViewGetImageHeaderForAddress = core.BNDSCViewGetImageHeaderForAddress +_BNDSCViewGetImageHeaderForAddress.restype = ctypes.POINTER(ctypes.c_byte) +_BNDSCViewGetImageHeaderForAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetImageHeaderForAddress( + cache: ctypes.POINTER(BNSharedCache), + address: int + ) -> Optional[Optional[str]]: + result = _BNDSCViewGetImageHeaderForAddress(cache, address) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNDSCViewGetImageHeaderForName + +_BNDSCViewGetImageHeaderForName = core.BNDSCViewGetImageHeaderForName +_BNDSCViewGetImageHeaderForName.restype = ctypes.POINTER(ctypes.c_byte) +_BNDSCViewGetImageHeaderForName.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_char_p, + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetImageHeaderForName( + cache: ctypes.POINTER(BNSharedCache), + name: Optional[str] + ) -> Optional[Optional[str]]: + result = _BNDSCViewGetImageHeaderForName(cache, cstr(name)) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNDSCViewGetImageNameForAddress + +_BNDSCViewGetImageNameForAddress = core.BNDSCViewGetImageNameForAddress +_BNDSCViewGetImageNameForAddress.restype = ctypes.POINTER(ctypes.c_byte) +_BNDSCViewGetImageNameForAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetImageNameForAddress( + cache: ctypes.POINTER(BNSharedCache), + address: int + ) -> Optional[Optional[str]]: + result = _BNDSCViewGetImageNameForAddress(cache, address) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNDSCViewGetInstallNames + +_BNDSCViewGetInstallNames = core.BNDSCViewGetInstallNames +_BNDSCViewGetInstallNames.restype = ctypes.POINTER(ctypes.c_char_p) +_BNDSCViewGetInstallNames.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetInstallNames( + cache: ctypes.POINTER(BNSharedCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(ctypes.c_char_p)]: + result = _BNDSCViewGetInstallNames(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNDSCViewGetLoadProgress + +_BNDSCViewGetLoadProgress = core.BNDSCViewGetLoadProgress +_BNDSCViewGetLoadProgress.restype = DSCViewLoadProgressEnum +_BNDSCViewGetLoadProgress.argtypes = [ + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetLoadProgress( + sessionID: int + ) -> DSCViewLoadProgressEnum: + return _BNDSCViewGetLoadProgress(sessionID) + + +# ------------------------------------------------------- +# _BNDSCViewGetLoadedRegions + +_BNDSCViewGetLoadedRegions = core.BNDSCViewGetLoadedRegions +_BNDSCViewGetLoadedRegions.restype = ctypes.POINTER(BNDSCMappedMemoryRegion) +_BNDSCViewGetLoadedRegions.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetLoadedRegions( + cache: ctypes.POINTER(BNSharedCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNDSCMappedMemoryRegion)]: + result = _BNDSCViewGetLoadedRegions(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNDSCViewGetMemoryUsageInfo + +_BNDSCViewGetMemoryUsageInfo = core.BNDSCViewGetMemoryUsageInfo +_BNDSCViewGetMemoryUsageInfo.restype = BNDSCMemoryUsageInfo +_BNDSCViewGetMemoryUsageInfo.argtypes = [ + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetMemoryUsageInfo( + ) -> BNDSCMemoryUsageInfo: + return _BNDSCViewGetMemoryUsageInfo() + + +# ------------------------------------------------------- +# _BNDSCViewGetNameForAddress + +_BNDSCViewGetNameForAddress = core.BNDSCViewGetNameForAddress +_BNDSCViewGetNameForAddress.restype = ctypes.POINTER(ctypes.c_byte) +_BNDSCViewGetNameForAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetNameForAddress( + cache: ctypes.POINTER(BNSharedCache), + address: int + ) -> Optional[Optional[str]]: + result = _BNDSCViewGetNameForAddress(cache, address) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNDSCViewGetState + +_BNDSCViewGetState = core.BNDSCViewGetState +_BNDSCViewGetState.restype = DSCViewStateEnum +_BNDSCViewGetState.argtypes = [ + ctypes.POINTER(BNSharedCache), + ] + + +# noinspection PyPep8Naming +def BNDSCViewGetState( + cache: ctypes.POINTER(BNSharedCache) + ) -> DSCViewStateEnum: + return _BNDSCViewGetState(cache) + + +# ------------------------------------------------------- +# _BNDSCViewLoadAllSymbolsAndWait + +_BNDSCViewLoadAllSymbolsAndWait = core.BNDSCViewLoadAllSymbolsAndWait +_BNDSCViewLoadAllSymbolsAndWait.restype = ctypes.POINTER(BNDSCSymbolRep) +_BNDSCViewLoadAllSymbolsAndWait.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNDSCViewLoadAllSymbolsAndWait( + cache: ctypes.POINTER(BNSharedCache), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNDSCSymbolRep)]: + result = _BNDSCViewLoadAllSymbolsAndWait(cache, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNDSCViewLoadImageContainingAddress + +_BNDSCViewLoadImageContainingAddress = core.BNDSCViewLoadImageContainingAddress +_BNDSCViewLoadImageContainingAddress.restype = ctypes.c_bool +_BNDSCViewLoadImageContainingAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewLoadImageContainingAddress( + cache: ctypes.POINTER(BNSharedCache), + address: int + ) -> bool: + return _BNDSCViewLoadImageContainingAddress(cache, address) + + +# ------------------------------------------------------- +# _BNDSCViewLoadImageWithInstallName + +_BNDSCViewLoadImageWithInstallName = core.BNDSCViewLoadImageWithInstallName +_BNDSCViewLoadImageWithInstallName.restype = ctypes.c_bool +_BNDSCViewLoadImageWithInstallName.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_char_p, + ] + + +# noinspection PyPep8Naming +def BNDSCViewLoadImageWithInstallName( + cache: ctypes.POINTER(BNSharedCache), + name: Optional[str] + ) -> bool: + return _BNDSCViewLoadImageWithInstallName(cache, cstr(name)) + + +# ------------------------------------------------------- +# _BNDSCViewLoadSectionAtAddress + +_BNDSCViewLoadSectionAtAddress = core.BNDSCViewLoadSectionAtAddress +_BNDSCViewLoadSectionAtAddress.restype = ctypes.c_bool +_BNDSCViewLoadSectionAtAddress.argtypes = [ + ctypes.POINTER(BNSharedCache), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNDSCViewLoadSectionAtAddress( + cache: ctypes.POINTER(BNSharedCache), + name: int + ) -> bool: + return _BNDSCViewLoadSectionAtAddress(cache, name) + + +# ------------------------------------------------------- +# _BNDSCViewLoadedImageCount + +_BNDSCViewLoadedImageCount = core.BNDSCViewLoadedImageCount +_BNDSCViewLoadedImageCount.restype = ctypes.c_ulonglong +_BNDSCViewLoadedImageCount.argtypes = [ + ctypes.POINTER(BNSharedCache), + ] + + +# noinspection PyPep8Naming +def BNDSCViewLoadedImageCount( + cache: ctypes.POINTER(BNSharedCache) + ) -> int: + return _BNDSCViewLoadedImageCount(cache) + + +# ------------------------------------------------------- +# _BNFreeSharedCacheReference + +_BNFreeSharedCacheReference = core.BNFreeSharedCacheReference +_BNFreeSharedCacheReference.restype = None +_BNFreeSharedCacheReference.argtypes = [ + ctypes.POINTER(BNSharedCache), + ] + + +# noinspection PyPep8Naming +def BNFreeSharedCacheReference( + cache: ctypes.POINTER(BNSharedCache) + ) -> None: + return _BNFreeSharedCacheReference(cache) + + +# ------------------------------------------------------- +# _BNGetSharedCache + +_BNGetSharedCache = core.BNGetSharedCache +_BNGetSharedCache.restype = ctypes.POINTER(BNSharedCache) +_BNGetSharedCache.argtypes = [ + ctypes.POINTER(BNBinaryView), + ] + + +# noinspection PyPep8Naming +def BNGetSharedCache( + data: ctypes.POINTER(BNBinaryView) + ) -> Optional[ctypes.POINTER(BNSharedCache)]: + result = _BNGetSharedCache(data) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNNewSharedCacheReference + +_BNNewSharedCacheReference = core.BNNewSharedCacheReference +_BNNewSharedCacheReference.restype = ctypes.POINTER(BNSharedCache) +_BNNewSharedCacheReference.argtypes = [ + ctypes.POINTER(BNSharedCache), + ] + + +# noinspection PyPep8Naming +def BNNewSharedCacheReference( + cache: ctypes.POINTER(BNSharedCache) + ) -> Optional[ctypes.POINTER(BNSharedCache)]: + result = _BNNewSharedCacheReference(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/sharedcache/api/python/_sharedcachecore_template.py b/view/sharedcache/api/python/_sharedcachecore_template.py new file mode 100644 index 00000000..6b5b174f --- /dev/null +++ b/view/sharedcache/api/python/_sharedcachecore_template.py @@ -0,0 +1,43 @@ +import binaryninja +import ctypes, os + +from typing import Optional +from . import sharedcache_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, "libsharedcache.dylib")) + +elif core_platform == "Linux": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libsharedcache.so")) + +elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0): + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "sharedcache.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/sharedcache/api/python/generator.cpp b/view/sharedcache/api/python/generator.cpp new file mode 100644 index 00000000..91ff88c0 --- /dev/null +++ b/view/sharedcache/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/sharedcache/api/python/sharedcache.py b/view/sharedcache/api/python/sharedcache.py new file mode 100644 index 00000000..ca031e03 --- /dev/null +++ b/view/sharedcache/api/python/sharedcache.py @@ -0,0 +1,260 @@ +import os +import ctypes +import dataclasses +import traceback + +import binaryninja +from binaryninja._binaryninjacore import BNFreeStringList, BNAllocString, BNFreeString + +from . import _sharedcachecore as sccore +from .sharedcache_enums import * + + +@dataclasses.dataclass +class DSCMemoryMapping: + filePath: str + name: str + vmAddress: int + rawViewOffset: int + size: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCMemoryMapping '{self.name}' {os.path.basename(self.filePath)} raw<{self.rawViewOffset:x}>: {self.vmAddress:x}+{self.size:x}>" + + +@dataclasses.dataclass +class LoadedRegion: + name: str + headerAddress: int + mappings: list[DSCMemoryMapping] + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<LoadedRegion {self.name} @ {self.headerAddress:x}>" + + +@dataclasses.dataclass +class DSCBackingCacheMapping: + vmAddress: int + size: int + fileOffset: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCBackingCacheMapping {self.vmAddress:x}+{self.size:x} @ {self.fileOffset:x}" + + +@dataclasses.dataclass +class DSCBackingCache: + path: str + isPrimary: bool + mappings: list[DSCBackingCacheMapping] + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCBackingCache {self.path} {'Primary' if self.isPrimary else 'Secondary'} | {len(self.mappings)} mappings>" + + +@dataclasses.dataclass +class DSCImageMemoryMapping: + filePath: str + name: str + vmAddress: int + size: int + loaded: bool + rawViewOffset: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCImageMemoryMapping '{self.name}' {os.path.basename(self.filePath)} raw<{self.rawViewOffset:x}>: {self.vmAddress:x}+{self.size:x}>" + + +@dataclasses.dataclass +class DSCImage: + name: str + headerAddress: int + mappings: list[DSCImageMemoryMapping] + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCImage {self.name} @ {self.headerAddress:x}>" + + +@dataclasses.dataclass +class DSCSymbol: + name: str + image: str + address: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<DSCSymbol {self.name} @ {self.address:x} ({self.image}>" + + +class SharedCache: + def __init__(self, view): + self.handle = sccore.BNGetSharedCache(view.handle) + + def load_image_with_install_name(self, installName): + str = BNAllocString(installName.encode('utf-8')) + return sccore.BNDSCViewLoadImageWithInstallName(self.handle, str) + + def load_section_at_address(self, addr): + return sccore.BNDSCViewLoadSectionAtAddress(self.handle, addr) + + def load_image_containing_address(self, addr): + return sccore.BNDSCViewLoadImageContainingAddress(self.handle, addr) + + @property + def caches(self): + count = ctypes.c_ulonglong() + value = sccore.BNDSCViewGetBackingCaches(self.handle, count) + if value is None: + return [] + + result = [] + for i in range(count.value): + mappings = [] + for j in range(value[i].mappingCount): + mapping = DSCBackingCacheMapping( + value[i].mappings[j].vmAddress, + value[i].mappings[j].size, + value[i].mappings[j].fileOffset + ) + mappings.append(mapping) + result.append(DSCBackingCache( + value[i].path, + value[i].isPrimary, + mappings + )) + + sccore.BNDSCViewFreeBackingCaches(value, count) + return result + + @property + def images(self): + count = ctypes.c_ulonglong() + value = sccore.BNDSCViewGetAllImages(self.handle, count) + if value is None: + return [] + + result = [] + for i in range(count.value): + mappings = [] + for j in range(value[i].mappingCount): + mapping = DSCImageMemoryMapping( + value[i].mappings[j].filePath, + value[i].mappings[j].name, + value[i].mappings[j].vmAddress, + value[i].mappings[j].size, + value[i].mappings[j].loaded, + value[i].mappings[j].rawViewOffset + ) + mappings.append(mapping) + result.append(DSCImage( + value[i].name, + value[i].headerAddress, + mappings + )) + + sccore.BNDSCViewFreeAllImages(value, count) + return result + + @property + def loaded_images(self): + count = ctypes.c_ulonglong() + value = sccore.BNDSCViewGetLoadedImages(self.handle, count) + if value is None: + return [] + + result = [] + for i in range(count.value): + mappings = [] + for j in range(value[i].mappingCount): + mapping = DSCMemoryMapping( + value[i].mappings[j].filePath, + value[i].mappings[j].name, + value[i].mappings[j].vmAddress, + value[i].mappings[j].rawViewOffset, + value[i].mappings[j].size + ) + mappings.append(mapping) + result.append(LoadedRegion( + value[i].name, + value[i].headerAddress, + mappings + )) + + sccore.BNDSCViewFreeLoadedImages(value, count) + return result + + def load_all_symbols_and_wait(self): + count = ctypes.c_ulonglong() + value = sccore.BNDSCViewLoadAllSymbolsAndWait(self.handle, count) + if value is None: + return [] + result = [] + for i in range(count.value): + sym = DSCSymbol( + value[i].name, + value[i].image, + value[i].address + ) + result.append(sym) + + sccore.BNDSCViewFreeSymbols(value, count) + return result + + @property + def image_names(self): + count = ctypes.c_ulonglong() + value = sccore.BNDSCViewGetInstallNames(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 image_count(self): + return sccore.BNDSCViewLoadedImageCount(self.handle) + + @property + def state(self): + return DSCViewState(sccore.BNDSCViewGetState(self.handle)) + + def get_name_for_address(self, address): + name = sccore.BNDSCViewGetNameForAddress(self.handle, address) + if name is None: + return "" + result = name + return result + + def get_image_name_for_address(self, address): + name = sccore.BNDSCViewGetImageNameForAddress(self.handle, address) + if name is None: + return "" + result = name + return result + + def find_symbol_at_addr_and_apply_to_addr(self, symbolAddress, targetAddress, triggerReanalysis) -> None: + sccore.BNDSCFindSymbolAtAddressAndApplyToAddress(self.handle, symbolAddress, targetAddress, triggerReanalysis) diff --git a/view/sharedcache/api/python/sharedcache_enums.py b/view/sharedcache/api/python/sharedcache_enums.py new file mode 100644 index 00000000..34668424 --- /dev/null +++ b/view/sharedcache/api/python/sharedcache_enums.py @@ -0,0 +1,14 @@ +import enum + + +class DSCViewLoadProgress(enum.IntEnum): + LoadProgressNotStarted = 0 + LoadProgressLoadingCaches = 1 + LoadProgressLoadingImages = 2 + LoadProgressFinished = 3 + + +class DSCViewState(enum.IntEnum): + Unloaded = 0 + Loaded = 1 + LoadedWithImages = 2 |
