仿真分析
固件仿真又称固件模拟,或者Rehosting(有些差异,可参见笔者Rehosting综述),模拟是二进制动态分析的重要手段,一般在没有设备或需要成规模研究的情况下使用。
一、基础
固件模拟根据不同需要可能有以下3个场景:
(一) 只需模拟某个应用,比如web、upnpd、dnsmasq等,目的是对该应用作调试。此时可以直接用模拟工具运行该程序,只需考虑动态库是否能加载。
(二) 需要模拟固件shell,与整个系统有所交互。这里可以通过chroot改变根路径,并利用模拟工具执行/bin/sh。同时可以挂在/proc,这样在ps查看进程时看上去更加真实。
(三) 需要模拟整个固件启动,并且网卡等也能正常使用。这里要利用可模拟img系统的工具直接加载整个系统,也可以利用“套娃”大法,先模拟该架构的debian.img,再用chroot起设备的roofs。
从模拟方式出发常用的有QEMU、Unicon,包括后起之秀Qiling等等,这些框架或模拟系统,或模拟执行指令,代码也都有交集。
1.1 QEMU
Qemu是最老牌的多架构模拟工具,上述3个使用场景qemu都可以满足。qemu可以下载安装也可以直接利用源安装,这里要注意的是如果模拟应用不仅需要qemu,还需安装qemu-user。
qemu模拟应用
以模拟ARM固件为例,解开固件得到rootfs,下面是利用qemu模拟执行busybox:
qemu模拟shell
利用qemu模拟sh与上述情况相似,可以先mount proc,使得ps查看进程时显得更加真实:
qemu模拟系统
qemu的功能很强大,可以像在vmware、virtualbox中一样安装系统:
利用qemu模拟完整固件最常规的办法就是将rootfs制作成img或qcow2文件,再用相应架构的qemu模拟执行,这里我们介绍一下前面提到的“套娃”大法。 首先下载Debian官方ARM构架的系统镜像。 挂在qcow2镜像,将rootfs拷贝至qcow2镜像:
然后启动qcow2文件:
qemu-system-arm -M versatilepb -kernel vmlinuz-3.2.0-4-versatile -initrd initrd.img-3.2.0-4-versatile -hda debian_wheezy_armel_standard.qcow2 -append "root=/dev/sda1"
利用chroot“套娃”系统:
使用debian现成的qcow2镜像可以省去网卡和其他一些配置过程,提高模拟效率。
值得注意的是,qemu在对程序黑盒测试时也经常使用,比如AFL的qemu_mode,在后面的篇章里我们会探讨,当然AFL同样可以使用unicorn_mode,使用的就是下面介绍的模拟器unicorn。
1.2 Unicorn
Unicorn一个基于Qemu的cpu指令模拟框架,也就是可以模拟任何的cpu指令。我们一般可以利用Unicorn指令级模拟特性:
对(IOT)程序作模糊测试,用于gdb插件,或代码模拟执行的插桩,修改代码逻辑,模拟执行一些复杂混淆代码,提高人工逆向效率,关于Unicorn模拟执行修改代码逻辑的教程比较多,这里不再赘述,下面介绍一款基于Unicorn的IDA pro插件,该插件可以在IDA pro中模拟执行,并给出执行结果,UnicornFuzz相关内容会在后面的篇章中介绍。
上面非常简单的计算器C代码是该插件的官方示例,其mipsel IDA pro反汇编代码如下:
创建一个 emu object Python>a = EmuMips()
配置模拟地址和参数
开始指令模拟
通过Unicorn模拟执行得到sum(2+3)的结果为0x5。
QEMU 提供了一个完整的仿真环境,既可以模拟硬件外设、整个系统,也可以模拟单个二进制程序。而 Unicorn 专注于 CPU 指令的仿真。
1.3 Qiling
Qiling是一款很年轻的基于Unicorn的模拟器,专为IOT研究而生。Qiling可以作为IDA pro插件,也可以利用Qiling Unicornalf进行fuzz,还可以模拟IOT设备固件。Qiling用python3开发,可以直接用pip3 install qiling
安装,以下是官方模拟ARMj架构路由器固件的部分代码:
可以看到Qiling框架进一步简化了模拟所需代码,同时提供了指令级插桩功能,还是很强大的。
二、类型模拟
模拟分析从固件类型出发粗略可分两种,一种是有明确文件系统目录的,一般基于Linux,FreeBSD等开源系统/内核;一种是没有的文件系统的RTOS(Real-time operating system),比如Vxworks,FreeRTOS等。基于Linux的固件模拟相对简单,而RTOS模块较难分离。
2.1 基于Linux的固件
Firmadyne [2]
Firmadyne框架基于QEMU,旨在完成整个固件系统的运行。
一些Rehosting&Fuzzing的框架会在其基础上二次开发,Firmadyne模块如下图所示:
其运行流程是:
1、通过网络途径获取固件(ARM/MIPS),并对固件解包;
2、利用定制动态链接库完成库函数劫持以完成NVRAM设备调用;
3、利用定制内核完成系统启动、网络探测、虚拟硬件和网络构建;
4、调用QEMU仿真系统。
对固件解包模拟的比较熟悉的小伙伴通过以上描述就会大概率放弃该工具,因为该流程存在大量不确定因素,成规模使用的成功利率会很低,而测试结果也正是如此。在Firmadyne获取的23035个固件中,8617个可解包;解包成功后有8591个可启动系统;启动系统后2797个可配置网络;最后只有1971个可以正常访问网络。也就是仅有**22.9%**的固件完成模拟并进行测试。
Firmadyne的使用方法这里略过,Github上教程也很详细。
FirmAE [3]
FirmAE目标是创造一个可以供用户动态调试分析的环境,而不是复现和硬件一模一样的环境,,其卖点是大规模(Large-Scale)。
论文开头就直指“友商”Firmadyne,提出其模拟成功率低等问题,其将原本Firmadyne的成功率16.28%提高到79.36%,其底层还是使用了QEMU。FirmAE从固件的启动、网络、NVRAM、内核和其它五个方面,总结了导致固件仿真失败的原因及通用解决方法,并集成check、run、analyze和debug四种模式。check 模式会对固件进行各项仿真检测,并保存相关的日志信息记录在缓存文件中。run模式是根据check模式构建的各种处理信息进行仿真。
在漏洞检测方面,FirmAE根据固件的文件系统和内核日志来推断Web服务状态,其发现了23个设备的12个0DAY。
FirmAE的使用方法网上不乏教程,也可以参考Github。
FirmAFL [4]
FirmAFL使用并改进了Firmdyne模拟方式,并利用AFL对IoT固件实施高通量灰盒Fuzzing。
FirmAFL的贡献在于:
1、针对全系统仿真(高通用性、低效率)和用户模式仿真(低通用性、高效率)的矛盾特点,提出了增强过程仿真的新技术;
2、设计并实现了第一个基于代码覆盖率的灰盒fuzzer—FirmAFL,支持mipsel、mipseb和armel三种CPU架构,涵盖Firmadyne数据库中90.2%的固件。
FirmAFL的使用可参考Github,以下简单梳理:
FirmFuzz [5]
FirmFuzz通过监控固件运行所生成的日志以检测漏洞,侧重挖掘命令注入、缓冲区溢出和空指针引用等。FirmFuzz在模拟层面基于QEMU,支持MIPS和ARM架构。
Firmfuzz论文中将固件可能存在的缺陷归因于三个方面,分别是(Linux)内核、开源软件和厂商自研软件,Firmfuzz重点挖掘厂商自研软件漏洞。其主要贡献点在于:
1、利用这些设备的web应用界面作为入口,生成语法上合法的输入;
2、将运行监控器嵌入固件的运行时环境中,以允许上下文感知的监控;
3、从静态分析中收集信息来指导固件模拟和Fuzzing生成。
输入检测、插庄反馈和静态预分析都是Fuzzing的常规操作,只是框架使用的方案各异。
FirmFuzz的使用侧重Fuzzing,固件解包可单独使用firmadyne extractor或者binwalk,具体可参见Github教程。
可以看到,在上述提及的Linux-based的框架和工具中,大部分都直接或间接基于QEMU模拟,以下探讨一些基于RTOS的固件分析框架。
2.2 基于RTOS固件
Avatar [6]
Avatar 在物理设备与外部模拟器之间架构一个软件代理Avatar,可以执行模拟器中的固件指令,来对物理设备进行IO操作。
Avatar框架比较经典(老牌)的框架之一,其最早将符号执行应用于嵌入式设备固件分析领域,提供了一个仿真器后端组件,用于控制 S2E(Symbolic Sombolic Exection),选择性地执行二进制代码。S2E 是一个选择性的符号执行平台,底层基于QEMU之上。S2E 提供了一个功能强大的插件接口,可以通过这些接口拦截仿真事件,通过符号化执行特定代码段可以检测固件中存在的漏洞。
其迭代框架Avatar2 [7]允许分析人员选择不同的执行环境,共同在相同的执行状态上执行其分析任务,其优势在于可以在多个分析环境中共享不同分析工具得到的分析状态。Avatar2 支持的动态分析工具包括GDB、OpenOCD、QEMU、angr 和 PANDA。
使用方法这里略过,可以参见Avatar Github和Avatar2 Github,值得注意的是Avatar2编译使用较为简单。
Jetset [8]
Jetset来自一篇较新(2021)的论文,其使用符号执行来推断固件期望从目标设备得到什么操作,通过C模拟实现硬件外设模块功能,利用QEMU启动固件。
Jetset测试了13个不同的固件,包括3个架构、3个应用领域(电网、航空电子设备和消费电子设备)和5个不同的操作系统。同时Fuzzing了一个航空电子设备固件,挖掘出一个提权0Day。
抛开繁琐的细节,Jetset从整体上还是比较好理解的,即利用符号执行技术找到固件正常运行的代码流路径,再通过软件模块方式满足,最终实现固件的Rehosting。
Jetset安装和使用可参考Github:
HALucinator [9]
HALucinator利用硬件抽象层(HAL: Hardware Abstract Layers)作为Rehosting和分析固件的基础,通过提供HAL功能的高级替代品(称为HLE),使硬件与固件脱钩,并利用QEMU模拟固件。
其主要贡献在于:
1、提出HAL库模拟技术,可在无需依赖实际硬件的情况下使用QEMU等模拟器仿真二进制固件,
改进了现有的依赖库匹配技术;
2、提出HALucinator仿真系统,可通过抽象处理程序和外设模型库的方式进行交互式仿真和固件Fuzzing;
3、通过对16个固件的模拟,证明了方法的实用性。并通过Fuzzing的方式发现了两个0day。
其缺点在于,HALucinator仅适用于固件中使用了芯片厂商提供了HAL的那些设备,并且编译环境要跟固件的编译环境类似,仅这两点就大大限制了其可用范围。
HALucinator使用可参见Github。
P²IM [10]
P²IM(Processor-Peripheral Interface Modeling)针对MCU处理器和外设之间的处理接口,根据处理器设计文档,自动化提供与之等价的接口模型来模拟外部 I/O 操作。
P²IM在模拟层面仍然使用QEMU,其主要使用抽象模型定义(Abstract Model Definition)和自动模块实例化(Automatic Model Instantiation)模块。抽象模型定义基于专家经验定义模型,把寄存器分为控制寄存器、状态寄存器、数据寄存器、控制-状态寄存器。自动模块实例化则基于之前的模型定义,基于执行对模型进行填充。主要是查找寄存器的类型、寄存器在内存中的位置、中断等。
P²IM只实现了寄存器和中断,没有实现DMA(Direct Memory Access)的情况,下文的DICE正为此问题而构建。其目录结构如下:
安装与使用可参见Github。
DICE [11]
DICE是一种模拟并生成DMA(Direct Memory Access)输入的动态分析方法,其核心思想是识别和模拟抽象的DMA input channel(作者定义的一个抽象概念,可以视为固件与外设通过DMA交换数据的桥梁),而不是模拟种类繁多的外设及控制器。
目前的针对于固件的动态分析一般无法处理固件中的DMA操作,比如上文的P²IM,从而限制分析所支持的设备种类以及代码覆盖率。作者在针对ARM Cortex-M的P²IM以及针对于MIPS M4K/M-class的PIC32模拟器上使用了DICE。在83个benchmarks上进行测试,检测到了来自5个不同厂商的9种不同的DMA 控制器。对7个fuzz-tested的真实世界的固件进行分析,执行路径比之前的高79倍,并额外发现了5个位置漏洞。
研究细节这里就不作展开,DICE主要贡献在于:针对DMA在基于MCU设备上的普遍性和多样性,通过集成到现有分析方案,实现了与硬件无关的DMA输入模拟,并能够自动化的推断出DMA缓冲区的位置和大小。
DICE结构如下:
安装与使用可参见Github。
Para-rehosting [12]
Para-rehosting针对MCU软件的可移植性问题,使用POSIX接口抽象和实现了一个可移植的MCU来降低移植难度,其会对MCU的常见函数建模。
Para-rehosting与前文介绍的HALucinator类似,也在HAL上做文章,其使用基于HAL的次要函数替换,即用主机上的等效后端驱动程序替换高级硬件功能,这些后端驱动程序由para- api调用,可以在许多MCU操作系统中重用。Para-rehosting成功地重新rehost了9个MCU操作系统,包括广泛部署的Amazon FreeRTOS、ARM Mbed、Zephyr和LiteOS,并使用动态分析工具(AFL和ASAN)发现了28个未知的bug,其中五个获得了CVE,19个被厂商承认。
官方推荐Para-rehosting在Ubuntu 18.04环境下编译使用,具体可参见Github。
Firmwire [13]
Firmwire是一个支持三星和联发科的全系统基带固件分析平台,它支持对基带固件映像进行模糊测试、模拟和调试。
FIRMWIRE提供基带专用API,可轻松添对供应商、固件映像和安全分析的支持。论文中选择 LTE 和 GSM 协议进行Fuzzing,发现7个可能导致RCE,其中包含 4 个0Day。
使用Firmwire需安装Panda(基于QEMU的仿真器,分支众多,本文就不在赘述),安装使用了参见Firmwire手册,或从Github获取更多信息。
Fuzzware [14]
Fuzzware是一个以可扩展方式对原始单片机固件进行模糊测试的专用系统,其提出了一种细粒度的access modeling 方法,使用精准的MMIO建模,可以通过固件逻辑保存所有路径,帮助Fuzzing变换集中在改变重要的输入上,从而增加了代码覆盖率和Fuzzing效率。
论文中总结了之前一些Rehosting的方法和弊端:
1、High-level emulation:通过hook和从新部署已知库来解决缺少外设的问题,从而避免硬件访问抱错,这样会导致前期手动工作量很大;
2、Pattern-based modeling:通过将访问模式匹配到常见的静态硬件寄存器类型来建模外设寄存器,弊端是开销消除不完整(可以消除 full input overhead,无法消除partial input overhead);
3、Guided symbolic execution-based modeling:将硬件寄存器视为符号输入源,然后将得到的符号值求解到固件最可能运行的路径,缺点当然是需要解决路径消除。
而对硬件外设的许多访问是short-lived,且这些访问的原因是与固件的整体行为无关的(例如:检查外设的状态或设置其配置)。对于确实影响固件行为的访问,固件通常不会使用所有的输入。(例如:directly:从32位数据中提取几位;indirectly:区分少数几个状态值) 因此Fuzzware给出了其解决方案,由于篇幅有限,仅阐释其MMIO访问处理设计,如下所示 :
Fuzzing引擎生成一个原始输入文件,在MMIO访问时,MMIO访问模型将使用输入文件的块,并将其转换为(可能更大的)硬件生成值,然后将其提供给仿真固件。一旦原始输入耗尽,覆盖反馈将提供给Fuzzing引擎以指导继续Fuzzing操作。
Fuzzware的安装与使用可参见Github。
Periscope [15]
Periscope是用于分析设备与驱动之间交互的动态分析框架,并在此框架扩展提出了专用于该场景下的fuzzing框架PeriFuzz。
Periscope贡献在于提出了分析硬件和驱动之间交互的分析框架和fuzz框架,在技术上主要通过hook page fault handler来分析针对驱动与设备共享内存的读写操作,PeriFuzz在两种WiFi的驱动上发现了15个不同的漏洞,其中包括9个0Day。本文的核心贡献在于。
硬件和驱动之间的交互方式可以分为设备中断、**MMIO(Memory mapping IO)和DMA(Direct Memory Access)**3类,目前常用的分析设备和驱动间的方法主要包括以下3种:
1、通过为设备加入额外的能力来分析交互流程,好处是不依赖于操作系统,缺点是要求设备的可编程性或者分析人员即设备开发者。
2、分析驱动与虚拟设备在虚拟运行环境下的交互,但要求比较苛刻,对每款设备的分析都需要与之对应的虚拟化设备支持。
3、将设备向驱动传输的数据都认为是符号化的值,从而进行符号执行。虽然不需要虚拟化设备和可编程设备的支持,但可能会产生路径爆炸。
Periscope则通过hook MMIO或DMA下的allocate函数来标记共享的memory region,进一步通过hook page fault handler,将针对这些memory region的读写都记录下来,从而达到分析的效果。
PeriFuzz使用AFL作为Fuzzing工具,关于Periscope和PeriFuzz安装与使用可参见GitHub。
Pretender [16]
Pretender通过观测固件与设备之间的交互记录,使用机器学习/模式识别为每一个外设创建模型,再使用QEMU或angr结合生成的外设模型实现固件Rehosting。
值得注意的是,Pretender的使用条件比较苛刻,需安装Avatar,OpenOCD及其众多依赖项,在设置和部署时可能出现问题。组件中的任何一个小版本更改都可能导致工具无法运行,或者间歇性地运行,尤其在中断方面。官方建议在Ubuntu 16.04环境下,使用mkvirtualenv安装运行Pretender,具体可参见Github。
Laelaps [17]
Laelaps针对ARM MCU,通过符号执行辅助外设仿真来推断固件的预期行为,从而生成适当的输入以动态地引导具体模拟执行。
Laelaps结合了固件常规运行和符号执行,在常规运行因访问未实现的外设卡住时,切换到符号执行,以找到正确的输入,从而引导QEMU找到与实际执行最相近的路径。其主要贡献在于:
1、对基于ARM Cortex-M的MCU系统模型进行了抽象,并提取了系统仿真中所缺少的关键部分;
2、通过设计一个符号化引导的模拟器,填补了全系统设备仿真的缺失部分,该模拟器能够运行各种固件的ARM MCU与以前未知的外设;
3、融入了boofuzz、angr和PANDA等工具辅助动态分析,以Fuzzing固件中的漏洞。
Laelpas的安装和使用可参见Github。
μEmu [18]
μEmu是一种针对ARM微处理器(MCU)固件task code的动态分析工具,其核心组件是一个设备无关的仿真器,用于仿真未知外设的驱动代码。
在μEmu论文中,研究人员将以往Rehosting思路和弊端大致总结:
1、思路:将不支持的外设转发给真实的硬件。
弊端:需要真是硬件,无法大规模操作。
2、思路:模拟抽象层,固件依赖抽象层运行。
弊端:需要生态支持,不支持定制SOC;不好解耦固件和驱动的安全测试;没有对外设逻辑的测试。
3、思路:全系统模拟,可在硬件不可知时实现高保真模拟。
弊端:不能模拟复杂系统;P²IM需要盲猜状态寄存器的反应,搜索范围过大;Laelaps只能找到短期好的选择;P²IM和Laelaps都可能造成崩溃或死机。
μEmu不像现存的工作为每个外设建立一个通用的模型,μEmu着眼于正确模拟外设的每一个独立的存取点。要实现这一举措需解决外设输入获取和判断两个难点。μEmu使用符号执行获取模拟固件映像所需信息,固件收到的反应不正确时,执行阶段应该反射出错误,并进入一种失效状态,而失效的执行状态直接反应为一条失效的路径。
μEmu的结构如下:
安装与使用可参见**Github**。
Frankenstein [19]
Frankenstein 提供了一个虚拟环境来fuzzing无线设备固件,该工具在固件在运行时转储其当前状态,然后在虚拟环境中重新执行以进fuzzing。
Frankenstein使用QEMU运行仿真,其特点在于通过转储状态使固件“恢复生机”,并为芯片的虚拟调制解调器提供模糊输入。研究人员利用Frankenstein在Broadcom和Cypress蓝牙堆栈中发现三个0Day,该堆栈广泛应用于Apple、三星、Raspberry Pi等设备。
Frankenstein可使用Web接口配置,功能包括符号管理和内存转储,生成文件和链接器脚本均可自动生成。通过python3 manage.py runserver
启动服务后,访问本机8000端口完成配置。
Frankenstein更多安装和使用信息可参见Github。
模拟的难点在于找到规模和精确的平衡点,想在效率和规模上有所建树需要提高工具的通用性,有些复杂固件难以模拟;而想精确模拟需要提高工具的专用性,此时又难以扩大规模。针对某些固件静态分析也不失为较好的方式,动静当然各有优劣,静态分析一般围绕符号执行和污点分析开展。
Last updated