I’ve had a quick look into the OpenBinder ioctls zinging around within the Android emulator. Here’s a summary of what I’ve done, and the results I’ve had (such as they are… it’s not been too fruitful).
First, the context. We know that Android IPC uses a system called OpenBinder, thanks both to Benno’s pioneering investigations, and the fact that one of the OpenBinder developers kindly posted a load of information on the web! But, we also know that the user-side OpenBinder is not based on the existing MPL version, or Google would have had to release the code already. (For this reason a lot of my terminology is probably wrong – it looks like perhaps the MPL version is OpenBinder, whilst we’ve got some other type of Binder, perhaps a not-quite-Open-yet-Binder).
In fact, the only code we’ve got relating to the OpenBinder in use within Android are the code for the module, provided within the Android Linux kernel. That includes two header files – include/linux/binder_module.h being the important one – and about 10 files code for the kernel-side driver.
According to the OpenBinder website, the kernel driver is responsible for reference-counting as well as the pure data-copying aspect of IPC. So, with any luck, the kernel-side code will have to understand the data structures passed back and forth, and therefore so can we.
Anyway, this is what I did.
- First, I modified strace to output some information about OpenBinder. I only bothered with the BINDER_WRITE_READ ioctl, and essentially just printed out the payload in binary, for the moment. My changes are here – sorry it’s not a nice easy patch, but all the makefiles etc. got changed too and I didn’t want to include them in the patch. strace-4.5.15-diffs
- I built this using my Scratchbox as per my previous instructions. You’ll need to run automake first to rebuild the makefiles to include the extra source code file.
- I then followed Benno’s instructions to boot Android without the automatic startup of Zygote and the runtime. I ran my modified strace in exactly the same way as Benno to get the output I wanted.
Enough! What are the results? Well… not very spectacular yet.
Here’s a typical ioctl:
ioctl(7, BINDER_WRITE_READ, {write_size=8,write_consumed=8,write_buffer=0x14eb0,read_size=256, read_consumed=44,read_buffer=0x14da0, write_data={0x4,0xc1,0xb,0x0,0x84,0xc1,0xb,0x0,}, read_data={0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xb9,0xfc,0x1,0x0,0xe,0x0,0x0,0x0,0xb1,0xfc,0x1,0x0, 0xe,0x0,0x0,0x0,0xa9,0xfc,0x1,0x0,0xe,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0, 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,}) = 0
My thoughts about that (in no particular order):
- I don’t like the fact that the write_buffer and read-buffer addresses are so small. I am not at all familiar with the Linux memory map, though, so maybe Linux process memory really does start all the way down there. And, their type is signed long. (In standard OpenBinder it’s void*, according to the documentation). Comments suggest that this strange type is used to allow for mixed 32/64-bit systems, but I still slightly worry that these parameters aren’t addresses at all, but could be offsets into some shared mmapped area or something.
- However, each time I tried to fetch the data at this address, it worked. So this makes me think they’re real addresses.
- I think these data areas should contain lists of commands and replies, respectively. The commands and replies are variable-length, so without some analysis it’s hard to work out second and subsequent commands/replies. The first byte is an opcode from BinderDriverCommandProtocol. I’m encouraged that the first byte is never 0×0, which would be bcNOOP, and is small enough to be likely to be an enumeration. So again, I’m happy these are the command streams.
- But I’m surprised we’re seeing so much null data. I’d also hoped that we’d see some strings related to the objects being bound to. But perhaps they’re pointed to by pointers within the data stream (certainly, the main ‘transaction’ command usually contains pointers to the real data).
- Maybe the read_data is usually blank; the client always allocates 256 bytes for a reply. Quite often the first opcode in the reply is 0xe, no-op, which might back that up. Although I’m not clear how the no-op operation causes the rest of the buffer to be ignored.
The code within the kernel module does parse and try to understand this data, and it does match the understanding above.
My overall conclusion: this approach does work, and it’s just a matter of getting time to implementing code within strace fully to parse the Binder command stream. I reckon we should be able to get as far as tracking individual object lifetimes, and what method are called on which objects. I reckon we should be able to get the names of the objects (as that’s part of the binding) but I haven’t yet worked out how binder specifies the methods which are called… it’s probably by ordinal into the interface rather than by name, so perhaps we won’t be able to work out what methods are being called. We’ll see, though. It would be great if we could see what methods were being called by one process on another.
You might wonder why I stopped here… mostly because I ran out of time, but also because I planned to do the analysis using Perl from this point onwards. But I think we’re going to need to access pointers to other bits of memory, so strace is probably the way to go.
(It might be easier just to rebuild the kernel with binder logging turned on, but I haven’t yet tried rebuilding the kernel for Android).
Here is an archive of the strace logs retrieved. Logs
I hope I will eventually get around to implementing much more code within strace to dig into what Binder is up to, although I reckon Benno’s bound to get there first :-)