Hooking C Functions at Runtime

July 24, 2015

 

This is a quick writeup on something I experimented with recently, runtime function hooking in C. The basic idea of hooking a function is to replace the function's code with your own code, so when the function is called your code is run instead. Hooking at runtime lets you change the way the program works when it's executed without having its code or actually modifying its file in any way. Runtime function hooking isn't uncommon, and is used for iOS jailbreak tweaks (powered by Cydia Substrate or Substitute) as well as programs using the Xposed framework on Android.

If you'd like to follow along with this post on your own computer, you'll need a Mac with Xcode and the Xcode command line tools installed. The code can be found here on Github.

 

The Sample Program

The sample program we'll be hooking into is very basic:

Compiling and running it gives the following output:

Our goal is to hook the function hookTargetFunction and change the number that's returned to something other than 5.

 

Hooking the Target Function

The way we'll hook the target function is by creating a dynamic library and loading it when the program is run. The dynamic library's constructor will be run before the main of the target executable, so we'll be able to modify the target executable in memory before it runs. To make our replacement code run, we'll insert machine code for a jump instruction to our function at the beginning of the function we're hooking. In other words, when the computer tries to run the target function it will instead jump to where our replacement function is located and run our code instead.

The first step of the process is to create a dynamic library containing a constructor and a replacement function.

When it's compiled and loaded with the target program using the DYLD_INSERT_LIBRARIES environment variable, we can see that its constructor runs before the main program.

Now we can begin putting code in the constructor to hook the target function. Since x86 jump instructions use relative addressing, we can't just give the computer an address in memory to jump to. We'll first need to find the offset of the replacement function from the target function, which can be done by getting pointers to each function then subtracting one from the other.

There are a few interesting things in this code sample. The first is using dlopen to get a pointer to our target executable. dlopen is normally used for loading shared libraries, but according to its documentation it can also be used to access the main executable if NULL is passed as the file name. The second thing to note is that jump offsets are actually taken from the address of the next instruction, which in this case is the address of the target function plus 5 bytes since the inserted jump instruction will be 5 bytes in size.

One minor step which I'm omitting from this writeup is making the memory of the target function writable, since it's only readable and executable by default for security reasons. Once that's complete, the final step is creating and inserting the jump instruction. The x86 opcode for an unconditional jump with an immediate offset is E9, so we'll put that as the first byte of the instruction followed by the offset.

Here is the finished inject.c file:

When it's compiled and run, it actually changes the output of the main program!

Here's another run with some debugging output, which shows the jump instruction inserted at the beginning of the target function:

 

Limitations

One limitation of this method of hooking is that it requires the target function to be at least 5 bytes in size for a jump instruction to be inserted. That may seem like a silly restriction, but it's definitely possible to create functions that small (a single byte ret instruction, for example). I can't think of a way around this, after all it's hard to do much in a single byte. The most straightforward solution is just to not hook functions smaller than 5 bytes.

Another problem I ran into was getting this code to work on Linux. For some reason, Linux consistently loaded the dynamic library at a high address, so high that the offset overflowed the 32 bits available to it. I don't think this is fixable while still using a jump instruction, since the maximum size an offset can be is 32 bits. However, the function could be hooked using another method - for example pushing the address of the replacement function onto the stack, then jumping to it with a ret instruction. This would take more space than simply using a jump, but it's the only idea I have at the moment.

I hope you enjoyed this writeup! Once again, feel free to download and test out the code on your own machine. It's much more fun when you try it yourself!