Skip to main content
This example demonstrates how to create custom workflow activities that integrate into Binary Ninja’s analysis pipeline, using a function inliner as a practical example.

Overview

The inliner.cpp example shows how to:
  • Create custom workflow activities
  • Clone and modify existing workflows
  • Integrate activities into the analysis pipeline
  • Use LLIL manipulation for code transformation
  • Register plugin commands that interact with workflows

Complete Source Code

#include <cinttypes>
#include <cstdint>
#include <cstdio>
#include <mutex>
#include <string>
#include <unordered_map>

#include "binaryninjaapi.h"
#include "lowlevelilinstruction.h"
#include "mediumlevelilinstruction.h"

using namespace BinaryNinja;
using namespace std;

extern "C"
{
    BN_DECLARE_CORE_ABI_VERSION

    std::mutex g_mutex;
    unordered_map<void*, unordered_map<uint64_t, set<uint64_t>>> g_callSiteInlines;

    void FunctionInliner(Ref<AnalysisContext> analysisContext)
    {
        std::unique_lock<std::mutex> lock(g_mutex);

        Ref<Function> function = analysisContext->GetFunction();
        Ref<BinaryView> data = function->GetView();
        auto gItr = g_callSiteInlines.find(data->GetObject());
        if (gItr == g_callSiteInlines.end())
            return;

        auto itr = gItr->second.find(function->GetStart());
        if (itr == gItr->second.end())
            return;

        auto& callSiteInlines = itr->second;
        lock.unlock();

        bool updated = false;
        uint8_t opcode[BN_MAX_INSTRUCTION_LENGTH];
        InstructionInfo iInfo;
        Ref<LowLevelILFunction> llilFunc = analysisContext->GetLowLevelILFunction();
        
        for (const auto inlineAddr : callSiteInlines)
        {
            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;
                    }

                    // Get call destination
                    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;
                    }

                    // Get target function and inline its IL
                    Ref<Function> targetFunc = data->GetAnalysisFunction(
                        function->GetPlatform(), platformAddr);
                    auto targetLlil = targetFunc->GetLowLevelIL();
                    
                    LowLevelILLabel inlineStartLabel;
                    llilFunc->MarkLabel(inlineStartLabel);
                    instr.Replace(llilFunc->Goto(inlineStartLabel));

                    // Copy target function's IL
                    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)
                            {
                                // Replace return with goto to next instruction
                                LowLevelILLabel label;
                                label.operand = instrIndex + 1;
                                llilFunc->AddInstruction(llilFunc->Goto(label));
                            }
                            else
                                llilFunc->AddInstruction(tinstr.CopyTo(llilFunc));
                        }
                    }
                    llilFunc->Finalize();

                    updated = true;
                    break;
                }
            }
        }

        if (!updated)
            return;

        // Regenerate SSA form after modifications
        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 Function at Current Call Site", 
            "Inline function call at current call site.",
            [](BinaryView* view, Function* func) {
                std::lock_guard<std::mutex> lock(g_mutex);
                g_callSiteInlines[view->GetObject()][func->GetStart()].insert(view->GetCurrentOffset());
                func->Reanalyze();
            },
            inlinerIsValid);

        // Create custom workflow
        Ref<Workflow> inlinerWorkflow = Workflow::Get("core.function.baseAnalysis")->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 demonstrates Binary Ninja's extensible analysis APIs.",
            "targetType" : "function"
            })#");

        return true;
    }
}

Key Concepts Explained

1
Define an Activity Function
2
void FunctionInliner(Ref<AnalysisContext> analysisContext)
{
    Ref<Function> function = analysisContext->GetFunction();
    Ref<LowLevelILFunction> llilFunc = analysisContext->GetLowLevelILFunction();
    
    // Perform custom analysis or transformations
    // ...
}
3
Workflow activities are functions that:
4
  • Take an AnalysisContext parameter
  • Access function and IL through the context
  • Perform analysis or transformations
  • Are called automatically during analysis
  • 5
    Clone an Existing Workflow
    6
    Ref<Workflow> inlinerWorkflow = Workflow::Get("core.function.baseAnalysis")->Clone("InlinerWorkflow");
    
    7
    Start with a built-in workflow:
    8
  • Workflow::Get(name) - Retrieve existing workflow
  • ->Clone(newName) - Create a customizable copy
  • Clone preserves all existing activities
  • 9
    Common base workflows:
    10
  • "core.function.baseAnalysis" - Standard function analysis
  • "core.module.defaultAnalysis" - Binary-level analysis
  • 11
    Register a Custom Activity
    12
    inlinerWorkflow->RegisterActivity(new Activity("extension.functionInliner", &FunctionInliner));
    
    13
    Create and register activities:
    14
  • First parameter: Unique activity identifier
  • Second parameter: Function pointer to activity implementation
  • Use namespaced names (e.g., "extension.*") for custom activities
  • 15
    Insert Activity into Pipeline
    16
    inlinerWorkflow->Insert("core.function.translateTailCalls", "extension.functionInliner");
    
    17
    Control activity execution order:
    18
  • Insert(beforeActivity, newActivity) - Run before specified activity
  • Activities run in the order they appear in the workflow
  • Choose insertion point based on data dependencies
  • 19
    Common insertion points:
    20
  • Before "core.function.translateTailCalls" - After basic LLIL construction
  • After "core.function.generateSSA" - When SSA form is available
  • 21
    Register the Workflow
    22
    Workflow::RegisterWorkflow(inlinerWorkflow,
        R"#({
        "title" : "Function Inliner (Example)",
        "description" : "This analysis demonstrates Binary Ninja's extensible analysis APIs.",
        "targetType" : "function"
        })#");
    
    23
    Make the workflow available:
    24
  • JSON metadata describes the workflow
  • targetType can be "function" or "module"
  • Users can select the workflow when analyzing binaries
  • 25
    Modify LLIL During Analysis
    26
    // Replace call instruction with goto
    instr.Replace(llilFunc->Goto(inlineStartLabel));
    
    // Copy IL from another function
    llilFunc->PrepareToCopyFunction(targetLlil);
    for (auto& block : targetLlil->GetBasicBlocks())
    {
        llilFunc->PrepareToCopyBlock(block);
        for (size_t i = block->GetStart(); i < block->GetEnd(); i++)
        {
            LowLevelILInstruction tinstr = targetLlil->GetInstruction(i);
            llilFunc->AddInstruction(tinstr.CopyTo(llilFunc));
        }
    }
    llilFunc->Finalize();
    
    27
    IL modification workflow:
    28
  • PrepareToCopyFunction() - Initialize for copying from another function
  • PrepareToCopyBlock() - Prepare to copy a block
  • AddInstruction() - Add copied or new instructions
  • Finalize() - Complete IL construction
  • GenerateSSAForm() - Regenerate SSA if needed
  • 29
    Create Workflow-Aware Plugin Commands
    30
    auto inlinerIsValid = [](BinaryView* view, Function* func) {
        if (auto workflow = func->GetWorkflow(); workflow)
            return workflow->Contains("extension.functionInliner");
        return false;
    };
    
    PluginCommand::RegisterForFunction(
        "Optimizer\\Inline Function at Current Call Site",
        "Inline function call at current call site.",
        [](BinaryView* view, Function* func) {
            // Mark function for inlining
            func->Reanalyze();
        },
        inlinerIsValid  // Only show command for functions using this workflow
    );
    
    31
    Commands can check if a workflow is active:
    32
  • func->GetWorkflow() - Get function’s workflow
  • workflow->Contains(activityName) - Check if activity is present
  • Use as validation callback to show/hide commands
  • Workflow Architecture

    Activity Lifecycle

    1. Binary opened with workflow selected
    
    2. Analysis begins
    
    3. Activities run in registered order
    
    4. Each activity receives AnalysisContext
    
    5. Activity performs transformations
    
    6. Next activity in pipeline runs
    
    7. Analysis completes
    

    Common Activity Patterns

    Data Collection Activity

    void CollectStatistics(Ref<AnalysisContext> ctx)
    {
        Ref<Function> func = ctx->GetFunction();
        
        // Collect metrics
        size_t instructionCount = 0;
        for (auto& block : func->GetBasicBlocks())
            instructionCount += block->GetLength();
        
        // Store in function metadata or global state
        LogInfo("Function %s: %zu instructions", 
            func->GetSymbol()->GetFullName().c_str(), instructionCount);
    }
    

    IL Transformation Activity

    void SimplifyExpressions(Ref<AnalysisContext> ctx)
    {
        Ref<LowLevelILFunction> il = ctx->GetLowLevelILFunction();
        
        bool modified = false;
        // Find and simplify patterns
        for (auto& block : il->GetBasicBlocks())
        {
            for (size_t i = block->GetStart(); i < block->GetEnd(); i++)
            {
                auto instr = il->GetInstruction(i);
                // Identify and transform patterns
                if (/* pattern match */)
                {
                    // Transform instruction
                    modified = true;
                }
            }
        }
        
        if (modified)
            il->GenerateSSAForm();
    }
    

    Type Inference Activity

    void InferTypes(Ref<AnalysisContext> ctx)
    {
        Ref<Function> func = ctx->GetFunction();
        Ref<MediumLevelILFunction> mlil = func->GetMediumLevelIL();
        
        // Analyze variable usage and infer types
        for (auto& var : func->GetVariables())
        {
            // Analyze how variable is used
            Confidence<Ref<Type>> inferredType = /* type inference logic */;
            func->CreateUserVariable(var, inferredType, var.name);
        }
    }
    

    Building Workflow Plugins

    CMake Configuration

    cmake_minimum_required(VERSION 3.13 FATAL_ERROR)
    project(WorkflowPlugin)
    
    find_package(BinaryNinja REQUIRED)
    
    add_library(WorkflowPlugin SHARED
        inliner.cpp
    )
    
    target_link_libraries(WorkflowPlugin BinaryNinja::API)
    bn_install_plugin(${PROJECT_NAME})
    

    Building

    cd ~/workspace/source/examples/workflows/inliner
    mkdir build && cd build
    cmake ..
    make
    

    Using Custom Workflows

    Select Workflow When Opening Binary

    1. Open binary in Binary Ninja
    2. In analysis options, select “InlinerWorkflow”
    3. Analysis runs with custom activity included

    Apply to Existing Function

    import binaryninja as bn
    
    bv = bn.load("/path/to/binary")
    func = bv.get_function_at(0x401000)
    
    # Change function's workflow
    workflow = bn.Workflow.get("InlinerWorkflow")
    func.set_user_workflow(workflow)
    func.reanalyze()
    

    Set as Default Workflow

    import binaryninja as bn
    
    # Set default workflow for all new analysis
    bn.Settings().set_string("analysis.workflows.functionWorkflow", "InlinerWorkflow")
    

    Expected Output

    When using the inliner workflow:
    1. Open binary with “InlinerWorkflow” selected
    2. Plugin command “Optimizer > Inline Function at Current Call Site” appears
    3. Position cursor at a function call
    4. Run the command
    5. Function re-analyzes with call site inlined
    6. Decompilation shows inlined code instead of call

    Advanced Workflow Concepts

    Activity Configuration

    class ConfigurableActivity : public Activity
    {
    public:
        ConfigurableActivity() : Activity("extension.configurable", 
            [](AnalysisContext* ctx) {
                // Activity logic with settings
                SettingsRef settings = Settings::Instance();
                bool enabled = settings->Get<bool>("myPlugin.enableFeature");
                if (enabled)
                {
                    // Perform analysis
                }
            })
        {}
    };
    

    Conditional Activity Execution

    void ConditionalActivity(Ref<AnalysisContext> ctx)
    {
        Ref<Function> func = ctx->GetFunction();
        
        // Only process functions matching criteria
        if (func->GetParameterVariables().size() < 3)
            return;  // Skip this function
        
        // Perform analysis
    }
    

    Use Cases

    • Custom Optimizations - Implement compiler-style optimizations
    • Deobfuscation - Remove obfuscation during analysis
    • Pattern Recognition - Identify code patterns automatically
    • Type Recovery - Advanced type inference
    • Security Analysis - Detect vulnerabilities during analysis
    • Code Metrics - Collect complexity metrics

    Build docs developers (and LLMs) love