-
-
[翻译]fuzzer开发4:快照、代码覆盖率与模糊测试
-
发表于: 2024-9-8 14:14 3077
-
这是关于一个快照模糊测试器开发过程的系列博文的下一篇,旨在利用 Bochs 作为目标执行引擎。你可以在 Lucid 仓库中找到模糊测试器及其代码。
在之前的文章中,我们实现了足够的 Linux 仿真逻辑,使得 Lucid 能够运行一个 -static-pie
的 Bochs 直到其启动菜单。自那之后的几个月里,我们取得了很多进展。我们现在已经实现了快照、代码覆盖反馈以及更多的 Linux 仿真逻辑,现在我们实际上可以开始模糊测试了!因此,在这篇文章中,我们将回顾一些代码库中新添加的主要功能,并提供一些关于如何设置模糊测试器进行模糊测试的示例。
这个模糊测试器设计的一个关键优势(感谢 Brandon Falk)是,被模拟或目标系统的整个状态完全由 Bochs 封装。其吸引力在于,如果我们能够可靠地记录和重置 Bochs 的状态,我们就可以默认获得目标快照。未来,当我们的目标程序影响设备状态时(例如模糊测试一个网络服务),这将对我们有很大帮助。那么现在的问题是,在 Linux 上,我们如何完美地记录和重置进程的状态?
我想到的解决方案,我认为非常美观。我们需要重置 Bochs 中的以下状态:
首先,动态内存应该比较容易记录,因为我们在模糊测试器的系统调用仿真代码中自己处理了所有对 mmap
的调用。因此,我们可以很容易地通过这种方式对 MMU 状态进行快照。这也适用于文件表,因为我们以相同的方式控制所有文件 I/O。不过目前,我还没有实现文件表快照,因为在我用于开发的模糊测试工具中,Bochs 不会触及任何文件。当前,我的解决办法是将文件标记为“脏”,如果我们在模糊测试时访问了这些文件,我会在那时让系统崩溃。以后,我们应该能够像处理 MMU 一样处理文件快照。
扩展的 CPU 状态可以通过机器指令保存。
然而,我一直在思考的问题是如何记录和重置 PT_LOAD 段。我们无法很好地跟踪这些页面在 Linux 用户空间中的修改,因为它们是在本机发生的。不过,在模糊测试领域,如果你想差异性地恢复这些页面,有一些常见的解决方法:
不过,最终为了简化起见,我决定每次都重置所有的可写段。
真正的问题是,Bochs 的动态内存分配可能非常庞大,因为它会分配堆内存来保存被模拟的来宾内存(即你的目标系统)。因此,如果你配置了一个具有 2GB 内存的来宾虚拟机,Bochs 将尝试进行一个 2GB 的堆分配。这使得捕获和恢复快照变得非常昂贵,因为每次模糊测试迭代都要进行 2GB 的 memcpy
操作,这将非常耗费成本。因此,我需要找到一种避免这种情况的方法。Bochs 确实有内存访问钩子,因此我可以通过这种方式跟踪来宾中的脏内存。如果我们发现当前的实现成为性能瓶颈,这可能是未来的一个实现方向。
根据我目前对 Lucid 项目的设计哲学,即我们可以为了更好的内省或架构/实现的简单性而牺牲性能,我决定有一个不错的解决方案可以利用,因为我们是将 Bochs 映射到内存中的,而不是由内核处理。只要 ELF 映像的可加载段是按顺序加载的,并且可写段最后加载,这意味着我们开始了一个需要重置的内存块。此时,你可以将内存中的映射视为这样的结构:
这对我们来说很有利,因为我们现在实际上有了一块需要在每次模糊测试迭代时恢复的连续的可写内存块。Bochs 会影响的其他我们关心的可变内存可以任意映射,让我们来思考一下:
因此,现在我们可以将需要快照的整个内存块概念化为:
为什么我们关心可写内存是这样紧凑和连续的?我们仍然面临一个问题,即 MMU/Brk 内存池太大,无法在每次模糊测试迭代时进行一次巨大的 memcpy
。我们的解决方案要么使用差异性重置(即只重置脏页),要么找到一种新的方法进行整体恢复,因为 memcpy
并不足够。
为了保持简单,我决定使用一种有效的方法,利用连续内存的概念来重置整个内存块,而不依赖于 memcpy
。我们可以使用 Linux 的共享内存对象来缓存模糊测试期间的快照内容,这些对象是通过 libc::shm_open
分配的。这基本上就像打开了一个由共享内存支持的文件,因此在每次快照恢复时,我们实际上不会触发任何磁盘读取或昂贵的文件 I/O。
接下来,当我们需要恢复时,我们可以简单地将这个“文件”通过 mmap
覆盖在脏的连续内存块上。它们大小相同,对吧?而且我们控制连续内存块的位置,所以这使得重置脏内存变得非常容易!代码基本上就是这样:
你只需要共享内存对象的文件描述符,就可以执行内存内容的恢复。在我的相对较旧的 CPU 和 VMWare 虚拟机中,我大约每秒能够重置这个内存块 18,000 次,这对于像 Lucid 这样的模糊测试器来说绝对够快了,因为瓶颈几乎肯定会出现在目标仿真代码上。不过,这并不意味着我们将来不会遇到问题。使用这种方法时,内核在销毁那些 mmap
覆盖了但不再需要的页面时会花费大量时间,如果我们未来扩大模糊测试规模,这可能会成为瓶颈。时间会证明一切。目前,我非常喜欢这种方法的简单和易用性。特别感谢 Dominik Maier 和模糊测试 Discord 社区的其他人帮助我完善了这个想法。
除此之外,这种方法的第二个重要优势就是性能相对稳定,无论内存块大小如何。我们利用了 Linux 内核的多种高效内存管理优化,不再因为 2GB 的 memcpy
操作而减慢速度。以我当前的设置(分配了 64MB 的来宾内存)来看,这种共享内存加 mmap
的方法比单纯的巨型 memcpy
快了大约 10 倍。我们从在快照恢复代码中花费 13% 的 CPU 时间提升到使用 memcpy
时的 96%。因此,当前对我们来说效果很好。
关于快照恢复的其他一些小细节,我们可以“克隆”一个现有的 MMU,比如我们在记录快照时保存的那个,通过类似这样简单的操作将其复制到当前(脏)的 MMU:
我们还需要处理 Bochs 的通用寄存器(GPRs),幸运的是,当 Bochs 切换上下文到 Lucid 以进行快照时,这些寄存器已经被保存了。
接下来我们需要做的是确定如何从来宾运行的 harness 中调用快照逻辑。我决定借用 Bochs 的方法,利用特定类型的 NOP 指令序列,这些序列在目标程序中不太可能出现(碰撞的可能性很低)。Bochs 使用这些类型的 NOP 作为使用 Bochs 调试模式编译时的魔法断点。它们如下:
这段代码位于 bochs/cpu/data_xfer16.cc
文件中。bxInstruction_c
结构体有处理这类操作的字段,跟踪源寄存器和目标寄存器。如果它们相同,它会根据指令编码中的二进制表示进行检查。例如,xchg dx, dx
表示 i->src()
和 i->dst()
都等于 2。
因此,在这个指令处理程序中,我们已经有了一个示例,说明如何实现逻辑,让 Bochs 识别来宾中的指令并执行某些操作。
我们实际上有两种快照。一种是使用带有图形用户界面的普通版本的 Bochs 时,我们的目标是将 Bochs 的状态“快照”到磁盘上,以便从那里开始模糊测试。这与模糊测试器构思的快照不同。例如,如果你像我一样构建了一个 harness,你可能会希望使用带有 GUI 的 Bochs 启动系统,获取一个 shell,然后运行你的 harness。你的 harness 可以触发这些魔法断点之一,让 Bochs 将其状态保存到磁盘上,这就是我所做的。
Bochs 具有在用户使用“暂停”功能时将其状态保存到磁盘的能力,就像暂停一个虚拟机一样。然后 Bochs 可以在将来恢复该暂停的虚拟机,这显然是一个很棒的功能。我们可以利用这一点,将这段代码从 GUI 模拟接口代码中复制粘贴到指令处理程序中。我所做的只是为 data_xfer16.cc
添加了一个额外的 include,然后像下面这样加入我的逻辑:
因此,如果我们构建一个带 GUI 的普通 Bochs,并在构建过程中定义了 BX_SNAPSHOT
,当 Bochs 遇到 xchg dx, dx
指令时,它应该能够将其状态保存到磁盘上,就像终端用户在我们的 harness 中完美时刻按下了暂停按钮一样。
我们将告诉 Bochs 恢复之前保存到磁盘的状态,并在它即将仿真 CPU 循环中的第一条指令时,返回到模糊测试器,并进行我们在上一节中讨论的快照。这是通过在 cpu/cpu.cc
中加入一些代码实现的,如下所示:
可以看到,如果我们为模糊测试器构建 Bochs(定义了 BX_LUCID
),我们将在开始仿真指令之前或通过 longjmp
或类似逻辑返回异常之前调用 take_snapshot
函数。take_snapshot
代码的逻辑非常简单,我们只需在全局执行上下文中设置一些变量,让 Lucid 知道我们为什么退出虚拟机以及应该对此做些什么:
现在 Lucid 可以将此状态保存为快照,并在每次模糊测试迭代后重置到此状态。所有这些仅仅是通过在你的模糊测试 harness 中包含一个简单的 xchg dx, dx
指令,非常酷!在一个 fuzzcase 结束时,当我们重置快照状态并希望从快照状态重新开始执行 Bochs 时,我们只需通过上下文切换调用此函数,以一个简单的 ret
指令结束。这将表现得就像 Bochs 正常从调用 lucid_take_snapshot
函数返回一样:
我觉得快照的部分基本上就是这样了,挺好奇它们未来的表现,不过现在它们已经起作用了。
在快照功能完成后,我开始实现代码覆盖反馈功能。起初,由于通过 Bochs 能访问所有内容,选项繁多让我有些不知所措。我们知道在每次模糊测试迭代中执行的每一个程序计数器(PC),所以理论上我们可以做任何想做的事情。最终,我实现了一个与老式 AFL 类似的方案,跟踪两级代码覆盖率:
在考虑了这两级内省之后,我们需要选择实现方式。幸运的是,我们可以通过 instrument/stubs/instrument.cc
暴露的存根编译 Bochs 进行插桩。一些存根对我们特别有用,因为它们为分支指令插桩。因此,如果你在编译 Bochs 时定义了 BX_INSTRUMENTATION
,这些存根将被编译到来宾的分支指令处理程序中。它们的原型记录了当前的 PC 和目标 PC。我不得不对条件分支未采取的插桩存根的签名做了一些修改,因为它没有跟踪可能被采取的 PC,而我们需要该信息来形成边缘对。以下是修改前后的存根逻辑:
然后我将它们改为:
因此,我不得不遍历并更改指令处理程序中的所有宏调用,以便为 bx_instr_cnear_branch_not_taken
计算一个新的 taken
PC,这虽然有点麻烦,但对于在别人的项目上做修改来说,还是很简单的。以下是 Bochs 补丁文件中我在调用点所做的更改的示例,你可以看到我必须计算一个新的变量 bx_address taken
来获取一个对:
现在,每次 Bochs 遇到分支指令时,我们都知道当前的 PC 和跳转到的 PC,是时候利用这些信息了。在 Lucid 端的 Rust 代码中,我实现了一个类似这样的覆盖率映射:
这是一个 u8
值的长数组,其中每个索引代表我们命中的一个边缘对。我们将该数组的地址传递给 Bochs,以便它可以在跟踪的边缘对对应的数组位置设置值。因此,Bochs 遇到分支指令时,它将具有当前 PC 和跳转到的 PC,并会生成一个有意义的值,将该值转换为 u8
数组覆盖率映射的索引。在该索引处,它会递增 u8
值。这个过程通过对两个边缘地址进行哈希运算,然后执行一个逻辑 AND 操作来完成,以屏蔽掉不属于覆盖率映射索引的位。这意味着我们可能会有碰撞,某些边缘对可能会产生与另一个不同边缘对相同的哈希值。但这是这种策略的一个缺陷,我们必须接受。还有其他方法可以避免边缘对的碰撞,但这需要每次遇到分支指令时进行哈希查找。这可能会比较昂贵,但考虑到我们的目标代码是由一个非常慢的模拟器运行的,未来我们可能会切换到这种范式,拭目以待。
对于哈希算法,我选择使用 dbj2_hash
,这是一种奇特的小哈希算法,速度快,而且据说提供了不错的分布(低碰撞率)。总的来说,我们执行以下操作:
这是我们如何从 Bochs 更新映射的:
现在我们在 Lucid 端有了这个 u8
值的数组,在每次模糊测试迭代后进行评估。Lucid 端需要做以下几件事:
你可以在这里看到这部分逻辑:
这基本上就是代码覆盖反馈的全部内容了。Bochs 通过分支指令处理程序中的插桩钩子更新映射,然后 Lucid 在每次模糊测试迭代结束时分析结果,并为下一次运行清空映射。直接从 AFL 的做法中借鉴而来。
为全系统快照模糊测试器设置目标环境总是很麻烦。这将非常特定于你的需求,且没有通用的方式来处理这种事情。这本质上是“harnessing”(构建测试环境)的问题,目前还没有通用的解决方案。这也是模糊测试器终端用户需要做的所有工作所在。不过,这也是其中的乐趣所在,修改你的目标以便它可以被模糊测试是我做过的最有趣的黑客活动之一。
对于 Lucid,我们需要一些 Bochs 能理解的东西。事实证明 Bochs 可以很容易地运行和启动 iso 文件,而且由于我主要对模糊测试 Linux 内核感兴趣,我决定制作一个自定义内核并将其编译为一个 iso 文件来与 Lucid 一起进行模糊测试。这效果非常好,而且一旦我掌握了创建 iso 文件的窍门,这就非常容易了。对于一个成熟的工作流程,我认为针对这种特定情况,我会尝试这样做:
至少对于 Linux 内核的模糊测试,我会这样做。
我开发了一个有趣的小玩具系统调用来进行模糊测试,如下:
我只是向内核添加了一个名为 fuzzme
的新系统调用,它的系统调用号是 451,然后我编译了一个 harness 并将其放入 iso 文件的 /usr/bin/harness
目录中。我还没有尝试找到一种通用的方法将崩溃连接到 Lucid,我只是将特殊的 NOP 指令放入 __crash
函数中以指示崩溃。不过,使用像 KASAN 这样的工具,我相信将来会有某个切入点可以用作捕获所有崩溃的通用方法。奇怪的是,从 Bochs 主机层面检测崩溃并不如内核向你的程序发送信号时那么简单(显然,如果你以这种方式构建它,某些内核 oops 会向你的 harness 发送信号)。
harness 非常简单,如下:
我创建了一个128位的签名值,Lucid可以在Bochs的堆内存中扫描它,并了解模糊测试输入的维度。一旦找到签名,我就可以从Lucid插入输入到Bochs。这也可能通过使用一些Bochs逻辑来翻译客户机的线性地址到主机Bochs的物理内存中实现,然后在快照期间通过GPR将这些值传递上去,但我还没有在这方面做很多工作。这种方式看起来相当通用?我不确定人们会更喜欢哪种方式,我们拭目以待。
你可以看到用于拍摄快照和恢复快照的特殊NOP指令。因此,我们实际上只对harness的系统调用部分进行模糊测试。
我基本上是按照这个教程来构建带有BusyBox的ISO的:构建ISO的教程。我将harness静态编译,然后将其复制到 /usr/bin/harness
,然后我可以从带有GUI的普通Bochs运行它,以在我们想要进行模糊测试快照的点保存Bochs状态到磁盘。
我将我的自定义系统调用添加到了6.0.1版本的Linux内核的 kernel/sys.c
文件的底部,并且我根据教程将harness添加到了initramfs中的 /usr/bin/harness
。当我创建ISO时,我的文件层次结构如下:
bzImage
是编译后的内核映像,initramfs.cpio.gz
是我们希望在虚拟机中使用的压缩initramfs文件系统,你可以通过导航到其根目录并执行类似 find . | cpio -o -H newc | gzip > /path/to/iso_files/boot/initramfs.cpio.gz
的命令来创建它。
我的 grub.cfg
文件的内容如下:
将 grub-mkrescue
指向 iso_files
目录,它会输出我们想要在Bochs中运行的ISO:grub-mkrescue -o lucid_linux.iso iso_files/
。
下图显示了从启动环境到结束的整个过程:
我们想要启动模拟,因此这里输入6。当我们这么做时,最终应该引导到GRUB的这个屏幕,选择Lucid Linux进行引导:
一旦我们引导并进入shell,我只需要从命令行调用 harness
,因为它已经自动在我的 $PATH
中,然后将Bochs状态保存到磁盘!
现在,/tmp/lucid_snapshot
拥有在Lucid的Bochs中恢复这个已保存Bochs状态所需的所有信息。我们只需要注释掉 /tmp/lucid_snapshot/config
中的显示库行,如下所示:
接下来,我们只需要运行Lucid,并给它正确的Bochs参数,以从磁盘恢复该已保存的状态:./lucid --input-signature 0x13371337133713371338133813381338 --verbose --bochs-image /tmp/lucid_bochs --bochs-args -f /home/h0mbre/git_bochs/Bochs/bochs/bochsrc_nogui.txt -q -r /tmp/lucid_snapshot
以下是这些配置文件的内容,分别是GUI版Bochs和我们传递给Lucid的Bochs,唯一的区别是注释掉的显示库行:
实际上没有太多内容,你只需要将ISO放入正确的设备并声明它已插入,这样你就可以开始模糊测试了!
现在我们可以设想用这个工具进行模糊测试,有很多小改动需要在未来进行,我会着手处理这些:
变异器:目前有一个临时的玩具变异器用于演示,我认为我们实际上不会在这篇博客中进行任何变异操作。我可能会把 Brandon 的基本变异器添加到模糊测试器中作为默认选项,但我认为通过 Rust 特性可以很容易地让你带上自己的输入生成器,我们拭目以待。也许这会成为一篇博客文章,谁知道呢。
语料库管理:目前还没有!不过应该很容易实现,不值得专门写一篇博客。
并行化:我认为这会是一篇有趣的博客文章,我希望模糊测试器能够轻松并行化,甚至可能分布在多个节点上。我想让这个东西在我几年前购买的服务器上运行,而这些服务器一直没用过,哈哈。
Redqueen:我们能如此轻松地访问相关指令,必须实现这个功能,它能极大提高效率。
LibAFL 集成:这绝对会是一篇博客文章,我们希望它最终成为 LibAFL 的执行引擎。
也许在下一篇博客中,我们会尝试模糊测试一个真实目标并找到一个 N-Day 漏洞?如果输入生成部分不是太费力的话,那会很有趣。让我知道你想看到什么,下次见。
本文使用chatGPT-4o翻译而成,如有错误之处,请斧正
原文链接:https://h0mbre.github.io/Lucid_Snapshots_Coverage/
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| Non
-
Writable ELF Segments |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
| <
-
-
Start of memory that we need to record
and
restore
| Writable ELF Segments |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| Non
-
Writable ELF Segments |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
| <
-
-
Start of memory that we need to record
and
restore
| Writable ELF Segments |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| Non
-
Writable ELF Segments |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
| <
-
-
Start of memory that we need to record
and
restore
| Writable ELF Segments |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| Bochs Extended CPU State |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| Bochs MMU
/
Brk Pool |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
| <
-
-
End of memory that we need to record
and
restore
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| Non
-
Writable ELF Segments |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
| <
-
-
Start of memory that we need to record
and
restore
| Writable ELF Segments |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| Bochs Extended CPU State |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
|
| Bochs MMU
/
Brk Pool |
|
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
| <
-
-
End of memory that we need to record
and
restore
// This function will take the saved data in the shm object and just mmap it
// overtop of the writable memory block to restore the memory contents
#[inline]
fn restore_memory_block(base: usize, length: usize, fd: i32) ->
Result<(), LucidErr> {
// mmap the saved memory contents overtop of the dirty contents
let result = unsafe {
libc::mmap(
base as *mut libc::c_void,
length,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_FIXED,
fd,
0
)
};
if
result == libc::MAP_FAILED || result != base as *mut libc::c_void {
return
Err(LucidErr::from(
"Failed to mmap restore snapshot"
));
}
Ok(())
}
// This function will take the saved data in the shm object and just mmap it
// overtop of the writable memory block to restore the memory contents
#[inline]
fn restore_memory_block(base: usize, length: usize, fd: i32) ->
Result<(), LucidErr> {
// mmap the saved memory contents overtop of the dirty contents
let result = unsafe {
libc::mmap(
base as *mut libc::c_void,
length,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_FIXED,
fd,
0
)
};
if
result == libc::MAP_FAILED || result != base as *mut libc::c_void {
return
Err(LucidErr::from(
"Failed to mmap restore snapshot"
));
}
Ok(())
}
// Copy the contents of an existing MMU, used for snapshot restore
pub fn restore(&mut self, mmu: &Mmu) {
self.map_base = mmu.map_base;
self.map_length = mmu.map_length;
self.brk_base = mmu.brk_base;
self.brk_size = mmu.brk_size;
self.curr_brk = mmu.curr_brk;
self.mmap_base = mmu.mmap_base;
self.mmap_size = mmu.mmap_size;
self.curr_mmap = mmu.curr_mmap;
self.next_mmap = mmu.next_mmap;
}
// Copy the contents of an existing MMU, used for snapshot restore
pub fn restore(&mut self, mmu: &Mmu) {
self.map_base = mmu.map_base;
self.map_length = mmu.map_length;
self.brk_base = mmu.brk_base;
self.brk_size = mmu.brk_size;
self.curr_brk = mmu.curr_brk;
self.mmap_base = mmu.mmap_base;
self.mmap_size = mmu.mmap_size;
self.curr_mmap = mmu.curr_mmap;
self.next_mmap = mmu.next_mmap;
}
66:87C9 | xchg cx,cx | 1000011111 001 001 -> 1
66:87D2 | xchg dx,dx | 1000011111 010 010 -> 2
66:87DB | xchg bx,bx | 1000011111 011 011 -> 3
66:87E4 | xchg sp,sp | 1000011111 100 100 -> 4
66:87ED | xchg bp,bp | 1000011111 101 101 -> 5
66:87F6 | xchg si,si | 1000011111 110 110 -> 6
66:87FF | xchg di,di | 1000011111 111 111 -> 7
66:87C9 | xchg cx,cx | 1000011111 001 001 -> 1
66:87D2 | xchg dx,dx | 1000011111 010 010 -> 2
66:87DB | xchg bx,bx | 1000011111 011 011 -> 3
66:87E4 | xchg sp,sp | 1000011111 100 100 -> 4
66:87ED | xchg bp,bp | 1000011111 101 101 -> 5
66:87F6 | xchg si,si | 1000011111 110 110 -> 6
66:87FF | xchg di,di | 1000011111 111 111 -> 7
#if BX_SNAPSHOT
// Check for take snapshot instruction `xchg dx, dx`
if
((i->src() == i->dst()) && (i->src() == 2)) {
BX_COMMIT_INSTRUCTION(i);
if
(BX_CPU_THIS_PTR async_event)
return
;
++i;
char
save_dir[] =
"/tmp/lucid_snapshot"
;
mkdir(save_dir, 0777);
printf
(
"Saving Lucid snapshot to '%s'...\n"
, save_dir);
if
(SIM->save_state(save_dir)) {
printf
(
"Successfully saved snapshot\n"
);
sleep(2);
exit
(0);
}
else
{
printf
(
"Failed to save snapshot\n"
);
}
BX_EXECUTE_INSTRUCTION(i);
}
#endif
#if BX_SNAPSHOT
// Check for take snapshot instruction `xchg dx, dx`
if
((i->src() == i->dst()) && (i->src() == 2)) {
BX_COMMIT_INSTRUCTION(i);
if
(BX_CPU_THIS_PTR async_event)
return
;
++i;
char
save_dir[] =
"/tmp/lucid_snapshot"
;
mkdir(save_dir, 0777);
printf
(
"Saving Lucid snapshot to '%s'...\n"
, save_dir);
if
(SIM->save_state(save_dir)) {
printf
(
"Successfully saved snapshot\n"
);
sleep(2);
exit
(0);
}
else
{
printf
(
"Failed to save snapshot\n"
);
}
BX_EXECUTE_INSTRUCTION(i);
}
#endif
jmp_buf
BX_CPU_C::jmp_buf_env;
void
BX_CPU_C::cpu_loop(
void
)
{
#if BX_SUPPORT_HANDLERS_CHAINING_SPEEDUPS
volatile
Bit8u stack_anchor = 0;
BX_CPU_THIS_PTR cpuloop_stack_anchor = &stack_anchor;
#endif
#if BX_DEBUGGER
BX_CPU_THIS_PTR break_point = 0;
BX_CPU_THIS_PTR magic_break = 0;
BX_CPU_THIS_PTR stop_reason = STOP_NO_REASON;
#endif
// Place the Lucid snapshot taking code here above potential long jump returns
#if BX_LUCID
lucid_take_snapshot();
#endif
if
(
setjmp
(BX_CPU_THIS_PTR jmp_buf_env)) {
// can get here only from exception function or VMEXIT
BX_CPU_THIS_PTR icount++;
BX_SYNC_TIME_IF_SINGLE_PROCESSOR(0);
#if BX_DEBUGGER || BX_GDBSTUB
if
(dbg_instruction_epilog())
return
;
#endif
#if BX_GDBSTUB
if
(bx_dbg.gdbstub_enabled)
return
;
#endif
}
jmp_buf
BX_CPU_C::jmp_buf_env;
void
BX_CPU_C::cpu_loop(
void
)
{
#if BX_SUPPORT_HANDLERS_CHAINING_SPEEDUPS
volatile
Bit8u stack_anchor = 0;
BX_CPU_THIS_PTR cpuloop_stack_anchor = &stack_anchor;
#endif
#if BX_DEBUGGER
BX_CPU_THIS_PTR break_point = 0;
BX_CPU_THIS_PTR magic_break = 0;
BX_CPU_THIS_PTR stop_reason = STOP_NO_REASON;
#endif
// Place the Lucid snapshot taking code here above potential long jump returns
#if BX_LUCID
lucid_take_snapshot();
#endif
if
(
setjmp
(BX_CPU_THIS_PTR jmp_buf_env)) {
// can get here only from exception function or VMEXIT
BX_CPU_THIS_PTR icount++;
BX_SYNC_TIME_IF_SINGLE_PROCESSOR(0);
#if BX_DEBUGGER || BX_GDBSTUB
if
(dbg_instruction_epilog())
return
;
#endif
#if BX_GDBSTUB
if
(bx_dbg.gdbstub_enabled)
return
;
#endif
}
// Call into Lucid to take snapshot of current Bochs state
__attribute__((optimize(0)))
void
lucid_take_snapshot(
void
) {
if
(!g_lucid_ctx)
return
;
// Set execution mode to Bochs
g_lucid_ctx->mode = BOCHS;
// Set the exit reason
g_lucid_ctx->exit_reason = TAKE_SNAPSHOT;
// Inline assembly to switch context back to fuzzer
__asm__ (
"push %%r15\n\t"
// Save r15 register
"mov %0, %%r15\n\t"
// Move context pointer into r15
"call *(%%r15)\n\t"
// Call context_switch
"pop %%r15"
// Restore r15 register
:
// No output
:
"r"
(g_lucid_ctx)
// Input
:
"memory"
// Clobber
);
return
;
}
// Call into Lucid to take snapshot of current Bochs state
__attribute__((optimize(0)))
void
lucid_take_snapshot(
void
) {
if
(!g_lucid_ctx)
return
;
// Set execution mode to Bochs
g_lucid_ctx->mode = BOCHS;
// Set the exit reason
g_lucid_ctx->exit_reason = TAKE_SNAPSHOT;
// Inline assembly to switch context back to fuzzer
__asm__ (
"push %%r15\n\t"
// Save r15 register
"mov %0, %%r15\n\t"
// Move context pointer into r15
"call *(%%r15)\n\t"
// Call context_switch
"pop %%r15"
// Restore r15 register
:
// No output
:
"r"
(g_lucid_ctx)
// Input
:
"memory"
// Clobber
);
return
;
}
// Restore Bochs' state from the snapshot
fn restore_bochs_execution(contextp: *mut LucidContext) {
// Set the mode to Bochs
let context = LucidContext::from_ptr_mut(contextp);
context.mode = ExecMode::Bochs;
// Get the pointer to the snapshot regs
let snap_regsp = context.snapshot_regs_ptr();
// Restore the extended state
context.restore_xstate();
// Move that pointer into R14 and restore our GPRs
unsafe {
asm!(
"mov r14, {0}"
,
"mov rax, [r14 + 0x0]"
,
"mov rbx, [r14 + 0x8]"
,
"mov rcx, [r14 + 0x10]"
,
"mov rdx, [r14 + 0x18]"
,
"mov rsi, [r14 + 0x20]"
,
"mov rdi, [r14 + 0x28]"
,
"mov rbp, [r14 + 0x30]"
,
"mov rsp, [r14 + 0x38]"
,
"mov r8, [r14 + 0x40]"
,
"mov r9, [r14 + 0x48]"
,
"mov r10, [r14 + 0x50]"
,
"mov r11, [r14 + 0x58]"
,
"mov r12, [r14 + 0x60]"
,
"mov r13, [r14 + 0x68]"
,
"mov r15, [r14 + 0x78]"
,
"mov r14, [r14 + 0x70]"
,
"sub rsp, 0x8"
,
// Recover saved CPU flags
"popfq"
,
"ret"
,
in(reg) snap_regsp,
);
}
}
// Restore Bochs' state from the snapshot
fn restore_bochs_execution(contextp: *mut LucidContext) {
// Set the mode to Bochs
let context = LucidContext::from_ptr_mut(contextp);
context.mode = ExecMode::Bochs;
// Get the pointer to the snapshot regs
let snap_regsp = context.snapshot_regs_ptr();
// Restore the extended state
context.restore_xstate();
// Move that pointer into R14 and restore our GPRs
unsafe {
asm!(
"mov r14, {0}"
,
"mov rax, [r14 + 0x0]"
,
"mov rbx, [r14 + 0x8]"
,
"mov rcx, [r14 + 0x10]"
,
"mov rdx, [r14 + 0x18]"
,
"mov rsi, [r14 + 0x20]"
,
"mov rdi, [r14 + 0x28]"
,
"mov rbp, [r14 + 0x30]"
,
"mov rsp, [r14 + 0x38]"
,
"mov r8, [r14 + 0x40]"
,
"mov r9, [r14 + 0x48]"
,
"mov r10, [r14 + 0x50]"
,
"mov r11, [r14 + 0x58]"
,
"mov r12, [r14 + 0x60]"
,
"mov r13, [r14 + 0x68]"
,
"mov r15, [r14 + 0x78]"
,
"mov r14, [r14 + 0x70]"
,
"sub rsp, 0x8"
,
// Recover saved CPU flags
"popfq"
,
"ret"
,
in(reg) snap_regsp,
);
}
}
void
bx_instr_cnear_branch_taken(unsigned cpu, bx_address branch_eip, bx_address new_eip) {}
void
bx_instr_cnear_branch_not_taken(unsigned cpu, bx_address branch_eip) {}
void
bx_instr_cnear_branch_taken(unsigned cpu, bx_address branch_eip, bx_address new_eip) {}
void
bx_instr_cnear_branch_not_taken(unsigned cpu, bx_address branch_eip) {}
void
bx_instr_cnear_branch_taken(unsigned cpu, bx_address branch_eip, bx_address new_eip) {}
void
bx_instr_cnear_branch_not_taken(unsigned cpu, bx_address branch_eip, bx_address new_eip) {}
void
bx_instr_cnear_branch_taken(unsigned cpu, bx_address branch_eip, bx_address new_eip) {}
void
bx_instr_cnear_branch_not_taken(unsigned cpu, bx_address branch_eip, bx_address new_eip) {}
- BX_INSTR_CNEAR_BRANCH_NOT_TAKEN(BX_CPU_ID, PREV_RIP);
+ bx_address taken = PREV_RIP + i->ilen();
+ BX_INSTR_CNEAR_BRANCH_NOT_TAKEN(BX_CPU_ID, PREV_RIP, taken);
- BX_INSTR_CNEAR_BRANCH_NOT_TAKEN(BX_CPU_ID, PREV_RIP);
+ bx_address taken = PREV_RIP + i->ilen();
+ BX_INSTR_CNEAR_BRANCH_NOT_TAKEN(BX_CPU_ID, PREV_RIP, taken);
const
COVERAGE_MAP_SIZE: usize = 65536;
#[derive(Clone)]
#[repr(C)]
pub
struct
CoverageMap {
pub curr_map: Vec<u8>,
// The hit count map updated by Bochs
history_map: Vec<u8>,
// The map from the previous run
curr_map_addr: *
const
u8,
// Address of the curr_map used by Bochs
}
const
COVERAGE_MAP_SIZE: usize = 65536;
#[derive(Clone)]
#[repr(C)]
pub
struct
CoverageMap {
pub curr_map: Vec<u8>,
// The hit count map updated by Bochs
history_map: Vec<u8>,
// The map from the previous run
curr_map_addr: *
const
u8,
// Address of the curr_map used by Bochs
}
static
inline
uint32_t dbj2_hash(uint64_t src, uint64_t dst) {
if
(!g_lucid_ctx)
return
0;
uint32_t hash = 5381;
hash = ((hash << 5) + hash) + (uint32_t)(src);
hash = ((hash << 5) + hash) + (uint32_t)(dst);
return
hash & (g_lucid_ctx->coverage_map_size - 1);
}
static
inline
void
update_coverage_map(uint64_t hash) {
// Get the address of the coverage map
if
(!g_lucid_ctx)
return
;
uint8_t *map_addr = g_lucid_ctx->coverage_map_addr;
// Mark this as hit
map_addr[hash]++;
// If it's been rolled-over to zero, make it one
if
(map_addr[hash] == 0) {
map_addr[hash] = 1;
}
}
void
bx_instr_cnear_branch_taken(unsigned cpu, bx_address branch_eip, bx_address new_eip) {
uint64_t hash = dbj2_hash(branch_eip, new_eip);
update_coverage_map(hash);
//printf("CNEAR TAKEN: (0x%lx, 0x%lx) Hash: 0x%lx\n", branch_eip, new_eip, hash);
}
void
bx_instr_cnear_branch_not_taken(unsigned cpu, bx_address branch_eip, bx_address new_eip) {
uint64_t hash = dbj2_hash(branch_eip, new_eip);
update_coverage_map(hash);
//printf("CNEAR NOT TAKEN: (0x%lx, 0x%lx) Hash: 0x%lx\n", branch_eip, new_eip, hash);
}
static
inline
uint32_t dbj2_hash(uint64_t src, uint64_t dst) {
if
(!g_lucid_ctx)
return
0;
uint32_t hash = 5381;
hash = ((hash << 5) + hash) + (uint32_t)(src);
hash = ((hash << 5) + hash) + (uint32_t)(dst);
return
hash & (g_lucid_ctx->coverage_map_size - 1);
}
static
inline
void
update_coverage_map(uint64_t hash) {
// Get the address of the coverage map
if
(!g_lucid_ctx)
return
;
uint8_t *map_addr = g_lucid_ctx->coverage_map_addr;
// Mark this as hit