【问题标题】:ASM - Modified registers during "function call"ASM - “函数调用”期间修改的寄存器
【发布时间】:2014-09-01 12:36:13
【问题描述】:

我尝试挂钩一些函数,不管 x86 上是 __stdcall 还是 __cdecl。

我想做以下事情:
1. 保留堆栈
2. 保留去寄存器
3. 做我的事
4. 恢复寄存器
5. 恢复栈

我的堆栈没有问题,但我的寄存器备份确实有一些问题:如果不修改其中一些寄存器,我就无法备份寄存器(我不能使用堆栈,因为我无法以这种方式备份堆栈)!

我可以在堆上备份它们(我使用具有一些 .EAX 、 .EBX 等成员的结构),我从 ASM 访问该结构,这就是问题所在......我必须修改一些寄存器能够做到这一点!

然而,这是我的故事。我真正想了解的是以下问题的答案:

对于函数调用期间可以修改的寄存器和不修改的寄存器有什么“规则”吗?

我用调试器检查了一些函数调用。我在“调用 SomeFunction”处添加了一个断点,按 F8 键“跳过函数调用”并检查修改后的寄存器。我可以看到这样的东西: 1. ESP/EBP 可能会根据调用约定进行修改(cdecl vs stdcall) 2. EAX、EBX、EDX - 几乎总是被修改! 3. EBX、EDI、ESI 似乎总是被保留!

所以我的“伪解决方案”来了:如果我只保留那些寄存器(EBX、EDI、ESI)可以吗?我不会弄乱堆栈,所以 EBP 和 ESP 不是问题。但我必须修改一些寄存器(EAX、ECX、EDX)。

我对一些编译器优化有什么问题吗?是否可以通过修改那些“无辜”的寄存器来搞乱代码:EAX、ECX、EDX?

谢谢

【问题讨论】:

  • 保留堆栈是什么意思?你并没有真正保存它,只是在你完成后恢复SP。
  • 是的,我不修改 EBP,我修改但恢复 ESP。问题比我描述的要复杂。我只需要知道是否可以只保留这些寄存器:EBX、EDI、ESI。
  • @lonut 您必须保留所有寄存器。但我不明白你的问题,这听起来微不足道。
  • 一个函数看起来像这样:push param;调用函数;调用将下一个 EIP 推入堆栈并转到该函数。 1. 在那个函数上,我跳转到我的代码(仅 asm 函数,裸) 2. 我必须调用一些函数并跳转到另一个函数(回调) 3. 回调函数必须具有调用前的堆栈(相同的原型) 4.回调函数执行后我必须有未修改的寄存器和堆栈
  • @lonut 抱歉,我还没有完全理解。你想做一些定制的事情还是什么?你不只是遵循调用约定并保持简单吗?

标签: assembly call cpu-registers


【解决方案1】:

目前还不清楚为什么除了保存寄存器的标准技术之外还需要其他任何东西。通常的方案是:

    ; call site: push args into stack, move to registers as required for callee
          push  arg1
          push  arg2
          mov   reg, arg3
          call  subroutine
          lea   esp, sizeof(arg1)+sizeof(arg2)[esp]  ; pop arg1 and arg2
          ...

    subroutine:
          push  ebp

          push  reg1     ; save the registers that subroutine is documented to preserve
          ...
          push  regn     ; if you insist, save *all* the registers
          mov   ebp, esp ; now ebp points to stacked args/saved registers no matter what you do next
          ; note: we have not saved the ESP, but we do know how much we pushed on ESP
          ...
          body of subroutine, move ESP up and down
          ...
    subroutine_exit:
          mov    esp, ebp ; now points to saved registers
          pop    regn
          ...
          pop    reg1
          pop    ebp
          ret

【讨论】:

  • 我假设他正在做一个通用的钩子,但不知道参数占用了多少空间。
  • 我的代码示例并不关心他推送了多少参数,或者它们是否在寄存器中。
  • 但它也不是一个钩子,或者至少我不明白它是怎么回事。
  • 钩子只是一个看起来很有趣的子程序,它保留了寄存器。不得不这么说似乎很奇怪,但子例程只是保留了它们使用的寄存器。钩子也一样。
【解决方案2】:

是的,调用约定允许您破坏EAXECXEDX。我相信 microsoft c++ ECX 用于 this 指针。请注意,这仅适用于遵循约定的函数。如果您尝试挂钩一个可能已优化为不使用标准约定的内部函数,它将不起作用。

您可以将内容保存在数据部分,如果适用,请特别注意可重入性和并发性。

【讨论】:

    【解决方案3】:

    是的,我做了以下事情:
    1. 放置一个跳转而不是前 5 个函数字节(经典钩子)
    2. 跳转到我的纯 asm 函数
    3. 在我的函数中,我需要调用一些函数(memcpy - 恢复原始字节,FlushInstruction 缓存)并使用一个结构,所以这里我需要修改一些寄存器
    4. 我在堆栈上添加东西,但最后堆栈与“调用”时刻相同
    5. 我用正确的堆栈跳转到回调函数(jmp EAX)!我无法回到这里,因为在回调函数中我需要调用原始函数并返回“调用函数 + 5”(返回调用时放入堆栈的 EIP)。而且我必须用正确的堆栈跳转,因为参数在堆栈上,钩子是通用的,我不知道有多少参数!

    所以,在回调函数中我做我的事情,调用原始函数并恢复钩子。我需要的正是:我需要恢复寄存器,或者确保我不修改它们,但正如我所说,我必须这样做。

    目前我正在尝试避免使用 EBX、EDI 和 ESI。我希望它会起作用,并且我不会在优化方面遇到问题。如果不行,我会尝试在我的结构中备份它们,并在回调函数中恢复它们。

    谢谢大家!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-11
      • 1970-01-01
      • 1970-01-01
      • 2014-07-06
      • 1970-01-01
      • 1970-01-01
      • 2022-01-07
      • 1970-01-01
      相关资源
      最近更新 更多