开源固件仿真平台FAP对嵌入式固件的模拟与定制

固件,即烧录进芯片的嵌入式程序,通常包含bootloader、内核以及文件系统。因其一般不容易更改,我们称之为“固件”。固件可以通过硬件手段获取,也可以在网上下载各种嵌入式产品的固件。但是更多时候,我们手中没有相应的设备。也就是说,获取各种固件成本低廉,而获取产品则需要花费一定财力。

对固件的仿真需求由此出现,firmadyne是诞生于2016年的一个优秀开源固件分析工具,可以较好的完成常见路由器固件的模拟。但是其仿真流程较为繁琐,后来有相应的开源项目firmware-analysis-toolkit整合了仿真流程,进行了简化。但其环境依赖存在较多问题,在不同的Linux发行版上,会遇到各种奇奇怪怪的问题,可能需要耗费很长时间才能将工具的环境搭建好。

在这种情况下,笔者整合和修改了部分代码,构建了一个新的开源仿真平台 —— firmware-analysis-plus。使用该工具,可以进一步缩减搭建环境耗费的时间,让我们的精力进一步聚焦于各种路由器仿真的定制以及漏洞挖掘。

0x10 firmware-analysis-plus

上游项目支持:binwalkfirmadynefirmware-analysis-toolkit

firmware-analysis-plusFAP)主要用于常见路由器固件的仿真,可以进行固件的安全测试。感谢以下开源项目:binwalk提供优秀的固件提取 API,firmadyne提供优秀的固件仿真核心支持,firmware-analysis-toolkit提供简化流程的思想。

开源固件仿真平台FAP对嵌入式固件的模拟与定制

FAP只是站在巨人的肩膀上,做出改进和定制,提供一个更加高效的仿真平台。包括精简不必要组件,优化仿真流程,优化网络环境大幅压缩安装时间,修复若干bug,一键仿真固件。其原理主要包括两点

qemu提供多种架构指令的模拟,使用预先编译好的内核启动固件中的核心业务

多数嵌入式设备含有一个nvram芯片,保存一些重要的配置信息,firmadyne实现一个新的libnvram.so库文件,通过代码模拟固件启动时加载nvram配置信息的行为。

FAP 版本 python 版本 支持系统 安装方法
v0.1 python2、python3 Ubuntu16.04、Ubuntu 18.04、Kali 2020.02 FAP v0.1 版本手册
v1.0 python2、python3 Beta Beta
v2.0 python3 Kali 2020.04(不支持 Ubuntu 20.04,其他未测试) 如下所示

0x11 安装 binwalk

以编译源码的方式安装binwalk,时至今日,binwalk构建脚本中的诸多依赖已无法正常安装,于是自己fork了一份新的binwalk,进行了修改。关于修改细节的描述,可参考:https://github.com/liyansong2018/binwalk

git clone https://github.com/liyansong2018/binwalk.git
cd binwalk
./deps.sh
sudo python3 setup.py install

0x12 安装 FAP

git clone https://github.com/liyansong2018/firmware-analysis-plus.git
cd firmware-analysis-plus
./setup.sh

0x13 配置

修改fat.config文件中的密码,改为root系统用户的密码

0x14 运行

运行fat.py脚本,即可对相应的固件进行模拟

./fat.py -q ./2.5.0/ ./testcases/wnap320_V3.7.11.4_firmware.tar

结果

┌──(lys㉿kali)-[~/Tools/firmware-analysis-plus]
└─$ ./fat.py -q ./2.5.0/ ./testcases/wnap320_V3.7.11.4_firmware.tar


                ______   _                ___
                |  ___| (_)              / _ \
                | |_     _   _ __ ___   / /_\ \  _ __    ___
                |  _|   | | | '_ ` _ \  |  _  | | '_ \  / __| ++
                | |     | | | | | | | | | | | | | | | | \__ \ 
                \_|     |_| |_| |_| |_| \_| |_/ |_| |_| |___/

                Welcome to the Firmware Analysis Plus - v2.0
            By lys - https://blog.csdn.net/song_lee | @liyansong

[+] Firmware: wnap320_V3.7.11.4_firmware.tar
[+] Extracting the firmware...
[+] Image ID: 1
[+] Identifying architecture...
[+] Architecture: mipseb
[+] Building QEMU disk image...
[+] Setting up the network connection, please standby...
[+] Network interfaces: [('brtrunk', '192.168.0.100')]
[+] Using qemu-system-mips from /home/lys/Tools/firmware-analysis-plus/qemu-builds/2.5.0
[+] All set! Press ENTER to run the firmware...
[+] When running, press Ctrl + A X to terminate qemu

通过回车键即可模拟运行目标固件,通过提供的网络接口,打开相应的路由器管理页面

开源固件仿真平台FAP对嵌入式固件的模拟与定制

0x15 重置

如果仿真次数过多,会生成较多的中间文件,直接运行reset.py删除即可

./reset.py

0x20 定制

上游的firmadyne已经设定好了一些规则,在不对源码进行修改的情况下,可以仿真以下版本的固件

wnap320_V3.7.11.4_firmware.tar

DIR-601_REVB_FIRMWARE_2.01.BIN

DIR890A1_FW103b07.bin

但更多时候,我们想要模拟的程序往往不在上述列表中,这就需要考虑对firmaydne进行定制,其实就是对libnvram的定制。不同设备芯片上的配置信息是不一样的,固件在启动过程中,所读取的硬件信息也是不一样的,固件在启动之初,会依靠libnvram.so提供的函数,读取(如flash)芯片上的内容。而在实际环境中,我们没有该芯片,所以就要靠定制libnvram,预先设定好一些参数,这样就达到模拟真实硬件的目的。

firmadyne提供的libnvram源码结构如下

firmware-analysis-plus/firmadyne/sources/libnvram$ tree
.
├── alias.c     # 一些libnvram库函数的别名
├── alias.h
├── config.h    # 预先设定好的nvram配置信息
├── LICENSE.txt
├── Makefile
├── nvram.c     # libnvram库函数
├── nvram.h     # 要初始化的所有key/value
├── README.md
└── test.c

在仿真不同固件时,往往需要对上述代码进行定制,下面重点讲解几种常用方法。

0x21 设置固件封装的库函数别名

固件通常使用libvram.so提供的库函数,实现对硬件存储的配置信息的访问和修改

nvram_get,顾名思义,从内核的NVRAM模块获取值,其实就是从芯片中获取某个键对应的值,例如获取LAN_MAC对应的mac地址

nvram_set,与上述 API 相反,给硬件设置一个键值对

nvram_clear,通过写入1,覆盖所有的键对应的值

libnvram.so提供的函数还有很多,功能都是类似的。

但是,在一些固件中,往往会封装自己的API,以 Tenda AC 15为例,文件系统中的libCfm.so实现了自己的nvram库函数

开源固件仿真平台FAP对嵌入式固件的模拟与定制

firmadyne只是实现了了libnvram.so标准库函数的hook,因此我们需要将目标固件封装的一些函数添加到firmadyne源码中

alias.c源码中添加如下代码

/* Tenda AC15 */
int bcm_nvram_set(const char *key, const char *val) __attribute__ ((alias ("nvram_set")));
char *bcm_nvram_get(const char *key) __attribute__ ((alias ("nvram_get")));
int bcm_nvram_init(void) __attribute__ ((alias ("nvram_init")));
// ...

0x22 预先设置固件需要的 nvram 键值对

config.h文件已经包含作者设定好的一些固件需要的键值对

// Default values for NVRAM.
#define NVRAM_DEFAULTS \
    /* Linux kernel log level, used by "WRT54G3G_2.11.05_ETSI_code.bin" (305) */ \
    ENTRY("console_loglevel", nvram_set, "7") \
    /* Reset NVRAM to default at bootup, used by "WNR3500v2-V1.0.2.10_23.0.70NA.chk" (1018) */ \
    ENTRY("restore_defaults", nvram_set, "1") \
    ENTRY("sku_name", nvram_set, "") \
    ENTRY("wla_wlanstate", nvram_set, "") \
    ENTRY("lan_if", nvram_set, "br0") \
    ENTRY("lan_ipaddr", nvram_set, "192.168.0.50") \
    ENTRY("lan_bipaddr", nvram_set, "192.168.0.255") \
    ENTRY("lan_netmask", nvram_set, "255.255.255.0") \

但是我们的目标固件往往是没有包含在其中的,因此需要手动添加。一般来说包含一下两种情况

一、firmadyne错误日志(./firmadyne/scratch/1/qemu.initial.serial.log)直接提供。这种情况比较简单,直接根据错误日志直接找到缺少的键值对,如下所示

开源固件仿真平台FAP对嵌入式固件的模拟与定制

直接在config.h中添加键值

/* Tenda AC15 */ \
    ENTRY("default_nvram", nvram_set, "1") \
    ENTRY("image_boot", nvram_set, "0")

二、更多时候,没有办法直接从错误日志中直接找到问题所在,那就只能分析相应的二进制,如下所示是 DIR-2640固件在仿真中产生的错误日志

开源固件仿真平台FAP对嵌入式固件的模拟与定制

根据打印的一些错误日志和调用栈,找到相应的错误代码

开源固件仿真平台FAP对嵌入式固件的模拟与定制

也就是说,缺少factory_mode键对应的值,同样的,增加键值到config.h中即可。

0x23 其他函数拦截

某些固件可能还会遇到一些其他函数带来的错误,导致固件运行失败,遇到这种情况,可以使用IDA给相应的二进制打patch,绕过该函数,不过这种方式需要重新打包固件。好在firmadyne也考虑到此类情况,可以直接hook普通函数。

例如在 AC15 中,加载如下函数遇到错误,导致malloc失败

开源固件仿真平台FAP对嵌入式固件的模拟与定制

跟踪该函数,发现该函数的目的是读取某些mtd设备的大小,很明显,我们是没有实际设备

开源固件仿真平台FAP对嵌入式固件的模拟与定制

因此需要hook该函数,返回一个恰当值,在alias.c添加相应函数的定义

int get_cfm_blk_size_from_cache(){
    return 0x20000;
}

0x24 编译

修改后的libnvram需要编译,拷贝到相应位置,firmadyne提供了交叉编译工具

# arm
cd ./firmadyne/sources/console
make clean && CC=/opt/cross/arm-linux-musleabi/bin/arm-linux-musleabi-gcc make && mv console ../../binaries/console.armel
# mipseb
make clean && CC=/opt/cross/mipseb-linux-musl/bin/mipseb-linux-musl-gcc make && mv console ../../binaries/console.mipseb
# mipsel
make clean && CC=/opt/cross/mipsel-linux-musl/bin/mipsel-linux-musl-gcc make && mv console ../../binaries/console.mipsel

这样就完成了libnvram的定制。

0x30 总结

固件仿真不是一蹴而就的,无论是 FAP还是 firmadyne,只是为我们提供了一个框架,预置代码支持的固件类型和版本有限。尝试运行不同厂商的固件往往会遇到各种奇奇怪怪的问题,需要根据 firmadyne提供的崩溃日志,定位到出错的代码,分析崩溃原因,定制libnvram。这是一个艰难而漫长的过程,但是在这个过程中,可以加深对目标固件业务的理解,并且一旦仿真成功,我们可以复现各种已知CVE,甚至可以很容易挖掘到各种0day漏洞。

注:本文全文转载自:https://www.freebuf.com/sectool/264053.html

上一篇:个性化制作nodemcu-firmware(esp8266 硬件制作)----程序bin的制作


下一篇:手机CE认证标准及申请流程