固件模拟 Case Study (2)

最近分析 Dlink 的一款设备 DI-500WF,官网产品地址:http://www.dlink.com.cn/business/product?id=2918
这种设备在酒店、机场等公共场所比较常见,一般称之为路由条。因为类似酒店、车站这种公共地点摆放普通路由器提供 WiFI 服务存在一定问题(体积太大,走线困难等),所以在房屋装修的时候会考虑安装多个弱电箱或在不显眼的位置安装网络接口,此时路由条就派上用场了,这种设备不需要外接电源,可以直接使用 RJ-45 接口供电,并且体积小,易于安装。虽然路由条体积小,但是其内部系统和普通路由器类似,今天我们来简单看一下如何模拟 DI-500WF 这款设备的系统。
注:我会一步步展示在模拟过程中遇到的问题,并提出解决问题的几个方法,希望能给大家拓展一下思路。
下载固件
固件下载地址:http://support.dlink.com.cn/techsupport/DI-500WF-WT (目前最新版本为 2019-01-08)
下载固件的同时我们也将使用手册下载下来。
firmadyne 模拟
常规操作,按照 github 官网给出的提示一步步输入命令,到 inferNetwork 这一步会遇到问题,整个脚本运行过程中并没有报错,但脚本执行完毕并没有输出网卡信息。
这里我没有去深究原因,而是直接执行 run.sh 尝试启动镜像,内核信息输出正常,最后会打印一行
1 | Hit ENTER for console... |
但接着会输出一大堆 /proc/nvram: 没有这个文件或目录。
大概 1 分钟左右输出如下信息:
1 | [ 13.192000] do_page_fault() #2: sending SIGSEGV to init for invalid read access from |
显示出现错误,内核尝试 kill 掉 init 进程。熟悉 linux 的同学应该知道 init 是内核启动的第一个用户级别进程,是最基本的程序之一。连 init 进程都崩溃掉了,进入系统肯定无望。
增加 strace 选项之后简单梳理一下,崩溃的进程是 /sbin/rc,根目录中的 init 是它的符号链接。
init 进程尝试初始化 nvram 设置的时候会调用 libnvram.so 提供的接口,在此文件的 nvram_init 函数中试图访问 /proc/nvram 文件,猜测在原始内核初始化过程中会自动建立 /proc/nvram。
内核被我们替换了,自然不会自动创建 nvram 文件。这里最简单的想法就是手动创建一个空的 nvram 文件,先解决文件找不到的问题,至于其内容是否合法我们之后再谈。
不过又一个比较棘手的问题出现了,/proc 目录下存储的并不是普通的文件,而是虚拟文件系统,这些文件和内核模块有关。firmadyne 提供的内核是已经编译好的,我们很难去手动增删模块,而且编写内核模块又是一个很繁琐的事情。有没有一个更简单的方法呢?
这里我想到两个解决方案,一是下载标准的 MIPS 镜像(例如 debian),使用 qemu 的 system 模式启动,然后将固件的文件系统中的各个文件分别放在 qemu 系统的对应位置(例如 image/lib > /lib),再手动编写内核模块,创建 /proc/nvram ,之后尝试仿真。这种方法比较靠谱,但是操作及其麻烦。
二是在正常的虚拟机上解压镜像,在镜像的 /proc 目录下创建一个普通文件 nvram,再用 chroot + qemu 的方式启动。
综合来看第二种方式比较简单,于是动手试了下(操作前一定要先拍摄快照!!!)
1 | touch ./proc/nvram |
init 执行一段时间报错段错误,原先的找不到 nvram 错误已经消失。但这时候无论打开什么软件都没有响应,打开终端提示没有那个进程 QAQ。
不过还是有了意外收获,查看 /proc/nvram 文件发现里面已经填写了很多内容,一部分是标准的键值对格式,另外一部分类似于乱码,总归有了进展。
固件分析
firmadyne 模拟失败,qemu 模拟也失败,现在似乎没有什么头绪了。
这时候我决定先分析一下固件,先从 init 进程开始,init 是 /sbin/rc 的符号链接,所以把 rc 拷贝出来,丢进 ghidra 简单看一下。
一番查找找到了和 nvram 有关的部分代码,但是并没有访问 /proc/nvram 的迹象,查看导入函数发现绝大部分和 nvram 有关的函数来自 libnvram.so ,于是转而分析 libnvram.so。找到如下函数
1 | int nvram_init(void) |
原来是初始化过程中调用了 /proc/nvram 尝试读取相关配置,这时突然想到 firmadyne 提供了修改版的 libnvram.so ,能否直接替换过来解决问题呢?
再次用 firmadyne 模拟,不过解压固件之后手动替换 libnvram.so 。配置网络阶段依旧没有 IP。
查看日志发现和 nvram 有关的错误消失,不过多出来错误:
1 | /sbin/init: can't resolve symbol 'load_parameter |
由于我们替换了 libc,导致很多设备自己特有的函数丢失,运行依旧发生错误。
这里我想到2个解决方案:修改 firmadyne 提供的 libnvram.so 的代码,缺哪些 API 就增加上去。不过此方法工作量很大,而且需要一点点分析整个程序的逻辑。
第二个比较取巧,既然找不到 /proc 下的文件,我们又不能直接创建文件,那么可以尝试将访问 /proc/nvram 修改成访问 /test/nvram。
用 IDA patch libnvram.so(原版的),再用 firmadyne 模拟。
很遗憾,这样操作还是存在问题。查看日志提示无法访问 /proc/mtd。我简单看了一下,这个固件要访问的 /proc 文件很多,一个个去手动 patch 很麻烦,而且就算 patch 成功,也有很多配置的缺失,正常模拟的概率很低。(我按照报错信息 patch 了 mtd,但是和 nvram 不同,依旧报错,猜测是配置丢失或错误导致的)
通过查看用户手册,发现这款设备存在 web 管理界面,既然能通过 web 访问,肯定有和 http 相关的程序,在目录中搜索 http,发现一个名为 jhttpd 的程序,应该是自己实现的 web 服务器。
另外没有发现类似 html、js 等静态文件,猜测所有 web 处理逻辑全都集中在 jhttpd 中。
那么单独模拟 jhttpd 这个程序,是否就能看到 web 管理界面呢?
模拟单个程序非常简单,使用命令
1 | qemu-mips -L . ./usr/sbin/jhttpd |
执行起来提示找不到 nvram,按照上面的操作自己建立一个,再次执行,没出现任何报错信息。尝试用浏览器访问:
这样可以成功模拟设备的 web 服务。
按照用户手册提示的账户(admin:admin)登录,提示用户名或密码错误。
我逆向分析了一下 jhttpd 程序,发现他会尝试去 nvram 读取配置,其中包含账号和密码,nvram 中默认的是 admin:admin,但是我们自己创建的 nvram 并不包含这些配置,所以无法正常登录。
当 jhttpd 检测到无法读取用户名和密码时,会设置默认的用户名为 root,密码为 admin,尝试登陆成功。
不过,这种模拟方式还存在很多问题,我们现在只能看到 web 界面,并使用很少的一部分功能,大部分功能在发送请求之后会导致 jhttpd 崩溃,经过一番排查发现大部分错误发生在调用某 libc 函数时,例如 ping 功能会调用 fork 函数执行命令,不过 qemu 单独模拟的环境执行 fork 将失败。配置 wifi 的请求会调用某脚本,而模拟环境下脚本不能正常执行,进而整个进程会崩溃。
- Title: 固件模拟 Case Study (2)
- Author: Catalpa
- Created at : 2020-01-10 00:00:00
- Updated at : 2024-10-17 08:48:19
- Link: https://wzt.ac.cn/2020/01/10/firmadyne2/
- License: This work is licensed under CC BY-SA 4.0.