撰写一个 VB6 P
背景
在这篇文章中,我们将讨论如何为 VB6 Pcode 编写一个除错器。自从我在2000年代初第一次看到 Mr Silver 和 Mr Snow 撰写的 WKTVBDE PCode Debugger 以来,这一直是我非常想做的事情。
当我第一次看到那个除错器时,感觉有一种神奇的魔力。那时我刚开始我的职业生涯,我热爱以 VB6 编程,并且反向分析对我来说是一门神秘的黑暗艺术。
在休假期间,我终于找到了时间静下心来深入研究这个主题。现在,我将分享我在这个过程中所发现的内容。
这篇文章将大量依赖于之前的论文《VB PCode 反组译》[1]。在这篇论文中,我们详细介绍了运行时如何处理 PCode 并在不同处理程序之间转移执行。
我们将针对这一执行流程来控制我们的除错器。
这篇论文中详述的除错器架构示例可以在免费的 vbdec Pcode 反组译器和除错器 中找到。
探索
当我开始研究这个主题时,我首先想检查在 WKTVBDE PCode Debugger 中运行的过程是什么样子的。
我将一个测试的 PCode 可执行文件与带有调试符号的 VB 运行时放在一起。这个可执行文件在 WKTVBDE 下启动,然后附加了一个原生的除错器。
检查 0x66106D14 的 PCode 函数指针表显示,所有指针都已经被修补到 WKTVBDEdll 中的同一个函数。
火烧云加速器官网这让我们首次获得了他们如何实现其除错器的线索。此时值得注意的是,WKTVBDE 除错器完全在被除错的过程中运行,包括 GUI!
要启动除错器,您需要运行 loaderexe 并指定目标可执行文件。然后,它会启动该过程并在其中注入 WKTVBDEdll。一旦加载,WKTVBDEdll 将用自己的函数钩住整个基础的 PCode 处理程序表,从而优先获取即将执行的任何 PCode。
除错器还包含:
一个 PCode 反组译器解析所有嵌套 VB 内部结构的能力列出所有代码对象和控制事件如计时器或按钮点击的能力这还包括正常的除错器 UI 操作,如数据转储、断点管理、堆栈显示等。
这是一段相当复杂的代码要运行作为注入的 DLL。调试这所有内容肯定涉及大量工作。
在大致了解了除错器的运作方式后,我开始在网上搜索我能找到的其他信息。我很高兴找到了一篇 Mr Silver 在 Woodmann 上的旧文章,我为了保存历史进行了镜像[3]。
在这篇文章中,Mr Silver 阐述了他们撰写 PCode 除错器的历史,并给出他们使用的钩子函数模板。这是一次非常有趣的阅读,为我提供了一个良好的起点。
设计考量
展望未来,我希望在这个架构中做出一些设计上的调整。
第一个改变是,我希望将所有结构解析、反组译引擎和用户界面代码移到一个独立的进程中。这些任务相当复杂,作为 DLL 注入 进行调试非常困难。
为了达成这一任务,我们需要一种简单易用、稳定的进程间通信IPC技术,并且本身是同步的。我在这个类别中最喜欢的技术是使用 Windows Message,这样外部进程在返回之前会自动等待窗口过程完成。
我已经广泛使用这项技术来延迟恶意软件在其自身解包后的行为[4]。我甚至将其与一个与 IDA 的远端实例接口的 Javascript 引擎联系起来[5]。
这种设计使我们能够自由撰写并调试文件格式解析、反组译引擎和用户界面代码,完全独立于除错器的核心。
此时,除错器的整合实质上成为了反组译器的附加功能。注入的 DLL 现在仅需拦截执行并与主界面进行通信。
在本论文的其余部分,我们将假设一个完全运作中的反组译器已经被创建,并仅专注于除错器特定的细节。
有关如何实现反组译器和结构解析的参考实现,请参考之前的论文[1]。
实现
现在拥有足够的资讯,该是开始尝试控制执行流程的时候了。
我们的第一个任务是弄清楚如何钩住 PCode 函数指针表。在我们可以钩住它之前,我们实际上首先需要找到它!这可以通过几种方式完成。从 WKTVBDE 作者的论文来看,他们主要进行了三个阶段的进展。首先,他们以手动修补的方式开始了 VB 运行时和在导入表中引用的修改过的 DLL。
其次,他们进展到一个单一的支持复本的运行时,并对要修补的硬编码偏移进行修补,然后加载器将调试器 DLL 注入到目标过程中。最后,他们增加了能够动态定位和修补表的能力,无论运行时版本如何。
这是一个不错的实验进展,他们详细介绍了这个过程。第二个阶段对于任何能理解这篇论文的人都是可轻易访问的,且效果也相当好。我将留给读者去探索注入和钩子细节。
基本步骤是:
将内存设置为可写复制原始函数指针表用自己的钩子程序替换原始处理程序已发布的示例还利用了自我修改代码,而我们会尽量避免这样做。为了避免这样的情况,我们将引入单独的钩子存根,每个表一个,以记录一些额外数据。
在进入单独的钩子存根之前,我们注意到他们在一个全局结构中存储了一些运行时/状态信息。我们将根据以下内容进行扩展:
从钩子代码中,您会注意到第一个表中的所有基本操作码不包括领导字节处理程序都收到了相同的钩子。每个结尾的 LeadX 字节则收到了自己的处理程序。
以下显示了前两个表的钩子处理程序示例,其他四个遵循相同的模式:
每个单独表的钩子配置了当前领导字节和表基础的全局 VM 结构字段。实现的实质部分现在从通用的钩子程序开始。
在主 PCodeHookProc 中,您会注意到我们调用了另一个函数:void NotifyUI()。
在这个函数中,我们会检查断点、处理单步执行等等。这个函数然后使用同步 IPC 与外部进程的除错器用户界面进行对话。
除错器 UI 将接收步进通知,然后进入等待循环,直到用户给出步进/继续/停止的命令。这将使被除错过程冻结,直到 SendMessage 处理返回。您可以在 SysAnalyzer ApiLogger 的源代码中找到这方面的范例实现[6]。
我们之所以要从 PCodeHookProc 调用另一个函数,是因为它是以裸函数的形式用组合语言编写的。一旦摆脱这一点,我们现在可以在 C 中轻松实现更复杂的逻辑。
进一步的步骤
一旦所有的钩子实现,您仍然需要一种方式来操控被除错程序。当代码远程冻结时,远端 GUI 仍然可以通过一个单独的 IPC 回道向冻结的过程发送新命令。
这样,您可以管理断点、变更步进模式,并通过运行时导出如 rtcTypeName 实现查找服务。
钩子 DLL 也可以修补自定义操作码。以下代码在未使用的 slot 0x01 中添加我们的单字节 NOP 指令。
如注释中所暗示的,当前操作码的实时修补以及“在此设置新原点”类型的功能都是可行的。这些是通过除错器直接对全局 VM 结构进行 WriteProcessMemory 调用来实现的。该结构的地址在启动时的初始化消息中透露。
结论
编写 PCode 除错器是一个非常有趣的概念。这是我个人想做的事情,已经快20年了。
当您近距离看到所有运作零件时,这并不像乍看之下那样令人生畏。
拥有一个运作中的 PCode 除错器也是学习 PCode 指令集如何实际运作的基础步骤。能够实时观看 VB6 Pcode 的执行,并结合堆叠差异检测和数据查看器工具,这是相当有启发性的。在这么高的粒度水平进行单步执行,让您更清楚地了解发生了什么。
尽管钩子代码本身在技术上具有挑战性,但在开始之前您就已经需要完成一些庞大的工作。
这些前提包括:
准确解析未知的文件格式一个稳健的反组译引擎,用于未知的 PCode 指令集一个方便的用户界面,以实现数据显示和除错器控制对于一名逆向工程师来说,这样的项目就像糖果一样。有如此多的方面可以分析和研究。如此多未记录的东西等待探索。这是一个由千千万万片段组成的谜题。
可以挤出什么能力?还有多少东西等待发现?
对我来说,这是一段相当吸引人的旅程,也让我更亲近我所喜爱的语言。希望这些文章能启发他人,引导他们探索的旅程。
[1] VB PCode 反组译[2] 带有符号的 VB6 运行时 (MD5 EEBEB73979D0AD3C74B248EBF1B6E770)[3] Mr Silver 的 VB P码信息[4] ApiLogger 突破恶意软件[5] IDA JScript[6] SysAnalyzer ApiLogger 冻结远程进程
标签:除错器、PCode、研究、系列、VB
分享:XFacebook