summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--activity.cpp41
-rw-r--r--binaryninjaapi.h102
-rw-r--r--binaryninjacore.h66
-rw-r--r--binaryview.cpp9
-rw-r--r--examples/CMakeLists.txt5
-rw-r--r--examples/workflows/inliner/CMakeLists.txt39
-rw-r--r--examples/workflows/inliner/inliner.cpp159
-rw-r--r--examples/workflows/objectivec/CMakeLists.txt39
-rw-r--r--examples/workflows/objectivec/objectivec.cpp156
-rw-r--r--examples/workflows/tailcall/CMakeLists.txt39
-rw-r--r--examples/workflows/tailcall/tailcall.cpp129
-rw-r--r--function.cpp9
-rw-r--r--python/__init__.py1
-rw-r--r--python/binaryview.py8
-rw-r--r--python/function.py8
-rw-r--r--python/workflow.py429
-rw-r--r--rapidjsonwrapper.h4
-rw-r--r--workflow.cpp284
19 files changed, 1529 insertions, 3 deletions
diff --git a/README.md b/README.md
index 3a27bfde..ee55989a 100644
--- a/README.md
+++ b/README.md
@@ -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)
+// {
+
+// }