4

我有一个调用 GetOpenFileNameA 和 GetSaveFileNameA 的旧应用程序。两个调用都是错误的。应用程序崩溃!我使用 OllyDbg 和 API Monitor 来读取存储在 OPENFILENAME 结构中的大小。结构的大小为 76 字节(使用 Windows 7 x64 进行测试)。调用 GetOpenFileNameA 或 GetSaveFileNameA 时出现访问冲突异常。我假设在运行时 Windows 尝试读取 88 字节而不是 76 字节。看看这个: http ://dotnetbutchering.blogspot.de/2007/10/vc-60-getting-0xc0000005-access.html 和这个 http://www.asmcommunity.net/board/index.php?topic =5768.15

我进行了一些研究,在此过程中我检测到以下行为:运行 Microsoft Spy++ 时,应用程序不会崩溃!我逐步通过调试器,我看到访问冲突异常仍然发生,但不知何故该异常被吞没了。该应用程序运行良好!我可以加载和保存文件。

我有以下想法。你觉得他们怎么样?

  1. 写。就像一个 Loader.exe,它和 Spy++ 一样。调用两个 API 时吞下访问冲突异常。

  2. 使用 DLL 注入和 API 挂钩。我可以将 GetOpenFileName 和 GetSaveFileName 与自定义 DLL 中的自定义实现挂钩。我的实现将修复结构并将更正后的结构传递给原始 API 调用。

  3. 使用 SetWindowsHook 挂钩窗口消息?!?!?!

  4. 修补二进制文件。是否可以通过使用 HEX 编辑器进行修补来解决此结构大小问题?

哪一个会起作用?你知道我该如何解决这个问题吗?

我无法获得这个旧应用程序的源代码。我必须使用现有的二进制文件来修复它。我的解决方案必须至少在 Windows XP 和 Windows 7(x86、x64)上运行

工具 PEiD 向我显示了有关旧应用程序的以下信息:链接器信息:2.55 MS Visual C++ 4.0

4

2 回答 2

1

结构的大小为 76 字节(使用 Windows 7 x64 进行测试)。调用 GetOpenFileNameA 或 GetSaveFileNameA 时出现访问冲突异常。我假设在运行时 Windows 尝试读取 88 字节而不是 76 字节。

如果你看一下OPENFILENAME struct你会注意到:

#if (_WIN32_WINNT >= 0x0500)
  void *        pvReserved;
  DWORD         dwReserved;
  DWORD         FlagsEx;
#endif // (_WIN32_WINNT >= 0x0500)

这在 32 位程序(VC++ 4 不支持 64 位目标)中转换为正好 12 个字节的差异。只要lStructSize调用者正确设置,这根本不应该是一个问题。procdump从 Microsoft/Sysinternals 获取确切状态的小型转储(或附加调试器并进行调查)可能是值得的。您遇到的异常不一定是由于struct大小。如果是这样,微软更有可能在此功能的向后兼容性方面失手。显然OPENFILENAME::lStructSize有版本控制struct和确保你遇到的事情不会发生。但是,我们谈论的是使用 Windows 2000 之前的编译器/链接器构建的程序。

写。就像一个 Loader.exe,它和 Spy++ 一样。调用两个 API 时吞下访问冲突异常。这是一个公平的观点。如果您要在顶层插入异常处理,您可以做您想做的事情,但它可能会导致副作用,具体取决于导致异常的确切原因(即覆盖了确切的内存)。

使用 DLL 注入和 API 挂钩。我可以将 GetOpenFileName 和 GetSaveFileName 与自定义 DLL 中的自定义实现挂钩。我的实现将修复结构并将更正后的结构传递给原始 API 调用。这和第一个有很大关系。我认为这将是最简单和最安全的,因为这样你就可以在没有太多干扰的情况下纠正行为。请进一步阅读下文。另外,请查看 NInjectLib。

使用 SetWindowsHook 挂钩窗口消息?!?!?!除了促进 DLL 的注入(对于 1. 和 2.)之外,我看不出这有什么帮助。

修补二进制文件。是否可以通过使用 HEX 编辑器进行修补来解决此结构大小问题?这可能是最棘手的,取决于它OPENFILENAME是在二进制文件中(初始化数据)还是在堆栈中,或者它是否在堆上分配(那么容易)。


1. 和 2. 的一种可能的混合方法是:

  1. 添加一个以HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options您正在执行的程序命名的子键(例如foo.exe
  2. 在新创建的子键中创建一个REG_SZ名为Debugger的值,并将该值设置为我现在将尝试简要描述的程序。

这有效地为您的这个旧应用程序设置了一个调试器,这意味着我们要编写的调试器将接收您应用程序的命令行作为参数。它很方便,因为它对最终用户是透明的,您可以根据自己的需要进行调整。

您需要编写一个调试器。这个任务并不像一开始看起来那样令人讨厌,因为您可以使用 Win32 提供的调试助手。要点在调试器循环中。通常,您使用CreateProcess传递适当的标志来自己创建目标进程以便能够对其进行调试。使用WaitForDebugEventContinueDebugEvent控制执行。出于所有实际目的,您甚至可能根本不需要调试器循环,因为您可以创建挂起的目标应用程序的主线程(传递CREATE_SUSPENDEDCreateProcess),然后CONTEXT一开始就将主线程指向您自己的代码,然后调用ResumeThread(pi.hThread). 这样,您将在主线程启动之前完成。但是,这可能会导致问题由于方式kernel32.dllCreateThread工作(这涉及使用 Win32 子系统 aka 注册新线程csrss.exe)。因此,建议改为在内存中修补目标的 IAT 或类似的东西。毕竟你只对两个功能感兴趣。

查看此处此处的两篇文章,以更详细地了解该主题。

我更喜欢基于 PaiMei 编写我的调试器PyDbg我承认我没有尝试在Image File Execution Options.

于 2012-05-24T16:03:56.063 回答
1

(1) 将是纯 hack,很难做到(Spy++ 行为的哪个方面?或者你想重新发明完整的 Syp++?),即使你这样做了,你怎么能确保应用程序能正常工作(对于所有输入)在“吞下异常”之后?程序的内部状态可能是未定义的,并在以后导致其他问题。

(2) 假设您没有资源,因此您无法以正常方式修复它,恕我直言,这似乎是解决该问题的最佳方法。

(3) 我看不出这对你有什么帮助。

(4) 可能,但可能需要做很多工作。假设堆栈上有一些数据,然后通过调整其中一个(OPENFILENAMEA结构)的大小,您可以移动其他数据的偏移量,因此您必须“修复”对这些数据的引用。

于 2012-05-24T14:44:38.770 回答