Dangerous Detours, Part 1: Introduction
Detours is a library that allows you to hook arbitrary functions by rewriting machine code. While a description of the exact implementation approach can be found in the corresponding paper as well as in numerous other sources, the basic idea is as follows:
- In the to-be-hooked function, disassemble the first instructions until you have read at least 5 bytes. As instructions are variable length on x86, we may end up having to read more than 5 bytes to reach the next instruction boundary. Let the number of bytes read be n.
- Allocate n+5 bytes of memory which will make up the trampoline.
- Copy the n bytes from the to-be-hooked function to the trampoline, followed by a near jump to the to-be-hooked function + offset n (i.e. the first instruction after the instructions we copied)
- Now overwrite the first 5 bytes of the to-be-hooked function with a near jump to the hook-function
- The hook function may either return, so that the original function is never executed or instead jump to the trampoline
If we want to replace the to-be-hooked function, the execution flow is as follows:
- Entering to-be-hooked function
- Jump to hook function
- Hook function does its work
- Return to caller
The body of the to-be-hooked function is never executed.
Caller function
/ ^
/ |
v |
(via jmp |
in to-be-hooked |
function) |
| |
v | return
Hook --------+
function
If instead the hook function, after having done its work, wants the original function to execute, the execution flow is as follows:
- Entering to-be-hooked function
- Jump to hook function
- Hook function does its work
- Jump to trampoline
- Execute n bytes in trampoline
- Jump to original function + offset n
Original function runs and finally returns
Caller function / ^ / \ v \
(via jmp
in to-be-hooked To-be-hooked function) function | ^ v | Hook ————-Trampoline function
Example: Replacing a function
As an example, we ‘replace’ OriginalFunction by AlternateFunction. The source code is as follows (error checking omitted):
#include <stdio.h>
#include <tchar.h>
#include <windows.h>
#include <detours.h>
__declspec(noinline)
static void OriginalFunction( PCWSTR Arg1, LONG Arg2 )
{
wprintf( L"OriginalFunction(%s, %d)\n", Arg1, Arg2 );
}
__declspec(noinline)
static void AlternateFunction( PCWSTR Arg1, LONG Arg2 )
{
wprintf( L"AlternateFunction(%s, %d)\n", Arg1, Arg2 );
}
int wmain()
{
//
// Install hook.
//
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread() );
PVOID Func = ( PVOID ) OriginalFunction;
DetourAttach(
&Func,
AlternateFunction );
DetourTransactionCommit();
//
// Call (hooked) function.
//
OriginalFunction( L"Hello", 42 );
return 0;
}
The code is straightforward – we instruct Detours to hook OriginalFunction and call AlternateFunction instead. We do not make use of the trampoline. The output is:
AlternateFunction(Hello, 42)
Up to this point, everything works as advertised. And indeed there is little that can go wrong if we just want to ‘replace’ a function. If, however, we want the hook function eventually call the original function by making use of the trampoline, it gets more interesting, as we will see in Part 2.