我知道提取所需信息的四种方法,各有优缺点。在你做任何事情之前,你需要知道你创建的任何解决方案都不能保证,事实上如果目标应用程序更新,它也不太可能继续工作。原因是在每种情况下,您都依赖于实现细节,而不是用于导出数据的预定义接口。
连接 GUI
第一种方法是按照您的建议挂钩 GUI。在这种情况下,您所做的只是从实际用户看到的内容中读取。这通常更容易,因为您正在挂钩明确定义的 WinAPI。一个危险是程序显示的内容与它应该表示的内部数据相比不一致或不完整。
通常,有两种常见的方式来执行 WinAPI 挂钩:
这种方法的一个警告是,它仅在目标使用或扩展 WinAPI 控件时才有效。
从 GUI 读取
除了挂钩 GUI,您还可以使用 WinAPI 直接从目标窗口读取。但是,在某些情况下,这可能是不允许的。在这种情况下没有什么可做的,只能尝试看看它是否有效。这实际上可能是最简单的方法。通常,您将发送WM_GETTEXT等消息来查询目标窗口当前显示的内容。为此,您需要获取包含您感兴趣的控件的确切窗口层次结构。例如,假设您要读取一个编辑控件,您需要查看窗口层次结构中它上方的父窗口为了获得它的窗口句柄。
从内存中读取(高级)
这种方法是迄今为止最复杂的,但如果您能够对目标程序进行完全逆向工程,则最有可能获得一致的数据。这种方法通过您从目标进程中读取内存来工作。这种技术在游戏黑客中非常常用,以添加“功能”并观察游戏的内部状态。
考虑到除了在 GUI 中存储信息之外,程序通常还拥有自己的所有数据的内部模型。当使用的控件是虚拟的并且只是查询要显示的数据的子集时尤其如此。这是前两种方法没有多大用处的情况的示例。这些数据通常保存在某种抽象数据类型中,例如列表,甚至可能是数组。诀窍是在内存中找到这个列表并直接读取值。这可以在外部使用ReadProcessMemory或通过 DLL 注入在内部完成。难点主要在于两个前提:
- 首先,您必须能够可靠地定位这些数据结构。这样做的问题是代码不能保证在同一个地方,尤其是像ASLR这样的特性。通俗地说,这有时被称为代码转换。ASLR 可以通过使用模块基址的偏移量并使用GetModuleHandle等函数动态获取模块基址来击败。与 ASLR 一样,发生这种情况的一个原因是动态内存分配(例如通过
malloc
)。在这种情况下,您需要找到一个存储指针的堆地址(例如返回malloc
),取消引用并找到您的列表。该指针将倾向于 ASLR,而不是指针,它可能是双指针、三指针等。
- 您面临的第二个问题是每个列表项都很少是原始类型。例如,您可能会遇到对象列表,而不是字符数组(字符串)列表。您需要进一步对每种对象类型进行逆向工程并了解内部布局(至少能够确定您感兴趣的原始值的偏移量,即它与对象基的偏移量)。更高级的方法围绕着对对象的vtable进行逆向工程并调用它们的“API”。
您可能会注意到我无法在此处提供具体的信息。原因在于,就其本质而言,使用此方法需要深入了解目标的内部结构,因此,细节仅由目标的编程方式定义。除非你有逆向工程的知识和经验,否则你不太可能想走这条路。
挂钩目标的内部 API(高级)
与上述解决方案一样,您无需挖掘数据结构,而是挖掘内部 API。我在前面讨论 vtables 时简要介绍了这一点。您将尝试查找在修改 GUI 时调用的内部 API,而不是这样做。通常,当修改视图/UI 时,程序将拥有自己的包装函数,而不是直接调用 WinAPI 来更新它,它会调用该函数,然后调用 WinAPI。你只需要找到这个函数并钩住它。这也是可能的,但需要逆向工程技能。您可能会发现您发现自己想调用的函数。在这种情况下,除了能够定位函数的位置外,您还必须对其采用的参数进行逆向工程,
我认为这种方法是先进的。它当然可以做到,并且是游戏黑客观察内部状态和操纵目标行为的另一种常用技术,但很难!
前两种方法非常适合从 WinAPI 程序读取数据,而且要容易得多。后两种方法允许更大的灵活性。通过足够的工作,您可以阅读目标封装的任何内容,但需要很多技巧。
另一个可能与您的案例相关或不相关的关注点是,如果目标都被更新,那么更新您的解决方案以使其工作有多容易。使用前两种方法,更有可能不需要进行任何更改或进行小的更改。使用后两种方法,即使源代码中的微小变化也可能导致您所依赖的偏移量的重定位。处理此问题的一种方法是使用字节签名来动态生成偏移量。前段时间我写了另一个答案,解决了这是如何完成的。
我所写的只是对可用于您想要实现的目标的各种技术的简要总结。我可能错过了方法,但这些是我所知道并有经验的最常见的方法。由于这些本身就是大主题,因此如果您想获得有关任何特定主题的更多详细信息,我建议您提出一个新问题。请注意,在我讨论过的所有方法中,它们都不会受到外界可见的任何交互的影响,因此您不会遇到任何弹出的问题。正如您所描述的,它将是“沉默的”。
这是关于绕行/蹦床的相关信息,这是我从我之前写的答案中提取的:
如果您正在寻找程序绕道执行其他进程的方法,通常是通过以下两种方式之一:
- 动态(运行时)绕行 - 这是更常见的方法,也是Microsoft Detours等库所使用的方法。这是一篇相关论文,其中函数的前几个字节被覆盖以无条件地分支到仪器。
- (静态)二进制重写 - 对于 rootkit,这是一种不太常见的方法,但被研究项目使用。它允许通过静态分析和覆盖二进制文件来执行绕行。执行此操作的旧的(非公开可用的)Windows 软件包是 Etch。本文给出了它在概念上如何工作的高级视图。
尽管 Detours 展示了一种动态迂回的方法,但业界使用的方法不计其数,尤其是在逆向工程和黑客领域。其中包括我上面提到的 IAT 和断点方法。要为这些“指明正确的方向”,您应该查看在研究项目和逆向工程领域进行的“研究”。