如何将一个方法作为回调传递给Windows API调用(跟进)? [英] How to pass a method as callback to a Windows API call (Follow-up)?

查看:156
本文介绍了如何将一个方法作为回调传递给Windows API调用(跟进)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

本文是对相关问题 Ran 发布。



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



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


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


坚定地认为应该有一种方式使用的参数适配器(以重新编译除去不需要的自隐式引用,并提供一个指针,以适应适应的回调函数),我最终发现这回调类的a1411-callback-a-class.htmlrel =nofollow peter-morris.htmlrel =nofollow> Peter Morris



总之,他使用thunking技术作为适应技巧。 (免责声明:我从来没有测试过代码)。



我知道它不是一个解决方案很干净,但它允许OO设计所有的益处。 b
$ b

我的问题:



了解TCallbackThunk 函数签名,如果像Peter Morris所做的那样,如何去做,上述引用的帖子的答案是什么?



解决方案

你不需要经过所有的工作,因为 EnumWindows 问题)提供数据参数。你可以把任何你想要的值,例如在答案中演示的对象引用。 Morris的技术更适合不提供任何通用数据参数的回调函数。



为了使答案适应Morris的代码,您首先需要请确保回调方法的签名与API的回调函数的签名匹配。因为我们调用 EnumWindows ,我们需要一个返回Bool的双参数函数。调用约定需要是stdcall(因为Morris的代码假设它,并且很难折腾任何其他调用约定)。

  function TAutoClickOKThread.cbEnumWindowsClickOK(
Wnd:HWnd; Param:LParam):Bool; stdcall;
begin
// ...
结束;

接下来,我们设置 TCallbackThunk 数据结构,所有的机器码和跳转偏移指向预期的回调方法。



但是,我们不使用Morris描述的方法。他的代码将数据结构放在堆栈上。这意味着我们将可执行代码放入堆栈。现代处理器和操作系统不允许这样做 - 操作系统将暂停您的程序。我们可以通过调用 VirtualProtect 来修改当前堆栈页面的权限,允许它被执行,但是这使得整个页面可执行,我们可以避开想要离开程序打开攻击。相反,我们将分配一个内存块,特别是对于thunk记录,与堆栈分开。

  procedure TAutoClickOKThread.Execute; 
var
回调:PCallbackThunk;
begin
回调:= 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指令是什么。


This post is a follow-up of a related question posted here by Ran.

The accepted answer sticks to the use of the usual a plain old function.

This excerpt particularly catch my attention:

An instance method has an extra, implicit, parameter containing the instance reference, i.e. Self.

With the firm conviction that there should be a way to use a kind of "parameters" adapter (to rephrase get rid of the uneeded Self implicit reference and provide a pointer to a complying adapted callback function), I end up finding this article entitled Callback a class by Peter Morris.

To sum up, he uses thunking technique as adaptation trick. (Disclaimer: I never tested the code).

I know it's not very clean as a solution but it allows OO design with all the supposed benefits.

My Question:

Knowing that TCallbackThunk is based on the callback function signature, what would be the answer of the above refered post if doing it as Peter Morris did is the way to go?

.

解决方案

You don't really need to go through all that work since EnumWindows (the function in the referenced question) provides a data parameter. You can put whatever value you want there, such as the object reference demonstrated in the answer. Morris's technique is better suited for callback functions that don't provide any general-purpose data parameter.

To adapt the answer to use Morris's code, you'll first need to make sure the signature of the callback method matches the signature of the API's callback function. Since we're calling EnumWindows, we need a two-argument function returning Bool. The calling convention needs to be stdcall (because Morris's code assumes it, and it's difficult to thunk any other calling convention).

function TAutoClickOKThread.cbEnumWindowsClickOK(
  Wnd: HWnd; Param: LParam): Bool; stdcall;
begin
  // ...
end;

Next, we set up the TCallbackThunk data structure with all the machine code and the jump offset referring to the intended callback method.

However, we don't use the way Morris described. His code puts the data structure on the stack. That means we're putting executable code on the stack. Modern processors and operating systems don't allow that anymore — the OS will halt your program. We could get around that by calling VirtualProtect to modify the permissions of the current stack page, allowing it to be executed, but that makes the whole page executable, and we don't want to leave the program open for attack. Instead, we'll allocate a block of memory especially for the thunk record, separate from the stack.

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;

Note that those are 32-bit x86 instructions in that record. I have no idea what the corresponding x86_64 instructions would be.

这篇关于如何将一个方法作为回调传递给Windows API调用(跟进)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆