diff options
| -rw-r--r-- | README.md | 5 | ||||
| -rw-r--r-- | activity.cpp | 41 | ||||
| -rw-r--r-- | binaryninjaapi.h | 102 | ||||
| -rw-r--r-- | binaryninjacore.h | 66 | ||||
| -rw-r--r-- | binaryview.cpp | 9 | ||||
| -rw-r--r-- | examples/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | examples/workflows/inliner/CMakeLists.txt | 39 | ||||
| -rw-r--r-- | examples/workflows/inliner/inliner.cpp | 159 | ||||
| -rw-r--r-- | examples/workflows/objectivec/CMakeLists.txt | 39 | ||||
| -rw-r--r-- | examples/workflows/objectivec/objectivec.cpp | 156 | ||||
| -rw-r--r-- | examples/workflows/tailcall/CMakeLists.txt | 39 | ||||
| -rw-r--r-- | examples/workflows/tailcall/tailcall.cpp | 129 | ||||
| -rw-r--r-- | function.cpp | 9 | ||||
| -rw-r--r-- | python/__init__.py | 1 | ||||
| -rw-r--r-- | python/binaryview.py | 8 | ||||
| -rw-r--r-- | python/function.py | 8 | ||||
| -rw-r--r-- | python/workflow.py | 429 | ||||
| -rw-r--r-- | rapidjsonwrapper.h | 4 | ||||
| -rw-r--r-- | workflow.cpp | 284 |
19 files changed, 1529 insertions, 3 deletions
@@ -6,13 +6,13 @@ This repository contains documentation and source code for the [Binary Ninja](ht ## Branches -Please note that the [dev](/Vector35/binaryninja-api/tree/dev/) branch tracks changes on the `dev` build of binary ninja and is generally the place where all pull requests should be submitted to. However, the [master](/Vector35/binaryninja-api/tree/master/) branch tracks the `stable` build of Binary Ninja which is the default version run after installation. Online [documentation](https://api.binary.ninja/) tracks the stable branch. +Please note that the [dev](/Vector35/binaryninja-api/tree/dev/) branch tracks changes on the `dev` build of binary ninja and is generally the place where all pull requests should be submitted to. However, the [master](/Vector35/binaryninja-api/tree/master/) branch tracks the `stable` build of Binary Ninja which is the default version run after installation. Online [documentation](https://api.binary.ninja/) tracks the stable branch. ## Contributing Public contributions are welcome to this repository. All the API and documentation in this repository is licensed under an MIT license, however, the API interfaces with a closed-source commercial application, [Binary Ninja](https://binary.ninja). -If you're interested in contributing when you submit your first PR, you'll receive a notice from [CLA Assistant](https://cla-assistant.io/) that allows you to sign our [Contribution License Agreement](https://binary.ninja/cla.pdf) online. +If you're interested in contributing when you submit your first PR, you'll receive a notice from [CLA Assistant](https://cla-assistant.io/) that allows you to sign our [Contribution License Agreement](https://binary.ninja/cla.pdf) online. ## Issues @@ -64,6 +64,7 @@ There are many examples available. The [Python examples folder ](https://github. * [mlil-parser](https://github.com/Vector35/binaryninja-api/tree/dev/examples/mlil_parser) parses Medium-Level IL, demonstrating how to match types and use a visitor class (only usable with licenses that support headless API access) * [print_syscalls](https://github.com/Vector35/binaryninja-api/tree/dev/examples/print_syscalls) is a standalone executable that prints the syscalls used in a given binary (only usable with licenses that support headless API access) * [triage](https://github.com/Vector35/binaryninja-api/tree/dev/examples/triage) is a fully featured plugin that is shipped and enabled by default, demonstrating how to do a wide variety of tasks including extending the UI through QT +* [workflows](https://github.com/Vector35/binaryninja-api/tree/dev/examples/workflows) is a collection of plugins that demonstrate using Workflows to extend the analysis pipeline * [x86 extension](https://github.com/Vector35/binaryninja-api/tree/dev/examples/x86_extension) creates an architecture extension which shows how to modify the behavior of the build-in architectures without creating a complete replacement ## Licensing diff --git a/activity.cpp b/activity.cpp new file mode 100644 index 00000000..31584343 --- /dev/null +++ b/activity.cpp @@ -0,0 +1,41 @@ +#include "binaryninjaapi.h" +#include <string> + +using namespace BinaryNinja; +using namespace std; + + +Activity::Activity(const string& name, const std::function<void(Ref<AnalysisContext> analysisContext)>& action): m_action(action) +{ + //LogError("API-Side Activity Constructed!"); + m_object = BNCreateActivity(name.c_str(), this, Run); +} + + +Activity::Activity(BNActivity* activity) +{ + m_object = BNNewActivityReference(activity); +} + + +Activity::~Activity() +{ + //LogError("API-Side Activity Destructed!"); +} + + +void Activity::Run(void* ctxt, BNAnalysisContext* analysisContext) +{ + Activity* activity = (Activity*)ctxt; + Ref<AnalysisContext> ac = new AnalysisContext(BNNewAnalysisContextReference(analysisContext)); + activity->m_action(ac); +} + + +string Activity::GetName() const +{ + char* name = BNActivityGetName(m_object); + string result = name; + BNFreeString(name); + return result; +} diff --git a/binaryninjaapi.h b/binaryninjaapi.h index a4c723dd..e7358613 100644 --- a/binaryninjaapi.h +++ b/binaryninjaapi.h @@ -35,6 +35,8 @@ #include <atomic> #include <memory> #include <cstdint> +#include <type_traits> +#include <variant> #include "binaryninjacore.h" #include "json/json.h" @@ -540,6 +542,7 @@ namespace BinaryNinja class BackgroundTask; class Platform; class Settings; + class Workflow; class Type; class DataBuffer; class MainThreadAction; @@ -1945,6 +1948,8 @@ __attribute__ ((format (printf, 1, 2))) void Reanalyze(); + Ref<Workflow> GetWorkflow() const; + void ShowPlainTextReport(const std::string& title, const std::string& contents); void ShowMarkdownReport(const std::string& title, const std::string& contents, const std::string& plainText); void ShowHTMLReport(const std::string& title, const std::string& contents, const std::string& plainText); @@ -3087,6 +3092,101 @@ __attribute__ ((format (printf, 1, 2))) EnumerationBuilder& ReplaceMember(size_t idx, const std::string& name, uint64_t value); }; + #if ((__cplusplus >= 201403L) || (_MSVC_LANG >= 201703L)) + template <class... Ts> struct overload : Ts... { using Ts::operator()...; }; + template <class... Ts> overload(Ts...) -> overload<Ts...>; + #endif + + class AnalysisContext: public CoreRefCountObject<BNAnalysisContext, BNNewAnalysisContextReference, BNFreeAnalysisContext> + { + std::unique_ptr<Json::CharReader> m_reader; + Json::StreamWriterBuilder m_builder; + + public: + AnalysisContext(BNAnalysisContext* analysisContext); + virtual ~AnalysisContext(); + + Ref<Function> GetFunction(); + Ref<LowLevelILFunction> GetLowLevelILFunction(); + Ref<MediumLevelILFunction> GetMediumLevelILFunction(); + + void SetLowLevelILFunction(Ref<LowLevelILFunction> lowLevelIL); + + bool Inform(const std::string& request); + + #if ((__cplusplus >= 201403L) || (_MSVC_LANG >= 201703L)) + template <typename... Args> + bool Inform(Args... args) + { + //using T = std::variant<Args...>; // FIXME: remove type duplicates + using T = std::variant<std::string, const char*, uint64_t, Ref<Architecture>>; + std::vector<T> unpackedArgs {args...}; + Json::Value request(Json::arrayValue); + for (auto& arg : unpackedArgs) + std::visit(overload { + [&](Ref<Architecture> arch) { request.append(Json::Value(arch->GetName())); }, + [&](uint64_t val) { request.append(Json::Value(val)); }, + [&](auto& val) { request.append(Json::Value(std::forward<decltype(val)>(val))); } + }, arg); + + return Inform(Json::writeString(m_builder, request)); + } + #endif + }; + + class Activity: public CoreRefCountObject<BNActivity, BNNewActivityReference, BNFreeActivity> + { + protected: + std::function<void(Ref<AnalysisContext> analysisContext)> m_action; + + static void Run(void* ctxt, BNAnalysisContext* analysisContext); + + public: + Activity(const std::string& name, const std::function<void(Ref<AnalysisContext>)>& action); + Activity(BNActivity* activity); + virtual ~Activity(); + + std::string GetName() const; + }; + + class Workflow: public CoreRefCountObject<BNWorkflow, BNNewWorkflowReference, BNFreeWorkflow> + { + public: + Workflow(const std::string& name = ""); + Workflow(BNWorkflow* workflow); + virtual ~Workflow() { } + + static std::vector<Ref<Workflow>> GetList(); + static Ref<Workflow> Instance(const std::string& name = ""); + static bool RegisterWorkflow(Ref<Workflow> workflow, const std::string& description = ""); + + Ref<Workflow> Clone(const std::string& name, const std::string& activity = ""); + bool RegisterActivity(Ref<Activity> activity, const std::string& description = ""); + bool RegisterActivity(Ref<Activity> activity, std::initializer_list<const char*> initializer) { return RegisterActivity(activity, std::vector<std::string>(initializer.begin(), initializer.end())); } + bool RegisterActivity(Ref<Activity> activity, const std::vector<std::string>& subactivities, const std::string& description = ""); + + bool Contains(const std::string& activity); + std::string GetConfiguration(const std::string& activity = ""); + std::string GetName() const; + bool IsRegistered() const; + size_t Size() const; + + Ref<Activity> GetActivity(const std::string& activity); + std::vector<std::string> GetActivityRoots(const std::string& activity = ""); + std::vector<std::string> GetSubactivities(const std::string& activity = "", bool immediate = true); + bool AssignSubactivities(const std::string& activity, const std::vector<std::string>& subactivities = {}); + bool Clear(); + bool Insert(const std::string& activity, const std::string& newActivity); + bool Insert(const std::string& activity, const std::vector<std::string>& activities); + bool Remove(const std::string& activity); + bool Replace(const std::string& activity, const std::string& newActivity); + + Ref<FlowGraph> GetGraph(const std::string& activity = "", bool sequential = false); + void ShowReport(const std::string& name); + + //bool Run(const std::string& activity, Ref<AnalysisContext> analysisContext); + }; + class DisassemblySettings: public CoreRefCountObject<BNDisassemblySettings, BNNewDisassemblySettingsReference, BNFreeDisassemblySettings> { @@ -3501,6 +3601,8 @@ __attribute__ ((format (printf, 1, 2))) void Reanalyze(); + Ref<Workflow> GetWorkflow() const; + void RequestAdvancedAnalysisData(); void ReleaseAdvancedAnalysisData(); void ReleaseAdvancedAnalysisData(size_t count); diff --git a/binaryninjacore.h b/binaryninjacore.h index 42eb2b59..8cf76db6 100644 --- a/binaryninjacore.h +++ b/binaryninjacore.h @@ -189,6 +189,9 @@ extern "C" struct BNEnumerationBuilder; struct BNCallingConvention; struct BNPlatform; + struct BNActivity; + struct BNAnalysisContext; + struct BNWorkflow; struct BNAnalysisCompletionEvent; struct BNDisassemblySettings; struct BNSaveSettings; @@ -2130,6 +2133,16 @@ extern "C" BNPossibleValueSet value; }; + enum BNWorkflowState + { + WorkflowInitial, + WorkflowIdle, + WorkflowRun, + WorkflowHalt, + WorkflowHold, + WorkflowInvalid + }; + enum BNAnalysisState { InitialState, @@ -3818,6 +3831,9 @@ __attribute__ ((format (printf, 1, 2))) BINARYNINJACOREAPI void BNReanalyzeAllFunctions(BNBinaryView* view); BINARYNINJACOREAPI void BNReanalyzeFunction(BNFunction* func); + BINARYNINJACOREAPI BNWorkflow* BNGetWorkflowForBinaryView(BNBinaryView* view); + BINARYNINJACOREAPI BNWorkflow* BNGetWorkflowForFunction(BNFunction* func); + BINARYNINJACOREAPI BNHighlightColor BNGetInstructionHighlight(BNFunction* func, BNArchitecture* arch, uint64_t addr); BINARYNINJACOREAPI void BNSetAutoInstructionHighlight(BNFunction* func, BNArchitecture* arch, uint64_t addr, BNHighlightColor color); @@ -3961,6 +3977,56 @@ __attribute__ ((format (printf, 1, 2))) BINARYNINJACOREAPI void BNFreeVariableList(BNVariable* vars); BINARYNINJACOREAPI void BNFreeVariableReferenceSourceList(BNVariableReferenceSource* vars, size_t count); + // Analysis Context + BINARYNINJACOREAPI BNAnalysisContext* BNCreateAnalysisContext(void); + BINARYNINJACOREAPI BNAnalysisContext* BNNewAnalysisContextReference(BNAnalysisContext* analysisContext); + BINARYNINJACOREAPI void BNFreeAnalysisContext(BNAnalysisContext* analysisContext); + BINARYNINJACOREAPI BNFunction* BNAnalysisContextGetFunction(BNAnalysisContext* analysisContext); + BINARYNINJACOREAPI BNLowLevelILFunction* BNAnalysisContextGetLowLevelILFunction(BNAnalysisContext* analysisContext); + BINARYNINJACOREAPI BNMediumLevelILFunction* BNAnalysisContextGetMediumLevelILFunction(BNAnalysisContext* analysisContext); + BINARYNINJACOREAPI void BNSetLowLevelILFunction(BNAnalysisContext* analysisContext, BNLowLevelILFunction* lowLevelIL); + BINARYNINJACOREAPI bool BNAnalysisContextInform(BNAnalysisContext* analysisContext, const char* request); + + // Activity + BINARYNINJACOREAPI BNActivity* BNCreateActivity(const char* name, void* ctxt, void (*action)(void*, BNAnalysisContext*)); + BINARYNINJACOREAPI BNActivity* BNNewActivityReference(BNActivity* activity); + BINARYNINJACOREAPI void BNFreeActivity(BNActivity* activity); + + BINARYNINJACOREAPI char* BNActivityGetName(BNActivity* activity); + + // Workflow + BINARYNINJACOREAPI BNWorkflow* BNCreateWorkflow(const char* name); + BINARYNINJACOREAPI BNWorkflow* BNNewWorkflowReference(BNWorkflow* workflow); + BINARYNINJACOREAPI void BNFreeWorkflow(BNWorkflow* workflow); + + BINARYNINJACOREAPI BNWorkflow** BNGetWorkflowList(size_t* count); + BINARYNINJACOREAPI void BNFreeWorkflowList(BNWorkflow** workflows, size_t count); + BINARYNINJACOREAPI BNWorkflow* BNWorkflowInstance(const char* name); + BINARYNINJACOREAPI bool BNRegisterWorkflow(BNWorkflow* workflow, const char* description); + + BINARYNINJACOREAPI BNWorkflow* BNWorkflowClone(BNWorkflow* workflow, const char* name, const char* activity); + BINARYNINJACOREAPI bool BNWorkflowRegisterActivity(BNWorkflow* workflow, BNActivity* activity, const char** subactivities, size_t size, const char* description); + + BINARYNINJACOREAPI bool BNWorkflowContains(BNWorkflow* workflow, const char* activity); + BINARYNINJACOREAPI char* BNWorkflowGetConfiguration(BNWorkflow* workflow, const char* activity); + BINARYNINJACOREAPI char* BNGetWorkflowName(BNWorkflow* workflow); + BINARYNINJACOREAPI bool BNWorkflowIsRegistered(BNWorkflow* workflow); + BINARYNINJACOREAPI size_t BNWorkflowSize(BNWorkflow* workflow); + + BINARYNINJACOREAPI BNActivity* BNWorkflowGetActivity(BNWorkflow* workflow, const char* activity); + BINARYNINJACOREAPI const char** BNWorkflowGetActivityRoots(BNWorkflow* workflow, const char* activity, size_t* inoutSize); + BINARYNINJACOREAPI const char** BNWorkflowGetSubactivities(BNWorkflow* workflow, const char* activity, bool immediate, size_t* inoutSize); + BINARYNINJACOREAPI bool BNWorkflowAssignSubactivities(BNWorkflow* workflow, const char* activity, const char** activities, size_t size); + BINARYNINJACOREAPI bool BNWorkflowClear(BNWorkflow* workflow); + BINARYNINJACOREAPI bool BNWorkflowInsert(BNWorkflow* workflow, const char* activity, const char** activities, size_t size); + BINARYNINJACOREAPI bool BNWorkflowRemove(BNWorkflow* workflow, const char* activity); + BINARYNINJACOREAPI bool BNWorkflowReplace(BNWorkflow* workflow, const char* activity, const char* newActivity); + + BINARYNINJACOREAPI BNFlowGraph* BNWorkflowGetGraph(BNWorkflow* workflow, const char* activity, bool sequential); + BINARYNINJACOREAPI void BNWorkflowShowReport(BNWorkflow* workflow, const char* name); + + //BINARYNINJACOREAPI bool BNWorkflowRun(const char* activity, BNAnalysisContext* analysisContext); + // Disassembly settings BINARYNINJACOREAPI BNDisassemblySettings* BNCreateDisassemblySettings(void); BINARYNINJACOREAPI BNDisassemblySettings* BNNewDisassemblySettingsReference(BNDisassemblySettings* settings); diff --git a/binaryview.cpp b/binaryview.cpp index 7ab01790..db9b217b 100644 --- a/binaryview.cpp +++ b/binaryview.cpp @@ -3327,6 +3327,15 @@ void BinaryView::Reanalyze() } +Ref<Workflow> BinaryView::GetWorkflow() const +{ + BNWorkflow* workflow = BNGetWorkflowForBinaryView(m_object); + if (!workflow) + return nullptr; + return new Workflow(workflow); +} + + void BinaryView::ShowPlainTextReport(const string& title, const string& contents) { BNShowPlainTextReport(m_object, title.c_str(), contents.c_str()); diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d770b60d..4aa55dc8 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -7,4 +7,9 @@ add_subdirectory(print_syscalls) if(NOT HEADLESS) add_subdirectory(uinotification) endif() +if(NOT DEMO AND NOT PERSONAL) + add_subdirectory(workflows/inliner) + add_subdirectory(workflows/objectivec) + add_subdirectory(workflows/tailcall) +endif() add_subdirectory(x86_extension) diff --git a/examples/workflows/inliner/CMakeLists.txt b/examples/workflows/inliner/CMakeLists.txt new file mode 100644 index 00000000..59611afb --- /dev/null +++ b/examples/workflows/inliner/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(workflow_inliner) + +if((NOT BN_API_PATH) AND (NOT BN_INTERNAL_BUILD)) + set(BN_API_PATH $ENV{BN_API_PATH}) + if(NOT BN_API_PATH) + message(FATAL_ERROR "Provide path to Binary Ninja API source in BN_API_PATH") + endif() +endif() +if(NOT BN_INTERNAL_BUILD) + add_subdirectory(${BN_API_PATH} ${PROJECT_BINARY_DIR}/api) +endif() + +file(GLOB SOURCES + *.cpp + *.c + *.h) + +add_library(workflow_inliner SHARED ${SOURCES}) + +target_link_libraries(workflow_inliner binaryninjaapi) + +set_target_properties(workflow_inliner PROPERTIES + CXX_STANDARD 17 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + C_STANDARD 99 + C_STANDARD_REQUIRED ON + C_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON) + +if(BN_INTERNAL_BUILD) + plugin_rpath(workflow_inliner) + set_target_properties(workflow_inliner PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR} + RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}) +endif() diff --git a/examples/workflows/inliner/inliner.cpp b/examples/workflows/inliner/inliner.cpp new file mode 100644 index 00000000..01eacbd2 --- /dev/null +++ b/examples/workflows/inliner/inliner.cpp @@ -0,0 +1,159 @@ +#define _CRT_SECURE_NO_WARNINGS +#define NOMINMAX + +#include <cinttypes> +#include <cstdint> +#include <cstdio> +#include <string> +#include <tuple> +#include <unordered_map> + +#include "binaryninjaapi.h" +#include "lowlevelilinstruction.h" +#include "mediumlevelilinstruction.h" + + +using namespace BinaryNinja; +using namespace std; + +#if defined(_MSC_VER) +#define snprintf _snprintf +#endif + + +extern "C" +{ + BN_DECLARE_CORE_ABI_VERSION + + unordered_map<uint64_t, set<uint64_t>> g_callSiteInlines; + + void FunctionInliner(Ref<AnalysisContext> analysisContext) + { + Ref<Function> function = analysisContext->GetFunction(); + Ref<BinaryView> data = function->GetView(); + + set<uint64_t> callSiteInlines; + if (const auto& itr = g_callSiteInlines.find(function->GetStart()); itr != g_callSiteInlines.end()) + callSiteInlines = itr->second; + + bool updated = false; + uint8_t opcode[BN_MAX_INSTRUCTION_LENGTH]; + InstructionInfo iInfo; + Ref<LowLevelILFunction> llilFunc = analysisContext->GetLowLevelILFunction(); + for (const auto inlineAddr : callSiteInlines) + { + // if (m_owner->IsAborted()) + // return; + + for (auto& i : llilFunc->GetBasicBlocks()) + { + Ref<Architecture> arch = i->GetArchitecture(); + for (size_t instrIndex = i->GetStart(); instrIndex < i->GetEnd(); instrIndex++) + { + LowLevelILInstruction instr = llilFunc->GetInstruction(instrIndex); + + if (instr.address != inlineAddr) + continue; + + if (instr.operation != LLIL_CALL) + { + LogWarn("Failed to inline function at: 0x%" PRIx64 ". Mapping to LLIL_CALL Failed!", instr.address); + continue; + } + + uint64_t platformAddr; + LowLevelILInstruction destExpr = instr.GetDestExpr<LLIL_CALL>(); + RegisterValue target = destExpr.GetValue(); + if (target.IsConstant()) + platformAddr = target.value; + else + { + LogWarn("Failed to inline function at: 0x%" PRIx64 ". Destination not Constant!", instr.address); + continue; + } + + size_t opLen = data->Read(opcode, instr.address, arch->GetMaxInstructionLength()); + if (!opLen || !arch->GetInstructionInfo(opcode, instr.address, opLen, iInfo)) + continue; + Ref<Platform> platform = iInfo.archTransitionByTargetAddr ? function->GetPlatform()->GetAssociatedPlatformByAddress(platformAddr) : function->GetPlatform(); + if (platform) + { + Ref<Function> targetFunc = data->GetAnalysisFunction(platform, platformAddr); + auto targetLlil = targetFunc->GetLowLevelIL(); + LowLevelILLabel inlineStartLabel; + llilFunc->MarkLabel(inlineStartLabel); + instr.Replace(llilFunc->Goto(inlineStartLabel)); + + llilFunc->PrepareToCopyFunction(targetLlil); + for (auto& ti : targetLlil->GetBasicBlocks()) + { + llilFunc->PrepareToCopyBlock(ti); + for (size_t tinstrIndex = ti->GetStart(); tinstrIndex < ti->GetEnd(); tinstrIndex++) + { + LowLevelILInstruction tinstr = targetLlil->GetInstruction(tinstrIndex); + if (tinstr.operation == LLIL_RET) + { + LowLevelILLabel label; + label.operand = instrIndex + 1; + llilFunc->AddInstruction(llilFunc->Pop(instr.size)); + llilFunc->AddInstruction(llilFunc->Goto(label)); + } + else + llilFunc->AddInstruction(tinstr.CopyTo(llilFunc)); + } + } + llilFunc->Finalize(); + } + + updated = true; + break; + } + } + } + + if (!updated) + return; + + // Updates found, regenerate SSA form + llilFunc->GenerateSSAForm(); + } + + + BINARYNINJAPLUGIN bool CorePluginInit() + { + auto inlinerIsValid = [](BinaryView* view, Function* func) { + if (auto workflow = func->GetWorkflow(); workflow) + return workflow->Contains("extension.functionInliner"); + return false; + }; + + // PluginCommand::RegisterForFunction( + // "Optimizer\\Inline All Calls to Current Function", + // "Inline all calls to the current function.", + // [](BinaryView* view, Function* func) { + // LogError("TODO Inline Current Function: %" PRIx64, func->GetStart()); + // }, inlinerIsValid); + + PluginCommand::RegisterForFunction( + "Optimizer\\Inline Function at Current Call Site", + "Inline function call at current call site.", + [](BinaryView* view, Function* func) { + // TODO func->Inform("inlinedCallSites") + // TODO resolve multiple embedded inlines + g_callSiteInlines[func->GetStart()].insert(view->GetCurrentOffset()); + func->Reanalyze(); + }, inlinerIsValid); + + Ref<Workflow> inlinerWorkflow = Workflow::Instance()->Clone("InlinerWorkflow"); + inlinerWorkflow->RegisterActivity(new Activity("extension.functionInliner", &FunctionInliner)); + inlinerWorkflow->Insert("core.function.translateTailCalls", "extension.functionInliner"); + Workflow::RegisterWorkflow(inlinerWorkflow, + R"#({ + "title" : "Function Inliner (Example)", + "description" : "This analysis stands in as an example to demonstrate Binary Ninja's extensible analysis APIs. ***Note** this feature is under active development and subject to change without notice.", + "capabilities" : [] + })#"); + + return true; + } +} diff --git a/examples/workflows/objectivec/CMakeLists.txt b/examples/workflows/objectivec/CMakeLists.txt new file mode 100644 index 00000000..e807235c --- /dev/null +++ b/examples/workflows/objectivec/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(workflow_objectivec) + +if((NOT BN_API_PATH) AND (NOT BN_INTERNAL_BUILD)) + set(BN_API_PATH $ENV{BN_API_PATH}) + if(NOT BN_API_PATH) + message(FATAL_ERROR "Provide path to Binary Ninja API source in BN_API_PATH") + endif() +endif() +if(NOT BN_INTERNAL_BUILD) + add_subdirectory(${BN_API_PATH} ${PROJECT_BINARY_DIR}/api) +endif() + +file(GLOB SOURCES + *.cpp + *.c + *.h) + +add_library(workflow_objectivec SHARED ${SOURCES}) + +target_link_libraries(workflow_objectivec binaryninjaapi) + +set_target_properties(workflow_objectivec PROPERTIES + CXX_STANDARD 17 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + C_STANDARD 99 + C_STANDARD_REQUIRED ON + C_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON) + +if(BN_INTERNAL_BUILD) + plugin_rpath(workflow_objectivec) + set_target_properties(workflow_objectivec PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR} + RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}) +endif() diff --git a/examples/workflows/objectivec/objectivec.cpp b/examples/workflows/objectivec/objectivec.cpp new file mode 100644 index 00000000..e967c5e3 --- /dev/null +++ b/examples/workflows/objectivec/objectivec.cpp @@ -0,0 +1,156 @@ +#define _CRT_SECURE_NO_WARNINGS +#define NOMINMAX + +#include <cinttypes> +#include <cstdint> +#include <cstdio> +#include <string> +#include <tuple> +#include <unordered_map> + +#include "binaryninjaapi.h" +#include "lowlevelilinstruction.h" +#include "mediumlevelilinstruction.h" + + +using namespace BinaryNinja; +using namespace std; + +#if defined(_MSC_VER) +#define snprintf _snprintf +#endif + + +extern "C" +{ + BN_DECLARE_CORE_ABI_VERSION + + // TODO + // * __objc_const missing xrefs to implementations + void ObjectiveCAnalysis(Ref<AnalysisContext> analysisContext) + { + Ref<Function> function = analysisContext->GetFunction(); + Ref<BinaryView> data = function->GetView(); + + static unordered_map<uint64_t, tuple<string, uint64_t>> classData; + static bool sInit = false; + if (!sInit) // TODO move to module-based workflow activity with run-once semantics + { + auto constSection = data->GetSectionByName("__objc_const"); + if (!constSection) + return; + + BinaryReader reader(data); + reader.SetEndianness(data->GetDefaultEndianness()); // TODO fix GetDefaultEndianness for non-elf formats + reader.Seek(constSection->GetStart()); + + reader.Read32(); + reader.Read32(); + reader.Read32(); + reader.Read32(); + reader.Read64(); + uint64_t namePtr = reader.Read64(); + reader.Read64(); + reader.Read64(); + reader.Read64(); + reader.Read64(); + reader.Read64(); + uint32_t methodListFlags = reader.Read32(); + uint32_t methodListCount = reader.Read32(); + for (uint32_t i = 0; i < methodListCount; i++) // section end/symbol validation + { + uint64_t selector = reader.Read64(); + uint64_t typePtr = reader.Read64(); + uint64_t impPtr = reader.Read64(); + //string methodName = reader.ReadCString(selector); + string typeEncoding = "";//reader.ReadCString(typePtr); + classData.insert_or_assign(selector, std::forward_as_tuple(typeEncoding, impPtr)); + } + + reader.Seek(namePtr); + string className = reader.ReadCString(); + + auto cfStringSection = data->GetSectionByName("__cfstring"); + if (!cfStringSection) + return; + + sInit = true; + } + + bool updated = false; + uint8_t opcode[BN_MAX_INSTRUCTION_LENGTH]; + InstructionInfo iInfo; + + // // if (m_owner->IsAborted()) + // // return; + + // TODO fix this.... + auto sym = data->GetSymbolByRawName("_objc_msgSend"); + if (!sym) + return; + uint64_t msgSendAddr = sym->GetAddress(); + + Ref<LowLevelILFunction> llilFunc = analysisContext->GetLowLevelILFunction(); + auto ssa = llilFunc->GetSSAForm(); + if (!ssa) + return; + for (auto& i : ssa->GetBasicBlocks()) + { + Ref<Architecture> arch = i->GetArchitecture(); + for (size_t instrIndex = i->GetStart(); instrIndex < i->GetEnd(); instrIndex++) + { + LowLevelILInstruction instr = ssa->GetInstruction(instrIndex); + + // TODO process subexpressions + if (instr.operation != LLIL_CALL_SSA) + continue; + + LowLevelILInstruction destExpr = instr.GetDestExpr<LLIL_CALL_SSA>(); + if (msgSendAddr == (uint64_t)destExpr.GetValue().value) + { + auto params = instr.GetParameterExprs<LLIL_CALL_SSA>(); + if ((params.size() >= 2) && (params[0].operation == LLIL_REG_SSA) && (params[1].operation == LLIL_REG_SSA)) + { + auto selfSSAReg = params[0].GetSourceSSARegister<LLIL_REG_SSA>(); + auto selSSAReg = params[1].GetSourceSSARegister<LLIL_REG_SSA>(); + if (auto itr = classData.find(ssa->GetSSARegisterValue(selSSAReg).value); itr != classData.end()) + { + size_t llilIndex = ssa->GetNonSSAInstructionIndex(instrIndex); + LowLevelILInstruction llilInstr = llilFunc->GetInstruction(llilIndex); + auto destExpr = llilInstr.GetDestExpr<LLIL_CALL>(); + const auto& [typeEncoding, impPtr] = itr->second; + destExpr.Replace(llilFunc->ConstPointer(destExpr.size, impPtr, destExpr)); + llilInstr.Replace(llilFunc->Call(destExpr.exprIndex, llilInstr)); + analysisContext->Inform("directRefs", "insert", impPtr, i->GetArchitecture(), instr.address); + updated = true; + } + } + // else + // LogError("Unhandled _objc_msgSend: 0x%" PRIx64, instr.address); + } + } + } + + if (!updated) + return; + + // Updates found, regenerate SSA form + llilFunc->GenerateSSAForm(); + } + + + BINARYNINJAPLUGIN bool CorePluginInit() + { + Ref<Workflow> objectiveCWorkflow = Workflow::Instance()->Clone("ObjectiveCWorkflow"); + objectiveCWorkflow->RegisterActivity(new Activity("extension.objectiveC", &ObjectiveCAnalysis)); + objectiveCWorkflow->Insert("core.function.translateTailCalls", "extension.objectiveC"); + Workflow::RegisterWorkflow(objectiveCWorkflow, + R"#({ + "title" : "Objective C Meta-Analysis (Example)", + "description" : "This analysis stands in as an example to demonstrate Binary Ninja's extensible analysis APIs. ***Note** this feature is under active development and subject to change without notice.", + "capabilities" : [] + })#"); + + return true; + } +} diff --git a/examples/workflows/tailcall/CMakeLists.txt b/examples/workflows/tailcall/CMakeLists.txt new file mode 100644 index 00000000..d1d547a4 --- /dev/null +++ b/examples/workflows/tailcall/CMakeLists.txt @@ -0,0 +1,39 @@ +cmake_minimum_required(VERSION 3.9 FATAL_ERROR) + +project(workflow_tailcall) + +if((NOT BN_API_PATH) AND (NOT BN_INTERNAL_BUILD)) + set(BN_API_PATH $ENV{BN_API_PATH}) + if(NOT BN_API_PATH) + message(FATAL_ERROR "Provide path to Binary Ninja API source in BN_API_PATH") + endif() +endif() +if(NOT BN_INTERNAL_BUILD) + add_subdirectory(${BN_API_PATH} ${PROJECT_BINARY_DIR}/api) +endif() + +file(GLOB SOURCES + *.cpp + *.c + *.h) + +add_library(workflow_tailcall SHARED ${SOURCES}) + +target_link_libraries(workflow_tailcall binaryninjaapi) + +set_target_properties(workflow_tailcall PROPERTIES + CXX_STANDARD 17 + CXX_VISIBILITY_PRESET hidden + CXX_STANDARD_REQUIRED ON + C_STANDARD 99 + C_STANDARD_REQUIRED ON + C_VISIBILITY_PRESET hidden + VISIBILITY_INLINES_HIDDEN ON + POSITION_INDEPENDENT_CODE ON) + +if(BN_INTERNAL_BUILD) + plugin_rpath(workflow_tailcall) + set_target_properties(workflow_tailcall PROPERTIES + LIBRARY_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR} + RUNTIME_OUTPUT_DIRECTORY ${BN_CORE_PLUGIN_DIR}) +endif() diff --git a/examples/workflows/tailcall/tailcall.cpp b/examples/workflows/tailcall/tailcall.cpp new file mode 100644 index 00000000..9305a51d --- /dev/null +++ b/examples/workflows/tailcall/tailcall.cpp @@ -0,0 +1,129 @@ +#define _CRT_SECURE_NO_WARNINGS +#define NOMINMAX + +#include <cinttypes> +#include <cstdint> +#include <cstdio> +#include <string> +#include <tuple> +#include <unordered_map> + +#include "binaryninjaapi.h" +#include "lowlevelilinstruction.h" +#include "mediumlevelilinstruction.h" + + +using namespace BinaryNinja; +using namespace std; + +#if defined(_MSC_VER) +#define snprintf _snprintf +#endif + + +extern "C" +{ + BN_DECLARE_CORE_ABI_VERSION + + void TailCallTranslation(Ref<AnalysisContext> analysisContext) + { + Ref<Function> function = analysisContext->GetFunction(); + Ref<BinaryView> data = function->GetView(); + + bool updated = false; + uint8_t opcode[BN_MAX_INSTRUCTION_LENGTH]; + InstructionInfo iInfo; + + // Look for jumps to other functions + Ref<LowLevelILFunction> llilFunc = analysisContext->GetLowLevelILFunction(); + for (auto& i : llilFunc->GetBasicBlocks()) + { + // if (m_owner->IsAborted()) + // return; + + Ref<Architecture> arch = i->GetArchitecture(); + size_t instrIndex = i->GetEnd() - 1; + LowLevelILInstruction instr = llilFunc->GetInstruction(instrIndex); + if (instr.operation != LLIL_JUMP) + continue; + + uint64_t platformAddr; + LowLevelILInstruction destExpr = instr.GetDestExpr<LLIL_JUMP>(); + RegisterValue target = destExpr.GetValue(); + if (target.IsConstant()) + platformAddr = target.value; + else if (target.state == ImportedAddressValue) // Call to imported function, look up type from import symbol + platformAddr = target.value; + else if (target.state == ExternalPointerValue && target.offset == 0) + platformAddr = target.value; + else + continue; + + size_t opLen = data->Read(opcode, instr.address, arch->GetMaxInstructionLength()); + if (!opLen || !arch->GetInstructionInfo(opcode, instr.address, opLen, iInfo)) + continue; + Ref<Platform> platform = iInfo.archTransitionByTargetAddr ? function->GetPlatform()->GetAssociatedPlatformByAddress(platformAddr) : function->GetPlatform(); + if (platform) + { + bool canReturn = true; + Ref<Function> targetFunc = nullptr; + if (target.state == ImportedAddressValue) + { + DataVariable var; + if (data->GetDataVariableAtAddress(target.value, var)) + { + if (var.type && (var.type->GetClass() == PointerTypeClass) && (var.type->GetChildType()->GetClass() == FunctionTypeClass)) + canReturn = var.type->GetChildType()->CanReturn().GetValue(); + } + } + else if (target.state == ExternalPointerValue && target.offset == 0) + { + targetFunc = data->GetAnalysisFunction(platform, platformAddr); + if (targetFunc) + canReturn = targetFunc->CanReturn(); + } + else + { + targetFunc = data->GetAnalysisFunction(platform, platformAddr); + if (targetFunc) + canReturn = targetFunc->CanReturn(); + else + continue; + } + + updated = true; + instr.Replace(llilFunc->TailCall(destExpr.exprIndex, instr)); + analysisContext->Inform("directRefs", "insert", platformAddr, i->GetArchitecture(), instr.address); + + if (!canReturn) + { + analysisContext->Inform("directNoReturnCalls", "insert", i->GetArchitecture(), instr.address); + i->GetSourceBlock()->SetCanExit(false); + i->SetCanExit(false); + } + } + } + + if (!updated) + return; + + // Updates found, regenerate SSA form + llilFunc->GenerateSSAForm(); + } + + + BINARYNINJAPLUGIN bool CorePluginInit() + { + Ref<Workflow> customTailCallWorkflow = Workflow::Instance()->Clone("CustomTailCallWorkflow"); + customTailCallWorkflow->RegisterActivity(new Activity("extension.translateTailCalls", &TailCallTranslation)); + customTailCallWorkflow->Replace("core.function.translateTailCalls", "extension.translateTailCalls"); + Workflow::RegisterWorkflow(customTailCallWorkflow, + R"#({ + "title" : "Tail Call Translation (Example)", + "description" : "This analysis stands in as an example to demonstrate Binary Ninja's extensible analysis APIs. ***Note** this feature is under active development and subject to change without notice.", + "capabilities" : [] + })#"); + + return true; + } +} diff --git a/function.cpp b/function.cpp index f8b84a8a..9ede9b67 100644 --- a/function.cpp +++ b/function.cpp @@ -1953,6 +1953,15 @@ void Function::Reanalyze() } +Ref<Workflow> Function::GetWorkflow() const +{ + BNWorkflow* workflow = BNGetWorkflowForFunction(m_object); + if (!workflow) + return nullptr; + return new Workflow(workflow); +} + + void Function::RequestAdvancedAnalysisData() { BNRequestAdvancedFunctionAnalysisData(m_object); diff --git a/python/__init__.py b/python/__init__.py index 4f8a373f..54936753 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -64,6 +64,7 @@ from binaryninja.settings import * from binaryninja.metadata import * from binaryninja.flowgraph import * from binaryninja.datarender import * +from binaryninja.workflow import * def shutdown(): diff --git a/python/binaryview.py b/python/binaryview.py index 14b0bdcb..092cceec 100644 --- a/python/binaryview.py +++ b/python/binaryview.py @@ -51,6 +51,7 @@ from binaryninja import highlight from binaryninja import function from binaryninja.function import AddressRange from binaryninja import settings +from binaryninja import workflow from binaryninja import pyNativeStr # 2-3 compatibility @@ -6204,6 +6205,13 @@ class BinaryView(object): """ core.BNReanalyzeAllFunctions(self.handle) + @property + def workflow(self): + handle = core.BNGetWorkflowForBinaryView(self.handle) + if handle is None: + return None + return binaryninja.Workflow(handle = handle) + def rebase(self, address, force = False, progress_func = None): """ ``rebase`` rebase the existing :py:class:`BinaryView` into a new :py:class:`BinaryView` at the specified virtual address diff --git a/python/function.py b/python/function.py index e3d0521c..9bc83b09 100644 --- a/python/function.py +++ b/python/function.py @@ -35,6 +35,7 @@ from binaryninja import highlight from binaryninja import log from binaryninja import types from binaryninja import decorators +from binaryninja import workflow from binaryninja.enums import (AnalysisSkipReason, FunctionGraphType, BranchType, SymbolType, InstructionTextTokenType, HighlightStandardColor, HighlightColorStyle, RegisterValueType, ImplicitRegisterExtend, DisassemblyOption, IntegerDisplayType, InstructionTextTokenContext, VariableSourceType, @@ -3226,6 +3227,13 @@ class Function(object): """ core.BNReanalyzeFunction(self.handle) + @property + def workflow(self): + handle = core.BNGetWorkflowForFunction(self.handle) + if handle is None: + return None + return binaryninja.Workflow(handle = handle) + def request_advanced_analysis_data(self): core.BNRequestAdvancedFunctionAnalysisData(self.handle) self._advanced_analysis_requests += 1 diff --git a/python/workflow.py b/python/workflow.py new file mode 100644 index 00000000..d74b8184 --- /dev/null +++ b/python/workflow.py @@ -0,0 +1,429 @@ +# Copyright (c) 2015-2021 Vector 35 Inc +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +# IN THE SOFTWARE. + +import ctypes +import json +import random + +# Binary Ninja components +import binaryninja +from binaryninja import _binaryninjacore as core +from binaryninja import BranchType +from binaryninja.flowgraph import EdgeStyle, EdgePenStyle, FlowGraph, FlowGraphNode, ThemeColor +from typing import List, Union + +# 2-3 compatibility +from binaryninja import range +from binaryninja import pyNativeStr + +_action_callbacks = {} + +class Activity(object): + """ + :class:`Activity` + """ + + def __init__(self, name = "", handle = None, action = None): + if handle is None: + #cls._notify(ac, callback) + action_callback = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.POINTER(core.BNAnalysisContext))(lambda ctxt, ac: self._action(ac)) + self.handle = core.BNCreateActivity(name, None, action_callback) + self.action = action + global _action_callbacks + _action_callbacks[len(_action_callbacks)] = action_callback + else: + self.handle = handle + self.__dict__["name"] = name + + def _action(self, ac): + try: + if self.action is not None: + self.action(ac) + except: + binaryninja.log.log_error(traceback.format_exc()) + + def __del__(self): + binaryninja.log.log_error("Activity DEL called!") + if self.handle is not None: + core.BNFreeActivity(self.handle) + + def __repr__(self): + return f"<Activity: {self.name}>" + + def __str__(self): + return self.name + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return ctypes.addressof(self.handle.contents) == ctypes.addressof(other.handle.contents) + + def __ne__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return not (self == other) + + def __hash__(self): + return hash((self.instance_id, ctypes.addressof(self.handle.contents))) + + @property + def name(self): + """Activity name (read-only)""" + return core.BNActivityGetName(self.handle) + + +class _WorkflowMetaclass(type): + + @property + def list(self): + """List all Workflows (read-only)""" + binaryninja._init_plugins() + count = ctypes.c_ulonglong() + workflows = core.BNGetWorkflowList(count) + result = [] + for i in range(0, count.value): + result.append(Workflow(handle = core.BNNewWorkflowReference(workflows[i]))) + core.BNFreeWorkflowList(workflows, count.value) + return result + + def __iter__(self): + binaryninja._init_plugins() + count = ctypes.c_ulonglong() + workflows = core.BNGetWorkflowList(count) + try: + for i in range(0, count.value): + yield Workflow(handle = core.BNNewWorkflowReference(workflows[i])) + finally: + core.BNFreeWorkflowList(workflows, count.value) + + def __getitem__(self, value): + binaryninja._init_plugins() + workflow = core.BNWorkflowInstance(str(value)) + return Workflow(handle = workflow) + + def __setattr__(self, name, value): + try: + type.__setattr__(self, name, value) + except AttributeError: + raise AttributeError("attribute '%s' is read only" % name) + + +class Workflow(object, metaclass=_WorkflowMetaclass): + """ + :class:`Workflow` A Binary Ninja Workflow is an abstraction of a computational binary analysis pipeline and it provides the extensibility \ + mechanism needed for tailored binary analysis and decompilation. More specifically, a Workflow is a repository of activities along with a \ + unique strategy to execute them. Binary Ninja provides two Workflows named ``core.module.defaultAnalysis`` and ``core.function.defaultAnalysis`` \ + which expose the core analysis. + + A Workflow starts in the unregistered state from either creating a new empty Workflow, or cloning an existing Workflow. While unregistered \ + it's possible to add and remove activities, as well as change the execution strategy. In order to use the Workflow on a binary it must be \ + registered. Once registered the Workflow is immutable and available for use. + + Currently, Workflows is disabled by default and can be enabled via Settings:: + + >>> Settings().set_bool('workflows.enable', True) + + Retrieve the default Workflow by creating a Workflow object:: + + >>> Workflow() + <Workflow: core.module.defaultAnalysis> + + Retrieve any registered Workflow by name:: + + >>> list(Workflow) + [<Workflow: core.function.defaultAnalysis>, <Workflow: core.module.defaultAnalysis>] + >>> Workflow('core.module.defaultAnalysis') + <Workflow: core.module.defaultAnalysis> + >>> Workflow('core.function.defaultAnalysis') + <Workflow: core.function.defaultAnalysis> + + Create a new Workflow, show it in the UI, modify and then register it. Try it via Open with Options and selecting the new Workflow:: + + >>> pwf = Workflow().clone("PythonLogWarnWorkflow") + >>> pwf.show_topology() + >>> pwf.register_activity(Activity("PythonLogWarn", action=lambda analysis_context: binaryninja.log.log_warn("PythonLogWarn Called!"))) + >>> pwf.insert("core.function.basicBlockAnalysis", ["PythonLogWarn"]) + >>> pwf.register() + + .. note:: Binary Ninja Workflows is currently under development and available as an early feature preview. For additional documentation:: + + >>> Workflow().show_documentation() + """ + + def __init__(self, name = "", handle = None, query_registry = True): + if handle is None: + if query_registry: + self.handle = core.BNWorkflowInstance(str(name)) + else: + self.handle = core.BNCreateWorkflow(name) + else: + self.handle = handle + self.__dict__["name"] = core.BNGetWorkflowName(self.handle) + + def __del__(self): + if self.handle is not None: + core.BNFreeWorkflow(self.handle) + + def __len__(self): + return int(core.BNWorkflowSize(self.handle)) + + def __repr__(self): + if core.BNWorkflowIsRegistered(self.handle): + return f"<Workflow: {self.name}>" + else: + return f"<Workflow [Unregistered]: {self.name}>" + + def __str__(self): + return self.name + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return ctypes.addressof(self.handle.contents) == ctypes.addressof(other.handle.contents) + + def __ne__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return not (self == other) + + def __hash__(self): + return hash((self.instance_id, ctypes.addressof(self.handle.contents))) + + def register(self, description = "") -> bool: + """ + ``register`` Register this Workflow, making it immutable and available for use. + + :param str description: a JSON description of the Workflow + :return: True on Success, False otherwise + :rtype: bool + """ + return core.BNRegisterWorkflow(self.handle, str(description)) + + def clone(self, name, activity = "") -> "Workflow": + """ + ``clone`` Clone a new Workflow, copying all Activities and the execution strategy. + + :param str name: the name for the new Workflow + :param str activity: if specified, perform the clone operation using ``activity`` as the root + :return: a new Workflow + :rtype: Workflow + """ + workflow = core.BNWorkflowClone(self.handle, str(name), str(activity)) + return Workflow(handle = workflow) + + def register_activity(self, activity, subactivities = [], description = "") -> bool: + """ + ``register_activity`` Register an Activity with this Workflow. + + :param Activity activity: the Activity to register + :param list[str] subactivities: the list of Activities to assign + :param str description: a JSON description of the Activity + :return: True on Success, False otherwise + :rtype: bool + """ + if activity is None: + return None + input_list = (ctypes.c_char_p * len(subactivities))() + for i in range(0, len(subactivities)): + input_list[i] = binaryninja.cstr(str(subactivities[i])) + return core.BNWorkflowRegisterActivity(self.handle, activity.handle, input_list, len(subactivities), str(description)) + + def contains(self, activity) -> bool: + """ + ``contains`` Determine if an Activity exists in this Workflow. + + :param str activity: the Activity name + :return: True if the Activity exists, False otherwise + :rtype: bool + """ + return core.BNWorkflowContains(self.handle, str(name)) + + def configuration(self, activity = "") -> str: + """ + ``configuration`` Retrieve the configuration as an adjacency list in JSON for the Workflow, or if specified just for the given ``activity``. + + :param str activity: if specified, return the configuration for the ``activity`` + :return: an adjacency list representation of the configuration in JSON + :rtype: str + """ + return core.BNWorkflowGetConfiguration(self.handle, str(activity)) + + @property + def registered(self) -> bool: + """ + ``registered`` Whether this Workflow is registered or not. A Workflow becomes immutable once it is registered. + + :type: bool + """ + return core.BNWorkflowIsRegistered(self.handle) + + def get_activity(self, activity) -> Union[None, Activity]: + """ + ``get_activity`` Retrieve the Activity object for the specified ``activity``. + + :param str activity: the Activity name + :return: the Activity object + :rtype: Activity + """ + handle = core.BNWorkflowGetActivity(self.handle, str(activity)) + if handle is None: + return None + return Activity(activity, handle) + + def activity_roots(self, activity = "") -> List[str]: + """ + ``activity_roots`` Retrieve the list of activity roots for the Workflow, or if specified just for the given ``activity``. + + :param str activity: if specified, return the roots for the ``activity`` + :return: list of root activity names + :rtype: list[str] + """ + length = ctypes.c_ulonglong() + result = core.BNWorkflowGetActivityRoots(self.handle, str(activity), ctypes.byref(length)) + out_list = [] + for i in range(length.value): + out_list.append(pyNativeStr(result[i])) + core.BNFreeStringList(result, length) + return out_list + + def subactivities(self, activity = "", immediate = True) -> List[str]: + """ + ``subactivities`` Retrieve the list of all activities, or optionally a filtered list. + + :param str activity: if specified, return the direct children and optionally the descendants of the ``activity`` (includes ``activity``) + :param bool immediate: whether to include only direct children of ``activity`` or all descendants + :return: list of activity names + :rtype: list[str] + """ + length = ctypes.c_ulonglong() + result = core.BNWorkflowGetSubactivities(self.handle, str(activity), immediate, ctypes.byref(length)) + out_list = [] + for i in range(length.value): + out_list.append(pyNativeStr(result[i])) + core.BNFreeStringList(result, length) + return out_list + + def assign_subactivities(self, activity, activities) -> bool: + """ + ``assign_subactivities`` Assign the list of ``activities`` as the new set of children for the specified ``activity``. + + :param str activity: the Activity node to assign children + :param list[str] activities: the list of Activities to assign + :return: True on success, False otherwise + :rtype: bool + """ + input_list = (ctypes.c_char_p * len(activities))() + for i in range(0, len(activities)): + input_list[i] = binaryninja.cstr(str(activities[i])) + return core.BNWorkflowAssignSubactivities(self.handle, str(activity), input_list, len(activities)) + + def clear(self) -> bool: + """ + ``clear`` Remove all Activity nodes from this Workflow. + + :return: True on success, False otherwise + :rtype: bool + """ + return core.BNWorkflowClear(self.handle) + + def insert(self, activity, activities) -> bool: + """ + ``insert`` Insert the list of ``activities`` before the specified ``activity`` and at the same level. + + :param str activity: the Activity node for which to insert ``activities`` before + :param list[str] activities: the list of Activities to insert + :return: True on success, False otherwise + :rtype: bool + """ + input_list = (ctypes.c_char_p * len(activities))() + for i in range(0, len(activities)): + input_list[i] = binaryninja.cstr(str(activities[i])) + return core.BNWorkflowInsert(self.handle, str(activity), input_list, len(activities)) + + def remove(self, activity) -> bool: + """ + ``remove`` Remove the specified ``activity``. + + :param str activity: the Activity to remove + :return: True on success, False otherwise + :rtype: bool + """ + return core.BNWorkflowRemove(self.handle, str(activity)) + + def replace(self, activity, new_activity) -> bool: + """ + ``replace`` Replace the specified ``activity``. + + :param str activity: the Activity to replace + :param list[str] new_activity: the replacement Activity + :return: True on success, False otherwise + :rtype: bool + """ + return core.BNWorkflowReplace(self.handle, str(activity), str(new_activity)) + + def graph(self, activity = "", sequential = False, show = True) -> Union[None, FlowGraph]: + """ + ``graph`` Generate a FlowGraph object for the current Workflow and optionally show it in the UI. + + :param str activity: if specified, generate the Flowgraph using ``activity`` as the root + :param bool sequential: whether to generate a **Composite** or **Sequential** style graph + :param bool show: whether to show the graph in the UI or not + :return: FlowGraph object on success, None on failure + :rtype: FlowGraph + """ + graph = core.BNWorkflowGetGraph(self.handle, str(activity), sequential) + if not graph: + return None + graph = binaryninja.flowgraph.CoreFlowGraph(graph) + if show: + core.BNShowGraphReport(None, f'{self.name} <{activity}>' if activity else self.name, graph.handle) + return graph + + def show_documentation(self) -> None: + """ + ``show_documentation`` Show the Workflows documentation in the UI. + + :rtype: None + """ + core.BNWorkflowShowReport(self.handle, "documentation") + + def show_metrics(self) -> None: + """ + ``show_metrics`` Not yet implemented. + + :rtype: None + """ + core.BNWorkflowShowReport(self.handle, "metrics") + + def show_topology(self) -> None: + """ + ``show_topology`` Show the Workflow topology in the UI. + + :rtype: None + """ + core.BNWorkflowShowReport(self.handle, "topology") + + def show_trace(self) -> None: + """ + ``show_trace`` Not yet implemented. + + :rtype: None + """ + core.BNWorkflowShowReport(self.handle, "trace") diff --git a/rapidjsonwrapper.h b/rapidjsonwrapper.h index ac418807..d1988b42 100644 --- a/rapidjsonwrapper.h +++ b/rapidjsonwrapper.h @@ -27,16 +27,18 @@ struct GenericException: public std::exception throw ParseException(parseErrorCode, #parseErrorCode, offset) #include "rapidjson/error/error.h" -struct ParseException: public std::runtime_error, rapidjson::ParseResult +struct ParseException: public std::runtime_error, public rapidjson::ParseResult { ParseException(rapidjson::ParseErrorCode code, const char* msg, size_t offset) : std::runtime_error(msg), ParseResult(code, offset) {} }; +#include "rapidjson/error/en.h" #include "rapidjson/rapidjson.h" #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" +#include "rapidjson/prettywriter.h" #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic pop diff --git a/workflow.cpp b/workflow.cpp new file mode 100644 index 00000000..63137ebc --- /dev/null +++ b/workflow.cpp @@ -0,0 +1,284 @@ +#include "binaryninjaapi.h" +#include "json/json.h" +#include <string> +#include <variant> + +using namespace BinaryNinja; +using namespace std; + + +AnalysisContext::AnalysisContext(BNAnalysisContext* analysisContext): m_reader(Json::CharReaderBuilder().newCharReader()) +{ + //LogError("API-Side AnalysisContext Constructed!"); + m_object = analysisContext; + m_builder["indentation"] = ""; +} + + +AnalysisContext::~AnalysisContext() +{ + //LogError("API-Side AnalysisContext Destructed!"); +} + + +Ref<Function> AnalysisContext::GetFunction() +{ + BNFunction* func = BNAnalysisContextGetFunction(m_object); + if (!func) + return nullptr; + return new Function(func); +} + + +Ref<LowLevelILFunction> AnalysisContext::GetLowLevelILFunction() +{ + BNLowLevelILFunction* func = BNAnalysisContextGetLowLevelILFunction(m_object); + if (!func) + return nullptr; + return new LowLevelILFunction(func); +} + + +Ref<MediumLevelILFunction> AnalysisContext::GetMediumLevelILFunction() +{ + BNMediumLevelILFunction* func = BNAnalysisContextGetMediumLevelILFunction(m_object); + if (!func) + return nullptr; + return new MediumLevelILFunction(func); +} + + +void AnalysisContext::SetLowLevelILFunction(Ref<LowLevelILFunction> lowLevelIL) +{ + BNSetLowLevelILFunction(m_object, lowLevelIL->m_object); +} + + +bool AnalysisContext::Inform(const string& request) +{ + return BNAnalysisContextInform(m_object, request.c_str()); +} + + +Workflow::Workflow(const string& name) +{ + m_object = BNCreateWorkflow(name.c_str()); +} + + +Workflow::Workflow(BNWorkflow* workflow) +{ + m_object = BNNewWorkflowReference(workflow); +} + + +vector<Ref<Workflow>> Workflow::GetList() +{ + size_t count; + BNWorkflow** list = BNGetWorkflowList(&count); + + vector<Ref<Workflow>> result; + result.reserve(count); + for (size_t i = 0; i < count; i++) + result.push_back(new Workflow(BNNewWorkflowReference(list[i]))); + + BNFreeWorkflowList(list, count); + return result; +} + + +Ref<Workflow> Workflow::Instance(const string& name) +{ + return new Workflow(BNWorkflowInstance(name.c_str())); +} + + +bool Workflow::RegisterWorkflow(Ref<Workflow> workflow, const string& description) +{ + return BNRegisterWorkflow(workflow->m_object, description.c_str()); +} + + +Ref<Workflow> Workflow::Clone(const string& name, const string& activity) +{ + return new Workflow(BNWorkflowClone(m_object, name.c_str(), activity.c_str())); +} + + +bool Workflow::RegisterActivity(Ref<Activity> activity, const string& description) +{ + return RegisterActivity(activity, {}, description); +} + + +bool Workflow::RegisterActivity(Ref<Activity> activity, const vector<string>& subactivities, const string& description) +{ + activity->AddRefForRegistration(); // TODO + + char** buffer = new char*[subactivities.size()]; + if (!buffer) + return false; + + for (size_t i = 0; i < subactivities.size(); i++) + buffer[i] = BNAllocString(subactivities[i].c_str()); + + bool result = BNWorkflowRegisterActivity(m_object, activity->GetObject(), (const char**)buffer, subactivities.size(), description.c_str()); + BNFreeStringList(buffer, subactivities.size()); + return result; +} + + +bool Workflow::Contains(const string& activity) +{ + return BNWorkflowContains(m_object, activity.c_str()); +} + + +string Workflow::GetConfiguration(const string& activity) +{ + char* tmpStr = BNWorkflowGetConfiguration(m_object, activity.c_str()); + string result(tmpStr); + BNFreeString(tmpStr); + return result; +} + + +string Workflow::GetName() const +{ + char* str = BNGetWorkflowName(m_object); + string result = str; + BNFreeString(str); + return result; +} + + +bool Workflow::IsRegistered() const +{ + return BNWorkflowIsRegistered(m_object); +} + + +size_t Workflow::Size() const +{ + return BNWorkflowSize(m_object); +} + + +Ref<Activity> Workflow::GetActivity(const string& activity) +{ + BNActivity* activityObject = BNWorkflowGetActivity(m_object, activity.c_str()); + return new Activity(BNNewActivityReference(activityObject)); +} + + +vector<string> Workflow::GetActivityRoots(const string& activity) +{ + size_t size = 0; + char** outBuffer = (char**)BNWorkflowGetActivityRoots(m_object, activity.c_str(), &size); + + vector<string> result; + result.reserve(size); + for (size_t i = 0; i < size; i++) + result.emplace_back(outBuffer[i]); + + BNFreeStringList(outBuffer, size); + return result; +} + + +vector<string> Workflow::GetSubactivities(const string& activity, bool immediate) +{ + size_t size = 0; + char** outBuffer = (char**)BNWorkflowGetSubactivities(m_object, activity.c_str(), immediate, &size); + + vector<string> result; + result.reserve(size); + for (size_t i = 0; i < size; i++) + result.emplace_back(outBuffer[i]); + + BNFreeStringList(outBuffer, size); + return result; +} + + +bool Workflow::AssignSubactivities(const string& activity, const vector<string>& subactivities) +{ + char** buffer = new char*[subactivities.size()]; + if (!buffer) + return false; + + for (size_t i = 0; i < subactivities.size(); i++) + buffer[i] = BNAllocString(subactivities[i].c_str()); + + bool result = BNWorkflowAssignSubactivities(m_object, activity.c_str(), (const char**)buffer, subactivities.size()); + BNFreeStringList(buffer, subactivities.size()); + return result; +} + + +bool Workflow::Clear() +{ + return BNWorkflowClear(m_object); +} + + +bool Workflow::Insert(const string& activity, const std::string& newActivity) +{ + char** buffer = new char*[1]; + if (!buffer) + return false; + + buffer[0] = BNAllocString(newActivity.c_str()); + + bool result = BNWorkflowInsert(m_object, activity.c_str(), (const char**)buffer, 1); + BNFreeStringList(buffer, 1); + return result; +} + + +bool Workflow::Insert(const string& activity, const vector<string>& activities) +{ + char** buffer = new char*[activities.size()]; + if (!buffer) + return false; + + for (size_t i = 0; i < activities.size(); i++) + buffer[i] = BNAllocString(activities[i].c_str()); + + bool result = BNWorkflowInsert(m_object, activity.c_str(), (const char**)buffer, activities.size()); + BNFreeStringList(buffer, activities.size()); + return result; +} + + +bool Workflow::Remove(const string& activity) +{ + return BNWorkflowRemove(m_object, activity.c_str()); +} + + +bool Workflow::Replace(const string& activity, const string& newActivity) +{ + return BNWorkflowReplace(m_object, activity.c_str(), newActivity.c_str()); +} + + +Ref<FlowGraph> Workflow::GetGraph(const string& activity, bool sequential) +{ + BNFlowGraph* graph = BNWorkflowGetGraph(m_object, activity.c_str(), sequential); + if (!graph) + return nullptr; + return new CoreFlowGraph(graph); +} + + +void Workflow::ShowReport(const std::string& name) +{ + BNWorkflowShowReport(m_object, name.c_str()); +} + + +//bool Workflow::Run(const string& activity, Ref<AnalysisContext> analysisContext) +// { + +// } |
