Splice Hooking for Unix-Like Systems
Hooking is a powerful programming technique for monitoring software behavior or extending functionality without altering the original code. The idea is to intercept certain events or system calls and use them to initiate your own custom code.
At Apriorit, we use hooking all the time when creating solutions for our clients, particularly in the areas of cybersecurity, data acquisition, and systems control. As part of our development efforts for Unix-like systems, we’ve created a custom solution for hooking based on the Linux splice technique (you can find decent description and splice Linux example here). We want to share it with you in this brief article.
Custom splice hooking technique for Unix-like systems
Our custom hooking technique for Unix-like systems was inspired by the popular Mhook library, the source code for which can be found on GitHub. This library is powerful for hooking, but supports only Windows platforms and includes surplus modules that aren’t acceptable for *nix.
To implement hooking in Unix, we took ideas from Mhook and modified them, increasing the flexibility and functionality of our tool in the process.
Splicing algorithm for *nix kernel
The general splicing hooking algorithm for *nix kernel hooks can be described as follows:
Store the memory from the required function pointer to the memory buffer (the size of stored memory should be equal to or greater than the jump instruction size). To get the identifier for a specific function in Linux, for example, you can use kprobes internals.
Rewrite the required function pointer with a jump instruction that contains a pointer to the hook function (“hook call” in Figure 1).
This stored buffer will be used for the original call, allowing to restore original call on the module removal or pause.
Executing an original call
Typically, we use the original function’s wrapping when replacing a function with a hook. But if an original call needs to be executed inside the hook or anywhere else, the algorithm’s approach should be modified to handle a larger buffer size.
After the original function pointer, analyze the memory via the disassembler. As a result of this analysis, you should retrieve the aligned instructions offset (the offset should be greater than the size of the jump instruction).
The memory buffer must have an executable flag (PAGE_KERNEL_EXEC). The size of the memory buffer should be calculated based on two parts: the calculated offset and the size of the jump back instruction to the memory from the original call, which stays after the inserted jump (“original call” in Figure 1).
Finally, the original function can be called by casting the stored buffer to the function’s signature and executing it.
This algorithm is visualized in Figure 1 for an x64 platform.
Advantages of this technique
Compared to existing solutions, this approach provides several advantages in terms of functionality:
Allows hooking of all available symbols in the kernel – this is especially useful if the kernel module in question works with internal kernel functions
Less noticeable to malware detectors – hooking can be used to create malware, and thus many Windows, macOS and Linux rootkit detection solutions often flag them as harmful, even if they are used legitimately. Since it’s impossible to test your hooks with all anti-malware software on the market, hooking techniques that don’t prompt false positives are all the more valuable.
Disadvantages of this technique
When we talk about disadvantages of this approach, we’re not talking about any flaws or limitations in terms of functionality, but rather about difficulties that arise in actual implementation. In this regard, there are two main disadvantages:
This technique requires a reliable disassembler, since libraries aren’t acceptable for *nix kernels.
This technique is architecture dependent, since each architecture has its own jump instructions.
We actively use the Unix splice hooking approach described above in projects we create for our clients here at Apriorit, particularly in the area of cybersecurity. We’ve implemented this hook type for a variety of architectures and kernel versions, including x86_64, x86, and ARM in Linux 2.6.32 to 4.10.
We hope that you find this approach useful and that you’ll be able to use some of the ideas presented in this article for your own hooking needs.