Search code examples
c++visual-c++x86inline-assemblycalling-convention

Function hook crashes unless certain registers are used


so I'm trying to hook a function for a game but there is one small problem. If registers such as eax, ebx, ecx, and edx are interchangeable, how come the first code sample below is crashing the game process but the second code does not crash and works as intended?

// Crashes game process
void __declspec(naked) HOOK_UnfreezePlayer()
{
    __asm push eax

    if ( !state->player.frozen || !state->ready )
        __asm jmp hk_Disabled

    __asm
    {
        mov eax, g_dwBase_Addr
        mov ebx, [eax + LOCAL_PLAYER_INFO_OFFSET]
        add ebx, 0x4
        mov ecx, [ebx]
        add ecx, 0x40
        lea edx, [esi + 0x0C]
        cmp edx, ecx
        je hk_Return

        hk_Disabled:
        movss [esi + 0x0C], xmm0

        hk_Return:
        pop eax
        mov ecx, g_dwBase_Addr
        add ecx, RETURN_UnfreezePlayer
        jmp ecx
    }
}

// Works
void __declspec(naked) HOOK_UnfreezePlayer()
{
    __asm push eax

    if ( !state->player.frozen || !state->ready )
        __asm jmp hk_Disabled

    __asm
    {
        mov ecx, g_dwBase_Addr
        mov edx, [ecx + LOCAL_PLAYER_INFO_OFFSET]
        add edx, 0x4
        mov ebp, [edx]
        add ebp, 0x40
        lea ecx, [esi + 0x0C]
        cmp ecx, ebp
        je hk_Return

        hk_Disabled:
        movss [esi + 0x0C], xmm0

        hk_Return:
        pop eax
        mov ecx, g_dwBase_Addr
        add ecx, RETURN_UnfreezePlayer
        jmp ecx
    }
}

I think the crash might be caused by my assembly code overwriting important data in the registers eax, ebx, ecx, etc.. What if the game is storing an important value in eax for example and then that data is lost because my if statement is moving a struct pointer into eax? Is there a way to preserve the contents of these registers and restore them to their original value before returning?


Solution

  • Registers are certainly not interchangable when hooking an already compiled program, as the meaning of the individual registers is defined by the code of the hooked program and the location of the hook in that code. Therefore, you must examine the hooked code and the location of the hook in order to determine whether the hooked code relies on the contents of certain registers being preserved.

    With the push eax instruction at the start and the pop eax instruction at the end, you are already preserving the contents of the EAX register and restoring it afterwards. You can do the same with the EBX and EDX registers or simply use the PUSHAD/POPAD instructions to save all general-purpose registers. Depending on the location of the hook in the game, you may also have to preserve the EFLAGS register, which requires the PUSHFD/POPFD instructions.

    Saving and restoring the ECX register would not be so easy, as the hook is using that register to calculate the address to jump to after it is finished.

    However, since you say that the second code sample works while the first code sample causes the hooked program to crash, it is likely that the problem is only with the EBX register being modified. This is because the first code sample modifies the EBX register, whereas the second code sample does not.

    Therefore, the likely solution to your problem would be to preserve the EBX register the same way as the EAX register is being preserved. In order to do so, you simply have to add a push ebx instruction at the same location of the push eax instruction and to add a pop ebx instruction at the same location as the pop eax instruction. However, please note that due to the way the stack works, the push and pop instructions must be in reverse order, like this:

    Hook start:

    push eax
    push ebx
    

    Hook end:

    pop ebx
    pop eax