Best way to inject functionality into a binary

Moe picture Moe · Nov 4, 2008 · Viewed 8.1k times · Source

What would be the best way of inserting functionality into a binary application (3d party, closed source).

The target application is on OSX and seems to have been compiled using gcc 3+. I can see the listing of functions implemented in the binary and have debugged and isolated one particular function which I would like to remotely call.

Specifically, I would like to call this function - let's call it void zoomByFactor(x,y) - when I receive certain data from a complex HIDevice.

I can easily modify or inject instructions into the binary file itself (ie. the patching does not need to occur only in RAM).

What would you recommend as a way of "nicely" doing this?

Edit:

I do indeed need to entire application. So I can't ditch it and use a library. (For those who need an ethical explanation: this is a proprietary piece of CAD software whose company website hasn't been updated since 2006. I have paid for this product (quite a lot of money for what it is, really) and have project data which I can not easily migrate away from it. The product suits me just fine as it is, but I want to use a new HID which I recently got. I've examined the internals of the application, and I'm fairly confident that I can call the correct function with the relevant data and get it to work properly).

Here's what I've done so far, and it is quite gheto.

I've already modified parts of the application through this process:

xxd -g 0 binary > binary.hex
cat binary.hex | awk 'substitute work' > modified.hex
xxd -r modified.hex > newbinary
chmod 777 newbinary

I'm doing this kind of jumping through hoops because the binary is almost 100 megs large.

The jist of what I'm thinking is that I'd jmp somewhere in the main application loop, launch a thread, and return to the main function.

Now, the questions are: where can I insert the new code? do I need to modify symbol tables? alternatively, how could I make a dylib load automatically so that the only "hacking" I need to do is inserting a call to a normally loaded dylib into the main function?

Answer

Moe picture Moe · Nov 8, 2008

For those interested in what I've ended up doing, here's a summary:

I've looked at several possibilities. They fall into runtime patching, and static binary file patching.

As far as file patching is concerned, I essentially tried two approaches:

  1. modifying the assembly in the code segments (__TEXT) of the binary.

  2. modifying the load commands in the mach header.

The first method requires there to be free space, or methods you can overwrite. It also suffers from extremely poor maintainability. Any new binaries will require hand patching them once again, especially if their source code has even slightly changed.

The second method was to try and add a LC_ LOAD_ DYLIB entry into the mach header. There aren't many mach-o editors out there, so it's hairy, but I actually modified the structures so that my entry was visible by otool -l. However, this didn't actually work as there was a dyld: bad external relocation length at runtime. I'm assuming I need to muck around with import tables etc. And this is way too much effort to get right without an editor.

Second path was to inject code at runtime. There isn't much out there to do this. Even for apps you have control over (ie. a child application you launch). Maybe there's a way to fork() and get the initialization process launched, but I never go that.

There is SIMBL, but this requires your app to be Cocoa because SIMBL will pose as a system wide InputManager and selectively load bundles. I dismissed this because my app was not Cocoa, and besides, I dislike system wide stuff.

Next up was mach_ inject and the mach_star project. There is also a newer project called PlugSuit hosted at google which seems to be nothing more than a thin wrapper around mach_inject.

Mach_inject provides an API to do what the name implies. I did find a problem in the code though. On 10.5.4, the mmap method in the mach_inject.c file requires there to be a MAP_ SHARED or'd with the MAP_READ or else the mmap will fail.

Aside from that, the whole thing actually works as advertised. I ended up using mach_ inject_ bundle to do what I had intended to do with the static addition of a DYLIB to the mach header: namely launching a new thread on module init that does its dirty business.

Anyways, I've made this a wiki. Feel free to add, correct or update information. There's practically no information available on this kind of work on OSX. The more info, the better.