Reimplement 3rd party non-virtual function

On Windows you can use hotpatching: https://jpassing.com/2011/05/03/windows-hotpatching-a-walkthrough/ .

Compile with /hotpatch. This will add a two-byte NOP to the beginning of every function, and a 6-byte nop (5 on 32-bit) before, allowing you to patch in a redirection. What you want to do is modify the two-byte nop at the beginning to jump back into the 6-byte nop block, which then can jump to your callback wrapper, which then calls your callback and then jumps back into the function proper. To implement it, add this to a C++ source file:

void pages_allow_all_access(void* range_begin, size_t range_size) {
    DWORD new_settings = PAGE_EXECUTE_READWRITE;
    DWORD old_settings;
    VirtualProtect(
        range_begin,
        range_size,
        new_settings,
        &old_settings
    );
}

void patch(void* patch_func, void* callback_wrapper) {
    char* patch_func_bytes = (char*)patch_func;
    char* callback_wrapper_bytes = (char*)callback_wrapper;
    
    pages_allow_all_access(patch_func_bytes - 6, 8);

    // jmp short -5 (to the jmp rel32 instruction)
    patch_func_bytes[0] = 0xEB;
    patch_func_bytes[1] = 0x100 - 0x7;
    // nop (probably won't be executed)
    patch_func_bytes[-6] = 0x90;
    // jmp rel32 to callback_wrapper
    patch_func_bytes[-5] = 0xE9;
    *(int32_t*)&patch_func_bytes[-4]
        = (int32_t)(callback_wrapper_bytes - patch_func_bytes);
}

The callback wrapper might need to be defined in an assembly file:

callback_wrapper:
    ; save registers
    pushad
    pushfd
    call QWORD PTR [callback]
    popfd
    popad
    jmp QWORD PTR [after_trampoline]

The symbols callback and after_trampoline should be exposed in a C++ file (so at global scope).

void* callback = &callback_func;
void* after_trampoline = (char*)&patch_func + 2;

Then call patch at the top of main or some other suitable initialization time and you're set.

Also, you may have to allow write permissions on the memory pages you're modifying (the ones that patch_func is in) using a VirtualProtect call: https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect . EDIT: I've added this code to the example above.

I may add ways of doing this on Linux or other Unixy systems later.

When you don't have a convenient set of NOPs to use in the function, hooking becomes more difficult, particularly on the x86 architecture, since instructions have widely varying lengths and so it's difficult to find out programmatically where an instruction ends so you can jump back to the next instruction. @ajm suggests this library: https://github.com/kubo/funchook for Linux & OSX. However, personally, when I don't have hotpatching, I usually use a debugger to find out a sequence of instructions in the patch target of length at least 9 bytes that I can replace. Then in the program I replace those instructions with a jump to absolute immediate 64-bit, using a technique similar to the above, but I also add those replaced instructions to be executed near the end of the callback wrapper. Avoid replacing call or jmp instructions as these are often relative to the instruction pointer, which will have a different value in the callback wrapper than in the original function.

Tags:

C++