从堆栈中,我们看到有一个显示适配器设置属性表。我们正在破坏它,并且在尝试验证窗口过程地址时崩溃了。
我们前段时间看到可以通过检查来找出错误的地址。
作为移位源的寄存器是 rax ,所以这就是函数指针。从寄存器转储中,我们看到地址是
是的,该地址看起来不像一个有效的函数指针。
在 64 位系统上,用户模式指针具有低地址(以 0000 开头),而内核模式指针具有高地址(以 ffff 开头)。所以这个函数指针对于用户态显然是无效的。
也许我们可以修复它,使其再次有效。我们来看看这个过程中哪些代码地址是有效的。
我怀疑函数指针被截断为 32 位值,然后符号扩展回 64 位值。因此,我们正在寻找 xxxxxxxx`924bbde0 形式的有效函数指针。在上面的有效代码地址列表中,唯一具有 92xxxxxx 范围内低位的地址的高 32 位都是 00007fff ,所以让我们将其插入并看看我们是否获得了一个窗口过程。
所以调用者可能子类化了一个窗口,然后尝试恢复原始的窗口过程,但搞砸了并且只恢复了底部的 32 位。
但那会是谁呢?
这是一个属性表,因此我们应该能够提取属性表的页面。 (注意:需要内部 Microsoft 符号,因此您无法在家中执行此操作。)
桌面后台控制面板是可扩展的,插件向桌面后台控制面板添加页面的方式是处理 IShellPropSheetExt::AddPages 方法并使用 HPROPSHEETPAGE 调用提供的“页面添加函数”。该函数的作用是将 HPROPSHEETPAGE 添加到属性表中的页面。 (我们可以看到 rPages 中有 100 个空间。)
psh 是 PROPSHEETHEADER 。
我们看到有四个页面,因此我们可以检查 rPages 中的前四个 HPROPSHEETPAGE 。
嘿,看,我们有一个 HPROPSHEETPAGE 结构数组
HPROPSHEETPAGE 是一个不透明的结构,但我们可以转储它并寻找有趣的东西,仅供娱乐。
这里有一堆 HMODULE,它们可能是属性表页面来自的模块。前三个随 Windows 一起提供。最后一个显然是 Contoso。让我们重点关注最后一个。
在前两个值(看起来像指针)之后,我们有 0x00000068,这不是巧合的 sizeof(PROPSHEETPAGE) ,所以我猜测这是系统存储创建句柄的 PROPSHEETPAGE 的位置。
注意:请注意,这是一个实现细节,只能用于调试目的。请不要编写依赖于此的程序,因为它可能会发生变化。
对话过程为 0x00000001`800047ac 。我希望我能够对它进行足够的逆向工程,以查看它错误地对按钮进行子类化的地方。
我们知道 WM_INITDIALOG 消息的 lParam 参数是作为“参数”传递给 CreateDialogParam 等函数的值,特别是对于属性表,它是一个指向 PROPSHEETPAGE 的指针。我们从上面的结构转储中看到,偏移量 0x30 是 lParam 。
从这个函数的结构可以清楚地看出,神奇值 -21 是 GWLP_USERDATA ,神秘函数 1 是 SetWindowLongPtr ,神秘函数 2 是 GetWindowLongPtr 。这是对话框函数的标准模式,通常使用包装函数。
真正的对话过程是第三个神秘函数,让我们看一下。
初始寄存器溢出并保存后,它检查消息是否为 0x2B : WM_DRAWITEM 。这对我们来说并不是特别有趣,所以我们假设不是。
哦,WM_DESTROY 消息很有趣。它可能会在其 WM_DESTROY 处理程序中恢复原始窗口过程,这就是我们希望找到截断的地方。
收到 WM_DESTROY 消息后,代码首先从 this 指针中获取一些内容(我们在序言中看到该指针保存在 rdi 中),然后从全局变量加载一些其他内容。
接下来,它调用神秘函数00000001`8002b4e0,并使用0x668作为第二个参数。不确定那是什么,但我们会记住它。
接下来,我们设置另一个函数调用,我们识别出这个函数:00000001`8002b4a0 是 SetWindowLongPtr 的导入地址表条目。我们在静态对话程序中看到了它。
参数是从神秘函数 4 获得的窗口句柄、常量 -12 以及我们从 00000001`80039c50 加载的 32 位值。神秘函数 4 可能是 GetDlgItem 。由于我们发现被调用的函数是 SetWindowLongPtr ,因此值 -12 是 GWLP_WNDPROC 。
所设置的值是第三个参数,由 movsxd dword ptr 加载,这是一个 32 位到 64 位符号扩展加载。这是一个问题,因为窗口过程是 64 位值。
我敢打赌他们加载的值不正确。
嘿,看,这是我们本来应该使用的完整 64 位指针,只不过我们弄乱并截断了指针。
C++源代码大概是这样的:
强制转换为 LONG 就是进行截断和符号扩展。它应该是 LONG_PTR 的强制转换。
在查看处理器指令编码文档后,我们可以将其修补到二进制文件中。
原来的指令是
文档说 movxsd r64, r/m32 的编码是“REX.W + 63 /r”。
我们想要的是 mov rbx, [00000001`80039c50] ,文档说 mov r64, r/m64 的编码是“REX.W + 8B /r”。
因此,让我们将 63 修补为 8b 。
这实际上是一个单字节错误修复。
下次,我们将推测这个错误是如何出现的。
奖励阅读:诱饵控制面板。
1 早在 1990 年代末,我们就发现了一个程序,它对 Windows 95 属性表管理器的内部数据结构进行了逆向工程,它不是传递由 CreatePropertySheetPage 函数创建的 HPROPSHEETPAGE,而是创建了在内存中手动构造的假 HPROPSHEETPAGE。这使得添加对 Unicode 属性表的支持变得更加困难,因为 HPROPSHEETPAGE 的内部结构发生了变化,以便支持 ANSI 和 Unicode 属性表页面,并且它们正在传递旧版本。属性表管理器必须认识到它正在获得一个假的 HPROPSHEETPAGE,并即时将其转换为真实的 HPROPSHEETPAGE。
Raymond 参与 Windows 的发展已超过 30 年。 2003 年,他创建了一个名为“The Old New Thing”的网站,该网站的受欢迎程度远远超出了他最疯狂的想象,这一发展至今仍让他心惊胆战。该网站催生了一本书,巧合的是,它的标题也是《旧的新事物》(Addison Wesley 2007)。他偶尔会出现在 Windows 开发文档 Twitter 帐户上,讲述一些没有传达任何有用信息的故事。