Hooking can be used as powerful introspection technique that redirects a function invocation to an attacker-controlled implementation. By doing this, an attacker can achieve a multitude of goals. It can be used to, for example:
A concrete example could be an app containing premium features. In those apps there must be logic to verify if a user is allowed access to those features. A malicious party could redirect that logic to a different implementation that always allow access.
Function hooking relies on abusing existing control flow redirections or in some cases adding new ones. On iOS this can take on the following forms:
__la_symbol_ptrsections in a Mach-O file. This hooking method only works for functions that are dynamically linked, for example, system library functions.
jmpinstruction) towards a new implementation. In other words, inline hooking in the assembly.
In this article, we’ll focus on abusing the language dynamic dispatching mechanism in Objective-C, where every method call goes through the Objective-C runtime. Visually this looks something like this:
Because of this, it’s possible to change the destination of function calls at
runtime. The language even offers this as a feature, generally referred to as
method swizzling. Objective-C offers several APIs to this end, for example
method_exchangeImplementation, etc… . The objective-C
available in the binary is instrumental in its dynamic dispatch. Reverse
engineers leverage this system to hook/swizzle Objective-C methods.
To prevent method swizzling this post demonstrates an LLVM based approach to
devirtualization for Objective-C function calls so that the runtime is circumvented for those “lowered” functions and swizzling has no effect.
To fully understand what devirtualizing means, we must first understand how methods in Objective-C are “virtual” to begin with. Let’s take the following code sample:
The output of this program would be:
When looking at the binary in a disassembler, we see that the function calls
don’t happen directly but happen through a kind of helper function called
So under the hood, the method call
[classA instanceMethodA: 1]; is actually
translated to the following
objc_msgSend(classA, @selector(instanceMethodA), 1);.
This means that for image 1 a more accurate representation would be:
So what is this
objc_msgSend? This is a regular C function that takes
For more details see: https://developer.apple.com/documentation/objectivec/1456712-objc_msgsend
In other words, the function is called indirectly by passing a message to the Objective-C runtime. Which performs a lookup to invoke the correct function with the provided parameters.
It’s because of this dispatching model that a feature called method swizzling is available in the Objective-C language. By offering an API that replaces the address in the runtime, it’s possible to dynamically change the implementation of a function at run time.
This is a powerful tool for a developer when implementng complex logic, but even more useful for a reverse engineer to swap out the implementation of security-sensitive functions.
Also note that since almost every Objective-C function call is translated to an
objc_msgSend call, by hooking
objc_msgSend itself a reverse engineer can
get an accurate trace of the program execution. This is another good reason to
avoid this indirection.
To show how this works in practice, let’s extend the main function of the
previous example with a call to
method_setImplementation that replaces the
instanceMethodA with that of
Now the output of this program is:
Note that the call to
[classA instanceMethodA: 1]; has been redirected
and doesn’t execute the original implementation anymore!
We can circumvent the runtime by skipping the
objc_msgSend call and directly
calling the actual function. Let’s have a look at the LLVM bitcode that is
generated for the code sample above. It can be compiled with the following
Use a tool like ebcutil to extract
the bitcode from the binary (you need to run llvm-dis on the extracted
bitcode to obtain human-readable
The bitcode looks like this:
In this bitcode snippet we can see two important things:
objc_msgSendcalls that were discussed earlier.
So, on a bitcode level the Objective-C functions are the same as a regular
C-style function. There’s, for example, no immediately obvious difference with
main function in this bitcode file, nor would there be a visible
difference with any other C function we might implement. This makes a lot of
sense, because Objective-C is built on top of the C language and extends it with
We can replace the indirect
objc_msgSend call with a direct function call to
"\01-[Class1 instanceMethod1:]" by replacing the following lines in the
This way the Objective-C runtime is removed from the equation and the call happens directly:
You can compile the human-readable
.ll file with
The output binary can be executed and the output is back to the original, as if the swizzling never even happened:
The method call is now devirtualized and the runtime is not involved in the function call anymore making it impossible to swizzle.
This was a manual demonstration of this technique. To perform method call devirtualization in an automated fashion there are two difficult hurdles to face:
objc_msgSendcall to a direct call, the callee must be predicted. In simple cases, like the bitcode above, it’s trivial to trace back the call to the original selector and class instance. But in real-world situations, this is a lot harder and there are many edge cases.
We’ve shown how we can protect applications against one of the more popular hooking techniques by devirtualizing Objective-C method calls. The observant reader will notice that by converting objc_msgSend calls to direct calls, the call graph can now be retrieved more easily, making it easier to find new hooking targets. While this is true, it must be said that most decent decompilers can create a call graph for objc_msgSend calls anyway. It’s also important to note that the other hooking techniques mentioned in the introduction require additional protection.
iXGuard provides effective protection against iOS application hooking.