通过绕行特定地址获取寄存器值[Windows上的x86程序集]

Posted

技术标签:

【中文标题】通过绕行特定地址获取寄存器值[Windows上的x86程序集]【英文标题】:Get register value by detouring specific address [x86 assembly on Windows] 【发布时间】:2014-07-22 19:41:30 【问题描述】:

我要做的是在 x86 asm 中的给定地址处截取寄存器值并将其放入变量中。为此,我将一个 dll 注入到我的程序中,这里是:

#include <Windows.h>
#include <stdio.h>

void test()

    int randomNum;
    DWORD address = 0x088AD6D; // return address (used only with JMP)

    __asm
    
        //rewriting what I erased with my detour
        movsx eax, byte ptr ds:[esi+4]
        push ebp
        push eax

        // moving ecx to my variable
        mov randomNum, ecx
    

    printf("number: %d\n", randomNum); // This correctly print the value I detoured


    // this part is only used if I use JMP instead of call
    __asm
    
        JMP address // return to where the program should have been
    




void DetourAddress(void* funcPtr, void* hook)


    // write jmp
    BYTE cmd[5] =
     
        0xE9, //jmp
        0x00, 0x00, 0x00, 0x00  //address
    ;

    /*
    // write call
    BYTE cmd[5] =
    
        0xE8, // call
        0x00, 0x00, 0x00, 0x00 // our function address
    ;
    */

    DWORD dwProtect;

    VirtualProtect(funcPtr, 5, PAGE_EXECUTE_READWRITE, &dwProtect); // make memory writable
    DWORD offset = ((DWORD)hook - (DWORD)funcPtr - 5);  //((to)-(from)-5)
    memcpy(&cmd[1], &offset, 4); // write address into jmp
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)funcPtr, cmd, 5, 0); // write jmp / call
    VirtualProtect(funcPtr, 5, dwProtect, NULL); // reprotect


BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved  )

    switch (ul_reason_for_call)
    
    case DLL_PROCESS_ATTACH:
        AllocConsole();
        freopen("CONOUT$", "w", stdout);

        DetourAddress((void*)0x088AD68, (void*)&test);

        break;
    case DLL_PROCESS_DETACH:
        FreeConsole();
        break;
    

    return TRUE;

DetourAddress 函数是所有魔法应该发生的地方,我在我想绕道到我的 test() 函数的地址处编写了一个 jmp 命令。测试函数复制了被擦除的代码,我将 ecx 值移动到我的变量 randomNum 中。然后,如果我不绕道,我会跳回到代码应该在的位置。从理论上讲,它可以正常工作,但问题是在我的 test() 函数期间,寄存器值发生了变化,当我跳转回原始代码时,我的程序不仅停止按预期工作,而且很快就崩溃了因为访问冲突...

对于那些视觉化的人,以下是 ollydbg 中发生的事情:

第1步(原代码):

第二步,我绕行代码后的样子:

第三步,ollydbg中的test()函数:

第四步,jmp回到原代码:

如您所见,当我们返回到原始代码时,寄存器的值会完全改变,这会导致各种问题......我正在寻找一种方法来防止这种情况发生。

我发现这个问题 (C++ mid-function hook: get register values and jump back [x86 assembly on windows]) 与我的相似(甚至相同...),但给出的答案对我没有帮助。我尝试使用 call 命令而不是 jmp 不仅寄存器值不断变化,而且还导致另一个问题:当我从 test() 函数返回时,它返回到非可读内存而不是原始函数...

请帮忙?

【问题讨论】:

所以问题似乎源于您在绕道途中调用 printf 的事实。调用函数时,有些寄存器必须被调用函数保留,有些可以被覆盖。这些规则因使用的调用约定而异。也许您可以对值执行其他操作(将其写入缓冲区?)或推送/弹出在 printf 之前/之后更改的寄存器。 【参考方案1】:

而是将调用挂起一些指令,这将允许您正确保留寄存器,同时避免任何内联汇编。构建传递和捕获看起来像(假设它什么都不返回):

typedef void (__stdcall * hookfn)(const char* str, DWORD dw1, DWORD dw2, DWORD dw3, DWORD dw4);

void __stdcall Intercept(const char* str, DWORD dw1, DWORD dw2, DWORD dw3, DWORD dw4)

    randomNum = dw1;
    printf("%d\n",randomNum);

    hookfn fn = (hookfn)((DWORD)BaseOfTargetModule + RVAOfCall);
    fn(str,dw1,dw2,dw3,dw4);

您将使用相同的修补机制来覆盖相对调用地址。通过使用 Base+RVA 而不是固定的虚拟地址来考虑重定位也是一个好主意。

如果你仍然想要你的钩子,你需要保留所有的寄存器,这很容易在进入时使用 PUSHAD 和在退出时使用 POPAD 完成(这些保存/恢复所有寄存器都会禁止标志)。此外,您应该只执行您在跳回之前重写的指令(但在POPAD 之后),否则它们可能会导致您的钩子可能正在执行的任何问题。

【讨论】:

以上是关于通过绕行特定地址获取寄存器值[Windows上的x86程序集]的主要内容,如果未能解决你的问题,请参考以下文章

如何从 windows phone 中的纬度和经度值获取地址?

linux入门汇编

GDB使用——pwn相关

关于修改虚拟表绕行的问题

Windows 逆向OD 调试器工具 ( 分析 OD 硬件断点处的关键代码 | 添加硬件断点 | 关键代码 | MOV 指令 | EAX 寄存器值分析 | 使用命令查看 esi+0cc 地址 )(代码

如何获取特定类型(按钮/文本框)的Windows窗体表单的所有子控件?