分析一个实模式二进制文件,比如 DOS 可执行文件或者 MBR 代码从来不是一个简单的任务。目前最好的办法是先用 Bochs, QEMU 或者 Dosbox 模拟运行,然后再使用 gdb, debug.exe 或者 IDA Pro来远程调试。但是这都是在没有麒麟框架(Qiling Framework)的情况下。现在我们想骄傲的宣布,麒麟框架正式支持 16bit 二进制的模拟运行,并且与之伴随的还有麒麟框架多维度分析二进制文件的能力。
本文在介绍实模式模拟实现细节的同时也会作为一篇教程来介绍麒麟框架的使用。
本文同样也发表于我的博客。
麒麟框架基于 Unicorn 引擎,所以在介绍麒麟之前,我想先介绍一下 Unicorn 引擎。Unicorn 是一个以 QEMU 部分代码为基础实现的一个纯粹的 CPU 模拟器。与 QEMU 相比,Unicorn 有很多亮眼的特点:
但是正如之前所说,Unicorn是一个纯粹的CPU模拟器,它不知道自己上层运行的操作系统是什么,所以这里就有了麒麟框架。
有时候,我自己都不知道怎么解释麒麟到底是什么。 ---- Lau Kaijern,麒麟的作者
确实,很难用一句话来定义麒麟。为了更全面的描述麒麟,我想从麒麟四个核心功能来说明:模拟、分析、调试、扩展。
麒麟最基础的功能就是模拟,但是和 Unicorn 的模拟有所不同。刚才提到过,Unicorn 是一个 CPU 模拟器,而麒麟实际上是一个操作系统模拟器。下面是一个简单的图来说明一个原生应用和一个模拟应用的联系。
通常来说,原生应用在一个特定的操作系统环境上运行,这个操作系统会提供各种各样的 API,而操作系统运行在一个 CPU 之上。对于一个用麒麟模拟的应用,架构是非常相似的:Unicorn 扮演了 CPU 模拟器的角色,麒麟来完成操作系统的任务执行目标二进制文件。为了实现这些,麒麟分为了三层。
接下来用 DOS 和 HI.COM作为例子来讲解这三层是如何相互配合来模拟操作系统的。
对于 arch
层,我们可以假设 CPU 是 8086
,它设置起来比较简单,只需要设置好寄存器即可。
COM 文件就只是一个内存镜像,没有任何文件头。因此 loader
层的实现极其简单:分配足够的内存,把整个文件写入到内存,设置好 PC 和栈即可。
对于 os
层,麒麟要做的事情就是尽可能多的实现中断。下面是 HI.COM
的源代码:
当 Unicorn 执行到 INT 21
的时候,它并不知道这个中断的具体含义,因此它会调用我们在 os
层中定义的 hook 函数,下面就是 os
层相应的实现:
这就是麒麟为了模拟 COM 执行文件所有的工作。其实还是蛮简单的,不是吗?
实际上,为了模拟一个 16bit 二进制文件,我大多数的工作都是合理的实现中断。比如 Petya 是一个 MBR 病毒,它大量的使用了 BIOS 提供的 INT 10
图像服务。在现代终端上一个对应的实现是 curses
所以我花了很多时间来把过去的图像中断翻译成了 curses
的调用。以 INT10, 0
为例子。
英文有句名言是 "Simple is better"。现在,相比以前的屏幕,终端已经变得越来越复杂了,结果实现一个简单的屏幕反而变得更难了,因为有太多的 corner cases。
有心的读者可能已经注意到了,我们这里的模拟其实和真正的 DOS 系统行为并不完全一样。没错,麒麟的模拟就是这样的。完美和快速的模拟从来不是麒麟的首要目标。麒麟关注的是为用户在二进制分析时提供最大的便利。我马上会谈到这一点。
顺带一提,麒麟从设计之初就是平台独立的。这意味着用户可以在 Windows 上运行一个 Linux ELF,在 Mac OS 上运行一个 Windows exe。然而对于 QEMU 来说这是不可能的,因为它需要把系统调用转发到主机上。
麒麟远远不止是一个模拟器。它同样提供了大量的API。
让我们以 fs_mapper
为例(因为我刚重构过 XD):
目标二进制文件 x86_fetch_urandom
的源代码为:
这个二进制文件就简单的从 Linux 系统下的伪随机字节流 /dev/urandom
获取一个字节。然而,如果使用了 ql.add_fs_mapper("/dev/urandom", Fake_urandom())
我们就可以劫持这次读取过程并且返回一个固定的字节。
除了设备文件,fs_mapper
还可以模拟一个完整的磁盘。下面是模拟 Petya 的代码:
0x80
是实模式下磁盘的索引。对于 Linux 和 Windows,这个值可以是 /dev/sda
或者 \\.\PHYSICALDRIVE0
。QlDisk
是一个类,可以把一个文件模拟成磁盘,比如磁头、柱面和扇区的支持等等。之所以要模拟一个侧畔,是因为 Petya 使用了 BIOS 提供的 INT 13
磁盘服务来直接读写硬盘。为了实现这些中断,把一个文件映射成模拟环境下的磁盘是有必要的。
另一个例子是系统调用劫持。下面是文档里的一个例子。
因为麒麟自己就是整个 OS 环境,所以把系统调用暴露给用户是非常简单就可以实现的。
快照可以让用户保存或者恢复一个程序执行的上下文,包括 CPU 寄存器,内存内容甚至是文件描述符。配合部分执行的API,快照可以让用户完全控制一个程序,随意的测试一个程序内的任何一段代码。下面是分析 Petya 的例子:
在上面的代码中,我们首先准备好了堆栈,然后保存了一次快照。之后我们把解密的 key 写入到函数参数然后直接调用了验证函数。如果结果表明 key 是错误的,那么我们直接恢复之前保存的快照然后再次尝试。
限于篇幅,我无法在这里介绍麒麟所有的功能。如果有兴趣可以参考麒麟的文档。总而言之,正是这些强大的 API 让麒麟成为了一个真正的动态打桩分析框架,让麒麟区别于其他的模拟器。
我们知道,QEMU 是支持 gdb 远程调试的,那么麒麟怎么能不支持这么好的 feature 呢?
在写下这篇文章的时候,麒麟的调试功能仍然在紧张开发中,但是 16bit 调试已经完成了。简单的增加一行 ql.debugger=True
然后连接 gdb 到 127.0.0.1:9999
即可,下面是一张截图。
目前,麒麟正在把一些逻辑解耦成为单独的扩展。其中我们最近的一个工作是 IDA 插件,下面是相应的截图:
哦,等一下,我知道大家都是命令行高手,这样的插件真的有意义吗?
当然,正是有了 IDA 插件,至少你不需要再买一份 IDA Linux 授权,因为你可以在 Windows/Mac OS 上运行 Linux ELF,不是吗?
在麒麟的 qiling/extensions directory 目录下还有很多有意思的插件,有兴趣可以查看相应的 REAME 或者文档。
麒麟框架是一个非常有野心的框架。我们的终极目标是写出一个像瑞士军刀一样的分析框架,解除各种限制,比如因为平台或者系统不同导致的壁垒,为广大的逆向工程师们提供最好的体验。没有什么能阻止我们进一步的二进制分析!
然而,麒麟框架现在仍然不完美。欢迎大家给我们一个 star 以示鼓励并且加入到我们的开发中来!我们期待合并你的第一个 PR!
项目地址为:https://github.com/qilingframework/qiling
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2020-9-6 11:51
被澪同学编辑
,原因: