如何将一个方法作为回调函数传递给Windows API调用(跟进)?

4

这篇文章是关于一个有关问题的后续,由Ran在此发表。

被接受的答案坚持使用通常的普通函数。

这段摘录特别引起了我的注意:

实例方法有一个额外的隐式参数,包含实例引用,即Self。

我坚信应该有一种方法来使用一种"参数"适配器(重新排除不需要的Self隐式引用并提供指向符合适应的回调函数的指针),最终我找到了这篇文章,题为"Callback a class",作者是 Peter Morris

总之,他使用thunking技术作为适配技巧。(免责声明:我从未测试过这个代码)。

我知道这不是一个非常干净的解决方案,但它允许OO设计并享有所有所谓的好处。

我的问题:

如果像Peter Morris那样做是正确的途径,那么基于回调函数签名的TCallbackThunk将会是上述问题的答案吗?

.


我想补充一下,一些API回调可以通过lParam实现解决方法,但有些则不行(例如:图示中的WinNLS)。此外,这应该被称为StdcallThunk或其他类似的名称。 - OnTheFly
这不会起作用,因为生成的代码不会驻留在可执行页面中。你可以解决这个问题。但是,那样你将得到只能在x86上工作且在x64上失败的代码。 - David Heffernan
@David Heffernan:谢谢。我的计算机是x86架构的,任何在Delphi 5、7、2007、XE上运行的代码都可以。我必须编辑这篇文章。 - menjaraz
你需要使用VirtualAlloc来分配可执行页面。不要做任何愚蠢的事情,使你的堆栈可执行!但是,实际上,我无法理解为什么这种方法比标准函数更好。 - David Heffernan
导致图示代码失败的原因是 DEP。 - OnTheFly
1个回答

3
你实际上不需要做那么多工作,因为EnumWindows(引用问题中的函数)提供了一个数据参数。你可以在那里放置任何想要的值,例如答案中演示的对象引用。Morris的技术更适用于不提供任何通用数据参数的回调函数。
要使答案适应使用Morris的代码,首先需要确保回调方法的签名与API的回调函数的签名匹配。由于我们调用的是EnumWindows,所以我们需要一个返回Bool的两个参数函数。调用约定需要是stdcall(因为Morris的代码假定它,并且很难 thunk 任何其他调用约定)。
function TAutoClickOKThread.cbEnumWindowsClickOK(
  Wnd: HWnd; Param: LParam): Bool; stdcall;
begin
  // ...
end;

接下来,我们使用所有机器代码和跳转偏移量设置了 TCallbackThunk 数据结构,以便引用所需的回调方法。
然而,我们不使用 Morris 描述的方式。他的代码将数据结构放在堆栈上。这意味着我们正在将 可执行代码 放在 堆栈 上。现代处理器和操作系统不再允许这样做 - 操作系统将停止您的程序。我们可以通过调用 VirtualProtect 来修改当前堆栈页面的权限,从而允许其被执行,但这将使整个页面变得可执行,我们不想为攻击留下程序漏洞。相反,我们将为 thunk 记录分配一块内存块,与堆栈分开。
procedure TAutoClickOKThread.Execute;
var
  Callback: PCallbackThunk;
begin
  Callback := VirtualAlloc(nil, SizeOf(Callback^),
    Mem_Commit, Page_Execute_ReadWrite);
  try
    Callback.POPEDX := $5A;
    Callback.MOVEAX := $B8;
    Callback.SelfPtr := Self;
    Callback.PUSHEAX := $50;
    Callback.PUSHEDX := $52;
    Callback.JMP := $E9;
    Callback.JmpOffset := Integer(@TAutoClickOKThread.cbEnumWindowsClickOK)
      - Integer(@Callback.JMP) - 5;

    EnumWindows(Callback, 0);
  finally
    VirtualFree(Callback);
  end;
end;

请注意,该记录中的指令为32位x86指令。我不知道相应的x86_64指令是什么。

+1:非常有教益!在接受答案之前,我只想确定两件事,考虑到Morris的代码已经过时。1)指针调整仍然有效吗(VMT/TObject在版本之间发生了变化)2)硬编码的ASM指令也是如此。我必须承认我对ASM和BASM知之甚少。谢谢。 - menjaraz
我现在清楚地看到为什么进行代码注入可能会危及安全。 - menjaraz
指针调整是为了补偿JMP指令的大小。 JMP指令中的偏移量是相对于下一个指令的(因为EIP在地址计算之前被提前)。这与VMT大小或对象无关。X86指令没有改变。 - Rob Kennedy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接