Scripting r2 with pipes


Writing bindings is boring and tedious, mostly because 90% of the code is spaguetti and it requires an idiomatic translation in order to make them feel natural to the language.

First bindings in Radare were hand-made, but after realizing the crappy C interface provided by most prominent languages (perl, python, ..) to avoid the spaguetti I decided to write valabind; A program written in Vala that translates VAPI files into swig interface files (or ctypes, c++, d, …) that are compiled to create the bindings for a huge list of target languages (Ruby, Perl, Python, CSharp, Java, LUA, Guile, PHP, …)

Before language bindings appeared in radare-land, People used just pipes to get results from commands into files or other processes. Unix-like, but this have some limitations: unidirectional communication and forks.

The slow startup is not really problematic for launching scripts, in fact, using JSON and system pipes, tends to be way faster than using FFI with native code wrapping. This is because modern languages allow JSON serialization/deserialization to native objects, which turns to be the easiest way to import information from one process to another.

So, by cooking all this stuff together, I wrote the #!pipe command to spawn a new program from the shell exposing two pipes and described in two environment variables R2PIPE_IN and R2PIPE_OUT. One of those pipes is used to write r2 commands, and the other one to read the results, always NULL byte terminated.

This operation is guaranteed to be a synchronous, but as long as it’s provided on filedescriptors, it feels great for writing async programs in NodeJS that use or extend r2 functionality.

Your first script

Let’s make a simple program that runs inside r2 and shows some information from the functions in the analyzed binary that are bigger than 128 bytes.

import r2pipe
r2p=r2pipe.open()  # open without arguments only for #!pipe
r2p.cmd('aa;aac') # analyze all symbols and calls
for a in r2p.cmdj('aflj'):
if a['size'] > 128:
print('[+] Function '+a['name'])
print(r2p.cmd('pif@'+str(a['offset'])+'~call'))

To run this script just write this line in your terminal:

$ r2 -i fun.py /bin/ls

Or just from inside r2 with any of those two commands:

[0x00000000]> #!pipe python fun.py
[0x00000000]> . fun.py

As you may noticed, the r2pipe.open() receives no arguments here, so it will only work if executed from inside r2. If you want to run this script as a standalone program you’ll need to pass the filename to it:

$ grep open fun.py
r2p=r2pipe.open(sys.argv[1])
$ python fun.py
...

The same script can be rewritten in Javascript like this:

function analyzeFunctions(funcs) {
for (var f in funcs) {
if (f.size<=128) {
continue;
}
var c = 'pif@'+str(a.offset)+'~call';
r2p.cmd(c, function(calls) {
console.log('[+] Function '+f.name);
console.log(calls);
});
}
}
function doStuff(r2p) {
r2p.cmd("aa;aac", function() {
r2p.cmdj("aflj", analyzeFunctions);
});
}
require(“r2pipe”).rlangpipe(doStuff);

The magic of Javascript is that the same code will run in NodeJS, Duktape or any web browser. So you can launch r2.js scripts from the webui.

$ r2 -c=H /bin/ls
$ r2 -i foo.js /bin/ls
$ r2 -c '#!pipe node foo.js' /bin/ls
$ r2 -c '. foo.js' /bin/ls

If you are worried about the async nesting, don’t stress. Javascript provides some solutions to this problem, and r2pipe api is getting better and better in order to simplify writing Javascript for r2, this is:

  • Promises
  • Coroutines
  • Generators
  • Async (or any other helper in npm)

Some of those features come by default in ES6, which is also supported by r2pipe:

$ sudo npm install -g babel
$ r2 -i foo.es6 /bin/ls

Networking

As long as r2pipe is based on a simple concept of a bidirectional communication with the r2 prompt. The r2pipe APIs provide methods for connecting via TCP, HTTP or even Spawn.

This makes the scripts much more flexible because they are able to run several instances of r2 in parallel, in local or remote machines and manage all the information in JSON, opening the door to writing frontends in scripting languages without bindings problems or binary dependencies.

Future

The r2pipe concept is just the beginning. I have plans to make them even more awesome, increase performance and provide higher-level APIs, but probably the area that is still not covering is the plugins. At the moment of writing, r2 only supports native plugins, well. you can also write RAsm plugins in DuktapeJS using the r2plugin() builtin, But that’s just a Proof-of-Concept.

My plan for r2pipe in plugins consists in allowing external processes communicate with r2 via the plugin interface using pipes and serializing all the RPC with JSON. This may be easy to do and should work quite fine in theory, but requires some rethinking of the plugin model and this is not going to happen before 0.9.9.

Also, stderr, or error logs are not handled by r2pipe, only commands output is captured, so, there’s still room for improvement, but current state is by far the most stable and usable way to script Radare2 using *any* language.

Final words

If you are interested in using Radare2 in batch mode, or automatic static or dynamic applications, this is probably the best way to do it.

For more details you may want to read https://github.com/radare/radare2-bindings/tree/master/r2pipe or join the IRC (irc.freenode.net #radare) and drop us some lines.

— pancake

Like what you read? Give pancake a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.