Skip to content

Writing Plugins

Writing Python Plugins

Creating the Plugin

First, take a look at some of the example plugins, or some of the community plugins to get a feel for different APIs you might be interested in. Of course, the full API docs are online and available offline via the Help/Open Python API Reference....

To start, we suggest you download the sample plugin as a template since it contains all of the elements you're likely to need.

  • Begin by editing the plugin.json file
  • Next, update the LICENSE
  • For small scripts, you can include all the code inside of __init__.py, though we recommend for most larger scripts that init just act as an initializer and call into functions organized appropriately in other files.
  • If you have python dependencies, create a requirements.txt listing any python dependencies.

Submitting to the Plugin Manager

If your plugin was created as described above, there's only two steps to get it submitted to the plugin manager!

  1. First, create a release either manually or using our release helper.
  2. Next, just file an issue letting us know about your plugin.

For future releases all you need to do is increment the version and create a new release.

Using Your Own Plugin Repository

The simplest way to run your own plugin repository is to duplicate the structure of https://github.com/vector35/community-plugins. Specifically, the plugins.json, as listing.json is used along with generate_index.py to create that file.

Once you've created your test repository, use the pluginManager.unofficialName and pluginManager.unofficialUrl settings to add your third-party repository.

The add_repository API can also be used to add the repository, though it may require manual creation of the repository folder.

Testing

It's useful to be able to reload your plugin during testing. On the Commercial or Ultimate editions of Binary Ninja, this is easily accomplished with a stand-alone headless install using import binaryninja after installing the API. (install_api.py is included in each platforms respective installation folder)

Additionally, some plugin types like Architectures or BinaryViews are only loaded at launch and cannot be reloaded during a running session.

For other plugins, we recommend the following workflow from the scripting console which enables easy iteration and testing:

import pluginname
import importlib
importlib.reload(pluginname);pluginname.callbackmethod(bv)

Then just [UP] [ENTER] to trigger the reload when the plugin has changed.

Writing plugins using other IDEs (tab completion)

Even though non-commercial licenses don't have headless automation, the install API script (which is included in the installation directory) allows you to add the binaryninja module to your python environment. Once you do that, you should get automatic completion in any editor that supports it even on non-commercial! Of course, on Commercial and Ultimate installations, the script is even more useful, allowing for headless scripts with your existing python interpreter.

Debugging using other IDEs

If you wish to debug your python scripts, there are a few methods specific to different IDEs:

Remote debugging with VSCode:

  1. Run pip install --user debugpy in the Python interpreter you have selected in Binary Ninja Settings.
  2. In VSCode, open the Run and Debug sidebar.
  3. Create a launch.json file if one does not already exist, or open launch.json if one does.
  4. In launch.json, select Add Configuration > Python > Remote Attach
  5. Enter a host of localhost and any port
  6. Set the path mapping to be from / to / (Windows: C:\\ to C:\\)
  7. Open Binary Ninja
  8. Use connect_vscode_debugger(port=12345) in the Python Console, using whichever port you selected in launch.json.
  9. In VSCode, start debugging. You should see the bottom toolbar change color, and the debugger should be attached.

Remote debugging with IntelliJ PyCharm

WARNING: Does not work on PyCharm Community, requires PyCharm Professional

  1. In PyCharm, add a Run Configuration for Python Debug Server. Give it a name and choose a port and host.
  2. Run the pip install script displayed in the Run Configuration using whichever python interpreter you have selected for Binary Ninja.
  3. In PyCharm, start debugging. You should see "Waiting for process connection..." in the Debugger panel.
  4. Open Binary Ninja
  5. Use connect_pycharm_debugger(port=12345) in the Python Console, using whichever port you selected in the Run Configuration. You should now see "Connected" in the PyCharm Debugger panel.

UI Plugins

Binary Ninja UI plugins should always import binaryninjaui before an import PySide6. Not only does this make sure the correct PySide6 is loaded (running the wrong version of PySide6 can result in crashing), but this prevents plugins from running headlessly.

UI plugins can take many forms. Some, like Snippets create their own UI elements and interact via UIActions. Others extend the UI via existing UI elements such as Triage, Kaitai, hellosidebar, or helloglobalarea.

Many other third-party plugins also implement UI based examples, such as BNIL Graph using the FlowGraph APIs.

Unfortunately, due to a PySide documentation generation issue, the best and most reliable documentation on the UI system is not in the regular python API docs, but in the C++ documentation which translates fairly cleanly to their python equivalent.

Writing Native Plugins

Writing native plugins allows for higher performance code and lower level access to the Binary Ninja API, but comes with a couple more hurdles than Python. Notably, native plugins are built against a specific version of the API, cannot be hot-reloaded, and require more sophisticated build setups.

Supported Toolchains

When building native plugins for Binary Ninja, the following toolchains and dependencies are required, based on host OS. Older versions may work but are not supported.

  • macOS: Xcode 13+
  • Windows: VS 2019 Professional with C/C++ Native Tools package
  • Linux: GCC 9.4+

Additionally, Binary Ninja uses C++17 features, and requires a C++17 compatible compiler.

CMake Setup

Binary Ninja uses the CMake build system generator to compile native code, and provides convenient helper scripts for those making plugins. As of writing, CMake 3.13 or greater is required, although it is recommended to use the latest version.

Project Setup

The first things to specify in your CMake file are a couple boilerplate options for building C++:

# Pick whatever version you have
cmake_minimum_required(VERSION 3.24)

# Name your plugin
project(TestPlugin CXX)

set(CMAKE_CXX_STANDARD 17)

# Unless you are writing a plugin that needs Qt's UI, specify this
set(HEADLESS 1)

Then you want to get the matching API repository for the version of Binary Ninja you have. This information is contained in a file named api_REVISION.txt that exists in the root install folder for Linux, the Contents/Resources sub-folder on macOS, and the root installation folder on Windows.

Once you know which revision to use, you can clone a copy of the binaryninja-api repository and reference it directly in your plugin. If you're using git, this can be accomplished easily using a submodule:

git submodule add https://github.com/Vector35/binaryninja-api.git binaryninjaapi
cd binaryninjaapi
# Pick the revision from api_REVISION.txt
git checkout 6466fba3341b2ea7dbfceeeebbc6c0322a5d8514

If you're not using git, you can clone the repository elsewhere:

git clone https://github.com/Vector35/binaryninja-api.git binaryninjaapi
cd binaryninjaapi 
# Pick the revision from api_REVISION.txt
git checkout 6466fba3341b2ea7dbfceeeebbc6c0322a5d8514

Now that you have the correct copy of the api, you need to point CMake at it and include it for use. Include something like the following in your CMake script and either add the path of your clone to the HINTS list or set the BN_API_PATH environment variable to the location of your clone

find_path(
    BN_API_PATH
    NAMES binaryninjaapi.h
    # List of paths to search for the clone of the api
    HINTS ../.. binaryninjaapi $ENV{BN_API_PATH}
    REQUIRED
)
add_subdirectory(${BN_API_PATH} api)

Be sure to create a shared library and link against the Binary Ninja api. Also, you can use the bn_install_plugin helper to automatically set up your plugin to install to the Binary Ninja plugins directory when you use cmake install.

# Use whichever sources and plugin name you want
add_library(TestPlugin SHARED TestPlugin.cpp)

# Link with Binary Ninja
target_link_libraries(TestPlugin PUBLIC binaryninjaapi)

# Tell `cmake --install` to copy your plugin to the plugins directory
bn_install_plugin(TestPlugin)

From there you can write the rest of your plugin's CMake configuration, including any other dependencies or options that you want. When you want to run your plugin, you can use cmake --build and cmake --install to compile and copy your plugin to your Binary Ninja plugins directory, or set up an IDE to do that for you. You could also copy the plugin manually if you are using a different plugins directory location.

In the source code of your plugin, you will need to export some functions that Binary Ninja uses to load your plugin at runtime:

#include "binaryninjaapi.h"
extern "C" {
    // Tells Binary Ninja which version of the API you compiled against
    BN_DECLARE_CORE_ABI_VERSION

    // Function run on plugin startup, do simple initialization here (Settings, BinaryViewTypes, etc)
    BINARYNINJAPLUGIN bool CorePluginInit()
    {
        return true;
    }

    // (Optional) Function to add other plugin dependencies in case your plugin requires them
    BINARYNINJAPLUGIN void CorePluginDependencies()
    {
        // For example, if you require the x86 to be loaded before your plugin
        AddRequiredPluginDependency("arch_x86");
    }
}

From there, you can implement your plugin functionality as you desire. I highly recommend looking at other plugins for API usage since the C++ API is less well-documented than the Python API. Usually the functions and classes are named identically, but you may find some outliers. Also, C++ has a way more difficult task of managing memory, since there is no garbage collector to handle it for you. Generally speaking, most API objects are reference-counted via the Ref<T> class, and you should only ever handle Refs or bare pointers. When in doubt, feel free to ask on our Slack and both our team and helpful community can assist.

Automated GitHub CI

Managing build infrastructure to build cross-platform native plugins can be a headache even for stables, let alone trying to track all dev releases. To help with that, we've published an example plugin that includes GitHub actions to build on MacOS, Linux, and Windows. Combining this with something like a plugin loader can simplify both publishing and using native plugins.

UI Plugins

If you want to include UI in your plugin, you can integrate with Binary Ninja's Qt-based UI by linking with Qt and binaryninjaui. You will need to use the same version of Qt as Binary Ninja. We provide steps for building it here, or you can attempt to use a system-provided copy if you use Linux and like to live dangerously. Building it is a bit of a process, but should provide you with a working installation. Once you have a Qt build, you can amend your CMake file to make a UI plugin. You will need the following CMake:

# Remove this or set to 0
# set(HEADLESS 1)

# If you are using Qt MOC (i.e. use Q_OBJECT/Q_SIGNALS/Q_SLOTS)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

# Locate Qt installation for linking
find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED)

# Add MOCS to your build
add_library(TestPlugin SHARED library.cpp ${MOCS})

# Link against both binaryninjaapi/binaryninjaui and Qt6
target_link_libraries(TestPlugin PUBLIC binaryninjaapi binaryninjaui Qt6::Core Qt6::Gui Qt6::Widgets)

Then, in your plugin code, instead of using the exported functions for a core plugin, use the ones for a UI plugin:

#include "binaryninjaapi.h"
#include "uitypes.h"
#include "uicontext.h"

extern "C" {
    // Tells Binary Ninja which version of the API you compiled against
    BN_DECLARE_UI_ABI_VERSION

    // Function run on plugin startup, do simple initialization here (ViewTypes, SidebarWidgetTypes, etc)
    BINARYNINJAPLUGIN bool UIPluginInit()
    {
        return true;
    }

    // (Optional) Function to add other plugin dependencies in case your plugin requires them
    // Historically, these have never actually been used 
    BINARYNINJAPLUGIN void UIPluginDependencies()
    {
        // For example, if you require triage view to be loaded before your plugin
        AddRequiredUIPluginDependency("triage");
    }
}

From there, you can implement whatever wacky Qt user interfaces you dream up. Be warned that the Binary Ninja UI API is rather poorly documented and often missing helper functions for use by plugins. Feel free to ask for assistance and suggestions, but know that it's very easy to run into memory bugs when working with Qt. I would recommend looking at the source to the debugger, as an example of the largest, best-maintained UI plugin for Binary Ninja.

Python Integration

If you want your C++ plugin to also support a Python API, you will have a lot of work to do. Generally speaking, there are no cookie-cutter solutions to this problem, but there is a general strategy:

  1. Expose a C API from your plugin
  2. Provide a set of Python bindings to that C API
  3. Load those Python bindings as a Python plugin in Binary Ninja

Again, I'm going to point out the debugger as a fantastic example of how to implement this. Generally speaking, you will either need to write both sides of the FFI in a similar way, or you may be able to find a library that does that for you. Possibly libffi, although there aren't any examples of using it for Binary Ninja specifically. If you manage to get something working, let us know! We would love to see more complex plugins with extensible behavior!

IDE Setup

CLion

CLion is generally pretty good at handling CMake projects. Given the above CMake configuration, it can automatically detect the plugin target and will compile and install correctly. Here are a a few steps to finish setup for building and live debugging your plugin:

  1. If you installed Binary Ninja somewhere other than the default, add an environment variable in your CMake Profile pointing at the installation, e.g.: BN_INSTALL_DIR=/Applications/Binary Ninja.app
  2. If you are writing a UI plugin, you will need to include the directory containing qmake to the PATH Environment Variable in your CMake Profile, e.g.: PATH=/usr/bin:/bin:/usr/sbin:/sbin:/Users/user/Qt/6.7.2/clang_64/bin
  3. In your Run Configuration's Before Launch steps, add an Install step. This will copy the updated version of your plugin before starting, so you don't have to run Install manually.
  4. Set the Executable of your Run Configuration to point to the Binary Ninja executable. This allows you to compile your plugin and start Binary Ninja automatically. i. On macOS, you will need the full path to /Applications/Binary Ninja.app/Contents/MacOS/binaryninja
  5. (Optionally) Add the -e flag to the Program Arguments to get error logs printed to your console
  6. (Optionally) Add the -e -d flags to the Program Arguments to get debug logs printed to your console. This may slow down Binary Ninja (and CLion) due to the large volume of logs produced.
  7. (Optionally) Add the -l /tmp/bn_out.txt flags to the Program Arguments so your logs also get printed to a text file when you inevitably fill up the Console buffer in CLion and want to see what happened.
  8. (Optionally on macOS) Add the Environment Variables MallocScribble=1 and MallocPreScribble=1 to make memory errors easier to spot.

Visual Studio Code

VSCode takes a bit of configuration to set up, but can build and debug plugins efficiently once ready. You can install the C/C++ extension, the CMake extension, and the CMake Tools extension. You need to set up a task in .vscode/tasks.json to build and install your plugin. Something like this:

// tasks.json
{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "cmake",
            "label": "CMake: install",
            "command": "install",
            "problemMatcher": [],
            "detail": "CMake template install task",
            "options": {
                "environment": {
                    // You will need this if your Binary Ninja installation is not in the default location   
                    "BN_INSTALL_DIR": "C:\\Users\\User\\AppData\\Local\\Vector35\\BinaryNinja",
                    // You will need this if you are writing a UI plugin
                    "PATH": "C:\\Users\\User\\Qt\\6.7.2\\msvc2019_64\\bin"
                }
            }
        }
    ]
}

You will also want to set up a launch task in .vscode/launch.json to launch Binary Ninja in a debugger, so you can debug your plugin. Be sure to set "preLaunchTask" to use the CMake: install task created above, so your code updates will be built and installed automatically before you start debugging.

// launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(Windows) Launch",
            "type": "cppvsdbg",
            "request": "launch",
            "program": "C:\\Users\\User\\AppData\\Local\\Vector35\\BinaryNinja\\binaryninja.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "C:\\Users\\User\\AppData\\Local\\Vector35\\BinaryNinja",
            "environment": [],
            "console": "externalTerminal",
            "preLaunchTask": "CMake: install"
        }
    ]
}

There are a few other options you can use to assist in debugging:

  1. (Optionally) Add the "-e" flag to the launch configuration's args to get error logs printed to your console
  2. (Optionally) Add the "-e", "-d" flags to the launch configuration's args to get debug logs printed to your console. This may slow down Binary Ninja (and VSCode) due to the large volume of logs produced.
  3. (Optionally) Add the "-l", "/tmp/bn_out.txt" flags to the launch configuration's args so your logs also get printed to a text file when you inevitably fill up the Console buffer and want to see what happened.
  4. (Optionally on macOS) Add the environment variables { "name": "MallocScribble", "value": "1" } and { "name": "MallocPreScribble", "value": "1" } to make memory errors easier to spot.

As a footnote, it should be noted that most of the team at Vector 35 use VSCode as a bare text editor and use command-line lldb or gdb to debug their code. Shout-outs to people trying to get this working in Vim.

Submitting to the plugin manager

While native plugins are not supported in the plugin manager at this time, it's possible to work around this limitation by pre-building a native plugin for all three platforms and using a python plugin that acts as a loader for the native plugin. There aren't any good examples of automated workflows for this, but binexport has supposedly got it working. But given that the people you're distributing your plugin to may have a different version of the API, you will likely want to just distribute the source code and build setup, and have them build against whatever version of Binary Ninja they have installed.

Examples

Several native plugin examples exist:

Contributing to Official Plugins

There are many many official plugins released as open source. Python ones are included in the official plugin repository, native architectures are available on GitHub along with several others that are included with the default product such as the debugger, the views, platforms, and some rust plugins.

The first time you contribute, you'll be asked to sign a CLA automatically by cla-assistant. Further commits after the first should not require any changes.