Hooking By Example
A series of increasingly complex programs demonstrating function hooking on 64 bit Windows.
I wrote these programs while teaching myself how function hooking works. They may be helpful for other folks trying to do the same thing (or future me after I forget how all this works again). They are intended to be looked at in order. The first time a function is used in a program, it will be included in the .cpp file for that specific example program, with its name prefixed by an underscore. Any subsequent examples that use the same function will use the copy of that function included in hooking_common.h, to minimize code duplication and keep the later example programs small enough to still be easily readable.
I've done a bit of work to clean them up, but the later examples are still a bit messy. It's important to note that the final result in this repo isn't enough to write a fully featured hooking library, but it's enough to get started down that path.
At runtime, some projects may rely on other projects being built (injecting a dll requires that dll has been built), or running at the same time (hooking a target program requires it be already running).
All examples were built using Visual Studio 2019 (v142) with Windows SDK 10.0.17763.0. I don't think there's anything here that's VS or SDK version dependent, but I'm listing it here just in case. There are almost certainly some things that are MSVC specific.
Finally, the last trampoline example installs a hook in mspaint. I assume at some point in the future, an update to mspaint will cause this example to break. At the time of writing, the current version of mspaint was 1909 (OS Build 18363.1016).
The examples are divided into two categories: those that use trampolines, and those that don't. The non-trampoline examples exist solely to demonstrate redirecting program flow from one function to another in different situations. Building trampolines is complicated, and when I was trying to figure out how function hooking worked, it was immensely helpful to start out by building the non-trampoline examples first. Additionally, there are 4 "target programs" which are used by examples that want to demonstrate how to install hooks in different (already running) processes.
Most of these examples leak memory related to the hooks. I don't really care, both because these examples are just to demonstrate a hooking concept, and because these "leaked" allocs need to exist until program termination anyway.
While there doesn't appear to be much in the way of standard terminology for function hooking techniques, the code (and readmes) in this repository use the following terms:
- Target Function: The function being hooked
- Payload Function: The function which will be called instead of the target function once the hook is installed
- Relay Function: A function containing a 64 bit absolute jump instruction, used to redirect program flow from the target to the payload in 64 bit applications
- Trampoline Function: A function, called by the Payload function, that executes the logic that the target function contained BEFORE any hooks were installed. (this is a simplified explanation)
- Stolen Bytes: the instruction bytes in the target function that are overwritten when a hook is installed in that function
Since these examples don't create trampolines when installing their hooks, I think of these functions as demonstrating "destructive" hooking, in that the original function is completely unusable after being hooked.
A small example of overwriting the starting bytes of a function with a jump instruction that redirects program flow to a different function within the same program. Since there is no trampoline being constructed, this operation is destructive, and the original function is no longer callable. This is the only 32 bit example in the repository.
The 64 bit version of the previous example. In 64 bit applications, functions can be located far enough away in memory to not be reachable via a 32 bit relative jump instruction. Since there is no 64 bit relative jump instruction, this program first creates a "relay" function, which contains bytes for an absolute jmp instruction that can reach anywhere in memory (and jumps to the payload func). The 32 bit jump that gets installed in the target function jumps to this relay function, instead of immediately to the payload.
Provides an example of using the techniques from the previous project to hook a member function, rather than a free function.
Slightly different from the previous examples, this program shows how to install a hook into a virtual member function by getting the address of that function through an object's vtable. No other examples deal with virtual functions, but I thought it was interesting enough to include here.
The simplest example of installing a hook into another running process. This example uses the DbgHelp library to locate a function in a target processs (A - Target With Free Function) by string name. This is only possible because the target program is built with debug symbols enabled. While simple, this example is a bit longer than previous programs because of the large number of new functions it introduces (for locating and manipulating a remote process).
This example shows how to hook a function that another process has imported from a dll. There's some nuance to how to get the address of a dll function in a remote process due to how ASLR works, which is demonstrated here. Otherwise, this example is almost identical to the previous one.
This example shows how to install a hook in a function that is not imported by a dll, and which is not in the symbol table (likely because the remote process does not have debug symbols). This means there's no (easy) way to find the target function by string name. Instead, this example assumes that you've used a disassembler like x64dbg to get the relative virtual address (RVA) of the funtion you want to hook. This program uses that RVA to install a hook.
Similar to the above, except this example uses dll injection to install the payload function rather than writing raw machine code bytes. This is much easier to work with, since your payloads can be written in C++ again. The payload for this example is contained in the project 08B-DLL-Payload.
The following examples install trampolines when hooking, meaning that the program can still execute the logic in the target function after a hook has been installed. Since installing a hook overwrites at least the first 5 bytes in the target function, the instructions contained in these 5 bytes are moved to the trampoline function. Thus, calling the trampoline function effectively executes the original logic of the target function.
The trampoline-installing equivalent of example #2. This example is a bit weird because I wanted to demonstrate creating a trampoline without needing to use a disassembly engine. In this case, the target function was created to have a known 5 byte instruction at the beginning, so we can just copy the first five bytes of that function to the trampoline function. This means creating the trampoline is really easy, since we know it's exact size and that it doesn't use any relative addressing that needs to be fixed up. If you were writing a trampoline for a really specific use case, you could probably get away with just doing a variation on this.
This example shows a similar scenario to the previous one, except this time I'm using a disassembler (capstone) to get the bytes we need to steal out of the target function. This allows the hooking code to be used on any function, not just ones that we know are going to be easy cases. There's actually a whole lot going on in this example, because it's jumping from a targetted hook (like the previous one) to building a generic hooking function. The trampoline has to convert relative calls/jumps into instructions that use absolute addresses, which complicates things further. This isn't a 100% polished example of generic hooking either, it will fail with loop instructions, and if you try to hook functions with fewer than 5 bytes of instructions.
Basically the same as the above, except this example includes code to pause all executing threads while it installs a hook. This isn't guaranteed to be threadsafe in all cases, but it's definitely a lot more safe than doing nothing.
This expands on the hooking/trampoline code used in the previous two examples to support having multiple functions redirect to the same payload, and to allow payload functions to call other functions with hooks installed in them.
This is the first trampoline example that installs a hook in a different process (in this case, the target app B - Target with Free Functions From DLL). All the hooking logic is contained in a dll payload 13B - Trampoline Imported Func DLL Payload. There's not much new here, this example just combines the trampoline hooking stuff already done with the previously shown techniques for hooking a function imported from a dll.
The crown jewel of the repo. This example injects a dll payload (14B - Trampoline Hook MSPaint Payload) into a running instance of mspaint (you have to launch mspaint yourself before running this). The installed hook causes brushes to draw as red, no matter what color you've actually selected in MSPaint. There's honestly nothing here that wasn't shown in the previous example, it's just cool to see this working on a non-contrived program.
Simple target application that calls a free function in a loop. Compiled with debug information included.
Target application that calls a free function that has been imported from a dll (B2 - GetNum-DLL) in a loop.
Target application that calls a non virtual member function in a loop.
Target application that calls a virtual member function in a loop.