Building

IPython Support for Binary Ninja

This blogpost is about the release of a plugin for Binary Ninja that allows you to run a Python Kernel inside the Binary Ninja GUI environment to which you can attach a Jupyer (QT) console, formerly known as IPython shell. The first section is about why this is useful, the second is about some issues I encountered and how to solve them, and the third contains everything you need to know to set it up.

The Why

Binary Ninja is a fairly new competitor on the field of disassemblers and binary analysis frameworks. But one of the features I find most interesting is that it exposes a fairly rich API and various Intermediate Languages which allows you to easily write your own scripts and plugins on top of it. You can even use it to model vulnerabilities and find heartbleed.

But personally I found the built-in Python shell that gives you access to the API fairly lacking. No tab completion, no automatic indenting for multiline code and no fancy features like inline SVG or chart rendering. Especially for exploring foreign Python APIs I have found IPython incredibly helpful so my goal was to somehow get an IPython shell running in the same environment as the built-in shell.

The How

The actual lines of code for this plugin are fairly few, yet no one had written this before. Other projects like Ryan Hileman’s bnrepl project did something similar but just emulated a simple python shell with tab completion which wasn’t enough for me. But this project, or rather the author themselves, proved incredibly helpful while developing.

Running the kernel

Running the kernel turned out to be simpler than I thought in the end. After various attempts with Binary Ninja Plugin Threads, embeding vs starting a kernel and encountering various issues like a started kernel I couldn’t attach too, the kernel refusing to start because it wasn’t being started in the main thread and thus signals didn’t work and others, the solution was basically:

self.app.init_signal = lambda *args, **kw: None
self.app.initialize()
self.app.start()

Patching the init_signal method with a no op allowed the kernel to run in a background thread and I could attach to it.

Getting the magic variables

The Binary Ninja built-in shell has various magic variables that are automatically updated according to some GUI properties, e.g. bv for the current open BinaryView, here for the cursor adress, current_function for the object corresponding the function that is currently open in the GUI (if any) and various others. They are crucial because they are the only references to those data structures we have and allow us to propagate any changes we make back into the GUI. So for example running current_function.name = 'foobar' renames the function that is currently opened in the GUI to foobar. Or we could write a script that colors any xor instructions in the currently open binary view in pink.

There were two main problems with this: Finding out where to get those values and object references from and how to inject them into the IPython/Jupyter environment.

Ryan’s bnrepl project had already solved the first so I adapted their solution.

The solution for the first problem are the magic lines:

obj = [o for o in gc.get_objects() if isinstance(o, scriptingprovider.PythonScriptingInstance.InterpreterThread)]
self._interpreter = obj[0]

This uses the garbage collector (gc) to find the object that is used internally by Binary Ninja to bridge the core (which knows about the currently opened function and the other magic variables) and the built-in shell. Most importantly it already has the proper contents for all the magic variables as attributes.

The solution to the second problem is now fairly simple. The local variables are accessible as a dict in Python and can be updated like one. So the plugin has a function that has a (cached) reference to the ScriptingProvider and updates the local variables of the Python kernel and registers this as a pre_execute hook in IPython/Jupyter.

Because I found no way to do this when starting the kernel from the plugin this is currently implemented as an IPython/Jupyterconsole extension that has to be loaded once per kernel. There might be a way to load this extension via the IPython/Jupyter API as soon as the kernel is started, but I haven’t found one yet.

Other tricks

The last trick is not about getting the kernel running but about stopping it. If you run a command that will run forever, at least until the end of the universe or just longer than you planned you can’t interrupt it without some tricks. The attached IPython shell/JupyerConsole can’t raise an KeyboardInterrupt exception in the kernel thread.

ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(thread_obj.ident), ctypes.py_object(exception))

This is executed in the plugin context and uses ctypes to inject an exception (in our case the KeyboardInterrupt) into the IPython kernel thread. This can be triggered from the GUI. Credit again to Ryan for telling me about this.

The Plugin

Now for the most important part, installing and using it:

  1. Checkout the repo and link/move it to your binary ninja plugin folder ~/.binaryninja/plugins/
  2. Activate the virtualenv or python environment you want to use and run pip install ./binja-ipython. This installs the IPython extension
  3. Open Binary Ninja and open some binary
  4. Use the menu to run a QT console or attach with jupyter console --existing
    1. Properly installing the QT console might be more work, so test with the above command first
  5. run %load_ext binjamagic in this shell to get the variables
  6. Evaluating bv should now give you a reference to the currently opened binary view

Caveats

There are some limitations and known bugs:

  • only properly tested on Linux
    • in general it should be fairly platform agnostic
  • interrupting the kernel while nothing is executed crashes it

If you run into any other bugs, please report them as an issue on Github.

Conclusion

I hope this plugin provides useful to you or maybe even motivates you to try out Binary Ninja. For me the lack of a proper shell was what stopped me from exploring and leveraging the Binary Ninja API, so now I finally get to play around with it and can automate some reversing tasks.

Cheers

Florian Magin