mach-detours/docs/using.md
2025-10-04 00:20:05 +02:00

3.5 KiB

Using mach-detours

Two things are necessary in order to detour a target function: a target pointer containing the address of the target function and a detour function. For proper interception the target function, detour function, and the target pointer must have exactly the same call signature.

The code fragment in Figure 5 illustrates the usage of the mach-detours library. User code must include the mach_detours.h header file and link with the mach_detours library.


#include <assert.h>
#include <string.h>

#include <mach_detours.h>

char* (*real_strerror)(int errno) = strerror;

static int counter = 0;

char* my_strerror(const int errno)
{
    counter++;
    return real_strerror(errno);
}

int main(int argc, const char* argv[])
{
    counter = 0;
    strerror(0);
    assert(counter == 0);

    detour_transaction_begin();
    detour_attach((detour_func_t*)&real_strerror, (detour_func_t)my_strerror);
    detour_transaction_commit();

    assert(counter == 0);
    strerror(0);
    assert(counter == 1);

    detour_transaction_begin();
    detour_detach((detour_func_t*)&real_strerror, (detour_func_t)my_strerror);
    detour_transaction_commit();

    assert(counter == 1);
    strerror(0);
    assert(counter == 1);

    return 0;
}
Figure 5. Simple example detour to modify the strerror API.

Interception of the target function is enabled by invoking the detour_attach API within a detour transaction. A detour transaction is marked by calls to the detour_transaction_begin API and the detour_transaction_commit API. The detour_attach API takes two arguments: the address of the target pointer and the pointer to the detour function. The target function is not given as an argument because it must already be stored in the target pointer.

In a multi-threaded application, detour_manage_thread API enlists threads in the transaction so that they are suspended while the transaction is being set up and so that their instruction pointers are appropriately updated when the transaction commits.

The detour_attach API allocates and prepares a trampoline for calling the target function. When the detour transaction commits, the target function and trampoline are rewritten and the target pointer is updated to point to the trampoline function.

Once a target function has been detoured, any call to the target function will be re-routed through the detour function. It is the responsibility of the detour function to copy arguments when invoking the target function through the trampoline. This is intuitive as the target function becomes simply a subroutine callable by the detour function.

Interception of a target function is removed by calling the detour_detach API within a detour transaction. Like the detour_attach API, the detour_detach API takes two arguments: the address of the target pointer and the pointer to the detour function. When the detour transaction commits, the target function is rewritten and restored to its original code, the trampoline function is deleted, and the target pointer is restored to point to the original target function.

In cases where detour functions need to inserted into an existing application without source code access, the detour functions should be packaged in a dylib. The dylib can be inserted into a new process using DYLD_INSERT_LIBRARIES or the mach task port API.