diff options
Diffstat (limited to 'plugins')
81 files changed, 11488 insertions, 3106 deletions
diff --git a/plugins/warp/CMakeLists.txt b/plugins/warp/CMakeLists.txt index 37b86165..4b680b40 100644 --- a/plugins/warp/CMakeLists.txt +++ b/plugins/warp/CMakeLists.txt @@ -21,6 +21,13 @@ file(GLOB_RECURSE PLUGIN_SOURCES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/Cargo.toml ${PROJECT_SOURCE_DIR}/src/*.rs) +# Add API library. +add_subdirectory(api) +# Add UI plugin. +if(NOT HEADLESS) + add_subdirectory(ui) +endif() + if(CMAKE_BUILD_TYPE MATCHES Debug) set(TARGET_DIR ${PROJECT_BINARY_DIR}/target/debug) set(CARGO_OPTS --target-dir=${PROJECT_BINARY_DIR}/target) @@ -38,8 +45,14 @@ endif() set(CARGO_FEATURES "--no-default-features") set(OUTPUT_FILE_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}) set(OUTPUT_PDB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.pdb) +set(OUTPUT_LIB_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.dll.lib) set(OUTPUT_FILE_PATH ${BN_CORE_PLUGIN_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}) set(OUTPUT_PDB_PATH ${BN_CORE_PLUGIN_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}${PROJECT_NAME}.pdb) +set(OUTPUT_LIB_PATH ${BN_CORE_PLUGIN_DIR}/${PROJECT_NAME}.lib) + +# warp_ninja_interface is the target hack im going with for now to get warp_api to build on windows +add_library(${PROJECT_NAME}_interface INTERFACE) +add_dependencies(${PROJECT_NAME}_interface ${PROJECT_NAME}) add_custom_target(${PROJECT_NAME} ALL DEPENDS ${OUTPUT_FILE_PATH}) add_dependencies(${PROJECT_NAME} binaryninjaapi) @@ -109,11 +122,12 @@ if(APPLE) endif() elseif(WIN32) add_custom_command( - OUTPUT ${OUTPUT_FILE_PATH} + OUTPUT ${OUTPUT_FILE_PATH} ${OUTPUT_PDB_PATH} ${OUTPUT_LIB_PATH} COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} clean ${CARGO_OPTS} --package binaryninjacore-sys COMMAND ${CMAKE_COMMAND} -E env BINARYNINJADIR=${BINJA_LIB_DIR} ${RUSTUP_COMMAND} build ${CARGO_OPTS} ${CARGO_FEATURES} COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_FILE_NAME} ${OUTPUT_FILE_PATH} COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_PDB_NAME} ${OUTPUT_PDB_PATH} + COMMAND ${CMAKE_COMMAND} -E copy ${TARGET_DIR}/${OUTPUT_LIB_NAME} ${OUTPUT_LIB_PATH} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} DEPENDS ${PLUGIN_SOURCES} ${API_SOURCES} ) diff --git a/plugins/warp/Cargo.toml b/plugins/warp/Cargo.toml index 07e0a4de..afebf577 100644 --- a/plugins/warp/Cargo.toml +++ b/plugins/warp/Cargo.toml @@ -10,32 +10,31 @@ crate-type = ["lib", "cdylib"] [dependencies] binaryninja = { workspace = true, features = ["rayon"] } binaryninjacore-sys.workspace = true -warp = { git = "https://github.com/Vector35/warp/", rev = "0ee5a6f" } +warp = { git = "https://github.com/Vector35/warp/", tag = "1.0.0" } log = "0.4" -arboard = "3.4" +itertools = "0.14.0" +dashmap = { version = "6.1", features = ["rayon"]} rayon = "1.10" -dashmap = "6.1" +arboard = "3.4" walkdir = "2.5" -rfd = "0.15" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +uuid = { version = "1.12.0", features = ["v4"] } +thiserror = "2.0" +ar = { git = "https://github.com/mdsteele/rust-ar" } +tempdir = "0.3.7" +regex = "1.11" -# For sigem -env_logger = { version = "0.11", optional = true } -clap = { version = "4.5", features = ["derive"], optional = true } -ar = { git = "https://github.com/mdsteele/rust-ar", optional = true } -tempdir = { version = "0.3.7", optional = true } +# For reports +minijinja = "2.10.2" +minijinja-embed = "2.10.2" -[dev-dependencies] -criterion = "0.5.1" -insta = { version = "1.38.0", features = ["yaml"] } +[build-dependencies] +minijinja-embed = "2.10.2" -[features] -default = ["sigem"] -sigem = ["env_logger", "clap", "ar", "tempdir"] - -[[bin]] -name = "sigem" -required-features = ["sigem"] +[dev-dependencies] +criterion = "0.6" +insta = { version = "1.42", features = ["yaml"] } [[bench]] name = "guid" diff --git a/plugins/warp/api/CMakeLists.txt b/plugins/warp/api/CMakeLists.txt new file mode 100644 index 00000000..4aded248 --- /dev/null +++ b/plugins/warp/api/CMakeLists.txt @@ -0,0 +1,49 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +project(warp_api CXX C) + +add_library(warp_api STATIC warp.cpp warp.h warpcore.h) +target_include_directories(warp_api PUBLIC ${PROJECT_SOURCE_DIR}) + +if (NOT BN_API_BUILD_EXAMPLES AND NOT BN_INTERNAL_BUILD) + # Out-of-tree build + find_path( + BN_API_PATH + NAMES binaryninjaapi.h + HINTS ../.. binaryninjaapi $ENV{BN_API_PATH} + REQUIRED + ) + add_subdirectory(${BN_API_PATH} api) +endif() + +# Make sure the core rust dylib is built before warp_api. +add_dependencies(warp_api warp_ninja) + +target_link_libraries(warp_api binaryninjaui) + +if (NOT DEMO) + add_subdirectory(python) +endif() + +# Link to the warp plugin. +# TODO: Need to make this less scuffed, but this will do for now. +if (WIN32) + # By linking to warp_ninja_interface we insure that we get built _after_ the lib has been generated. + target_link_libraries(${PROJECT_NAME} warp_ninja_interface "${BN_CORE_PLUGIN_DIR}/warp_ninja.lib") +else () + target_link_libraries(${PROJECT_NAME} warp_ninja_interface ${BN_CORE_PLUGIN_DIR}/libwarp_ninja${CMAKE_SHARED_LIBRARY_SUFFIX}) + # Set RPATH so the library can be found at runtime + if(APPLE) + set_target_properties(${PROJECT_NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "@loader_path") + else() + set_target_properties(${PROJECT_NAME} PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE INSTALL_RPATH "$ORIGIN") + endif() +endif() + +set_target_properties(warp_api 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)
\ No newline at end of file diff --git a/plugins/warp/api/python/CMakeLists.txt b/plugins/warp/api/python/CMakeLists.txt new file mode 100644 index 00000000..8e6003c0 --- /dev/null +++ b/plugins/warp/api/python/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.9...3.15 FATAL_ERROR) + +project(warp-python-api) + +file(GLOB PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/*.py) +list(REMOVE_ITEM PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/_warpcore.py) +list(REMOVE_ITEM PYTHON_SOURCES ${PROJECT_SOURCE_DIR}/enums.py) + +add_executable(warp_generator + ${PROJECT_SOURCE_DIR}/generator.cpp) +target_link_libraries(warp_generator binaryninjaapi) +target_include_directories(warp_generator PUBLIC {PROJECT_SOURCE_DIR}/../../api) + +set_target_properties(warp_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/warp/) +else() + set(PYTHON_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/out/plugins/warp/) +endif() + +if(WIN32) + if (BN_INTERNAL_BUILD) + add_custom_command(TARGET warp_generator PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BN_CORE_OUTPUT_DIR}/binaryninjacore.dll ${PROJECT_BINARY_DIR}/) + else() + add_custom_command(TARGET warp_generator PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BN_INSTALL_DIR}/binaryninjacore.dll ${PROJECT_BINARY_DIR}/) + endif() +endif() + +add_custom_target(warp_generator_copy ALL + BYPRODUCTS ${PROJECT_SOURCE_DIR}/_warpcore.py ${PROJECT_SOURCE_DIR}/enums.py + DEPENDS ${PYTHON_SOURCES} ${PROJECT_SOURCE_DIR}/../warpcore.h $<TARGET_FILE:warp_generator> + COMMAND ${CMAKE_COMMAND} -E echo "Copying WARP Python Sources" + COMMAND ${CMAKE_COMMAND} -E make_directory ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E env ASAN_OPTIONS=detect_leaks=0 $<TARGET_FILE:warp_generator> + ${PROJECT_SOURCE_DIR}/../warpcore.h + ${PROJECT_SOURCE_DIR}/_warpcore.py + ${PROJECT_SOURCE_DIR}/_warpcore_template.py + ${PROJECT_SOURCE_DIR}/warp_enums.py + + COMMAND ${CMAKE_COMMAND} -E copy ${PYTHON_SOURCES} ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/_warpcore.py ${PYTHON_OUTPUT_DIRECTORY} + COMMAND ${CMAKE_COMMAND} -E copy ${PROJECT_SOURCE_DIR}/warp_enums.py ${PYTHON_OUTPUT_DIRECTORY}) + diff --git a/plugins/warp/api/python/__init__.py b/plugins/warp/api/python/__init__.py new file mode 100644 index 00000000..f4bc8e83 --- /dev/null +++ b/plugins/warp/api/python/__init__.py @@ -0,0 +1,7 @@ +import os + +from binaryninja._binaryninjacore import BNGetUserPluginDirectory +user_plugin_dir = os.path.realpath(BNGetUserPluginDirectory()) +current_path = os.path.realpath(__file__) + +from .warp import *
\ No newline at end of file diff --git a/plugins/warp/api/python/_warpcore.py b/plugins/warp/api/python/_warpcore.py new file mode 100644 index 00000000..c53feefa --- /dev/null +++ b/plugins/warp/api/python/_warpcore.py @@ -0,0 +1,1081 @@ +import binaryninja +import ctypes, os + +from typing import Optional +from . import warp_enums +# Load core module +import platform +core = None +core_platform = platform.system() + +from binaryninja import Settings +if Settings().get_bool("corePlugins.warp"): + from binaryninja._binaryninjacore import BNGetBundledPluginDirectory + if core_platform == "Darwin": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.dylib")) + + elif core_platform == "Linux": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.so")) + + elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0): + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "warp_ninja.dll")) + else: + raise Exception("OS not supported") +else: + from binaryninja._binaryninjacore import BNGetUserPluginDirectory + if core_platform == "Darwin": + _base_path = BNGetUserPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.dylib")) + + elif core_platform == "Linux": + _base_path = BNGetUserPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.so")) + + elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0): + _base_path = BNGetUserPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "warp_ninja.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))) + +from binaryninja._binaryninjacore import BNFreeString +# Type definitions +from binaryninja._binaryninjacore import BNArchitecture, BNArchitectureHandle +from binaryninja._binaryninjacore import BNBasicBlock, BNBasicBlockHandle +from binaryninja._binaryninjacore import BNBinaryView, BNBinaryViewHandle +from binaryninja._binaryninjacore import BNFunction, BNFunctionHandle +from binaryninja._binaryninjacore import BNLowLevelILFunction, BNLowLevelILFunctionHandle +from binaryninja._binaryninjacore import BNPlatform, BNPlatformHandle +from binaryninja._binaryninjacore import BNSymbol, BNSymbolHandle +from binaryninja._binaryninjacore import BNType, BNTypeHandle +class BNWARPConstraint(ctypes.Structure): + pass +BNWARPConstraintHandle = ctypes.POINTER(BNWARPConstraint) +class BNWARPContainer(ctypes.Structure): + pass +BNWARPContainerHandle = ctypes.POINTER(BNWARPContainer) +class BNWARPFunction(ctypes.Structure): + pass +BNWARPFunctionHandle = ctypes.POINTER(BNWARPFunction) +class BNWARPFunctionComment(ctypes.Structure): + @property + def text(self): + return pyNativeStr(self._text) + @text.setter + def text(self, value): + self._text = cstr(value) +BNWARPFunctionCommentHandle = ctypes.POINTER(BNWARPFunctionComment) +class BNWARPTarget(ctypes.Structure): + pass +BNWARPTargetHandle = ctypes.POINTER(BNWARPTarget) +class BNWARPUUID(ctypes.Structure): + pass +BNWARPUUIDHandle = ctypes.POINTER(BNWARPUUID) + +# Structure definitions +BNWARPBasicBlockGUID = BNWARPUUID +BNWARPBasicBlockGUIDHandle = BNWARPUUIDHandle +BNWARPConstraintGUID = BNWARPUUID +BNWARPConstraintGUIDHandle = BNWARPUUIDHandle +BNWARPFunctionComment._fields_ = [ + ("_text", ctypes.c_char_p), + ("offset", ctypes.c_longlong), + ] +BNWARPFunctionGUID = BNWARPUUID +BNWARPFunctionGUIDHandle = BNWARPUUIDHandle +BNWARPSource = BNWARPUUID +BNWARPSourceHandle = BNWARPUUIDHandle +BNWARPTypeGUID = BNWARPUUID +BNWARPTypeGUIDHandle = BNWARPUUIDHandle +BNWARPUUID._fields_ = [ + ("uuid", ctypes.c_ubyte * 16), + ] +BNWARPConstraint._fields_ = [ + ("guid", BNWARPConstraintGUID), + ("offset", ctypes.c_longlong), + ] + +# Function definitions +# ------------------------------------------------------- +# _BNWARPContainerAddFunctions + +_BNWARPContainerAddFunctions = core.BNWARPContainerAddFunctions +_BNWARPContainerAddFunctions.restype = ctypes.c_bool +_BNWARPContainerAddFunctions.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPTarget), + ctypes.POINTER(BNWARPSource), + ctypes.POINTER(ctypes.POINTER(BNWARPFunction)), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPContainerAddFunctions( + container: ctypes.POINTER(BNWARPContainer), + target: ctypes.POINTER(BNWARPTarget), + source: ctypes.POINTER(BNWARPSource), + functions: ctypes.POINTER(ctypes.POINTER(BNWARPFunction)), + count: int + ) -> bool: + return _BNWARPContainerAddFunctions(container, target, source, functions, count) + + +# ------------------------------------------------------- +# _BNWARPContainerAddSource + +_BNWARPContainerAddSource = core.BNWARPContainerAddSource +_BNWARPContainerAddSource.restype = ctypes.c_bool +_BNWARPContainerAddSource.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.c_char_p, + ctypes.POINTER(BNWARPSource), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerAddSource( + container: ctypes.POINTER(BNWARPContainer), + sourcePath: Optional[str], + result: ctypes.POINTER(BNWARPSource) + ) -> bool: + return _BNWARPContainerAddSource(container, cstr(sourcePath), result) + + +# ------------------------------------------------------- +# _BNWARPContainerAddTypes + +_BNWARPContainerAddTypes = core.BNWARPContainerAddTypes +_BNWARPContainerAddTypes.restype = ctypes.c_bool +_BNWARPContainerAddTypes.argtypes = [ + ctypes.POINTER(BNBinaryView), + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPSource), + ctypes.POINTER(ctypes.POINTER(BNType)), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPContainerAddTypes( + view: ctypes.POINTER(BNBinaryView), + container: ctypes.POINTER(BNWARPContainer), + source: ctypes.POINTER(BNWARPSource), + types: ctypes.POINTER(ctypes.POINTER(BNType)), + count: int + ) -> bool: + return _BNWARPContainerAddTypes(view, container, source, types, count) + + +# ------------------------------------------------------- +# _BNWARPContainerCommitSource + +_BNWARPContainerCommitSource = core.BNWARPContainerCommitSource +_BNWARPContainerCommitSource.restype = ctypes.c_bool +_BNWARPContainerCommitSource.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPSource), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerCommitSource( + container: ctypes.POINTER(BNWARPContainer), + source: ctypes.POINTER(BNWARPSource) + ) -> bool: + return _BNWARPContainerCommitSource(container, source) + + +# ------------------------------------------------------- +# _BNWARPContainerGetFunctionsWithGUID + +_BNWARPContainerGetFunctionsWithGUID = core.BNWARPContainerGetFunctionsWithGUID +_BNWARPContainerGetFunctionsWithGUID.restype = ctypes.POINTER(ctypes.POINTER(BNWARPFunction)) +_BNWARPContainerGetFunctionsWithGUID.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPTarget), + ctypes.POINTER(BNWARPSource), + ctypes.POINTER(BNWARPFunctionGUID), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerGetFunctionsWithGUID( + container: ctypes.POINTER(BNWARPContainer), + target: ctypes.POINTER(BNWARPTarget), + source: ctypes.POINTER(BNWARPSource), + guid: ctypes.POINTER(BNWARPFunctionGUID), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(ctypes.POINTER(BNWARPFunction))]: + result = _BNWARPContainerGetFunctionsWithGUID(container, target, source, guid, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPContainerGetName + +_BNWARPContainerGetName = core.BNWARPContainerGetName +_BNWARPContainerGetName.restype = ctypes.POINTER(ctypes.c_byte) +_BNWARPContainerGetName.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerGetName( + container: ctypes.POINTER(BNWARPContainer) + ) -> Optional[Optional[str]]: + result = _BNWARPContainerGetName(container) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNWARPContainerGetSourcePath + +_BNWARPContainerGetSourcePath = core.BNWARPContainerGetSourcePath +_BNWARPContainerGetSourcePath.restype = ctypes.POINTER(ctypes.c_byte) +_BNWARPContainerGetSourcePath.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPSource), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerGetSourcePath( + container: ctypes.POINTER(BNWARPContainer), + source: ctypes.POINTER(BNWARPSource) + ) -> Optional[Optional[str]]: + result = _BNWARPContainerGetSourcePath(container, source) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNWARPContainerGetSources + +_BNWARPContainerGetSources = core.BNWARPContainerGetSources +_BNWARPContainerGetSources.restype = ctypes.POINTER(BNWARPSource) +_BNWARPContainerGetSources.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerGetSources( + container: ctypes.POINTER(BNWARPContainer), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNWARPSource)]: + result = _BNWARPContainerGetSources(container, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPContainerGetSourcesWithFunctionGUID + +_BNWARPContainerGetSourcesWithFunctionGUID = core.BNWARPContainerGetSourcesWithFunctionGUID +_BNWARPContainerGetSourcesWithFunctionGUID.restype = ctypes.POINTER(BNWARPSource) +_BNWARPContainerGetSourcesWithFunctionGUID.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPTarget), + ctypes.POINTER(BNWARPFunctionGUID), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerGetSourcesWithFunctionGUID( + container: ctypes.POINTER(BNWARPContainer), + target: ctypes.POINTER(BNWARPTarget), + guid: ctypes.POINTER(BNWARPFunctionGUID), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNWARPSource)]: + result = _BNWARPContainerGetSourcesWithFunctionGUID(container, target, guid, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPContainerGetSourcesWithTypeGUID + +_BNWARPContainerGetSourcesWithTypeGUID = core.BNWARPContainerGetSourcesWithTypeGUID +_BNWARPContainerGetSourcesWithTypeGUID.restype = ctypes.POINTER(BNWARPSource) +_BNWARPContainerGetSourcesWithTypeGUID.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPTypeGUID), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerGetSourcesWithTypeGUID( + container: ctypes.POINTER(BNWARPContainer), + guid: ctypes.POINTER(BNWARPTypeGUID), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNWARPSource)]: + result = _BNWARPContainerGetSourcesWithTypeGUID(container, guid, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPContainerGetTypeGUIDsWithName + +_BNWARPContainerGetTypeGUIDsWithName = core.BNWARPContainerGetTypeGUIDsWithName +_BNWARPContainerGetTypeGUIDsWithName.restype = ctypes.POINTER(BNWARPTypeGUID) +_BNWARPContainerGetTypeGUIDsWithName.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPSource), + ctypes.c_char_p, + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerGetTypeGUIDsWithName( + container: ctypes.POINTER(BNWARPContainer), + source: ctypes.POINTER(BNWARPSource), + name: Optional[str], + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNWARPTypeGUID)]: + result = _BNWARPContainerGetTypeGUIDsWithName(container, source, cstr(name), count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPContainerGetTypeWithGUID + +_BNWARPContainerGetTypeWithGUID = core.BNWARPContainerGetTypeWithGUID +_BNWARPContainerGetTypeWithGUID.restype = ctypes.POINTER(BNType) +_BNWARPContainerGetTypeWithGUID.argtypes = [ + ctypes.POINTER(BNArchitecture), + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPSource), + ctypes.POINTER(BNWARPTypeGUID), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerGetTypeWithGUID( + arch: ctypes.POINTER(BNArchitecture), + container: ctypes.POINTER(BNWARPContainer), + source: ctypes.POINTER(BNWARPSource), + guid: ctypes.POINTER(BNWARPTypeGUID) + ) -> Optional[ctypes.POINTER(BNType)]: + result = _BNWARPContainerGetTypeWithGUID(arch, container, source, guid) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPContainerIsSourceUncommitted + +_BNWARPContainerIsSourceUncommitted = core.BNWARPContainerIsSourceUncommitted +_BNWARPContainerIsSourceUncommitted.restype = ctypes.c_bool +_BNWARPContainerIsSourceUncommitted.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPSource), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerIsSourceUncommitted( + container: ctypes.POINTER(BNWARPContainer), + source: ctypes.POINTER(BNWARPSource) + ) -> bool: + return _BNWARPContainerIsSourceUncommitted(container, source) + + +# ------------------------------------------------------- +# _BNWARPContainerIsSourceWritable + +_BNWARPContainerIsSourceWritable = core.BNWARPContainerIsSourceWritable +_BNWARPContainerIsSourceWritable.restype = ctypes.c_bool +_BNWARPContainerIsSourceWritable.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPSource), + ] + + +# noinspection PyPep8Naming +def BNWARPContainerIsSourceWritable( + container: ctypes.POINTER(BNWARPContainer), + source: ctypes.POINTER(BNWARPSource) + ) -> bool: + return _BNWARPContainerIsSourceWritable(container, source) + + +# ------------------------------------------------------- +# _BNWARPContainerRemoveFunctions + +_BNWARPContainerRemoveFunctions = core.BNWARPContainerRemoveFunctions +_BNWARPContainerRemoveFunctions.restype = ctypes.c_bool +_BNWARPContainerRemoveFunctions.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPTarget), + ctypes.POINTER(BNWARPSource), + ctypes.POINTER(ctypes.POINTER(BNWARPFunction)), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPContainerRemoveFunctions( + container: ctypes.POINTER(BNWARPContainer), + target: ctypes.POINTER(BNWARPTarget), + source: ctypes.POINTER(BNWARPSource), + functions: ctypes.POINTER(ctypes.POINTER(BNWARPFunction)), + count: int + ) -> bool: + return _BNWARPContainerRemoveFunctions(container, target, source, functions, count) + + +# ------------------------------------------------------- +# _BNWARPContainerRemoveTypes + +_BNWARPContainerRemoveTypes = core.BNWARPContainerRemoveTypes +_BNWARPContainerRemoveTypes.restype = ctypes.c_bool +_BNWARPContainerRemoveTypes.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ctypes.POINTER(BNWARPSource), + ctypes.POINTER(BNWARPTypeGUID), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPContainerRemoveTypes( + container: ctypes.POINTER(BNWARPContainer), + source: ctypes.POINTER(BNWARPSource), + types: ctypes.POINTER(BNWARPTypeGUID), + count: int + ) -> bool: + return _BNWARPContainerRemoveTypes(container, source, types, count) + + +# ------------------------------------------------------- +# _BNWARPFreeConstraintList + +_BNWARPFreeConstraintList = core.BNWARPFreeConstraintList +_BNWARPFreeConstraintList.restype = None +_BNWARPFreeConstraintList.argtypes = [ + ctypes.POINTER(BNWARPConstraint), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPFreeConstraintList( + constraints: ctypes.POINTER(BNWARPConstraint), + count: int + ) -> None: + return _BNWARPFreeConstraintList(constraints, count) + + +# ------------------------------------------------------- +# _BNWARPFreeContainerList + +_BNWARPFreeContainerList = core.BNWARPFreeContainerList +_BNWARPFreeContainerList.restype = None +_BNWARPFreeContainerList.argtypes = [ + ctypes.POINTER(ctypes.POINTER(BNWARPContainer)), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPFreeContainerList( + containers: ctypes.POINTER(ctypes.POINTER(BNWARPContainer)), + count: int + ) -> None: + return _BNWARPFreeContainerList(containers, count) + + +# ------------------------------------------------------- +# _BNWARPFreeContainerReference + +_BNWARPFreeContainerReference = core.BNWARPFreeContainerReference +_BNWARPFreeContainerReference.restype = None +_BNWARPFreeContainerReference.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ] + + +# noinspection PyPep8Naming +def BNWARPFreeContainerReference( + container: ctypes.POINTER(BNWARPContainer) + ) -> None: + return _BNWARPFreeContainerReference(container) + + +# ------------------------------------------------------- +# _BNWARPFreeFunctionCommentList + +_BNWARPFreeFunctionCommentList = core.BNWARPFreeFunctionCommentList +_BNWARPFreeFunctionCommentList.restype = None +_BNWARPFreeFunctionCommentList.argtypes = [ + ctypes.POINTER(BNWARPFunctionComment), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPFreeFunctionCommentList( + comments: ctypes.POINTER(BNWARPFunctionComment), + count: int + ) -> None: + return _BNWARPFreeFunctionCommentList(comments, count) + + +# ------------------------------------------------------- +# _BNWARPFreeFunctionList + +_BNWARPFreeFunctionList = core.BNWARPFreeFunctionList +_BNWARPFreeFunctionList.restype = None +_BNWARPFreeFunctionList.argtypes = [ + ctypes.POINTER(ctypes.POINTER(BNWARPFunction)), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPFreeFunctionList( + functions: ctypes.POINTER(ctypes.POINTER(BNWARPFunction)), + count: int + ) -> None: + return _BNWARPFreeFunctionList(functions, count) + + +# ------------------------------------------------------- +# _BNWARPFreeFunctionReference + +_BNWARPFreeFunctionReference = core.BNWARPFreeFunctionReference +_BNWARPFreeFunctionReference.restype = None +_BNWARPFreeFunctionReference.argtypes = [ + ctypes.POINTER(BNWARPFunction), + ] + + +# noinspection PyPep8Naming +def BNWARPFreeFunctionReference( + function: ctypes.POINTER(BNWARPFunction) + ) -> None: + return _BNWARPFreeFunctionReference(function) + + +# ------------------------------------------------------- +# _BNWARPFreeTargetReference + +_BNWARPFreeTargetReference = core.BNWARPFreeTargetReference +_BNWARPFreeTargetReference.restype = None +_BNWARPFreeTargetReference.argtypes = [ + ctypes.POINTER(BNWARPTarget), + ] + + +# noinspection PyPep8Naming +def BNWARPFreeTargetReference( + target: ctypes.POINTER(BNWARPTarget) + ) -> None: + return _BNWARPFreeTargetReference(target) + + +# ------------------------------------------------------- +# _BNWARPFreeUUIDList + +_BNWARPFreeUUIDList = core.BNWARPFreeUUIDList +_BNWARPFreeUUIDList.restype = None +_BNWARPFreeUUIDList.argtypes = [ + ctypes.POINTER(BNWARPUUID), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPFreeUUIDList( + uuids: ctypes.POINTER(BNWARPUUID), + count: int + ) -> None: + return _BNWARPFreeUUIDList(uuids, count) + + +# ------------------------------------------------------- +# _BNWARPFunctionApply + +_BNWARPFunctionApply = core.BNWARPFunctionApply +_BNWARPFunctionApply.restype = None +_BNWARPFunctionApply.argtypes = [ + ctypes.POINTER(BNWARPFunction), + ctypes.POINTER(BNFunction), + ] + + +# noinspection PyPep8Naming +def BNWARPFunctionApply( + function: ctypes.POINTER(BNWARPFunction), + analysisFunction: ctypes.POINTER(BNFunction) + ) -> None: + return _BNWARPFunctionApply(function, analysisFunction) + + +# ------------------------------------------------------- +# _BNWARPFunctionGetComments + +_BNWARPFunctionGetComments = core.BNWARPFunctionGetComments +_BNWARPFunctionGetComments.restype = ctypes.POINTER(BNWARPFunctionComment) +_BNWARPFunctionGetComments.argtypes = [ + ctypes.POINTER(BNWARPFunction), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNWARPFunctionGetComments( + function: ctypes.POINTER(BNWARPFunction), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNWARPFunctionComment)]: + result = _BNWARPFunctionGetComments(function, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPFunctionGetConstraints + +_BNWARPFunctionGetConstraints = core.BNWARPFunctionGetConstraints +_BNWARPFunctionGetConstraints.restype = ctypes.POINTER(BNWARPConstraint) +_BNWARPFunctionGetConstraints.argtypes = [ + ctypes.POINTER(BNWARPFunction), + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNWARPFunctionGetConstraints( + function: ctypes.POINTER(BNWARPFunction), + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(BNWARPConstraint)]: + result = _BNWARPFunctionGetConstraints(function, count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPFunctionGetGUID + +_BNWARPFunctionGetGUID = core.BNWARPFunctionGetGUID +_BNWARPFunctionGetGUID.restype = BNWARPFunctionGUID +_BNWARPFunctionGetGUID.argtypes = [ + ctypes.POINTER(BNWARPFunction), + ] + + +# noinspection PyPep8Naming +def BNWARPFunctionGetGUID( + function: ctypes.POINTER(BNWARPFunction) + ) -> BNWARPFunctionGUID: + return _BNWARPFunctionGetGUID(function) + + +# ------------------------------------------------------- +# _BNWARPFunctionGetSymbol + +_BNWARPFunctionGetSymbol = core.BNWARPFunctionGetSymbol +_BNWARPFunctionGetSymbol.restype = ctypes.POINTER(BNSymbol) +_BNWARPFunctionGetSymbol.argtypes = [ + ctypes.POINTER(BNWARPFunction), + ctypes.POINTER(BNFunction), + ] + + +# noinspection PyPep8Naming +def BNWARPFunctionGetSymbol( + function: ctypes.POINTER(BNWARPFunction), + analysisFunction: ctypes.POINTER(BNFunction) + ) -> Optional[ctypes.POINTER(BNSymbol)]: + result = _BNWARPFunctionGetSymbol(function, analysisFunction) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPFunctionGetSymbolName + +_BNWARPFunctionGetSymbolName = core.BNWARPFunctionGetSymbolName +_BNWARPFunctionGetSymbolName.restype = ctypes.POINTER(ctypes.c_byte) +_BNWARPFunctionGetSymbolName.argtypes = [ + ctypes.POINTER(BNWARPFunction), + ] + + +# noinspection PyPep8Naming +def BNWARPFunctionGetSymbolName( + function: ctypes.POINTER(BNWARPFunction) + ) -> Optional[Optional[str]]: + result = _BNWARPFunctionGetSymbolName(function) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + +# ------------------------------------------------------- +# _BNWARPFunctionGetType + +_BNWARPFunctionGetType = core.BNWARPFunctionGetType +_BNWARPFunctionGetType.restype = ctypes.POINTER(BNType) +_BNWARPFunctionGetType.argtypes = [ + ctypes.POINTER(BNWARPFunction), + ctypes.POINTER(BNFunction), + ] + + +# noinspection PyPep8Naming +def BNWARPFunctionGetType( + function: ctypes.POINTER(BNWARPFunction), + analysisFunction: ctypes.POINTER(BNFunction) + ) -> Optional[ctypes.POINTER(BNType)]: + result = _BNWARPFunctionGetType(function, analysisFunction) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPFunctionsEqual + +_BNWARPFunctionsEqual = core.BNWARPFunctionsEqual +_BNWARPFunctionsEqual.restype = ctypes.c_bool +_BNWARPFunctionsEqual.argtypes = [ + ctypes.POINTER(BNWARPFunction), + ctypes.POINTER(BNWARPFunction), + ] + + +# noinspection PyPep8Naming +def BNWARPFunctionsEqual( + functionA: ctypes.POINTER(BNWARPFunction), + functionB: ctypes.POINTER(BNWARPFunction) + ) -> bool: + return _BNWARPFunctionsEqual(functionA, functionB) + + +# ------------------------------------------------------- +# _BNWARPGetAnalysisFunctionGUID + +_BNWARPGetAnalysisFunctionGUID = core.BNWARPGetAnalysisFunctionGUID +_BNWARPGetAnalysisFunctionGUID.restype = ctypes.c_bool +_BNWARPGetAnalysisFunctionGUID.argtypes = [ + ctypes.POINTER(BNFunction), + ctypes.POINTER(BNWARPFunctionGUID), + ] + + +# noinspection PyPep8Naming +def BNWARPGetAnalysisFunctionGUID( + analysisFunction: ctypes.POINTER(BNFunction), + result: ctypes.POINTER(BNWARPFunctionGUID) + ) -> bool: + return _BNWARPGetAnalysisFunctionGUID(analysisFunction, result) + + +# ------------------------------------------------------- +# _BNWARPGetBasicBlockGUID + +_BNWARPGetBasicBlockGUID = core.BNWARPGetBasicBlockGUID +_BNWARPGetBasicBlockGUID.restype = ctypes.c_bool +_BNWARPGetBasicBlockGUID.argtypes = [ + ctypes.POINTER(BNBasicBlock), + ctypes.POINTER(BNWARPBasicBlockGUID), + ] + + +# noinspection PyPep8Naming +def BNWARPGetBasicBlockGUID( + basicBlock: ctypes.POINTER(BNBasicBlock), + result: ctypes.POINTER(BNWARPBasicBlockGUID) + ) -> bool: + return _BNWARPGetBasicBlockGUID(basicBlock, result) + + +# ------------------------------------------------------- +# _BNWARPGetContainers + +_BNWARPGetContainers = core.BNWARPGetContainers +_BNWARPGetContainers.restype = ctypes.POINTER(ctypes.POINTER(BNWARPContainer)) +_BNWARPGetContainers.argtypes = [ + ctypes.POINTER(ctypes.c_ulonglong), + ] + + +# noinspection PyPep8Naming +def BNWARPGetContainers( + count: ctypes.POINTER(ctypes.c_ulonglong) + ) -> Optional[ctypes.POINTER(ctypes.POINTER(BNWARPContainer))]: + result = _BNWARPGetContainers(count) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPGetFunction + +_BNWARPGetFunction = core.BNWARPGetFunction +_BNWARPGetFunction.restype = ctypes.POINTER(BNWARPFunction) +_BNWARPGetFunction.argtypes = [ + ctypes.POINTER(BNFunction), + ] + + +# noinspection PyPep8Naming +def BNWARPGetFunction( + analysisFunction: ctypes.POINTER(BNFunction) + ) -> Optional[ctypes.POINTER(BNWARPFunction)]: + result = _BNWARPGetFunction(analysisFunction) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPGetMatchedFunction + +_BNWARPGetMatchedFunction = core.BNWARPGetMatchedFunction +_BNWARPGetMatchedFunction.restype = ctypes.POINTER(BNWARPFunction) +_BNWARPGetMatchedFunction.argtypes = [ + ctypes.POINTER(BNFunction), + ] + + +# noinspection PyPep8Naming +def BNWARPGetMatchedFunction( + analysisFunction: ctypes.POINTER(BNFunction) + ) -> Optional[ctypes.POINTER(BNWARPFunction)]: + result = _BNWARPGetMatchedFunction(analysisFunction) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPGetTarget + +_BNWARPGetTarget = core.BNWARPGetTarget +_BNWARPGetTarget.restype = ctypes.POINTER(BNWARPTarget) +_BNWARPGetTarget.argtypes = [ + ctypes.POINTER(BNPlatform), + ] + + +# noinspection PyPep8Naming +def BNWARPGetTarget( + platform: ctypes.POINTER(BNPlatform) + ) -> Optional[ctypes.POINTER(BNWARPTarget)]: + result = _BNWARPGetTarget(platform) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPIsLiftedInstructionBlacklisted + +_BNWARPIsLiftedInstructionBlacklisted = core.BNWARPIsLiftedInstructionBlacklisted +_BNWARPIsLiftedInstructionBlacklisted.restype = ctypes.c_bool +_BNWARPIsLiftedInstructionBlacklisted.argtypes = [ + ctypes.POINTER(BNLowLevelILFunction), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPIsLiftedInstructionBlacklisted( + liftedFunction: ctypes.POINTER(BNLowLevelILFunction), + idx: int + ) -> bool: + return _BNWARPIsLiftedInstructionBlacklisted(liftedFunction, idx) + + +# ------------------------------------------------------- +# _BNWARPIsLiftedInstructionVariant + +_BNWARPIsLiftedInstructionVariant = core.BNWARPIsLiftedInstructionVariant +_BNWARPIsLiftedInstructionVariant.restype = ctypes.c_bool +_BNWARPIsLiftedInstructionVariant.argtypes = [ + ctypes.POINTER(BNLowLevelILFunction), + ctypes.c_ulonglong, + ] + + +# noinspection PyPep8Naming +def BNWARPIsLiftedInstructionVariant( + liftedFunction: ctypes.POINTER(BNLowLevelILFunction), + idx: int + ) -> bool: + return _BNWARPIsLiftedInstructionVariant(liftedFunction, idx) + + +# ------------------------------------------------------- +# _BNWARPNewContainerReference + +_BNWARPNewContainerReference = core.BNWARPNewContainerReference +_BNWARPNewContainerReference.restype = ctypes.POINTER(BNWARPContainer) +_BNWARPNewContainerReference.argtypes = [ + ctypes.POINTER(BNWARPContainer), + ] + + +# noinspection PyPep8Naming +def BNWARPNewContainerReference( + container: ctypes.POINTER(BNWARPContainer) + ) -> Optional[ctypes.POINTER(BNWARPContainer)]: + result = _BNWARPNewContainerReference(container) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPNewFunctionReference + +_BNWARPNewFunctionReference = core.BNWARPNewFunctionReference +_BNWARPNewFunctionReference.restype = ctypes.POINTER(BNWARPFunction) +_BNWARPNewFunctionReference.argtypes = [ + ctypes.POINTER(BNWARPFunction), + ] + + +# noinspection PyPep8Naming +def BNWARPNewFunctionReference( + function: ctypes.POINTER(BNWARPFunction) + ) -> Optional[ctypes.POINTER(BNWARPFunction)]: + result = _BNWARPNewFunctionReference(function) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPNewTargetReference + +_BNWARPNewTargetReference = core.BNWARPNewTargetReference +_BNWARPNewTargetReference.restype = ctypes.POINTER(BNWARPTarget) +_BNWARPNewTargetReference.argtypes = [ + ctypes.POINTER(BNWARPTarget), + ] + + +# noinspection PyPep8Naming +def BNWARPNewTargetReference( + target: ctypes.POINTER(BNWARPTarget) + ) -> Optional[ctypes.POINTER(BNWARPTarget)]: + result = _BNWARPNewTargetReference(target) + if not result: + return None + return result + + +# ------------------------------------------------------- +# _BNWARPRunMatcher + +_BNWARPRunMatcher = core.BNWARPRunMatcher +_BNWARPRunMatcher.restype = None +_BNWARPRunMatcher.argtypes = [ + ctypes.POINTER(BNBinaryView), + ] + + +# noinspection PyPep8Naming +def BNWARPRunMatcher( + view: ctypes.POINTER(BNBinaryView) + ) -> None: + return _BNWARPRunMatcher(view) + + +# ------------------------------------------------------- +# _BNWARPUUIDEqual + +_BNWARPUUIDEqual = core.BNWARPUUIDEqual +_BNWARPUUIDEqual.restype = ctypes.c_bool +_BNWARPUUIDEqual.argtypes = [ + ctypes.POINTER(BNWARPUUID), + ctypes.POINTER(BNWARPUUID), + ] + + +# noinspection PyPep8Naming +def BNWARPUUIDEqual( + a: ctypes.POINTER(BNWARPUUID), + b: ctypes.POINTER(BNWARPUUID) + ) -> bool: + return _BNWARPUUIDEqual(a, b) + + +# ------------------------------------------------------- +# _BNWARPUUIDGetString + +_BNWARPUUIDGetString = core.BNWARPUUIDGetString +_BNWARPUUIDGetString.restype = ctypes.POINTER(ctypes.c_byte) +_BNWARPUUIDGetString.argtypes = [ + ctypes.POINTER(BNWARPUUID), + ] + + +# noinspection PyPep8Naming +def BNWARPUUIDGetString( + uuid: ctypes.POINTER(BNWARPUUID) + ) -> Optional[Optional[str]]: + result = _BNWARPUUIDGetString(uuid) + if not result: + return None + string = str(pyNativeStr(ctypes.cast(result, ctypes.c_char_p).value)) + BNFreeString(result) + return string + + + +# 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/plugins/warp/api/python/_warpcore_template.py b/plugins/warp/api/python/_warpcore_template.py new file mode 100644 index 00000000..fd923450 --- /dev/null +++ b/plugins/warp/api/python/_warpcore_template.py @@ -0,0 +1,58 @@ +import binaryninja +import ctypes, os + +from typing import Optional +from . import warp_enums +# Load core module +import platform +core = None +core_platform = platform.system() + +from binaryninja import Settings +if Settings().get_bool("corePlugins.warp"): + from binaryninja._binaryninjacore import BNGetBundledPluginDirectory + if core_platform == "Darwin": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.dylib")) + + elif core_platform == "Linux": + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.so")) + + elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0): + _base_path = BNGetBundledPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "warp_ninja.dll")) + else: + raise Exception("OS not supported") +else: + from binaryninja._binaryninjacore import BNGetUserPluginDirectory + if core_platform == "Darwin": + _base_path = BNGetUserPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.dylib")) + + elif core_platform == "Linux": + _base_path = BNGetUserPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "libwarp_ninja.so")) + + elif (core_platform == "Windows") or (core_platform.find("CYGWIN_NT") == 0): + _base_path = BNGetUserPluginDirectory() + core = ctypes.CDLL(os.path.join(_base_path, "warp_ninja.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/plugins/warp/api/python/generator.cpp b/plugins/warp/api/python/generator.cpp new file mode 100644 index 00000000..f40fc01a --- /dev/null +++ b/plugins/warp/api/python/generator.cpp @@ -0,0 +1,652 @@ +/* +Copyright 2020-2025 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().GetValue(), true, true); + for (auto& i : type->GetChildType()->GetParameters()) + { + fprintf(out, ", "); + OutputType(out, i.type.GetValue()); + } + fprintf(out, ")"); + break; + } + fprintf(out, "ctypes.POINTER("); + OutputType(out, type->GetChildType().GetValue()); + fprintf(out, ")"); + break; + case ArrayTypeClass: + OutputType(out, type->GetChildType().GetValue()); + 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().GetValue(), true, true); + for (auto& i : type->GetChildType()->GetParameters()) + { + fprintf(out, ", "); + OutputType(out, i.type.GetValue()); + } + fprintf(out, ")"); + break; + } + fprintf(out, "ctypes.POINTER("); + OutputType(out, type->GetChildType().GetValue()); + fprintf(out, ")"); + break; + case ArrayTypeClass: + OutputType(out, type->GetChildType().GetValue()); + 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, "from binaryninja._binaryninjacore import BNFreeString\n"); + 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 (name == "BNArchitecture") + { + fprintf(out, "from binaryninja._binaryninjacore import BNArchitecture, BNArchitectureHandle\n"); + continue; + } + if (name == "BNBasicBlock") + { + fprintf(out, "from binaryninja._binaryninjacore import BNBasicBlock, BNBasicBlockHandle\n"); + continue; + } + if (name == "BNFunction") + { + fprintf(out, "from binaryninja._binaryninjacore import BNFunction, BNFunctionHandle\n"); + continue; + } + if (name == "BNLowLevelILFunction") + { + fprintf(out, "from binaryninja._binaryninjacore import BNLowLevelILFunction, BNLowLevelILFunctionHandle\n"); + continue; + } + if (name == "BNPlatform") + { + fprintf(out, "from binaryninja._binaryninjacore import BNPlatform, BNPlatformHandle\n"); + continue; + } + if (name == "BNSymbol") + { + fprintf(out, "from binaryninja._binaryninjacore import BNSymbol, BNSymbolHandle\n"); + continue; + } + if (name == "BNType") + { + fprintf(out, "from binaryninja._binaryninjacore import BNType, BNTypeHandle\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) && + (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.GetValue()); + 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().GetValue(), 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.GetValue(), true); + } + else + { + OutputType(out, j.type.GetValue()); + } + 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.GetValue()); + else + OutputType(out, arg.type.GetValue()); + argN ++; + } + } + fprintf(out, "\n\t\t) -> "); + if (swizzleArgs) + { + if (stringResult || pointerResult) + fprintf(out, "Optional["); + OutputSwizzledType(out, i.second->GetChildType().GetValue()); + if (stringResult || pointerResult) + fprintf(out, "]"); + } + else + { + OutputType(out, i.second->GetChildType().GetValue()); + } + 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/plugins/warp/api/python/warp.py b/plugins/warp/api/python/warp.py new file mode 100644 index 00000000..e0500215 --- /dev/null +++ b/plugins/warp/api/python/warp.py @@ -0,0 +1,384 @@ +import ctypes +import dataclasses +import uuid +from typing import List, Optional, Union + +import binaryninja +from binaryninja import BinaryView, Function, BasicBlock, Architecture, Platform, Type, Symbol, LowLevelILInstruction, LowLevelILFunction +from binaryninja._binaryninjacore import BNFreeString, BNAllocString, BNType + +from . import _warpcore as warpcore + + +class WarpUUID: + def __init__(self, _uuid: Union[warpcore.BNWARPUUID, str, uuid.UUID]): + if isinstance(_uuid, str): + _uuid = uuid.UUID(_uuid) + if isinstance(_uuid, uuid.UUID): + uuid_bytes = _uuid.bytes + _uuid = warpcore.BNWARPUUID() + _uuid.uuid = (ctypes.c_ubyte * 16).from_buffer_copy(uuid_bytes) + elif isinstance(_uuid, warpcore.BNWARPUUID): + # We must create a copy! + new_uuid = warpcore.BNWARPUUID() + new_uuid.uuid = (ctypes.c_ubyte * 16).from_buffer_copy(_uuid.uuid) + _uuid = new_uuid + self._uuid = _uuid + + def to_string(self) -> str: + return warpcore.BNWARPUUIDGetString(self._uuid) + + def __str__(self): + return self.to_string() + + def __repr__(self): + return f"<WarpUUID '{str(self)}'>" + + def __hash__(self): + # Hash based on the UUID bytes + return hash(bytes(self._uuid.uuid)) + + def __eq__(self, other): + if not isinstance(other, WarpUUID): + return False + return warpcore.BNWARPUUIDEqual(self._uuid, other._uuid) + + @property + def uuid(self): + return self._uuid + + +class Source(WarpUUID): + def __repr__(self): + return f"<Source '{str(self)}'>" + + +class BasicBlockGUID(WarpUUID): + def __repr__(self): + return f"<BasicBlockGUID '{str(self)}'>" + + +class FunctionGUID(WarpUUID): + def __repr__(self): + return f"<FunctionGUID '{str(self)}'>" + + +class ConstraintGUID(WarpUUID): + def __repr__(self): + return f"<ConstraintGUID '{str(self)}'>" + + +class TypeGUID(WarpUUID): + def __repr__(self): + return f"<TypeGUID '{str(self)}'>" + + +@dataclasses.dataclass +class WarpFunctionComment: + text: str + offset: int + + def __str__(self): + return repr(self) + + def __repr__(self): + return f"<WarpFunctionComment '{self.text}': {self.offset:#x}>" + + @staticmethod + def from_api(comment: warpcore.BNWARPFunctionComment) -> 'WarpFunctionComment': + return WarpFunctionComment( + text=comment.text.decode('utf-8'), + offset=comment.offset + ) + +@dataclasses.dataclass +class WarpConstraint: + guid: ConstraintGUID + offset: Optional[int] + + def __str__(self): + return repr(self) + + def __repr__(self): + if self.offset is None: + return f"<WarpConstraint '{self.guid}'>" + return f"<WarpConstraint '{self.guid}': {self.offset:#x}>" + + @staticmethod + def from_api(constraint: warpcore.BNWARPConstraint) -> 'WarpConstraint': + if constraint.offset == -1: + return WarpConstraint(guid=ConstraintGUID(constraint.guid), offset=None) + return WarpConstraint(guid=ConstraintGUID(constraint.guid), offset=constraint.offset) + +class WarpTarget: + def __init__(self, handle: Union[warpcore.BNWARPTarget, Platform]): + if isinstance(handle, Platform): + self.handle = warpcore.BNWARPGetTarget(handle.handle) + else: + self.handle = handle + + def __del__(self): + if self.handle is not None: + warpcore.BNWARPFreeTargetReference(self.handle) + + @staticmethod + def from_platform(platform: Platform) -> Optional['WarpTarget']: + handle = warpcore.BNWARPGetTarget(platform.handle) + if not handle: + return None + return WarpTarget(handle) + + +class WarpFunction: + def __init__(self, handle: Union[warpcore.BNWARPFunction, Function]): + if isinstance(handle, Function): + self.handle = warpcore.BNWARPGetFunction(handle.handle) + else: + self.handle = handle + def __del__(self): + if self.handle is not None: + warpcore.BNWARPFreeFunctionReference(self.handle) + + def __repr__(self): + return f"<WarpFunction '{self.name}': '{self.guid}'>" + + @property + def guid(self) -> FunctionGUID: + return FunctionGUID(warpcore.BNWARPFunctionGetGUID(self.handle)) + + @property + def name(self) -> str: + return warpcore.BNWARPFunctionGetSymbolName(self.handle) + + def get_symbol(self, function: Function) -> Symbol: + symbol_handle = warpcore.BNWARPFunctionGetSymbol(self.handle, function.handle) + return Symbol(symbol_handle) + + def get_type(self, function: Function) -> Optional[Type]: + type_handle = warpcore.BNWARPFunctionGetType(self.handle, function.handle) + if not type_handle: + return None + return Type(type_handle) + + @property + def constraints(self) -> List[WarpConstraint]: + count = ctypes.c_size_t() + constraints = warpcore.BNWARPFunctionGetConstraints(self.handle, count) + if not constraints: + return [] + result = [] + for i in range(count.value): + result.append(WarpConstraint.from_api(constraints[i])) + warpcore.BNWARPFreeConstraintList(constraints, count.value) + return result + + @property + def comments(self) -> List[WarpFunctionComment]: + count = ctypes.c_size_t() + comments = warpcore.BNWARPFunctionGetComments(self.handle, count) + if not comments: + return [] + result = [] + for i in range(count.value): + result.append(WarpFunctionComment.from_api(comments[i])) + warpcore.BNWARPFreeFunctionCommentList(comments, count.value) + return result + + @staticmethod + def get_matched(function: Function) -> Optional['WarpFunction']: + handle = warpcore.BNWARPGetMatchedFunction(function.handle) + if not handle: + return None + return WarpFunction(handle) + + def apply(self, function: Function): + warpcore.BNWARPFunctionApply(self.handle, function.handle) + + +class _WarpContainerMetaclass(type): + def __iter__(self): + binaryninja._init_plugins() + count = ctypes.c_ulonglong() + containers = warpcore.BNWARPGetContainers(count) + try: + for i in range(0, count.value): + yield WarpContainer(warpcore.BNWARPNewContainerReference(containers[i])) + finally: + warpcore.BNWARPFreeContainerList(containers, count.value) + + def __getitem__(self, value): + binaryninja._init_plugins() + count = ctypes.c_ulonglong() + containers = warpcore.BNWARPGetContainers(count) + try: + for i in range(0, count.value): + container = WarpContainer(warpcore.BNWARPNewContainerReference(containers[i])) + if container.name == str(value): + return container + raise KeyError(f"'{value}' is not a valid container name") + finally: + warpcore.BNWARPFreeContainerList(containers, count.value) + + +class WarpContainer(metaclass=_WarpContainerMetaclass): + def __init__(self, handle: warpcore.BNWARPContainer): + self.handle = handle + + def __del__(self): + if self.handle is not None: + warpcore.BNWARPFreeContainerReference(self.handle) + + def __repr__(self): + return f"<WarpContainer '{self.name}'>" + + @staticmethod + def all() -> List['WarpContainer']: + count = ctypes.c_size_t() + containers = warpcore.BNWARPGetContainers(count) + if not containers: + return [] + result = [] + for i in range(count.value): + result.append(WarpContainer(warpcore.BNWARPNewContainerReference(containers[i]))) + warpcore.BNWARPFreeContainerList(containers, count.value) + return result + + @property + def name(self) -> str: + return warpcore.BNWARPContainerGetName(self.handle) + + @property + def sources(self) -> List[Source]: + count = ctypes.c_size_t() + sources = warpcore.BNWARPContainerGetSources(self.handle, count) + if not sources: + return [] + result = [] + for i in range(count.value): + result.append(Source(sources[i])) + warpcore.BNWARPFreeUUIDList(sources, count.value) + return result + + def add_source(self, source_path: str) -> Optional[Source]: + source = warpcore.BNWARPUUID() + if not warpcore.BNWARPContainerAddSource(self.handle, source_path, source): + return None + return Source(source) + + def commit_source(self, source: Source) -> bool: + return warpcore.BNWARPContainerCommitSource(self.handle, source.uuid) + + def is_source_uncommitted(self, source: Source) -> bool: + return warpcore.BNWARPContainerIsSourceWritable(self.handle, source.uuid) + + def is_source_writable(self, source: Source) -> bool: + return warpcore.BNWARPContainerIsSourceWritable(self.handle, source.uuid) + + def get_source_path(self, source: Source) -> Optional[str]: + return warpcore.BNWARPContainerGetSourcePath(self.handle, source.uuid) + + def add_functions(self, target: WarpTarget, source: Source, functions: List[Function]) -> bool: + count = len(functions) + core_funcs = (ctypes.POINTER(warpcore.BNWARPFunction) * count)() + for i in range(count): + core_funcs[i] = functions[i].handle + return warpcore.BNWARPContainerAddFunctions(self.handle, target.handle, source.uuid, core_funcs, count) + + def add_types(self, view: BinaryView, source: Source, types: List[Type]) -> bool: + count = len(types) + core_types = (ctypes.POINTER(BNType) * count)() + for i in range(count): + core_types[i] = types[i].handle + return warpcore.BNWARPContainerAddTypes(view.handle, self.handle, source.uuid, core_types, count) + + def remove_functions(self, target: WarpTarget, source: Source, functions: List[Function]) -> bool: + count = len(functions) + core_funcs = (ctypes.POINTER(warpcore.BNWARPFunction) * count)() + for i in range(count): + core_funcs[i] = functions[i].handle + return warpcore.BNWARPContainerRemoveFunctions(self.handle, target.handle, source.uuid, core_funcs, count) + + def remove_types(self, source: Source, guids: List[TypeGUID]) -> bool: + count = len(guids) + core_guids = (ctypes.POINTER(warpcore.BNWARPTypeGUID) * count)() + for i in range(count): + core_guids[i] = guids[i].uuid + return warpcore.BNWARPContainerRemoveTypes(self.handle, source.uuid, core_guids, count) + + def get_sources_with_function_guid(self, target: WarpTarget, guid: FunctionGUID) -> List[Source]: + count = ctypes.c_size_t() + sources = warpcore.BNWARPContainerGetSourcesWithFunctionGUID(self.handle, target.handle, guid.uuid, count) + if not sources: + return [] + result = [] + for i in range(count.value): + result.append(Source(sources[i])) + warpcore.BNWARPFreeUUIDList(sources, count.value) + return result + + def get_sources_with_type_guid(self, guid: TypeGUID) -> List[Source]: + count = ctypes.c_size_t() + sources = warpcore.BNWARPContainerGetSourcesWithTypeGUID(self.handle, guid.uuid, count) + if not sources: + return [] + result = [] + for i in range(count.value): + result.append(Source(sources[i])) + warpcore.BNWARPFreeUUIDList(sources, count.value) + return result + + def get_functions_with_guid(self, target: WarpTarget, source: Source, guid: FunctionGUID) -> List[Function]: + count = ctypes.c_size_t() + funcs = warpcore.BNWARPContainerGetFunctionsWithGUID(self.handle, target.handle, source.uuid, guid.uuid, count) + if not funcs: + return [] + result = [] + for i in range(count.value): + result.append(WarpFunction(warpcore.BNWARPNewFunctionReference(funcs[i]))) + warpcore.BNWARPFreeFunctionList(funcs, count.value) + return result + + def get_type_with_guid(self, arch: Architecture, source: Source, guid: TypeGUID) -> Optional[Type]: + ty = warpcore.BNWARPContainerGetTypeWithGUID(self.handle, arch.handle, source.uuid, guid.uuid) + if not ty: + return None + return Type(ty) + + def get_type_guids_with_name(self, source: Source, name: str) -> List[TypeGUID]: + count = ctypes.c_size_t() + guids = warpcore.BNWARPContainerGetTypeGUIDsWithName(self.handle, source.uuid, name, count) + if not guids: + return [] + result = [] + for i in range(count.value): + result.append(TypeGUID(guids[i])) + warpcore.BNWARPFreeUUIDList(guids, count.value) + return result + + +def run_matcher(view: BinaryView): + warpcore.BNWARPRunMatcher(view.handle) + +def is_instruction_variant(function: LowLevelILFunction, variant: LowLevelILInstruction) -> bool: + return warpcore.BNWARPIsLiftedInstructionVariant(function.handle, variant.instr_index) + +def is_instruction_blacklisted(function: LowLevelILFunction, variant: LowLevelILInstruction) -> bool: + return warpcore.BNWARPIsLiftedInstructionBlacklisted(function.handle, variant.instr_index) + +def get_function_guid(function: Function) -> Optional[FunctionGUID]: + guid = warpcore.BNWARPUUID() + if not warpcore.BNWARPGetAnalysisFunctionGUID(function.handle, guid): + return None + return FunctionGUID(guid) + + +def get_basic_block_guid(basic_block: BasicBlock) -> Optional[BasicBlockGUID]: + # TODO: I believe this won't work for HLIL: https://github.com/Vector35/binaryninja-api/issues/6998 + if basic_block.is_il: + basic_block = basic_block.source_block + guid = warpcore.BNWARPUUID() + if not warpcore.BNWARPGetBasicBlockGUID(basic_block.handle, guid): + return None + return BasicBlockGUID(guid) + +# TODO: Magic matched_function, possible_functions
\ No newline at end of file diff --git a/plugins/warp/api/python/warp_enums.py b/plugins/warp/api/python/warp_enums.py new file mode 100644 index 00000000..e47ff1c3 --- /dev/null +++ b/plugins/warp/api/python/warp_enums.py @@ -0,0 +1 @@ +import enum diff --git a/plugins/warp/api/warp.cpp b/plugins/warp/api/warp.cpp new file mode 100644 index 00000000..7d91c6e2 --- /dev/null +++ b/plugins/warp/api/warp.cpp @@ -0,0 +1,336 @@ +#include "warpcore.h" +#include "warp.h" + +#include <utility> + +using namespace Warp; + +std::string WarpUUID::ToString() const +{ + char *str = BNWARPUUIDGetString(&uuid); + std::string result = str; + BNFreeString(str); + return result; +} + +Target::Target(BNWARPTarget *target) +{ + m_object = target; +} + +Ref<Target> Target::FromPlatform(const BinaryNinja::Platform &platform) +{ + BNWARPTarget *result = BNWARPGetTarget(platform.m_object); + if (!result) + return nullptr; + return new Target(result); +} + +Constraint::Constraint(ConstraintGUID guid, std::optional<int64_t> offset) +{ + this->guid = guid; + this->offset = offset; +} + +Constraint Constraint::FromAPIObject(BNWARPConstraint *constraint) +{ + auto offset = constraint->offset == INT64_MAX ? std::nullopt : std::optional(constraint->offset); + return {constraint->guid, offset}; +} + +FunctionComment::FunctionComment(std::string text, int64_t offset) +{ + this->text = std::move(text); + this->offset = offset; +} + +FunctionComment FunctionComment::FromAPIObject(BNWARPFunctionComment *comment) +{ + return {comment->text, comment->offset}; +} + +Function::Function(BNWARPFunction *function) +{ + m_object = function; +} + +FunctionGUID Function::GetGUID() const +{ + return BNWARPFunctionGetGUID(m_object); +} + +std::string Function::GetSymbolName() const +{ + char *name = BNWARPFunctionGetSymbolName(m_object); + std::string result = name; + BNFreeString(name); + return result; +} + +BinaryNinja::Ref<BinaryNinja::Symbol> Function::GetSymbol(const BinaryNinja::Function &function) const +{ + BNSymbol *symbol = BNWARPFunctionGetSymbol(m_object, function.m_object); + if (!symbol) + return nullptr; + return new BinaryNinja::Symbol(symbol); +} + +BinaryNinja::Ref<BinaryNinja::Type> Function::GetType(const BinaryNinja::Function &function) const +{ + BNType *type = BNWARPFunctionGetType(m_object, function.m_object); + if (!type) + return nullptr; + return new BinaryNinja::Type(type); +} + +std::vector<Constraint> Function::GetConstraints() const +{ + size_t count; + BNWARPConstraint *constraints = BNWARPFunctionGetConstraints(m_object, &count); + std::vector<Constraint> result; + result.reserve(count); + for (int i = 0; i < count; i++) + result.push_back(Constraint::FromAPIObject(&constraints[i])); + BNWARPFreeConstraintList(constraints, count); + return result; +} + +std::vector<FunctionComment> Function::GetComments() const +{ + size_t count; + BNWARPFunctionComment *comments = BNWARPFunctionGetComments(m_object, &count); + std::vector<FunctionComment> result; + result.reserve(count); + for (int i = 0; i < count; i++) + result.push_back(FunctionComment::FromAPIObject(&comments[i])); + BNWARPFreeFunctionCommentList(comments, count); + return result; +} + +Ref<Function> Function::Get(const BinaryNinja::Function &function) +{ + BNWARPFunction *result = BNWARPGetFunction(function.m_object); + if (!result) + return nullptr; + return new Function(result); +} + +Ref<Function> Function::GetMatched(const BinaryNinja::Function &function) +{ + BNWARPFunction *result = BNWARPGetMatchedFunction(function.m_object); + if (!result) + return nullptr; + return new Function(result); +} + +void Function::Apply(const BinaryNinja::Function &function) const +{ + BNWARPFunctionApply(m_object, function.m_object); +} + +void Function::RemoveMatch(const BinaryNinja::Function &function) +{ + BNWARPFunctionApply(nullptr, function.m_object); +} + +Container::Container(BNWARPContainer *container) +{ + m_object = container; +} + +std::vector<Ref<Container> > Container::All() +{ + size_t count; + BNWARPContainer **containers = BNWARPGetContainers(&count); + std::vector<Ref<Container> > result; + result.reserve(count); + for (int i = 0; i < count; i++) + result.push_back(new Container(BNWARPNewContainerReference(containers[i]))); + BNWARPFreeContainerList(containers, count); + return result; +} + +std::string Container::GetName() const +{ + char *rawName = BNWARPContainerGetName(m_object); + std::string name = rawName; + BNFreeString(rawName); + return name; +} + +std::vector<Source> Container::GetSources() const +{ + size_t count; + BNWARPSource *sources = BNWARPContainerGetSources(m_object, &count); + std::vector<Source> result; + result.reserve(count); + for (int i = 0; i < count; i++) + result.emplace_back(sources[i]); + BNWARPFreeUUIDList(sources, count); + return result; +} + +std::optional<Source> Container::AddSource(const std::string &sourcePath) const +{ + Source source; + if (!BNWARPContainerAddSource(m_object, sourcePath.c_str(), source.RawMut())) + return std::nullopt; + return source; +} + +bool Container::CommitSource(const Source &source) const +{ + return BNWARPContainerCommitSource(m_object, source.Raw()); +} + +bool Container::IsSourceUncommitted(const Source &source) const +{ + return BNWARPContainerIsSourceUncommitted(m_object, source.Raw()); +} + +bool Container::IsSourceWritable(const Source &source) const +{ + return BNWARPContainerIsSourceWritable(m_object, source.Raw()); +} + +std::optional<std::string> Container::SourcePath(const Source &source) const +{ + char *rawPath = BNWARPContainerGetSourcePath(m_object, source.Raw()); + if (!rawPath) + return std::nullopt; + std::string path = rawPath; + BNFreeString(rawPath); + return path; +} + +bool Container::AddFunctions(const Target &target, const Source &source, const std::vector<Ref<Function> > &functions) const +{ + size_t count = functions.size(); + BNWARPFunction **apiFunctions = new BNWARPFunction *[count]; + for (size_t i = 0; i < count; i++) + apiFunctions[i] = functions[i]->m_object; + const bool result = BNWARPContainerAddFunctions(m_object, target.m_object, source.Raw(), apiFunctions, count); + delete[] apiFunctions; + return result; +} + +bool Container::AddTypes(const BinaryNinja::BinaryView &view, const Source &source, + const std::vector<BinaryNinja::Ref<BinaryNinja::Type> > &types) const +{ + size_t count = types.size(); + BNType **apiTypes = new BNType *[count]; + for (size_t i = 0; i < count; i++) + apiTypes[i] = types[i]->m_object; + const bool result = BNWARPContainerAddTypes(view.m_object, m_object, source.Raw(), apiTypes, count); + delete[] apiTypes; + return result; +} + +bool Container::RemoveFunctions(const Target &target, const Source &source, + const std::vector<Ref<Function>> &functions) const +{ + size_t count = functions.size(); + BNWARPFunction **apiFunctions = new BNWARPFunction *[count]; + for (size_t i = 0; i < count; i++) + apiFunctions[i] = functions[i]->m_object; + const bool result = BNWARPContainerRemoveFunctions(m_object, target.m_object, source.Raw(), apiFunctions, count); + delete[] apiFunctions; + return result; +} + +bool Container::RemoveTypes(const Source &source, const std::vector<TypeGUID> &guids) const +{ + size_t count = guids.size(); + BNWARPTypeGUID* apiGuids = new BNWARPTypeGUID[count]; + for (size_t i = 0; i < count; i++) + apiGuids[i] = *guids[i].Raw(); + const bool result = BNWARPContainerRemoveTypes(m_object, source.Raw(), apiGuids, count); + delete[] apiGuids; + return result; +} + +std::vector<Source> Container::GetSourcesWithFunctionGUID(const Target& target, const FunctionGUID &guid) const +{ + size_t count; + BNWARPSource *sources = BNWARPContainerGetSourcesWithFunctionGUID(m_object, target.m_object, guid.Raw(), &count); + std::vector<Source> result; + result.reserve(count); + for (int i = 0; i < count; i++) + result.emplace_back(sources[i]); + BNWARPFreeUUIDList(sources, count); + return result; +} + +std::vector<Source> Container::GetSourcesWithTypeGUID(const TypeGUID &guid) const +{ + size_t count; + BNWARPSource *sources = BNWARPContainerGetSourcesWithTypeGUID(m_object, guid.Raw(), &count); + std::vector<Source> result; + result.reserve(count); + for (int i = 0; i < count; i++) + result.emplace_back(sources[i]); + BNWARPFreeUUIDList(sources, count); + return result; +} + +std::vector<Ref<Function> > Container::GetFunctionsWithGUID(const Target& target, const Source &source, const FunctionGUID &guid) const +{ + size_t count; + BNWARPFunction **functions = BNWARPContainerGetFunctionsWithGUID(m_object, target.m_object, source.Raw(), guid.Raw(), &count); + std::vector<Ref<Function> > result; + result.reserve(count); + for (int i = 0; i < count; i++) + result.push_back(new Function(BNWARPNewFunctionReference(functions[i]))); + BNWARPFreeFunctionList(functions, count); + return result; +} + +BinaryNinja::Ref<BinaryNinja::Type> Container::GetTypeWithGUID(const BinaryNinja::Architecture &arch, + const Source &source, const TypeGUID &guid) const +{ + BNType *type = BNWARPContainerGetTypeWithGUID(arch.m_object, m_object, source.Raw(), guid.Raw()); + return new BinaryNinja::Type(type); +} + +std::vector<TypeGUID> Container::GetTypeGUIDsWithName(const Source &source, const std::string &name) const +{ + size_t count; + BNWARPTypeGUID *guids = BNWARPContainerGetTypeGUIDsWithName(m_object, source.Raw(), name.c_str(), &count); + std::vector<TypeGUID> result; + result.reserve(count); + for (int i = 0; i < count; i++) + result.emplace_back(guids[i]); + BNWARPFreeUUIDList(guids, count); + return result; +} + +void Warp::RunMatcher(const BinaryNinja::BinaryView &view) +{ + BNWARPRunMatcher(view.m_object); +} + +bool IsInstructionVariant(const BinaryNinja::LowLevelILFunction &function, BinaryNinja::ExprId idx) +{ + return BNWARPIsLiftedInstructionVariant(function.m_object, idx); +} + +bool IsInstructionBlacklisted(const BinaryNinja::LowLevelILFunction &function, BinaryNinja::ExprId idx) +{ + return BNWARPIsLiftedInstructionBlacklisted(function.m_object, idx); +} + +std::optional<FunctionGUID> Warp::GetAnalysisFunctionGUID(const BinaryNinja::Function &function) +{ + FunctionGUID guid; + if (!BNWARPGetAnalysisFunctionGUID(function.m_object, guid.RawMut())) + return std::nullopt; + return guid; +} + +std::optional<BasicBlockGUID> Warp::GetBasicBlockGUID(const BinaryNinja::BasicBlock &basicBlock) +{ + BasicBlockGUID guid; + if (!BNWARPGetBasicBlockGUID(basicBlock.m_object, guid.RawMut())) + return std::nullopt; + return guid; +} diff --git a/plugins/warp/api/warp.h b/plugins/warp/api/warp.h new file mode 100644 index 00000000..b57a0c7b --- /dev/null +++ b/plugins/warp/api/warp.h @@ -0,0 +1,378 @@ +#pragma once + +#include <binaryninjaapi.h> +#include "warpcore.h" + +template<class T, T *(*AddObjectReference)(T *), void (*FreeObjectReference)(T *)> +class WarpRefCountObject +{ + 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; + + WarpRefCountObject() : m_refs(0), m_object(nullptr) + { + } + + virtual ~WarpRefCountObject() = default; + + T *GetObject() const { return m_object; } + + static T *GetObject(WarpRefCountObject *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; + } +}; + +namespace Warp { + template<class T> + class Ref + { + T *m_obj; +#ifdef BN_REF_COUNT_DEBUG + void* m_assignmentTrace = nullptr; +#endif + + public: + Ref() : m_obj(NULL) + { + } + + Ref(T *obj) : m_obj(obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + } + } + + Ref(const Ref &obj) : m_obj(obj.m_obj) + { + if (m_obj) + { + m_obj->AddRef(); +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + } + } + + Ref(Ref &&other) : m_obj(other.m_obj) + { + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + } + + ~Ref() + { + if (m_obj) + { + m_obj->Release(); +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif + } + } + + Ref<T> &operator=(const Ref<T> &obj) + { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj.m_obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T *oldObj = m_obj; + m_obj = obj.m_obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } + + Ref<T> &operator=(Ref<T> &&other) + { + if (m_obj) + { +#ifdef BN_REF_COUNT_DEBUG + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); +#endif + m_obj->Release(); + } + m_obj = other.m_obj; + other.m_obj = 0; +#ifdef BN_REF_COUNT_DEBUG + m_assignmentTrace = other.m_assignmentTrace; +#endif + return *this; + } + + Ref<T> &operator=(T *obj) + { +#ifdef BN_REF_COUNT_DEBUG + if (m_obj) + BNUnregisterObjectRefDebugTrace(typeid(T).name(), m_assignmentTrace); + if (obj) + m_assignmentTrace = BNRegisterObjectRefDebugTrace(typeid(T).name()); +#endif + T *oldObj = m_obj; + m_obj = obj; + if (m_obj) + m_obj->AddRef(); + if (oldObj) + oldObj->Release(); + return *this; + } + + operator T *() const + { + return m_obj; + } + + T *operator->() const + { + return m_obj; + } + + T &operator*() const + { + return *m_obj; + } + + bool operator!() const + { + return m_obj == NULL; + } + + bool operator==(const T *obj) const + { + return T::GetObject(m_obj) == T::GetObject(obj); + } + + bool operator==(const Ref<T> &obj) const + { + return T::GetObject(m_obj) == T::GetObject(obj.m_obj); + } + + bool operator!=(const T *obj) const + { + return T::GetObject(m_obj) != T::GetObject(obj); + } + + bool operator!=(const Ref<T> &obj) const + { + return T::GetObject(m_obj) != T::GetObject(obj.m_obj); + } + + bool operator<(const T *obj) const + { + return T::GetObject(m_obj) < T::GetObject(obj); + } + + bool operator<(const Ref<T> &obj) const + { + return T::GetObject(m_obj) < T::GetObject(obj.m_obj); + } + + T *GetPtr() const + { + return m_obj; + } + }; + + class WarpUUID + { + BNWARPUUID uuid; + + public: + WarpUUID() = default; + + WarpUUID(BNWARPUUID uuid) : uuid(uuid) {} + + std::string ToString() const; + + bool operator==(const WarpUUID &other) const + { + return BNWARPUUIDEqual(&uuid, &other.uuid); + } + + bool operator!=(const WarpUUID &other) const + { + return !(*this == other); + } + + BNWARPUUID* RawMut() + { + return &uuid; + } + + const BNWARPUUID* Raw() const + { + return &uuid; + } + }; + + typedef WarpUUID Source; + typedef WarpUUID BasicBlockGUID; + typedef WarpUUID FunctionGUID; + typedef WarpUUID ConstraintGUID; + typedef WarpUUID TypeGUID; + + class Target : public WarpRefCountObject<BNWARPTarget, BNWARPNewTargetReference, + BNWARPFreeTargetReference> + { + public: + explicit Target(BNWARPTarget *target); + + static Ref<Target> FromPlatform(const BinaryNinja::Platform& platform); + }; + + struct Constraint + { + ConstraintGUID guid; + std::optional<int64_t> offset; + + Constraint(ConstraintGUID guid, std::optional<int64_t> offset); + + static Constraint FromAPIObject(BNWARPConstraint* constraint); + }; + + struct FunctionComment + { + std::string text; + int64_t offset; + + FunctionComment(std::string text, int64_t offset); + + static FunctionComment FromAPIObject(BNWARPFunctionComment* comment); + }; + + class Function : public WarpRefCountObject<BNWARPFunction, BNWARPNewFunctionReference, BNWARPFreeFunctionReference> + { + public: + explicit Function(BNWARPFunction *function); + + bool operator==(const Function &other) const + { + return BNWARPFunctionsEqual(m_object, other.m_object); + } + + FunctionGUID GetGUID() const; + + std::string GetSymbolName() const; + + BinaryNinja::Ref<BinaryNinja::Symbol> GetSymbol(const BinaryNinja::Function &function) const; + + BinaryNinja::Ref<BinaryNinja::Type> GetType(const BinaryNinja::Function &function) const; + + std::vector<Constraint> GetConstraints() const; + + std::vector<FunctionComment> GetComments() const; + + static Ref<Function> Get(const BinaryNinja::Function &function); + + static Ref<Function> GetMatched(const BinaryNinja::Function &function); + + void Apply(const BinaryNinja::Function &function) const; + + static void RemoveMatch(const BinaryNinja::Function &function); + }; + + class Container : public WarpRefCountObject<BNWARPContainer, BNWARPNewContainerReference, + BNWARPFreeContainerReference> + { + public: + explicit Container(BNWARPContainer *container); + + /// Retrieve all available containers. + static std::vector<Ref<Container> > All(); + + std::string GetName() const; + + std::vector<Source> GetSources() const; + + std::optional<Source> AddSource(const std::string &sourcePath) const; + + bool CommitSource(const Source &source) const; + + bool IsSourceUncommitted(const Source &source) const; + + bool IsSourceWritable(const Source &source) const; + + std::optional<std::string> SourcePath(const Source &source) const; + + bool AddFunctions(const Target &target, const Source &source, const std::vector<Ref<Function> > &functions) const; + + bool AddTypes(const BinaryNinja::BinaryView &view, const Source &source, + const std::vector<BinaryNinja::Ref<BinaryNinja::Type> > &types) const; + + bool RemoveFunctions(const Target &target, const Source &source, const std::vector<Ref<Function> > &functions) const; + + bool RemoveTypes(const Source &source, const std::vector<TypeGUID> &guids) const; + + std::vector<Source> GetSourcesWithFunctionGUID(const Target& target, const FunctionGUID &guid) const; + + std::vector<Source> GetSourcesWithTypeGUID(const TypeGUID &guid) const; + + std::vector<Ref<Function> > GetFunctionsWithGUID(const Target& target, const Source &source, const FunctionGUID &guid) const; + + BinaryNinja::Ref<BinaryNinja::Type> GetTypeWithGUID(const BinaryNinja::Architecture &arch, const Source &source, + const TypeGUID &guid) const; + + std::vector<TypeGUID> GetTypeGUIDsWithName(const Source &source, const std::string &name) const; + }; + + void RunMatcher(const BinaryNinja::BinaryView& view); + + bool IsInstructionVariant(const BinaryNinja::LowLevelILFunction &function, BinaryNinja::ExprId idx); + + bool IsInstructionBlacklisted(const BinaryNinja::LowLevelILFunction &function, BinaryNinja::ExprId idx); + + std::optional<FunctionGUID> GetAnalysisFunctionGUID(const BinaryNinja::Function &function); + + std::optional<BasicBlockGUID> GetBasicBlockGUID(const BinaryNinja::BasicBlock &basicBlock); +} diff --git a/plugins/warp/api/warpcore.h b/plugins/warp/api/warpcore.h new file mode 100644 index 00000000..d12c6b98 --- /dev/null +++ b/plugins/warp/api/warpcore.h @@ -0,0 +1,143 @@ +#pragma once + +#ifndef BN_TYPE_PARSER +#ifdef __cplusplus +#include <cstdint> +#include <cstddef> +#include <cstdlib> +#else +#include <stdbool.h> +#include <stdint.h> +#include <stddef.h> +#include <stdlib.h> +#endif +#endif + +#ifdef __GNUC__ + #ifdef WARP_LIBRARY + #define WARP_FFI_API __attribute__((visibility("default"))) + #else // WARP_LIBRARY + #define WARP_FFI_API + #endif // WARP_LIBRARY +#else // __GNUC__ + #ifdef _MSC_VER + #ifndef DEMO_VERSION + #ifdef WARP_LIBRARY + #define WARP_FFI_API __declspec(dllexport) + #else // WARP_LIBRARY + #define WARP_FFI_API __declspec(dllimport) + #endif // WARP_LIBRARY + #else + #define WARP_FFI_API + #endif + #else // _MSC_VER + #define WARP_FFI_API + #endif // _MSC_VER +#endif // __GNUC__C + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef struct BNArchitecture BNArchitecture; + typedef struct BNBinaryView BNBinaryView; + typedef struct BNPlatform BNPlatform; + typedef struct BNBasicBlock BNBasicBlock; + typedef struct BNLowLevelILFunction BNLowLevelILFunction; + typedef struct BNFunction BNFunction; + typedef struct BNSymbol BNSymbol; + typedef struct BNType BNType; + + struct BNWARPUUID + { + uint8_t uuid[16]; + }; + + struct BNWARPFunctionComment + { + char* text; + int64_t offset; + }; + + char* BNWARPUUIDGetString(const BNWARPUUID* uuid); + bool BNWARPUUIDEqual(const BNWARPUUID* a, const BNWARPUUID* b); + void BNWARPFreeUUIDList(BNWARPUUID* uuids, size_t count); + + typedef BNWARPUUID BNWARPSource; + typedef BNWARPUUID BNWARPBasicBlockGUID; + typedef BNWARPUUID BNWARPConstraintGUID; + typedef BNWARPUUID BNWARPFunctionGUID; + typedef BNWARPUUID BNWARPTypeGUID; + + typedef struct BNWARPTarget BNWARPTarget; + typedef struct BNWARPContainer BNWARPContainer; + typedef struct BNWARPFunction BNWARPFunction; + typedef struct BNWARPConstraint BNWARPConstraint; + + struct BNWARPConstraint + { + BNWARPConstraintGUID guid; + int64_t offset; + }; + + WARP_FFI_API void BNWARPRunMatcher(BNBinaryView* view); + + WARP_FFI_API bool BNWARPGetBasicBlockGUID(BNBasicBlock* basicBlock, BNWARPBasicBlockGUID* result); + WARP_FFI_API bool BNWARPGetAnalysisFunctionGUID(BNFunction* analysisFunction, BNWARPFunctionGUID* result); + WARP_FFI_API bool BNWARPIsLiftedInstructionVariant(BNLowLevelILFunction* liftedFunction, size_t idx); + WARP_FFI_API bool BNWARPIsLiftedInstructionBlacklisted(BNLowLevelILFunction* liftedFunction, size_t idx); + + WARP_FFI_API BNWARPFunction* BNWARPGetFunction(BNFunction* analysisFunction); + WARP_FFI_API BNWARPFunction* BNWARPGetMatchedFunction(BNFunction* analysisFunction); + WARP_FFI_API BNWARPContainer** BNWARPGetContainers(size_t* count); + + WARP_FFI_API char* BNWARPContainerGetName(BNWARPContainer* container); + + WARP_FFI_API BNWARPSource* BNWARPContainerGetSources(BNWARPContainer* container, size_t* count); + WARP_FFI_API bool BNWARPContainerAddSource(BNWARPContainer* container, const char* sourcePath, BNWARPSource* result); + WARP_FFI_API bool BNWARPContainerCommitSource(BNWARPContainer* container, const BNWARPSource* source); + WARP_FFI_API bool BNWARPContainerIsSourceUncommitted(BNWARPContainer* container, const BNWARPSource* source); + WARP_FFI_API bool BNWARPContainerIsSourceWritable(BNWARPContainer* container, const BNWARPSource* source); + WARP_FFI_API char* BNWARPContainerGetSourcePath(BNWARPContainer* container, const BNWARPSource* source); + + WARP_FFI_API bool BNWARPContainerAddFunctions(BNWARPContainer* container, const BNWARPTarget* target, const BNWARPSource* source, BNWARPFunction** functions, size_t count); + WARP_FFI_API bool BNWARPContainerAddTypes(BNBinaryView* view, BNWARPContainer* container, const BNWARPSource* source, BNType** types, size_t count); + + WARP_FFI_API bool BNWARPContainerRemoveFunctions(BNWARPContainer* container, const BNWARPTarget* target, const BNWARPSource* source, BNWARPFunction** functions, size_t count); + WARP_FFI_API bool BNWARPContainerRemoveTypes(BNWARPContainer* container, const BNWARPSource* source, BNWARPTypeGUID* types, size_t count); + + WARP_FFI_API BNWARPSource* BNWARPContainerGetSourcesWithFunctionGUID(BNWARPContainer* container, const BNWARPTarget* target, const BNWARPFunctionGUID* guid, size_t* count); + WARP_FFI_API BNWARPSource* BNWARPContainerGetSourcesWithTypeGUID(BNWARPContainer* container, const BNWARPTypeGUID* guid, size_t* count); + WARP_FFI_API BNWARPFunction** BNWARPContainerGetFunctionsWithGUID(BNWARPContainer* container, const BNWARPTarget* target, const BNWARPSource* source, const BNWARPFunctionGUID* guid, size_t* count); + WARP_FFI_API BNType* BNWARPContainerGetTypeWithGUID(BNArchitecture* arch, BNWARPContainer* container, const BNWARPSource* source, const BNWARPTypeGUID* guid); + WARP_FFI_API BNWARPTypeGUID* BNWARPContainerGetTypeGUIDsWithName(BNWARPContainer* container, const BNWARPSource* source, const char* name, size_t* count); + + WARP_FFI_API BNWARPContainer* BNWARPNewContainerReference(BNWARPContainer* container); + WARP_FFI_API void BNWARPFreeContainerReference(BNWARPContainer* container); + WARP_FFI_API void BNWARPFreeContainerList(BNWARPContainer** containers, size_t count); + + WARP_FFI_API void BNWARPFunctionApply(BNWARPFunction* function, BNFunction* analysisFunction); + WARP_FFI_API BNWARPFunctionGUID BNWARPFunctionGetGUID(BNWARPFunction* function); + WARP_FFI_API BNSymbol* BNWARPFunctionGetSymbol(BNWARPFunction* function, BNFunction* analysisFunction); + WARP_FFI_API char* BNWARPFunctionGetSymbolName(BNWARPFunction* function); + WARP_FFI_API BNType* BNWARPFunctionGetType(BNWARPFunction* function, BNFunction* analysisFunction); + WARP_FFI_API BNWARPConstraint* BNWARPFunctionGetConstraints(BNWARPFunction* function, size_t* count); + WARP_FFI_API BNWARPFunctionComment* BNWARPFunctionGetComments(BNWARPFunction* function, size_t* count); + WARP_FFI_API bool BNWARPFunctionsEqual(BNWARPFunction* functionA, BNWARPFunction* functionB); + + WARP_FFI_API void BNWARPFreeFunctionCommentList(BNWARPFunctionComment* comments, size_t count); + WARP_FFI_API void BNWARPFreeConstraintList(BNWARPConstraint* constraints, size_t count); + + WARP_FFI_API BNWARPFunction* BNWARPNewFunctionReference(BNWARPFunction* function); + WARP_FFI_API void BNWARPFreeFunctionReference(BNWARPFunction* function); + WARP_FFI_API void BNWARPFreeFunctionList(BNWARPFunction** functions, size_t count); + + WARP_FFI_API BNWARPTarget* BNWARPGetTarget(BNPlatform* platform); + + WARP_FFI_API BNWARPTarget* BNWARPNewTargetReference(BNWARPTarget* target); + WARP_FFI_API void BNWARPFreeTargetReference(BNWARPTarget* target); + +#ifdef __cplusplus +} +#endif diff --git a/plugins/warp/benches/convert.rs b/plugins/warp/benches/convert.rs index 7d7f40b3..e0acdc15 100644 --- a/plugins/warp/benches/convert.rs +++ b/plugins/warp/benches/convert.rs @@ -4,33 +4,36 @@ use criterion::{criterion_group, criterion_main, Criterion}; use std::path::PathBuf; use warp_ninja::convert::from_bn_type; +// These are the target files present in OUT_DIR +// Add the files to fixtures/bin +static TARGET_FILES: [&str; 1] = ["atox.obj"]; + pub fn type_conversion_benchmark(c: &mut Criterion) { let session = Session::new().expect("Failed to initialize session"); let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); - for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") { - let entry = entry.expect("Failed to read directory entry"); - let path = entry.path(); - if path.is_file() { - if let Some(bv) = session.load(path.to_str().unwrap()) { - let functions = bv.functions(); - c.bench_function("type conversion all functions", |b| { - b.iter(|| { - for func in &functions { - from_bn_type(&bv, &func.function_type(), u8::MAX); - } - }) - }); + for file_name in TARGET_FILES { + let path = out_dir.join(file_name); + let view = session.load(&path).expect("Failed to load view"); + + // Bench function types. + let functions: Vec<_> = view.functions().iter().map(|f| f.to_owned()).collect(); + c.bench_function("type conversion all functions", |b| { + b.iter(|| { + for func in &functions { + from_bn_type(&view, &func.function_type(), u8::MAX); + } + }) + }); - let types = bv.types(); - c.bench_function("type conversion all types", |b| { - b.iter(|| { - for ty in &types { - from_bn_type(&bv, &ty.ty, u8::MAX); - } - }) - }); - } - } + // Bench view types. + let types = view.types().to_vec(); + c.bench_function("type conversion all types", |b| { + b.iter(|| { + for ty in &types { + from_bn_type(&view, &ty.ty, u8::MAX); + } + }) + }); } } diff --git a/plugins/warp/benches/function.rs b/plugins/warp/benches/function.rs index 0ddf9ce2..dc5016be 100644 --- a/plugins/warp/benches/function.rs +++ b/plugins/warp/benches/function.rs @@ -2,43 +2,43 @@ use binaryninja::binary_view::BinaryViewExt; use binaryninja::headless::Session; use criterion::{criterion_group, criterion_main, Criterion}; use rayon::prelude::*; +use std::path::PathBuf; use warp_ninja::build_function; -use warp_ninja::cache::FunctionCache; + +// These are the target files present in OUT_DIR +// Add the files to fixtures/bin +static TARGET_FILES: [&str; 1] = ["atox.obj"]; pub fn function_benchmark(c: &mut Criterion) { let session = Session::new().expect("Failed to initialize session"); - let bv = session.load(env!("TEST_BIN_LIBRARY_OBJ")).unwrap(); - let functions = bv.functions(); - assert_eq!(functions.len(), 6); - let mut function_iter = functions.into_iter(); - let first_function = function_iter.next().unwrap(); - - c.bench_function("signature first function", |b| { - b.iter(|| { - let _ = build_function(&first_function, &first_function.low_level_il().unwrap()); - }) - }); + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + for file_name in TARGET_FILES { + let path = out_dir.join(file_name); + let view = session.load(&path).expect("Failed to load view"); - c.bench_function("signature all functions", |b| { - b.iter(|| { - for func in &functions { - let _ = build_function(&func, &func.low_level_il().unwrap()); - } - }) - }); + let functions: Vec<_> = view.functions().iter().map(|f| f.to_owned()).collect(); + // Bench all functions sequentially + c.bench_function("signature all functions", |b| { + b.iter(|| { + for func in &functions { + let _ = build_function(&func, &func.lifted_il().unwrap()); + } + }) + }); - let cache = FunctionCache::default(); - c.bench_function("signature all functions rayon", |b| { - b.iter(|| { - functions - .par_iter() - .map_with(cache.clone(), |par_cache, func| { - let llil = func.low_level_il().ok()?; - Some(par_cache.function(func.as_ref(), llil.as_ref())) - }) - .collect::<Vec<_>>() - }) - }); + // Bench all functions in parallel + c.bench_function("signature all functions rayon", |b| { + b.iter(|| { + functions + .par_iter() + .filter_map(|func| { + let llil = func.lifted_il().ok()?; + Some(build_function(func.as_ref(), llil.as_ref())) + }) + .collect::<Vec<_>>() + }) + }); + } } criterion_group!(benches, function_benchmark); diff --git a/plugins/warp/benches/guid.rs b/plugins/warp/benches/guid.rs index 046886b5..eddf2f06 100644 --- a/plugins/warp/benches/guid.rs +++ b/plugins/warp/benches/guid.rs @@ -1,22 +1,31 @@ use binaryninja::binary_view::BinaryViewExt; use binaryninja::headless::Session; use criterion::{criterion_group, criterion_main, Criterion}; +use std::path::PathBuf; use warp_ninja::function_guid; +// These are the target files present in OUT_DIR +// Add the files to fixtures/bin +static TARGET_FILES: [&str; 1] = ["atox.obj"]; + pub fn guid_benchmark(c: &mut Criterion) { let session = Session::new().expect("Failed to initialize session"); - let bv = session.load(env!("TEST_BIN_LIBRARY_OBJ")).unwrap(); - let functions = bv.functions(); - assert_eq!(functions.len(), 6); - let mut function_iter = functions.into_iter(); - let first_function = function_iter.next().unwrap(); + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + for file_name in TARGET_FILES { + let path = out_dir.join(file_name); + let view = session.load(&path).expect("Failed to load view"); - c.bench_function("function guid", |b| { - b.iter(|| { - let llil = first_function.low_level_il().unwrap(); - function_guid(&first_function, &llil); - }) - }); + let functions: Vec<_> = view.functions().iter().map(|f| f.to_owned()).collect(); + // Bench all functions sequentially + c.bench_function("guid all functions", |b| { + b.iter(|| { + for func in &functions { + let llil = func.lifted_il().unwrap(); + function_guid(&func, &llil); + } + }) + }); + } } criterion_group!(benches, guid_benchmark); diff --git a/plugins/warp/build.rs b/plugins/warp/build.rs index 39b3238b..53b31182 100644 --- a/plugins/warp/build.rs +++ b/plugins/warp/build.rs @@ -31,4 +31,8 @@ fn main() { } } } + + println!("cargo::rerun-if-changed=src/templates"); + // Templates used for rendering reports. + minijinja_embed::embed_templates!("src/templates"); } diff --git a/plugins/warp/fixtures/bin/random.warp b/plugins/warp/fixtures/bin/random.warp Binary files differnew file mode 100644 index 00000000..47c7367d --- /dev/null +++ b/plugins/warp/fixtures/bin/random.warp diff --git a/plugins/warp/src/bin/sigem.rs b/plugins/warp/src/bin/sigem.rs deleted file mode 100644 index a50b4ae2..00000000 --- a/plugins/warp/src/bin/sigem.rs +++ /dev/null @@ -1,269 +0,0 @@ -use std::collections::HashSet; -use std::fs::File; -use std::io::Read; -use std::path::{Path, PathBuf}; - -use ar::Archive; -use clap::{arg, Parser}; -use rayon::prelude::*; - -use binaryninja::binary_view::{BinaryView, BinaryViewExt}; -use binaryninja::function::Function as BNFunction; -use binaryninja::rc::Guard as BNGuard; -use binaryninja::settings::Settings; -use serde_json::{json, Value}; -use walkdir::WalkDir; -use warp::signature::Data; -use warp_ninja::cache::{cached_type_references, register_cache_destructor}; - -#[derive(Parser, Debug)] -#[command(about, long_about)] -/// A simple CLI utility to generate WARP signature files headlessly using Binary Ninja. -/// -/// NOTE: This requires a headless compatible Binary Ninja, make sure it's in your path. -struct Args { - /// Path to create signatures from, this can be: - /// - A binary (that can be opened with Binary Ninja) - /// - A directory (all files will be merged) - /// - An archive (with ext: a, lib, rlib) - /// - A BNDB - /// - A Signature file (sbin) - #[arg(index = 1, verbatim_doc_comment)] - path: PathBuf, - - /// The signature output file - /// - /// NOTE: If not specified the output will be the input path with the sbin extension - /// as an example `mylib.a` will output `mylib.sbin`. - #[arg(index = 2)] - output: Option<PathBuf>, - - /// Should we overwrite output file - /// - /// NOTE: If the file exists we will exit early to prevent wasted effort. - #[arg(short, long)] - overwrite: Option<bool>, - - /// The external debug information file to use - #[arg(short, long)] - debug_info: Option<PathBuf>, - // TODO: Add a file filter and default to filter out files starting with "." -} - -fn default_settings(bn_settings: &Settings) -> Value { - // TODO: Make these settings configurable through the CLI - let mut settings = json!({ - "analysis.linearSweep.autorun": false, - "analysis.signatureMatcher.autorun": false, - "analysis.mode": "full", - // The reason we need to do this is a little unfortunate. - // Basically some of the COFF's have really low image bases that confuses - // Analysis and also our basic block GUID when a constant value points to a low address section. - // TODO: This might not exist, we should set this based on the view. - "loader.imageBase": 0x1000000, - }); - - // If WARP is enabled we must turn it off to prevent matching on other stuff. - if bn_settings.contains("analysis.warp.matcher") { - settings["analysis.warp.matcher"] = json!(false); - settings["analysis.warp.guid"] = json!(false); - } - - settings -} - -fn main() { - let args = Args::parse(); - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); - - // TODO: After analysis finishes for a file we should save off the bndb to another directory called the bndb cache - // TODO: This cache should be used before opening a file for first analysis. - - // TODO: We should resolve the path to something sensible in cases where user is passing CWD. - // If no output file was given, just prepend binary with extension sbin - let output_file = args - .output - .unwrap_or(args.path.to_owned()) - .with_extension("sbin"); - - if output_file.exists() && !args.overwrite.unwrap_or(false) { - log::info!("Output file already exists, skipping... {:?}", output_file); - return; - } - - log::debug!("Starting Binary Ninja session..."); - let _headless_session = - binaryninja::headless::Session::new().expect("Failed to initialize session"); - - // Adjust the amount of worker threads so that we can actually free BinaryViews. - let worker_count = rayon::current_num_threads() * 4; - log::debug!("Adjusting Binary Ninja worker count to {}...", worker_count); - binaryninja::worker_thread::set_worker_thread_count(worker_count); - - // Make sure caches are flushed when the views get destructed. - register_cache_destructor(); - - let bn_settings = Settings::new(); - let settings = default_settings(&bn_settings); - - log::info!("Creating functions for {:?}...", args.path); - let start = std::time::Instant::now(); - let data = data_from_file(&settings, &args.path) - .expect("Failed to read data, check your license and Binary Ninja version!"); - log::info!("Functions created in {:?}", start.elapsed()); - - // TODO: Add a way to override the symbol type to make it a different function symbol. - // TODO: Right now the consumers must dictate that. - // TODO: The binja_warp consumer sets this to library function fwiw - - if !data.functions.is_empty() { - std::fs::write(&output_file, data.to_bytes()).expect("Failed to write functions to file"); - log::info!( - "{} functions written to {:?}...", - data.functions.len(), - output_file - ); - } else { - log::warn!("No functions found for binary {:?}...", args.path); - } -} - -fn data_from_view(view: &BinaryView) -> Data { - let mut data = Data::default(); - let is_function_named = |f: &BNGuard<BNFunction>| { - !f.symbol().short_name().to_string_lossy().contains("sub_") || f.has_user_annotations() - }; - - data.functions = view - .functions() - .iter() - .filter(is_function_named) - .filter_map(|f| { - let llil = f.low_level_il().ok()?; - Some(warp_ninja::cache::cached_function(&f, &llil)) - }) - .collect::<Vec<_>>(); - - if let Some(ref_ty_cache) = cached_type_references(view) { - let referenced_types = ref_ty_cache - .cache - .iter() - .filter_map(|t| t.to_owned()) - .collect::<Vec<_>>(); - - data.types.extend(referenced_types); - } - - data -} - -fn data_from_archive<R: Read>(settings: &Value, mut archive: Archive<R>) -> Option<Data> { - // TODO: I feel like this is a hack... - let temp_dir = tempdir::TempDir::new("tmp_archive").ok()?; - // Iterate through the entries in the ar file and make a temp dir with them - let mut entry_files: HashSet<PathBuf> = HashSet::new(); - while let Some(entry) = archive.next_entry() { - match entry { - Ok(mut entry) => { - let name = String::from_utf8_lossy(entry.header().identifier()).to_string(); - // Write entry data to a temp directory - let output_path = temp_dir.path().join(&name); - if !entry_files.contains(&output_path) { - let mut output_file = - File::create(&output_path).expect("Failed to create entry file"); - std::io::copy(&mut entry, &mut output_file).expect("Failed to read entry data"); - entry_files.insert(output_path); - } else { - log::debug!("Skipping already inserted entry: {}", name); - } - } - Err(e) => { - log::error!("Failed to read archive entry: {}", e); - } - } - } - - // Create the data. - let entry_data = entry_files - .into_par_iter() - .filter_map(|path| { - log::debug!("Creating data for ENTRY {:?}...", path); - data_from_file(settings, &path) - }) - .collect::<Vec<_>>(); - - Some(Data::merge(entry_data)) -} - -fn data_from_directory(settings: &Value, dir: PathBuf) -> Option<Data> { - let files = WalkDir::new(dir) - .into_iter() - .filter_map(|e| { - let path = e.ok()?.into_path(); - if path.is_file() { - Some(path) - } else { - None - } - }) - .collect::<Vec<_>>(); - - let unmerged_data = files - .into_par_iter() - .filter_map(|path| { - log::info!("Creating data for FILE {:?}...", path); - data_from_file(settings, &path) - }) - .collect::<Vec<_>>(); - - if !unmerged_data.is_empty() { - Some(Data::merge(unmerged_data)) - } else { - None - } -} - -fn data_from_file(settings: &Value, path: &Path) -> Option<Data> { - match path.extension() { - Some(ext) if ext == "a" || ext == "lib" || ext == "rlib" => { - let archive_file = File::open(path).expect("Failed to open archive file"); - let archive = Archive::new(archive_file); - data_from_archive(settings, archive) - } - Some(ext) if ext == "sbin" => { - let contents = std::fs::read(path).ok()?; - Data::from_bytes(&contents) - } - _ if path.is_dir() => data_from_directory(settings, path.into()), - _ => { - let path_str = path.to_str().unwrap(); - let view = binaryninja::load_with_options(path_str, true, Some(settings.to_string()))?; - let data = data_from_view(&view); - view.file().close(); - Some(data) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - #[test] - fn test_data_from_file() { - env_logger::init(); - // TODO: Store oracles here to get more out of this test. - let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); - let _headless_session = - binaryninja::headless::Session::new().expect("Failed to initialize session"); - let bn_settings = Settings::new(); - let settings = default_settings(&bn_settings); - for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") { - let entry = entry.expect("Failed to read directory entry"); - let path = entry.path(); - if path.is_file() { - let result = data_from_file(&settings, &path); - assert!(result.is_some()); - } - } - } -} diff --git a/plugins/warp/src/cache.rs b/plugins/warp/src/cache.rs index cc8dfded..09ad1444 100644 --- a/plugins/warp/src/cache.rs +++ b/plugins/warp/src/cache.rs @@ -1,29 +1,18 @@ -use crate::convert::{from_bn_symbol, from_bn_type_internal}; -use crate::{build_function, function_guid}; +pub mod container; +pub mod function; +pub mod guid; +pub mod type_reference; + +pub use function::*; +pub use guid::*; +pub use type_reference::*; + use binaryninja::binary_view::{BinaryView, BinaryViewExt}; -use binaryninja::confidence::MAX_CONFIDENCE; use binaryninja::function::Function as BNFunction; -use binaryninja::low_level_il::function::{FunctionMutability, LowLevelILFunction, NonSSA}; -use binaryninja::low_level_il::LowLevelILRegularFunction; use binaryninja::rc::Guard; use binaryninja::rc::Ref as BNRef; -use binaryninja::symbol::Symbol as BNSymbol; -use binaryninja::types::NamedTypeReference as BNNamedTypeReference; use binaryninja::ObjectDestructor; -use dashmap::mapref::one::Ref; -use dashmap::DashMap; -use std::collections::HashSet; use std::hash::{DefaultHasher, Hash, Hasher}; -use std::sync::OnceLock; -use warp::r#type::ComputedType; -use warp::signature::function::constraints::FunctionConstraint; -use warp::signature::function::{Function, FunctionGUID}; - -pub static MATCHED_FUNCTION_CACHE: OnceLock<DashMap<ViewID, MatchedFunctionCache>> = - OnceLock::new(); -pub static FUNCTION_CACHE: OnceLock<DashMap<ViewID, FunctionCache>> = OnceLock::new(); -pub static GUID_CACHE: OnceLock<DashMap<ViewID, GUIDCache>> = OnceLock::new(); -pub static TYPE_REF_CACHE: OnceLock<DashMap<ViewID, TypeRefCache>> = OnceLock::new(); pub fn register_cache_destructor() { pub static mut CACHE_DESTRUCTOR: CacheDestructor = CacheDestructor; @@ -34,351 +23,6 @@ pub fn register_cache_destructor() { }; } -pub fn cached_function_match<F>(function: &BNFunction, f: F) -> Option<Function> -where - F: Fn() -> Option<Function>, -{ - let view = function.view(); - let view_id = ViewID::from(view.as_ref()); - let function_id = FunctionID::from(function); - let function_cache = MATCHED_FUNCTION_CACHE.get_or_init(Default::default); - match function_cache.get(&view_id) { - Some(cache) => cache.get_or_insert(&function_id, f).to_owned(), - None => { - let cache = MatchedFunctionCache::default(); - let matched = cache.get_or_insert(&function_id, f).to_owned(); - function_cache.insert(view_id, cache); - matched - } - } -} - -pub fn try_cached_function_match(function: &BNFunction) -> Option<Function> { - let view = function.view(); - let view_id = ViewID::from(view); - let function_id = FunctionID::from(function); - let function_cache = MATCHED_FUNCTION_CACHE.get_or_init(Default::default); - function_cache - .get(&view_id)? - .get(&function_id)? - .value() - .to_owned() -} - -pub fn cached_function(function: &BNFunction, llil: &LowLevelILRegularFunction) -> Function { - let view = function.view(); - let view_id = ViewID::from(view.as_ref()); - let function_cache = FUNCTION_CACHE.get_or_init(Default::default); - match function_cache.get(&view_id) { - Some(cache) => cache.function(function, llil), - None => { - let cache = FunctionCache::default(); - let function = cache.function(function, llil); - function_cache.insert(view_id, cache); - function - } - } -} - -pub fn cached_call_site_constraints(function: &BNFunction) -> HashSet<FunctionConstraint> { - let view = function.view(); - let view_id = ViewID::from(view); - let guid_cache = GUID_CACHE.get_or_init(Default::default); - match guid_cache.get(&view_id) { - Some(cache) => cache.call_site_constraints(function), - None => { - let cache = GUIDCache::default(); - let constraints = cache.call_site_constraints(function); - guid_cache.insert(view_id, cache); - constraints - } - } -} - -pub fn cached_adjacency_constraints<F>( - function: &BNFunction, - filter: F, -) -> HashSet<FunctionConstraint> -where - F: Fn(&BNFunction) -> bool, -{ - let view = function.view(); - let view_id = ViewID::from(view); - let guid_cache = GUID_CACHE.get_or_init(Default::default); - match guid_cache.get(&view_id) { - Some(cache) => cache.adjacency_constraints(function, filter), - None => { - let cache = GUIDCache::default(); - let constraints = cache.adjacency_constraints(function, filter); - guid_cache.insert(view_id, cache); - constraints - } - } -} - -pub fn cached_function_guid<M: FunctionMutability>( - function: &BNFunction, - llil: &LowLevelILFunction<M, NonSSA>, -) -> FunctionGUID { - let view = function.view(); - let view_id = ViewID::from(view); - let guid_cache = GUID_CACHE.get_or_init(Default::default); - match guid_cache.get(&view_id) { - Some(cache) => cache.function_guid(function, llil), - None => { - let cache = GUIDCache::default(); - let guid = cache.function_guid(function, llil); - guid_cache.insert(view_id, cache); - guid - } - } -} - -pub fn try_cached_function_guid(function: &BNFunction) -> Option<FunctionGUID> { - let view = function.view(); - let view_id = ViewID::from(view); - let guid_cache = GUID_CACHE.get_or_init(Default::default); - guid_cache.get(&view_id)?.try_function_guid(function) -} - -pub fn cached_type_reference( - view: &BinaryView, - visited_refs: &mut HashSet<TypeRefID>, - type_ref: &BNNamedTypeReference, -) -> Option<ComputedType> { - let view_id = ViewID::from(view); - let type_ref_cache = TYPE_REF_CACHE.get_or_init(Default::default); - match type_ref_cache.get(&view_id) { - Some(cache) => cache.cached_type_reference(view, visited_refs, type_ref), - None => { - let cache = TypeRefCache::default(); - let ntr = cache.cached_type_reference(view, visited_refs, type_ref); - type_ref_cache.insert(view_id, cache); - ntr - } - } -} - -pub fn cached_type_references(view: &BinaryView) -> Option<Ref<ViewID, TypeRefCache>> { - let view_id = ViewID::from(view); - let type_ref_cache = TYPE_REF_CACHE.get_or_init(Default::default); - type_ref_cache.get(&view_id) -} - -#[derive(Clone, Debug, Default)] -pub struct MatchedFunctionCache { - pub cache: DashMap<FunctionID, Option<Function>>, -} - -impl MatchedFunctionCache { - pub fn get_or_insert<F>( - &self, - function_id: &FunctionID, - f: F, - ) -> Ref<'_, FunctionID, Option<Function>> - where - F: FnOnce() -> Option<Function>, - { - self.cache.get(function_id).unwrap_or_else(|| { - self.cache.insert(*function_id, f()); - self.cache.get(function_id).unwrap() - }) - } - - pub fn get(&self, function_id: &FunctionID) -> Option<Ref<'_, FunctionID, Option<Function>>> { - self.cache.get(function_id) - } -} - -#[derive(Clone, Debug, Default)] -pub struct FunctionCache { - pub cache: DashMap<FunctionID, Function>, -} - -impl FunctionCache { - pub fn function(&self, function: &BNFunction, llil: &LowLevelILRegularFunction) -> Function { - let function_id = FunctionID::from(function); - match self.cache.get(&function_id) { - Some(function) => function.value().to_owned(), - None => { - let function = build_function(function, llil); - self.cache.insert(function_id, function.clone()); - function - } - } - } -} - -#[derive(Clone, Debug, Default)] -pub struct GUIDCache { - pub cache: DashMap<FunctionID, FunctionGUID>, -} - -impl GUIDCache { - pub fn call_site_constraints(&self, function: &BNFunction) -> HashSet<FunctionConstraint> { - let view = function.view(); - let func_id = FunctionID::from(function); - let func_start = function.start(); - let func_platform = function.platform(); - let mut constraints = HashSet::new(); - for call_site in &function.call_sites() { - for cs_ref_addr in view.code_refs_from_addr(call_site.address, Some(function)) { - match view.function_at(&func_platform, cs_ref_addr) { - Some(cs_ref_func) => { - // Call site is a function, constrain on it. - let cs_ref_func_id = FunctionID::from(cs_ref_func.as_ref()); - if cs_ref_func_id != func_id { - let call_site_offset: i64 = - call_site.address.wrapping_sub(func_start) as i64; - // TODO: If the function is thunk we should also insert the called function. - constraints - .insert(self.function_constraint(&cs_ref_func, call_site_offset)); - } - } - None => { - // We could be dealing with an extern symbol, get the symbol as a constraint. - let call_site_offset: i64 = - call_site.address.wrapping_sub(func_start) as i64; - if let Some(call_site_sym) = view.symbol_by_address(cs_ref_addr) { - constraints.insert( - self.function_constraint_from_symbol( - &call_site_sym, - call_site_offset, - ), - ); - } - } - } - } - } - constraints - } - - pub fn adjacency_constraints<F>( - &self, - function: &BNFunction, - filter: F, - ) -> HashSet<FunctionConstraint> - where - F: Fn(&BNFunction) -> bool, - { - let view = function.view(); - let func_id = FunctionID::from(function); - let func_start = function.start(); - let mut constraints = HashSet::new(); - - let mut func_addr_constraint = |func_start_addr| { - // NOTE: We could potentially have dozens of functions all at the same start address. - for curr_func in &view.functions_at(func_start_addr) { - let curr_func_id = FunctionID::from(curr_func.as_ref()); - if curr_func_id != func_id && filter(curr_func.as_ref()) { - // NOTE: For this to work the GUID has to have already been cached. If not it will just be the symbol. - // Function adjacent to another function, constrain on the pattern. - let curr_addr_offset = (func_start_addr as i64) - func_start as i64; - constraints.insert(self.function_constraint(&curr_func, curr_addr_offset)); - } - } - }; - - let mut before_func_start = func_start; - for _ in 0..2 { - before_func_start = view.function_start_before(before_func_start); - func_addr_constraint(before_func_start); - } - - let mut after_func_start = func_start; - for _ in 0..2 { - after_func_start = view.function_start_after(after_func_start); - func_addr_constraint(after_func_start); - } - - constraints - } - - /// Construct a function constraint, must pass the offset at which it is located. - pub fn function_constraint(&self, function: &BNFunction, offset: i64) -> FunctionConstraint { - let guid = self.try_function_guid(function); - let symbol = from_bn_symbol(&function.symbol()); - FunctionConstraint { - guid, - symbol: Some(symbol), - offset, - } - } - - /// Construct a function constraint from a symbol, typically used for extern function call sites, must pass the offset at which it is located. - pub fn function_constraint_from_symbol( - &self, - symbol: &BNSymbol, - offset: i64, - ) -> FunctionConstraint { - let symbol = from_bn_symbol(symbol); - FunctionConstraint { - guid: None, - symbol: Some(symbol), - offset, - } - } - - pub fn function_guid<M: FunctionMutability>( - &self, - function: &BNFunction, - llil: &LowLevelILFunction<M, NonSSA>, - ) -> FunctionGUID { - let function_id = FunctionID::from(function); - match self.cache.get(&function_id) { - Some(function_guid) => function_guid.value().to_owned(), - None => { - let function_guid = function_guid(function, llil); - self.cache.insert(function_id, function_guid); - function_guid - } - } - } - - pub fn try_function_guid(&self, function: &BNFunction) -> Option<FunctionGUID> { - let function_id = FunctionID::from(function); - self.cache - .get(&function_id) - .map(|function_guid| function_guid.value().to_owned()) - } -} - -#[derive(Clone, Debug, Default)] -pub struct TypeRefCache { - pub cache: DashMap<TypeRefID, Option<ComputedType>>, -} - -impl TypeRefCache { - /// NOTE: No self-referential type must be used on this function. - pub fn cached_type_reference( - &self, - view: &BinaryView, - visited_refs: &mut HashSet<TypeRefID>, - type_ref: &BNNamedTypeReference, - ) -> Option<ComputedType> { - let ntr_id = TypeRefID::from(type_ref); - match self.cache.get(&ntr_id) { - Some(cache) => cache.to_owned(), - None => match type_ref.target(view) { - Some(raw_ty) => { - let computed_ty = ComputedType::new(from_bn_type_internal( - view, - visited_refs, - &raw_ty, - MAX_CONFIDENCE, - )); - self.cache - .entry(ntr_id) - .insert(Some(computed_ty)) - .to_owned() - } - None => self.cache.entry(ntr_id).insert(None).to_owned(), - }, - } - } -} - /// A unique view ID, used for caching. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct ViewID(u64); @@ -430,48 +74,11 @@ impl From<Guard<'_, BNFunction>> for FunctionID { } } -/// A unique named type reference ID, used for caching. -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct TypeRefID(u64); - -impl From<&BNNamedTypeReference> for TypeRefID { - fn from(value: &BNNamedTypeReference) -> Self { - let mut hasher = DefaultHasher::new(); - hasher.write(value.id().as_bytes()); - Self(hasher.finish()) - } -} - -impl From<BNRef<BNNamedTypeReference>> for TypeRefID { - fn from(value: BNRef<BNNamedTypeReference>) -> Self { - Self::from(value.as_ref()) - } -} - -impl From<Guard<'_, BNNamedTypeReference>> for TypeRefID { - fn from(value: Guard<'_, BNNamedTypeReference>) -> Self { - Self::from(value.as_ref()) - } -} - pub struct CacheDestructor; impl ObjectDestructor for CacheDestructor { fn destruct_view(&self, view: &BinaryView) { - // Clear caches as the view is no longer alive. - let view_id = ViewID::from(view); - if let Some(cache) = MATCHED_FUNCTION_CACHE.get() { - cache.remove(&view_id); - } - if let Some(cache) = FUNCTION_CACHE.get() { - cache.remove(&view_id); - } - if let Some(cache) = GUID_CACHE.get() { - cache.remove(&view_id); - } - if let Some(cache) = TYPE_REF_CACHE.get() { - cache.remove(&view_id); - } + clear_type_ref_cache(view); log::debug!("Removed WARP caches for {:?}", view.file().filename()); } } diff --git a/plugins/warp/src/cache/container.rs b/plugins/warp/src/cache/container.rs new file mode 100644 index 00000000..59b31bad --- /dev/null +++ b/plugins/warp/src/cache/container.rs @@ -0,0 +1,28 @@ +use crate::container::Container; +use dashmap::DashMap; +use std::ops::Deref; +use std::sync::{Arc, OnceLock, RwLock}; + +pub static CONTAINER_CACHE: OnceLock<DashMap<String, Arc<RwLock<Box<dyn Container>>>>> = + OnceLock::new(); + +pub fn for_cached_containers(f: impl Fn(&dyn Container)) { + let containers_cache = CONTAINER_CACHE.get_or_init(Default::default); + for container in containers_cache.iter() { + if let Ok(guarded_container) = container.read() { + f(guarded_container.deref().as_ref()); + } + } +} + +// TODO: The static lifetime here is a little wierd... (we need it to Box) +pub fn add_cached_container(container: impl Container + 'static) { + let containers_cache = CONTAINER_CACHE.get_or_init(Default::default); + let container_name = container.to_string(); + containers_cache.insert(container_name, Arc::new(RwLock::new(Box::new(container)))); +} + +pub fn cached_containers() -> Vec<Arc<RwLock<Box<dyn Container>>>> { + let containers_cache = CONTAINER_CACHE.get_or_init(Default::default); + containers_cache.iter().map(|c| c.clone()).collect() +} diff --git a/plugins/warp/src/cache/function.rs b/plugins/warp/src/cache/function.rs new file mode 100644 index 00000000..2fef05fd --- /dev/null +++ b/plugins/warp/src/cache/function.rs @@ -0,0 +1,28 @@ +use binaryninja::function::{Function as BNFunction, FunctionUpdateType}; +use warp::signature::function::Function; + +/// Inserts a function match into the cache. +/// +/// IMPORTANT: This will mark the function as needing updates, if you intend to fill in functions with +/// no match (i.e. `None`), then you must change this function to prevent marking that as needing updates. +/// However, it's perfectly valid to remove a match and need to update the function still, so be careful. +pub fn insert_cached_function_match(function: &BNFunction, matched_function: Option<Function>) { + // NOTE: If we expect to run match_function multiple times on a function, we should move this elsewhere. + // Mark the function as needing updates so that reanalysis occurs on the function, and we apply the match. + function.mark_updates_required(FunctionUpdateType::FullAutoFunctionUpdate); + match matched_function { + Some(matched_function) => { + function.store_metadata("warp_matched_function", &matched_function.to_bytes(), false); + } + None => { + function.remove_metadata("warp_matched_function"); + } + } +} + +// TODO: This does allocations, and for every reanalysis. +pub fn try_cached_function_match(function: &BNFunction) -> Option<Function> { + let metadata = function.query_metadata("warp_matched_function")?; + let raw_metadata = metadata.get_raw()?; + Function::from_bytes(&raw_metadata) +} diff --git a/plugins/warp/src/cache/guid.rs b/plugins/warp/src/cache/guid.rs new file mode 100644 index 00000000..f1788a33 --- /dev/null +++ b/plugins/warp/src/cache/guid.rs @@ -0,0 +1,193 @@ +use crate::cache::FunctionID; +use crate::convert::from_bn_symbol; +use crate::function_guid; +use binaryninja::binary_view::BinaryViewExt; +use binaryninja::function::Function as BNFunction; +use binaryninja::low_level_il::function::{FunctionMutability, LowLevelILFunction, NonSSA}; +use binaryninja::symbol::Symbol as BNSymbol; +use std::collections::HashSet; +use uuid::Uuid; +use warp::signature::constraint::Constraint; +use warp::signature::function::FunctionGUID; + +pub fn cached_function_guid<M: FunctionMutability>( + function: &BNFunction, + lifted_il: &LowLevelILFunction<M, NonSSA>, +) -> FunctionGUID { + let cached_guid = try_cached_function_guid(function); + if let Some(cached_guid) = cached_guid { + return cached_guid; + } + + let function_guid = function_guid(function, lifted_il); + function.store_metadata( + "warp_function_guid", + &function_guid.as_bytes().to_vec(), + false, + ); + function_guid +} + +pub fn try_cached_function_guid(function: &BNFunction) -> Option<FunctionGUID> { + let metadata = function.query_metadata("warp_function_guid")?; + let raw_metadata = metadata.get_raw()?; + let uuid = Uuid::from_slice(raw_metadata.as_slice()).ok()?; + Some(FunctionGUID::from(uuid)) +} + +pub fn cached_constraints<F>(function: &BNFunction, filter: F) -> HashSet<Constraint> +where + F: Fn(&BNFunction) -> bool, +{ + // TODO: Implied constraints, symbol name, image offset + let cs_constraints = cached_call_site_constraints(function); + let adj_constraints = cached_adjacency_constraints(function, filter); + cs_constraints.union(&adj_constraints).cloned().collect() +} + +pub fn cached_call_site_constraints(function: &BNFunction) -> HashSet<Constraint> { + let cache = ConstraintBuilder; + cache.call_site_constraints(function) +} + +pub fn cached_adjacency_constraints<F>(function: &BNFunction, filter: F) -> HashSet<Constraint> +where + F: Fn(&BNFunction) -> bool, +{ + let cache = ConstraintBuilder; + cache.adjacency_constraints(function, filter) +} + +#[derive(Clone, Debug, Default)] +pub struct ConstraintBuilder; + +impl ConstraintBuilder { + pub fn call_site_constraints(&self, function: &BNFunction) -> HashSet<Constraint> { + let view = function.view(); + let func_id = FunctionID::from(function); + let func_start = function.start(); + let func_platform = function.platform(); + let mut constraints = HashSet::new(); + for call_site in &function.call_sites() { + for cs_ref_addr in view.code_refs_from_addr(call_site.address, Some(function)) { + match view.function_at(&func_platform, cs_ref_addr) { + Some(cs_ref_func) => { + // Call site is a function, constrain on it. + let cs_ref_func_id = FunctionID::from(cs_ref_func.as_ref()); + if cs_ref_func_id != func_id { + let call_site_offset: i64 = + call_site.address.wrapping_sub(func_start) as i64; + // TODO: If the function is thunk we should also insert the called function. + constraints.extend( + self.related_function_constraint(&cs_ref_func, call_site_offset), + ); + } + } + None => { + // We could be dealing with an extern symbol, get the symbol as a constraint. + let call_site_offset: i64 = + call_site.address.wrapping_sub(func_start) as i64; + if let Some(call_site_sym) = view.symbol_by_address(cs_ref_addr) { + constraints.insert( + self.related_symbol_constraint(&call_site_sym, call_site_offset), + ); + } + } + } + } + } + constraints + } + + pub fn adjacency_constraints<F>(&self, function: &BNFunction, filter: F) -> HashSet<Constraint> + where + F: Fn(&BNFunction) -> bool, + { + let view = function.view(); + let func_id = FunctionID::from(function); + let func_start = function.start(); + let mut constraints = HashSet::new(); + + let mut func_addr_constraint = |func_start_addr| { + // NOTE: We could potentially have dozens of functions all at the same start address. + for curr_func in &view.functions_at(func_start_addr) { + let curr_func_id = FunctionID::from(curr_func.as_ref()); + if curr_func_id != func_id && filter(curr_func.as_ref()) { + // NOTE: For this to work the GUID has to have already been cached. If not it will just be the symbol. + // Function adjacent to another function, constrain on the pattern. + let curr_addr_offset = (func_start_addr as i64) - func_start as i64; + constraints + .extend(self.related_function_constraint(&curr_func, curr_addr_offset)); + } + } + }; + + let mut before_func_start = func_start; + for _ in 0..2 { + before_func_start = view.function_start_before(before_func_start); + func_addr_constraint(before_func_start); + } + + let mut after_func_start = func_start; + for _ in 0..2 { + after_func_start = view.function_start_after(after_func_start); + func_addr_constraint(after_func_start); + } + + constraints + } + + /// Construct a function constraint, must pass the offset at which it is located. + pub fn related_function_constraint( + &self, + function: &BNFunction, + offset: i64, + ) -> Vec<Constraint> { + let mut constraints = vec![]; + if let Some(guid) = try_cached_function_guid(function) { + let guid_constraint = Constraint::from_function(&guid, Some(offset)); + constraints.push(guid_constraint); + } + let symbol_constraint = self.related_symbol_constraint(&function.symbol(), offset); + constraints.push(symbol_constraint); + constraints + } + + /// Construct a symbol constraint, must pass the offset at which it is located. + pub fn related_symbol_constraint(&self, symbol: &BNSymbol, offset: i64) -> Constraint { + let mut symbol = from_bn_symbol(symbol); + symbol.name = clean_symbol_name(&symbol.name); + Constraint::from_symbol(&symbol, Some(offset)) + } +} + +/// Cleans various internal symbol prefixes and suffixes for consistency. +/// +/// This is very important for getting matching symbol constraints. +/// +/// Examples: +/// - "__imp__RemoveDirectoryW@4" -> "RemoveDirectoryW" +/// - "__free_base" -> "free_base" +/// - "__impl__free_base" -> "free_base" +/// - "j___free_base" -> "free_base" +/// - "j_free_base" -> "free_base" +/// - "_free_base" -> "free_base" +pub fn clean_symbol_name(symbol_name: &str) -> String { + // Handle MSVC-style imported symbols + let without_imp = symbol_name.strip_prefix("__imp__").unwrap_or(symbol_name); + + // Handle jump thunk prefix + let without_jump = without_imp.strip_prefix("j_").unwrap_or(without_imp); + + // Strip all remaining leading underscores + let mut result = without_jump; + while result.starts_with('_') { + result = &result[1..]; + } + + // Remove stdcall decoration (@N suffix) + match result.find('@') { + Some(pos) => result[..pos].to_string(), + None => result.to_string(), + } +} diff --git a/plugins/warp/src/cache/type_reference.rs b/plugins/warp/src/cache/type_reference.rs new file mode 100644 index 00000000..0b07c139 --- /dev/null +++ b/plugins/warp/src/cache/type_reference.rs @@ -0,0 +1,105 @@ +use crate::cache::ViewID; +use crate::convert::from_bn_type_internal; +use binaryninja::binary_view::BinaryView; +use binaryninja::confidence::MAX_CONFIDENCE; +use binaryninja::rc::Guard; +use binaryninja::rc::Ref as BNRef; +use binaryninja::types::NamedTypeReference as BNNamedTypeReference; +use dashmap::mapref::one::Ref; +use dashmap::DashMap; +use std::collections::HashSet; +use std::hash::{DefaultHasher, Hasher}; +use std::sync::OnceLock; +use warp::r#type::ComputedType; + +pub static TYPE_REF_CACHE: OnceLock<DashMap<ViewID, TypeRefCache>> = OnceLock::new(); + +pub fn clear_type_ref_cache(view: &BinaryView) { + let view_id = ViewID::from(view); + if let Some(cache) = TYPE_REF_CACHE.get() { + cache.remove(&view_id); + } +} + +pub fn cached_type_reference( + view: &BinaryView, + visited_refs: &mut HashSet<TypeRefID>, + type_ref: &BNNamedTypeReference, +) -> Option<ComputedType> { + let view_id = ViewID::from(view); + let type_ref_cache = TYPE_REF_CACHE.get_or_init(Default::default); + match type_ref_cache.get(&view_id) { + Some(cache) => cache.cached_type_reference(view, visited_refs, type_ref), + None => { + let cache = TypeRefCache::default(); + let ntr = cache.cached_type_reference(view, visited_refs, type_ref); + type_ref_cache.insert(view_id, cache); + ntr + } + } +} + +pub fn cached_type_references(view: &BinaryView) -> Option<Ref<ViewID, TypeRefCache>> { + let view_id = ViewID::from(view); + let type_ref_cache = TYPE_REF_CACHE.get_or_init(Default::default); + type_ref_cache.get(&view_id) +} + +#[derive(Clone, Debug, Default)] +pub struct TypeRefCache { + pub cache: DashMap<TypeRefID, Option<ComputedType>>, +} + +impl TypeRefCache { + /// NOTE: No self-referential type must be used on this function. + pub fn cached_type_reference( + &self, + view: &BinaryView, + visited_refs: &mut HashSet<TypeRefID>, + type_ref: &BNNamedTypeReference, + ) -> Option<ComputedType> { + let ntr_id = TypeRefID::from(type_ref); + match self.cache.get(&ntr_id) { + Some(cache) => cache.to_owned(), + None => match type_ref.target(view) { + Some(raw_ty) => { + let computed_ty = ComputedType::new(from_bn_type_internal( + view, + visited_refs, + &raw_ty, + MAX_CONFIDENCE, + )); + self.cache + .entry(ntr_id) + .insert(Some(computed_ty)) + .to_owned() + } + None => self.cache.entry(ntr_id).insert(None).to_owned(), + }, + } + } +} + +/// A unique named type reference ID, used for caching. +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub struct TypeRefID(u64); + +impl From<&BNNamedTypeReference> for TypeRefID { + fn from(value: &BNNamedTypeReference) -> Self { + let mut hasher = DefaultHasher::new(); + hasher.write(value.id().as_bytes()); + Self(hasher.finish()) + } +} + +impl From<BNRef<BNNamedTypeReference>> for TypeRefID { + fn from(value: BNRef<BNNamedTypeReference>) -> Self { + Self::from(value.as_ref()) + } +} + +impl From<Guard<'_, BNNamedTypeReference>> for TypeRefID { + fn from(value: Guard<'_, BNNamedTypeReference>) -> Self { + Self::from(value.as_ref()) + } +} diff --git a/plugins/warp/src/container.rs b/plugins/warp/src/container.rs new file mode 100644 index 00000000..8d720021 --- /dev/null +++ b/plugins/warp/src/container.rs @@ -0,0 +1,266 @@ +use crate::container::disk::NAMESPACE_DISK_SOURCE; +use std::collections::HashMap; +use std::fmt::{Debug, Display}; +use std::hash::Hash; +use std::io; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use thiserror::Error; +use uuid::Uuid; +use warp::r#type::guid::TypeGUID; +use warp::r#type::{ComputedType, Type}; +use warp::signature::function::{Function, FunctionGUID}; +use warp::target::Target; + +pub mod disk; +pub mod memory; +pub mod network; + +pub type ContainerResult<T> = Result<T, ContainerError>; + +#[derive(Debug, Error, PartialEq, Eq, Hash)] +pub enum ContainerError { + #[error("source {0} was not found")] + SourceNotFound(SourceId), + #[error("source {0} is not writable")] + SourceNotWritable(SourceId), + #[error("source with path {0} already exists")] + SourceAlreadyExists(SourcePath), + #[error("source with path {0} cannot be created in container")] + CannotCreateSource(SourcePath), + #[error("operation failed due to corrupted data: {0}")] + CorruptedData(&'static str), + #[error("failed io operation: {0}")] + FailedIO(io::ErrorKind), + #[error("source {0} does not have an available path")] + SourcePathUnavailable(SourceId), +} + +/// Represents the ID for a single container source. +/// +/// A [`SourceId`] can be used in multiple separate containers, but **must** be unique in a container. +/// +/// A source is used to relate types and functions separate from the container. This allows +/// type name lookups and for containers which are bandwidth sensitive to exist. +/// +/// An example of a bandwidth-sensitive container would be a container that pulls functions over +/// the network instead of from memory. +/// +/// This type is marked `repr(transparent)` to the underlying `[u8; 16]` type, so it is safe to use in FFI. +#[repr(transparent)] +#[derive(Clone, Debug, Eq, PartialEq, Hash, Copy)] +pub struct SourceId(Uuid); + +impl SourceId { + pub fn new() -> Self { + Self(Uuid::new_v4()) + } +} + +impl From<Uuid> for SourceId { + fn from(value: Uuid) -> Self { + Self(value) + } +} + +impl FromStr for SourceId { + type Err = uuid::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + Uuid::parse_str(s).map(Into::into) + } +} + +impl Display for SourceId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +/// Represents a unique path to a source. +/// +/// This is used when first creating a source for a container, the path is given to the container +/// as otherwise the user has no control over source creation and where the source is ultimately located. +/// +/// While the underlying type is a [`PathBuf`], a source path can be really anything, the [`PathBuf`] +/// just provides an easier way to join segments for nested source locations. +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct SourcePath(PathBuf); + +impl SourcePath { + pub fn new(path: PathBuf) -> Self { + Self(path) + } + + pub fn new_with_str(value: &str) -> Self { + Self(PathBuf::from(value)) + } + + pub fn to_source_id(&self) -> SourceId { + // TODO: This path is not relative to the disk container is it? + // TODO: The path here should be relative to the container I think? + // TODO: The above is important so that the id is the same across users. + let value: Vec<u8> = self.to_string().into_bytes(); + SourceId(Uuid::new_v5(&NAMESPACE_DISK_SOURCE, &value)) + } +} + +impl AsRef<PathBuf> for SourcePath { + fn as_ref(&self) -> &PathBuf { + &self.0 + } +} + +impl AsRef<Path> for SourcePath { + fn as_ref(&self) -> &Path { + &self.0 + } +} + +impl Display for SourcePath { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.display()) + } +} + +/// Storage for WARP information. +/// +/// Containers are made up of sources, see [`SourceId`] for more details. +pub trait Container: Send + Sync + Display + Debug { + /// Available container sources. + /// + /// NOTE: Due to the nature of some containers, this list of sources may be incomplete. Do not + /// rely on this list to retrieve data, instead prefer: + /// - [Container::sources_with_type_guid] + /// - [Container::sources_with_type_guids] + /// - [Container::sources_with_function_guid] + /// - [Container::sources_with_function_guids] + fn sources(&self) -> ContainerResult<Vec<SourceId>>; + + /// Create a new source in the container or add the existing source at the given path to known sources. + /// + /// The returned [`SourceId`] can be used to add, query and remove information from the source. + /// + /// NOTE: Adding a source does **NOT** mean that it, and the data associated with it, has been + /// persisted, you **MUST** call [`Container::commit_source`] to persist the created source. + /// + /// NOTE: Adding a source does **NOT** mean that you can write to it, use [`Container::is_source_writable`] + /// to verify the permissions of the source. + fn add_source(&mut self, path: SourcePath) -> ContainerResult<SourceId>; + + /// Flush changes made to a source. + /// + /// Because writing to a source can require file or network operations, we let the container + /// offer the ability to hold off performing that operation until the data needs to be committed. + fn commit_source(&mut self, source: &SourceId) -> ContainerResult<bool>; + + /// Whether the source can be written to. + /// + /// The source must be mutable to perform the following: + /// - [Container::add_types] + /// - [Container::add_computed_types] + /// - [Container::remove_types] + /// - [Container::add_functions] + /// - [Container::remove_functions] + fn is_source_writable(&self, source: &SourceId) -> ContainerResult<bool>; + + /// Whether the source has uncommitted changes or not. + /// + /// NOTE: This is **NOT** whether the source has been committed at all, rather a flag to indicate + /// that a source has uncommitted changes. + fn is_source_uncommitted(&self, source: &SourceId) -> ContainerResult<bool>; + + /// Retrieve the [`SourcePath`] for the given source. + /// + /// NOTE: This does not have to be a filesystem path, its representation is dictated + /// by the implementation. + fn source_path(&self, source: &SourceId) -> ContainerResult<SourcePath>; + + // TODO: Note about commit_source + fn add_types(&mut self, source: &SourceId, types: &[Type]) -> ContainerResult<()> { + let computed_types: Vec<_> = types.iter().cloned().map(ComputedType::new).collect(); + self.add_computed_types(source, &computed_types) + } + + // TODO: Note about commit_source + fn add_computed_types( + &mut self, + source: &SourceId, + types: &[ComputedType], + ) -> ContainerResult<()>; + + // TODO: Note about commit_source + fn remove_types(&mut self, source: &SourceId, guids: &[TypeGUID]) -> ContainerResult<()>; + + // TODO: Note about commit_source + fn add_functions( + &mut self, + target: &Target, + source: &SourceId, + functions: &[Function], + ) -> ContainerResult<()>; + + // TODO: Note about commit_source + fn remove_functions( + &mut self, + target: &Target, + source: &SourceId, + functions: &[Function], + ) -> ContainerResult<()>; + + /// Get the sources that contain a type with the given [`TypeGUID`]. + fn sources_with_type_guid(&self, guid: &TypeGUID) -> ContainerResult<Vec<SourceId>>; + + /// Plural version of [`Container::sources_with_type_guid`]. + /// + /// Each source will have a list of the containing GUID's so that when looking up a source, you give + /// it only the GUID's that it knows about, for networking this means cutting down traffic significantly. + fn sources_with_type_guids( + &self, + guids: &[TypeGUID], + ) -> ContainerResult<HashMap<TypeGUID, Vec<SourceId>>>; + + /// Retrieve all [`TypeGUID`]'s with the given name. + fn type_guids_with_name(&self, source: &SourceId, name: &str) + -> ContainerResult<Vec<TypeGUID>>; + + fn type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<Option<Type>>; + + fn has_type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<bool> { + Ok(self.type_with_guid(source, guid)?.is_some()) + } + + /// Get the sources that contain functions with the given [`FunctionGUID`]. + fn sources_with_function_guid( + &self, + target: &Target, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<SourceId>>; + + // TODO: Allocating with Vec is not good. + /// Plural version of [`Container::sources_with_function_guid`]. + /// + /// Each source will have a list of the containing GUID's so that when looking up a source you give + /// it only the GUID's that it knows about, for networking this means cutting down traffic significantly. + fn sources_with_function_guids( + &self, + target: &Target, + guids: &[FunctionGUID], + ) -> ContainerResult<HashMap<FunctionGUID, Vec<SourceId>>>; + + fn functions_with_guid( + &self, + target: &Target, + source: &SourceId, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<Function>>; + + fn has_function_with_guid( + &self, + target: &Target, + source: &SourceId, + guid: &FunctionGUID, + ) -> ContainerResult<bool> { + Ok(!self.functions_with_guid(target, source, guid)?.is_empty()) + } +} diff --git a/plugins/warp/src/container/disk.rs b/plugins/warp/src/container/disk.rs new file mode 100644 index 00000000..ca685a5f --- /dev/null +++ b/plugins/warp/src/container/disk.rs @@ -0,0 +1,402 @@ +use crate::container::{Container, ContainerError, ContainerResult, SourceId, SourcePath}; +use std::collections::HashMap; +use std::fmt::{Debug, Display, Formatter}; +use std::hash::{Hash, Hasher}; +use std::path::PathBuf; +use uuid::{uuid, Uuid}; +use walkdir::{DirEntry, WalkDir}; +use warp::chunk::{Chunk, ChunkKind, CompressionType}; +use warp::r#type::chunk::TypeChunk; +use warp::r#type::guid::TypeGUID; +use warp::r#type::{ComputedType, Type}; +use warp::signature::chunk::SignatureChunk; +use warp::signature::function::{Function, FunctionGUID}; +use warp::target::Target; +use warp::{WarpFile, WarpFileHeader}; + +pub const NAMESPACE_DISK_SOURCE: Uuid = uuid!("ea89e8ab-a27a-432b-8fbd-77b026cd5f41"); + +// TODO: How to support remote projects? I.e. collaboration? +pub struct DiskContainer { + pub name: String, + pub sources: HashMap<SourceId, DiskContainerSource>, +} + +impl DiskContainer { + pub fn new(name: String, sources: HashMap<SourceId, DiskContainerSource>) -> Self { + Self { name, sources } + } + + pub fn new_from_dir(dir_path: PathBuf) -> Self { + let source_from_entry = |entry: DirEntry| { + let path = SourcePath(entry.into_path()); + let source_id = path.to_source_id(); + let path_ext = path.0.extension().unwrap_or_default().to_str(); + match (DiskContainerSource::new_from_path(path.clone()), path_ext) { + (Ok(source), _) => Some((source_id, source)), + (Err(err), Some("warp")) => { + log::error!("Failed to load source '{}' from disk: {}", path, err); + None + } + // We don't care to show errors loading for non-warp files. + (Err(_), _) => None, + } + }; + + // TODO: For now, any file that does not have the "warp" extension will be filtered out. + // TODO: cont. in the future we might want to remove this for convenience. + let name = dir_path.to_string_lossy().to_string(); + let sources = WalkDir::new(dir_path) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.file_type().is_file()) + .filter(|e| e.path().extension().is_some_and(|e| e == "warp")) + .filter_map(source_from_entry) + .collect(); + + Self::new(name, sources) + } +} + +impl Container for DiskContainer { + fn sources(&self) -> ContainerResult<Vec<SourceId>> { + Ok(self.sources.keys().copied().collect()) + } + + fn add_source(&mut self, path: SourcePath) -> ContainerResult<SourceId> { + // Disk sources have there source id computed from the path. + let source_id = path.to_source_id(); + if self.sources.contains_key(&source_id) { + return Err(ContainerError::SourceAlreadyExists(path)); + } + // NOTE: We let anyone add a file from anywhere on the file system because of this. + match path.0.exists() { + true => { + let disk_source = DiskContainerSource::new_from_path(path.clone())?; + self.sources.insert(source_id, disk_source); + Ok(source_id) + } + false => { + let file = WarpFile::new(WarpFileHeader::new(), vec![]); + let disk_source = DiskContainerSource::new(path, file); + self.sources.insert(source_id, disk_source); + Ok(source_id) + } + } + } + + fn commit_source(&mut self, source: &SourceId) -> ContainerResult<bool> { + let disk_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + + disk_source.commit_to_disk() + } + + fn is_source_writable(&self, source: &SourceId) -> ContainerResult<bool> { + let _disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + // TODO: I think this should be up to the container. (cant write to bundled files) + Ok(true) + } + + fn is_source_uncommitted(&self, source: &SourceId) -> ContainerResult<bool> { + let disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(disk_source.uncommitted) + } + + fn source_path(&self, source: &SourceId) -> ContainerResult<SourcePath> { + let disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(disk_source.path.clone()) + } + + fn add_computed_types( + &mut self, + source: &SourceId, + types: &[ComputedType], + ) -> ContainerResult<()> { + let disk_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + + disk_source.add_computed_types(types) + } + + // TODO: I believe any remove has to happen immediately, i.e. we cant add an uncommitted for this? + fn remove_types(&mut self, source: &SourceId, _guids: &[TypeGUID]) -> ContainerResult<()> { + let _disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + + // TODO: Do this. + Err(ContainerError::SourceNotWritable(*source)) + } + + fn add_functions( + &mut self, + target: &Target, + source: &SourceId, + functions: &[Function], + ) -> ContainerResult<()> { + let disk_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + + disk_source.add_functions(target.clone(), functions) + } + + // TODO: I believe any remove has to happen immediately, i.e. we cant add an uncommitted for this? + fn remove_functions( + &mut self, + _target: &Target, + source: &SourceId, + _functions: &[Function], + ) -> ContainerResult<()> { + let _disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + + // TODO: Do this. + Err(ContainerError::SourceNotWritable(*source)) + } + + fn sources_with_type_guid(&self, guid: &TypeGUID) -> ContainerResult<Vec<SourceId>> { + let sources = self + .sources + .iter() + .filter(|(_, source)| source.has_type_with_guid(guid)) + .map(|(id, _)| *id) + .collect(); + Ok(sources) + } + + fn sources_with_type_guids<'a>( + &'a self, + guids: &'a [TypeGUID], + ) -> ContainerResult<HashMap<TypeGUID, Vec<SourceId>>> { + let mut result: HashMap<TypeGUID, Vec<SourceId>> = HashMap::new(); + for (source_id, source) in &self.sources { + guids + .iter() + .filter(|guid| source.has_type_with_guid(guid)) + .for_each(|guid| result.entry(*guid).or_default().push(*source_id)); + } + Ok(result) + } + + fn type_guids_with_name( + &self, + source: &SourceId, + name: &str, + ) -> ContainerResult<Vec<TypeGUID>> { + let disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(disk_source.type_guids_with_name(name)) + } + + fn type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<Option<Type>> { + let disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(disk_source.type_with_guid(guid)) + } + + fn sources_with_function_guid( + &self, + target: &Target, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<SourceId>> { + let sources = self + .sources + .iter() + .filter(|(_, source)| source.has_function_with_guid(target, guid)) + .map(|(id, _)| *id) + .collect(); + Ok(sources) + } + + fn sources_with_function_guids<'a>( + &self, + target: &Target, + guids: &[FunctionGUID], + ) -> ContainerResult<HashMap<FunctionGUID, Vec<SourceId>>> { + let mut result: HashMap<FunctionGUID, Vec<SourceId>> = HashMap::new(); + for (source_id, source) in &self.sources { + guids + .iter() + .filter(|guid| source.has_function_with_guid(target, guid)) + .for_each(|guid| result.entry(*guid).or_default().push(*source_id)); + } + Ok(result) + } + + fn functions_with_guid( + &self, + target: &Target, + source: &SourceId, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<Function>> { + let disk_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(disk_source.functions_with_guid(target, guid)) + } +} + +impl Display for DiskContainer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + +impl Debug for DiskContainer { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DiskContainer") + .field("name", &self.name) + .field("sources", &self.sources) + .finish() + } +} + +pub struct DiskContainerSource { + pub path: SourcePath, + file: WarpFile<'static>, + uncommitted: bool, +} + +impl DiskContainerSource { + pub fn new(path: SourcePath, file: WarpFile<'static>) -> Self { + Self { + path, + file, + uncommitted: false, + } + } + + pub fn new_from_path(path: SourcePath) -> ContainerResult<Self> { + // TODO: To keep the lifetime out of DiskContainerSource we do not allow mapping file to memory. + let contents = std::fs::read(&path).map_err(|e| ContainerError::FailedIO(e.kind()))?; + let file = WarpFile::from_owned_bytes(contents).ok_or(ContainerError::CorruptedData( + "file data failed to validate", + ))?; + Ok(Self::new(path, file)) + } + + fn add_computed_types(&mut self, types: &[ComputedType]) -> ContainerResult<()> { + let type_chunk = TypeChunk::new_with_computed(types).ok_or( + ContainerError::CorruptedData("type chunk failed to validate"), + )?; + let chunk = Chunk::new(ChunkKind::Type(type_chunk), CompressionType::None); + self.file.chunks.push(chunk); + self.uncommitted = true; + Ok(()) + } + + fn add_functions(&mut self, target: Target, functions: &[Function]) -> ContainerResult<()> { + let signature_chunk = SignatureChunk::new(functions).ok_or( + ContainerError::CorruptedData("signature chunk failed to validate"), + )?; + let chunk = Chunk::new_with_target( + ChunkKind::Signature(signature_chunk), + CompressionType::None, + target, + ); + self.file.chunks.push(chunk); + self.uncommitted = true; + Ok(()) + } + + fn commit_to_disk(&mut self) -> ContainerResult<bool> { + let file = self.file.to_bytes(); + std::fs::write(&self.path, file).map_err(|e| ContainerError::FailedIO(e.kind()))?; + self.uncommitted = false; + Ok(true) + } + + fn type_guids_with_name(&self, name: &str) -> Vec<TypeGUID> { + let mut found: Vec<TypeGUID> = Vec::new(); + for chunk in &self.file.chunks { + if let ChunkKind::Type(tc) = &chunk.kind { + found.extend( + tc.raw_type_with_name(name) + .into_iter() + .map(|t| TypeGUID::from(t.guid())), + ); + } + } + found + } + + fn type_with_guid(&self, guid: &TypeGUID) -> Option<Type> { + self.file.chunks.iter().find_map(|chunk| { + if let ChunkKind::Type(tc) = &chunk.kind { + tc.type_with_guid(guid) + } else { + None + } + }) + } + + // TODO: When we support reading lazily instead of all in memory. + fn has_type_with_guid(&self, guid: &TypeGUID) -> bool { + self.type_with_guid(guid).is_some() + } + + fn functions_with_guid(&self, target: &Target, guid: &FunctionGUID) -> Vec<Function> { + let mut found: Vec<Function> = Vec::new(); + for chunk in &self.file.chunks { + if chunk.header.target != *target { + continue; + } + if let ChunkKind::Signature(sc) = &chunk.kind { + found.extend(sc.functions_with_guid(guid)); + } + } + found + } + + // TODO: When we support reading lazily instead of all in memory. + fn has_function_with_guid(&self, target: &Target, guid: &FunctionGUID) -> bool { + // TODO: How about we dont clone. + !self.functions_with_guid(target, guid).is_empty() + } +} + +impl Hash for DiskContainerSource { + fn hash<H: Hasher>(&self, state: &mut H) { + self.path.hash(state); + } +} + +impl Display for DiskContainerSource { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.path) + } +} + +impl Debug for DiskContainerSource { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DiskContainerSource") + .field("path", &self.path) + .field("file_header", &self.file.header) + .field("file_chunks", &self.file.chunks.len()) + .finish() + } +} diff --git a/plugins/warp/src/container/memory.rs b/plugins/warp/src/container/memory.rs new file mode 100644 index 00000000..cf53390e --- /dev/null +++ b/plugins/warp/src/container/memory.rs @@ -0,0 +1,307 @@ +use crate::container::{Container, ContainerError, ContainerResult, SourceId, SourcePath}; +use std::collections::HashMap; +use std::fmt::Display; +use warp::r#type::guid::TypeGUID; +use warp::r#type::{ComputedType, Type}; +use warp::signature::function::{Function, FunctionGUID}; +use warp::target::Target; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct MemoryContainer { + sources: HashMap<SourceId, MemorySource>, +} + +impl MemoryContainer { + pub fn new() -> Self { + MemoryContainer::default() + } + + pub fn with_source(mut self, id: SourceId, source: MemorySource) -> Self { + self.sources.insert(id, source); + self + } + + pub fn with_source_function( + mut self, + id: SourceId, + guid: FunctionGUID, + func: Function, + ) -> Self { + self.sources + .entry(id) + .or_default() + .functions + .entry(guid) + .or_default() + .push(func); + self + } + + pub fn with_source_type(mut self, id: SourceId, guid: TypeGUID, ty: Type) -> Self { + self.sources.entry(id).or_default().types.insert(guid, ty); + self + } +} + +impl Container for MemoryContainer { + fn sources(&self) -> ContainerResult<Vec<SourceId>> { + todo!() + } + + fn add_source(&mut self, path: SourcePath) -> ContainerResult<SourceId> { + Err(ContainerError::CannotCreateSource(path)) + } + + fn commit_source(&mut self, _source: &SourceId) -> ContainerResult<bool> { + Ok(false) + } + + fn is_source_writable(&self, source: &SourceId) -> ContainerResult<bool> { + let memory_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(memory_source.writable) + } + + fn is_source_uncommitted(&self, source: &SourceId) -> ContainerResult<bool> { + let _memory_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + // NOTE: Memory containers do not have a notion of uncommitted data. + Ok(false) + } + + fn source_path(&self, source: &SourceId) -> ContainerResult<SourcePath> { + Err(ContainerError::SourcePathUnavailable(*source)) + } + + fn add_computed_types( + &mut self, + source: &SourceId, + types: &[ComputedType], + ) -> ContainerResult<()> { + let memory_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + match memory_source.writable { + true => { + for ty in types { + memory_source.types.insert(ty.guid, ty.ty.clone()); + } + Ok(()) + } + false => Err(ContainerError::SourceNotWritable(*source)), + } + } + + fn remove_types(&mut self, source: &SourceId, guids: &[TypeGUID]) -> ContainerResult<()> { + let memory_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + match memory_source.writable { + true => { + for guid in guids { + memory_source.types.remove(guid); + } + Ok(()) + } + false => Err(ContainerError::SourceNotWritable(*source)), + } + } + + fn add_functions( + &mut self, + _target: &Target, + source: &SourceId, + functions: &[Function], + ) -> ContainerResult<()> { + let memory_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + match memory_source.writable { + true => { + for function in functions { + memory_source + .functions + .entry(function.guid) + .or_default() + .push(function.clone()); + } + Ok(()) + } + false => Err(ContainerError::SourceNotWritable(*source)), + } + } + + fn remove_functions( + &mut self, + _target: &Target, + source: &SourceId, + functions: &[Function], + ) -> ContainerResult<()> { + let memory_source = self + .sources + .get_mut(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + match memory_source.writable { + true => { + for function in functions { + if let Some(src_funcs) = memory_source.functions.get_mut(&function.guid) { + src_funcs.retain(|f| f != function); + if src_funcs.is_empty() { + memory_source.functions.remove(&function.guid); + } + } + } + Ok(()) + } + false => Err(ContainerError::SourceNotWritable(*source)), + } + } + + fn sources_with_type_guid(&self, guid: &TypeGUID) -> ContainerResult<Vec<SourceId>> { + let sources = self + .sources + .iter() + .filter(|(_, source)| source.has_type_with_guid(guid)) + .map(|(id, _)| *id) + .collect(); + Ok(sources) + } + + fn sources_with_type_guids( + &self, + guids: &[TypeGUID], + ) -> ContainerResult<HashMap<TypeGUID, Vec<SourceId>>> { + let mut result: HashMap<TypeGUID, Vec<SourceId>> = HashMap::new(); + for (source_id, source) in &self.sources { + guids + .iter() + .filter(|guid| source.has_type_with_guid(guid)) + .for_each(|guid| result.entry(*guid).or_default().push(*source_id)); + } + Ok(result) + } + + fn type_guids_with_name( + &self, + source: &SourceId, + name: &str, + ) -> ContainerResult<Vec<TypeGUID>> { + let memory_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(memory_source.type_guids_with_name(name)) + } + + fn type_with_guid(&self, source: &SourceId, guid: &TypeGUID) -> ContainerResult<Option<Type>> { + let memory_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(memory_source.type_with_guid(guid)) + } + + fn sources_with_function_guid( + &self, + _target: &Target, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<SourceId>> { + let sources = self + .sources + .iter() + .filter(|(_, source)| source.has_function_with_guid(guid)) + .map(|(id, _)| *id) + .collect(); + Ok(sources) + } + + fn sources_with_function_guids( + &self, + _target: &Target, + guids: &[FunctionGUID], + ) -> ContainerResult<HashMap<FunctionGUID, Vec<SourceId>>> { + let mut result: HashMap<FunctionGUID, Vec<SourceId>> = HashMap::new(); + for (source_id, source) in &self.sources { + guids + .iter() + .filter(|guid| source.has_function_with_guid(guid)) + .for_each(|guid| result.entry(*guid).or_default().push(*source_id)); + } + Ok(result) + } + + fn functions_with_guid( + &self, + _target: &Target, + source: &SourceId, + guid: &FunctionGUID, + ) -> ContainerResult<Vec<Function>> { + let memory_source = self + .sources + .get(source) + .ok_or(ContainerError::SourceNotFound(*source))?; + Ok(memory_source.functions_with_guid(guid)) + } +} + +impl Display for MemoryContainer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("MemoryContainer") + } +} + +/// An in-memory store of functions. +/// +/// This is typically an overlay on top of a container source. +#[derive(Eq, PartialEq, Debug, Clone)] +pub struct MemorySource { + pub writable: bool, + pub functions: HashMap<FunctionGUID, Vec<Function>>, + pub types: HashMap<TypeGUID, Type>, + pub named_types: HashMap<String, Vec<TypeGUID>>, +} + +impl MemorySource { + pub fn type_guids_with_name(&self, name: &str) -> Vec<TypeGUID> { + // TODO: The function here is a little goofy. + // TODO: This is cloned. + self.named_types.get(name).cloned().unwrap_or_default() + } + + pub fn type_with_guid(&self, guid: &TypeGUID) -> Option<Type> { + // TODO: This is cloned. + self.types.get(guid).cloned() + } + + pub fn functions_with_guid(&self, guid: &FunctionGUID) -> Vec<Function> { + // TODO: The function here is a little goofy. + // TODO: This is cloned. + self.functions.get(guid).cloned().unwrap_or_default() + } + + pub fn has_type_with_guid(&self, guid: &TypeGUID) -> bool { + self.type_with_guid(guid).is_some() + } + + pub fn has_function_with_guid(&self, guid: &FunctionGUID) -> bool { + !self.functions_with_guid(guid).is_empty() + } +} + +impl Default for MemorySource { + fn default() -> Self { + Self { + writable: true, + functions: HashMap::new(), + types: HashMap::new(), + named_types: HashMap::new(), + } + } +} diff --git a/plugins/warp/src/container/network.rs b/plugins/warp/src/container/network.rs new file mode 100644 index 00000000..ffbe6108 --- /dev/null +++ b/plugins/warp/src/container/network.rs @@ -0,0 +1,13 @@ +pub struct NetworkContainer {} + +// TODO: The container is populated as the user is navigating a binary +// TODO: We need to have a few helper functions here to post and pull +// TODO: Then in the interface we operate off the network cache +// TODO: The network cache could just be a disk container? Or disk container sources? +// TODO: We should also store the cache on the filesystem for a certain time, will need to timestamp +// TODO: When we commit we need to actually POST i believe. +// TODO: There needs to be a setting that adjusts the sweep size of functions at the cursor. +// TODO: Probably need a callback or something to tell the network containers to refresh from the network. +// TODO: The network container should never instantiate itself, unless its gurenteed to not have any data in it? + +// TODO: Need to PUSH chunks and PULL chunks diff --git a/plugins/warp/src/convert.rs b/plugins/warp/src/convert.rs index b6c2f7b4..8272768b 100644 --- a/plugins/warp/src/convert.rs +++ b/plugins/warp/src/convert.rs @@ -1,678 +1,54 @@ -use std::collections::HashSet; +pub mod symbol; +pub mod types; -use binaryninja::architecture::Architecture as BNArchitecture; -use binaryninja::architecture::ArchitectureExt; -use binaryninja::binary_view::{BinaryView, BinaryViewExt}; -use binaryninja::calling_convention::CoreCallingConvention as BNCallingConvention; -use binaryninja::confidence::{Conf as BNConf, MAX_CONFIDENCE}; -use binaryninja::rc::Ref as BNRef; -use binaryninja::symbol::{Symbol as BNSymbol, SymbolType as BNSymbolType}; -use binaryninja::types::{ - BaseStructure as BNBaseStructure, EnumerationBuilder as BNEnumerationBuilder, - FunctionParameter as BNFunctionParameter, MemberAccess as BNMemberAccess, MemberAccess, - MemberScope as BNMemberScope, NamedTypeReference, NamedTypeReference as BNNamedTypeReference, - NamedTypeReferenceClass, StructureBuilder as BNStructureBuilder, - StructureMember as BNStructureMember, -}; -use binaryninja::types::{ - StructureType as BNStructureType, Type as BNType, TypeClass as BNTypeClass, -}; +use binaryninja::function::Comment as BNComment; +use binaryninja::function::Function as BNFunction; +use binaryninja::platform::Platform; +use binaryninja::rc::Ref; +pub use symbol::*; +pub use types::*; +use warp::signature::comment::FunctionComment; +use warp::target::Target; -use crate::cache::{cached_type_reference, TypeRefID}; -use warp::r#type::class::array::ArrayModifiers; -use warp::r#type::class::function::{Location, RegisterLocation}; -use warp::r#type::class::pointer::PointerAddressing; -use warp::r#type::class::structure::StructureMemberModifiers; -use warp::r#type::class::{ - ArrayClass, BooleanClass, CallingConvention, CharacterClass, EnumerationClass, - EnumerationMember, FloatClass, FunctionClass, FunctionMember, IntegerClass, PointerClass, - ReferrerClass, StructureClass, StructureMember, TypeClass, -}; -use warp::r#type::Type; -use warp::symbol::class::SymbolClass; -use warp::symbol::{Symbol, SymbolModifiers}; - -pub fn from_bn_symbol(raw_symbol: &BNSymbol) -> Symbol { - // TODO: Use this? - let _is_export = raw_symbol.external(); - let symbol_name = raw_symbol.raw_name().to_string_lossy().to_string(); - match raw_symbol.sym_type() { - BNSymbolType::ImportAddress => { - Symbol::new( - symbol_name, - SymbolClass::Function, - // TODO: External = symbolic i guess - SymbolModifiers::External, - ) - } - BNSymbolType::Data => { - Symbol::new( - symbol_name, - // TODO: Data? - SymbolClass::Data, - SymbolModifiers::default(), - ) - } - BNSymbolType::Symbolic => { - Symbol::new( - symbol_name, - SymbolClass::Function, - // TODO: External = symbolic i guess - SymbolModifiers::External, - ) - } - BNSymbolType::LocalLabel => { - // TODO: This is a placeholder for another symbol. - Symbol::new(symbol_name, SymbolClass::Data, SymbolModifiers::External) - } - BNSymbolType::External => Symbol::new( - symbol_name, - // TODO: External data? - SymbolClass::Function, - SymbolModifiers::External, - ), - BNSymbolType::ImportedData => { - Symbol::new(symbol_name, SymbolClass::Data, SymbolModifiers::External) - } - BNSymbolType::LibraryFunction | BNSymbolType::Function => Symbol::new( - symbol_name, - SymbolClass::Function, - SymbolModifiers::default(), - ), - BNSymbolType::ImportedFunction => Symbol::new( - symbol_name, - SymbolClass::Function, - // TODO: Exported? - SymbolModifiers::External, - ), - } -} - -pub fn to_bn_symbol_at_address(view: &BinaryView, symbol: &Symbol, addr: u64) -> BNRef<BNSymbol> { - let is_external = symbol.modifiers.contains(SymbolModifiers::External); - let _is_exported = symbol.modifiers.contains(SymbolModifiers::Exported); - let symbol_type = match symbol.class { - SymbolClass::Function if is_external => BNSymbolType::ImportedFunction, - // TODO: We should instead make it a Function, however due to the nature of the imports we are setting them to library for now. - SymbolClass::Function => BNSymbolType::LibraryFunction, - SymbolClass::Data if is_external => BNSymbolType::ImportedData, - SymbolClass::Data => BNSymbolType::Data, - }; - let raw_name = symbol.name.as_str(); - let mut symbol_builder = BNSymbol::builder(symbol_type, &symbol.name, addr); - // Demangle symbol name (short is with simplifications). - if let Some(arch) = view.default_arch() { - if let Some((full_name, _)) = - binaryninja::demangle::demangle_generic(&arch, raw_name, Some(view), false) - { - symbol_builder = symbol_builder.full_name(full_name); - } - if let Some((short_name, _)) = - binaryninja::demangle::demangle_generic(&arch, raw_name, Some(view), false) - { - symbol_builder = symbol_builder.short_name(short_name); - } - } - symbol_builder.create() -} - -pub fn from_bn_type(view: &BinaryView, raw_ty: &BNType, confidence: u8) -> Type { - from_bn_type_internal(view, &mut HashSet::new(), raw_ty, confidence) -} - -pub fn from_bn_type_internal( - view: &BinaryView, - visited_refs: &mut HashSet<TypeRefID>, - raw_ty: &BNType, - confidence: u8, -) -> Type { - let bytes_to_bits = |val| val * 8; - let raw_ty_bit_width = bytes_to_bits(raw_ty.width()); - let type_class = match raw_ty.type_class() { - BNTypeClass::VoidTypeClass => TypeClass::Void, - BNTypeClass::BoolTypeClass => { - let bool_class = BooleanClass { width: None }; - TypeClass::Boolean(bool_class) - } - BNTypeClass::IntegerTypeClass => { - let signed = raw_ty.is_signed().contents; - let width = Some(raw_ty_bit_width as u16); - if signed && width == Some(8) { - // NOTE: if its an i8, its a char. - let char_class = CharacterClass { width: None }; - TypeClass::Character(char_class) - } else { - let int_class = IntegerClass { width, signed }; - TypeClass::Integer(int_class) - } - } - BNTypeClass::FloatTypeClass => { - let float_class = FloatClass { - width: Some(raw_ty_bit_width as u16), - }; - TypeClass::Float(float_class) - } - // TODO: Union????? - BNTypeClass::StructureTypeClass => { - let raw_struct = raw_ty.get_structure().unwrap(); - - let mut members = raw_struct - .members() - .into_iter() - .map(|raw_member| { - let bit_offset = bytes_to_bits(raw_member.offset); - let mut modifiers = StructureMemberModifiers::empty(); - // If this member is not public mark it as internal. - modifiers.set( - StructureMemberModifiers::Internal, - !matches!(raw_member.access, MemberAccess::PublicAccess), - ); - StructureMember { - name: Some(raw_member.name), - offset: bit_offset, - ty: from_bn_type_internal( - view, - visited_refs, - &raw_member.ty.contents, - raw_member.ty.confidence, - ), - modifiers, - } - }) - .collect::<Vec<_>>(); - - // Add base structures as flattened members - let base_to_member_iter = raw_struct.base_structures().into_iter().map(|base_struct| { - let bit_offset = bytes_to_bits(base_struct.offset); - let mut modifiers = StructureMemberModifiers::empty(); - modifiers.set(StructureMemberModifiers::Flattened, true); - let base_struct_ty = from_bn_type_internal( - view, - visited_refs, - &BNType::named_type(&base_struct.ty), - MAX_CONFIDENCE, - ); - StructureMember { - name: base_struct_ty.name.to_owned(), - offset: bit_offset, - ty: base_struct_ty, - modifiers, - } - }); - members.extend(base_to_member_iter); - - // TODO: Check if union - let struct_class = StructureClass::new(members); - TypeClass::Structure(struct_class) - } - BNTypeClass::EnumerationTypeClass => { - let raw_enum = raw_ty.get_enumeration().unwrap(); - - let enum_ty_signed = raw_ty.is_signed().contents; - let enum_ty = Type::builder::<String, _>() - .class(TypeClass::Integer(IntegerClass { - width: Some(raw_ty_bit_width as u16), - signed: enum_ty_signed, - })) - .build(); - - let members = raw_enum - .members() - .into_iter() - .map(|raw_member| EnumerationMember { - name: Some(raw_member.name), - constant: raw_member.value, - }) - .collect(); - - let enum_class = EnumerationClass::new(enum_ty, members); - TypeClass::Enumeration(enum_class) - } - BNTypeClass::PointerTypeClass => { - let raw_child_ty = raw_ty.target().unwrap(); - let ptr_class = PointerClass { - width: Some(raw_ty_bit_width as u16), - child_type: from_bn_type_internal( - view, - visited_refs, - &raw_child_ty.contents, - raw_child_ty.confidence, - ), - // TODO: Handle addressing. - addressing: PointerAddressing::Absolute, - }; - TypeClass::Pointer(ptr_class) - } - BNTypeClass::ArrayTypeClass => { - let length = raw_ty.count(); - let raw_member_ty = raw_ty.element_type().unwrap(); - let array_class = ArrayClass { - length: Some(length), - member_type: from_bn_type_internal( - view, - visited_refs, - &raw_member_ty.contents, - raw_member_ty.confidence, - ), - modifiers: ArrayModifiers::empty(), - }; - TypeClass::Array(array_class) - } - BNTypeClass::FunctionTypeClass => { - let in_members = raw_ty - .parameters() - .unwrap() - .into_iter() - .map(|raw_member| { - // TODO: Location... - let _location = Location::Register(RegisterLocation); - FunctionMember { - name: Some(raw_member.name), - ty: from_bn_type_internal( - view, - visited_refs, - &raw_member.ty.contents, - raw_member.ty.confidence, - ), - // TODO: Just omit location for now? - // TODO: Location should be optional... - locations: vec![], - } - }) - .collect(); - - let mut out_members = Vec::new(); - if let Some(return_ty) = raw_ty.return_value() { - out_members.push(FunctionMember { - name: None, - ty: from_bn_type_internal( - view, - visited_refs, - &return_ty.contents, - return_ty.confidence, - ), - locations: vec![], - }); - } - - let calling_convention = raw_ty - .calling_convention() - .map(|bn_cc| from_bn_calling_convention(bn_cc.contents)); - - let func_class = FunctionClass { - calling_convention, - in_members, - out_members, - }; - TypeClass::Function(func_class) - } - BNTypeClass::VarArgsTypeClass => TypeClass::Void, - BNTypeClass::ValueTypeClass => { - // What the is this. - TypeClass::Void - } - BNTypeClass::NamedTypeReferenceClass => { - let raw_ntr = raw_ty.get_named_type_reference().unwrap(); - let ref_id = TypeRefID::from(raw_ntr.as_ref()); - let mut ref_class = ReferrerClass::new(None, Some(raw_ntr.name().to_string())); - if visited_refs.insert(ref_id) { - // This ntr is NOT self-referential, meaning we can deduce a type GUID. - if let Some(computed_ty) = cached_type_reference(view, visited_refs, &raw_ntr) { - // NOTE: The GUID here must always equal the same for any given type for this to work effectively. - ref_class.guid = Some(computed_ty.guid); - } - visited_refs.remove(&ref_id); - } - TypeClass::Referrer(ref_class) - } - BNTypeClass::WideCharTypeClass => { - let char_class = CharacterClass { - width: Some(raw_ty_bit_width as u16), - }; - TypeClass::Character(char_class) - } - }; - - let name = raw_ty.registered_name().map(|n| n.name().to_string()); - - Type { - name, - class: Box::new(type_class), - confidence, - // TODO: Fill these out... - modifiers: vec![], - alignment: Default::default(), - // TODO: Filling this out is... weird. - // TODO: we _do_ want this for networked types (this is the only way we can update type is if we fill this out) - ancestors: vec![], +pub fn bn_comment_to_comment(func: &BNFunction, bn_comment: BNComment) -> FunctionComment { + let offset = (bn_comment.addr as i64) - (func.start() as i64); + FunctionComment { + offset, + text: bn_comment.comment, } } -pub fn from_bn_calling_convention(raw_cc: BNRef<BNCallingConvention>) -> CallingConvention { - // NOTE: Currently calling convention just stores the name. - CallingConvention::new(raw_cc.name().as_str()) -} - -pub fn to_bn_calling_convention<A: BNArchitecture>( - arch: &A, - calling_convention: &CallingConvention, -) -> BNRef<BNCallingConvention> { - for cc in &arch.calling_conventions() { - if cc.name().as_str() == calling_convention.name { - return cc.clone(); - } +pub fn comment_to_bn_comment(func: &BNFunction, comment: FunctionComment) -> BNComment { + BNComment { + addr: comment + .offset + .checked_add_unsigned(func.start()) + .unwrap_or_default() as u64, + comment: comment.text, } - arch.get_default_calling_convention().unwrap() } -pub fn to_bn_type<A: BNArchitecture>(arch: &A, ty: &Type) -> BNRef<BNType> { - let bits_to_bytes = |val: u64| (val / 8); - let addr_size = arch.address_size() as u64; - match ty.class.as_ref() { - TypeClass::Void => BNType::void(), - TypeClass::Boolean(_) => BNType::bool(), - TypeClass::Integer(c) => { - let width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(4); - BNType::int(width as usize, c.signed) - } - TypeClass::Character(c) => match c.width { - Some(w) => BNType::wide_char(bits_to_bytes(w as _) as usize), - None => BNType::char(), - }, - TypeClass::Float(c) => { - let width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(4); - BNType::float(width as usize) +pub fn platform_to_target(platform: &Platform) -> Target { + let arch_name = platform.arch().name(); + let platform_name = platform.name(); + // We do not want to populate the platform if we are actually only the architecture. + if arch_name == platform_name { + Target { + architecture: Some(arch_name), + platform: None, } - TypeClass::Pointer(ref c) => { - let child_type = to_bn_type(arch, &c.child_type); - let ptr_width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(addr_size); - // TODO: Child type confidence - let constant = ty.is_const(); - let volatile = ty.is_volatile(); - // TODO: If the pointer is to a null terminated array of chars, make it a pointer to char - // TODO: Addressing mode - BNType::pointer_of_width(&child_type, ptr_width as usize, constant, volatile, None) - } - TypeClass::Array(c) => { - let member_type = to_bn_type(arch, &c.member_type); - // TODO: How to handle DST array (length is None) - BNType::array(&member_type, c.length.unwrap_or(0)) - } - TypeClass::Structure(c) => { - let mut builder = BNStructureBuilder::new(); - // TODO: Structure type class? - // TODO: Alignment - // TODO: Other modifiers? - let mut base_structs: Vec<BNBaseStructure> = Vec::new(); - for member in &c.members { - let member_type = BNConf::new(to_bn_type(arch, &member.ty), u8::MAX); - let member_name = member.name.to_owned().unwrap_or("field_OFFSET".into()); - let member_offset = bits_to_bytes(member.offset); - let member_access = if member - .modifiers - .contains(StructureMemberModifiers::Internal) - { - BNMemberAccess::PrivateAccess - } else { - BNMemberAccess::PublicAccess - }; - // TODO: Member scope - let member_scope = BNMemberScope::NoScope; - if member - .modifiers - .contains(StructureMemberModifiers::Flattened) - { - // Add member as a base structure to inherit its fields. - match member.ty.class.as_ref() { - TypeClass::Referrer(c) => { - // We only support base structures with a referrer right now. - let base_struct_ntr_name = - c.name.to_owned().unwrap_or("base_UNKNOWN".into()); - let base_struct_ntr = match c.guid { - Some(guid) => BNNamedTypeReference::new_with_id( - NamedTypeReferenceClass::UnknownNamedTypeClass, - &guid.to_string(), - base_struct_ntr_name, - ), - None => BNNamedTypeReference::new( - NamedTypeReferenceClass::UnknownNamedTypeClass, - base_struct_ntr_name, - ), - }; - base_structs.push(BNBaseStructure::new( - base_struct_ntr, - member_offset, - member.ty.size().unwrap_or(0), - )) - } - _ => { - log::error!( - "Adding base {:?} with invalid ty: {:?}", - ty.name, - member.ty - ); - } - } - } else { - builder.insert_member( - BNStructureMember::new( - member_type, - member_name, - member_offset, - member_access, - member_scope, - ), - false, - ); - } - } - builder.base_structures(&base_structs); - BNType::structure(&builder.finalize()) - } - TypeClass::Enumeration(c) => { - let mut builder = BNEnumerationBuilder::new(); - for member in &c.members { - // TODO: Add default name? - let member_name = member.name.to_owned().unwrap_or("enum_VAL".into()); - let member_value = member.constant; - builder.insert(&member_name, member_value); - } - // TODO: Warn if enumeration has no size. - let width = bits_to_bytes(c.member_type.size().unwrap()) as usize; - let signed = matches!(*c.member_type.class, TypeClass::Integer(c) if c.signed); - // TODO: Passing width like this is weird. - BNType::enumeration(&builder.finalize(), width.try_into().unwrap(), signed) - } - TypeClass::Union(c) => { - let mut builder = BNStructureBuilder::new(); - builder.structure_type(BNStructureType::UnionStructureType); - for member in &c.members { - let member_type = BNConf::new(to_bn_type(arch, &member.ty), u8::MAX); - let member_name = member.name.to_owned(); - // TODO: Member access - let member_access = BNMemberAccess::PublicAccess; - // TODO: Member scope - let member_scope = BNMemberScope::NoScope; - let structure_member = BNStructureMember::new( - member_type, - member_name, - 0, // Union members all exist at 0 right? - member_access, - member_scope, - ); - builder.insert_member(structure_member, false); - } - BNType::structure(&builder.finalize()) - } - TypeClass::Function(c) => { - let return_type = if !c.out_members.is_empty() { - // TODO: WTF - to_bn_type(arch, &c.out_members[0].ty) - } else { - BNType::void() - }; - let params: Vec<_> = c - .in_members - .iter() - .map(|member| { - let member_type = to_bn_type(arch, &member.ty); - let name = member.name.clone(); - // TODO: Location AND fix default param name - BNFunctionParameter::new(member_type, name.unwrap_or("param_IDK".into()), None) - }) - .collect(); - // TODO: Variable arguments - let variable_args = false; - // If we have a calling convention we run the extended function type creation. - match c.calling_convention.as_ref() { - Some(cc) => { - let calling_convention = to_bn_calling_convention(arch, cc); - BNType::function_with_opts( - &return_type, - ¶ms, - variable_args, - BNConf::new(calling_convention, u8::MAX), - BNConf::new(0, 0), - ) - } - None => BNType::function(&return_type, params, variable_args), - } - } - TypeClass::Referrer(c) => { - let ntr = match c.guid { - Some(guid) => { - let guid_str = guid.to_string(); - let ntr_name = c.name.to_owned().unwrap_or(guid_str.clone()); - NamedTypeReference::new_with_id( - NamedTypeReferenceClass::UnknownNamedTypeClass, - &guid_str, - ntr_name, - ) - } - None => match c.name.as_ref() { - Some(ntr_name) => NamedTypeReference::new( - NamedTypeReferenceClass::UnknownNamedTypeClass, - ntr_name, - ), - None => { - log::error!("Referrer with no reference! {:?}", c); - NamedTypeReference::new( - NamedTypeReferenceClass::UnknownNamedTypeClass, - "AHHHHHH", - ) - } - }, - }; - BNType::named_type(&ntr) + } else { + Target { + architecture: Some(arch_name), + platform: Some(platform_name), } } } -#[cfg(test)] -mod tests { - use super::*; - use binaryninja::binary_view::BinaryViewExt; - use binaryninja::headless::Session; - use std::path::PathBuf; - use std::sync::OnceLock; - use warp::r#type::guid::TypeGUID; - - static INIT: OnceLock<Session> = OnceLock::new(); - - fn get_session<'a>() -> &'a Session { - INIT.get_or_init(|| Session::new().expect("Failed to initialize session")) - } - - #[test] - fn type_conversion() { - let session = get_session(); - let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); - for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") { - let entry = entry.expect("Failed to read directory entry"); - let path = entry.path(); - if path.is_file() { - if let Some(bv) = session.load(path.to_str().unwrap()) { - let types_len = bv.types().len(); - let converted_types: Vec<_> = bv - .types() - .iter() - .map(|qualified_name_and_type| { - let ty = from_bn_type(&bv, &qualified_name_and_type.ty, u8::MAX); - (TypeGUID::from(&ty), ty) - }) - .collect(); - assert_eq!(types_len, converted_types.len()); - } - } - } - } - - #[ignore] - #[test] - fn check_for_leaks() { - let session = get_session(); - let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); - for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") { - let entry = entry.expect("Failed to read directory entry"); - let path = entry.path(); - if path.is_file() { - if let Some(inital_bv) = session.load(path.to_str().unwrap()) { - let types_len = inital_bv.types().len(); - let converted_types: Vec<_> = inital_bv - .types() - .iter() - .map(|t| { - let ty = from_bn_type(&inital_bv, &t.ty, u8::MAX); - (TypeGUID::from(&ty), ty) - }) - .collect(); - assert_eq!(types_len, converted_types.len()); - // Hold on to a reference to the core to prevent view getting dropped in worker thread. - let _core_ref = inital_bv - .functions() - .iter() - .next() - .map(|f| f.unresolved_stack_adjustment_graph()); - // Drop the file and view. - inital_bv.file().close(); - std::mem::drop(inital_bv); - let initial_memory_info = binaryninja::memory_info(); - if let Some(second_bv) = session.load(path.to_str().unwrap()) { - let types_len = second_bv.types().len(); - let converted_types: Vec<_> = second_bv - .types() - .iter() - .map(|t| { - let ty = from_bn_type(&second_bv, &t.ty, u8::MAX); - (TypeGUID::from(&ty), ty) - }) - .collect(); - assert_eq!(types_len, converted_types.len()); - // Hold on to a reference to the core to prevent view getting dropped in worker thread. - let _core_ref = second_bv - .functions() - .iter() - .next() - .map(|f| f.unresolved_stack_adjustment_graph()); - // Drop the file and view. - second_bv.file().close(); - std::mem::drop(second_bv); - let final_memory_info = binaryninja::memory_info(); - for info in initial_memory_info { - let initial_count = info.1; - if let Some(&final_count) = final_memory_info.get(&info.0) { - assert!( - final_count <= initial_count, - "{}: final objects {} vs initial objects {}", - info.0, - final_count, - initial_count - ); - } - } - } - } - } - } +pub fn target_to_platform(target: Target) -> Option<Ref<Platform>> { + // First try using the platform, then try using the arch. + match Platform::by_name(&target.platform.unwrap()) { + None => Platform::by_name(&target.architecture.unwrap()).map(|platform| platform), + Some(platform) => Some(platform), } } diff --git a/plugins/warp/src/convert/symbol.rs b/plugins/warp/src/convert/symbol.rs new file mode 100644 index 00000000..0107cff5 --- /dev/null +++ b/plugins/warp/src/convert/symbol.rs @@ -0,0 +1,91 @@ +use binaryninja::binary_view::{BinaryView, BinaryViewExt}; +use binaryninja::rc::Ref as BNRef; +use binaryninja::symbol::Symbol as BNSymbol; +use binaryninja::symbol::SymbolType as BNSymbolType; +use warp::symbol::{Symbol, SymbolClass, SymbolModifiers}; + +pub fn from_bn_symbol(raw_symbol: &BNSymbol) -> Symbol { + // TODO: Use this? + let _is_export = raw_symbol.external(); + let raw_symbol_name = raw_symbol.raw_name(); + let symbol_name = raw_symbol_name.to_string_lossy(); + match raw_symbol.sym_type() { + BNSymbolType::ImportAddress => { + Symbol::new( + symbol_name, + SymbolClass::Function, + // TODO: External = symbolic i guess + SymbolModifiers::External, + ) + } + BNSymbolType::Data => { + Symbol::new( + symbol_name, + // TODO: Data? + SymbolClass::Data, + SymbolModifiers::default(), + ) + } + BNSymbolType::Symbolic => { + Symbol::new( + symbol_name, + SymbolClass::Function, + // TODO: External = symbolic i guess + SymbolModifiers::External, + ) + } + BNSymbolType::LocalLabel => { + // TODO: This is a placeholder for another symbol. + Symbol::new(symbol_name, SymbolClass::Data, SymbolModifiers::External) + } + BNSymbolType::External => Symbol::new( + symbol_name, + // TODO: External data? + SymbolClass::Function, + SymbolModifiers::External, + ), + BNSymbolType::ImportedData => { + Symbol::new(symbol_name, SymbolClass::Data, SymbolModifiers::External) + } + BNSymbolType::LibraryFunction | BNSymbolType::Function => Symbol::new( + symbol_name, + SymbolClass::Function, + SymbolModifiers::default(), + ), + BNSymbolType::ImportedFunction => Symbol::new( + symbol_name, + SymbolClass::Function, + // TODO: Exported? + SymbolModifiers::External, + ), + } +} + +pub fn to_bn_symbol_at_address(view: &BinaryView, symbol: &Symbol, addr: u64) -> BNRef<BNSymbol> { + let is_external = symbol.modifiers.contains(SymbolModifiers::External); + let _is_exported = symbol.modifiers.contains(SymbolModifiers::Exported); + let symbol_type = match symbol.class { + SymbolClass::Function if is_external => BNSymbolType::ImportedFunction, + // TODO: We should instead make it a Function, however due to the nature of the imports we are setting them to library for now. + SymbolClass::Function => BNSymbolType::LibraryFunction, + SymbolClass::Data if is_external => BNSymbolType::ImportedData, + SymbolClass::Data => BNSymbolType::Data, + _ => BNSymbolType::Data, + }; + let raw_name = symbol.name.as_str(); + let mut symbol_builder = BNSymbol::builder(symbol_type, &symbol.name, addr); + // Demangle symbol name (short is with simplifications). + if let Some(arch) = view.default_arch() { + if let Some((full_name, _)) = + binaryninja::demangle::demangle_generic(&arch, raw_name, Some(view), false) + { + symbol_builder = symbol_builder.full_name(full_name); + } + if let Some((short_name, _)) = + binaryninja::demangle::demangle_generic(&arch, raw_name, Some(view), false) + { + symbol_builder = symbol_builder.short_name(short_name); + } + } + symbol_builder.create() +} diff --git a/plugins/warp/src/convert/types.rs b/plugins/warp/src/convert/types.rs new file mode 100644 index 00000000..1dfe5fa9 --- /dev/null +++ b/plugins/warp/src/convert/types.rs @@ -0,0 +1,516 @@ +use crate::cache::{cached_type_reference, TypeRefID}; +use binaryninja::architecture::Architecture as BNArchitecture; +use binaryninja::architecture::ArchitectureExt; +use binaryninja::binary_view::BinaryView; +use binaryninja::calling_convention::CoreCallingConvention as BNCallingConvention; +use binaryninja::confidence::Conf as BNConf; +use binaryninja::confidence::MAX_CONFIDENCE; +use binaryninja::rc::Ref as BNRef; +use binaryninja::types::BaseStructure as BNBaseStructure; +use binaryninja::types::EnumerationBuilder as BNEnumerationBuilder; +use binaryninja::types::FunctionParameter as BNFunctionParameter; +use binaryninja::types::MemberAccess as BNMemberAccess; +use binaryninja::types::MemberScope as BNMemberScope; +use binaryninja::types::NamedTypeReference as BNNamedTypeReference; +use binaryninja::types::StructureBuilder as BNStructureBuilder; +use binaryninja::types::StructureMember as BNStructureMember; +use binaryninja::types::StructureType as BNStructureType; +use binaryninja::types::Type as BNType; +use binaryninja::types::TypeClass as BNTypeClass; +use binaryninja::types::{NamedTypeReference, NamedTypeReferenceClass}; +use std::collections::HashSet; +use warp::r#type::class::array::ArrayModifiers; +use warp::r#type::class::pointer::PointerAddressing; +use warp::r#type::class::structure::StructureMemberModifiers; +use warp::r#type::class::{ + ArrayClass, BooleanClass, CallingConvention, CharacterClass, EnumerationClass, + EnumerationMember, FloatClass, FunctionClass, FunctionMember, IntegerClass, PointerClass, + ReferrerClass, StructureClass, StructureMember, TypeClass, +}; +use warp::r#type::{Type, TypeModifiers}; + +pub fn from_bn_type(view: &BinaryView, raw_ty: &BNType, confidence: u8) -> Type { + from_bn_type_internal(view, &mut HashSet::new(), raw_ty, confidence) +} + +pub fn from_bn_type_internal( + view: &BinaryView, + visited_refs: &mut HashSet<TypeRefID>, + raw_ty: &BNType, + confidence: u8, +) -> Type { + let bytes_to_bits = |val| val * 8; + let raw_ty_bit_width = bytes_to_bits(raw_ty.width()); + let type_class = match raw_ty.type_class() { + BNTypeClass::VoidTypeClass => TypeClass::Void, + BNTypeClass::BoolTypeClass => { + let bool_class = BooleanClass { width: None }; + TypeClass::Boolean(bool_class) + } + BNTypeClass::IntegerTypeClass => { + let signed = raw_ty.is_signed().contents; + let width = Some(raw_ty_bit_width as u16); + if signed && width == Some(8) { + // NOTE: if its an i8, its a char. + let char_class = CharacterClass { width: None }; + TypeClass::Character(char_class) + } else { + let int_class = IntegerClass { width, signed }; + TypeClass::Integer(int_class) + } + } + BNTypeClass::FloatTypeClass => { + let float_class = FloatClass { + width: Some(raw_ty_bit_width as u16), + }; + TypeClass::Float(float_class) + } + // TODO: Union????? + BNTypeClass::StructureTypeClass => { + let raw_struct = raw_ty.get_structure().unwrap(); + + let mut members = raw_struct + .members() + .into_iter() + .map(|raw_member| { + let bit_offset = bytes_to_bits(raw_member.offset); + let mut modifiers = StructureMemberModifiers::empty(); + // If this member is not public mark it as internal. + modifiers.set( + StructureMemberModifiers::Internal, + !matches!(raw_member.access, BNMemberAccess::PublicAccess), + ); + StructureMember { + name: Some(raw_member.name), + offset: bit_offset, + ty: Box::new(from_bn_type_internal( + view, + visited_refs, + &raw_member.ty.contents, + raw_member.ty.confidence, + )), + modifiers, + } + }) + .collect::<Vec<_>>(); + + // Add base structures as flattened members + let base_to_member_iter = raw_struct.base_structures().into_iter().map(|base_struct| { + let bit_offset = bytes_to_bits(base_struct.offset); + let mut modifiers = StructureMemberModifiers::empty(); + modifiers.set(StructureMemberModifiers::Flattened, true); + let base_struct_ty = from_bn_type_internal( + view, + visited_refs, + &BNType::named_type(&base_struct.ty), + MAX_CONFIDENCE, + ); + StructureMember { + name: base_struct_ty.name.to_owned(), + offset: bit_offset, + ty: Box::new(base_struct_ty), + modifiers, + } + }); + members.extend(base_to_member_iter); + + // TODO: Check if union + let struct_class = StructureClass::new(members); + TypeClass::Structure(struct_class) + } + BNTypeClass::EnumerationTypeClass => { + let raw_enum = raw_ty.get_enumeration().unwrap(); + + let enum_ty_signed = raw_ty.is_signed().contents; + let enum_ty = Type::builder::<String, _>() + .class(TypeClass::Integer(IntegerClass { + width: Some(raw_ty_bit_width as u16), + signed: enum_ty_signed, + })) + .build(); + + let members = raw_enum + .members() + .into_iter() + .map(|raw_member| EnumerationMember { + name: Some(raw_member.name), + constant: raw_member.value, + }) + .collect(); + + let enum_class = EnumerationClass::new(enum_ty, members); + TypeClass::Enumeration(enum_class) + } + BNTypeClass::PointerTypeClass => { + let raw_child_ty = raw_ty.target().unwrap(); + let ptr_class = PointerClass { + width: Some(raw_ty_bit_width as u16), + child_type: Box::new(from_bn_type_internal( + view, + visited_refs, + &raw_child_ty.contents, + raw_child_ty.confidence, + )), + // TODO: Handle addressing. + addressing: PointerAddressing::Absolute, + }; + TypeClass::Pointer(ptr_class) + } + BNTypeClass::ArrayTypeClass => { + let length = raw_ty.count(); + let raw_member_ty = raw_ty.element_type().unwrap(); + let array_class = ArrayClass { + length: Some(length), + member_type: Box::new(from_bn_type_internal( + view, + visited_refs, + &raw_member_ty.contents, + raw_member_ty.confidence, + )), + modifiers: ArrayModifiers::empty(), + }; + TypeClass::Array(array_class) + } + BNTypeClass::FunctionTypeClass => { + let in_members = raw_ty + .parameters() + .unwrap() + .into_iter() + .map(|raw_member| { + // TODO: Location... + // let _location = Location::Register(RegisterLocation); + FunctionMember { + name: Some(raw_member.name), + ty: Box::new(from_bn_type_internal( + view, + visited_refs, + &raw_member.ty.contents, + raw_member.ty.confidence, + )), + // TODO: Just omit location for now? + // TODO: Location should be optional... + location: None, + } + }) + .collect(); + + let mut out_members = Vec::new(); + if let Some(return_ty) = raw_ty.return_value() { + out_members.push(FunctionMember { + name: None, + ty: Box::new(from_bn_type_internal( + view, + visited_refs, + &return_ty.contents, + return_ty.confidence, + )), + location: None, + }); + } + + let calling_convention = raw_ty + .calling_convention() + .map(|bn_cc| from_bn_calling_convention(bn_cc.contents)); + + let func_class = FunctionClass { + calling_convention, + in_members, + out_members, + }; + TypeClass::Function(func_class) + } + BNTypeClass::VarArgsTypeClass => TypeClass::Void, + BNTypeClass::ValueTypeClass => { + // What the is this. + TypeClass::Void + } + BNTypeClass::NamedTypeReferenceClass => { + let raw_ntr = raw_ty.get_named_type_reference().unwrap(); + let ref_id = TypeRefID::from(raw_ntr.as_ref()); + let mut ref_class = ReferrerClass::new(None, Some(raw_ntr.name().to_string())); + if visited_refs.insert(ref_id) { + // This ntr is NOT self-referential, meaning we can deduce a type GUID. + if let Some(computed_ty) = cached_type_reference(view, visited_refs, &raw_ntr) { + // NOTE: The GUID here must always equal the same for any given type for this to work effectively. + ref_class.guid = Some(computed_ty.guid); + } + visited_refs.remove(&ref_id); + } + TypeClass::Referrer(ref_class) + } + BNTypeClass::WideCharTypeClass => { + let char_class = CharacterClass { + width: Some(raw_ty_bit_width as u16), + }; + TypeClass::Character(char_class) + } + }; + + let name = raw_ty.registered_name().map(|n| n.name().to_string()); + + Type { + name, + class: type_class, + confidence, + // TODO: Fill these out... + modifiers: TypeModifiers::empty(), + metadata: vec![], + alignment: Default::default(), + // TODO: Filling this out is... weird. + // TODO: we _do_ want this for networked types (this is the only way we can update type is if we fill this out) + ancestors: vec![], + } +} + +pub fn from_bn_calling_convention(raw_cc: BNRef<BNCallingConvention>) -> CallingConvention { + // NOTE: Currently calling convention just stores the name. + CallingConvention::new(raw_cc.name().as_str()) +} + +pub fn to_bn_calling_convention<A: BNArchitecture>( + arch: &A, + calling_convention: &CallingConvention, +) -> BNRef<BNCallingConvention> { + for cc in &arch.calling_conventions() { + if cc.name().as_str() == calling_convention.name { + return cc.clone(); + } + } + arch.get_default_calling_convention().unwrap() +} + +pub fn to_bn_type<A: BNArchitecture>(arch: &A, ty: &Type) -> BNRef<BNType> { + let bits_to_bytes = |val: u64| (val / 8); + let addr_size = arch.address_size() as u64; + match &ty.class { + TypeClass::Void => BNType::void(), + TypeClass::Boolean(_) => BNType::bool(), + TypeClass::Integer(c) => { + let width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(4); + BNType::int(width as usize, c.signed) + } + TypeClass::Character(c) => match c.width { + Some(w) => BNType::wide_char(bits_to_bytes(w as _) as usize), + None => BNType::char(), + }, + TypeClass::Float(c) => { + let width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(4); + BNType::float(width as usize) + } + TypeClass::Pointer(ref c) => { + let child_type = to_bn_type(arch, &c.child_type); + let ptr_width = c.width.map(|w| bits_to_bytes(w as _)).unwrap_or(addr_size); + // TODO: Child type confidence + let constant = ty.is_const(); + let volatile = ty.is_volatile(); + // TODO: If the pointer is to a null terminated array of chars, make it a pointer to char + // TODO: Addressing mode + BNType::pointer_of_width(&child_type, ptr_width as usize, constant, volatile, None) + } + TypeClass::Array(c) => { + let member_type = to_bn_type(arch, &c.member_type); + // TODO: How to handle DST array (length is None) + BNType::array(&member_type, c.length.unwrap_or(0)) + } + TypeClass::Structure(c) => { + let mut builder = BNStructureBuilder::new(); + // TODO: Structure type class? + // TODO: Alignment + // TODO: Other modifiers? + let mut base_structs: Vec<BNBaseStructure> = Vec::new(); + for member in &c.members { + let member_type = BNConf::new(to_bn_type(arch, &member.ty), u8::MAX); + let member_name = member.name.to_owned().unwrap_or("field_OFFSET".into()); + let member_offset = bits_to_bytes(member.offset); + let member_access = if member + .modifiers + .contains(StructureMemberModifiers::Internal) + { + BNMemberAccess::PrivateAccess + } else { + BNMemberAccess::PublicAccess + }; + // TODO: Member scope + let member_scope = BNMemberScope::NoScope; + if member + .modifiers + .contains(StructureMemberModifiers::Flattened) + { + // Add member as a base structure to inherit its fields. + match &member.ty.class { + TypeClass::Referrer(c) => { + // We only support base structures with a referrer right now. + let base_struct_ntr_name = + c.name.to_owned().unwrap_or("base_UNKNOWN".into()); + let base_struct_ntr = match c.guid { + Some(guid) => BNNamedTypeReference::new_with_id( + NamedTypeReferenceClass::UnknownNamedTypeClass, + &guid.to_string(), + base_struct_ntr_name, + ), + None => BNNamedTypeReference::new( + NamedTypeReferenceClass::UnknownNamedTypeClass, + base_struct_ntr_name, + ), + }; + base_structs.push(BNBaseStructure::new( + base_struct_ntr, + member_offset, + member.ty.size().unwrap_or(0), + )) + } + _ => { + log::error!( + "Adding base {:?} with invalid ty: {:?}", + ty.name, + member.ty + ); + } + } + } else { + builder.insert_member( + BNStructureMember::new( + member_type, + member_name, + member_offset, + member_access, + member_scope, + ), + false, + ); + } + } + builder.base_structures(&base_structs); + BNType::structure(&builder.finalize()) + } + TypeClass::Enumeration(c) => { + let mut builder = BNEnumerationBuilder::new(); + for member in &c.members { + // TODO: Add default name? + let member_name = member.name.to_owned().unwrap_or("enum_VAL".into()); + let member_value = member.constant; + builder.insert(&member_name, member_value); + } + // TODO: Warn if enumeration has no size. + let width = bits_to_bytes(c.member_type.size().unwrap()) as usize; + let signed = matches!(c.member_type.class, TypeClass::Integer(c) if c.signed); + // TODO: Passing width like this is weird. + BNType::enumeration(&builder.finalize(), width.try_into().unwrap(), signed) + } + TypeClass::Union(c) => { + let mut builder = BNStructureBuilder::new(); + builder.structure_type(BNStructureType::UnionStructureType); + for member in &c.members { + let member_type = BNConf::new(to_bn_type(arch, &member.ty), u8::MAX); + let member_name = member.name.to_owned(); + // TODO: Member access + let member_access = BNMemberAccess::PublicAccess; + // TODO: Member scope + let member_scope = BNMemberScope::NoScope; + let structure_member = BNStructureMember::new( + member_type, + member_name, + 0, // Union members all exist at 0 right? + member_access, + member_scope, + ); + builder.insert_member(structure_member, false); + } + BNType::structure(&builder.finalize()) + } + TypeClass::Function(c) => { + let return_type = if !c.out_members.is_empty() { + // TODO: WTF + to_bn_type(arch, &c.out_members[0].ty) + } else { + BNType::void() + }; + let params: Vec<_> = c + .in_members + .iter() + .map(|member| { + let member_type = to_bn_type(arch, &member.ty); + let name = member.name.clone(); + // TODO: Location AND fix default param name + BNFunctionParameter::new(member_type, name.unwrap_or("param_IDK".into()), None) + }) + .collect(); + // TODO: Variable arguments + let variable_args = false; + // If we have a calling convention we run the extended function type creation. + match c.calling_convention.as_ref() { + Some(cc) => { + let calling_convention = to_bn_calling_convention(arch, cc); + BNType::function_with_opts( + &return_type, + ¶ms, + variable_args, + BNConf::new(calling_convention, u8::MAX), + BNConf::new(0, 0), + ) + } + None => BNType::function(&return_type, params, variable_args), + } + } + TypeClass::Referrer(c) => { + let ntr = match c.guid { + Some(guid) => { + let guid_str = guid.to_string(); + let ntr_name = c.name.to_owned().unwrap_or(guid_str.clone()); + NamedTypeReference::new_with_id( + NamedTypeReferenceClass::TypedefNamedTypeClass, + &guid_str, + ntr_name, + ) + } + None => match c.name.as_ref() { + Some(ntr_name) => NamedTypeReference::new( + NamedTypeReferenceClass::UnknownNamedTypeClass, + ntr_name, + ), + None => { + log::error!("Referrer with no reference! {:?}", c); + NamedTypeReference::new( + NamedTypeReferenceClass::UnknownNamedTypeClass, + "AHHHHHH", + ) + } + }, + }; + BNType::named_type(&ntr) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use binaryninja::binary_view::BinaryViewExt; + use binaryninja::headless::Session; + use std::path::PathBuf; + use warp::r#type::guid::TypeGUID; + + #[test] + fn type_conversion() { + let session = Session::new().expect("Failed to initialize session"); + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") { + let entry = entry.expect("Failed to read directory entry"); + let path = entry.path(); + if path.is_file() { + if let Some(bv) = session.load(path) { + let types_len = bv.types().len(); + let converted_types: Vec<_> = bv + .types() + .iter() + .map(|qualified_name_and_type| { + let ty = from_bn_type(&bv, &qualified_name_and_type.ty, u8::MAX); + (TypeGUID::from(&ty), ty) + }) + .collect(); + assert_eq!(types_len, converted_types.len()); + } + } + } + } +} diff --git a/plugins/warp/src/lib.rs b/plugins/warp/src/lib.rs index 6d147bdc..aab26932 100644 --- a/plugins/warp/src/lib.rs +++ b/plugins/warp/src/lib.rs @@ -1,7 +1,5 @@ -use crate::cache::{ - cached_adjacency_constraints, cached_call_site_constraints, cached_function_guid, -}; -use crate::convert::{from_bn_symbol, from_bn_type}; +use crate::cache::{cached_constraints, cached_function_guid}; +use crate::convert::{bn_comment_to_comment, from_bn_symbol, from_bn_type}; use binaryninja::architecture::{ Architecture, ImplicitRegisterExtend, Register as BNRegister, RegisterInfo, }; @@ -9,25 +7,55 @@ use binaryninja::basic_block::BasicBlock as BNBasicBlock; use binaryninja::binary_view::{BinaryView, BinaryViewExt}; use binaryninja::confidence::MAX_CONFIDENCE; use binaryninja::function::{Function as BNFunction, NativeBlock}; -use binaryninja::low_level_il::expression::{ExpressionHandler, LowLevelILExpressionKind}; +use binaryninja::low_level_il::expression::{ + ExpressionHandler, LowLevelILExpression, LowLevelILExpressionKind, ValueExpr, +}; use binaryninja::low_level_il::function::{FunctionMutability, LowLevelILFunction, NonSSA}; use binaryninja::low_level_il::instruction::{ InstructionHandler, LowLevelILInstruction, LowLevelILInstructionKind, }; use binaryninja::low_level_il::{LowLevelILRegisterKind, VisitorAction}; -use binaryninja::rc::Ref as BNRef; +use binaryninja::rc::{Ref as BNRef, Ref}; use std::ops::Range; use std::path::PathBuf; use warp::signature::basic_block::BasicBlockGUID; -use warp::signature::function::constraints::FunctionConstraints; use warp::signature::function::{Function, FunctionGUID}; +use binaryninja::tags::TagType; +use binaryninja::variable::RegisterValueType; +/// Re-export the warp crate that is used, this is useful for consumers of this crate. +pub use warp; + pub mod cache; +pub mod container; pub mod convert; -mod matcher; +pub mod matcher; +pub mod processor; +pub mod report; + /// Only used when compiled for cdylib target. mod plugin; +// TODO: Make this 4kb +/// If the address is within this range before or after a relocatable region, we will assume the address to be relocatable. +const ADDRESS_RELOCATION_THRESHOLD: u64 = 0x10000; + +const TAG_ICON: &str = "🌐"; +const TAG_NAME: &str = "WARP"; + +fn get_warp_tag_type(view: &BinaryView) -> Ref<TagType> { + view.tag_type_by_name(TAG_NAME) + .unwrap_or_else(|| view.create_tag_type(TAG_NAME, TAG_ICON)) +} + +const INCLUDE_TAG_ICON: &str = "🚀"; +const INCLUDE_TAG_NAME: &str = "WARP: Selected Function"; + +fn get_warp_include_tag_type(view: &BinaryView) -> Ref<TagType> { + view.tag_type_by_name(INCLUDE_TAG_NAME) + .unwrap_or_else(|| view.create_tag_type(INCLUDE_TAG_NAME, INCLUDE_TAG_ICON)) +} + pub fn core_signature_dir() -> PathBuf { // Get core signatures for the given platform let install_dir = binaryninja::install_directory(); @@ -45,22 +73,34 @@ pub fn user_signature_dir() -> PathBuf { pub fn build_function<M: FunctionMutability>( func: &BNFunction, - llil: &LowLevelILFunction<M, NonSSA>, + lifted_il: &LowLevelILFunction<M, NonSSA>, ) -> Function { - let bn_fn_ty = func.function_type(); + let comments = func + .comments() + .iter() + .map(|c| bn_comment_to_comment(func, c)) + .collect(); Function { - guid: cached_function_guid(func, llil), + guid: cached_function_guid(func, lifted_il), symbol: from_bn_symbol(&func.symbol()), - ty: from_bn_type(&func.view(), &bn_fn_ty, MAX_CONFIDENCE), - constraints: FunctionConstraints { - // NOTE: Adding adjacent only works if analysis is complete. - // NOTE: We do not filter out adjacent functions here. - adjacent: cached_adjacency_constraints(func, |_| true), - call_sites: cached_call_site_constraints(func), - // TODO: Add caller sites (when adjacent and call sites are minimal) - // NOTE: Adding caller sites only works if analysis is complete. - caller_sites: Default::default(), + // Currently we only store the type if its a user type. + // TODO: In the future we might want to make this configurable. + ty: match func.has_user_type() || func.has_explicitly_defined_type() { + true => Some(from_bn_type( + &func.view(), + &func.function_type(), + MAX_CONFIDENCE, + )), + false => None, }, + // NOTE: Adding adjacent only works if analysis is complete. + // NOTE: We do not filter out adjacent functions here. + constraints: cached_constraints(func, |_| true), + comments, + // TODO: Gather relevant variables (only user?). + // TODO: Will need MLIL SSA for this to locate def sites. + // TODO: Add this info in a second pass? + variables: vec![], } } @@ -71,20 +111,21 @@ pub fn sorted_basic_blocks(func: &BNFunction) -> Vec<BNRef<BNBasicBlock<NativeBl .iter() .map(|bb| bb.clone()) .collect::<Vec<_>>(); + // NOTE: start_index is actually the address with [`NativeBlock`]. basic_blocks.sort_by_key(|f| f.start_index()); basic_blocks } pub fn function_guid<M: FunctionMutability>( func: &BNFunction, - llil: &LowLevelILFunction<M, NonSSA>, + lifted_il: &LowLevelILFunction<M, NonSSA>, ) -> FunctionGUID { // TODO: We might want to make this configurable, or otherwise _not_ retrieve from the view here. let relocatable_regions = relocatable_regions(&func.view()); let basic_blocks = sorted_basic_blocks(func); let basic_block_guids = basic_blocks .iter() - .map(|bb| basic_block_guid(&relocatable_regions, bb, llil)) + .map(|bb| basic_block_guid(&relocatable_regions, bb, lifted_il)) .collect::<Vec<_>>(); FunctionGUID::from_basic_blocks(&basic_block_guids) } @@ -92,30 +133,60 @@ pub fn function_guid<M: FunctionMutability>( pub fn basic_block_guid<M: FunctionMutability>( relocatable_regions: &[Range<u64>], basic_block: &BNBasicBlock<NativeBlock>, - llil: &LowLevelILFunction<M, NonSSA>, + lifted_il: &LowLevelILFunction<M, NonSSA>, ) -> BasicBlockGUID { let func = basic_block.function(); + // TODO: We really should never consult another IL, no guarantee that it exists. + let low_level_il = func.low_level_il(); let view = func.view(); let arch = func.arch(); let max_instr_len = arch.max_instr_len(); + // NOTE: Whenever you make a change here, prefer being "additive", that is, make a smaller change that + // only increases the masked contents, instead of making a larger change that could *remove* masked + // contents. The reason is that we assume any change that is purely additive to increase the ability + // to match previously "unmatchable" functions, whereas the latter would take away. This is not always + // the case, but it is generally a good rule to follow. let basic_block_range = basic_block.start_index()..basic_block.end_index(); let mut basic_block_bytes = Vec::with_capacity(basic_block_range.count()); for instr_addr in basic_block.into_iter() { let mut instr_bytes = view.read_vec(instr_addr, max_instr_len); if let Some(instr_info) = arch.instruction_info(&instr_bytes, instr_addr) { instr_bytes.truncate(instr_info.length); - if let Some(instr_llil) = llil.instruction_at(instr_addr) { - // If instruction is blacklisted don't include the bytes. - if !is_blacklisted_instruction(&instr_llil) { - if is_variant_instruction(relocatable_regions, &instr_llil) { - // Found a variant instruction, mask off entire instruction. + + // Find variant and blacklisted instructions using lifted il. + if let Some(lifted_il_instr) = lifted_il.instruction_at(instr_addr) { + // If instruction is blacklisted, don't include the bytes. + if is_blacklisted_instruction(&lifted_il_instr) { + continue; + } + + if is_variant_instruction(relocatable_regions, &lifted_il_instr) { + // Found a variant instruction, mask off the entire instruction. + instr_bytes.fill(0); + } + } + + // TODO: We cannot access the values of expression in lifted IL, we have to go and consult low level IL. + // TODO: But because of some extremely annoying simplifications that are happening at LLIL, namely + // TODO: Folding of expressions into other instructions, we cannot use only LLIL. Therefor + // TODO: We only put the checks that require the expr value here. + // TODO: This still has the issue of, some (if (rax + 44) => 28) expression being masked, + // TODO: But the only way to remove that is to not consult LLIL at all and have the values + // TODO: Available at lifted IL, I have not found a good way to do this without making + // TODO: A "mapped llil" or having some simple data flow, the simple data flow is the most attractive + // TODO: "solution", but it would require + if let Ok(llil) = &low_level_il { + if let Some(low_level_instr) = llil.instruction_at(instr_addr) { + if is_computed_variant_instruction(relocatable_regions, &low_level_instr) { + // Found a computed variant instruction, mask off the entire instruction. instr_bytes.fill(0); } - // Add the instructions bytes to the basic blocks bytes - basic_block_bytes.extend(instr_bytes); } } + + // Add the instruction bytes to the basic blocks bytes + basic_block_bytes.extend(instr_bytes); } } @@ -127,8 +198,8 @@ pub fn basic_block_guid<M: FunctionMutability>( /// Blacklisted instructions will make an otherwise identical function GUID fail to match. /// /// Example: NOPs and useless moves are blacklisted to allow for hot-patchable functions. -pub fn is_blacklisted_instruction<A: Architecture, M: FunctionMutability>( - instr: &LowLevelILInstruction<A, M, NonSSA<RegularNonSSA>>, +pub fn is_blacklisted_instruction<M: FunctionMutability>( + instr: &LowLevelILInstruction<M, NonSSA>, ) -> bool { match instr.kind() { LowLevelILInstructionKind::Nop(_) => true, @@ -138,13 +209,13 @@ pub fn is_blacklisted_instruction<A: Architecture, M: FunctionMutability>( if op.dest_reg() == source_op.source_reg() => { match op.dest_reg() { - LowLevelILRegister::ArchReg(r) => { - // If this register has no implicit extend then we can safely assume it's a NOP. + LowLevelILRegisterKind::Arch(r) => { + // If this register has no implicit extend, we can safely assume it's a NOP. // Ex. on x86_64 we don't want to remove `mov edi, edi` as it will zero the upper 32 bits. // Ex. on x86 we do want to remove `mov edi, edi` as it will not have a side effect like above. matches!(r.info().implicit_extend(), ImplicitRegisterExtend::NoExtend) } - LowLevelILRegister::Temp(_) => false, + LowLevelILRegisterKind::Temp(_) => false, } } _ => false, @@ -154,24 +225,22 @@ pub fn is_blacklisted_instruction<A: Architecture, M: FunctionMutability>( } } -pub fn is_variant_instruction<A: Architecture, M: FunctionMutability>( +pub fn is_variant_instruction<M: FunctionMutability>( relocatable_regions: &[Range<u64>], - instr: &LowLevelILInstruction<A, M, NonSSA<RegularNonSSA>>, + instr: &LowLevelILInstruction<M, NonSSA>, ) -> bool { - let is_variant_expr = |expr: &LowLevelILExpressionKind<A, M, NonSSA<RegularNonSSA>>| { - match expr { + let is_variant_expr = |expr: &LowLevelILExpression<M, NonSSA, ValueExpr>| { + match expr.kind() { LowLevelILExpressionKind::ConstPtr(op) if is_address_relocatable(relocatable_regions, op.value()) => { // Constant Pointer must be in a section for it to be relocatable. - // NOTE: We cannot utilize segments here as there will be a zero based segment. true } LowLevelILExpressionKind::Const(op) if is_address_relocatable(relocatable_regions, op.value()) => { // Constant value must be in a section for it to be relocatable. - // NOTE: We cannot utilize segments here as there will be a zero based segment. true } LowLevelILExpressionKind::ExternPtr(_) => true, @@ -181,7 +250,7 @@ pub fn is_variant_instruction<A: Architecture, M: FunctionMutability>( // Visit instruction expressions looking for variant expression, [VisitorAction::Halt] means variant. instr.visit_tree(&mut |expr| { - if is_variant_expr(&expr.kind()) { + if is_variant_expr(expr) { // Found a variant expression. VisitorAction::Halt } else { @@ -191,18 +260,87 @@ pub fn is_variant_instruction<A: Architecture, M: FunctionMutability>( }) == VisitorAction::Halt } -/// If the address is inside any of the given ranges we will assume the address to be relocatable. +/// NOTE: This will only work at LLIL, **NOT** lifted IL. You must do this in a second pass. +/// +/// This was previously done inside `is_variant_instruction` but had to be moved to access expr value. +pub fn is_computed_variant_instruction<M: FunctionMutability>( + relocatable_regions: &[Range<u64>], + instr: &LowLevelILInstruction<M, NonSSA>, +) -> bool { + let is_expr_constant = |expr: &LowLevelILExpression<M, NonSSA, ValueExpr>| match expr.kind() { + LowLevelILExpressionKind::Const(_) => true, + _ => false, + }; + + let is_variant_observed_expr = |expr: &LowLevelILExpression<M, NonSSA, ValueExpr>| { + match expr.kind() { + // TODO: Skip problematic expressions like IF? + LowLevelILExpressionKind::Add(op) | LowLevelILExpressionKind::Sub(op) => { + // For now, we limit to only expressions that contain some constant; this keeps add expressions + // with two registers with known values from being marked variant. + let constant_expressed = + is_expr_constant(&op.left()) || is_expr_constant(&op.right()); + // NOTE: Lifted IL does not have the value ever, we must consult Low Level IL. + // If the expression value is known, we check to see if it's a relocatable address. + let expr_value = expr.value(); + match expr_value.state { + RegisterValueType::EntryValue + | RegisterValueType::ConstantValue + | RegisterValueType::ConstantPointerValue + | RegisterValueType::ExternalPointerValue + | RegisterValueType::StackFrameOffset + | RegisterValueType::ReturnAddressValue + | RegisterValueType::ImportedAddressValue + if constant_expressed + && is_address_relocatable( + relocatable_regions, + expr_value.value as u64, + ) => + { + // Concrete arithmetic operation with a relocatable result. + true + } + _ => false, + } + } + _ => false, + } + }; + + // Visit instruction expressions looking for an observed variant expression, [VisitorAction::Halt] means variant. + instr.visit_tree(&mut |expr| { + if is_variant_observed_expr(expr) { + // Found a variant expression. + VisitorAction::Halt + } else { + // Keep looking for an observed variant expression. + VisitorAction::Descend + } + }) == VisitorAction::Halt +} + +/// If the address is inside any of the given ranges, we will assume the address to be relocatable. pub fn is_address_relocatable(relocatable_regions: &[Range<u64>], address: u64) -> bool { relocatable_regions .iter() - .any(|range| range.contains(&address)) + .any(|range| { + // Check if the address is within the range itself + (range.contains(&address)) + // Check if the address is within the threshold **AFTER** the range + // NOTE: The address must at least be larger than the threshold itself, for lower image-based binaries. + || (address > range.end && address > ADDRESS_RELOCATION_THRESHOLD && address <= range.end + ADDRESS_RELOCATION_THRESHOLD) + // Check if the address is within the threshold **BEFORE** the range + // NOTE: The address must at least be larger than the threshold itself, for lower image-based binaries. + || (address < range.start && address > ADDRESS_RELOCATION_THRESHOLD && address >= range.start.saturating_sub(ADDRESS_RELOCATION_THRESHOLD)) + }) } // TODO: This might need to be configurable, in that case we better remove this function. /// Get the relocatable regions of the view. /// -/// Currently, this is all the sections, however this might be refined later. +/// Currently, this is all the sections, however, this might be refined later. pub fn relocatable_regions(view: &BinaryView) -> Vec<Range<u64>> { + // NOTE: We cannot use segments here as there will be a zero-based segment. view.sections() .iter() .map(|s| Range { @@ -211,41 +349,3 @@ pub fn relocatable_regions(view: &BinaryView) -> Vec<Range<u64>> { }) .collect() } - -#[cfg(test)] -mod tests { - use crate::cache::cached_function_guid; - use binaryninja::binary_view::BinaryViewExt; - use binaryninja::headless::Session; - use std::path::PathBuf; - use std::sync::OnceLock; - - static INIT: OnceLock<Session> = OnceLock::new(); - - fn get_session<'a>() -> &'a Session { - // TODO: This is not shared between other test modules, should still be fine (mutex in core now). - INIT.get_or_init(|| Session::new().expect("Failed to initialize session")) - } - - #[test] - fn insta_signatures() { - let session = get_session(); - let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); - for entry in std::fs::read_dir(out_dir).expect("Failed to read OUT_DIR") { - let entry = entry.expect("Failed to read directory entry"); - let path = entry.path(); - if path.is_file() { - let view = session.load(&path).expect("Failed to load view"); - let mut functions = view - .functions() - .iter() - .map(|f| cached_function_guid(&f, &f.low_level_il().unwrap())) - .collect::<Vec<_>>(); - functions.sort_by_key(|guid| guid.guid); - let snapshot_name = - format!("snapshot_{}", path.file_stem().unwrap().to_string_lossy()); - insta::assert_debug_snapshot!(snapshot_name, functions); - } - } - } -} diff --git a/plugins/warp/src/matcher.rs b/plugins/warp/src/matcher.rs index 0ce258ad..c6ab7f9f 100644 --- a/plugins/warp/src/matcher.rs +++ b/plugins/warp/src/matcher.rs @@ -1,363 +1,282 @@ +use crate::cache::cached_constraints; +use crate::container::{Container, SourceId}; +use crate::convert::to_bn_type; use binaryninja::architecture::Architecture as BNArchitecture; use binaryninja::binary_view::{BinaryView, BinaryViewExt}; use binaryninja::function::Function as BNFunction; -use binaryninja::platform::Platform; -use binaryninja::rc::Guard; -use binaryninja::rc::Ref as BNRef; -use dashmap::DashMap; +use binaryninja::settings::{QueryOptions, Settings as BNSettings}; use serde_json::json; use std::cmp::Ordering; -use std::collections::{HashMap, HashSet}; -use std::hash::{DefaultHasher, Hash, Hasher}; -use std::path::PathBuf; -use std::sync::OnceLock; -use walkdir::{DirEntry, WalkDir}; +use std::collections::HashSet; +use std::hash::Hash; use warp::r#type::class::TypeClass; -use warp::r#type::guid::TypeGUID; use warp::r#type::Type; -use warp::signature::function::{Function, FunctionGUID}; -use warp::signature::Data; - -use crate::cache::{ - cached_adjacency_constraints, cached_call_site_constraints, cached_function_match, - try_cached_function_guid, -}; -use crate::convert::to_bn_type; -use crate::plugin::on_matched_function; -use crate::{core_signature_dir, user_signature_dir}; - -pub static PLAT_MATCHER_CACHE: OnceLock<DashMap<PlatformID, Matcher>> = OnceLock::new(); - -pub fn cached_function_matcher(function: &BNFunction) { - let platform = function.platform(); - let platform_id = PlatformID::from(platform.as_ref()); - let matcher_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default); - match matcher_cache.get(&platform_id) { - Some(matcher) => matcher.match_function(function), - None => { - let matcher = Matcher::from_platform(platform); - matcher.match_function(function); - matcher_cache.insert(platform_id, matcher); - } - } -} - -// TODO: Maybe just clear individual platforms? This works well enough either way. -pub fn invalidate_function_matcher_cache() { - let matcher_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default); - matcher_cache.clear(); -} +use warp::signature::function::Function; -#[derive(Debug, Default, Clone)] +/// A matcher represents a specific configuration for identify functions using WARP. A matcher +/// does not store/own any WARP information directly, instead the matcher is given a [`Container`] +/// that holds all of that information. +/// +/// The separation of the WARP information from the [`Matcher`] allows a greater degree of control and +/// provides a clean interface for further logic to be built on top of. A matcher instance, unlike +/// a typical [`Container`] implementation, is cheap to create. +#[derive(Debug, Clone, Copy)] pub struct Matcher { - // TODO: Storing the settings here means that they are effectively global. - // TODO: If we want scoped or view settings they must be moved out. pub settings: MatcherSettings, - pub functions: DashMap<FunctionGUID, Vec<Function>>, - pub types: DashMap<TypeGUID, Type>, - pub named_types: DashMap<String, Type>, } impl Matcher { - /// Create a matcher from the platforms signature subdirectory. - pub fn from_platform(platform: BNRef<Platform>) -> Self { - let platform_name = platform.name().to_string(); + pub fn new(settings: MatcherSettings) -> Self { + Matcher { settings } + } - // Get core and user signatures. - // TODO: Separate each file into own bucket for filtering? - let plat_core_sig_dir = core_signature_dir().join(&platform_name); - let mut data = get_data_from_dir(&plat_core_sig_dir); - let plat_user_sig_dir = user_signature_dir().join(&platform_name); - let user_data = get_data_from_dir(&plat_user_sig_dir); + pub fn match_function_from_constraints<'a>( + &self, + function: &BNFunction, + matched_functions: &'a [Function], + ) -> Option<&'a Function> { + let function_len = function.highest_address() - function.lowest_address(); + let is_function_trivial = { function_len < self.settings.trivial_function_len }; + let is_function_allowed = { + function_len >= self.settings.minimum_function_len + && function_len < self.settings.maximum_function_len.unwrap_or(u64::MAX) + }; - data.extend(user_data); - let merged_data = Data::merge(data.values().cloned().collect::<Vec<_>>()); - log::debug!("Loaded signatures: {:?}", data.keys()); - Matcher::from_data(merged_data) - } + // Function isn't allowed, or no matches so stop early. + if !is_function_allowed || matched_functions.is_empty() { + return None; + } - pub fn from_data(data: Data) -> Self { - let functions = data.functions.into_iter().fold( - DashMap::new(), - |map: DashMap<FunctionGUID, Vec<_>>, func| { - map.entry(func.guid).or_default().push(func); - map - }, - ); - let types = data - .types - .iter() - .map(|ty| (ty.guid, ty.ty.clone())) - .collect(); - let named_types = data - .types - .into_iter() - .filter_map(|ty| ty.ty.name.to_owned().map(|name| (name, ty.ty))) - .collect(); + // If we have a single possible match than that must be our function. + // We must also not be a trivial function, as those will likely be artifacts of an incomplete dataset + if matched_functions.len() == 1 && !is_function_trivial { + return matched_functions.first(); + } + // Filter out adjacent functions which are trivial, this helps avoid false positives. + // NOTE: If the user sets `trivial_function_adjacent_allowed` to true we will always match. + // TODO: Expand on this more later. We might want to match on adjacent functions smaller than this. + let adjacent_function_filter = |adj_func: &BNFunction| { + let adj_func_len = adj_func.highest_address() - adj_func.lowest_address(); + adj_func_len >= self.settings.trivial_function_len + || self.settings.trivial_function_adjacent_allowed + }; - Self { - // NOTE: Settings will be retrieved from global state every time this is called. - settings: MatcherSettings::global(), - functions, - types, - named_types, + // TODO: When the highest count has two matches we return None. Need to alert the user. + // "common" being the intersection between the observed and matched. + let constraints = cached_constraints(function, adjacent_function_filter); + let mut highest_count = 0; + let mut matched_func = None; + for matched in matched_functions { + let common_count = constraints.intersection(&matched.constraints).count(); + match common_count.cmp(&highest_count) { + Ordering::Equal => matched_func = None, + Ordering::Greater => { + highest_count = common_count; + matched_func = Some(matched); + } + Ordering::Less => {} + } } - } - pub fn extend_with_matcher(&mut self, matcher: Matcher) { - self.functions.extend(matcher.functions); - self.types.extend(matcher.types); - self.named_types.extend(matcher.named_types); + // If we have a match below the minimum threshold, ignore. + match highest_count.cmp(&self.settings.minimum_matched_constraints) { + Ordering::Equal => matched_func, + Ordering::Greater => matched_func, + Ordering::Less => None, + } } - pub fn add_type_to_view<A: BNArchitecture>(&self, view: &BinaryView, arch: &A, ty: &Type) { + // TODO: I would really like for WARP types to be added in a seperate type container, so that we don't + // TODO: just add them as system or user types. + pub fn add_type_to_view<A: BNArchitecture>( + &self, + container: &dyn Container, + source: &SourceId, + view: &BinaryView, + arch: &A, + ty: &Type, + ) where + Self: Sized, + { fn inner_add_type_to_view<A: BNArchitecture>( - matcher: &Matcher, + container: &dyn Container, + source: &SourceId, view: &BinaryView, arch: &A, visited_refs: &mut HashSet<String>, ty: &Type, ) { - let ty_id_str = TypeGUID::from(ty).to_string(); - if view.type_by_id(&ty_id_str).is_some() { - // Type already added. - return; - } // Type not already added to the view. // Verify all nested types are added before adding type. - match ty.class.as_ref() { - TypeClass::Pointer(c) => { - inner_add_type_to_view(matcher, view, arch, visited_refs, &c.child_type) - } - TypeClass::Array(c) => { - inner_add_type_to_view(matcher, view, arch, visited_refs, &c.member_type) - } + match &ty.class { + TypeClass::Pointer(c) => inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &c.child_type, + ), + TypeClass::Array(c) => inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &c.member_type, + ), TypeClass::Structure(c) => { for member in &c.members { - inner_add_type_to_view(matcher, view, arch, visited_refs, &member.ty) + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &member.ty, + ) } } - TypeClass::Enumeration(c) => { - inner_add_type_to_view(matcher, view, arch, visited_refs, &c.member_type) - } + TypeClass::Enumeration(c) => inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &c.member_type, + ), TypeClass::Union(c) => { for member in &c.members { - inner_add_type_to_view(matcher, view, arch, visited_refs, &member.ty) + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &member.ty, + ) } } TypeClass::Function(c) => { for out_member in &c.out_members { - inner_add_type_to_view(matcher, view, arch, visited_refs, &out_member.ty) + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &out_member.ty, + ) } for in_member in &c.in_members { - inner_add_type_to_view(matcher, view, arch, visited_refs, &in_member.ty) + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &in_member.ty, + ) } } TypeClass::Referrer(c) => { // Check to see if the referrer has been added to the view. - let mut resolved = false; + let mut resolved_ty = None; if let Some(ref_guid) = c.guid { // NOTE: We do not need to check for cyclic reference here because // NOTE: GUID references are unable to be referenced by themselves. if view.type_by_id(&ref_guid.to_string()).is_none() { // Add the referrer to the view if it is in the Matcher types - if let Some(ref_ty) = matcher.types.get(&ref_guid) { - inner_add_type_to_view(matcher, view, arch, visited_refs, &ref_ty); - resolved = true; + if let Ok(Some(ref_ty)) = container.type_with_guid(source, &ref_guid) { + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &ref_ty, + ); + resolved_ty = Some(ref_ty); } } } if let Some(ref_name) = &c.name { // Only try and resolve by name if not already visiting. - if !resolved + if resolved_ty.is_none() && visited_refs.insert(ref_name.to_string()) && view.type_by_name(ref_name).is_none() { // Add the ref to the view if it is in the Matcher types - if let Some(ref_ty) = matcher.named_types.get(ref_name) { - inner_add_type_to_view(matcher, view, arch, visited_refs, &ref_ty); + let type_guids = container + .type_guids_with_name(source, ref_name) + .unwrap_or_default(); + // TODO: What happens if we have more than one? + if type_guids.len() == 1 { + // TODO: What happens if we cant get the guid? + if let Ok(Some(ref_ty)) = + container.type_with_guid(source, &type_guids[0]) + { + inner_add_type_to_view( + container, + source, + view, + arch, + visited_refs, + &ref_ty, + ); + resolved_ty = Some(ref_ty); + } } // No longer visiting type. visited_refs.remove(ref_name); } } - // All nested types _should_ be added now, we can add this type. - // TODO: Do we want to make unnamed types visible? I think we should, but some people might be opposed. - let ty_name = ty.name.to_owned().unwrap_or_else(|| ty_id_str.clone()); - view.define_auto_type_with_id(ty_name, &ty_id_str, &to_bn_type(arch, ty)); - } - _ => {} - } - } - inner_add_type_to_view(self, view, arch, &mut HashSet::new(), ty) - } - - pub fn match_function(&self, function: &BNFunction) { - // Call this the first time you matched on the function. - let resolve_new_types = |matched: &Function| { - // We also want to resolve the types here. - if let TypeClass::Function(c) = matched.ty.class.as_ref() { - // Recursively go through the function type and resolve referrers - let view = function.view(); - let arch = function.arch(); - for out_member in &c.out_members { - self.add_type_to_view(&view, &arch, &out_member.ty); - } - for in_member in &c.in_members { - self.add_type_to_view(&view, &arch, &in_member.ty); - } - } - }; - - if let Some(matched_function) = cached_function_match(function, || { - // We have yet to match on this function. - let function_len = function.highest_address() - function.lowest_address(); - let is_function_trivial = { function_len < self.settings.trivial_function_len }; - let is_function_allowed = { - function_len > self.settings.minimum_function_len - && function_len < self.settings.maximum_function_len.unwrap_or(u64::MAX) - }; - let warp_func_guid = try_cached_function_guid(function)?; - match self.functions.get(&warp_func_guid) { - _ if !is_function_allowed => None, - Some(matched) if matched.len() == 1 && !is_function_trivial => { - resolve_new_types(&matched[0]); - Some(matched[0].to_owned()) - } - Some(matched) => { - let matched_on = self.match_function_from_constraints(function, &matched)?; - resolve_new_types(matched_on); - Some(matched_on.to_owned()) - } - None => None, - } - }) { - on_matched_function(function, &matched_function); - } - } - - pub fn match_function_from_constraints<'a>( - &self, - function: &BNFunction, - matched_functions: &'a [Function], - ) -> Option<&'a Function> { - // Filter out adjacent functions which are trivial, this helps avoid false positives. - // NOTE: If the user sets `trivial_function_adjacent_allowed` to true we will always match. - // TODO: Expand on this more later. We might want to match on adjacent functions smaller than this. - let adjacent_function_filter = |adj_func: &BNFunction| { - let adj_func_len = adj_func.highest_address() - adj_func.lowest_address(); - adj_func_len > self.settings.trivial_function_len - || self.settings.trivial_function_adjacent_allowed - }; - - let call_sites = cached_call_site_constraints(function); - let adjacent = cached_adjacency_constraints(function, adjacent_function_filter); - - // "common" being the intersection between the observed and matched. - fn find_highest_common_count<'a, F, T>( - observed_items: &HashSet<T>, - matched_functions: &'a [Function], - extract_items: F, - ) -> (usize, Option<&'a Function>) - where - F: Fn(&Function) -> HashSet<T>, - T: Hash + Eq, - { - let mut highest_count = 0; - let mut matched_func = None; - for matched in matched_functions { - let matched_items = extract_items(matched); - let common_count = observed_items.intersection(&matched_items).count(); - match common_count.cmp(&highest_count) { - Ordering::Equal => matched_func = None, - Ordering::Greater => { - highest_count = common_count; - matched_func = Some(matched); + // Adds the ref'd type to the view. + match (c.guid, &c.name, resolved_ty) { + (Some(guid), Some(name), Some(ref_ty)) => { + view.define_auto_type_with_id( + name, + &guid.to_string(), + &to_bn_type(arch, &ref_ty), + ); + } + (Some(_guid), Some(_name), None) => { + // TODO: Got name and guid but no type? Do we add a bare NTR? + } + (Some(_guid), None, _) => { + // TODO: How would we reference this type without a name??? + } + (None, Some(_name), _) => { + // TODO: Cyclic type reference if no guid, so... dont define? + } + (None, None, _) => { + // TODO: What?!?!? + } } - Ordering::Less => {} } + TypeClass::Void + | TypeClass::Boolean(_) + | TypeClass::Integer(_) + | TypeClass::Character(_) + | TypeClass::Float(_) => {} } - (highest_count, matched_func) - } - - let call_site_guids: HashSet<_> = call_sites.iter().filter_map(|c| c.guid).collect(); - let call_site_symbol_names: HashSet<_> = call_sites - .into_iter() - .filter_map(|c| c.symbol.map(|s| s.name)) - .collect(); - let adjacent_guids: HashSet<_> = adjacent.iter().filter_map(|c| c.guid).collect(); - let adjacent_symbol_names: HashSet<_> = adjacent - .into_iter() - .filter_map(|c| c.symbol.map(|s| s.name)) - .collect(); - - // Ordered from the lowest confidence to the highest confidence constraint. - let checked_constraints = [ - find_highest_common_count(&adjacent_symbol_names, matched_functions, |matched| { - matched - .constraints - .adjacent - .iter() - .filter_map(|c| c.symbol.to_owned().map(|s| s.name)) - .collect() - }), - find_highest_common_count(&adjacent_guids, matched_functions, |matched| { - matched - .constraints - .adjacent - .iter() - .filter_map(|c| c.guid) - .collect() - }), - find_highest_common_count(&call_site_symbol_names, matched_functions, |matched| { - matched - .constraints - .call_sites - .iter() - .filter_map(|c| c.symbol.to_owned().map(|s| s.name)) - .collect() - }), - find_highest_common_count(&call_site_guids, matched_functions, |matched| { - matched - .constraints - .call_sites - .iter() - .filter_map(|c| c.guid) - .collect() - }), - ]; - // If there is a tie, the last one wins, which should be call_site guid. - checked_constraints - .into_iter() - .max_by_key(|&(count, _)| count) - .filter(|&(count, _)| count >= self.settings.minimum_matched_constraints) - .and_then(|(_, func)| func) + // TODO: Some refs likely need to ommitted because they are just that, refs to another type. + // let guid = TypeGUID::from(ty); + // let name = ty.name.clone().unwrap_or(guid.to_string()); + // view.define_auto_type_with_id(name, &guid.to_string(), &to_bn_type(arch, ty)); + } + inner_add_type_to_view(container, source, view, arch, &mut HashSet::new(), ty) } } -fn get_data_from_dir(dir: &PathBuf) -> HashMap<PathBuf, Data> { - let data_from_entry = |entry: DirEntry| { - let path = entry.path(); - let contents = std::fs::read(path).ok()?; - Data::from_bytes(&contents) - }; - - WalkDir::new(dir) - .into_iter() - .filter_map(|e| e.ok()) - .filter(|e| e.file_type().is_file()) - .filter_map(|e| Some((e.clone().into_path(), data_from_entry(e)?))) - .collect() -} - -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct MatcherSettings { /// Any function under this length will be required to constrain. /// - /// This is set to [MatcherSettings::DEFAULT_TRIVIAL_FUNCTION_LEN] by default. + /// This is set to [MatcherSettings::TRIVIAL_FUNCTION_LEN_DEFAULT] by default. pub trivial_function_len: u64, /// Any function under this length will not match. /// @@ -367,13 +286,15 @@ pub struct MatcherSettings { /// /// This is set to [MatcherSettings::MAXIMUM_FUNCTION_LEN_DEFAULT] by default. pub maximum_function_len: Option<u64>, - /// For a successful constrained function match the number of matches must be above this. + /// For a successful constrained function match, the number of matches must be above this. /// - /// This is set to [MatcherSettings::DEFAULT_TRIVIAL_FUNCTION_LEN] by default. + /// This is set to [MatcherSettings::MINIMUM_MATCHED_CONSTRAINTS_DEFAULT] by default. pub minimum_matched_constraints: usize, - /// For a successful constrained function match the number of matches must be above this. + /// When function constraints are checked, if this is enabled, functions can match based off trivial adjacent functions. + /// + /// Any function under `trivial_function_len` will be considered trivial. /// - /// This is set to [MatcherSettings::DEFAULT_TRIVIAL_FUNCTION_LEN] by default. + /// This is set to [MatcherSettings::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_DEFAULT] by default. pub trivial_function_adjacent_allowed: bool, } @@ -395,17 +316,15 @@ impl MatcherSettings { /// /// Call this once when you initialize so that the settings exist. /// - /// NOTE: If you are using this as a library then just modify the MatcherSettings directly + /// NOTE: If you are using this as a library, then modify the [`MatcherSettings`] directly /// in the matcher instance, that way you don't need to round-trip through Binary Ninja. - pub fn register() { - let bn_settings = binaryninja::settings::Settings::new(); - + pub fn register(bn_settings: &mut BNSettings) { let trivial_function_len_props = json!({ "title" : "Trivial Function Length", "type" : "number", "default" : Self::TRIVIAL_FUNCTION_LEN_DEFAULT, "description" : "Functions below this length in bytes will be required to match on constraints.", - "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + "ignore" : [] }); bn_settings.register_setting_json( Self::TRIVIAL_FUNCTION_LEN_SETTING, @@ -417,7 +336,7 @@ impl MatcherSettings { "type" : "number", "default" : Self::MINIMUM_FUNCTION_LEN_DEFAULT, "description" : "Functions below this length will not be matched.", - "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + "ignore" : [] }); bn_settings.register_setting_json( Self::MINIMUM_FUNCTION_LEN_SETTING, @@ -429,7 +348,7 @@ impl MatcherSettings { "type" : "number", "default" : Self::MAXIMUM_FUNCTION_LEN_DEFAULT, "description" : "Functions above this length will not be matched. A value of 0 will disable this check.", - "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + "ignore" : [] }); bn_settings.register_setting_json( Self::MAXIMUM_FUNCTION_LEN_SETTING, @@ -441,7 +360,7 @@ impl MatcherSettings { "type" : "number", "default" : Self::MINIMUM_MATCHED_CONSTRAINTS_DEFAULT, "description" : "When function constraints are checked the amount of constraints matched must be at-least this.", - "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + "ignore" : [] }); bn_settings.register_setting_json( Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING, @@ -453,7 +372,7 @@ impl MatcherSettings { "type" : "boolean", "default" : Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_DEFAULT, "description" : "When function constraints are checked if this is enabled functions can match based off trivial adjacent functions.", - "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + "ignore" : [] }); bn_settings.register_setting_json( Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_SETTING, @@ -461,26 +380,32 @@ impl MatcherSettings { ); } - pub fn global() -> Self { + /// Retrieve matcher settings from [`BNSettings`]. + pub fn from_settings(bn_settings: &BNSettings, query_opts: &mut QueryOptions) -> Self { let mut settings = MatcherSettings::default(); - let bn_settings = binaryninja::settings::Settings::new(); if bn_settings.contains(Self::TRIVIAL_FUNCTION_LEN_SETTING) { settings.trivial_function_len = - bn_settings.get_integer(Self::TRIVIAL_FUNCTION_LEN_SETTING); + bn_settings.get_integer_with_opts(Self::TRIVIAL_FUNCTION_LEN_SETTING, query_opts); } if bn_settings.contains(Self::MINIMUM_FUNCTION_LEN_SETTING) { settings.minimum_function_len = - bn_settings.get_integer(Self::MINIMUM_FUNCTION_LEN_SETTING); + bn_settings.get_integer_with_opts(Self::MINIMUM_FUNCTION_LEN_SETTING, query_opts); } if bn_settings.contains(Self::MAXIMUM_FUNCTION_LEN_SETTING) { - match bn_settings.get_integer(Self::MAXIMUM_FUNCTION_LEN_SETTING) { + match bn_settings.get_integer_with_opts(Self::MAXIMUM_FUNCTION_LEN_SETTING, query_opts) + { 0 => settings.maximum_function_len = None, len => settings.maximum_function_len = Some(len), } } if bn_settings.contains(Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING) { - settings.minimum_matched_constraints = - bn_settings.get_integer(Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING) as usize; + settings.minimum_matched_constraints = bn_settings + .get_integer_with_opts(Self::MINIMUM_MATCHED_CONSTRAINTS_SETTING, query_opts) + as usize; + } + if bn_settings.contains(Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_SETTING) { + settings.trivial_function_adjacent_allowed = bn_settings + .get_bool_with_opts(Self::TRIVIAL_FUNCTION_ADJACENT_ALLOWED_SETTING, query_opts); } settings } @@ -498,27 +423,3 @@ impl Default for MatcherSettings { } } } - -/// A unique platform ID, used for caching. -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub struct PlatformID(u64); - -impl From<&Platform> for PlatformID { - fn from(value: &Platform) -> Self { - let mut hasher = DefaultHasher::new(); - hasher.write(value.name().as_bytes()); - Self(hasher.finish()) - } -} - -impl From<BNRef<Platform>> for PlatformID { - fn from(value: BNRef<Platform>) -> Self { - Self::from(value.as_ref()) - } -} - -impl From<Guard<'_, Platform>> for PlatformID { - fn from(value: Guard<'_, Platform>) -> Self { - Self::from(value.as_ref()) - } -} diff --git a/plugins/warp/src/plugin.rs b/plugins/warp/src/plugin.rs index 4adab293..f8c4c0c5 100644 --- a/plugins/warp/src/plugin.rs +++ b/plugins/warp/src/plugin.rs @@ -1,186 +1,40 @@ use crate::cache::register_cache_destructor; +use std::time::Instant; +use crate::cache::container::add_cached_container; +use crate::container::disk::DiskContainer; use crate::matcher::MatcherSettings; use crate::plugin::render_layer::HighlightRenderLayer; -use binaryninja::binary_view::{BinaryView, BinaryViewExt}; -use binaryninja::command::{Command, FunctionCommand}; -use binaryninja::function::{Function, FunctionUpdateType}; +use crate::plugin::settings::PluginSettings; +use crate::{core_signature_dir, user_signature_dir}; +use binaryninja::background_task::BackgroundTask; +use binaryninja::command::{ + register_command, register_command_for_function, register_command_for_project, +}; use binaryninja::logger::Logger; -use binaryninja::rc::Ref; -use binaryninja::tags::TagType; -use binaryninja::ObjectDestructor; +use binaryninja::settings::Settings; use log::LevelFilter; -use warp::signature::function::constraints::FunctionConstraint; -use warp::signature::function::Function as WarpFunction; -mod add; -mod copy; mod create; -mod find; +mod debug; +mod ffi; +mod file; +mod function; mod load; +mod project; mod render_layer; -mod types; +mod settings; mod workflow; -// TODO: This icon is a little much -const TAG_ICON: &str = "🌏"; -const TAG_NAME: &str = "WARP"; - -fn get_warp_tag_type(view: &BinaryView) -> Ref<TagType> { - view.tag_type_by_name(TAG_NAME) - .unwrap_or_else(|| view.create_tag_type(TAG_NAME, TAG_ICON)) -} - -// What happens to the function when it is matched. -// TODO: add user: bool -// TODO: Rename to markup_function or something. -pub fn on_matched_function(function: &Function, matched: &WarpFunction) { - let view = function.view(); - // TODO: Using user symbols here is problematic - // TODO: For one they queue up a bunch of main thread actions - // TODO: Secondly by queueing up those main thread actions if you attempt to save the file - // TODO: Before the undo actions are done completing - view.define_user_symbol(&to_bn_symbol_at_address( - &view, - &matched.symbol, - function.symbol().address(), - )); - function.set_user_type(&to_bn_type(&function.arch(), &matched.ty)); - // TODO: Add metadata. (both binja metadata and warp metadata) - function.add_tag( - &get_warp_tag_type(&view), - &matched.guid.to_string(), - None, - true, - None, - ); - // Seems to be the only way to get the analysis update to work correctly. - function.mark_updates_required(FunctionUpdateType::FullAutoFunctionUpdate); -} - -struct DebugFunction; - -impl FunctionCommand for DebugFunction { - fn action(&self, _view: &BinaryView, func: &Function) { - if let Ok(llil) = func.low_level_il() { - log::info!("{:#?}", build_function(func, &llil)); - } - } - - fn valid(&self, _view: &BinaryView, _func: &Function) -> bool { - true - } -} - -struct DebugMatcher; - -impl FunctionCommand for DebugMatcher { - fn action(&self, _view: &BinaryView, function: &Function) { - let Ok(llil) = function.low_level_il() else { - log::error!("No LLIL for function 0x{:x}", function.start()); - return; - }; - let platform = function.platform(); - // Build the matcher every time this is called to make sure we aren't in a bad state. - let matcher = Matcher::from_platform(platform); - let func = build_function(function, &llil); - // TODO: Clean this up. - if let Some(possible_matches) = matcher.functions.get(&func.guid) { - let print_constraint = |prefix: &str, constraint: &FunctionConstraint| { - log::info!( - " {} {} ({})", - prefix, - constraint - .to_owned() - .symbol - .map(|s| s.name) - .unwrap_or("*".to_string()), - constraint - .guid - .map(|g| g.to_string()) - .unwrap_or("*".to_string()) - ); - }; - for possible_match in possible_matches.value() { - log::info!("{} ({})", possible_match.symbol.name, possible_match.guid); - for constraint in &possible_match.constraints.call_sites { - print_constraint("CS", constraint); - } - for constraint in &possible_match.constraints.call_sites { - print_constraint("ADJ", constraint); - } - } - } else { - log::error!( - "No possible matches found for the function 0x{:x}", - function.start() - ); - }; - } - - fn valid(&self, _view: &BinaryView, _function: &Function) -> bool { - true - } -} - -struct DebugCache; - -impl Command for DebugCache { - fn action(&self, view: &BinaryView) { - let view_id = ViewID::from(view); - let function_cache = FUNCTION_CACHE.get_or_init(Default::default); - if let Some(cache) = function_cache.get(&view_id) { - log::info!("View functions: {}", cache.cache.len()); - } - - let matched_function_cache = MATCHED_FUNCTION_CACHE.get_or_init(Default::default); - if let Some(cache) = matched_function_cache.get(&view_id) { - log::info!("View matched functions: {}", cache.cache.len()); - } - - let function_guid_cache = GUID_CACHE.get_or_init(Default::default); - if let Some(cache) = function_guid_cache.get(&view_id) { - log::info!("View function guids: {}", cache.cache.len()); - } - - let plat_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default); - if let Some(plat) = view.default_platform() { - let platform_id = PlatformID::from(plat); - if let Some(cache) = plat_cache.get(&platform_id) { - log::info!("Platform functions: {}", cache.functions.len()); - log::info!("Platform types: {}", cache.types.len()); - log::info!("Platform settings: {:?}", cache.settings); - } - } - } - - fn valid(&self, _view: &BinaryView) -> bool { - true - } -} - -struct DebugInvalidateCache; - -impl Command for DebugInvalidateCache { - fn action(&self, view: &BinaryView) { - invalidate_function_matcher_cache(); - let destructor = cache::CacheDestructor {}; - destructor.destruct_view(view); - log::info!("Invalidated all WARP caches..."); - } - - fn valid(&self, _view: &BinaryView) -> bool { - true - } -} - #[no_mangle] #[allow(non_snake_case)] pub extern "C" fn CorePluginInit() -> bool { Logger::new("WARP").with_level(LevelFilter::Debug).init(); - // Register our matcher settings. - MatcherSettings::register(); + // Register our matcher and plugin settings globally. + let mut global_bn_settings = Settings::new(); + MatcherSettings::register(&mut global_bn_settings); + PluginSettings::register(&mut global_bn_settings); // Make sure caches are flushed when the views get destructed. register_cache_destructor(); @@ -190,70 +44,93 @@ pub extern "C" fn CorePluginInit() -> bool { workflow::insert_workflow(); - binaryninja::command::register_command( + let plugin_settings = PluginSettings::from_settings(&global_bn_settings); + // We want to load all the bundled directories into the container cache. + let background_task = BackgroundTask::new("Loading WARP files...", false); + let start = Instant::now(); + if plugin_settings.load_bundled_files { + let core_disk_container = DiskContainer::new_from_dir(core_signature_dir()); + log::debug!("{:#?}", core_disk_container); + add_cached_container(core_disk_container); + } + if plugin_settings.load_user_files { + let user_disk_container = DiskContainer::new_from_dir(user_signature_dir()); + log::debug!("{:#?}", user_disk_container); + add_cached_container(user_disk_container); + } + log::info!("Loading bundled files took {:?}", start.elapsed()); + background_task.finish(); + + register_command( "WARP\\Run Matcher", "Run the matcher manually", workflow::RunMatcher {}, ); - binaryninja::command::register_command( + register_command( "WARP\\Debug\\Cache", "Debug cache sizes... because...", - DebugCache {}, + debug::DebugCache {}, ); - binaryninja::command::register_command( + register_command( "WARP\\Debug\\Invalidate Caches", "Invalidate all WARP caches", - DebugInvalidateCache {}, + debug::DebugInvalidateCache {}, ); - binaryninja::command::register_command_for_function( + register_command_for_function( "WARP\\Debug\\Function Signature", "Print the entire signature for the function", - DebugFunction {}, - ); - - binaryninja::command::register_command_for_function( - "WARP\\Debug\\Function Matcher", - "Print all possible matches for the function", - DebugMatcher {}, + debug::DebugFunction {}, ); - binaryninja::command::register_command( - "WARP\\Debug\\Apply Signature File Types", - "Load all types from a signature file and ignore functions", - types::LoadTypes {}, - ); - - binaryninja::command::register_command( - "WARP\\Load Signature File", + register_command( + "WARP\\Load File", "Load file into the matcher, this does NOT kick off matcher analysis", load::LoadSignatureFile {}, ); - binaryninja::command::register_command_for_function( - "WARP\\Copy Function GUID", + register_command_for_function( + "WARP\\Function\\Include", + "Add current function to the list of functions to add to the signature file", + function::IncludeFunction {}, + ); + + register_command_for_function( + "WARP\\Function\\Copy GUID", "Copy the computed GUID for the function", - copy::CopyFunctionGUID {}, + function::CopyFunctionGUID {}, ); - binaryninja::command::register_command( - "WARP\\Find Function From GUID", + register_command( + "WARP\\Function\\Find GUID", "Locate the function in the view using a GUID", - find::FindFunctionFromGUID {}, + function::FindFunctionFromGUID {}, + ); + + register_command( + "WARP\\Create\\From Current View", + "Creates a signature file containing all selected functions", + create::CreateFromCurrentView {}, + ); + + register_command( + "WARP\\Create\\From File(s)", + "Creates a signature file containing all selected functions", + create::CreateFromFiles {}, ); - binaryninja::command::register_command( - "WARP\\Generate Signature File", - "Generates a signature file containing all binary view functions", - create::CreateSignatureFile {}, + register_command( + "WARP\\Show Report", + "Creates a report for the selected file, displaying info on functions and types", + file::ShowFileReport {}, ); - binaryninja::command::register_command_for_function( - "WARP\\Add Function Signature to File", - "Stores the signature for the function in the signature file", - add::AddFunctionSignature {}, + register_command_for_project( + "WARP\\Create\\From Project", + "Create signature files from select project files", + project::CreateSignatures {}, ); true diff --git a/plugins/warp/src/plugin/add.rs b/plugins/warp/src/plugin/add.rs deleted file mode 100644 index 2f76c8b5..00000000 --- a/plugins/warp/src/plugin/add.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::cache::{cached_function, cached_type_references}; -use crate::matcher::invalidate_function_matcher_cache; -use crate::user_signature_dir; -use binaryninja::binary_view::BinaryView; -use binaryninja::command::FunctionCommand; -use binaryninja::function::Function; -use std::thread; - -pub struct AddFunctionSignature; - -impl FunctionCommand for AddFunctionSignature { - fn action(&self, view: &BinaryView, func: &Function) { - let func_plat_name = func.platform().name().to_string(); - let signature_dir = user_signature_dir().join(func_plat_name); - let view = view.to_owned(); - let func = func.to_owned(); - thread::spawn(move || { - let Ok(llil) = func.low_level_il() else { - log::error!("Could not get low level IL for function."); - return; - }; - - // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API. - // If we did need to save signature files to a project than this would need to change. - let Some(save_file) = rfd::FileDialog::new() - .add_filter("Signature Files", &["sbin"]) - .set_file_name("user.sbin") - .set_directory(signature_dir) - .save_file() - else { - return; - }; - - let mut data = warp::signature::Data::default(); - if let Ok(file_bytes) = std::fs::read(&save_file) { - // If the file we are adding the function to already has data we should preserve it! - log::info!("Signature file already exists, preserving data..."); - let Some(file_data) = warp::signature::Data::from_bytes(&file_bytes) else { - log::error!("Could not get data from signature file: {:?}", save_file); - return; - }; - data = file_data; - }; - - // Now add our function to the data. - data.functions.push(cached_function(&func, &llil)); - - if let Some(ref_ty_cache) = cached_type_references(&view) { - let referenced_types = ref_ty_cache - .cache - .iter() - .filter_map(|t| t.to_owned()) - .collect::<Vec<_>>(); - - data.types.extend(referenced_types); - } - - match std::fs::write(&save_file, data.to_bytes()) { - Ok(_) => { - log::info!("Signature file saved successfully."); - // Force rebuild platform matcher. - invalidate_function_matcher_cache(); - } - Err(e) => log::error!("Failed to write data to signature file: {:?}", e), - } - }); - } - - fn valid(&self, _view: &BinaryView, _func: &Function) -> bool { - true - } -} diff --git a/plugins/warp/src/plugin/copy.rs b/plugins/warp/src/plugin/copy.rs deleted file mode 100644 index b9b985fc..00000000 --- a/plugins/warp/src/plugin/copy.rs +++ /dev/null @@ -1,29 +0,0 @@ -use binaryninja::binary_view::BinaryView; -use binaryninja::command::FunctionCommand; -use binaryninja::function::Function; - -use crate::cache::cached_function_guid; - -pub struct CopyFunctionGUID; - -impl FunctionCommand for CopyFunctionGUID { - fn action(&self, _view: &BinaryView, func: &Function) { - let Ok(llil) = func.low_level_il() else { - log::error!("Could not get low level il for copied function"); - return; - }; - let guid = cached_function_guid(func, &llil); - log::info!( - "Function GUID for {:?}... {}", - func.symbol().short_name(), - guid - ); - if let Ok(mut clipboard) = arboard::Clipboard::new() { - let _ = clipboard.set_text(guid.to_string()); - } - } - - fn valid(&self, _view: &BinaryView, _func: &Function) -> bool { - true - } -} diff --git a/plugins/warp/src/plugin/create.rs b/plugins/warp/src/plugin/create.rs index 1dd83e6e..9ebcba2d 100644 --- a/plugins/warp/src/plugin/create.rs +++ b/plugins/warp/src/plugin/create.rs @@ -1,94 +1,232 @@ -use crate::cache::{cached_function, cached_type_references}; -use crate::matcher::invalidate_function_matcher_cache; -use crate::user_signature_dir; +use crate::processor::{ + new_processing_state_background_thread, CompressionTypeField, FileDataKindField, + IncludedFunctionsField, SaveReportToDiskField, WarpFileProcessor, +}; +use crate::report::{ReportGenerator, ReportKindField}; +use crate::{user_signature_dir, INCLUDE_TAG_NAME}; +use binaryninja::background_task::BackgroundTask; use binaryninja::binary_view::{BinaryView, BinaryViewExt}; use binaryninja::command::Command; -use binaryninja::function::Function; -use binaryninja::rc::Guard; -use rayon::prelude::*; -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering::Relaxed; +use binaryninja::interaction::form::{Form, FormInputField}; +use binaryninja::interaction::{MessageBoxButtonResult, MessageBoxButtonSet, MessageBoxIcon}; +use binaryninja::rc::Ref; +use std::path::PathBuf; use std::thread; -use std::time::Instant; +use warp::chunk::Chunk; +use warp::WarpFile; -pub struct CreateSignatureFile; +pub struct SaveFileField; -// TODO: Prompt the user to add the newly created signature file to the signature blacklist (so that it doesn't keep getting applied) +impl SaveFileField { + pub fn field(view: &BinaryView) -> FormInputField { + let default_name = view + .file() + .filename() + .split('/') + .last() + .unwrap_or("file") + .to_string(); + let signature_dir = user_signature_dir(); + let default_file_path = signature_dir.join(&default_name).with_extension("warp"); + FormInputField::SaveFileName { + prompt: "File Path".to_string(), + // TODO: This is called extension but is really a filter. + extension: Some("*.warp".to_string()), + default_name: Some(default_name), + default: Some(default_file_path.to_string_lossy().to_string()), + value: None, + } + } -impl Command for CreateSignatureFile { - fn action(&self, view: &BinaryView) { - let is_function_named = |f: &Guard<Function>| { - !f.symbol().short_name().to_string_lossy().contains("sub_") || f.has_user_annotations() - }; - let mut signature_dir = user_signature_dir(); - if let Some(default_plat) = view.default_platform() { - // If there is a default platform, put the signature in there. - // TODO: We should instead use the platform of the function. - signature_dir.push(default_plat.name().to_string()); + pub fn from_form(form: &Form) -> Option<PathBuf> { + let field = form.get_field_with_name("File Path")?; + let field_value = field.try_value_string()?; + Some(PathBuf::from(field_value)) + } +} + +pub struct OpenFileField; + +impl OpenFileField { + pub fn field() -> FormInputField { + FormInputField::OpenFileName { + prompt: "Input File Path".to_string(), + extension: None, + default: None, + value: None, } - let view = view.to_owned(); - thread::spawn(move || { - let total_functions = view.functions().len(); - let done_functions = AtomicUsize::default(); - let background_task = binaryninja::background_task::BackgroundTask::new( - &format!("Generating signatures... ({}/{})", 0, total_functions), - true, - ); + } + + pub fn from_form(form: &Form) -> Option<PathBuf> { + let field = form.get_field_with_name("Input File Path")?; + let field_value = field.try_value_string()?; + Some(PathBuf::from(field_value)) + } +} + +pub struct CreateFromCurrentView; - let start = Instant::now(); +impl CreateFromCurrentView { + pub fn execute(view: Ref<BinaryView>, external_file: bool) -> Option<()> { + // Prompt the user first so that they can go do other things and not worry about a popup. + let mut form = Form::new("Create From View"); - let mut data = warp::signature::Data::default(); - data.functions.par_extend( - view.functions() - .par_iter() - .inspect(|_| { - done_functions.fetch_add(1, Relaxed); - background_task.set_progress_text(&format!( - "Generating signatures... ({}/{})", - done_functions.load(Relaxed), - total_functions - )) - }) - .filter(is_function_named) - .filter(|f| !f.analysis_skipped()) - .filter_map(|func| { - let llil = func.low_level_il().ok()?; - Some(cached_function(&func, &llil)) - }), + if external_file { + form.add_field(OpenFileField::field()); + } + + form.add_field(SaveFileField::field(&view)); + + let fd_field = FileDataKindField::default(); + form.add_field(fd_field.to_field()); + + let compression_field = CompressionTypeField::default(); + form.add_field(compression_field.to_field()); + + let mut included_field = IncludedFunctionsField::default(); + // If the view has the include tag, we better set the default to the selected functions. + if view.tag_type_by_name(INCLUDE_TAG_NAME).is_some() { + included_field = IncludedFunctionsField::Selected; + } + form.add_field(included_field.to_field()); + + let report_field = ReportKindField::default(); + form.add_field(report_field.to_field()); + let report_to_disk_field = SaveReportToDiskField::default(); + form.add_field(report_to_disk_field.to_field()); + + if !form.prompt() { + return None; + } + let compression_type = CompressionTypeField::from_form(&form).unwrap_or_default(); + let file_path = SaveFileField::from_form(&form)?; + let file_data_kind = FileDataKindField::from_form(&form).unwrap_or_default(); + let file_included_functions = IncludedFunctionsField::from_form(&form).unwrap_or_default(); + let report_kind = ReportKindField::from_form(&form).unwrap_or_default(); + let save_report_to_disk = SaveReportToDiskField::from_form(&form).unwrap_or_default(); + let open_file_path = OpenFileField::from_form(&form); + + // If we already have a file, prompt the user if they want to add the data. + let mut existing_chunks = Vec::new(); + if file_path.exists() { + let prompt_result = binaryninja::interaction::show_message_box( + "Keep existing file data?", + "The file already exists. Do you want to keep the existing data?", + MessageBoxButtonSet::YesNoCancelButtonSet, + MessageBoxIcon::QuestionIcon, ); - if let Some(ref_ty_cache) = cached_type_references(&view) { - let referenced_types = ref_ty_cache - .cache - .iter() - .filter_map(|t| t.to_owned()) - .collect::<Vec<_>>(); + match prompt_result { + MessageBoxButtonResult::NoButton => { + // User wants to overwrite the file. + } + MessageBoxButtonResult::YesButton | MessageBoxButtonResult::OKButton => { + // User wants to keep the existing data. + let data = std::fs::read(&file_path).ok()?; + let existing_file = WarpFile::from_owned_bytes(data)?; + existing_chunks.extend(existing_file.chunks); + } + MessageBoxButtonResult::CancelButton => { + log::info!( + "User cancelled signature file creation, no operations were performed." + ); + return None; + } + } + } + + let processor = WarpFileProcessor::new() + .with_compression_type(compression_type) + .with_file_data(file_data_kind) + .with_included_functions(file_included_functions); - data.types.extend(referenced_types); + let file = match open_file_path { + None => { + // We are processing the current view. NOT an external file. + // Reference path is just used for the state tracking. Does not need to be readable. + let reference_path = file_path.clone(); + processor.process_view(reference_path, &view) + } + Some(open_file_path) => { + // This thread will show the state in a background task. + let background_task = BackgroundTask::new("Processing started...", true); + new_processing_state_background_thread(background_task.clone(), processor.state()); + let file = processor.process(open_file_path); + background_task.finish(); + file } + }; + + if let Err(err) = file { + binaryninja::interaction::show_message_box( + "Error", + &format!("Failed to create signature file: {}", err), + MessageBoxButtonSet::OKButtonSet, + MessageBoxIcon::ErrorIcon, + ); + log::error!("Failed to create signature file: {}", err); + return None; + } - log::info!("Signature generation took {:?}", start.elapsed()); - background_task.finish(); + let mut file = file.unwrap(); + // Add back the existing chunks if the user selected to keep them. + file.chunks.extend(existing_chunks); + // TODO: Make merging optional? + file.chunks = Chunk::merge(&file.chunks, compression_type.into()); + + if std::fs::write(&file_path, file.to_bytes()).is_err() { + log::error!("Failed to write data to signature file!"); + } - // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API. - // If we did need to save signature files to a project than this would need to change. - let Some(save_file) = rfd::FileDialog::new() - .add_filter("Signature Files", &["sbin"]) - .set_file_name(format!("{}.sbin", view.file().filename())) - .set_directory(signature_dir) - .save_file() - else { - return; - }; + // Show a report of the generate signatures, if desired. + let report_generator = ReportGenerator::new(); + if let Some(report_string) = report_generator.report(&report_kind, &file) { + if save_report_to_disk == SaveReportToDiskField::Yes { + let report_ext = report_generator + .report_extension(&report_kind) + .unwrap_or_default(); + let report_path = file_path.with_extension(report_ext); + let _ = std::fs::write(report_path, &report_string); + } - match std::fs::write(&save_file, data.to_bytes()) { - Ok(_) => { - log::info!("Signature file saved successfully."); - // Force rebuild platform matcher. - invalidate_function_matcher_cache(); + match report_kind { + ReportKindField::None => {} + ReportKindField::Html => { + view.show_html_report("Generated WARP File", report_string.as_str(), ""); + } + ReportKindField::Markdown => { + view.show_markdown_report("Generated WARP File", report_string.as_str(), ""); + } + ReportKindField::Json => { + view.show_plaintext_report("Generated WARP File", report_string.as_str()); } - Err(e) => log::error!("Failed to write data to signature file: {:?}", e), } + } + + Some(()) + } +} + +impl Command for CreateFromCurrentView { + fn action(&self, view: &BinaryView) { + let view = view.to_owned(); + thread::spawn(move || { + CreateFromCurrentView::execute(view, false); + }); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} + +pub struct CreateFromFiles; + +impl Command for CreateFromFiles { + fn action(&self, view: &BinaryView) { + let view = view.to_owned(); + thread::spawn(move || { + CreateFromCurrentView::execute(view, true); }); } diff --git a/plugins/warp/src/plugin/debug.rs b/plugins/warp/src/plugin/debug.rs new file mode 100644 index 00000000..fc4e5817 --- /dev/null +++ b/plugins/warp/src/plugin/debug.rs @@ -0,0 +1,48 @@ +use crate::cache::container::for_cached_containers; +use crate::{build_function, cache}; +use binaryninja::binary_view::BinaryView; +use binaryninja::command::{Command, FunctionCommand}; +use binaryninja::function::Function; +use binaryninja::ObjectDestructor; + +pub struct DebugFunction; + +impl FunctionCommand for DebugFunction { + fn action(&self, _view: &BinaryView, func: &Function) { + if let Ok(lifted_il) = func.lifted_il() { + log::info!("{:#?}", build_function(func, &lifted_il)); + } + } + + fn valid(&self, _view: &BinaryView, _func: &Function) -> bool { + true + } +} + +pub struct DebugCache; + +impl Command for DebugCache { + fn action(&self, _view: &BinaryView) { + for_cached_containers(|c| { + log::info!("Container: {:#?}", c); + }); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} + +pub struct DebugInvalidateCache; + +impl Command for DebugInvalidateCache { + fn action(&self, view: &BinaryView) { + let destructor = cache::CacheDestructor {}; + destructor.destruct_view(view); + log::info!("Invalidated all WARP caches..."); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} diff --git a/plugins/warp/src/plugin/ffi.rs b/plugins/warp/src/plugin/ffi.rs new file mode 100644 index 00000000..d78ca3c5 --- /dev/null +++ b/plugins/warp/src/plugin/ffi.rs @@ -0,0 +1,205 @@ +mod container; +mod function; + +use binaryninjacore_sys::{ + BNBasicBlock, BNBinaryView, BNFunction, BNLowLevelILFunction, BNPlatform, +}; +use std::ffi::c_char; +use std::sync::{Arc, RwLock}; +use uuid::Uuid; + +use binaryninja::basic_block::{BasicBlock, BasicBlockType}; +use binaryninja::function::{Function, NativeBlock}; + +use crate::cache::cached_function_guid; +use crate::container::{Container, SourceId}; +use crate::convert::platform_to_target; +use crate::plugin::workflow::run_matcher; +use crate::{ + basic_block_guid, is_blacklisted_instruction, is_computed_variant_instruction, + is_variant_instruction, relocatable_regions, +}; +use binaryninja::binary_view::BinaryView; +use binaryninja::low_level_il::function::{LowLevelILFunction, Mutable, NonSSA}; +use binaryninja::low_level_il::instruction::LowLevelInstructionIndex; +use binaryninja::platform::Platform; +use binaryninja::string::BnString; +use warp::r#type::guid::TypeGUID; +use warp::signature::basic_block::BasicBlockGUID; +use warp::signature::constraint::{Constraint, ConstraintGUID, UNRELATED_OFFSET}; +use warp::signature::function::FunctionGUID; + +/// [`SourceId`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI. +pub type BNWARPSource = SourceId; + +/// [`BasicBlockGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI. +pub type BNWARPBasicBlockGUID = BasicBlockGUID; + +/// [`ConstraintGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI. +pub type BNWARPConstraintGUID = ConstraintGUID; + +/// [`FunctionGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI. +pub type BNWARPFunctionGUID = FunctionGUID; + +/// [`TypeGUID`] is marked transparent to the underlying `[u8; 16]`, safe to use directly in FFI. +pub type BNWARPTypeGUID = TypeGUID; + +pub type BNWARPTarget = warp::target::Target; +pub type BNWARPFunction = warp::signature::function::Function; +pub type BNWARPContainer = RwLock<Box<dyn Container>>; + +// TODO: Some sort of callback for loading functions +// TODO: Be able to run matcher for a specific file +// TODO: Generate signatures for a file, return what? + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct BNWARPConstraint { + guid: BNWARPConstraintGUID, + offset: i64, +} + +impl From<BNWARPConstraint> for Constraint { + fn from(constraint: BNWARPConstraint) -> Self { + Constraint { + guid: constraint.guid, + offset: match constraint.offset { + UNRELATED_OFFSET => None, + _ => Some(constraint.offset), + }, + } + } +} + +impl From<Constraint> for BNWARPConstraint { + fn from(constraint: Constraint) -> Self { + BNWARPConstraint { + guid: constraint.guid, + offset: constraint.offset.unwrap_or(UNRELATED_OFFSET), + } + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPUUIDGetString(uuid: *const Uuid) -> *mut c_char { + let uuid_str = (*uuid).to_string(); + // NOTE: Leak the uuid string to be freed by BNFreeString + BnString::into_raw(uuid_str.into()) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPUUIDEqual(a: *const Uuid, b: *const Uuid) -> bool { + (*a) == (*b) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPRunMatcher(view: *mut BNBinaryView) { + let view = BinaryView::from_raw(view); + run_matcher(&view) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPGetBasicBlockGUID( + basic_block: *mut BNBasicBlock, + result: *mut BNWARPBasicBlockGUID, +) -> bool { + let basic_block = unsafe { BasicBlock::from_raw(basic_block, NativeBlock::new()) }; + if basic_block.block_type() != BasicBlockType::Native { + return false; + } + let function = basic_block.function(); + match function.lifted_il() { + Ok(lifted_il) => { + let relocatable_regions = relocatable_regions(&function.view()); + *result = basic_block_guid(&relocatable_regions, &basic_block, &lifted_il); + true + } + Err(_) => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPGetAnalysisFunctionGUID( + analysis_function: *mut BNFunction, + result: *mut BNWARPFunctionGUID, +) -> bool { + let function = unsafe { Function::from_raw(analysis_function) }; + match function.lifted_il() { + Ok(lifted_il) => { + *result = cached_function_guid(&function, &lifted_il); + true + } + Err(_) => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPIsLiftedInstructionVariant( + analysis_function: *mut BNLowLevelILFunction, + index: LowLevelInstructionIndex, +) -> bool { + let lifted_il: LowLevelILFunction<Mutable, NonSSA> = + unsafe { LowLevelILFunction::from_raw(analysis_function) }; + match lifted_il.instruction_from_index(index) { + Some(instr) => { + let relocatable_regions = relocatable_regions(&lifted_il.function().view()); + is_variant_instruction(&relocatable_regions, &instr) + } + None => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPIsLowLevelInstructionComputedVariant( + analysis_function: *mut BNLowLevelILFunction, + index: LowLevelInstructionIndex, +) -> bool { + let llil: LowLevelILFunction<Mutable, NonSSA> = + unsafe { LowLevelILFunction::from_raw(analysis_function) }; + match llil.instruction_from_index(index) { + Some(instr) => { + let relocatable_regions = relocatable_regions(&llil.function().view()); + is_computed_variant_instruction(&relocatable_regions, &instr) + } + None => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPIsLiftedInstructionBlacklisted( + analysis_function: *mut BNLowLevelILFunction, + index: LowLevelInstructionIndex, +) -> bool { + let lifted_il: LowLevelILFunction<Mutable, NonSSA> = + unsafe { LowLevelILFunction::from_raw(analysis_function) }; + match lifted_il.instruction_from_index(index) { + Some(instr) => is_blacklisted_instruction(&instr), + None => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeUUIDList(uuids: *mut Uuid, count: usize) { + let sources_ptr = std::ptr::slice_from_raw_parts_mut(uuids, count); + let _ = unsafe { Box::from_raw(sources_ptr) }; +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPGetTarget(platform: *mut BNPlatform) -> *mut BNWARPTarget { + let platform = Platform::from_raw(platform); + Arc::into_raw(Arc::new(platform_to_target(&platform))) as *mut BNWARPTarget +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPNewTargetReference(target: *mut BNWARPTarget) -> *mut BNWARPTarget { + Arc::increment_strong_count(target); + target +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeTargetReference(target: *mut BNWARPTarget) { + if target.is_null() { + return; + } + Arc::decrement_strong_count(target); +} diff --git a/plugins/warp/src/plugin/ffi/container.rs b/plugins/warp/src/plugin/ffi/container.rs new file mode 100644 index 00000000..21ad4dc0 --- /dev/null +++ b/plugins/warp/src/plugin/ffi/container.rs @@ -0,0 +1,412 @@ +use crate::cache::container::cached_containers; +use crate::container::SourcePath; +use crate::convert::{from_bn_type, to_bn_type}; +use crate::plugin::ffi::{ + BNWARPContainer, BNWARPFunction, BNWARPFunctionGUID, BNWARPSource, BNWARPTarget, BNWARPTypeGUID, +}; +use binaryninja::architecture::CoreArchitecture; +use binaryninja::binary_view::BinaryView; +use binaryninja::rc::Ref; +use binaryninja::string::BnString; +use binaryninja::types::Type; +use binaryninjacore_sys::{BNArchitecture, BNBinaryView, BNType}; +use std::ffi::{c_char, CStr}; +use std::mem::ManuallyDrop; +use std::sync::Arc; + +#[no_mangle] +pub unsafe extern "C" fn BNWARPGetContainers(count: *mut usize) -> *mut *mut BNWARPContainer { + // NOTE: Leak the arc pointers to be freed by BNWARPFreeContainerList + let boxed_raw_containers: Box<[_]> = + cached_containers().into_iter().map(Arc::into_raw).collect(); + *count = boxed_raw_containers.len(); + let leaked_raw_containers = Box::into_raw(boxed_raw_containers); + leaked_raw_containers as *mut *mut BNWARPContainer +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerGetName(container: *mut BNWARPContainer) -> *const c_char { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(container) = arc_container.read() else { + return std::ptr::null(); + }; + let name = container.to_string(); + // NOTE: Leak the container name to be freed by BNFreeString + BnString::into_raw(name.into()) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerGetSources( + container: *mut BNWARPContainer, + count: *mut usize, +) -> *mut BNWARPSource { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(container) = arc_container.write() else { + return std::ptr::null_mut(); + }; + + // NOTE: Leak the sources to be freed by BNWARPFreeSourceList + let boxed_sources: Box<[_]> = container.sources().unwrap_or_default().into_boxed_slice(); + *count = boxed_sources.len(); + Box::into_raw(boxed_sources) as *mut BNWARPSource +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerAddSource( + container: *mut BNWARPContainer, + source_path: *const c_char, + result: *mut BNWARPSource, +) -> bool { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(mut container) = arc_container.write() else { + return false; + }; + + let source_path_cstr = unsafe { CStr::from_ptr(source_path) }; + let source_path_str = source_path_cstr.to_str().unwrap(); + let source_path = SourcePath::new_with_str(source_path_str); + + match container.add_source(source_path) { + Ok(source) => { + // NOTE: Leak the source to be freed by BNFreeString + *result = source; + true + } + Err(_) => false, + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerCommitSource( + container: *mut BNWARPContainer, + source: *const BNWARPSource, +) -> bool { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(mut container) = arc_container.write() else { + return false; + }; + + let source = unsafe { *source }; + + container + .commit_source(&source) + .is_ok_and(|committed| committed) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerIsSourceUncommitted( + container: *mut BNWARPContainer, + source: *const BNWARPSource, +) -> bool { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(container) = arc_container.read() else { + return false; + }; + + let source = unsafe { *source }; + + container + .is_source_uncommitted(&source) + .is_ok_and(|uncommitted| uncommitted) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerIsSourceWritable( + container: *mut BNWARPContainer, + source: *const BNWARPSource, +) -> bool { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(container) = arc_container.read() else { + return false; + }; + + let source = unsafe { *source }; + + container + .is_source_writable(&source) + .is_ok_and(|writable| writable) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerGetSourcePath( + container: *mut BNWARPContainer, + source: *const BNWARPSource, +) -> *const c_char { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(container) = arc_container.read() else { + return std::ptr::null(); + }; + + let source = unsafe { *source }; + + match container.source_path(&source) { + Ok(path) => { + let path = path.to_string(); + // NOTE: Leak the source path to be freed by BNFreeString + BnString::into_raw(path.into()) + } + Err(_) => std::ptr::null(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerAddFunctions( + container: *mut BNWARPContainer, + target: *mut BNWARPTarget, + source: *const BNWARPSource, + functions: *mut *mut BNWARPFunction, + count: usize, +) -> bool { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(mut container) = arc_container.write() else { + return false; + }; + + let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) }; + + let source = unsafe { *source }; + + let functions_ptr = std::slice::from_raw_parts(functions, count); + // TODO: We have to clone the objects here to make the type checker happy. + // TODO: See about avoiding this later. + let functions: Vec<_> = functions_ptr + .iter() + .map(|&f| unsafe { ManuallyDrop::new(Arc::from_raw(f)).as_ref().clone() }) + .collect(); + container + .add_functions(&target, &source, &functions) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerAddTypes( + view: *mut BNBinaryView, + container: *mut BNWARPContainer, + source: *const BNWARPSource, + types: *mut *mut BNType, + count: usize, +) -> bool { + let view = unsafe { BinaryView::from_raw(view) }; + + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(mut container) = arc_container.write() else { + return false; + }; + + let source = unsafe { *source }; + + let types_ptr = std::slice::from_raw_parts(types, count); + let types: Vec<_> = types_ptr + .iter() + .map(|&t| Type::from_raw(t)) + .map(|ty| from_bn_type(&view, &ty, 255)) + .collect(); + container.add_types(&source, &types).is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerRemoveFunctions( + container: *mut BNWARPContainer, + target: *mut BNWARPTarget, + source: *const BNWARPSource, + functions: *mut *mut BNWARPFunction, + count: usize, +) -> bool { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(mut container) = arc_container.write() else { + return false; + }; + + let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) }; + + let source = unsafe { *source }; + + let functions_ptr = std::slice::from_raw_parts(functions, count); + // TODO: We have to clone the objects here to make the type checker happy. + // TODO: See about avoiding this later. + let functions: Vec<_> = functions_ptr + .iter() + .map(|&f| unsafe { ManuallyDrop::new(Arc::from_raw(f)).as_ref().clone() }) + .collect(); + container + .remove_functions(&target, &source, &functions) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerRemoveTypes( + container: *mut BNWARPContainer, + source: *const BNWARPSource, + guids: *mut BNWARPTypeGUID, + count: usize, +) -> bool { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(mut container) = arc_container.write() else { + return false; + }; + + let source = unsafe { *source }; + + let guids = std::slice::from_raw_parts(guids, count); + container.remove_types(&source, &guids).is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerGetSourcesWithFunctionGUID( + container: *mut BNWARPContainer, + target: *mut BNWARPTarget, + guid: *const BNWARPFunctionGUID, + count: *mut usize, +) -> *mut BNWARPSource { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(container) = arc_container.read() else { + return std::ptr::null_mut(); + }; + + let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) }; + + let guid = unsafe { *guid }; + + // NOTE: Leak the sources to be freed by BNWARPFreeSourceList + let boxed_sources: Box<[_]> = container + .sources_with_function_guid(&target, &guid) + .unwrap_or_default() + .into_boxed_slice(); + *count = boxed_sources.len(); + Box::into_raw(boxed_sources) as *mut BNWARPSource +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerGetSourcesWithTypeGUID( + container: *mut BNWARPContainer, + guid: *const BNWARPTypeGUID, + count: *mut usize, +) -> *mut BNWARPSource { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(container) = arc_container.read() else { + return std::ptr::null_mut(); + }; + + let guid = unsafe { *guid }; + + // NOTE: Leak the sources to be freed by BNWARPFreeSourceList + let boxed_sources: Box<[_]> = container + .sources_with_type_guid(&guid) + .unwrap_or_default() + .into_boxed_slice(); + *count = boxed_sources.len(); + Box::into_raw(boxed_sources) as *mut BNWARPSource +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerGetFunctionsWithGUID( + container: *mut BNWARPContainer, + target: *mut BNWARPTarget, + source: *const BNWARPSource, + guid: *const BNWARPFunctionGUID, + count: *mut usize, +) -> *mut *mut BNWARPFunction { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(container) = arc_container.read() else { + return std::ptr::null_mut(); + }; + + let source = unsafe { *source }; + + let target = unsafe { ManuallyDrop::new(Arc::from_raw(target)) }; + + let guid = unsafe { *guid }; + + // NOTE: Leak the functions to be freed by BNWARPFreeFunctionList + let raw_boxed_functions: Box<[_]> = container + .functions_with_guid(&target, &source, &guid) + .unwrap_or_default() + .into_iter() + .map(Arc::new) + .map(Arc::into_raw) + .collect(); + *count = raw_boxed_functions.len(); + Box::into_raw(raw_boxed_functions) as *mut *mut BNWARPFunction +} + +// TODO: Swap arch to Target? +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerGetTypeWithGUID( + arch: *mut BNArchitecture, + container: *mut BNWARPContainer, + source: *const BNWARPSource, + guid: *const BNWARPTypeGUID, +) -> *mut BNType { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(container) = arc_container.read() else { + return std::ptr::null_mut(); + }; + + // NOTE: to convert the type, we must have an architecture. + let arch = CoreArchitecture::from_raw(arch); + + let source = unsafe { *source }; + + let guid = unsafe { *guid }; + + let Some(ty) = container.type_with_guid(&source, &guid).unwrap_or_default() else { + return std::ptr::null_mut(); + }; + let function_type = to_bn_type(&arch, &ty); + // NOTE: The type ref has been pre-incremented for the caller. + unsafe { Ref::into_raw(function_type) }.handle +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPContainerGetTypeGUIDsWithName( + container: *mut BNWARPContainer, + source: *const BNWARPSource, + name: *const c_char, + count: *mut usize, +) -> *mut BNWARPTypeGUID { + let arc_container = ManuallyDrop::new(Arc::from_raw(container)); + let Ok(container) = arc_container.read() else { + return std::ptr::null_mut(); + }; + + let source = unsafe { *source }; + + let name_cstr = unsafe { CStr::from_ptr(name) }; + let name = name_cstr.to_str().unwrap(); + + // NOTE: Leak the guids to be freed by BNWARPFreeTypeGUIDList + let boxed_guids = container + .type_guids_with_name(&source, name) + .unwrap_or_default() + .into_boxed_slice(); + *count = boxed_guids.len(); + Box::into_raw(boxed_guids) as *mut BNWARPTypeGUID +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPNewContainerReference( + container: *mut BNWARPContainer, +) -> *mut BNWARPContainer { + Arc::increment_strong_count(container); + container +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeContainerReference(container: *mut BNWARPContainer) { + if container.is_null() { + return; + } + Arc::decrement_strong_count(container); +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeContainerList( + containers: *mut *mut BNWARPContainer, + count: usize, +) { + let containers_ptr = std::ptr::slice_from_raw_parts_mut(containers, count); + let containers = unsafe { Box::from_raw(containers_ptr) }; + for container in containers { + BNWARPFreeContainerReference(container); + } +} diff --git a/plugins/warp/src/plugin/ffi/function.rs b/plugins/warp/src/plugin/ffi/function.rs new file mode 100644 index 00000000..3db8f307 --- /dev/null +++ b/plugins/warp/src/plugin/ffi/function.rs @@ -0,0 +1,228 @@ +use crate::build_function; +use crate::cache::{insert_cached_function_match, try_cached_function_match}; +use crate::convert::{to_bn_symbol_at_address, to_bn_type}; +use crate::plugin::ffi::{BNWARPConstraint, BNWARPFunction, BNWARPFunctionGUID}; +use binaryninja::function::Function; +use binaryninja::rc::Ref; +use binaryninja::string::BnString; +use binaryninjacore_sys::{BNFunction, BNSymbol, BNType}; +use std::ffi::c_char; +use std::mem::ManuallyDrop; +use std::sync::Arc; +use warp::signature::comment::FunctionComment; + +#[repr(C)] +pub struct BNWarpFunctionComment { + pub text: *mut c_char, + pub offset: i64, +} + +impl BNWarpFunctionComment { + /// Leaks the text string to be freed with BNWARPFreeFunctionComment + pub fn from_owned(value: &FunctionComment) -> Self { + let text = BnString::into_raw(BnString::new(&value.text)); + Self { + text, + offset: value.offset, + } + } + + pub unsafe fn free_raw(value: &Self) { + unsafe { BnString::free_raw(value.text) } + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPGetFunction( + analysis_function: *mut BNFunction, +) -> *mut BNWARPFunction { + let function = Function::from_raw(analysis_function); + let Ok(lifted_il) = function.lifted_il() else { + return std::ptr::null_mut(); + }; + let function = build_function(&function, &lifted_il); + Arc::into_raw(Arc::new(function)) as *mut BNWARPFunction +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPGetMatchedFunction( + analysis_function: *mut BNFunction, +) -> *mut BNWARPFunction { + let function = Function::from_raw(analysis_function); + match try_cached_function_match(&function) { + Some(matched_function) => { + let arc_matched_function = Arc::new(matched_function); + // NOTE: Freed by BNWARPFreeFunctionReference + Arc::into_raw(arc_matched_function) as *mut BNWARPFunction + } + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFunctionApply( + function: *mut BNWARPFunction, + analysis_function: *mut BNFunction, +) { + let analysis_function = Function::from_raw(analysis_function); + match function.is_null() { + false => { + // Set the matched function to `function` and return previous. + let matched_function = ManuallyDrop::new(Arc::from_raw(function)); + insert_cached_function_match( + &analysis_function, + Some(matched_function.as_ref().clone()), + ) + } + true => { + // We are removing the previous match and returning it. + insert_cached_function_match(&analysis_function, None) + } + }; +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFunctionGetGUID( + function: *mut BNWARPFunction, +) -> BNWARPFunctionGUID { + // We do not own function so we should not drop. + let function = ManuallyDrop::new(Arc::from_raw(function)); + function.guid +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFunctionGetSymbol( + function: *mut BNWARPFunction, + analysis_function: *mut BNFunction, +) -> *mut BNSymbol { + let analysis_function = Function::from_raw(analysis_function); + // We do not own function so we should not drop. + let function = ManuallyDrop::new(Arc::from_raw(function)); + let view = analysis_function.view(); + let address = analysis_function.symbol().address(); + let function_symbol = to_bn_symbol_at_address(&view, &function.symbol, address); + // NOTE: The symbol ref has been pre-incremented for the caller. + Ref::into_raw(function_symbol).handle +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFunctionGetSymbolName(function: *mut BNWARPFunction) -> *mut c_char { + // We do not own function so we should not drop. + let function = ManuallyDrop::new(Arc::from_raw(function)); + let bn_name = BnString::new(&function.symbol.name); + // NOTE: The symbol name string to be freed by BNFreeString + BnString::into_raw(bn_name) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFunctionGetType( + function: *mut BNWARPFunction, + analysis_function: *mut BNFunction, +) -> *mut BNType { + let analysis_function = Function::from_raw(analysis_function); + // We do not own function so we should not drop. + let function = ManuallyDrop::new(Arc::from_raw(function)); + match &function.ty { + Some(func_ty) => { + let arch = analysis_function.arch(); + let function_type = to_bn_type(&arch, func_ty); + // NOTE: The type ref has been pre-incremented for the caller. + unsafe { Ref::into_raw(function_type) }.handle + } + None => std::ptr::null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFunctionGetConstraints( + function: *mut BNWARPFunction, + count: *mut usize, +) -> *mut BNWARPConstraint { + // We do not own function so we should not drop. + let function = ManuallyDrop::new(Arc::from_raw(function)); + let raw_constraints: Box<[BNWARPConstraint]> = function + .constraints + .clone() + .into_iter() + .map(Into::into) + .collect(); + *count = raw_constraints.len(); + let raw_constraints_ptr = Box::into_raw(raw_constraints); + raw_constraints_ptr as *mut BNWARPConstraint +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFunctionGetComments( + function: *mut BNWARPFunction, + count: *mut usize, +) -> *mut BNWarpFunctionComment { + // We do not own function so we should not drop. + let function = ManuallyDrop::new(Arc::from_raw(function)); + let raw_comments: Box<[_]> = function + .comments + .iter() + .map(BNWarpFunctionComment::from_owned) + .collect(); + *count = raw_comments.len(); + let raw_comments_ptr = Box::into_raw(raw_comments); + raw_comments_ptr as *mut BNWarpFunctionComment +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFunctionsEqual( + function_a: *mut BNWARPFunction, + function_b: *mut BNWARPFunction, +) -> bool { + // We do not own function so we should not drop. + let function_a = ManuallyDrop::new(Arc::from_raw(function_a)); + // We do not own function so we should not drop. + let function_b = ManuallyDrop::new(Arc::from_raw(function_b)); + function_a.eq(&function_b) +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPNewFunctionReference( + function: *mut BNWARPFunction, +) -> *mut BNWARPFunction { + Arc::increment_strong_count(function); + function +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeFunctionReference(function: *mut BNWARPFunction) { + if function.is_null() { + return; + } + Arc::decrement_strong_count(function); +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeFunctionList(functions: *mut *mut BNWARPFunction, count: usize) { + let functions_ptr = std::ptr::slice_from_raw_parts_mut(functions, count); + let functions = Box::from_raw(functions_ptr); + for function in functions { + // NOTE: The functions themselves should also be arc. + BNWARPFreeFunctionReference(function); + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeFunctionCommentList( + comments: *mut BNWarpFunctionComment, + count: usize, +) { + let comments_ptr = std::ptr::slice_from_raw_parts_mut(comments, count); + let comments = Box::from_raw(comments_ptr); + for comment in &comments { + BNWarpFunctionComment::free_raw(comment) + } +} + +#[no_mangle] +pub unsafe extern "C" fn BNWARPFreeConstraintList( + constraints: *mut BNWARPConstraint, + count: usize, +) { + let constraints_ptr = std::ptr::slice_from_raw_parts_mut(constraints, count); + let _constraints = unsafe { Box::from_raw(constraints_ptr) }; +} diff --git a/plugins/warp/src/plugin/file.rs b/plugins/warp/src/plugin/file.rs new file mode 100644 index 00000000..ec61bd78 --- /dev/null +++ b/plugins/warp/src/plugin/file.rs @@ -0,0 +1,41 @@ +use crate::report::ReportGenerator; +use binaryninja::binary_view::{BinaryView, BinaryViewExt}; +use binaryninja::command::Command; + +pub struct ShowFileReport; + +impl Command for ShowFileReport { + fn action(&self, view: &BinaryView) { + let view = view.to_owned(); + std::thread::spawn(move || { + let Some(path) = + binaryninja::interaction::get_open_filename_input("Select file to show", "*.warp") + else { + return; + }; + + let Ok(bytes) = std::fs::read(&path) else { + log::error!("Failed to read file: {:?}", path); + return; + }; + + let Some(file) = warp::WarpFile::from_bytes(&bytes) else { + log::error!("Failed to parse file: {:?}", path); + return; + }; + + let report_generator = ReportGenerator::new(); + if let Some(html_string) = report_generator.html_report(&file) { + view.show_html_report( + &format!("WARP File: {}", path.to_string_lossy()), + html_string.as_str(), + "", + ); + } + }); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} diff --git a/plugins/warp/src/plugin/find.rs b/plugins/warp/src/plugin/find.rs deleted file mode 100644 index accc015d..00000000 --- a/plugins/warp/src/plugin/find.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::cache::try_cached_function_guid; -use binaryninja::binary_view::{BinaryView, BinaryViewExt}; -use binaryninja::command::Command; -use binaryninja::function::Function as BNFunction; -use binaryninja::rc::Guard as BNGuard; -use rayon::prelude::*; -use std::thread; -use warp::signature::function::FunctionGUID; - -pub struct FindFunctionFromGUID; - -impl Command for FindFunctionFromGUID { - fn action(&self, view: &BinaryView) { - let Some(guid_str) = binaryninja::interaction::get_text_line_input( - "Function GUID", - "Find Function from GUID", - ) else { - return; - }; - - let Ok(searched_guid) = guid_str.parse::<FunctionGUID>() else { - log::error!("Failed to parse function guid... {}", guid_str); - return; - }; - - log::info!("Searching functions for GUID... {}", searched_guid); - let funcs = view.functions(); - thread::spawn(move || { - let background_task = binaryninja::background_task::BackgroundTask::new( - &format!("Searching functions for GUID... {}", searched_guid), - false, - ); - - // Only run this for functions which have already generated a GUID. - let matched = funcs - .par_iter() - .filter(|func| { - try_cached_function_guid(func).is_some_and(|guid| guid == searched_guid) - }) - .collect::<Vec<BNGuard<BNFunction>>>(); - - if matched.is_empty() { - log::info!("No matches found for GUID... {}", searched_guid); - } else { - for func in matched { - log::info!("Match found at function... 0x{:0x}", func.start()); - } - } - - background_task.finish(); - }); - } - - fn valid(&self, _view: &BinaryView) -> bool { - true - } -} diff --git a/plugins/warp/src/plugin/function.rs b/plugins/warp/src/plugin/function.rs new file mode 100644 index 00000000..130e8582 --- /dev/null +++ b/plugins/warp/src/plugin/function.rs @@ -0,0 +1,133 @@ +use crate::cache::{cached_function_guid, try_cached_function_guid}; +use crate::{get_warp_include_tag_type, INCLUDE_TAG_NAME}; +use binaryninja::background_task::BackgroundTask; +use binaryninja::binary_view::{BinaryView, BinaryViewExt}; +use binaryninja::command::{Command, FunctionCommand}; +use binaryninja::function::Function; +use binaryninja::rc::Guard; +use rayon::iter::ParallelIterator; +use std::thread; +use warp::signature::function::FunctionGUID; + +pub struct IncludeFunction; + +impl FunctionCommand for IncludeFunction { + fn action(&self, view: &BinaryView, func: &Function) { + let sym_name = func.symbol().short_name(); + let sym_name_str = sym_name.to_string_lossy(); + let should_add_tag = func.function_tags(None, Some(INCLUDE_TAG_NAME)).is_empty(); + let insert_tag_type = get_warp_include_tag_type(view); + match should_add_tag { + true => { + log::info!( + "Including selected function '{}' at 0x{:x}", + sym_name_str, + func.start() + ); + func.add_tag(&insert_tag_type, "", None, false, None); + } + false => { + log::info!( + "Removing included function '{}' at 0x{:x}", + sym_name_str, + func.start() + ); + func.remove_tags_of_type(&insert_tag_type, None, false, None); + } + } + } + + fn valid(&self, _view: &BinaryView, _func: &Function) -> bool { + // TODO: Only allow if the function is named? + true + } +} + +pub struct CopyFunctionGUID; + +impl FunctionCommand for CopyFunctionGUID { + fn action(&self, _view: &BinaryView, func: &Function) { + let Ok(lifted_il) = func.lifted_il() else { + log::error!("Could not get lifted il for copied function"); + return; + }; + let guid = cached_function_guid(func, &lifted_il); + log::info!( + "Function GUID for {:?}... {}", + func.symbol().short_name(), + guid + ); + if let Ok(mut clipboard) = arboard::Clipboard::new() { + let _ = clipboard.set_text(guid.to_string()); + } + } + + fn valid(&self, _view: &BinaryView, _func: &Function) -> bool { + true + } +} + +pub struct FindFunctionFromGUID; + +impl Command for FindFunctionFromGUID { + fn action(&self, view: &BinaryView) { + let Some(guid_str) = binaryninja::interaction::get_text_line_input( + "Function GUID", + "Find Function from GUID", + ) else { + return; + }; + + let Ok(searched_guid) = guid_str.parse::<FunctionGUID>() else { + log::error!("Failed to parse function guid... {}", guid_str); + return; + }; + + log::info!("Searching functions for GUID... {}", searched_guid); + let funcs = view.functions(); + let view = view.to_owned(); + thread::spawn(move || { + let background_task = BackgroundTask::new( + &format!("Searching functions for GUID... {}", searched_guid), + false, + ); + + // Only run this for functions which have already generated a GUID. + let matched: Vec<Guard<Function>> = funcs + .par_iter() + .filter(|func| { + try_cached_function_guid(func).is_some_and(|guid| guid == searched_guid) + }) + .collect(); + + if matched.is_empty() { + log::info!("No matches found for GUID... {}", searched_guid); + } else { + for func in &matched { + // Also navigate the user, as that is probably what they want. + if matched.len() == 1 { + let current_view = view.file().current_view(); + if view + .file() + .navigate_to(¤t_view, func.start()) + .is_err() + { + log::error!( + "Failed to navigate to found function 0x{:0x} in view {}", + func.start(), + current_view + ); + } + } + log::info!("Match found at function... 0x{:0x}", func.start()); + } + } + + background_task.finish(); + }); + } + + fn valid(&self, _view: &BinaryView) -> bool { + true + } +} diff --git a/plugins/warp/src/plugin/load.rs b/plugins/warp/src/plugin/load.rs index c86a16cb..c0319ea5 100644 --- a/plugins/warp/src/plugin/load.rs +++ b/plugins/warp/src/plugin/load.rs @@ -1,54 +1,162 @@ -use crate::matcher::{Matcher, PlatformID, PLAT_MATCHER_CACHE}; +use crate::cache::container::add_cached_container; +use crate::container::disk::{DiskContainer, DiskContainerSource}; +use crate::container::{ContainerError, SourcePath}; +use crate::convert::platform_to_target; +use crate::plugin::workflow::run_matcher; use binaryninja::binary_view::{BinaryView, BinaryViewExt}; use binaryninja::command::Command; +use binaryninja::interaction::{ + show_message_box, Form, FormInputField, MessageBoxButtonResult, MessageBoxButtonSet, + MessageBoxIcon, +}; +use binaryninja::rc::Ref; +use std::collections::HashMap; +use std::path::PathBuf; +use std::thread; +use warp::WarpFile; + +pub struct LoadFileField; + +impl LoadFileField { + pub fn field() -> FormInputField { + FormInputField::OpenFileName { + prompt: "File Path".to_string(), + // TODO: This is called extension but is really a filter. + extension: Some("*.warp".to_string()), + default: None, + value: None, + } + } + + pub fn from_form(form: &Form) -> Option<PathBuf> { + let field = form.get_field_with_name("File Path")?; + let field_value = field.try_value_string()?; + Some(PathBuf::from(field_value)) + } +} + +pub struct RunMatcherField; + +impl RunMatcherField { + pub fn field() -> FormInputField { + FormInputField::Choice { + prompt: "Rerun Initial Matcher".to_string(), + choices: vec!["No".to_string(), "Yes".to_string()], + default: Some(1), + value: 0, + } + } + + pub fn from_form(form: &Form) -> Option<bool> { + let field = form.get_field_with_name("Rerun Initial Matcher")?; + let field_value = field.try_value_index()?; + match field_value { + 1 => Some(true), + _ => Some(false), + } + } +} + pub struct LoadSignatureFile; -impl Command for LoadSignatureFile { - fn action(&self, view: &BinaryView) { - let Some(platform) = view.default_platform() else { - log::error!("Default platform must be set to load signature!"); - return; - }; +impl LoadSignatureFile { + pub fn read_file( + view: &BinaryView, + path: SourcePath, + ) -> Result<WarpFile<'static>, ContainerError> { + let contents = std::fs::read(&path).map_err(|e| ContainerError::FailedIO(e.kind()))?; + let mut file = WarpFile::from_owned_bytes(contents).ok_or( + ContainerError::CorruptedData("file data failed to validate"), + )?; - // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API. - // If we did need to load signature files from a project than this would need to change. - let Some(file) = rfd::FileDialog::new() - .add_filter("Signature Files", &["sbin"]) - .set_file_name(format!("{}.sbin", view.file().filename())) - .pick_file() - else { - return; - }; + let view_target = view + .default_platform() + .map(|p| platform_to_target(&p)) + .unwrap_or_default(); + let file_has_target = file + .chunks + .iter() + .find(|c| c.header.target == view_target) + .is_some(); + + if !file_has_target { + // File does not contain a view target, alert user if they would like to override the file chunks to the view target. + let text = format!( + "Attempting to load WARP file with no target `{:?}`, continue loading anyways?", + &view_target + ); + let res = show_message_box( + "Override file target?", + &text, + MessageBoxButtonSet::YesNoButtonSet, + MessageBoxIcon::WarningIcon, + ); + if res != MessageBoxButtonResult::YesButton { + return Err(ContainerError::CorruptedData( + "User does not want to load file", + )); + } + + // Take all the chunks and convert them to the target, so we load them. + // If we do not do this, the user will be surprised when they get no new matches. + for chunk in &mut file.chunks { + chunk.header.target = view_target.clone(); + } + } - let Ok(data) = std::fs::read(&file) else { - log::error!("Could not read signature file: {:?}", file); + Ok(file) + } + + pub fn execute(view: Ref<BinaryView>) { + let mut form = Form::new("Load Signature File"); + form.add_field(LoadFileField::field()); + // let fd_field = FileDataKindField::default(); + // form.add_field(fd_field.to_field()); + form.add_field(RunMatcherField::field()); + if !form.prompt() { return; - }; + } - let Some(data) = warp::signature::Data::from_bytes(&data) else { - log::error!("Could not get data from signature file: {:?}", file); + let Some(file_path) = LoadFileField::from_form(&form) else { return; }; + // TODO: Decide what to pull using the file data kind. + // let _file_data_kind = FileDataKindField::from_form(&form).unwrap_or_default(); + let rerun_matcher = RunMatcherField::from_form(&form).unwrap_or(false); + + let source_file_path = SourcePath::new(file_path.clone()); - let new_matcher = Matcher::from_data(data); - log::info!( - "Loading signature file with {} functions and {} types...", - new_matcher.functions.len(), - new_matcher.types.len() - ); - let platform_id = PlatformID::from(platform.as_ref()); - let matcher_cache = PLAT_MATCHER_CACHE.get_or_init(Default::default); - match matcher_cache.get_mut(&platform_id) { - Some(mut matcher) => matcher.extend_with_matcher(new_matcher), - None => { - // We still must uphold `from_platform` in case we are running this before the matcher workflow - // is kicked off. Other-wise we only will have the `new_matcher` data. - let mut matcher = Matcher::from_platform(platform); - matcher.extend_with_matcher(new_matcher); - matcher_cache.insert(platform_id, matcher); + let file = match LoadSignatureFile::read_file(&view, source_file_path.clone()) { + Ok(file) => file, + Err(e) => { + log::error!("Failed to read signature file: {}", e); + return; } + }; + + let container_source = DiskContainerSource::new(source_file_path.clone(), file); + log::info!("Loading container source: '{}'", container_source.path); + let mut map = HashMap::new(); + map.insert(source_file_path.to_source_id(), container_source); + let container = DiskContainer::new("Loaded signatures".to_string(), map); + // TODO: See notes in the matcher about doing this, we really need to load it into an existing container. + add_cached_container(container); + + if rerun_matcher { + thread::spawn(move || { + run_matcher(&view); + }); } } +} + +impl Command for LoadSignatureFile { + fn action(&self, view: &BinaryView) { + let view = view.to_owned(); + thread::spawn(move || { + LoadSignatureFile::execute(view); + }); + } fn valid(&self, _view: &BinaryView) -> bool { true diff --git a/plugins/warp/src/plugin/project.rs b/plugins/warp/src/plugin/project.rs new file mode 100644 index 00000000..ea5a5600 --- /dev/null +++ b/plugins/warp/src/plugin/project.rs @@ -0,0 +1,132 @@ +use binaryninja::background_task::BackgroundTask; +use binaryninja::command::ProjectCommand; +use binaryninja::interaction::{Form, FormInputField}; +use binaryninja::project::Project; +use binaryninja::rc::Ref; +use regex::Regex; +use std::thread; +use std::time::Instant; + +use crate::processor::{ + new_processing_state_background_thread, FileDataKindField, FileFilterField, WarpFileProcessor, +}; +use crate::report::{ReportGenerator, ReportKindField}; + +pub struct CreateSignaturesForm { + form: Form, +} + +impl CreateSignaturesForm { + pub fn new(_project: &Project) -> CreateSignaturesForm { + let mut form = Form::new("Create Signature File"); + form.add_field(Self::file_data_field()); + form.add_field(Self::file_filter_field()); + form.add_field(Self::generated_report_field()); + // TODO: Threads (we run the analysis in the background) + Self { form } + } + + pub fn file_data_field() -> FormInputField { + FileDataKindField::default().to_field() + } + + pub fn file_data_kind(&self) -> FileDataKindField { + FileDataKindField::from_form(&self.form).unwrap_or_default() + } + + pub fn file_filter_field() -> FormInputField { + FileFilterField::to_field() + } + + pub fn file_filter(&self) -> Option<Regex> { + FileFilterField::from_form(&self.form) + } + + pub fn generated_report_field() -> FormInputField { + ReportKindField::default().to_field() + } + + pub fn generated_report_kind(&self) -> ReportKindField { + ReportKindField::from_form(&self.form).unwrap_or_default() + } + + pub fn prompt(&mut self) -> bool { + self.form.prompt() + } +} + +pub struct CreateSignatures; + +impl CreateSignatures { + pub fn execute(project: Ref<Project>) { + let mut form = CreateSignaturesForm::new(&project); + if !form.prompt() { + return; + } + let file_data_kind = form.file_data_kind(); + let report_kind = form.generated_report_kind(); + + let mut processor = WarpFileProcessor::new().with_file_data(file_data_kind); + + // This thread will show the state in a background task. + let background_task = BackgroundTask::new("Processing started...", true); + new_processing_state_background_thread(background_task.clone(), processor.state()); + + if let Some(filter) = form.file_filter() { + processor = processor.with_file_filter(filter); + } + + let start = Instant::now(); + match processor.process_project(&project) { + Ok(warp_file) => { + // Print the processor string into the description of the file, so we know how it was generated. + let processor_str = format!("{:#?}", &processor); + + // TODO: File name needs to be configurable. + if project + .create_file( + &warp_file.to_bytes(), + None, + "generated.warp", + &processor_str, + ) + .is_err() + { + log::error!("Failed to create project file!"); + } + + let report = ReportGenerator::new(); + if let Some(generated) = report.report(&report_kind, &warp_file) { + let ext = report.report_extension(&report_kind).unwrap_or_default(); + let file_name = format!("report.{}", ext); + if project + .create_file(&generated.into_bytes(), None, &file_name, "Warp file") + .is_err() + { + log::error!("Failed to create project file!"); + } + } + } + Err(e) => { + log::error!("Failed to process project: {}", e); + } + } + log::info!("Processing project files took: {:?}", start.elapsed()); + + // Tells the processing state thread to finish. + background_task.finish(); + } +} + +impl ProjectCommand for CreateSignatures { + fn action(&self, project: &Project) { + let project = project.to_owned(); + thread::spawn(move || { + CreateSignatures::execute(project); + }); + } + + fn valid(&self, _view: &Project) -> bool { + true + } +} diff --git a/plugins/warp/src/plugin/render_layer.rs b/plugins/warp/src/plugin/render_layer.rs index cdac71f2..d7984303 100644 --- a/plugins/warp/src/plugin/render_layer.rs +++ b/plugins/warp/src/plugin/render_layer.rs @@ -1,56 +1,94 @@ -use crate::{is_blacklisted_instruction, is_variant_instruction, relocatable_regions}; +use crate::{ + is_blacklisted_instruction, is_computed_variant_instruction, is_variant_instruction, + relocatable_regions, +}; use binaryninja::basic_block::BasicBlock; use binaryninja::disassembly::DisassemblyTextLine; +use binaryninja::flowgraph::FlowGraph; use binaryninja::function::{HighlightColor, HighlightStandardColor, NativeBlock}; -use binaryninja::low_level_il::instruction::LowLevelInstructionIndex; +use binaryninja::low_level_il::LowLevelILRegularFunction; use binaryninja::render_layer::{register_render_layer, RenderLayer}; -pub struct HighlightRenderLayer {} +// TODO: Add a render layer to show basic block GUID's? +// TODO: Add a render layer to show constraints for current function? + +pub struct HighlightRenderLayer { + blacklist: HighlightColor, + variant: HighlightColor, + computed_variant: HighlightColor, +} impl HighlightRenderLayer { pub fn register() { register_render_layer( "WARP Highlight Layer", - HighlightRenderLayer {}, + // TODO: Make the highlight colors configurable. + HighlightRenderLayer { + blacklist: HighlightColor::StandardHighlightColor { + color: HighlightStandardColor::BlackHighlightColor, + alpha: 155, + }, + variant: HighlightColor::StandardHighlightColor { + color: HighlightStandardColor::RedHighlightColor, + alpha: 155, + }, + computed_variant: HighlightColor::StandardHighlightColor { + color: HighlightStandardColor::OrangeHighlightColor, + alpha: 155, + }, + }, Default::default(), ); } + + /// Highlights the lines that are variant or blacklisted. + pub fn highlight_lines( + &self, + lifted_il: &LowLevelILRegularFunction, + llil: &LowLevelILRegularFunction, + lines: &mut [DisassemblyTextLine], + ) { + let relocatable_regions = relocatable_regions(&lifted_il.function().view()); + for line in lines { + // We use address here instead of index since it's more reliable for other IL's. + if let Some(lifted_il_instr) = lifted_il.instruction_at(line.address) { + if is_blacklisted_instruction(&lifted_il_instr) { + line.highlight = self.blacklist; + } else if is_variant_instruction(&relocatable_regions, &lifted_il_instr) { + line.highlight = self.variant; + } + } + + if let Some(llil_instr) = llil.instruction_at(line.address) { + if is_computed_variant_instruction(&relocatable_regions, &llil_instr) { + line.highlight = self.computed_variant; + } + } + } + } } impl RenderLayer for HighlightRenderLayer { - fn apply_to_llil_block( + fn apply_to_flow_graph(&self, graph: &mut FlowGraph) { + if let (Some(lifted_il), Some(llil)) = (graph.lifted_il(), graph.low_level_il()) { + for node in &graph.nodes() { + let mut new_lines = node.lines().to_vec(); + self.highlight_lines(&lifted_il, &llil, &mut new_lines); + node.set_lines(new_lines); + } + } + } + + fn apply_to_block( &self, block: &BasicBlock<NativeBlock>, mut lines: Vec<DisassemblyTextLine>, ) -> Vec<DisassemblyTextLine> { - // Highlight any LLIL instruction that will be masked by WARP. + // Highlight any instruction that WARP will mask. let function = block.function(); - // TODO: We might need to make relocatable regions configurable. - let relocatable_regions = relocatable_regions(&function.view()); - let Ok(llil) = function.low_level_il() else { - // Don't even think this is possible but _shrug_. - return lines; - }; - - for line in &mut lines { - let llil_instr_idx = LowLevelInstructionIndex(line.instruction_index); - if let Some(llil_instr) = llil.instruction_from_index(llil_instr_idx) { - if is_blacklisted_instruction(&llil_instr) { - // We have a blacklisted instruction, highlight it as orange! - line.highlight = HighlightColor::StandardHighlightColor { - color: HighlightStandardColor::OrangeHighlightColor, - alpha: 155, - }; - } else if is_variant_instruction(&relocatable_regions, &llil_instr) { - // We have a variant instruction, highlight it as red! - line.highlight = HighlightColor::StandardHighlightColor { - color: HighlightStandardColor::RedHighlightColor, - alpha: 155, - }; - } - } + if let (Ok(lifted_il), Ok(llil)) = (function.lifted_il(), function.low_level_il()) { + self.highlight_lines(&lifted_il, &llil, &mut lines); } - lines } } diff --git a/plugins/warp/src/plugin/settings.rs b/plugins/warp/src/plugin/settings.rs new file mode 100644 index 00000000..6cd2e0cc --- /dev/null +++ b/plugins/warp/src/plugin/settings.rs @@ -0,0 +1,129 @@ +use binaryninja::settings::Settings as BNSettings; +use serde_json::json; +use std::string::ToString; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PluginSettings { + /// Whether to load bundled WARP files on startup. Turn this off if you want to manually load them. + /// + /// This is set to [PluginSettings::LOAD_BUNDLED_FILES_DEFAULT] by default. + pub load_bundled_files: bool, + /// Whether to load user WARP files on startup. Turn this off if you want to manually load them. + /// + /// This is set to [PluginSettings::LOAD_USER_FILES_DEFAULT] by default. + pub load_user_files: bool, + /// The WARP server to use. + /// + /// This is set to [PluginSettings::SERVER_URL_DEFAULT] by default. + pub server_url: String, + /// The API key to use for the selected WARP server, if not specified, you will be unable to push data and may be rate-limited. + /// + /// This is set to [PluginSettings::SERVER_API_KEY_DEFAULT] by default. + pub server_api_key: String, + /// Whether to allow networked WARP requests. Turning this off will not disable local WARP functionality. + /// + /// This is set to [PluginSettings::ENABLE_SERVER_DEFAULT] by default. + pub enable_server: bool, +} + +impl PluginSettings { + pub const LOAD_BUNDLED_FILES_DEFAULT: bool = true; + pub const LOAD_BUNDLED_FILES_SETTING: &'static str = "analysis.warp.loadBundledFiles"; + pub const LOAD_USER_FILES_DEFAULT: bool = true; + pub const LOAD_USER_FILES_SETTING: &'static str = "analysis.warp.loadUserFiles"; + pub const SERVER_URL_DEFAULT: &'static str = "https://warp.binary.ninja"; + pub const SERVER_URL_SETTING: &'static str = "analysis.warp.serverUrl"; + pub const SERVER_API_KEY_DEFAULT: &'static str = ""; + pub const SERVER_API_KEY_SETTING: &'static str = "analysis.warp.serverApiKey"; + pub const ENABLE_SERVER_DEFAULT: bool = true; + pub const ENABLE_SERVER_SETTING: &'static str = "network.enableWARP"; + + pub fn register(bn_settings: &mut BNSettings) { + let load_bundled_files_prop = json!({ + "title" : "Load Bundled Files", + "type" : "boolean", + "default" : Self::LOAD_BUNDLED_FILES_DEFAULT, + "description" : "Whether to load bundled WARP files on startup. Turn this off if you want to manually load them.", + "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + }); + bn_settings.register_setting_json( + Self::LOAD_BUNDLED_FILES_SETTING, + &load_bundled_files_prop.to_string(), + ); + let load_user_files_prop = json!({ + "title" : "Load User Files", + "type" : "boolean", + "default" : Self::LOAD_USER_FILES_DEFAULT, + "description" : "Whether to load user WARP files on startup. Turn this off if you want to manually load them.", + "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + }); + bn_settings.register_setting_json( + Self::LOAD_USER_FILES_SETTING, + &load_user_files_prop.to_string(), + ); + let server_url_prop = json!({ + "title" : "Server URL", + "type" : "string", + "default" : Self::SERVER_URL_DEFAULT, + "description" : "The WARP server to use.", + "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + }); + bn_settings.register_setting_json(Self::SERVER_URL_SETTING, &server_url_prop.to_string()); + let server_api_key_prop = json!({ + "title" : "Server API Key", + "type" : "string", + "default" : Self::SERVER_API_KEY_DEFAULT, + "description" : "The API key to use for the selected WARP server, if not specified you will be unable to push data, and may be rate limited.", + "ignore" : ["SettingsProjectScope", "SettingsResourceScope"], + "hidden": true + }); + bn_settings.register_setting_json( + Self::SERVER_API_KEY_SETTING, + &server_api_key_prop.to_string(), + ); + let server_enabled_prop = json!({ + "title" : "Enable WARP", + "type" : "boolean", + "default" : Self::ENABLE_SERVER_DEFAULT, + "description" : "Whether or not to allow networked WARP requests. Turning this off will not disable local WARP functionality.", + "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + }); + bn_settings.register_setting_json( + Self::ENABLE_SERVER_SETTING, + &server_enabled_prop.to_string(), + ); + } + + /// Retrieve plugin settings from [`BNSettings`]. + pub fn from_settings(bn_settings: &BNSettings) -> Self { + let mut settings = PluginSettings::default(); + if bn_settings.contains(Self::LOAD_BUNDLED_FILES_SETTING) { + settings.load_bundled_files = bn_settings.get_bool(Self::LOAD_BUNDLED_FILES_SETTING); + } + if bn_settings.contains(Self::LOAD_USER_FILES_SETTING) { + settings.load_user_files = bn_settings.get_bool(Self::LOAD_USER_FILES_SETTING); + } + if bn_settings.contains(Self::SERVER_URL_SETTING) { + settings.server_url = bn_settings.get_string(Self::SERVER_URL_SETTING); + } + if bn_settings.contains(Self::SERVER_API_KEY_SETTING) { + settings.server_url = bn_settings.get_string(Self::SERVER_API_KEY_SETTING); + } + if bn_settings.contains(Self::ENABLE_SERVER_SETTING) { + settings.enable_server = bn_settings.get_bool(Self::ENABLE_SERVER_SETTING); + } + settings + } +} + +impl Default for PluginSettings { + fn default() -> Self { + Self { + load_bundled_files: PluginSettings::LOAD_BUNDLED_FILES_DEFAULT, + load_user_files: PluginSettings::LOAD_USER_FILES_DEFAULT, + server_url: PluginSettings::SERVER_URL_DEFAULT.to_string(), + server_api_key: PluginSettings::SERVER_API_KEY_DEFAULT.to_string(), + enable_server: PluginSettings::ENABLE_SERVER_DEFAULT, + } + } +} diff --git a/plugins/warp/src/plugin/types.rs b/plugins/warp/src/plugin/types.rs deleted file mode 100644 index 41e03cd3..00000000 --- a/plugins/warp/src/plugin/types.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::convert::to_bn_type; -use binaryninja::binary_view::{BinaryView, BinaryViewExt}; -use binaryninja::command::Command; -use std::time::Instant; - -pub struct LoadTypes; - -impl Command for LoadTypes { - fn action(&self, view: &BinaryView) { - // NOTE: Because we only can consume signatures from a specific directory, we don't need to use the interaction API. - // If we did need to load signature files from a project than this would need to change. - let Some(file) = rfd::FileDialog::new() - .add_filter("Signature Files", &["sbin"]) - .set_file_name(format!("{}.sbin", view.file().filename())) - .pick_file() - else { - return; - }; - - let Ok(data) = std::fs::read(&file) else { - log::error!("Could not read signature file: {:?}", file); - return; - }; - - let Some(data) = warp::signature::Data::from_bytes(&data) else { - log::error!("Could not get data from signature file: {:?}", file); - return; - }; - - let Some(arch) = view.default_arch() else { - log::error!("Could not get default architecture"); - return; - }; - - let view = view.to_owned(); - std::thread::spawn(move || { - let background_task = binaryninja::background_task::BackgroundTask::new( - &format!("Applying {} types...", data.types.len()), - true, - ); - - let start = Instant::now(); - for comp_ty in data.types { - let ty_id = comp_ty.guid.to_string(); - let ty_name = comp_ty.ty.name.to_owned().unwrap_or_else(|| ty_id.clone()); - view.define_auto_type_with_id(ty_name, &ty_id, &to_bn_type(&arch, &comp_ty.ty)); - } - - log::info!("Type application took {:?}", start.elapsed()); - background_task.finish(); - }); - } - - fn valid(&self, _view: &BinaryView) -> bool { - true - } -} diff --git a/plugins/warp/src/plugin/workflow.rs b/plugins/warp/src/plugin/workflow.rs index f5763116..487fccfe 100644 --- a/plugins/warp/src/plugin/workflow.rs +++ b/plugins/warp/src/plugin/workflow.rs @@ -1,16 +1,40 @@ -use crate::cache::cached_function_guid; -use crate::matcher::cached_function_matcher; +use crate::cache::container::for_cached_containers; +use crate::cache::{ + cached_function_guid, insert_cached_function_match, try_cached_function_guid, + try_cached_function_match, +}; +use crate::convert::{ + comment_to_bn_comment, platform_to_target, to_bn_symbol_at_address, to_bn_type, +}; +use crate::matcher::{Matcher, MatcherSettings}; +use crate::{get_warp_tag_type, relocatable_regions}; use binaryninja::background_task::BackgroundTask; use binaryninja::binary_view::{BinaryView, BinaryViewExt}; use binaryninja::command::Command; +use binaryninja::settings::{QueryOptions, Settings}; use binaryninja::workflow::{Activity, AnalysisContext, Workflow}; +use itertools::Itertools; +use std::collections::HashMap; use std::time::Instant; +use warp::signature::function::{Function, FunctionGUID}; +use warp::target::Target; + +pub const APPLY_ACTIVITY_NAME: &str = "analysis.warp.apply"; +const APPLY_ACTIVITY_CONFIG: &str = r#"{ + "name": "analysis.warp.apply", + "title" : "WARP Apply Matched", + "description": "This analysis step applies WARP info to matched functions...", + "eligibility": { + "auto": {}, + "runOnce": false + } +}"#; pub const MATCHER_ACTIVITY_NAME: &str = "analysis.warp.matcher"; const MATCHER_ACTIVITY_CONFIG: &str = r#"{ "name": "analysis.warp.matcher", "title" : "WARP Matcher", - "description": "This analysis step applies WARP info to matched functions...", + "description": "This analysis step attempts to find matching WARP functions after the initial analysis is complete...", "eligibility": { "auto": {}, "runOnce": true @@ -24,7 +48,7 @@ const GUID_ACTIVITY_CONFIG: &str = r#"{ "description": "This analysis step generates the GUID for all analyzed functions...", "eligibility": { "auto": {}, - "runOnce": true + "runOnce": false } }"#; @@ -33,19 +57,8 @@ pub struct RunMatcher; impl Command for RunMatcher { fn action(&self, view: &BinaryView) { let view = view.to_owned(); - // TODO: Check to see if the GUID cache is empty and ask the user if they want to regenerate the guids. std::thread::spawn(move || { - let undo_id = view.file().begin_undo_actions(true); - let background_task = BackgroundTask::new("Matching on functions...", false); - let start = Instant::now(); - view.functions() - .iter() - .for_each(|function| cached_function_matcher(&function)); - log::info!("Function matching took {:?}", start.elapsed()); - background_task.finish(); - view.file().commit_undo_actions(&undo_id); - // Now we want to trigger re-analysis. - view.update_analysis(); + run_matcher(&view); }); } @@ -54,44 +67,157 @@ impl Command for RunMatcher { } } +pub fn run_matcher(view: &BinaryView) { + // Alert the user if we have no actual regions (one comes from the synthetic section). + let regions = relocatable_regions(view); + if regions.len() <= 1 { + log::warn!( + "No relocatable regions found, for best results please define sections for the binary!" + ); + } + + // Then we want to actually find matching functions. + let background_task = BackgroundTask::new("Matching on WARP functions...", true); + let start = Instant::now(); + + // Build matcher + let view_settings = Settings::new(); + let mut query_opts = QueryOptions::new_with_view(view); + let matcher_settings = MatcherSettings::from_settings(&view_settings, &mut query_opts); + let matcher = Matcher::new(matcher_settings); + + // TODO: Par iter this? Using dashmap + let functions_by_target_and_guid: HashMap<(FunctionGUID, Target), Vec<_>> = view + .functions() + .iter() + .filter_map(|f| { + let guid = try_cached_function_guid(&f)?; + let target = platform_to_target(&f.platform()); + Some(((guid, target), f.to_owned())) + }) + .into_group_map(); + + // TODO: Par iter this? Using dashmap + let guids_by_target: HashMap<Target, Vec<FunctionGUID>> = functions_by_target_and_guid + .keys() + .map(|(guid, target)| (target.clone(), *guid)) + .into_group_map(); + + // TODO: Target gets cloned a lot. + // TODO: Containers might both match on the same function. What should we do? + for_cached_containers(|container| { + if background_task.is_cancelled() { + return; + } + + for (target, guids) in &guids_by_target { + let function_guid_with_sources = container + .sources_with_function_guids(target, guids) + .unwrap_or_default(); + + for (guid, sources) in &function_guid_with_sources { + let matched_functions: Vec<Function> = sources + .iter() + .flat_map(|source| { + container + .functions_with_guid(target, source, guid) + .unwrap_or_default() + }) + .collect(); + + let functions = functions_by_target_and_guid + .get(&(*guid, target.clone())) + .expect("Function guid not found"); + + for function in functions { + // Match on all the possible functions + if let Some(matched_function) = + matcher.match_function_from_constraints(function, &matched_functions) + { + // We were able to find a match, add it to the match cache and then mark the function + // as requiring updates; this is so that we know about it in the applier activity. + insert_cached_function_match(function, Some(matched_function.clone())); + } + } + } + } + }); + + if background_task.is_cancelled() { + log::info!("Matcher was cancelled by user, you may run it again by running the 'Run Matcher' command."); + } + + log::info!("Function matching took {:?}", start.elapsed()); + background_task.finish(); + + // Now we want to trigger re-analysis. + view.update_analysis(); +} + pub fn insert_workflow() { + // "Hey look, it's a plier" ~ Josh 2025 + let apply_activity = |ctx: &AnalysisContext| { + let view = ctx.view(); + let function = ctx.function(); + if let Some(matched_function) = try_cached_function_match(&function) { + view.define_auto_symbol(&to_bn_symbol_at_address( + &view, + &matched_function.symbol, + function.symbol().address(), + )); + if let Some(func_ty) = &matched_function.ty { + function.set_auto_type(&to_bn_type(&function.arch(), func_ty)); + } + // TODO: How to clear the comments? They are just persisted. + // TODO: Also they generate an undo action, i hate implicit undo actions so much. + for comment in matched_function.comments { + let bn_comment = comment_to_bn_comment(&function, comment); + function.set_comment_at(bn_comment.addr, &bn_comment.comment); + } + function.add_tag( + &get_warp_tag_type(&view), + &matched_function.guid.to_string(), + None, + false, + None, + ); + } + }; + let matcher_activity = |ctx: &AnalysisContext| { let view = ctx.view(); - let undo_id = view.file().begin_undo_actions(true); - let background_task = BackgroundTask::new("Matching on functions...", false); - let start = Instant::now(); - view.functions() - .iter() - .for_each(|function| cached_function_matcher(&function)); - log::info!("Function matching took {:?}", start.elapsed()); - background_task.finish(); - view.file().commit_undo_actions(&undo_id); - // Now we want to trigger re-analysis. - view.update_analysis(); + run_matcher(&view); }; let guid_activity = |ctx: &AnalysisContext| { let function = ctx.function(); - // TODO: Returning RegularNonSSA means we cant modify the il (the lifting code was written just for lifted il, that needs to be fixed) - if let Some(llil) = unsafe { ctx.llil_function() } { - cached_function_guid(&function, &llil); + if let Some(lifted_il) = unsafe { ctx.lifted_il_function() } { + cached_function_guid(&function, &lifted_il); } }; let old_function_meta_workflow = Workflow::instance("core.function.metaAnalysis"); let function_meta_workflow = old_function_meta_workflow.clone_to("core.function.metaAnalysis"); let guid_activity = Activity::new_with_action(GUID_ACTIVITY_CONFIG, guid_activity); + let apply_activity = Activity::new_with_action(APPLY_ACTIVITY_CONFIG, apply_activity); function_meta_workflow .register_activity(&guid_activity) .unwrap(); + // Because we are going to impact analysis with application we must make sure the function update is triggered to continue to update analysis. + // TODO: need to ask why i cant do core.function.update like in the rtti plugin. + function_meta_workflow + .register_activity_with_subactivities::<Vec<String>>(&apply_activity, vec![]) + .unwrap(); function_meta_workflow.insert("core.function.runFunctionRecognizers", [GUID_ACTIVITY_NAME]); + function_meta_workflow.insert("core.function.generateMediumLevelIL", [APPLY_ACTIVITY_NAME]); function_meta_workflow.register().unwrap(); let old_module_meta_workflow = Workflow::instance("core.module.metaAnalysis"); let module_meta_workflow = old_module_meta_workflow.clone_to("core.module.metaAnalysis"); let matcher_activity = Activity::new_with_action(MATCHER_ACTIVITY_CONFIG, matcher_activity); + // Matcher activity must have core.module.update as subactivity otherwise analysis will sometimes never retrigger. module_meta_workflow - .register_activity(&matcher_activity) + .register_activity_with_subactivities(&matcher_activity, vec!["core.module.update"]) .unwrap(); module_meta_workflow.insert( "core.module.deleteUnusedAutoFunctions", diff --git a/plugins/warp/src/processor.rs b/plugins/warp/src/processor.rs new file mode 100644 index 00000000..4b973753 --- /dev/null +++ b/plugins/warp/src/processor.rs @@ -0,0 +1,829 @@ +use std::collections::{HashMap, HashSet}; +use std::fs::File; +use std::path::{Path, PathBuf}; +use std::sync::atomic::Ordering::Relaxed; +use std::sync::atomic::{AtomicBool, AtomicUsize}; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +use ar::Archive; +use dashmap::DashMap; +use rayon::iter::IntoParallelIterator; +use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; +use regex::Regex; +use serde_json::{json, Value}; +use tempdir::TempDir; +use thiserror::Error; +use walkdir::WalkDir; + +use binaryninja::background_task::BackgroundTask; +use binaryninja::binary_view::{BinaryView, BinaryViewExt}; +use binaryninja::function::Function as BNFunction; +use binaryninja::interaction::{Form, FormInputField}; +use binaryninja::project::file::ProjectFile; +use binaryninja::project::Project; +use binaryninja::rc::{Guard, Ref}; + +use warp::chunk::{Chunk, ChunkKind, CompressionType}; +use warp::r#type::chunk::TypeChunk; +use warp::signature::chunk::SignatureChunk; +use warp::signature::function::Function; +use warp::target::Target; +use warp::{WarpFile, WarpFileHeader}; + +use crate::cache::cached_type_references; +use crate::convert::platform_to_target; +use crate::{build_function, INCLUDE_TAG_ICON, INCLUDE_TAG_NAME}; + +#[derive(Error, Debug)] +pub enum ProcessingError { + #[error("Failed to open archive: {0}")] + ArchiveOpen(std::io::Error), + + #[error("Failed to read archive entry: {0}")] + ArchiveRead(std::io::Error), + + #[error("Binary view load error: {0}")] + BinaryViewLoad(PathBuf), + + #[error("Existing data load error: {0}")] + ExistingDataLoad(PathBuf), + + #[error("Temporary directory creation failed: {0}")] + TempDirCreation(std::io::Error), + + #[error("Failed to read file: {0}")] + FileRead(std::io::Error), + + #[error("Failed to create chunk, possibly too large")] + ChunkCreationFailed, + + #[error("Failed to retrieve path to project file: {0:?}")] + NoPathToProjectFile(Ref<ProjectFile>), + + #[error("Processing state has been poisoned")] + StatePoisoned, + + #[error("Processing has been cancelled")] + Cancelled, +} + +#[derive(Debug, Clone, Default)] +pub struct FileFilterField; + +impl FileFilterField { + pub fn to_field() -> FormInputField { + FormInputField::TextLine { + prompt: "File Filter".to_string(), + default: None, + value: None, + } + } + + pub fn from_form(form: &Form) -> Option<Regex> { + let field = form.get_field_with_name("File Filter")?; + let field_value = field.try_value_string()?; + + // TODO: This is pretty absurd but whatever. + let pattern = if field_value.contains(['*', '.', '[', '(']) { + // Assume it's a regex if it contains meta-characters. + field_value + } else { + // Treat it as a substring + format!(".*{}.*", regex::escape(&field_value)) + }; + + Regex::new(&pattern).ok() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum FileDataKindField { + Symbols, + Signatures, + Types, + #[default] + All, +} + +impl FileDataKindField { + pub fn to_field(&self) -> FormInputField { + FormInputField::Choice { + prompt: "File Data".to_string(), + choices: vec![ + "Symbols".to_string(), + "Signatures".to_string(), + "Types".to_string(), + "All".to_string(), + ], + default: Some(match self { + Self::Symbols => 0, + Self::Signatures => 1, + Self::Types => 2, + Self::All => 3, + }), + value: 0, + } + } + + pub fn from_form(form: &Form) -> Option<Self> { + let field = form.get_field_with_name("File Data")?; + let field_value = field.try_value_index()?; + match field_value { + 3 => Some(Self::All), + 2 => Some(Self::Types), + 1 => Some(Self::Signatures), + 0 => Some(Self::Symbols), + _ => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum IncludedFunctionsField { + Selected, + #[default] + Annotated, + All, +} + +impl IncludedFunctionsField { + pub fn to_field(&self) -> FormInputField { + // If the user has selected any functions, change the default value of the included functions field. + FormInputField::Choice { + prompt: "Included Functions".to_string(), + choices: vec![ + format!("Selected {}", INCLUDE_TAG_ICON), + "Annotated".to_string(), + "All".to_string(), + ], + default: Some(match self { + Self::Selected => 0, + Self::Annotated => 1, + Self::All => 2, + }), + value: 0, + } + } + + pub fn from_form(form: &Form) -> Option<Self> { + let field = form.get_field_with_name("Included Functions")?; + let field_value = field.try_value_index()?; + match field_value { + 2 => Some(Self::All), + 1 => Some(Self::Annotated), + 0 => Some(Self::Selected), + _ => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum SaveReportToDiskField { + No, + #[default] + Yes, +} + +impl SaveReportToDiskField { + pub fn to_field(&self) -> FormInputField { + FormInputField::Checkbox { + prompt: "Save Report to Disk".to_string(), + default: Some(true), + value: false, + } + } + + pub fn from_form(form: &Form) -> Option<Self> { + let field = form.get_field_with_name("Save Report to Disk")?; + let field_value = field.try_value_int()?; + match field_value { + 1 => Some(Self::Yes), + _ => Some(Self::No), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum CompressionTypeField { + None, + #[default] + Zstd, +} + +impl CompressionTypeField { + pub fn to_field(&self) -> FormInputField { + FormInputField::Choice { + prompt: "Compression Type".to_string(), + choices: vec!["None".to_string(), "Zstd".to_string()], + default: Some(match self { + Self::None => 0, + Self::Zstd => 1, + }), + value: 0, + } + } + + pub fn from_form(form: &Form) -> Option<Self> { + let field = form.get_field_with_name("Compression Type")?; + let field_value = field.try_value_index()?; + match field_value { + 1 => Some(Self::Zstd), + _ => Some(Self::None), + } + } +} + +impl From<CompressionTypeField> for CompressionType { + fn from(field: CompressionTypeField) -> Self { + match field { + CompressionTypeField::None => CompressionType::None, + CompressionTypeField::Zstd => CompressionType::Zstd, + } + } +} + +pub fn new_processing_state_background_thread( + task: Ref<BackgroundTask>, + state: Arc<ProcessingState>, +) { + std::thread::spawn(move || { + let start = Instant::now(); + while !task.is_finished() { + std::thread::sleep(Duration::from_millis(100)); + // Check if the user wants to cancel the processing. + if task.is_cancelled() { + state.cancel(); + } + + let total = state.total_files(); + let processed = state.files_with_state(ProcessingFileState::Processed); + let unprocessed = state.files_with_state(ProcessingFileState::Unprocessed); + let analyzing = state.files_with_state(ProcessingFileState::Analyzing); + let processing = state.files_with_state(ProcessingFileState::Processing); + let completion = (processed as f64 / total as f64) * 100.0; + let elapsed = start.elapsed().as_secs_f32(); + let text = format!( + "Processing {} files... {{{}|{}|{}|{}}} ({:.2}%) [{:.2}s]", + total, unprocessed, analyzing, processing, processed, completion, elapsed + ); + task.set_progress_text(&text); + } + }); +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum ProcessingFileState { + /// File is yet to be processed. + Unprocessed, + /// File is being analyzed by Binary Ninja. + Analyzing, + /// File is currently generating WARP data. + /// TODO: (AtomicUsize) for the total and done functions, we can then write to it with Relaxed when processing. + Processing, + /// File is done being processed. + Processed, +} + +#[derive(Debug, Default)] +pub struct ProcessingState { + pub cancelled: AtomicBool, + pub files: DashMap<PathBuf, ProcessingFileState>, + pub total_functions: AtomicUsize, +} + +impl ProcessingState { + pub fn is_cancelled(&self) -> bool { + self.cancelled.load(Relaxed) + } + + pub fn cancel(&self) { + self.cancelled.store(true, Relaxed) + } + + pub fn increment_functions(&self) { + self.total_functions.fetch_add(1, Relaxed); + } + + pub fn total_files(&self) -> usize { + self.files.len() + } + + pub fn files_with_state(&self, state: ProcessingFileState) -> usize { + self.files.iter().filter(|f| *f.value() == state).count() + } + + pub fn set_file_state(&self, path: PathBuf, state: ProcessingFileState) { + self.files.insert(path, state); + } +} + +/// Create a new [`WarpFile`] from files, projects, and directories. +#[derive(Debug, Clone)] +pub struct WarpFileProcessor { + /// The Binary Ninja settings to use when analyzing the binaries. + analysis_settings: Value, + /// For any function without an LLIL, request analysis to be run, waiting for analysis to + /// complete to include in the analysis. + request_analysis: bool, + // TODO: Project cache path, so we save to a project instead of some temp path. + // TODO: Databases will require regenerating LLIL in some cases, so we must support generating the LLIL. + /// The path to a folder to intake and output analysis artifacts. + cache_path: Option<PathBuf>, + file_data: FileDataKindField, + included_functions: IncludedFunctionsField, + compression_type: CompressionTypeField, + /// Regex pattern used to filter out files. + file_filter: Option<Regex>, + /// Processor state, this is shareable between threads, so the processor and the consumer can + /// read / write to the state, use this if you want to show a progress indicator. + state: Arc<ProcessingState>, +} + +impl WarpFileProcessor { + pub fn new() -> Self { + Self { + analysis_settings: json!({ + "analysis.linearSweep.autorun": false, + "analysis.signatureMatcher.autorun": false, + "analysis.mode": "full", + // Disable warp when opening views. + "analysis.warp.guid": false, + "analysis.warp.matcher": false, + "analysis.warp.apply": false, + }), + request_analysis: true, + cache_path: None, + file_data: Default::default(), + included_functions: Default::default(), + compression_type: Default::default(), + file_filter: None, + state: Arc::new(ProcessingState::default()), + } + } + + /// Retrieve a thread-safe shared reference to the [`ProcessingState`]. + pub fn state(&self) -> Arc<ProcessingState> { + self.state.clone() + } + + pub fn with_analysis_settings(mut self, analysis_settings: Value) -> Self { + self.analysis_settings = analysis_settings; + self + } + + pub fn with_request_analysis(mut self, request_analysis: bool) -> Self { + self.request_analysis = request_analysis; + self + } + + pub fn with_cache_path(mut self, cache_path: PathBuf) -> Self { + self.cache_path = Some(cache_path); + self + } + + pub fn with_file_data(mut self, file_data: FileDataKindField) -> Self { + self.file_data = file_data; + self + } + + pub fn with_included_functions(mut self, included_functions: IncludedFunctionsField) -> Self { + self.included_functions = included_functions; + self + } + + pub fn with_compression_type(mut self, compression_type: CompressionTypeField) -> Self { + self.compression_type = compression_type; + self + } + + pub fn with_file_filter(mut self, file_filter: Regex) -> Self { + self.file_filter = Some(file_filter); + self + } + + pub fn file_filter(&self, path: &Path) -> bool { + match (&self.file_filter, path.to_str()) { + (Some(filter), Some(path)) => filter.is_match(path), + _ => true, + } + } + + /// Place a call to this in places to interrupt when canceled. + fn check_cancelled(&self) -> Result<(), ProcessingError> { + match self.state.is_cancelled() { + true => Err(ProcessingError::Cancelled), + false => Ok(()), + } + } + + pub fn process(&self, path: PathBuf) -> Result<WarpFile<'static>, ProcessingError> { + match path.extension() { + Some(ext) if ext == "a" || ext == "lib" || ext == "rlib" => self.process_archive(path), + Some(ext) if ext == "warp" => self.process_warp_file(path), + _ if path.is_dir() => self.process_directory(&path), + // TODO: process_database? + _ => self.process_file(path), + } + } + + pub fn process_project(&self, project: &Project) -> Result<WarpFile<'static>, ProcessingError> { + let filter_project_file = |file: &Guard<ProjectFile>| { + let path = project_file_path(file); + self.file_filter(&path) + }; + + let files: Vec<_> = project + .files() + .iter() + .filter(filter_project_file) + .map(|f| f.to_owned()) + .collect(); + + // Inform the state of the new unprocessed project files. + for project_file in &files { + // NOTE: We use the on disk path here because the downstream file state uses that. + if let Some(path) = project_file.path_on_disk() { + self.state + .set_file_state(path, ProcessingFileState::Unprocessed); + } + } + + let unmerged_files: Result<Vec<_>, _> = files + .par_iter() + .map(|file| { + self.check_cancelled()?; + self.process_project_file(file) + }) + .filter_map(|res| match res { + Ok(result) => Some(Ok(result)), + Err(ProcessingError::Cancelled) => Some(Err(ProcessingError::Cancelled)), + Err(e) => { + log::error!("Project file processing error: {:?}", e); + None + } + }) + .collect(); + + let unmerged_chunks: Vec<_> = unmerged_files? + .iter() + .flat_map(|f| f.chunks.clone()) + .collect(); + let merged_chunks = Chunk::merge(&unmerged_chunks, self.compression_type.into()); + Ok(WarpFile::new(WarpFileHeader::new(), merged_chunks)) + } + + pub fn process_project_file( + &self, + project_file: &ProjectFile, + ) -> Result<WarpFile<'static>, ProcessingError> { + let file_name = project_file.name(); + let extension = file_name.split('.').last(); + let path = project_file + .path_on_disk() + .ok_or_else(|| ProcessingError::NoPathToProjectFile(project_file.to_owned()))?; + match extension { + Some(ext) if ext == "a" || ext == "lib" || ext == "rlib" => self.process_archive(path), + Some("warp") => self.process_warp_file(path), + _ => self.process_file(path), + } + } + + pub fn process_warp_file(&self, path: PathBuf) -> Result<WarpFile<'static>, ProcessingError> { + let contents = std::fs::read(&path).map_err(ProcessingError::FileRead)?; + let file = WarpFile::from_owned_bytes(contents) + .ok_or(ProcessingError::ExistingDataLoad(path.clone())); + + // Inform the state of the new processed warp file. + self.state + .set_file_state(path, ProcessingFileState::Processed); + + file + } + + pub fn process_file(&self, path: PathBuf) -> Result<WarpFile<'static>, ProcessingError> { + // Inform the state of the new analyzing file. + self.state + .set_file_state(path.clone(), ProcessingFileState::Analyzing); + + // Load the view, either from the cache or from the given path. + // Using the cache can speed up the processing, especially for larger binaries. + let settings_str = self.analysis_settings.to_string(); + let view = match &self.cache_path { + Some(cache_path) => { + // Processor is caching analysis, try and find our file in the cache. + let file_cache_path = cache_path + .join(path.file_name().unwrap()) + .with_extension("bndb"); + if file_cache_path.exists() { + // TODO: Update analysis and wait option + log::debug!("Analysis database found in cache: {:?}", file_cache_path); + binaryninja::load_with_options(&file_cache_path, true, Some(settings_str)) + } else { + log::debug!("No database found in cache: {:?}", file_cache_path); + binaryninja::load_with_options(&path, true, Some(settings_str)) + } + } + None => { + // Processor is not caching analysis + binaryninja::load_with_options(&path, true, Some(settings_str)) + } + } + .ok_or(ProcessingError::BinaryViewLoad(path.clone()))?; + + // Analysis is complete, if needed, save the database to cache. + if let Some(cache_path) = &self.cache_path { + // Before we process the view we should cache the analysis database. + // Only cache the analysis database if there has been a change. + // TODO: What if there is multiple paths with the same name? + // TODO: We need more context than just the path, likely we need a processing path stack. + let file_cache_path = cache_path + .join(path.file_name().unwrap()) + .with_extension("bndb"); + // TODO: We should also update the cache if analysis has changed! + if !view.file().is_database_backed() { + // Update the cache. + log::debug!("Saving analysis database to {:?}", file_cache_path); + if !view.file().create_database(&file_cache_path) { + // TODO: We might want to error here... + log::warn!("Failed to save analysis database to {:?}", file_cache_path); + } + } else { + log::debug!( + "Analysis database unchanged, skipping save to {:?}", + file_cache_path + ); + } + } + + // Process the view + let warp_file = self.process_view(path, &view); + // Close the view manually, see comment in [`BinaryView`]. + view.file().close(); + warp_file + } + + pub fn process_directory(&self, path: &Path) -> Result<WarpFile<'static>, ProcessingError> { + // Collect all files in the directory + let files = WalkDir::new(path) + .into_iter() + .filter_map(|e| { + let path = e.ok()?.into_path(); + if path.is_file() && self.file_filter(&path) { + Some(path) + } else { + None + } + }) + .collect::<Vec<_>>(); + + // Inform the state of the new unprocessed files. + for entry_file in &files { + self.state + .set_file_state(entry_file.clone(), ProcessingFileState::Unprocessed); + } + + // Process all the files. + let unmerged_files: Result<Vec<_>, _> = files + .into_par_iter() + .inspect(|path| log::debug!("Processing file: {:?}", path)) + .map(|path| { + self.check_cancelled()?; + self.process(path) + }) + .filter_map(|res| match res { + Ok(result) => Some(Ok(result)), + Err(ProcessingError::Cancelled) => Some(Err(ProcessingError::Cancelled)), + Err(e) => { + log::error!("Directory file processing error: {:?}", e); + None + } + }) + .collect(); + + let unmerged_chunks: Vec<_> = unmerged_files? + .iter() + .flat_map(|f| f.chunks.clone()) + .collect(); + let merged_chunks = Chunk::merge(&unmerged_chunks, self.compression_type.into()); + Ok(WarpFile::new(WarpFileHeader::new(), merged_chunks)) + } + + pub fn process_archive(&self, path: PathBuf) -> Result<WarpFile<'static>, ProcessingError> { + // Open the archive. + let archive_file = File::open(&path).map_err(ProcessingError::ArchiveOpen)?; + let mut archive = Archive::new(archive_file); + + // Create a temp directory to store the archive entries. + let temp_dir = TempDir::new("tmp_archive").map_err(ProcessingError::TempDirCreation)?; + + // TODO: Use the file_filter? We would need to normalize the path then. + // Iterate through the entries in the ar file and make a temp dir with them + let mut entry_files: HashSet<PathBuf> = HashSet::new(); + while let Some(entry) = archive.next_entry() { + let mut entry = entry.map_err(ProcessingError::ArchiveRead)?; + // NOTE: The entry name here may resemble a full path, on unix this is fine, but + // on Windows this will prevent a file from being created, so we "normalize" the file name. + let name = String::from_utf8_lossy(entry.header().identifier()).to_string(); + // Normalize file name for Windows compatibility + let normalized_name = name + .replace(':', "_") + .replace('/', "_") + .replace('\\', "_") + .split_whitespace() + .collect::<Vec<_>>() + .join("_"); + let output_path = temp_dir.path().join(&normalized_name); + if !entry_files.contains(&output_path) { + let mut output_file = + File::create(&output_path).map_err(ProcessingError::TempDirCreation)?; + std::io::copy(&mut entry, &mut output_file).map_err(ProcessingError::FileRead)?; + entry_files.insert(output_path); + } else { + log::debug!("Skipping already inserted entry: {}", normalized_name); + } + } + + // Inform the state of the new unprocessed files. + for entry_file in &entry_files { + self.state + .set_file_state(entry_file.clone(), ProcessingFileState::Unprocessed); + } + + // TODO: Par iter? + // Process all the entries. + let unmerged_files: Result<Vec<_>, _> = entry_files + .into_par_iter() + .inspect(|path| log::debug!("Processing entry: {:?}", path)) + .map(|path| { + self.check_cancelled()?; + self.process_file(path) + }) + .filter_map(|res| match res { + Ok(result) => Some(Ok(result)), + Err(ProcessingError::Cancelled) => Some(Err(ProcessingError::Cancelled)), + Err(e) => { + log::error!("Archive file processing error: {:?}", e); + None + } + }) + .collect(); + + let unmerged_chunks: Vec<_> = unmerged_files? + .iter() + .flat_map(|f| f.chunks.clone()) + .collect(); + let merged_chunks = Chunk::merge(&unmerged_chunks, self.compression_type.into()); + Ok(WarpFile::new(WarpFileHeader::new(), merged_chunks)) + } + + pub fn process_view( + &self, + path: PathBuf, + view: &BinaryView, + ) -> Result<WarpFile<'static>, ProcessingError> { + self.state + .set_file_state(path.clone(), ProcessingFileState::Processing); + + let mut chunks = Vec::new(); + if self.file_data != FileDataKindField::Types { + let mut signature_chunks = self.create_signature_chunks(view)?; + for (target, signature_chunk) in signature_chunks.drain() { + let chunk = Chunk::new_with_target( + ChunkKind::Signature(signature_chunk), + self.compression_type.into(), + target, + ); + chunks.push(chunk) + } + } + + if self.file_data != FileDataKindField::Signatures { + chunks.push(Chunk::new( + ChunkKind::Type(self.create_type_chunk(view)?), + self.compression_type.into(), + )); + } + + self.state + .set_file_state(path, ProcessingFileState::Processed); + + Ok(WarpFile::new(WarpFileHeader::new(), chunks)) + } + + /// Create signature chunks for each unique [`Target`]. + /// + /// A [`Target`] in Binary Ninja is a [`Platform`], so we just fill in that information. + pub fn create_signature_chunks( + &self, + view: &BinaryView, + ) -> Result<HashMap<Target, SignatureChunk<'static>>, ProcessingError> { + let is_function_named = |f: &Guard<BNFunction>| { + self.included_functions == IncludedFunctionsField::All + || view.symbol_by_address(f.start()).is_some() + || f.has_user_annotations() + }; + let is_function_tagged = |f: &Guard<BNFunction>| { + self.included_functions != IncludedFunctionsField::Selected + || !f.function_tags(None, Some(INCLUDE_TAG_NAME)).is_empty() + }; + // TODO: is_function_blacklisted (use tag) + + // TODO: Move this background task to use the ProcessingState. + let view_functions = view.functions(); + let total_functions = view_functions.len(); + let done_functions = AtomicUsize::default(); + let background_task = BackgroundTask::new( + &format!("Generating signatures... ({}/{})", 0, total_functions), + true, + ); + + // Create all of the "built" functions, for the chunk. + // NOTE: This does a bit of filtering to remove undesired functions, look at this if + // a desired function is not in the created chunk. + // TODO: Make this interruptable. with background_task.is_cancelled. + let start = Instant::now(); + let built_functions: DashMap<Target, Vec<Function>> = view_functions + .par_iter() + .inspect(|_| { + done_functions.fetch_add(1, Relaxed); + background_task.set_progress_text(&format!( + "Generating signatures... ({}/{}) [{}s]", + done_functions.load(Relaxed), + total_functions, + start.elapsed().as_secs_f32() + )) + }) + .filter(is_function_tagged) + .filter(is_function_named) + .filter(|f| !f.analysis_skipped()) + .filter_map(|func| { + let lifted_il = func.lifted_il().ok()?; + let target = platform_to_target(&func.platform()); + let mut built_function = build_function(&func, &lifted_il); + // User asked to only save symbols, so we will remove the function type. + if self.file_data == FileDataKindField::Symbols { + built_function.ty = None; + } + Some((target, built_function)) + }) + .fold( + DashMap::new, + |acc: DashMap<Target, Vec<Function>>, (target, function)| { + acc.entry(target).or_default().push(function); + acc + }, + ) + .reduce(DashMap::new, |acc, other| { + other.into_iter().for_each(|(key, value)| { + acc.entry(key).or_default().extend(value); + }); + acc + }); + + let chunks: Result<HashMap<Target, SignatureChunk<'static>>, ProcessingError> = + built_functions + .into_iter() + .map(|(target, functions)| { + Ok(( + target, + SignatureChunk::new(&functions) + .ok_or(ProcessingError::ChunkCreationFailed)?, + )) + }) + .collect(); + + background_task.finish(); + chunks + } + + // TODO: Add a background task here. + pub fn create_type_chunk( + &self, + view: &BinaryView, + ) -> Result<TypeChunk<'static>, ProcessingError> { + let mut referenced_types = Vec::new(); + if let Some(ref_ty_cache) = cached_type_references(view) { + referenced_types = ref_ty_cache + .cache + .iter() + .filter_map(|t| t.to_owned()) + .collect::<Vec<_>>(); + } + TypeChunk::new_with_computed(&referenced_types).ok_or(ProcessingError::ChunkCreationFailed) + } +} + +fn project_file_path(file: &ProjectFile) -> PathBuf { + // Recurse up the folders to build a string like /foldera/folderb/myfile + let mut path = PathBuf::new(); + // Add file name + path.push(file.name()); + // Recursively add parent folder names + let mut current = file.folder(); + while let Some(folder) = current { + path = PathBuf::from(folder.name()).join(path); + current = folder.parent(); + } + path +} diff --git a/plugins/warp/src/report.rs b/plugins/warp/src/report.rs new file mode 100644 index 00000000..8ba9cc8b --- /dev/null +++ b/plugins/warp/src/report.rs @@ -0,0 +1,185 @@ +use binaryninja::interaction::{Form, FormInputField}; +use minijinja::Environment; +use serde::Serialize; +use warp::chunk::{Chunk, ChunkKind}; +use warp::r#type::guid::TypeGUID; +use warp::WarpFile; + +#[derive(Debug, Clone, Copy, PartialEq, Default)] +pub enum ReportKindField { + None, + #[default] + Html, + Markdown, + Json, +} + +impl ReportKindField { + pub fn to_field(&self) -> FormInputField { + FormInputField::Choice { + prompt: "Generated Report".to_string(), + choices: vec![ + "None".to_string(), + "HTML".to_string(), + "Markdown".to_string(), + "JSON".to_string(), + ], + default: Some(match self { + Self::None => 0, + Self::Html => 1, + Self::Markdown => 2, + Self::Json => 3, + }), + value: 0, + } + } + + pub fn from_form(form: &Form) -> Option<Self> { + let field = form.get_field_with_name("Generated Report")?; + let field_value = field.try_value_index()?; + match field_value { + 3 => Some(Self::Json), + 2 => Some(Self::Markdown), + 1 => Some(Self::Html), + _ => Some(Self::None), + } + } +} + +#[derive(Debug, Clone)] +pub struct ReportGenerator { + environment: Environment<'static>, +} + +impl ReportGenerator { + pub fn new() -> Self { + let mut environment = Environment::new(); + // Remove trailing lines for blocks, this is required for Markdown tables. + environment.set_trim_blocks(true); + minijinja_embed::load_templates!(&mut environment); + Self { environment } + } + + pub fn report(&self, kind: &ReportKindField, file: &WarpFile) -> Option<String> { + match kind { + ReportKindField::None => None, + ReportKindField::Html => self.html_report(file), + ReportKindField::Markdown => self.markdown_report(file), + ReportKindField::Json => self.json_report(file), + } + } + + pub fn report_extension(&self, kind: &ReportKindField) -> Option<&'static str> { + match kind { + ReportKindField::None => None, + ReportKindField::Html => Some("html"), + ReportKindField::Markdown => Some("md"), + ReportKindField::Json => Some("json"), + } + } + + pub fn html_report(&self, file: &WarpFile) -> Option<String> { + let data = FileReportData::new(file); + let tmpl = self.environment.get_template("file.html").ok()?; + tmpl.render(data).ok() + } + + pub fn markdown_report(&self, file: &WarpFile) -> Option<String> { + let data = FileReportData::new(file); + let tmpl = self.environment.get_template("file.md").ok()?; + tmpl.render(data).ok() + } + + pub fn json_report(&self, file: &WarpFile) -> Option<String> { + let data = FileReportData::new(file); + let tmpl = self.environment.get_template("file.json").ok()?; + tmpl.render(data).ok() + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct FileReportData { + pub title: String, + // pub header: WarpFileHeader, + pub chunks: Vec<ChunkReportData>, +} + +impl FileReportData { + pub fn new(file: &WarpFile) -> Self { + Self { + title: "Warp File Report".to_string(), + // header: file.header.clone(), + chunks: file + .chunks + .iter() + .map(|chunk| ChunkReportData::new(chunk)) + .collect(), + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct ChunkReportData { + pub title: String, + // pub header: ChunkHeader, + pub target: String, + pub total_item_count: usize, + /// View into a (possible subset) of chunk items. + pub item_view: Vec<ItemReportData>, +} + +impl ChunkReportData { + pub fn new(chunk: &Chunk) -> Self { + // TODO: Set a limit for the number of items so we dont construct 10000000 items in the report. + let items: Vec<_> = match &chunk.kind { + ChunkKind::Signature(sc) => sc + .raw_functions() + .map(|f| ItemReportData { + name: f.symbol().and_then(|s| s.name().map(|n| n.to_string())), + guid: f.guid().to_string(), + note: None, + }) + .collect(), + ChunkKind::Type(tc) => tc + .raw_types() + .map(|t| ItemReportData { + name: t.type_().and_then(|s| s.name().map(|n| n.to_string())), + guid: TypeGUID::from(t.guid()).to_string(), + note: None, + }) + .collect(), + }; + + let chunk_type = match &chunk.kind { + ChunkKind::Signature(_) => "Signature".to_string(), + ChunkKind::Type(_) => "Type".to_string(), + }; + + let size_in_kb = chunk.header.size as f64 / 1024.0; + let formatted_size = format!("{:.1}kb", size_in_kb); + + // For the target show the platform, or the architecture if available. + let target = chunk + .header + .target + .platform + .clone() + .or_else(|| chunk.header.target.architecture.clone()) + .unwrap_or_else(|| "None".to_string()); + + Self { + title: format!("{} Chunk ({})", chunk_type, formatted_size), + target, + // header: chunk.header.clone(), + total_item_count: items.len(), + item_view: items, + } + } +} + +#[derive(Debug, Clone, Serialize)] +pub struct ItemReportData { + pub guid: String, + pub name: Option<String>, + pub note: Option<String>, +} diff --git a/plugins/warp/src/templates/file.html b/plugins/warp/src/templates/file.html new file mode 100644 index 00000000..7ea465ef --- /dev/null +++ b/plugins/warp/src/templates/file.html @@ -0,0 +1,37 @@ +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>{{ title }}</title> +</head> +<body> +<h1>{{ title }}</h1> + +{% for chunk in chunks %} +<section> + <h2>{{ chunk.title }}</h2> + <p>Target: {{ chunk.target }}</p> + <p>Total items: {{ chunk.total_item_count }}</p> + + <table> + <thead> + <tr> + <th>GUID</th> + <th>Name</th> + <th>Note</th> + </tr> + </thead> + <tbody> + {% for item in chunk.item_view %} + <tr> + <td>{{ item.guid }}</td> + <td>{{ item.name or 'N/A' }}</td> + <td>{{ item.note or 'N/A' }}</td> + </tr> + {% endfor %} + </tbody> + </table> +</section> +{% endfor %} +</body> +</html>
\ No newline at end of file diff --git a/plugins/warp/src/templates/file.json b/plugins/warp/src/templates/file.json new file mode 100644 index 00000000..764d35cd --- /dev/null +++ b/plugins/warp/src/templates/file.json @@ -0,0 +1,21 @@ +{ + "title": "{{ title }}", + "chunks": [ + {% for chunk in chunks %} + { + "title": "{{ chunk.title }}", + "target": "{{ chunk.target }}", + "total_item_count": {{ chunk.total_item_count }}, + "item_view": [ + {% for item in chunk.item_view %} + { + "guid": "{{ item.guid }}", + "name": "{{ item.name or 'N/A' }}", + "note": "{{ item.note or 'N/A' }}" + }{% if not loop.last %},{% endif %} + {% endfor %} + ] + }{% if not loop.last %},{% endif %} + {% endfor %} + ] +}
\ No newline at end of file diff --git a/plugins/warp/src/templates/file.md b/plugins/warp/src/templates/file.md new file mode 100644 index 00000000..433cb8e9 --- /dev/null +++ b/plugins/warp/src/templates/file.md @@ -0,0 +1,15 @@ +# {{ title }} + +{% for chunk in chunks %} +## {{ chunk.title }} + +Target: {{ chunk.target }} + +Total items: {{ chunk.total_item_count }} + +| GUID | Name | Note | +|--------------|--------------|--------------| +{% for item in chunk.item_view -%} +| {{ item.guid }} | {{ item.name or 'N/A' }} | {{ item.note or 'N/A' }} | +{% endfor %} +{% endfor %} diff --git a/plugins/warp/tests/container.rs b/plugins/warp/tests/container.rs new file mode 100644 index 00000000..31b47377 --- /dev/null +++ b/plugins/warp/tests/container.rs @@ -0,0 +1,280 @@ +use std::collections::HashSet; +use std::path::PathBuf; +use std::str::FromStr; +use uuid::Uuid; +use warp::r#type::class::BooleanClass; +use warp::r#type::class::TypeClass::Void; +use warp::r#type::guid::TypeGUID; +use warp::r#type::{ComputedType, Type}; +use warp::signature::function::{Function, FunctionGUID}; +use warp::symbol::{Symbol, SymbolClass}; +use warp::target::Target; +use warp_ninja::container::disk::DiskContainer; +use warp_ninja::container::memory::{MemoryContainer, MemorySource}; +use warp_ninja::container::{Container, ContainerError, SourceId, SourcePath}; + +fn type_0() -> (TypeGUID, Type) { + let ty = Type::builder() + .name("type_0") + .class(BooleanClass::builder().width(1).build()) + .build(); + (TypeGUID::from(&ty), ty) +} + +fn type_1() -> (TypeGUID, Type) { + let ty = Type::builder() + .name("type_1") + .class(BooleanClass::builder().width(4).build()) + .build(); + (TypeGUID::from(&ty), ty) +} + +fn type_2() -> (TypeGUID, Type) { + let ty = Type::builder() + .name("type_2") + .class(BooleanClass::builder().width(8).build()) + .build(); + (TypeGUID::from(&ty), ty) +} + +fn func_0() -> (FunctionGUID, Function) { + let guid = FunctionGUID::from(Uuid::from_str("d4e56ec8-1f2b-4a87-9f2c-bc3f10c4d8e9").unwrap()); + let function = Function { + guid, + symbol: Symbol { + name: "func_0".to_string(), + modifiers: Default::default(), + class: SymbolClass::Function, + }, + // TODO: We might want to give this an actual function type. + ty: Some(Type::builder::<String, _>().class(Void).build()), + constraints: Default::default(), + comments: vec![], + variables: vec![], + }; + (guid, function) +} + +fn func_1() -> (FunctionGUID, Function) { + let guid = FunctionGUID::from(Uuid::from_str("b713c293-463a-5baa-b31b-ed010510d5c0").unwrap()); + let function = Function { + guid, + symbol: Symbol { + name: "func_1".to_string(), + modifiers: Default::default(), + class: SymbolClass::Function, + }, + // TODO: We might want to give this an actual function type. + ty: Some(Type::builder::<String, _>().class(Void).build()), + constraints: Default::default(), + comments: vec![], + variables: vec![], + }; + (guid, function) +} + +#[test] +fn test_sources_with_type_guid() { + let source1 = SourceId::new(); + let source2 = SourceId::new(); + let (guid_1, ty_1) = type_0(); + let (guid_2, ty_2) = type_1(); + + let container = MemoryContainer::new() + .with_source_type(source1, guid_1, ty_1) + .with_source_type(source1, guid_2, ty_2.clone()) + .with_source_type(source2, guid_2, ty_2); + + let result = container + .sources_with_type_guid(&guid_2) + .expect("Failed to get sources"); + // HashSet used for unordered comparison + let result_set: HashSet<_> = result.into_iter().collect(); + let expected_set: HashSet<_> = vec![source1, source2].into_iter().collect(); + assert_eq!(result_set, expected_set); + + let result = container + .sources_with_type_guid(&guid_1) + .expect("Failed to get sources"); + // HashSet used for unordered comparison + let result_set: HashSet<_> = result.into_iter().collect(); + let expected_set: HashSet<_> = vec![source1].into_iter().collect(); + assert_eq!(result_set, expected_set); +} + +#[test] +fn test_sources_with_function_guid() { + let target = Target::default(); + + let source1 = SourceId::new(); + let source2 = SourceId::new(); + let (guid_1, fn_1) = func_0(); + let (guid_2, fn_2) = func_1(); + + let container = MemoryContainer::new() + .with_source_function(source1, guid_1, fn_1) + .with_source_function(source1, guid_2, fn_2.clone()) + .with_source_function(source2, guid_2, fn_2); + + let result = container + .sources_with_function_guid(&target, &guid_2) + .expect("Failed to get sources"); + // HashSet used for unordered comparison + let result_set: HashSet<_> = result.into_iter().collect(); + let expected_set: HashSet<_> = vec![source1, source2].into_iter().collect(); + assert_eq!(result_set, expected_set); + + let result = container + .sources_with_function_guid(&target, &guid_1) + .expect("Failed to get sources"); + assert_eq!(result, vec![source1]); +} + +#[test] +fn test_sources_with_type_guids() { + let source1 = SourceId::new(); + let source2 = SourceId::new(); + let (guid_1, ty_1) = type_0(); + let (guid_2, ty_2) = type_1(); + let (guid_3, ty_3) = type_2(); + + let container = MemoryContainer::new() + .with_source_type(source1, guid_1, ty_1) + .with_source_type(source1, guid_2, ty_2.clone()) + .with_source_type(source2, guid_2, ty_2) + .with_source_type(source2, guid_3, ty_3); + + let result = container + .sources_with_type_guids(&[guid_2.clone(), guid_3.clone()]) + .expect("Failed to get sources"); + assert_eq!(result.len(), 2); + assert!(result.get(&guid_2).unwrap().contains(&source1)); + assert!(result.get(&guid_2).unwrap().contains(&source2)); + assert!(result.get(&guid_3).unwrap().contains(&source2)); +} + +#[test] +fn test_add_types() { + let source1 = SourceId::new(); + let source2 = SourceId::new(); + let (guid_1, ty_1) = type_0(); + let (guid_2, ty_2) = type_1(); + let mut container = MemoryContainer::new() + .with_source( + source1, + MemorySource { + writable: false, + ..Default::default() + }, + ) + .with_source_type(source1, guid_1, ty_1.clone()) + .with_source_type(source2, guid_2, ty_2.clone()); + + assert_eq!( + container.add_types(&source1, &[ty_1.clone()]), + Err(ContainerError::SourceNotWritable(source1)), + "Source should not be writable" + ); + assert_eq!( + container.add_types(&source2, &[ty_1.clone()]), + Ok(()), + "Source should be writable" + ); + container + .type_with_guid(&source1, &guid_1) + .expect("Failed to get existing type"); + container + .type_with_guid(&source2, &guid_1) + .expect("Failed to get added type"); + container + .type_with_guid(&source2, &guid_2) + .expect("Failed to get existing type"); +} + +#[test] +fn test_disk_container() { + // We are going to use the OUT_DIR as the disk container path, this might have other artifacts in it + // so it's a good test to make sure that we handle bad files gracefully. + let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR environment variable is not set"); + let out_dir_path: PathBuf = out_dir.parse().expect("Failed to parse OUT_DIR as path"); + + // TODO: Use a temp file for this instead lol. + let added_type_path = out_dir_path.join("added_type.warp"); + // Make sure that we have deleted the previous runs artifacts. + if std::fs::exists(&added_type_path).unwrap() { + std::fs::remove_file(&added_type_path).expect("Failed to remove existing file"); + } + + // Write a simple test to open a file with DiskContainer + let mut container = DiskContainer::new_from_dir(out_dir_path.clone()); + + // Just pass in the default target, there is no target specified in the file. + let target = Target::default(); + + // Test type retrieval. + // Type -> type_10 : 2f6d2876-ec42-5ca1-bee3-f12fe91d7e13 + let type_0 = TypeGUID::from(Uuid::from_str("2f6d2876-ec42-5ca1-bee3-f12fe91d7e13").unwrap()); + let sources: Vec<SourceId> = container + .sources_with_type_guid(&type_0) + .expect("Failed to get sources") + .into_iter() + .collect(); + assert_eq!(sources.len(), 1); + let result_type_0 = container + .type_with_guid(&sources[0], &type_0) + .expect("Failed to get type") + .expect("Type not found"); + assert_eq!(result_type_0.name, Some("type_10".to_string())); + let result_type_guids_0 = container + .type_guids_with_name(&sources[0], "type_10") + .expect("Failed to get type guids"); + assert_eq!(result_type_guids_0.len(), 1); + assert_eq!(result_type_guids_0[0], type_0); + + // Test function retrieval. + // Function -> function_95 : d5da0413-a020-5db8-b838-4d0ea8bd3dcb + let func_0 = + FunctionGUID::from(Uuid::from_str("d5da0413-a020-5db8-b838-4d0ea8bd3dcb").unwrap()); + let func_sources = container + .sources_with_function_guid(&target, &func_0) + .expect("Failed to get sources"); + assert_eq!(func_sources.len(), 1); + let result_func_0 = container + .functions_with_guid(&target, &func_sources[0], &func_0) + .expect("Failed to get functions"); + assert_eq!(result_func_0.len(), 1); + assert_eq!(result_func_0[0].symbol.name, "function_95".to_string()); + + // Test adding a type to an existing disk source. + let mut result_type_0_mut = result_type_0.clone(); + result_type_0_mut.name = Some("added_type".to_string()); + let computed_result_type_0 = ComputedType::new(result_type_0_mut); + container + .add_types(&sources[0], &[computed_result_type_0.ty]) + .expect("Failed to add type"); + let result_type_1 = container + .type_guids_with_name(&sources[0], "added_type") + .expect("Failed to get added type"); + assert_eq!( + result_type_1, + vec![computed_result_type_0.guid], + "Added type was not found in the existing source" + ); + + // Test commiting the updated source. + // NOTE: Because we don't want to modify the file, we are going to change the source path + // before trying to commit it, this is a bit hacky and should not really be done in real code. + let source = container + .sources + .get_mut(&sources[0]) + .expect("Container does not contain the source"); + source.path = SourcePath::new(out_dir_path.join("added_type.warp")); + container + .commit_source(&sources[0]) + .expect("Failed to commit source"); + assert_eq!( + std::fs::exists(&added_type_path).unwrap(), + true, + "File was not created" + ); +} diff --git a/plugins/warp/tests/determinism.rs b/plugins/warp/tests/determinism.rs new file mode 100644 index 00000000..403fff1d --- /dev/null +++ b/plugins/warp/tests/determinism.rs @@ -0,0 +1,40 @@ +//! This tests to make sure the function GUIDs are stable. +use binaryninja::binary_view::BinaryViewExt; +use binaryninja::headless::Session; +use std::collections::BTreeMap; +use std::path::PathBuf; +use warp::signature::function::FunctionGUID; +use warp_ninja::cache::cached_function_guid; + +// These are the target files present in OUT_DIR +// Add the files to fixtures/bin +static TARGET_FILES: [&str; 9] = [ + "_ctype.obj", + "_fptostr.obj", + "_mbslen.obj", + "_memicmp.obj", + "_strnicm.obj", + "_wctype.obj", + "atof.obj", + "atoldbl.obj", + "atox.obj", +]; + +#[test] +fn insta_signatures() { + let session = Session::new().expect("Failed to initialize session"); + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + for file_name in TARGET_FILES { + let path = out_dir.join(file_name); + let view = session.load(&path).expect("Failed to load view"); + let functions: BTreeMap<u64, FunctionGUID> = view + .functions() + .iter() + .map(|f| { + let guid = cached_function_guid(&f, &f.lifted_il().unwrap()); + (f.start(), guid) + }) + .collect(); + insta::assert_debug_snapshot!(file_name, functions); + } +} diff --git a/plugins/warp/tests/matcher.rs b/plugins/warp/tests/matcher.rs new file mode 100644 index 00000000..13e2ce01 --- /dev/null +++ b/plugins/warp/tests/matcher.rs @@ -0,0 +1,182 @@ +use binaryninja::architecture::CoreArchitecture; +use binaryninja::binary_view::{BinaryView, BinaryViewExt}; +use binaryninja::file_metadata::FileMetadata; +use binaryninja::function::Function as BNFunction; +use binaryninja::headless::Session; +use binaryninja::platform::Platform; +use binaryninja::rc::Ref; +use binaryninja::symbol::{Symbol as BNSymbol, SymbolType}; +use binaryninja::types::TypeClass as BNTypeClass; +use std::str::FromStr; +use warp::mock::{mock_constraint, mock_function}; +use warp::r#type::class::{IntegerClass, ReferrerClass, StructureClass, StructureMember}; +use warp::r#type::guid::TypeGUID; +use warp::r#type::Type; +use warp::signature::function::FunctionGUID; +use warp::target::Target; +use warp_ninja::container::memory::MemoryContainer; +use warp_ninja::container::{Container, SourceId}; +use warp_ninja::function_guid; +use warp_ninja::matcher::{Matcher, MatcherSettings}; + +const MOCK_FUNCTION_GUID: &'static str = "02e8690a-bd1e-54df-9a75-5e7bca594c30"; +const MOCK_FUNCTION_BYTES: &[u8] = &[ + // first_function + 0xA1, 0xFA, 0xF8, 0xF0, 0x99, // ; mov eax, [0x99f0f8fa] + 0x83, 0xC0, 0x37, // ; add eax, 55 + 0xC3, // ; ret + // second_function + 0xA1, 0xFA, 0xF8, 0xF0, 0x91, // ; mov eax, [0x91f0f8fa] + 0x83, 0xC0, 0x37, // ; add eax, 55 + 0xC3, // ; ret +]; + +fn create_mock_bn_function(_session: &Session) -> Ref<BNFunction> { + let file = FileMetadata::new(); + let view = + BinaryView::from_data(&file, MOCK_FUNCTION_BYTES).expect("Failed to create mock view"); + let platform = Platform::by_name("x86").unwrap(); + // Add the constraint symbol so that the matcher picks it up, so we can test constraint matching. + let constraint_symbol = + BNSymbol::builder(SymbolType::Function, "second_function", 0x9).create(); + view.define_user_symbol(&constraint_symbol); + let function_symbol = BNSymbol::builder(SymbolType::Function, "first_function", 0x0).create(); + view.define_user_symbol(&function_symbol); + // Define the constraint function. + view.add_user_function_with_platform(0x9, &platform) + .expect("Failed to create constraint function"); + // Actually define the function and return it. + view.add_user_function_with_platform(0x0, &platform) + .expect("Failed to create mock function") +} + +#[test] +fn test_match_function() { + let mut matcher_settings = MatcherSettings::default(); + matcher_settings.trivial_function_len = 0; + let matcher = Matcher::new(matcher_settings); + + // The memory container does not currently reference a target. + let target = Target::default(); + + let func = mock_function(MOCK_FUNCTION_GUID); + let func_guid = func.guid.clone(); + + let source = SourceId::new(); + let memory_container = + MemoryContainer::new().with_source_function(source, func_guid, func.clone()); + + // Create mock binary ninja function. + let session = Session::new().expect("Failed to create session"); + let bn_function = create_mock_bn_function(&session); + + let possible_funcs = memory_container + .functions_with_guid(&target, &source, &func_guid) + .expect("Failed to get functions"); + + // Because there is only a single possible function this should just return that function. + let matched_function = matcher + .match_function_from_constraints(&bn_function, &possible_funcs) + .expect("Failed to match function"); + assert_eq!(matched_function, &func); +} + +#[test] +fn test_match_function_from_constraints() { + let mut matcher_settings = MatcherSettings::default(); + // TODO: This is needed to make the test pass, the functions are "trivial" in length. + matcher_settings.trivial_function_adjacent_allowed = true; + let matcher = Matcher::new(matcher_settings); + let mut function = mock_function("first_function"); + function.guid = FunctionGUID::from_str(MOCK_FUNCTION_GUID).expect("Failed to parse guid"); + let func_guid = function.guid.clone(); + // Add constraint + function + .constraints + .insert(mock_constraint("second_function", Some(0x9))); + + let matched_func_1 = function.clone(); + let mut matched_func_2 = function.clone(); + // Remove the constraint from 2, this means that the first matched_func should match. + matched_func_2.constraints.clear(); + let mut matched_functions = vec![matched_func_1.clone(), matched_func_2]; + + // Create a mock binary ninja function. + let session = Session::new().expect("Failed to create session"); + let bn_function = create_mock_bn_function(&session); + let bn_function_guid = function_guid(&bn_function, &bn_function.lifted_il().unwrap()); + assert_eq!(bn_function_guid, func_guid); + + // We should match on the first as it has the adjacent constraint still. + let matched_0 = matcher + .match_function_from_constraints(&bn_function, &matched_functions) + .expect("Failed to match function"); + assert_eq!(matched_0, &function); + + // Now we want to verify we do not match when the matched function is duplicated. + // NOTE: That in the case of identical functions in a set, we would prune them eagerly, so this is + // NOTE: not really indicative of a real scenario. + matched_functions.push(matched_func_1); + + let matched_1 = matcher.match_function_from_constraints(&bn_function, &matched_functions); + assert_eq!(matched_1, None); +} + +fn create_mock_type() -> Type { + // Build the type, this is quite annoying. + let int_class = IntegerClass::builder().width(64).signed(true).build(); + let int_type = Type::builder() + .name("my_int".to_owned()) + .class(int_class) + .build(); + + let struct_member_0 = StructureMember::builder() + .name("field_0") + .ty(int_type) + .offset(0) + .build(); + let struct_class = StructureClass::builder() + .members(vec![struct_member_0]) + .build(); + + Type::builder() + .name("my_struct") + .class(struct_class) + .build() +} + +#[test] +fn test_add_type_to_view() { + let matcher_settings = MatcherSettings::default(); + let matcher = Matcher::new(matcher_settings); + + // Add a source type that we can reference and pull in from the container. + let struct_type = create_mock_type(); + let struct_type_guid = TypeGUID::from(&struct_type); + let struct_type_name = struct_type.name.clone().expect("Type should have name"); + + let source = SourceId::new(); + let container = MemoryContainer::new().with_source_type(source, struct_type_guid, struct_type); + + let _session = Session::new().expect("Failed to create session"); + let file = FileMetadata::new(); + let view = BinaryView::from_data(&file, &[]).expect("Failed to create view"); + let arch = CoreArchitecture::by_name("x86").expect("Failed to get architecture"); + + // Try and add a NTR to the view, this should also add the referenced struct type. + let ref_class = ReferrerClass::builder() + .name(struct_type_name) + .guid(struct_type_guid) + .build(); + let ref_type = Type::builder().name("my_ref").class(ref_class).build(); + matcher.add_type_to_view(&container, &source, &view, &arch, &ref_type); + + println!("{:#?}", view.types().to_vec()); + + // Verify the type was added to the view. + let found_type = view + .type_by_name("my_struct") + .expect("Failed to find added type"); + // Make sure we actually added it as a structure type. + assert_eq!(found_type.type_class(), BNTypeClass::StructureTypeClass); +} diff --git a/plugins/warp/tests/processor.rs b/plugins/warp/tests/processor.rs new file mode 100644 index 00000000..12749779 --- /dev/null +++ b/plugins/warp/tests/processor.rs @@ -0,0 +1,62 @@ +use std::path::PathBuf; +use tempdir::TempDir; +use warp_ninja::processor::WarpFileProcessor; + +// These are the target files present in OUT_DIR +// Add the files to fixtures/bin +static BIN_TARGET_FILES: [&str; 9] = [ + "_ctype.obj", + "_fptostr.obj", + "_mbslen.obj", + "_memicmp.obj", + "_strnicm.obj", + "_wctype.obj", + "atof.obj", + "atoldbl.obj", + "atox.obj", +]; + +#[test] +fn test_processor() { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let _headless_session = + binaryninja::headless::Session::new().expect("Failed to initialize session"); + + let processor = WarpFileProcessor::new(); + + // All files should process and not error. + for file_name in BIN_TARGET_FILES { + let path = out_dir.join(file_name); + processor.process(path).unwrap(); + } + + // We should be able to process a warp file. + let warp_path = out_dir.join("random.warp"); + processor.process(warp_path).unwrap(); +} + +#[test] +fn test_caching() { + let out_dir = env!("OUT_DIR").parse::<PathBuf>().unwrap(); + let cache_dir = TempDir::new("tmp_cache").unwrap(); + let _headless_session = + binaryninja::headless::Session::new().expect("Failed to initialize session"); + + let processor = WarpFileProcessor::new().with_cache_path(cache_dir.path().to_path_buf()); + + // Go through files, this should cache the databases. + for file_name in BIN_TARGET_FILES { + let path = out_dir.join(file_name); + processor.process(path).unwrap(); + } + + // Verify the databases were saved to the cache. + let mut cached_paths = Vec::new(); + for entry in std::fs::read_dir(cache_dir.path()).expect("Failed to read cache dir") { + let entry = entry.expect("Failed to read cache dir entry"); + let path = entry.path(); + assert!(path.is_file()); + cached_paths.push(path); + } + assert_eq!(BIN_TARGET_FILES.len(), cached_paths.len()); +} diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__ctype.snap b/plugins/warp/tests/snapshots/determinism___ctype.obj.snap index 0cda5d3a..a0ef5ea0 100644 --- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__ctype.snap +++ b/plugins/warp/tests/snapshots/determinism___ctype.obj.snap @@ -1,126 +1,126 @@ --- -source: plugins/warp/src/lib.rs +source: plugins/warp/tests/determinism.rs expression: functions --- -[ - FunctionGUID { - guid: 00c71b63-3039-5aa3-94f7-1737530239d9, - }, - FunctionGUID { - guid: 06ff982d-b167-5dce-9689-71a631820da6, +{ + 181712: FunctionGUID { + guid: 29690354-fa27-54d1-a8be-18535b19b1c3, }, - FunctionGUID { - guid: 0d6d3685-7a8a-5d1c-8b7a-fe14871218ff, + 182032: FunctionGUID { + guid: 1e794537-6289-59e7-bef9-0c72f3989db8, }, - FunctionGUID { - guid: 0df54a49-5267-52f7-9665-70a8e66ad0dd, + 182720: FunctionGUID { + guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9, }, - FunctionGUID { + 183072: FunctionGUID { guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, }, - FunctionGUID { - guid: 15d686e8-3e80-5632-9ce7-17c3510f8238, - }, - FunctionGUID { - guid: 18cfce71-47bc-5595-89cb-06e0563b211d, + 183376: FunctionGUID { + guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, }, - FunctionGUID { - guid: 1b6aa5a3-ac7f-542d-a0ab-bae8f142d8d8, + 183712: FunctionGUID { + guid: 53b484f3-751a-505d-beae-acc9d69c261d, }, - FunctionGUID { - guid: 1e794537-6289-59e7-bef9-0c72f3989db8, + 184032: FunctionGUID { + guid: 88d9b26b-3884-58d6-b164-435d8d888e5c, }, - FunctionGUID { - guid: 29690354-fa27-54d1-a8be-18535b19b1c3, + 184400: FunctionGUID { + guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, }, - FunctionGUID { - guid: 33e6bc5f-eeb6-5a07-810e-f04a2dba36cf, + 184736: FunctionGUID { + guid: 7f0e055b-1e83-5f7b-a1fd-6a0a3d0b74f5, }, - FunctionGUID { - guid: 3b992cd7-1721-5f30-b59c-84ea682be808, + 185040: FunctionGUID { + guid: c7bd1444-8f5c-5bc6-87c3-48f010b00455, }, - FunctionGUID { - guid: 3ba4d0cb-9c07-5904-aafe-34e051b89be5, + 185376: FunctionGUID { + guid: 18cfce71-47bc-5595-89cb-06e0563b211d, }, - FunctionGUID { - guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, + 185712: FunctionGUID { + guid: 8ff1fc25-6912-5772-8fa3-19e1b996aea7, }, - FunctionGUID { - guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c, + 186000: FunctionGUID { + guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c, }, - FunctionGUID { - guid: 53b484f3-751a-505d-beae-acc9d69c261d, + 186352: FunctionGUID { + guid: cdb3f650-974f-5686-b81f-10b6544680f4, }, - FunctionGUID { - guid: 5b7cc78f-3b2f-5b82-9726-37c26ad087b4, + 186784: FunctionGUID { + guid: 9e30f7d4-0a31-50cf-93f8-490a9e8c300b, }, - FunctionGUID { - guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c, + 187216: FunctionGUID { + guid: 0df54a49-5267-52f7-9665-70a8e66ad0dd, }, - FunctionGUID { - guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9, + 187664: FunctionGUID { + guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c, }, - FunctionGUID { - guid: 6db530d1-8ea4-568e-a089-c61164851f04, + 188096: FunctionGUID { + guid: 5b7cc78f-3b2f-5b82-9726-37c26ad087b4, }, - FunctionGUID { + 188512: FunctionGUID { guid: 7cae2466-6b19-5fff-869e-9a9717043df4, }, - FunctionGUID { + 188864: FunctionGUID { guid: 7cae2466-6b19-5fff-869e-9a9717043df4, }, - FunctionGUID { - guid: 7f0e055b-1e83-5f7b-a1fd-6a0a3d0b74f5, + 189216: FunctionGUID { + guid: 1b6aa5a3-ac7f-542d-a0ab-bae8f142d8d8, }, - FunctionGUID { - guid: 88d9b26b-3884-58d6-b164-435d8d888e5c, + 189632: FunctionGUID { + guid: 951d5c60-d457-5b62-98e7-ade4a37d7cbe, }, - FunctionGUID { - guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, + 190064: FunctionGUID { + guid: a7acb567-eb98-53e4-a843-5c60a8c59f19, }, - FunctionGUID { - guid: 8ff1fc25-6912-5772-8fa3-19e1b996aea7, + 190480: FunctionGUID { + guid: d5456209-db22-53a1-906d-1ecb3254ab2d, }, - FunctionGUID { - guid: 951d5c60-d457-5b62-98e7-ade4a37d7cbe, + 190912: FunctionGUID { + guid: bc4d18a4-96d5-5322-ba99-fcee83648701, }, - FunctionGUID { - guid: 95bf9d56-3b11-515f-960f-07abf69aed95, + 191328: FunctionGUID { + guid: ec40932b-8293-5ffb-9d31-04c282b6530d, }, - FunctionGUID { - guid: 9e30f7d4-0a31-50cf-93f8-490a9e8c300b, + 191744: FunctionGUID { + guid: 15d686e8-3e80-5632-9ce7-17c3510f8238, }, - FunctionGUID { - guid: a7acb567-eb98-53e4-a843-5c60a8c59f19, + 192160: FunctionGUID { + guid: faedbe75-8451-525b-83da-7a57ec96166e, }, - FunctionGUID { - guid: ae6f9d26-872c-5b6e-925e-40c0ec95c702, + 192592: FunctionGUID { + guid: 06ff982d-b167-5dce-9689-71a631820da6, }, - FunctionGUID { - guid: b8b2746b-20aa-5ad2-bcfd-ab5448d952eb, + 192944: FunctionGUID { + guid: 3b992cd7-1721-5f30-b59c-84ea682be808, }, - FunctionGUID { - guid: bc4d18a4-96d5-5322-ba99-fcee83648701, + 193296: FunctionGUID { + guid: 6db530d1-8ea4-568e-a089-c61164851f04, }, - FunctionGUID { - guid: c7bd1444-8f5c-5bc6-87c3-48f010b00455, + 193680: FunctionGUID { + guid: 95bf9d56-3b11-515f-960f-07abf69aed95, }, - FunctionGUID { + 194032: FunctionGUID { guid: cacf9fe4-7518-5b22-ae3a-ab775dea4c9a, }, - FunctionGUID { - guid: cdb3f650-974f-5686-b81f-10b6544680f4, + 194384: FunctionGUID { + guid: 00c71b63-3039-5aa3-94f7-1737530239d9, + }, + 194736: FunctionGUID { + guid: 33e6bc5f-eeb6-5a07-810e-f04a2dba36cf, }, - FunctionGUID { + 195088: FunctionGUID { guid: d344f254-4903-581d-ad93-713c9ff2be2e, }, - FunctionGUID { - guid: d5456209-db22-53a1-906d-1ecb3254ab2d, + 195440: FunctionGUID { + guid: 3ba4d0cb-9c07-5904-aafe-34e051b89be5, }, - FunctionGUID { - guid: ec40932b-8293-5ffb-9d31-04c282b6530d, + 195792: FunctionGUID { + guid: b8b2746b-20aa-5ad2-bcfd-ab5448d952eb, }, - FunctionGUID { - guid: faedbe75-8451-525b-83da-7a57ec96166e, + 196144: FunctionGUID { + guid: ae6f9d26-872c-5b6e-925e-40c0ec95c702, + }, + 196496: FunctionGUID { + guid: 0d6d3685-7a8a-5d1c-8b7a-fe14871218ff, }, -] +} diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__fptostr.snap b/plugins/warp/tests/snapshots/determinism___fptostr.obj.snap index 5628853f..717dc070 100644 --- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__fptostr.snap +++ b/plugins/warp/tests/snapshots/determinism___fptostr.obj.snap @@ -1,9 +1,9 @@ --- -source: plugins/warp/src/lib.rs +source: plugins/warp/tests/determinism.rs expression: functions --- -[ - FunctionGUID { +{ + 180832: FunctionGUID { guid: 9d4dba66-7106-5215-8e64-a47279f67dfe, }, -] +} diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__mbslen.snap b/plugins/warp/tests/snapshots/determinism___mbslen.obj.snap index 01ca9ec5..848666d9 100644 --- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__mbslen.snap +++ b/plugins/warp/tests/snapshots/determinism___mbslen.obj.snap @@ -1,39 +1,39 @@ --- -source: plugins/warp/src/lib.rs +source: plugins/warp/tests/determinism.rs expression: functions --- -[ - FunctionGUID { - guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, - }, - FunctionGUID { - guid: 1e794537-6289-59e7-bef9-0c72f3989db8, - }, - FunctionGUID { +{ + 178448: FunctionGUID { guid: 29690354-fa27-54d1-a8be-18535b19b1c3, }, - FunctionGUID { - guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, - }, - FunctionGUID { - guid: 6a7eb6dd-be3e-50dd-ae91-cea8f3eb4f06, + 178768: FunctionGUID { + guid: 1e794537-6289-59e7-bef9-0c72f3989db8, }, - FunctionGUID { + 179456: FunctionGUID { guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9, }, - FunctionGUID { - guid: 6b0d368e-83d9-55fc-9683-5812e651a49b, + 179808: FunctionGUID { + guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, }, - FunctionGUID { + 180112: FunctionGUID { guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, }, - FunctionGUID { - guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257, + 180448: FunctionGUID { + guid: 6b0d368e-83d9-55fc-9683-5812e651a49b, + }, + 181632: FunctionGUID { + guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, }, - FunctionGUID { + 181968: FunctionGUID { guid: f034945d-32e2-5d86-8a86-3761cc54f419, }, - FunctionGUID { + 182352: FunctionGUID { + guid: 6a7eb6dd-be3e-50dd-ae91-cea8f3eb4f06, + }, + 182688: FunctionGUID { + guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257, + }, + 183024: FunctionGUID { guid: f552d8ee-3064-50ed-af18-4e60f095c9ea, }, -] +} diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__memicmp.snap b/plugins/warp/tests/snapshots/determinism___memicmp.obj.snap index 8f83530e..3c9b0358 100644 --- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__memicmp.snap +++ b/plugins/warp/tests/snapshots/determinism___memicmp.obj.snap @@ -1,9 +1,9 @@ --- -source: plugins/warp/src/lib.rs +source: plugins/warp/tests/determinism.rs expression: functions --- -[ - FunctionGUID { +{ + 65728: FunctionGUID { guid: cf6e80f2-69aa-5757-a06f-fb2e4866ab14, }, -] +} diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__strnicm.snap b/plugins/warp/tests/snapshots/determinism___strnicm.obj.snap index 9d661016..4a118098 100644 --- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__strnicm.snap +++ b/plugins/warp/tests/snapshots/determinism___strnicm.obj.snap @@ -1,9 +1,9 @@ --- -source: plugins/warp/src/lib.rs +source: plugins/warp/tests/determinism.rs expression: functions --- -[ - FunctionGUID { +{ + 65728: FunctionGUID { guid: 7c350a2d-2282-5625-943e-009f317b8d2c, }, -] +} diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__wctype.snap b/plugins/warp/tests/snapshots/determinism___wctype.obj.snap index 2d9f4e73..3da522a5 100644 --- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot__wctype.snap +++ b/plugins/warp/tests/snapshots/determinism___wctype.obj.snap @@ -1,117 +1,117 @@ --- -source: plugins/warp/src/lib.rs +source: plugins/warp/tests/determinism.rs expression: functions --- -[ - FunctionGUID { - guid: 0a9027e0-fc87-5f83-a9da-ea7b1eef0761, +{ + 181536: FunctionGUID { + guid: 29690354-fa27-54d1-a8be-18535b19b1c3, }, - FunctionGUID { - guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, + 181856: FunctionGUID { + guid: 1e794537-6289-59e7-bef9-0c72f3989db8, }, - FunctionGUID { - guid: 1b434f0e-26b8-539b-9679-33d96a683190, + 182544: FunctionGUID { + guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9, }, - FunctionGUID { - guid: 1b434f0e-26b8-539b-9679-33d96a683190, + 182896: FunctionGUID { + guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, }, - FunctionGUID { - guid: 1e34d278-fd31-5cdf-98dc-395cb29419a7, + 183200: FunctionGUID { + guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, }, - FunctionGUID { - guid: 1e34d278-fd31-5cdf-98dc-395cb29419a7, + 183536: FunctionGUID { + guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, }, - FunctionGUID { - guid: 1e794537-6289-59e7-bef9-0c72f3989db8, + 183872: FunctionGUID { + guid: 1b434f0e-26b8-539b-9679-33d96a683190, }, - FunctionGUID { - guid: 23ba7382-bd48-5d47-a1f0-79ad5db87730, + 184208: FunctionGUID { + guid: b3723a76-a709-510c-9d93-97dc42b163a3, }, - FunctionGUID { - guid: 23ba7382-bd48-5d47-a1f0-79ad5db87730, + 184544: FunctionGUID { + guid: 576f40fd-30a0-53af-8102-7e1b5298d38a, }, - FunctionGUID { - guid: 29690354-fa27-54d1-a8be-18535b19b1c3, - }, - FunctionGUID { + 184960: FunctionGUID { guid: 33ce8a60-e3fa-5941-9ca6-09707bb1f579, }, - FunctionGUID { - guid: 33ce8a60-e3fa-5941-9ca6-09707bb1f579, + 185280: FunctionGUID { + guid: b2339a04-7dcf-5791-b74e-806220d37f56, }, - FunctionGUID { - guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, + 185600: FunctionGUID { + guid: b08dcc9b-b766-56e0-a9af-1d24cc6c6574, }, - FunctionGUID { - guid: 607a10c5-8943-5dec-b888-f5210f08b311, + 185952: FunctionGUID { + guid: 1e34d278-fd31-5cdf-98dc-395cb29419a7, }, - FunctionGUID { - guid: 607a10c5-8943-5dec-b888-f5210f08b311, + 186272: FunctionGUID { + guid: 1b434f0e-26b8-539b-9679-33d96a683190, }, - FunctionGUID { - guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9, + 186624: FunctionGUID { + guid: b3723a76-a709-510c-9d93-97dc42b163a3, }, - FunctionGUID { - guid: 6cebba56-9929-5f3d-9dd3-fc56f8e84593, + 186976: FunctionGUID { + guid: eed08c42-3616-5c27-82ad-719db5cce36e, }, - FunctionGUID { - guid: 75b0aba8-582a-5085-abbb-cebf55d2498d, + 187296: FunctionGUID { + guid: 23ba7382-bd48-5d47-a1f0-79ad5db87730, + }, + 187616: FunctionGUID { + guid: 90cb6aa0-f402-5874-ae2d-e10752ad6462, }, - FunctionGUID { + 187936: FunctionGUID { guid: 75b0aba8-582a-5085-abbb-cebf55d2498d, }, - FunctionGUID { - guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, + 188256: FunctionGUID { + guid: cc259c7e-0ada-5680-8ad7-b077d60bd2a1, }, - FunctionGUID { - guid: 90cb6aa0-f402-5874-ae2d-e10752ad6462, + 188576: FunctionGUID { + guid: 607a10c5-8943-5dec-b888-f5210f08b311, }, - FunctionGUID { - guid: 90cb6aa0-f402-5874-ae2d-e10752ad6462, + 188896: FunctionGUID { + guid: f6535880-21d4-5272-b2c0-5e63cbc208e7, }, - FunctionGUID { + 189216: FunctionGUID { guid: 9dd8ec46-e680-5fbe-b28b-30505b505fa3, }, - FunctionGUID { - guid: 9dd8ec46-e680-5fbe-b28b-30505b505fa3, + 189552: FunctionGUID { + guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e, }, - FunctionGUID { - guid: b08dcc9b-b766-56e0-a9af-1d24cc6c6574, + 189856: FunctionGUID { + guid: 33ce8a60-e3fa-5941-9ca6-09707bb1f579, }, - FunctionGUID { - guid: b08dcc9b-b766-56e0-a9af-1d24cc6c6574, + 190160: FunctionGUID { + guid: b2339a04-7dcf-5791-b74e-806220d37f56, }, - FunctionGUID { - guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e, + 190464: FunctionGUID { + guid: 6cebba56-9929-5f3d-9dd3-fc56f8e84593, }, - FunctionGUID { - guid: b2339a04-7dcf-5791-b74e-806220d37f56, + 190768: FunctionGUID { + guid: b08dcc9b-b766-56e0-a9af-1d24cc6c6574, }, - FunctionGUID { - guid: b2339a04-7dcf-5791-b74e-806220d37f56, + 191088: FunctionGUID { + guid: 1e34d278-fd31-5cdf-98dc-395cb29419a7, }, - FunctionGUID { - guid: b3723a76-a709-510c-9d93-97dc42b163a3, + 191392: FunctionGUID { + guid: eed08c42-3616-5c27-82ad-719db5cce36e, }, - FunctionGUID { - guid: b3723a76-a709-510c-9d93-97dc42b163a3, + 191696: FunctionGUID { + guid: 23ba7382-bd48-5d47-a1f0-79ad5db87730, }, - FunctionGUID { - guid: cc259c7e-0ada-5680-8ad7-b077d60bd2a1, + 192000: FunctionGUID { + guid: 90cb6aa0-f402-5874-ae2d-e10752ad6462, }, - FunctionGUID { - guid: cc259c7e-0ada-5680-8ad7-b077d60bd2a1, + 192304: FunctionGUID { + guid: 75b0aba8-582a-5085-abbb-cebf55d2498d, }, - FunctionGUID { - guid: eed08c42-3616-5c27-82ad-719db5cce36e, + 192608: FunctionGUID { + guid: cc259c7e-0ada-5680-8ad7-b077d60bd2a1, }, - FunctionGUID { - guid: eed08c42-3616-5c27-82ad-719db5cce36e, + 192912: FunctionGUID { + guid: 607a10c5-8943-5dec-b888-f5210f08b311, }, - FunctionGUID { + 193216: FunctionGUID { guid: f6535880-21d4-5272-b2c0-5e63cbc208e7, }, - FunctionGUID { - guid: f6535880-21d4-5272-b2c0-5e63cbc208e7, + 193520: FunctionGUID { + guid: 9dd8ec46-e680-5fbe-b28b-30505b505fa3, }, -] +} diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atof.snap b/plugins/warp/tests/snapshots/determinism__atof.obj.snap index 4ff8251c..a4841378 100644 --- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atof.snap +++ b/plugins/warp/tests/snapshots/determinism__atof.obj.snap @@ -1,390 +1,390 @@ --- -source: plugins/warp/src/lib.rs +source: plugins/warp/tests/determinism.rs expression: functions --- -[ - FunctionGUID { - guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d, +{ + 234752: FunctionGUID { + guid: 29690354-fa27-54d1-a8be-18535b19b1c3, }, - FunctionGUID { - guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d, + 235072: FunctionGUID { + guid: d8ba616a-1747-587c-9265-cd4e26ae35c8, }, - FunctionGUID { - guid: 06a9339a-7249-50f3-9c3d-131d3248d560, + 235536: FunctionGUID { + guid: 1b1669c2-4a81-5300-99f7-14cba1cc7b24, }, - FunctionGUID { - guid: 06a9339a-7249-50f3-9c3d-131d3248d560, + 236016: FunctionGUID { + guid: 72452dc5-da9c-54fc-82de-032aab1780a4, }, - FunctionGUID { - guid: 06a9339a-7249-50f3-9c3d-131d3248d560, + 236528: FunctionGUID { + guid: 87371a48-38c9-5e0e-81db-5e2d6da49202, }, - FunctionGUID { - guid: 06a9339a-7249-50f3-9c3d-131d3248d560, + 237088: FunctionGUID { + guid: c74ed160-24fe-50d7-ba1c-9751c4599420, }, - FunctionGUID { - guid: 07c4bac9-cf7d-5e00-833d-d23ba44c07d9, + 237600: FunctionGUID { + guid: 1eee5e05-95ef-5f99-8713-504c2003183c, }, - FunctionGUID { - guid: 0916139e-a5db-51c8-be24-2c6fb20a9e3a, + 238160: FunctionGUID { + guid: 65be15d9-ef02-58a4-af63-b40ddb3e56b7, }, - FunctionGUID { - guid: 0c7b078d-dafb-5be7-8b10-a25b1e691b33, + 238672: FunctionGUID { + guid: 3efd6327-6038-53ed-904c-a800bee86d2e, + }, + 239232: FunctionGUID { + guid: 82e98c80-859c-5ab3-83ab-b597fc520a3f, + }, + 239808: FunctionGUID { + guid: c44bdef1-d78a-57ec-bff5-4e7d32ff4337, + }, + 240448: FunctionGUID { + guid: 7cbd1cef-0134-5a29-a8c6-82697f5717fe, }, - FunctionGUID { + 240960: FunctionGUID { + guid: e4ba95bb-0221-5dbd-9884-13a39c4f9d55, + }, + 241520: FunctionGUID { guid: 0e6feb85-ff6f-5b86-bd2c-26fb9829241c, }, - FunctionGUID { + 242464: FunctionGUID { guid: 0e6feb85-ff6f-5b86-bd2c-26fb9829241c, }, - FunctionGUID { - guid: 11592e15-a2a6-51b3-bda2-534cc0434df0, + 243408: FunctionGUID { + guid: a0c07bfa-a027-5c19-9da4-d2d35d7beb40, }, - FunctionGUID { - guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, + 244208: FunctionGUID { + guid: a0c07bfa-a027-5c19-9da4-d2d35d7beb40, }, - FunctionGUID { - guid: 1b1669c2-4a81-5300-99f7-14cba1cc7b24, + 245008: FunctionGUID { + guid: 06a9339a-7249-50f3-9c3d-131d3248d560, }, - FunctionGUID { - guid: 1b969932-96ce-5a6f-b91c-82e5f7b7f33d, + 245408: FunctionGUID { + guid: 06a9339a-7249-50f3-9c3d-131d3248d560, }, - FunctionGUID { - guid: 1c037c1e-9af1-59af-b19e-122fdf885148, + 245808: FunctionGUID { + guid: 06a9339a-7249-50f3-9c3d-131d3248d560, }, - FunctionGUID { - guid: 1de88f85-a19c-5454-9122-ddc80f7509b4, + 246208: FunctionGUID { + guid: 06a9339a-7249-50f3-9c3d-131d3248d560, }, - FunctionGUID { + 246608: FunctionGUID { guid: 1de88f85-a19c-5454-9122-ddc80f7509b4, }, - FunctionGUID { - guid: 1e794537-6289-59e7-bef9-0c72f3989db8, - }, - FunctionGUID { - guid: 1eee5e05-95ef-5f99-8713-504c2003183c, + 246992: FunctionGUID { + guid: 1de88f85-a19c-5454-9122-ddc80f7509b4, }, - FunctionGUID { - guid: 2833f544-9acb-552d-aa4b-df7d51dbab64, + 247376: FunctionGUID { + guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83, }, - FunctionGUID { - guid: 29690354-fa27-54d1-a8be-18535b19b1c3, + 248672: FunctionGUID { + guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83, }, - FunctionGUID { - guid: 31a8e73e-74fe-5a33-b0ab-aa1b136e021d, + 249968: FunctionGUID { + guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83, }, - FunctionGUID { - guid: 364c5864-0cd1-5527-8ca0-2378d5b4c0a5, + 251264: FunctionGUID { + guid: 41fd6d24-0f8b-5848-9873-65b98bd370ab, }, - FunctionGUID { - guid: 3c38a678-9c4e-527a-b543-3075b345d532, + 255600: FunctionGUID { + guid: 89f0e45b-c1f6-5aeb-94a8-26e72bb66e6c, }, - FunctionGUID { - guid: 3c38a678-9c4e-527a-b543-3075b345d532, + 259968: FunctionGUID { + guid: 4f645f3c-3e89-5b5d-a185-9f1a25e94ed2, }, - FunctionGUID { - guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, + 261152: FunctionGUID { + guid: 4b60a419-4ad1-5808-b41f-d341868c9234, }, - FunctionGUID { - guid: 3eb2a4c5-84f5-5687-92a0-35ef66c8b746, + 262352: FunctionGUID { + guid: 7c43c697-22ef-57a9-a43a-2214e627749d, }, - FunctionGUID { - guid: 3efd6327-6038-53ed-904c-a800bee86d2e, + 263792: FunctionGUID { + guid: cfa4374b-0fc7-575d-9ba1-4a37d2de0406, }, - FunctionGUID { - guid: 40358a63-7e16-52da-9270-434c7d444b10, + 265248: FunctionGUID { + guid: 45770948-1c56-5b24-bd38-f5a559ae8d22, }, - FunctionGUID { - guid: 40358a63-7e16-52da-9270-434c7d444b10, + 265792: FunctionGUID { + guid: 45770948-1c56-5b24-bd38-f5a559ae8d22, }, - FunctionGUID { - guid: 40358a63-7e16-52da-9270-434c7d444b10, + 266336: FunctionGUID { + guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78, }, - FunctionGUID { - guid: 410b1cab-55ff-5931-a894-54842e56bee2, + 266880: FunctionGUID { + guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78, }, - FunctionGUID { - guid: 41fd6d24-0f8b-5848-9873-65b98bd370ab, + 267424: FunctionGUID { + guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab, }, - FunctionGUID { - guid: 4345609a-e619-5f33-84ca-7cc1ccdfa2d0, + 268912: FunctionGUID { + guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab, }, - FunctionGUID { - guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c, + 270400: FunctionGUID { + guid: 364c5864-0cd1-5527-8ca0-2378d5b4c0a5, }, - FunctionGUID { - guid: 45770948-1c56-5b24-bd38-f5a559ae8d22, + 271040: FunctionGUID { + guid: a5b947d4-a7e0-5072-86b3-240474c16595, }, - FunctionGUID { - guid: 45770948-1c56-5b24-bd38-f5a559ae8d22, + 271696: FunctionGUID { + guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, }, - FunctionGUID { - guid: 479b6feb-0dff-5c33-8f30-fd0c563b9abc, + 272128: FunctionGUID { + guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, }, - FunctionGUID { - guid: 4b60a419-4ad1-5808-b41f-d341868c9234, + 272560: FunctionGUID { + guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, }, - FunctionGUID { - guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4, + 272992: FunctionGUID { + guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, }, - FunctionGUID { - guid: 4f0bdeb9-691c-52f8-8541-6c834f3fbe64, + 273424: FunctionGUID { + guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, }, - FunctionGUID { - guid: 4f645f3c-3e89-5b5d-a185-9f1a25e94ed2, + 273856: FunctionGUID { + guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, }, - FunctionGUID { - guid: 52b2a610-5304-5e4f-8b79-9b975e9efb86, + 274288: FunctionGUID { + guid: a482559e-6a69-505e-b692-147deeced692, }, - FunctionGUID { - guid: 53b484f3-751a-505d-beae-acc9d69c261d, + 274720: FunctionGUID { + guid: a482559e-6a69-505e-b692-147deeced692, }, - FunctionGUID { - guid: 53b484f3-751a-505d-beae-acc9d69c261d, + 275152: FunctionGUID { + guid: 1e794537-6289-59e7-bef9-0c72f3989db8, }, - FunctionGUID { - guid: 56c82b1f-380c-52fe-ae70-a39962e5ffdc, + 275840: FunctionGUID { + guid: bbb9b018-a0cd-5f98-b941-367f8707434d, }, - FunctionGUID { - guid: 5779439c-a22f-5d33-8868-4e1832ad49e9, + 276224: FunctionGUID { + guid: 879b1d59-7301-5cb7-911e-db05292bee8f, }, - FunctionGUID { - guid: 5b55a8c8-1d3a-5825-a0d9-917f434e07af, + 276592: FunctionGUID { + guid: b9111cf4-dcff-52a5-8755-2dedea6d5c72, }, - FunctionGUID { - guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c, + 277040: FunctionGUID { + guid: 2833f544-9acb-552d-aa4b-df7d51dbab64, }, - FunctionGUID { - guid: 65be15d9-ef02-58a4-af63-b40ddb3e56b7, + 277488: FunctionGUID { + guid: 7995ca73-04fe-5b30-9e88-1e69197007c1, }, - FunctionGUID { - guid: 669a32f2-f1e9-5b38-98aa-9eb8431ebc4e, + 277872: FunctionGUID { + guid: 7995ca73-04fe-5b30-9e88-1e69197007c1, }, - FunctionGUID { + 278256: FunctionGUID { guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9, }, - FunctionGUID { - guid: 6c01cd47-89a2-59f3-ac6a-cd7806fb8f79, + 278608: FunctionGUID { + guid: bbb9b018-a0cd-5f98-b941-367f8707434d, }, - FunctionGUID { - guid: 72452dc5-da9c-54fc-82de-032aab1780a4, + 279008: FunctionGUID { + guid: 0c7b078d-dafb-5be7-8b10-a25b1e691b33, }, - FunctionGUID { - guid: 7995ca73-04fe-5b30-9e88-1e69197007c1, + 279536: FunctionGUID { + guid: 40358a63-7e16-52da-9270-434c7d444b10, }, - FunctionGUID { - guid: 7995ca73-04fe-5b30-9e88-1e69197007c1, + 279952: FunctionGUID { + guid: 40358a63-7e16-52da-9270-434c7d444b10, }, - FunctionGUID { - guid: 7c43c697-22ef-57a9-a43a-2214e627749d, + 280368: FunctionGUID { + guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, }, - FunctionGUID { - guid: 7cbd1cef-0134-5a29-a8c6-82697f5717fe, + 280784: FunctionGUID { + guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, }, - FunctionGUID { - guid: 7d4d1429-5fef-5c8b-9074-93e9d97ab57e, + 281200: FunctionGUID { + guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, }, - FunctionGUID { - guid: 82e98c80-859c-5ab3-83ab-b597fc520a3f, + 281616: FunctionGUID { + guid: 40358a63-7e16-52da-9270-434c7d444b10, }, - FunctionGUID { - guid: 84429ba2-9f72-585f-8cda-31e8ef4983fd, + 282032: FunctionGUID { + guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, }, - FunctionGUID { - guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab, + 282336: FunctionGUID { + guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, }, - FunctionGUID { - guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab, + 282672: FunctionGUID { + guid: 3eb2a4c5-84f5-5687-92a0-35ef66c8b746, }, - FunctionGUID { - guid: 87371a48-38c9-5e0e-81db-5e2d6da49202, + 283472: FunctionGUID { + guid: 5b55a8c8-1d3a-5825-a0d9-917f434e07af, }, - FunctionGUID { - guid: 879b1d59-7301-5cb7-911e-db05292bee8f, + 284352: FunctionGUID { + guid: f3d57592-4cb1-5356-9591-1f9327c58565, }, - FunctionGUID { - guid: 88d9b26b-3884-58d6-b164-435d8d888e5c, + 284768: FunctionGUID { + guid: fb20fa7c-c796-58e4-a2f9-7a128baaf5b1, }, - FunctionGUID { - guid: 89f0e45b-c1f6-5aeb-94a8-26e72bb66e6c, + 285216: FunctionGUID { + guid: 1c037c1e-9af1-59af-b19e-122fdf885148, }, - FunctionGUID { - guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, + 285712: FunctionGUID { + guid: 4345609a-e619-5f33-84ca-7cc1ccdfa2d0, }, - FunctionGUID { - guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, + 286208: FunctionGUID { + guid: c71f4559-f0af-54dc-a224-e1f341311988, }, - FunctionGUID { - guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, + 286688: FunctionGUID { + guid: 479b6feb-0dff-5c33-8f30-fd0c563b9abc, }, - FunctionGUID { - guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, + 288432: FunctionGUID { + guid: 7d4d1429-5fef-5c8b-9074-93e9d97ab57e, }, - FunctionGUID { - guid: 8dca0c9d-bdb7-56a1-9832-431aec16a68f, + 290768: FunctionGUID { + guid: c71f4559-f0af-54dc-a224-e1f341311988, }, - FunctionGUID { + 291232: FunctionGUID { guid: 9a9e3287-e407-5526-885b-1b5ee78a5300, }, - FunctionGUID { - guid: 9e4c3806-b291-569c-a687-0bcaa48e248b, + 291600: FunctionGUID { + guid: 84429ba2-9f72-585f-8cda-31e8ef4983fd, }, - FunctionGUID { - guid: 9ef21651-3eaf-5940-8e72-104bda834e85, + 291984: FunctionGUID { + guid: 07c4bac9-cf7d-5e00-833d-d23ba44c07d9, }, - FunctionGUID { - guid: a0c07bfa-a027-5c19-9da4-d2d35d7beb40, + 292384: FunctionGUID { + guid: 4f0bdeb9-691c-52f8-8541-6c834f3fbe64, }, - FunctionGUID { - guid: a0c07bfa-a027-5c19-9da4-d2d35d7beb40, + 296016: FunctionGUID { + guid: ecca9a9d-1722-5344-b68f-54f4527c0d78, }, - FunctionGUID { - guid: a23505e4-e7cd-5543-897c-46b97c1eaef3, + 296912: FunctionGUID { + guid: ed23805d-3729-590d-83a3-3b0803a56870, }, - FunctionGUID { - guid: a482559e-6a69-505e-b692-147deeced692, + 297280: FunctionGUID { + guid: 410b1cab-55ff-5931-a894-54842e56bee2, }, - FunctionGUID { - guid: a482559e-6a69-505e-b692-147deeced692, + 297648: FunctionGUID { + guid: 11592e15-a2a6-51b3-bda2-534cc0434df0, }, - FunctionGUID { - guid: a5b947d4-a7e0-5072-86b3-240474c16595, + 303056: FunctionGUID { + guid: 3c38a678-9c4e-527a-b543-3075b345d532, }, - FunctionGUID { + 303392: FunctionGUID { guid: a603d813-2b33-567d-80c8-06e31562a400, }, - FunctionGUID { - guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, - }, - FunctionGUID { - guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, - }, - FunctionGUID { - guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, - }, - FunctionGUID { - guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, - }, - FunctionGUID { - guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, + 303744: FunctionGUID { + guid: 56c82b1f-380c-52fe-ae70-a39962e5ffdc, }, - FunctionGUID { - guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, + 304096: FunctionGUID { + guid: eefbc8a9-f4f1-5725-9a4f-9dcd04ea3478, }, - FunctionGUID { - guid: adadfe23-cb3f-5c9f-ae8d-1dd2fd922367, + 304528: FunctionGUID { + guid: 1b969932-96ce-5a6f-b91c-82e5f7b7f33d, }, - FunctionGUID { - guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e, + 304848: FunctionGUID { + guid: a23505e4-e7cd-5543-897c-46b97c1eaef3, }, - FunctionGUID { - guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e, + 305216: FunctionGUID { + guid: 9ef21651-3eaf-5940-8e72-104bda834e85, }, - FunctionGUID { - guid: b0aca296-adc3-5b46-a6b1-e82ef2100ead, + 305568: FunctionGUID { + guid: 6c01cd47-89a2-59f3-ac6a-cd7806fb8f79, }, - FunctionGUID { - guid: b9111cf4-dcff-52a5-8755-2dedea6d5c72, + 306128: FunctionGUID { + guid: d72f40b9-0557-5695-aa4d-468515ec4b8e, }, - FunctionGUID { - guid: bbb9b018-a0cd-5f98-b941-367f8707434d, + 306464: FunctionGUID { + guid: 3c38a678-9c4e-527a-b543-3075b345d532, }, - FunctionGUID { - guid: bbb9b018-a0cd-5f98-b941-367f8707434d, + 306816: FunctionGUID { + guid: e480dd82-0b7d-5c75-ac67-ba8c0bb755cb, }, - FunctionGUID { - guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257, + 307168: FunctionGUID { + guid: e1b1e1df-fdb9-59d6-b966-5b7f926b5649, }, - FunctionGUID { - guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257, + 309232: FunctionGUID { + guid: f37337a6-4cf0-55b4-a0da-25a15e3d9bcb, }, - FunctionGUID { - guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203, + 309968: FunctionGUID { + guid: b0aca296-adc3-5b46-a6b1-e82ef2100ead, }, - FunctionGUID { - guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203, + 310352: FunctionGUID { + guid: db957206-4cca-5631-893d-1d3dc500c804, }, - FunctionGUID { - guid: c44bdef1-d78a-57ec-bff5-4e7d32ff4337, + 311760: FunctionGUID { + guid: adadfe23-cb3f-5c9f-ae8d-1dd2fd922367, }, - FunctionGUID { - guid: c71f4559-f0af-54dc-a224-e1f341311988, + 312416: FunctionGUID { + guid: dd480d6b-04f7-5fb2-a7b2-236e1d78b2b0, }, - FunctionGUID { - guid: c71f4559-f0af-54dc-a224-e1f341311988, + 312784: FunctionGUID { + guid: 5779439c-a22f-5d33-8868-4e1832ad49e9, }, - FunctionGUID { - guid: c74ed160-24fe-50d7-ba1c-9751c4599420, + 313216: FunctionGUID { + guid: 0916139e-a5db-51c8-be24-2c6fb20a9e3a, }, - FunctionGUID { + 313696: FunctionGUID { guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c, }, - FunctionGUID { + 314064: FunctionGUID { guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c, }, - FunctionGUID { - guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83, + 314432: FunctionGUID { + guid: 669a32f2-f1e9-5b38-98aa-9eb8431ebc4e, }, - FunctionGUID { - guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83, + 315360: FunctionGUID { + guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d, }, - FunctionGUID { - guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83, + 315696: FunctionGUID { + guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d, }, - FunctionGUID { - guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78, + 316032: FunctionGUID { + guid: 8dca0c9d-bdb7-56a1-9832-431aec16a68f, }, - FunctionGUID { - guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78, + 318192: FunctionGUID { + guid: 9e4c3806-b291-569c-a687-0bcaa48e248b, }, - FunctionGUID { - guid: cfa4374b-0fc7-575d-9ba1-4a37d2de0406, + 318960: FunctionGUID { + guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4, }, - FunctionGUID { - guid: d72f40b9-0557-5695-aa4d-468515ec4b8e, + 319616: FunctionGUID { + guid: 31a8e73e-74fe-5a33-b0ab-aa1b136e021d, }, - FunctionGUID { - guid: d8ba616a-1747-587c-9265-cd4e26ae35c8, + 320288: FunctionGUID { + guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203, }, - FunctionGUID { - guid: db957206-4cca-5631-893d-1d3dc500c804, + 320912: FunctionGUID { + guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203, }, - FunctionGUID { - guid: dd480d6b-04f7-5fb2-a7b2-236e1d78b2b0, + 321536: FunctionGUID { + guid: 8e490dee-4a47-5582-bb8d-0b502f4d70ba, }, - FunctionGUID { - guid: e1b1e1df-fdb9-59d6-b966-5b7f926b5649, + 322800: FunctionGUID { + guid: 88d9b26b-3884-58d6-b164-435d8d888e5c, }, - FunctionGUID { - guid: e480dd82-0b7d-5c75-ac67-ba8c0bb755cb, + 323168: FunctionGUID { + guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, }, - FunctionGUID { - guid: e4ba95bb-0221-5dbd-9884-13a39c4f9d55, + 323504: FunctionGUID { + guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257, }, - FunctionGUID { + 323824: FunctionGUID { guid: e8290b3d-cf56-5c1b-8315-abce0f135559, }, - FunctionGUID { - guid: e8290b3d-cf56-5c1b-8315-abce0f135559, + 324176: FunctionGUID { + guid: 53b484f3-751a-505d-beae-acc9d69c261d, }, - FunctionGUID { - guid: ecca9a9d-1722-5344-b68f-54f4527c0d78, + 324496: FunctionGUID { + guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257, }, - FunctionGUID { - guid: ed23805d-3729-590d-83a3-3b0803a56870, + 324816: FunctionGUID { + guid: e8290b3d-cf56-5c1b-8315-abce0f135559, }, - FunctionGUID { - guid: eefbc8a9-f4f1-5725-9a4f-9dcd04ea3478, + 325168: FunctionGUID { + guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c, }, - FunctionGUID { - guid: f37337a6-4cf0-55b4-a0da-25a15e3d9bcb, + 325520: FunctionGUID { + guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c, }, - FunctionGUID { - guid: f37cf37b-b19d-5575-b2c7-a6604d3faa69, + 325952: FunctionGUID { + guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e, }, - FunctionGUID { - guid: f3d57592-4cb1-5356-9591-1f9327c58565, + 326256: FunctionGUID { + guid: 53b484f3-751a-505d-beae-acc9d69c261d, }, - FunctionGUID { - guid: fb20fa7c-c796-58e4-a2f9-7a128baaf5b1, + 326576: FunctionGUID { + guid: b098f8a1-ecdd-5938-8da3-bb37935c6b7e, + }, + 326880: FunctionGUID { + guid: f37cf37b-b19d-5575-b2c7-a6604d3faa69, }, -] +} diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atoldbl.snap b/plugins/warp/tests/snapshots/determinism__atoldbl.obj.snap index 5dc305cc..2ff5221d 100644 --- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atoldbl.snap +++ b/plugins/warp/tests/snapshots/determinism__atoldbl.obj.snap @@ -1,192 +1,192 @@ --- -source: plugins/warp/src/lib.rs +source: plugins/warp/tests/determinism.rs expression: functions --- -[ - FunctionGUID { - guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d, +{ + 223424: FunctionGUID { + guid: 29690354-fa27-54d1-a8be-18535b19b1c3, }, - FunctionGUID { - guid: 07b3f23f-333b-5c13-873f-5f1973cee8e2, + 223744: FunctionGUID { + guid: c5717076-f595-525b-8db8-ca79496236ce, }, - FunctionGUID { - guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, + 224800: FunctionGUID { + guid: c5717076-f595-525b-8db8-ca79496236ce, }, - FunctionGUID { + 225856: FunctionGUID { guid: 1de88f85-a19c-5454-9122-ddc80f7509b4, }, - FunctionGUID { - guid: 1e794537-6289-59e7-bef9-0c72f3989db8, - }, - FunctionGUID { - guid: 22c32840-7bb0-5d80-858c-55dd04553db0, + 226240: FunctionGUID { + guid: c5766cb0-afbc-5701-ad2f-23d980f4bc15, }, - FunctionGUID { + 226720: FunctionGUID { guid: 29690354-fa27-54d1-a8be-18535b19b1c3, }, - FunctionGUID { - guid: 29690354-fa27-54d1-a8be-18535b19b1c3, + 227024: FunctionGUID { + guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83, }, - FunctionGUID { - guid: 30f44555-ef86-5e85-96c3-6153f4bcc11e, + 228320: FunctionGUID { + guid: 41fd6d24-0f8b-5848-9873-65b98bd370ab, }, - FunctionGUID { - guid: 364c5864-0cd1-5527-8ca0-2378d5b4c0a5, + 232656: FunctionGUID { + guid: 4f645f3c-3e89-5b5d-a185-9f1a25e94ed2, }, - FunctionGUID { - guid: 39cd0d51-d688-58e2-a757-ac868ff8635e, + 233840: FunctionGUID { + guid: 7c43c697-22ef-57a9-a43a-2214e627749d, }, - FunctionGUID { - guid: 3c83a5f5-215d-5d25-97c8-ffe65f73907c, + 235280: FunctionGUID { + guid: 45770948-1c56-5b24-bd38-f5a559ae8d22, }, - FunctionGUID { - guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, + 235824: FunctionGUID { + guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78, }, - FunctionGUID { - guid: 41fd6d24-0f8b-5848-9873-65b98bd370ab, + 236368: FunctionGUID { + guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab, }, - FunctionGUID { - guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c, + 237872: FunctionGUID { + guid: 364c5864-0cd1-5527-8ca0-2378d5b4c0a5, }, - FunctionGUID { - guid: 45770948-1c56-5b24-bd38-f5a559ae8d22, + 238512: FunctionGUID { + guid: 9891b84c-000b-529a-8c5b-d762120eff9d, }, - FunctionGUID { - guid: 4738dcb9-abab-5157-baef-8edf38d9fabc, + 239136: FunctionGUID { + guid: 61d32a04-c077-5985-b241-c326dd5b1864, }, - FunctionGUID { + 239760: FunctionGUID { guid: 48551d5f-2e1f-557b-85a3-a122d3ce07cf, }, - FunctionGUID { - guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4, + 240384: FunctionGUID { + guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, }, - FunctionGUID { - guid: 4f645f3c-3e89-5b5d-a185-9f1a25e94ed2, + 240816: FunctionGUID { + guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, }, - FunctionGUID { - guid: 5779439c-a22f-5d33-8868-4e1832ad49e9, + 241248: FunctionGUID { + guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, }, - FunctionGUID { - guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c, + 241680: FunctionGUID { + guid: a482559e-6a69-505e-b692-147deeced692, }, - FunctionGUID { - guid: 61d32a04-c077-5985-b241-c326dd5b1864, + 242112: FunctionGUID { + guid: 1e794537-6289-59e7-bef9-0c72f3989db8, }, - FunctionGUID { - guid: 6444ff14-38c5-520e-b9e3-6e3ae15215c4, + 242800: FunctionGUID { + guid: 7995ca73-04fe-5b30-9e88-1e69197007c1, }, - FunctionGUID { - guid: 6589669f-6b11-569e-9adf-ca3c6d6bc62e, + 243184: FunctionGUID { + guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9, }, - FunctionGUID { - guid: 6590d978-d2b0-510c-b5a6-a81361800609, + 243536: FunctionGUID { + guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, }, - FunctionGUID { - guid: 67f303c6-d2e4-55e1-8a5f-5492da69e69f, + 243952: FunctionGUID { + guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, }, - FunctionGUID { - guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9, + 244368: FunctionGUID { + guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, }, - FunctionGUID { - guid: 6ef93fa8-909e-596c-9454-74b89e8cabb7, + 244784: FunctionGUID { + guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, }, - FunctionGUID { - guid: 7995ca73-04fe-5b30-9e88-1e69197007c1, + 245088: FunctionGUID { + guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, }, - FunctionGUID { - guid: 7c43c697-22ef-57a9-a43a-2214e627749d, + 245424: FunctionGUID { + guid: a030f6f1-e29b-5e4a-8f83-56e6c6c51c15, }, - FunctionGUID { - guid: 86ab6ab4-45e5-5c08-9a47-05344ea503ab, + 245952: FunctionGUID { + guid: b9e4b6b0-47c3-54ca-9a01-56916ea0db8e, }, - FunctionGUID { - guid: 88d9b26b-3884-58d6-b164-435d8d888e5c, + 246352: FunctionGUID { + guid: b64bd893-5369-5ab2-a869-5f12d3fe313e, }, - FunctionGUID { - guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, + 246768: FunctionGUID { + guid: 6763e6c5-c15f-5eed-9dc9-e0c73284bb59, }, - FunctionGUID { - guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, + 247248: FunctionGUID { + guid: f489ca06-a1f3-5993-80ed-368eff1b7766, }, - FunctionGUID { - guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, + 247728: FunctionGUID { + guid: e12a9f6c-c7cc-573d-b488-e617331aedd9, }, - FunctionGUID { - guid: 8c736b66-b63f-5000-a060-2e60a1b8952a, + 248208: FunctionGUID { + guid: 1556466b-b8ff-5131-b8f3-ce53b21e973b, }, - FunctionGUID { - guid: 8ccf8b37-65c5-525a-b96a-64f26d711c75, + 248672: FunctionGUID { + guid: 73674636-a0a6-503a-bda7-55fc27f1bb58, }, - FunctionGUID { + 249680: FunctionGUID { guid: 9676fc90-ecba-595b-8d24-5df4e66ac683, }, - FunctionGUID { - guid: 9891b84c-000b-529a-8c5b-d762120eff9d, + 250048: FunctionGUID { + guid: 3c83a5f5-215d-5d25-97c8-ffe65f73907c, }, - FunctionGUID { - guid: a030f6f1-e29b-5e4a-8f83-56e6c6c51c15, + 250416: FunctionGUID { + guid: 4c636a77-6420-5996-95de-d3c1497d60b7, }, - FunctionGUID { - guid: a23505e4-e7cd-5543-897c-46b97c1eaef3, + 251088: FunctionGUID { + guid: a603d813-2b33-567d-80c8-06e31562a400, }, - FunctionGUID { - guid: a45d9322-0d45-5dc9-b17b-32b843416c4b, + 251440: FunctionGUID { + guid: 22c32840-7bb0-5d80-858c-55dd04553db0, }, - FunctionGUID { - guid: a482559e-6a69-505e-b692-147deeced692, + 252064: FunctionGUID { + guid: eefbc8a9-f4f1-5725-9a4f-9dcd04ea3478, }, - FunctionGUID { - guid: a603d813-2b33-567d-80c8-06e31562a400, + 252496: FunctionGUID { + guid: a23505e4-e7cd-5543-897c-46b97c1eaef3, }, - FunctionGUID { - guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, + 252864: FunctionGUID { + guid: 8ccf8b37-65c5-525a-b96a-64f26d711c75, }, - FunctionGUID { - guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, + 253424: FunctionGUID { + guid: f9a15815-d271-5c30-893a-239acdb42d21, }, - FunctionGUID { - guid: a707a2d3-c7d2-5b93-97c9-1db77d8ccfe5, + 255936: FunctionGUID { + guid: 7a4eafb9-4b86-5aac-b64f-af5d85be4a39, }, - FunctionGUID { - guid: b9e4b6b0-47c3-54ca-9a01-56916ea0db8e, + 256768: FunctionGUID { + guid: 0e4df1e2-2b70-5087-8518-d9cbbe62e048, }, - FunctionGUID { - guid: bae57993-863a-5c22-b8bb-92b862148af3, - }, - FunctionGUID { - guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257, + 257264: FunctionGUID { + guid: 5779439c-a22f-5d33-8868-4e1832ad49e9, }, - FunctionGUID { - guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203, + 257696: FunctionGUID { + guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c, }, - FunctionGUID { - guid: c5766cb0-afbc-5701-ad2f-23d980f4bc15, + 258064: FunctionGUID { + guid: bae57993-863a-5c22-b8bb-92b862148af3, }, - FunctionGUID { - guid: cb8ebff6-30e8-5a8a-9c1b-ec5f26041d65, + 258864: FunctionGUID { + guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d, }, - FunctionGUID { - guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c, + 259200: FunctionGUID { + guid: 6ef93fa8-909e-596c-9454-74b89e8cabb7, }, - FunctionGUID { - guid: ce28e080-0609-5bcd-abe1-2a6e57c91f83, + 259648: FunctionGUID { + guid: a45d9322-0d45-5dc9-b17b-32b843416c4b, }, - FunctionGUID { - guid: cf4808fe-2fea-517d-8f85-f88ddc96cc78, + 260000: FunctionGUID { + guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4, }, - FunctionGUID { - guid: e3b83b64-6ba1-5fe5-97da-5db987d79b07, + 260656: FunctionGUID { + guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203, }, - FunctionGUID { - guid: eefbc8a9-f4f1-5725-9a4f-9dcd04ea3478, + 261280: FunctionGUID { + guid: 88d9b26b-3884-58d6-b164-435d8d888e5c, }, - FunctionGUID { - guid: efcd394f-9201-57f7-aa2d-fc89ad355326, + 261648: FunctionGUID { + guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, }, - FunctionGUID { - guid: efcd394f-9201-57f7-aa2d-fc89ad355326, + 261984: FunctionGUID { + guid: bfaf2f0a-d92d-5ef7-879d-86eb0ca01257, }, - FunctionGUID { + 262304: FunctionGUID { guid: f25e7750-c61c-5ed8-a43d-959c3ba3a5b4, }, -] + 262976: FunctionGUID { + guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c, + }, + 263328: FunctionGUID { + guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c, + }, +} diff --git a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atox.snap b/plugins/warp/tests/snapshots/determinism__atox.obj.snap index c9960a24..57e4e2e9 100644 --- a/plugins/warp/src/snapshots/warp_ninja__tests__snapshot_atox.snap +++ b/plugins/warp/tests/snapshots/determinism__atox.obj.snap @@ -1,186 +1,186 @@ --- -source: plugins/warp/src/lib.rs +source: plugins/warp/tests/determinism.rs expression: functions --- -[ - FunctionGUID { - guid: 0244fa6b-6425-5aa9-ad87-2378cc4992e7, - }, - FunctionGUID { - guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d, - }, - FunctionGUID { - guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d, - }, - FunctionGUID { - guid: 067d6811-472a-5e4a-b65f-3760df0b7bd5, +{ + 220944: FunctionGUID { + guid: 29690354-fa27-54d1-a8be-18535b19b1c3, }, - FunctionGUID { - guid: 0916139e-a5db-51c8-be24-2c6fb20a9e3a, + 221264: FunctionGUID { + guid: 39d556f9-435a-5105-b56c-6558ddc63fc0, }, - FunctionGUID { - guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, + 221760: FunctionGUID { + guid: 46eae5e0-61e2-5316-9e4d-030dd2240567, }, - FunctionGUID { + 222304: FunctionGUID { guid: 1de88f85-a19c-5454-9122-ddc80f7509b4, }, - FunctionGUID { + 222688: FunctionGUID { guid: 1de88f85-a19c-5454-9122-ddc80f7509b4, }, - FunctionGUID { - guid: 1e794537-6289-59e7-bef9-0c72f3989db8, - }, - FunctionGUID { - guid: 283e5e36-a239-5f0c-8937-f48a4b1d86e0, - }, - FunctionGUID { - guid: 29690354-fa27-54d1-a8be-18535b19b1c3, - }, - FunctionGUID { - guid: 31a8e73e-74fe-5a33-b0ab-aa1b136e021d, + 223072: FunctionGUID { + guid: 067d6811-472a-5e4a-b65f-3760df0b7bd5, }, - FunctionGUID { + 225616: FunctionGUID { guid: 33717d5d-a15f-5c10-bc4a-d85cce963aca, }, - FunctionGUID { - guid: 39d556f9-435a-5105-b56c-6558ddc63fc0, + 228160: FunctionGUID { + guid: 65cb411a-0ae9-546f-9293-3abaa3fd1c31, }, - FunctionGUID { - guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, + 230912: FunctionGUID { + guid: 0244fa6b-6425-5aa9-ad87-2378cc4992e7, }, - FunctionGUID { + 233680: FunctionGUID { guid: 3f9f8782-0b8c-5843-8b1d-831a1ef9b024, }, - FunctionGUID { + 234128: FunctionGUID { guid: 3f9f8782-0b8c-5843-8b1d-831a1ef9b024, }, - FunctionGUID { + 234592: FunctionGUID { guid: 3f9f8782-0b8c-5843-8b1d-831a1ef9b024, }, - FunctionGUID { + 235056: FunctionGUID { guid: 3f9f8782-0b8c-5843-8b1d-831a1ef9b024, }, - FunctionGUID { - guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c, + 235520: FunctionGUID { + guid: a482559e-6a69-505e-b692-147deeced692, }, - FunctionGUID { - guid: 46eae5e0-61e2-5316-9e4d-030dd2240567, + 235952: FunctionGUID { + guid: a482559e-6a69-505e-b692-147deeced692, }, - FunctionGUID { - guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4, + 236384: FunctionGUID { + guid: 1e794537-6289-59e7-bef9-0c72f3989db8, }, - FunctionGUID { - guid: 56c82b1f-380c-52fe-ae70-a39962e5ffdc, + 237072: FunctionGUID { + guid: 7995ca73-04fe-5b30-9e88-1e69197007c1, }, - FunctionGUID { - guid: 5779439c-a22f-5d33-8868-4e1832ad49e9, + 237456: FunctionGUID { + guid: 7995ca73-04fe-5b30-9e88-1e69197007c1, }, - FunctionGUID { - guid: 580affd8-2657-50b2-9b65-3327422894de, + 237840: FunctionGUID { + guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9, }, - FunctionGUID { - guid: 580affd8-2657-50b2-9b65-3327422894de, + 238192: FunctionGUID { + guid: 13b16f81-0c6f-5aee-ad9f-0d142658cf18, }, - FunctionGUID { - guid: 580affd8-2657-50b2-9b65-3327422894de, + 238496: FunctionGUID { + guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, }, - FunctionGUID { - guid: 580affd8-2657-50b2-9b65-3327422894de, + 238832: FunctionGUID { + guid: a603d813-2b33-567d-80c8-06e31562a400, }, - FunctionGUID { - guid: 580affd8-2657-50b2-9b65-3327422894de, + 239184: FunctionGUID { + guid: 56c82b1f-380c-52fe-ae70-a39962e5ffdc, }, - FunctionGUID { - guid: 580affd8-2657-50b2-9b65-3327422894de, + 239536: FunctionGUID { + guid: a23505e4-e7cd-5543-897c-46b97c1eaef3, }, - FunctionGUID { - guid: 580affd8-2657-50b2-9b65-3327422894de, + 239904: FunctionGUID { + guid: 9ef21651-3eaf-5940-8e72-104bda834e85, }, - FunctionGUID { - guid: 580affd8-2657-50b2-9b65-3327422894de, + 240256: FunctionGUID { + guid: c66dd704-899b-5d58-9d4a-136063f5f552, }, - FunctionGUID { - guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c, + 240560: FunctionGUID { + guid: 283e5e36-a239-5f0c-8937-f48a4b1d86e0, }, - FunctionGUID { - guid: 65cb411a-0ae9-546f-9293-3abaa3fd1c31, + 240864: FunctionGUID { + guid: a8a9d162-b637-5ce6-82f8-a8ac0fb740dc, }, - FunctionGUID { - guid: 6abe3fc2-6d29-5fde-a31e-bb8249db6bf9, + 241168: FunctionGUID { + guid: f674b84a-7a05-5ca0-aac4-5b5598683087, }, - FunctionGUID { - guid: 7995ca73-04fe-5b30-9e88-1e69197007c1, + 241472: FunctionGUID { + guid: 5779439c-a22f-5d33-8868-4e1832ad49e9, }, - FunctionGUID { - guid: 7995ca73-04fe-5b30-9e88-1e69197007c1, + 241904: FunctionGUID { + guid: 0916139e-a5db-51c8-be24-2c6fb20a9e3a, }, - FunctionGUID { - guid: 88d9b26b-3884-58d6-b164-435d8d888e5c, + 242384: FunctionGUID { + guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c, }, - FunctionGUID { - guid: 8a2b7bda-5fdb-5ba3-888b-20d5b8d7b2cd, + 242752: FunctionGUID { + guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c, }, - FunctionGUID { - guid: 9ef21651-3eaf-5940-8e72-104bda834e85, + 243120: FunctionGUID { + guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d, }, - FunctionGUID { - guid: a23505e4-e7cd-5543-897c-46b97c1eaef3, + 243456: FunctionGUID { + guid: 02d864ef-80bc-5265-b78f-0e42815a4d1d, }, - FunctionGUID { - guid: a482559e-6a69-505e-b692-147deeced692, + 243792: FunctionGUID { + guid: 4ba8a311-60f3-57d4-a17a-c0cc93f5c2a4, }, - FunctionGUID { - guid: a482559e-6a69-505e-b692-147deeced692, + 244448: FunctionGUID { + guid: 31a8e73e-74fe-5a33-b0ab-aa1b136e021d, }, - FunctionGUID { - guid: a603d813-2b33-567d-80c8-06e31562a400, + 245120: FunctionGUID { + guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203, }, - FunctionGUID { - guid: a8a9d162-b637-5ce6-82f8-a8ac0fb740dc, + 245744: FunctionGUID { + guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203, }, - FunctionGUID { - guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, + 246368: FunctionGUID { + guid: 8e490dee-4a47-5582-bb8d-0b502f4d70ba, }, - FunctionGUID { - guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, + 247632: FunctionGUID { + guid: 88d9b26b-3884-58d6-b164-435d8d888e5c, }, - FunctionGUID { - guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, + 248000: FunctionGUID { + guid: 3cc2e827-b707-5c9d-82e9-d76ac3abc904, }, - FunctionGUID { + 248336: FunctionGUID { guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, }, - FunctionGUID { - guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, + 248640: FunctionGUID { + guid: 580affd8-2657-50b2-9b65-3327422894de, }, - FunctionGUID { - guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, + 248976: FunctionGUID { + guid: 580affd8-2657-50b2-9b65-3327422894de, + }, + 249296: FunctionGUID { + guid: 580affd8-2657-50b2-9b65-3327422894de, + }, + 249616: FunctionGUID { + guid: 580affd8-2657-50b2-9b65-3327422894de, + }, + 249936: FunctionGUID { + guid: 5b7f41e9-de1e-558c-ac7d-888acca3a76c, + }, + 250288: FunctionGUID { + guid: 43a8c54b-dd4f-5334-ae00-218cb758ad4c, }, - FunctionGUID { + 250720: FunctionGUID { guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, }, - FunctionGUID { + 251024: FunctionGUID { guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, }, - FunctionGUID { - guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203, + 251328: FunctionGUID { + guid: 580affd8-2657-50b2-9b65-3327422894de, }, - FunctionGUID { - guid: c3842e85-b9f2-54d5-b353-e9bb2a0b1203, + 251664: FunctionGUID { + guid: 580affd8-2657-50b2-9b65-3327422894de, }, - FunctionGUID { - guid: c66dd704-899b-5d58-9d4a-136063f5f552, + 251984: FunctionGUID { + guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, }, - FunctionGUID { - guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c, + 252288: FunctionGUID { + guid: 580affd8-2657-50b2-9b65-3327422894de, }, - FunctionGUID { - guid: ccbd9943-6b24-59d8-ae4d-3ee868a2729c, + 252608: FunctionGUID { + guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, }, - FunctionGUID { - guid: f674b84a-7a05-5ca0-aac4-5b5598683087, + 252912: FunctionGUID { + guid: 580affd8-2657-50b2-9b65-3327422894de, + }, + 253232: FunctionGUID { + guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, }, - FunctionGUID { - guid: f762c527-0674-52ce-b59f-49bf4e13d878, + 253536: FunctionGUID { + guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, + }, + 253840: FunctionGUID { + guid: bdf90a04-d375-59b1-8a92-b962b3f914ae, }, -] +} diff --git a/plugins/warp/ui/CMakeLists.txt b/plugins/warp/ui/CMakeLists.txt new file mode 100644 index 00000000..ebfaddc4 --- /dev/null +++ b/plugins/warp/ui/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) + +project(warp_ui CXX C) + +file(GLOB SOURCES CONFIGURE_DEPENDS + plugin.cpp plugin.h + matches.cpp matches.h + matched.cpp matched.h + shared/misc.cpp shared/misc.h + shared/constraint.cpp shared/constraint.h + shared/function.cpp shared/function.h + shared/container.cpp shared/container.h) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED) + +add_library(${PROJECT_NAME} SHARED ${SOURCES} ${MOCS}) + +target_include_directories(${PROJECT_NAME} PRIVATE ../api) + +if (NOT BN_API_BUILD_EXAMPLES AND NOT BN_INTERNAL_BUILD) + # Out-of-tree build + find_path( + BN_API_PATH + NAMES binaryninjaapi.h + HINTS ../.. binaryninjaapi $ENV{BN_API_PATH} + REQUIRED + ) + add_subdirectory(${BN_API_PATH} api) +endif () + +target_link_libraries(${PROJECT_NAME} binaryninjaui warp_api Qt6::Core Qt6::Gui Qt6::Widgets) + +set_target_properties(${PROJECT_NAME} PROPERTIES + CXX_STANDARD 17 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON) + +if (BN_INTERNAL_BUILD) + ui_plugin_rpath(${PROJECT_NAME}) + + set_target_properties(${PROJECT_NAME} PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR} + RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}) +else () + bn_install_plugin(${PROJECT_NAME}) +endif () diff --git a/plugins/warp/ui/matched.cpp b/plugins/warp/ui/matched.cpp new file mode 100644 index 00000000..475e0cdd --- /dev/null +++ b/plugins/warp/ui/matched.cpp @@ -0,0 +1,83 @@ +#include "matched.h" + +#include <QGridLayout> + +#include "theme.h" + +const char *WARP_APPLY_ACTIVITY = "analysis.warp.apply"; + +WarpMatchedWidget::WarpMatchedWidget(BinaryViewRef current) +{ + m_current = current; + // Create the QT stuff + QGridLayout *layout = new QGridLayout(this); + layout->setContentsMargins(2, 2, 2, 2); + layout->setSpacing(2); + auto newPalette = palette(); + newPalette.setColor(QPalette::Window, getThemeColor(SidebarWidgetBackgroundColor)); + setAutoFillBackground(true); + setPalette(newPalette); + + // TODO: Split horizontally if the widget is displayed in a sidebar that is vertically challenged. + m_splitter = new QSplitter(Qt::Vertical); + m_splitter->setContentsMargins(0, 0, 0, 0); + + // Add a widget to display the matches. + m_tableWidget = new WarpFunctionTableWidget(this); + m_tableWidget->setContentsMargins(0, 0, 0, 0); + m_splitter->addWidget(m_tableWidget); + + // Toggle the applying workflow, this workflow sets all the data for the function based on the matched function data. + m_tableWidget->RegisterContextMenuAction("Toggle Application", + [this](WarpFunctionItem *, std::optional<uint64_t> address) { + if (!address.has_value()) + return; + for (const auto &func: m_current->GetAnalysisFunctionsForAddress( + *address)) + { + const bool previous = BinaryNinja::Settings::Instance()->Get<bool>( + WARP_APPLY_ACTIVITY, func); + BinaryNinja::Settings::Instance()->Set( + WARP_APPLY_ACTIVITY, !previous, func); + func->Reanalyze(); + } + }); + + layout->addWidget(m_splitter, 1, 0, 1, 5); + setLayout(layout); + + Update(); + + connect(m_tableWidget->GetTableView(), &QTableView::clicked, this, + [this](const QModelIndex &index) { + if (m_current == nullptr) + return; + if (!index.isValid()) + return; + const QModelIndex sourceIndex = m_tableWidget->GetProxyModel()->mapToSource(index); + if (!sourceIndex.isValid()) + return; + auto selectedItem = m_tableWidget->GetModel()->GetAddress(sourceIndex); + if (!selectedItem.has_value()) + return; + // Navigate to the address in the view, so the user feels like they are doing something. + auto currentView = m_current->GetCurrentView(); + m_current->Navigate(currentView, selectedItem.value()); + }); +} + +void WarpMatchedWidget::Update() +{ + m_tableWidget->GetTableView()->setSortingEnabled(false); + m_tableWidget->GetTableView()->setEnabled(false); + for (const auto &analysisFunction: m_current->GetAnalysisFunctionList()) + { + if (const auto &matchedFunction = Warp::Function::GetMatched(*analysisFunction)) + { + uint64_t startAddress = analysisFunction->GetStart(); + m_tableWidget->InsertFunction(startAddress, new WarpFunctionItem(matchedFunction, analysisFunction)); + } + } + m_tableWidget->GetTableView()->setEnabled(true); + m_tableWidget->GetTableView()->setSortingEnabled(true); +} diff --git a/plugins/warp/ui/matched.h b/plugins/warp/ui/matched.h new file mode 100644 index 00000000..5e333e25 --- /dev/null +++ b/plugins/warp/ui/matched.h @@ -0,0 +1,27 @@ +#pragma once +#include <QSplitter> + +#include "uitypes.h" +#include "shared/function.h" + +class WarpMatchedFunctionTableWidget : public WarpFunctionTableWidget +{ + Q_OBJECT +}; + +class WarpMatchedWidget : public QWidget +{ + Q_OBJECT + BinaryViewRef m_current; + + QSplitter *m_splitter; + + WarpFunctionTableWidget *m_tableWidget; + +public: + explicit WarpMatchedWidget(BinaryViewRef current); + + ~WarpMatchedWidget() override = default; + + void Update(); +}; diff --git a/plugins/warp/ui/matches.cpp b/plugins/warp/ui/matches.cpp new file mode 100644 index 00000000..fad729d6 --- /dev/null +++ b/plugins/warp/ui/matches.cpp @@ -0,0 +1,164 @@ +#include <QGridLayout> +#include <QHeaderView> + +#include "matches.h" + +#include <QClipboard> +#include <QFormLayout> + +#include "theme.h" +#include "warp.h" +#include "shared/misc.h" + +WarpCurrentFunctionWidget::WarpCurrentFunctionWidget(FunctionRef current) +{ + // NOTE: Might be nullptr if the no selected function. + m_current = current; + + // Create the QT stuff + QGridLayout *layout = new QGridLayout(this); + layout->setContentsMargins(2, 2, 2, 2); + layout->setSpacing(2); + auto newPalette = palette(); + newPalette.setColor(QPalette::Window, getThemeColor(SidebarWidgetBackgroundColor)); + setAutoFillBackground(true); + setPalette(newPalette); + + // TODO: Split horizontally if the widget is displayed in a sidebar that is vertically challenged. + m_splitter = new QSplitter(Qt::Vertical); + m_splitter->setContentsMargins(0, 0, 0, 0); + + // Add a widget to display the matches. + m_tableWidget = new WarpFunctionTableWidget(this); + m_tableWidget->setContentsMargins(0, 0, 0, 0); + m_splitter->addWidget(m_tableWidget); + + // Add a widget to display the info about the selected function match. + m_infoWidget = new WarpFunctionInfoWidget(this); + m_infoWidget->setContentsMargins(0, 0, 0, 0); + m_splitter->addWidget(m_infoWidget); + + layout->addWidget(m_splitter, 1, 0, 1, 5); + setLayout(layout); + + m_tableWidget->RegisterContextMenuAction("Apply", [this](WarpFunctionItem *item, std::optional<uint64_t>) { + if (item == nullptr) + return; + Warp::Ref<Warp::Function> selectedFunction = item->GetFunction(); + if (!selectedFunction) + return; + selectedFunction->Apply(*m_current); + // Update analysis so that the selected function shows. + m_current->GetView()->UpdateAnalysis(); + // So it shows visually as selected. + m_tableWidget->GetModel()->SetMatchedFunction(selectedFunction); + }); + m_tableWidget->RegisterContextMenuAction("Search for Source", + [this](WarpFunctionItem *item, std::optional<uint64_t>) { + // Apply the source as the filter. + if (const auto source = item->GetSource(); source) + m_tableWidget->setFilter(source->ToString()); + }); + + connect(m_tableWidget->GetTableView(), &QTableView::clicked, this, + [this](const QModelIndex &index) { + if (m_current == nullptr) + return; + if (!index.isValid()) + return; + const QModelIndex sourceIndex = m_tableWidget->GetProxyModel()->mapToSource(index); + if (!sourceIndex.isValid()) + return; + auto selectedItem = m_tableWidget->GetModel()->GetItem(sourceIndex); + // Access the first column in the row + if (!selectedItem) + return; + m_infoWidget->SetFunction(selectedItem->GetFunction()); + m_infoWidget->UpdateInfo(); + }); + + + connect(m_tableWidget->GetTableView(), &QTableView::doubleClicked, this, [=](const QModelIndex &index) { + if (m_current == nullptr) + return; + // Get the selected row for the given index. + if (!index.isValid()) + return; + const QModelIndex sourceIndex = m_tableWidget->GetProxyModel()->mapToSource(index); + if (!sourceIndex.isValid()) + return; + auto selectedItem = m_tableWidget->GetModel()->GetItem(sourceIndex); + // Access the first column in the row + if (!selectedItem) + return; + Warp::Ref<Warp::Function> selectedFunction = selectedItem->GetFunction(); + + // Actually apply the newly selected function. + selectedFunction->Apply(*m_current); + + // Update analysis so that the selected function shows. + m_current->GetView()->UpdateAnalysis(); + + // So it shows visually as selected. + m_tableWidget->GetModel()->SetMatchedFunction(selectedFunction); + }); +} + +void WarpCurrentFunctionWidget::SetCurrentFunction(FunctionRef current) +{ + if (m_current == current) + return; + m_current = current; + m_infoWidget->SetAnalysisFunction(m_current); + UpdateMatches(); +} + +void WarpCurrentFunctionWidget::UpdateMatches() +{ + if (!m_current) + return; + const auto guid = Warp::GetAnalysisFunctionGUID(*m_current); + if (!guid.has_value()) + return; + + // Set the matched function for highlighting. + Warp::Ref<Warp::Function> matchedFunction = Warp::Function::GetMatched(*m_current); + m_tableWidget->GetModel()->SetMatchedFunction(matchedFunction); + + // We swapped functions, reset the info widget to the default state with new analysis function. + m_infoWidget->SetFunction(matchedFunction); + m_infoWidget->UpdateInfo(); + + Warp::Ref<Warp::Target> target = Warp::Target::FromPlatform(*m_current->GetPlatform()); + + // Add all the possible matches for the current function to the model. + QVector<WarpFunctionItem *> matches; + bool matchedFuncAdded = false; + // TODO: When we add in the networked container we need to update this stuff on a separate thread and show a spinny thing. + for (const auto &container: Warp::Container::All()) + { + for (const auto &source: container->GetSourcesWithFunctionGUID(*target, guid.value())) + { + for (const auto &function: container->GetFunctionsWithGUID(*target, source, guid.value())) + { + // TODO: This does not work. + if (matchedFunction && BNWARPFunctionsEqual(function->m_object, matchedFunction->m_object)) + matchedFuncAdded = true; + auto item = new WarpFunctionItem(function, m_current); + item->SetContainer(container); + item->SetSource(source); + matches.emplace_back(item); + } + } + } + + // Add the matched function unconditionally, assuming it has not been found in a container. + // NOTE: This happens when you load from a database for example. + if (matchedFunction && !matchedFuncAdded) + { + auto item = new WarpFunctionItem(matchedFunction, m_current); + matches.emplace_back(item); + } + + m_tableWidget->SetFunctions(matches); +} diff --git a/plugins/warp/ui/matches.h b/plugins/warp/ui/matches.h new file mode 100644 index 00000000..4021f10c --- /dev/null +++ b/plugins/warp/ui/matches.h @@ -0,0 +1,29 @@ +#pragma once + +#include <QSplitter> + +#include "filter.h" +#include "render.h" +#include "shared/function.h" + +class WarpCurrentFunctionWidget : public QWidget +{ + Q_OBJECT + FunctionRef m_current; + + QSplitter *m_splitter; + + WarpFunctionTableWidget *m_tableWidget; + WarpFunctionInfoWidget *m_infoWidget; + +public: + explicit WarpCurrentFunctionWidget(FunctionRef current); + + ~WarpCurrentFunctionWidget() override = default; + + void SetCurrentFunction(FunctionRef current); + + FunctionRef GetCurrentFunction() { return m_current; }; + + void UpdateMatches(); +}; diff --git a/plugins/warp/ui/plugin.cpp b/plugins/warp/ui/plugin.cpp new file mode 100644 index 00000000..69aaaf34 --- /dev/null +++ b/plugins/warp/ui/plugin.cpp @@ -0,0 +1,198 @@ +#include "plugin.h" + +#include <QToolBar> + +#include "matched.h" +#include "matches.h" +#include "symbollist.h" +#include "viewframe.h" + +using namespace BinaryNinja; + +QIcon GetColoredIcon(const QString &iconPath, const QColor &color) +{ + auto pixmap = QPixmap(iconPath); + auto mask = pixmap.createMaskFromColor(QColor(0, 0, 0), Qt::MaskInColor); + pixmap.fill(color); + pixmap.setMask(mask); + return QIcon(pixmap); +} + +Ref<BackgroundTask> GetMatcherTask() +{ + // TODO: What happens if we have multiple views open matching? This fails. + // Look for the matcher background task to determine if we are stopping or starting it. + Ref<BackgroundTask> matcherTask = nullptr; + for (const auto &task: BackgroundTask::GetRunningTasks()) + { + std::string progressText = task->GetProgressText(); + if (progressText.find("Matching on WARP") != std::string::npos) + matcherTask = task; + } + return matcherTask; +} + +WarpSidebarWidget::WarpSidebarWidget(BinaryViewRef data) : SidebarWidget("WARP"), m_data(data) +{ + m_logger = LogRegistry::CreateLogger("WARPUI"); + m_currentFrame = nullptr; + + m_headerWidget = new QWidget(); + QHBoxLayout *headerLayout = new QHBoxLayout(); + headerLayout->setContentsMargins(0, 0, 0, 0); + headerLayout->setSpacing(0); + + QToolBar *headerToolbar = new QToolBar(this); + headerToolbar->setContentsMargins(0, 0, 0, 0); + headerToolbar->setIconSize(QSize(20, 20)); + + static auto matcherStopIcon = GetColoredIcon(":/icons/images/stop.png", getThemeColor(RedStandardHighlightColor)); + static auto matcherStartIcon = GetColoredIcon(":/icons/images/start.png", + getThemeColor(GreenStandardHighlightColor)); + m_matcherAction = headerToolbar->addAction(matcherStartIcon, "Run Matcher", [this]() { + UIActionHandler *handler = m_currentFrame->getCurrentViewInterface()->actionHandler(); + if (Ref<BackgroundTask> matcherTask = GetMatcherTask()) + matcherTask->Cancel(); + else if (!isMatcherRunning) + { + handler->executeAction("WARP\\Run Matcher"); + setMatcherActionIcon(true); + } + }); + m_matcherAction->setToolTip("Run the matcher on all functions"); + + auto loadIcon = GetColoredIcon(":/icons/images/file-add.png", getThemeColor(BlueStandardHighlightColor)); + auto loadAction = headerToolbar->addAction(loadIcon, "Load Signature File", [this]() { + UIActionHandler *handler = m_currentFrame->getCurrentViewInterface()->actionHandler(); + handler->executeAction("WARP\\Load File"); + }); + loadAction->setToolTip("Load a signature file to match against"); + + auto saveIcon = GetColoredIcon(":/icons/images/edit.png", getThemeColor(BlueStandardHighlightColor)); + auto saveAction = headerToolbar->addAction(saveIcon, "Create Signature File", [this]() { + UIActionHandler *handler = m_currentFrame->getCurrentViewInterface()->actionHandler(); + handler->executeAction("WARP\\Create\\From Current View"); + }); + saveAction->setToolTip("Save data to a signature file"); + + auto refreshIcon = GetColoredIcon(":/icons/images/refresh.png", getThemeColor(BlueStandardHighlightColor)); + auto refreshAction = headerToolbar->addAction(refreshIcon, "Refresh the view data", [this]() { + Update(); + }); + refreshAction->setToolTip("Refresh the sidebar data"); + + // TODO: Add action for pushing to network sources. + + // Push the toolbar to the right using a stretch space. + headerLayout->addStretch(); + headerLayout->addWidget(headerToolbar, 0); + m_headerWidget->setLayout(headerLayout); + + QFrame *currentFunctionFrame = new QFrame(this); + m_currentFunctionWidget = new WarpCurrentFunctionWidget(nullptr); + QVBoxLayout *currentFunctionLayout = new QVBoxLayout(); + currentFunctionLayout->setContentsMargins(0, 0, 0, 0); + currentFunctionLayout->setSpacing(0); + currentFunctionLayout->addWidget(m_currentFunctionWidget); + currentFunctionFrame->setLayout(currentFunctionLayout); + + QFrame *matchedFrame = new QFrame(this); + m_matchedWidget = new WarpMatchedWidget(m_data); + QVBoxLayout *matchedLayout = new QVBoxLayout(); + matchedLayout->setContentsMargins(0, 0, 0, 0); + matchedLayout->setSpacing(0); + matchedLayout->addWidget(m_matchedWidget); + matchedFrame->setLayout(matchedLayout); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + auto tabWidget = new QTabWidget(this); + tabWidget->addTab(currentFunctionFrame, "Current Function"); + tabWidget->addTab(matchedFrame, "Matched Functions"); + + m_analysisEvent = new AnalysisCompletionEvent(m_data, [this]() { + ExecuteOnMainThread([this]() { + Update(); + }); + }); + + layout->addWidget(tabWidget); + this->setLayout(layout); +} + +WarpSidebarWidget::~WarpSidebarWidget() +{ + m_analysisEvent->Cancel(); +} + +void WarpSidebarWidget::focus() +{ +} + +void WarpSidebarWidget::Update() +{ + m_matchedWidget->Update(); + if (!GetMatcherTask()) + setMatcherActionIcon(false); +} + +void WarpSidebarWidget::setMatcherActionIcon(bool running) +{ + static auto matcherStopIcon = GetColoredIcon(":/icons/images/stop.png", getThemeColor(RedStandardHighlightColor)); + static auto matcherStartIcon = GetColoredIcon(":/icons/images/start.png", + getThemeColor(GreenStandardHighlightColor)); + isMatcherRunning = running; + if (running) + { + m_matcherAction->setIcon(matcherStopIcon); + m_matcherAction->setToolTip("Stop the matcher"); + m_matcherAction->setIconText("Stop Matcher"); + } else + { + m_matcherAction->setIcon(matcherStartIcon); + m_matcherAction->setToolTip("Run the matcher on all functions"); + m_matcherAction->setIconText("Run Matcher"); + } +} + +void WarpSidebarWidget::notifyViewChanged(ViewFrame *view) +{ + if (!view) + return; + + if (view == m_currentFrame) + return; + m_currentFrame = view; + // TODO: We need to set some stuff here prolly. +} + +void WarpSidebarWidget::notifyViewLocationChanged(View *view, const ViewLocation &location) +{ + auto function = location.getFunction(); + // TODO: Only update if the function exists? + // NOTE: The function called will exit early if it is the same function. + m_currentFunctionWidget->SetCurrentFunction(function); +} + +WarpSidebarWidgetType::WarpSidebarWidgetType() : SidebarWidgetType(QImage(":/icons/images/warp.png"), "WARP") +{ +} + + +extern "C" { +BN_DECLARE_UI_ABI_VERSION + +BINARYNINJAPLUGIN void CorePluginDependencies() +{ + // We must have WARP to enable this plugin! + AddRequiredPluginDependency("warp_ninja"); +} + +BINARYNINJAPLUGIN bool UIPluginInit() +{ + Sidebar::addSidebarWidgetType(new WarpSidebarWidgetType()); + return true; +} +} diff --git a/plugins/warp/ui/plugin.h b/plugins/warp/ui/plugin.h new file mode 100644 index 00000000..18525218 --- /dev/null +++ b/plugins/warp/ui/plugin.h @@ -0,0 +1,53 @@ +#pragma once + +#include "matched.h" +#include "matches.h" +#include "sidebar.h" +#include "sidebarwidget.h" + +class WarpSidebarWidget : public SidebarWidget +{ + Q_OBJECT + BinaryNinja::Ref<BinaryNinja::Logger> m_logger; + BinaryViewRef m_data; + ViewFrame *m_currentFrame; + QWidget *m_headerWidget; + + BinaryNinja::Ref<BinaryNinja::AnalysisCompletionEvent> m_analysisEvent; + QAction *m_matcherAction; + bool isMatcherRunning = false; + + WarpCurrentFunctionWidget *m_currentFunctionWidget; + WarpMatchedWidget *m_matchedWidget; + +public: + explicit WarpSidebarWidget(BinaryViewRef data); + + ~WarpSidebarWidget() override; + + QWidget *headerWidget() override { return m_headerWidget; } + + void focus() override; + + void Update(); + + void setMatcherActionIcon(bool running); + + void notifyViewChanged(ViewFrame *) override; + + void notifyViewLocationChanged(View *, const ViewLocation &) override; +}; + +class WarpSidebarWidgetType : public SidebarWidgetType +{ +public: + WarpSidebarWidgetType(); + + SidebarWidgetLocation defaultLocation() const override { return SidebarWidgetLocation::RightContent; } + SidebarContextSensitivity contextSensitivity() const override { return PerViewTypeSidebarContext; } + + WarpSidebarWidget *createWidget(ViewFrame *viewFrame, BinaryViewRef data) override + { + return new WarpSidebarWidget(data); + } +}; diff --git a/plugins/warp/ui/shared/constraint.cpp b/plugins/warp/ui/shared/constraint.cpp new file mode 100644 index 00000000..1d369bcd --- /dev/null +++ b/plugins/warp/ui/shared/constraint.cpp @@ -0,0 +1,118 @@ +#include "constraint.h" + +#include <QGridLayout> +#include <QHeaderView> + +WarpConstraintItem::WarpConstraintItem(const Warp::Constraint &constraint) : m_constraint(constraint) +{ + QString guidStr = QString::fromStdString(constraint.guid.ToString()); + if (const auto offset = constraint.offset; offset) + guidStr += QString(" @ %1").arg(*offset, 0, 16); + setText(guidStr); +} + +WarpConstraintItemModel::WarpConstraintItemModel(const QStringList &labels, QObject *parent) +{ + this->setHorizontalHeaderLabels(labels); +} + +void WarpConstraintItemModel::AddItem(WarpConstraintItem *item) +{ + QList<QStandardItem *> row = {}; + row.insert(COL_CONSTRAINT_ITEM, item); + appendRow(row); +} + +WarpConstraintItem *WarpConstraintItemModel::GetItem(const QModelIndex &index) const +{ + if (!index.isValid()) + return nullptr; + return dynamic_cast<WarpConstraintItem *>(item(index.row(), COL_CONSTRAINT_ITEM)); +} + +QVariant WarpConstraintItemModel::data(const QModelIndex &index, int role) const +{ + // Highlight constraints that are found in analysis. + if (role == Qt::BackgroundRole) + { + if (const auto item = GetItem(index); item) + { + auto itemConstraint = item->GetConstraint(); + // TODO: We really should store the guid in a hashmap or something instead of looping over it for every item. + // TODO: A less intense green? + // TODO: Take into account the constraint offset. + for (const auto &constraint: m_matchedConstraints) + if (constraint.guid == itemConstraint.guid) + return QBrush(Qt::green); + } + } + + return QStandardItemModel::data(index, role); +} + +WarpConstraintTableWidget::WarpConstraintTableWidget(QWidget *parent) +{ + QGridLayout *layout = new QGridLayout(this); + layout->setContentsMargins(2, 2, 2, 2); + layout->setVerticalSpacing(4); + + m_table = new QTableView(this); + m_model = new WarpConstraintItemModel({"Constraint"}, this); + m_proxyModel = new GenericTextFilterModel(this); + m_proxyModel->setSourceModel(m_model); + m_table->setModel(m_proxyModel); + + m_filterEdit = new FilterEdit(this); + m_filterView = new FilteredView(this, m_table, this, m_filterEdit); + m_filterView->setFilterPlaceholderText("Search constraints"); + + layout->addWidget(m_filterEdit, 0, 0, 1, 5); + layout->addWidget(m_table, 1, 0, 1, 5); + + // Make the table look nice. + m_table->horizontalHeader()->setStretchLastSection(true); + m_table->verticalHeader()->hide(); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_table->setFocusPolicy(Qt::NoFocus); + m_table->setShowGrid(false); + m_table->setAlternatingRowColors(false); + m_table->setSortingEnabled(true); + // NOTE: We only have a single column right now, so disable the header. + m_table->horizontalHeader()->hide(); + // Decrease row height to make it look nice. + m_table->verticalHeader()->setDefaultSectionSize(30); +} + +void WarpConstraintTableWidget::SetConstraints(QVector<WarpConstraintItem *> constraints) +{ + // Clear matches as they are no longer valid. + m_model->clear(); + m_model->setRowCount(0); + + // Temporarily disable sorting so we can add rows faster + m_table->setModel(m_model); + m_table->setSortingEnabled(false); + m_table->setEnabled(false); + + for (const auto &constraint: constraints) + m_model->AddItem(constraint); + + // We are done, re-enable table. + m_table->setEnabled(true); + m_table->setModel(m_proxyModel); + m_table->setSortingEnabled(true); +} + +void WarpConstraintTableWidget::SetMatchedConstraints( + const std::vector<Warp::Constraint> &analysisConstraints) +{ + m_model->SetMatchedConstraints(analysisConstraints); +} + +void WarpConstraintTableWidget::setFilter(const std::string &filter) +{ + m_proxyModel->setFilterFixedString(QString::fromStdString(filter)); + m_filterView->showFilter(QString::fromStdString(filter)); +} diff --git a/plugins/warp/ui/shared/constraint.h b/plugins/warp/ui/shared/constraint.h new file mode 100644 index 00000000..2f064836 --- /dev/null +++ b/plugins/warp/ui/shared/constraint.h @@ -0,0 +1,78 @@ +#pragma once +#include <qstandarditemmodel.h> +#include <QTableView> +#include <QWidget> + +#include "filter.h" +#include "misc.h" +#include "warp.h" + +class WarpConstraintItem : public QStandardItem +{ + Warp::Constraint m_constraint; + +public: + WarpConstraintItem(const Warp::Constraint &constraint); + + Warp::Constraint GetConstraint() { return m_constraint; } +}; + +class WarpConstraintItemModel : public QStandardItemModel +{ + Q_OBJECT + + // The current analysis constraints used to highlight matching constraints. + std::vector<Warp::Constraint> m_matchedConstraints; + +public: + WarpConstraintItemModel(const QStringList &labels, QObject *parent); + + static constexpr int COL_CONSTRAINT_ITEM = 0; + + void AddItem(WarpConstraintItem *item); + + WarpConstraintItem *GetItem(const QModelIndex &index) const; + + QVariant data(const QModelIndex &index, int role) const override; + + void SetMatchedConstraints(const std::vector<Warp::Constraint> &analysisConstraints) + { + m_matchedConstraints = analysisConstraints; + } +}; + +class WarpConstraintTableWidget : public QWidget, public FilterTarget +{ + Q_OBJECT + + QTableView *m_table; + WarpConstraintItemModel *m_model; + GenericTextFilterModel *m_proxyModel; + FilterEdit *m_filterEdit; + FilteredView *m_filterView; + +public: + explicit WarpConstraintTableWidget(QWidget *parent = nullptr); + + void SetConstraints(QVector<WarpConstraintItem *> constraints); + + void SetMatchedConstraints(const std::vector<Warp::Constraint> &analysisConstraints); + + void setFilter(const std::string &) override; + + void scrollToFirstItem() override + { + } + + void scrollToCurrentItem() override + { + } + + void selectFirstItem() override + { + } + + void activateFirstItem() override + { + } +}; diff --git a/plugins/warp/ui/shared/function.cpp b/plugins/warp/ui/shared/function.cpp new file mode 100644 index 00000000..2202fb4e --- /dev/null +++ b/plugins/warp/ui/shared/function.cpp @@ -0,0 +1,365 @@ +#include "theme.h" + +#include "function.h" + +#include <QClipboard> +#include <QGridLayout> +#include <QHeaderView> + +#include "constraint.h" +#include "misc.h" + +WarpFunctionItem::WarpFunctionItem(Warp::Ref<Warp::Function> function, + BinaryNinja::Ref<BinaryNinja::Function> analysisFunction) +{ + m_function = function; + + // TODO: This needs to be better. Symbol can be nullptr. + BinaryNinja::Ref<BinaryNinja::Symbol> symbol = m_function->GetSymbol(*analysisFunction); + std::string symbolName = symbol->GetShortName(); + setText(QString::fromStdString(symbolName)); + BinaryNinja::InstructionTextToken nameToken = {255, TextToken, symbolName}; + + // Serialize the tokens to make it accessible via QModelIndex. + // We will take these tokens and then user them in our custom item delegate. + TokenData tokenData = {}; + + // TODO: Make this not look like garbage + BinaryNinja::Ref<BinaryNinja::Type> type = m_function->GetType(*analysisFunction); + if (type) + { + BinaryNinja::Ref<BinaryNinja::Platform> platform = analysisFunction->GetPlatform(); + std::vector<BinaryNinja::InstructionTextToken> beforeTokens = type->GetTokensBeforeName(platform); + std::vector<BinaryNinja::InstructionTextToken> afterTokens = type->GetTokensAfterName(platform); + + for (const auto &token: beforeTokens) + tokenData.tokens.emplace_back(token); + tokenData.tokens.emplace_back(255, TextToken, " "); + tokenData.tokens.emplace_back(nameToken); + for (const auto &token: afterTokens) + tokenData.tokens.emplace_back(token); + } else + { + tokenData.tokens.emplace_back(nameToken); + } + + setData(QVariant::fromValue(tokenData), Qt::UserRole); +} + +void WarpFunctionItem::SetContainer(const Warp::Ref<Warp::Container> &container) +{ + m_container = container; + + // Add the container string to data so the filter model picks it up. + auto containerName = m_container->GetName(); + setData(QString::fromStdString(containerName), Qt::UserRole + 2); +} + +void WarpFunctionItem::SetSource(Warp::Source source) +{ + m_source = source; + + // Add the source string to data so the filter model picks it up. + std::string sourceStr = m_source->ToString(); + setData(QString::fromStdString(sourceStr), Qt::UserRole + 1); +} + +WarpFunctionItemModel::WarpFunctionItemModel(const QStringList &labels, QObject *parent) +{ + this->setHorizontalHeaderLabels(labels); +} + +void WarpFunctionItemModel::AppendFunction(WarpFunctionItem *item) +{ + QList<QStandardItem *> row = {}; + row.insert(COL_FUNCTION_ITEM, item); + appendRow(row); +} + +void WarpFunctionItemModel::InsertFunction(uint64_t address, WarpFunctionItem *item) +{ + // Update item if already available, this lets us keep the model + const auto iter = m_insertableFunctionRows.find(address); + if (iter != m_insertableFunctionRows.end()) + { + setItem(iter->second, COL_FUNCTION_ITEM, item); + return; + } + + AppendFunction(item); + m_insertableFunctionRows[address] = rowCount() - 1; +} + +WarpFunctionItem *WarpFunctionItemModel::GetItem(const QModelIndex &index) const +{ + if (!index.isValid()) + return nullptr; + return dynamic_cast<WarpFunctionItem *>(item(index.row(), COL_FUNCTION_ITEM)); +} + +std::optional<uint64_t> WarpFunctionItemModel::GetAddress(const QModelIndex &index) const +{ + if (!index.isValid()) + return std::nullopt; + // TODO: This is a hack, this means we must enumerate all rows to get the address. + for (const auto &[addr, row]: m_insertableFunctionRows) + if (row == index.row()) + return addr; + return std::nullopt; +} + +QVariant WarpFunctionItemModel::data(const QModelIndex &index, int role) const +{ + if (role == Qt::BackgroundRole) + { + auto itemFunction = GetItem(index); + // Check if we have a valid item and it's the matched function + if (m_matchedFunction && itemFunction) + { + // TODO: Why wont == go to the correct call??? + if (BNWARPFunctionsEqual(itemFunction->GetFunction()->m_object, m_matchedFunction->m_object)) + { + // TODO: Better color? + QColor matchedColor = getThemeColor(BlueStandardHighlightColor); + matchedColor.setAlpha(128); + return matchedColor; + } + } + } + + if (role == Qt::DisplayRole) + { + // We really only use this for searching as we have TokenData for our delegate. + WarpFunctionItem *item = GetItem(index); + if (!item) + return QVariant(); + TokenData tokenData = item->data(Qt::UserRole).value<TokenData>(); + // Add the function guid so we can filter by that. + QString text = tokenData.toString() + " " + QString::fromStdString(item->GetFunction()->GetGUID().ToString()); + if (auto source = item->GetSource(); source) + { + // Add the source guid so we can also filter by that. + std::string sourceStr = source->ToString(); + text = text + " " + QString::fromStdString(sourceStr); + } + return text; + } + + return QStandardItemModel::data(index, role); +} + +bool WarpFunctionFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + const QString filterString = filterRegularExpression().pattern(); + if (filterString.isEmpty()) + return true; + + // Filter on the first column only, this contains our actual function. + auto index = sourceModel()->index(sourceRow, 0, sourceParent); + auto data = QRegularExpression::escape(index.data().toString()); + if (data.contains(filterString, Qt::CaseInsensitive)) + return true; + return false; +} + +bool WarpFunctionFilterModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const +{ + // TODO: When we make the stuff _actually_ sortable. + return sourceLeft.row() < sourceRight.row(); +} + +WarpFunctionTableWidget::WarpFunctionTableWidget(QWidget *parent) : QWidget(parent) +{ + QGridLayout *layout = new QGridLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(2); + + m_table = new QTableView(this); + m_model = new WarpFunctionItemModel({"Function"}, this); + m_proxyModel = new WarpFunctionFilterModel(this); + m_proxyModel->setSourceModel(m_model); + m_table->setModel(m_proxyModel); + + m_filterEdit = new FilterEdit(this); + m_filterView = new FilteredView(this, m_table, this, m_filterEdit); + m_filterView->setFilterPlaceholderText("Search functions (By GUID, name or source)"); + + layout->addWidget(m_filterEdit, 0, 0, 1, 5); + layout->addWidget(m_table, 1, 0, 1, 5); + + // Make the table look nice. + m_table->horizontalHeader()->setStretchLastSection(true); + m_table->verticalHeader()->hide(); + m_table->setSelectionBehavior(QAbstractItemView::SelectRows); + m_table->setSelectionMode(QAbstractItemView::SingleSelection); + m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_table->setFocusPolicy(Qt::NoFocus); + m_table->setShowGrid(false); + m_table->setAlternatingRowColors(false); + m_table->setSortingEnabled(true); + // NOTE: We only have a single column right now, so disable header. + m_table->horizontalHeader()->hide(); + // Decrease row height to make it look nice. + m_table->verticalHeader()->setDefaultSectionSize(30); + + TokenDataDelegate *tokenDelegate = new TokenDataDelegate(this); + // NOTE: Column 0 is assumed to be the function with the token data. + m_table->setItemDelegateForColumn(0, tokenDelegate); + + AddressColorDelegate *addressDelegate = new AddressColorDelegate(this); + // NOTE: Column 1 is assumed to be the function address. + m_table->setItemDelegateForColumn(1, addressDelegate); + + // Add a dynamic context menu to the table. + // NOTE: This is a bit stupid, I am sure there is a better way to do this in QT. + m_contextMenu = new QMenu(this); + RegisterContextMenuAction("Copy Name", [](WarpFunctionItem *item, std::optional<uint64_t>) { + QClipboard *clipboard = QGuiApplication::clipboard(); + clipboard->setText(item->text()); + }); + RegisterContextMenuAction("Copy GUID", [](WarpFunctionItem *item, std::optional<uint64_t>) { + QClipboard *clipboard = QGuiApplication::clipboard(); + Warp::Ref<Warp::Function> function = item->GetFunction(); + std::string guidStr = function->GetGUID().ToString(); + clipboard->setText(QString::fromStdString(guidStr)); + }); + + m_table->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_table, &QTableView::customContextMenuRequested, this, [&](QPoint pos) { + const QModelIndex index = m_table->indexAt(pos); + if (!index.isValid()) + return; + const QModelIndex sourceIndex = m_proxyModel->mapToSource(index); + WarpFunctionItem *item = m_model->GetItem(sourceIndex); + if (!item || !item->GetFunction()) + return; + + // Execute the menu and get the selected action + const QAction *selectedAction = m_contextMenu->exec(m_table->viewport()->mapToGlobal(pos)); + if (!selectedAction) + return; + + const auto name = selectedAction->text(); + const auto iter = m_contextMenuActions.find(name); + if (iter != m_contextMenuActions.end()) + iter->second(item, m_model->GetAddress(sourceIndex)); + }); +} + +void WarpFunctionTableWidget::RegisterContextMenuAction(const QString &name, + const std::function<void( + WarpFunctionItem *, std::optional<uint64_t>)> &callback) +{ + m_contextMenu->addAction(name); + m_contextMenuActions[name] = callback; +} + +void WarpFunctionTableWidget::SetFunctions(QVector<WarpFunctionItem *> functions) +{ + // Clear matches as they are no longer valid. + m_model->clear(); + m_model->setRowCount(0); + + // Temporarily disable sorting so we can add rows faster + m_table->setModel(m_model); + m_table->setSortingEnabled(false); + m_table->setEnabled(false); + + for (const auto &function: functions) + m_model->AppendFunction(function); + + // We are done, re-enable table. + m_table->setEnabled(true); + m_table->setModel(m_proxyModel); + m_table->setSortingEnabled(true); + + // Update the filter text with the new count of functions. + m_filterView->setFilterPlaceholderText(QString("Search %1 functions").arg(m_model->rowCount())); +} + +void WarpFunctionTableWidget::InsertFunction(uint64_t address, WarpFunctionItem *function) +{ + m_model->InsertFunction(address, function); +} + +void WarpFunctionTableWidget::setFilter(const std::string &filter) +{ + m_proxyModel->setFilterFixedString(QString::fromStdString(filter)); + m_filterView->showFilter(QString::fromStdString(filter)); +} + +WarpFunctionInfoWidget::WarpFunctionInfoWidget(QWidget *parent) + : QWidget(parent) +{ + // Create a tab widget + QTabWidget *tabWidget = new QTabWidget(this); + tabWidget->setContentsMargins(0, 0, 0, 0); + + // Create tables for the "Constraints", "Comments", and "Variables" tabs + m_commentsTable = new QTableView(this); + // m_variablesTable = new QTableView(this); + + // TODO: On click navigate to where the constraint is located. + m_constraintsTable = new WarpConstraintTableWidget(this); + tabWidget->addTab(m_constraintsTable, "Constraints"); + + // Set up comments tab + m_commentsModel = new QStandardItemModel(this); + m_commentsModel->setHorizontalHeaderLabels({"Offset", "Text"}); + m_commentsModel->setColumnCount(2); + m_commentsTable->setModel(m_commentsModel); + m_commentsTable->horizontalHeader()->setStretchLastSection(true); + m_commentsTable->horizontalHeader()->setSelectionBehavior(QAbstractItemView::SelectRows); + m_commentsTable->horizontalHeader()->setSelectionMode(QAbstractItemView::SingleSelection); + m_commentsTable->horizontalHeader()->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_commentsTable->verticalHeader()->hide(); + m_commentsTable->horizontalHeader()->hide(); + tabWidget->addTab(m_commentsTable, "Comments"); + + // Set up variables tab + // m_variablesTable->setModel(new QStandardItemModel(this)); + // TODO: Add variables to data. + // tabWidget->addTab(m_variablesTable, "Variables"); + + // Add the tab widget to this widget's layout + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(tabWidget); + + setLayout(layout); +} + +void WarpFunctionInfoWidget::UpdateInfo() +{ + m_commentsModel->clear(); + m_commentsModel->setRowCount(0); + m_constraintsTable->SetMatchedConstraints({}); + m_constraintsTable->SetConstraints({}); + + Warp::Ref<Warp::Function> function = GetFunction(); + if (!function) + return; + + // Set the analysis constraints if there is an analysis function. + if (const auto analysisFunction = GetAnalysisFunction()) + { + const auto analysisConstraints = Warp::Function::Get(*analysisFunction)->GetConstraints(); + m_constraintsTable->SetMatchedConstraints(analysisConstraints); + } + + // Add all the constraints for the current function to the model. + QVector<WarpConstraintItem *> constraints; + for (const auto &constraint: function->GetConstraints()) + constraints.push_back(new WarpConstraintItem(constraint)); + m_constraintsTable->SetConstraints(constraints); + + // Add all the comments to the model. + for (const auto &comment: function->GetComments()) + { + m_commentsModel->appendRow({ + new QStandardItem(QString("0x%1").arg(comment.offset, 0, 16)), + new QStandardItem(QString::fromStdString(comment.text)) + }); + } +} diff --git a/plugins/warp/ui/shared/function.h b/plugins/warp/ui/shared/function.h new file mode 100644 index 00000000..90d52092 --- /dev/null +++ b/plugins/warp/ui/shared/function.h @@ -0,0 +1,168 @@ +#pragma once +#include <QStandardItemModel> +#include <QTableView> + +#include "binaryninjaapi.h" +#include "constraint.h" +#include "filter.h" +#include "misc.h" +#include "warp.h" + +class WarpFunctionItem : public QStandardItem +{ + Warp::Ref<Warp::Function> m_function; + + // Optional attached data used to show/manage the function. + Warp::Ref<Warp::Container> m_container; + std::optional<Warp::Source> m_source; + +public: + WarpFunctionItem(Warp::Ref<Warp::Function> function, + BinaryNinja::Ref<BinaryNinja::Function> analysisFunction); + + void SetContainer(const Warp::Ref<Warp::Container> &container); + + void SetSource(Warp::Source source); + + Warp::Ref<Warp::Function> GetFunction() { return m_function; } + Warp::Ref<Warp::Container> GetContainer() { return m_container; } + std::optional<Warp::Source> GetSource() { return m_source; } +}; + +class WarpFunctionItemModel : public QStandardItemModel +{ + Q_OBJECT + + // The current matched function, used to highlight currently. + Warp::Ref<Warp::Function> m_matchedFunction; + + // Mapping of function start address to the row index. + // This is used to identify unique functions for updating instead of resetting the entire model. + std::unordered_map<uint64_t, int> m_insertableFunctionRows; + +public: + WarpFunctionItemModel(const QStringList &labels, QObject *parent); + + static constexpr int COL_FUNCTION_ITEM = 0; + static constexpr int COL_ADDRESS_ITEM = 1; + + void AppendFunction(WarpFunctionItem *item); + + void InsertFunction(uint64_t address, WarpFunctionItem *item); + + WarpFunctionItem *GetItem(const QModelIndex &index) const; + + std::optional<uint64_t> GetAddress(const QModelIndex &index) const; + + QVariant data(const QModelIndex &index, int role) const override; + + void SetMatchedFunction(const Warp::Ref<Warp::Function> &matchedFunction) + { + Warp::Ref<Warp::Function> previousMatchedFunction = m_matchedFunction; + m_matchedFunction = matchedFunction; + + // Make sure to refresh the highlights so we don't keep the highlights from the previous function. + if (previousMatchedFunction) + { + const QModelIndex topLeft = index(0, 0); + const QModelIndex bottomRight = index(rowCount() - 1, 0); + emit dataChanged(topLeft, bottomRight); + } + } +}; + +class WarpFunctionFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + WarpFunctionFilterModel(QObject *parent): QSortFilterProxyModel(parent) + { + } + + ~WarpFunctionFilterModel() override = default; + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override; +}; + +class WarpFunctionTableWidget : public QWidget, public FilterTarget +{ + Q_OBJECT + + QTableView *m_table; + WarpFunctionItemModel *m_model; + WarpFunctionFilterModel *m_proxyModel; + FilterEdit *m_filterEdit; + FilteredView *m_filterView; + QMenu *m_contextMenu; + std::map<QString, std::function<void(WarpFunctionItem *, std::optional<uint64_t>)> > m_contextMenuActions; + +public: + explicit WarpFunctionTableWidget(QWidget *parent = nullptr); + + // TODO: Invert this and provide OnCallback functions that wrap the connect call. + QTableView *GetTableView() const { return m_table; } + WarpFunctionItemModel *GetModel() const { return m_model; } + WarpFunctionFilterModel *GetProxyModel() const { return m_proxyModel; } + + void RegisterContextMenuAction(const QString &name, + const std::function<void(WarpFunctionItem *, std::optional<uint64_t>)> &callback); + + void SetFunctions(QVector<WarpFunctionItem *> functions); + + void InsertFunction(uint64_t address, WarpFunctionItem *function); + + void setFilter(const std::string &) override; + + void scrollToFirstItem() override + { + } + + void scrollToCurrentItem() override + { + } + + void selectFirstItem() override + { + } + + void activateFirstItem() override + { + } +}; + +class WarpFunctionInfoWidget : public QWidget +{ + Q_OBJECT + + Warp::Ref<Warp::Function> m_function; + BinaryNinja::Ref<BinaryNinja::Function> m_analysisFunction; + + // Optionally provide this information to show the source information. + Warp::Ref<Warp::Container> m_container; + std::string source; + + WarpConstraintTableWidget *m_constraintsTable; + + QTableView *m_commentsTable; + QStandardItemModel *m_commentsModel; + + QTableView *m_variablesTable; + +public: + explicit WarpFunctionInfoWidget(QWidget *parent = nullptr); + + Warp::Ref<Warp::Function> GetFunction() { return m_function; } + void SetFunction(Warp::Ref<Warp::Function> function) { m_function = function; }; + + void SetAnalysisFunction(BinaryNinja::Ref<BinaryNinja::Function> analysisFunction) + { + m_analysisFunction = analysisFunction; + }; + BinaryNinja::Ref<BinaryNinja::Function> GetAnalysisFunction() { return m_analysisFunction; } + + // TODO: Make this private? + void UpdateInfo(); +}; diff --git a/plugins/warp/ui/shared/misc.cpp b/plugins/warp/ui/shared/misc.cpp new file mode 100644 index 00000000..71147079 --- /dev/null +++ b/plugins/warp/ui/shared/misc.cpp @@ -0,0 +1,81 @@ +#include "misc.h" + +#include <QGridLayout> +#include <QHeaderView> + +#include "action.h" +#include "fontsettings.h" +#include "render.h" +#include "theme.h" + +void TokenDataDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + painter->save(); + + auto tokenData = index.data(Qt::UserRole).value<TokenData>(); + + // Draw either the selected row or background color. + QVariant background = index.data(Qt::BackgroundRole); + if (background.canConvert<QBrush>()) + painter->fillRect(option.rect, background.value<QBrush>()); + else if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + painter->translate(option.rect.topLeft()); + + + auto renderContext = RenderContext((QWidget *) option.widget); + renderContext.init(*painter); + HighlightTokenState highlightState; + renderContext.drawDisassemblyLine(*painter, 5, 5, {tokenData.tokens.begin(), tokenData.tokens.end()}, + highlightState); + + painter->restore(); +} + +QSize TokenDataDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + auto tokenData = index.data(Qt::UserRole).value<TokenData>(); + auto renderContext = RenderContext((QWidget *) option.widget); + QFontMetrics fontMetrics = QFontMetrics(renderContext.getFont()); + QString line = ""; + for (const auto &token: tokenData.tokens) + line += token.text; + int width = qMax(0, fontMetrics.horizontalAdvance(line)); + return QSize(width, renderContext.getFontHeight()); +} + +void AddressColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + initStyleOption(&opt, index); + + opt.font = getMonospaceFont(qobject_cast<QWidget *>(parent())); + opt.palette.setColor(QPalette::Text, getThemeColor(BNThemeColor::AddressColor)); + opt.displayAlignment = Qt::AlignCenter | Qt::AlignVCenter; + + QStyledItemDelegate::paint(painter, opt, index); +} + +bool GenericTextFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + auto filterString = filterRegularExpression().pattern(); + if (filterString.isEmpty()) + return true; + + for (int i = 0; i < sourceModel()->columnCount(); i++) + { + auto index = sourceModel()->index(sourceRow, i, sourceParent); + auto data = QRegularExpression::escape(index.data().toString()); + if (data.contains(filterString, Qt::CaseInsensitive)) + return true; + } + + return false; +} + +bool GenericTextFilterModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const +{ + auto leftData = sourceLeft.data().toString(); + auto rightData = sourceRight.data().toString(); + return QString::localeAwareCompare(leftData, rightData) < 0; +} diff --git a/plugins/warp/ui/shared/misc.h b/plugins/warp/ui/shared/misc.h new file mode 100644 index 00000000..d2e6a1a8 --- /dev/null +++ b/plugins/warp/ui/shared/misc.h @@ -0,0 +1,84 @@ +#pragma once +#include <qmetatype.h> +#include <QSortFilterProxyModel> +#include <qstandarditemmodel.h> +#include <QStyledItemDelegate> +#include <QTableView> +#include <QVector> + +#include "binaryninjaapi.h" +#include "filter.h" + +// Used to serialize into the item data for rendering with TokenDataDelegate. +struct TokenData +{ + QVector<BinaryNinja::InstructionTextToken> tokens{}; + + TokenData() = default; + + TokenData(const std::vector<BinaryNinja::InstructionTextToken> &tokens) + { + for (const auto &token: tokens) + this->tokens.push_back(token); + } + + TokenData(const BinaryNinja::InstructionTextToken &token) + { + this->tokens.push_back(token); + } + + QString toString() const + { + QStringList tokenStrings; + for (const auto &token: tokens) + { + tokenStrings.append(QString::fromStdString(token.text)); + } + return tokenStrings.join(""); + } +}; + +Q_DECLARE_METATYPE(TokenData) + +class TokenDataDelegate final : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit TokenDataDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) + { + } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +class AddressColorDelegate final : public QStyledItemDelegate +{ + Q_OBJECT + +public: + explicit AddressColorDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) + { + } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + + +class GenericTextFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + GenericTextFilterModel(QObject *parent): QSortFilterProxyModel(parent) + { + } + + ~GenericTextFilterModel() override = default; + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override; +}; |
