IoT Practice Session
Created May 19, 2023
本文内容仅用于个人存档和技术交流学习,禁止使用文中内容进行未授权或恶意的攻击行为。
本文用于记录上海交大 NIS8017 Course,固件模拟习题课的相关材料。
附件下载
下载地址:https://drive.google.com/file/d/1Co4NSlLv1aCTQHIYwUsyTc5SX2wsVHrq/view?usp=drive_link
系统级固件模拟(D-Link DIR823G)
系统级模拟的优势:
- 更加稳定,利用整个内核和文件系统
- 支持一些系统级别的配置
使用FirmAE模拟DIR823G,并对goahead服务进行调试。
课前准备
请大家按照Github提供的指示,在Linux环境下安装并配置好FirmAE框架。
感兴趣的同学可以进一步阅读相关论文:
- Towards automated dynamic analysis for linux-based embedded firmware
- FirmAE: Towards large-scale emulation of IoT firmware for dynamic analysis
这部分附件的文件结构如下。
(如果在使用FirmAE/download.sh
下载FirmAE所需依赖时遇到网络问题,可下载binaries,并将其中的二进制文件拷贝到FirmAE对应的文件夹中)
.
├── DIR823GA1_FW102B03.bin
└── exp_release.py
模拟过程
将附件中的固件复制到FirmAE/firmwares/
后,执行以下命令:
cd FirmAE/
sudo ./run.sh -r dlink ./firmwares/DIR823GA1_FW102B03.bin # run mode
# or
sudo ./run.sh -d dlink ./firmwares/DIR823GA1_FW102B03.bin # user-level debug mode
以debug模式为例,模拟成功后会提供如下界面:

访问http://192.168.0.1/也可以看到路由器的管理界面。
注意,第一次模拟需要的时间会比较长,约10~20min,因为FirmAE需要对固件的网络配置进行推断。第一次模拟成功后,FirmAE会把相关配置记录到数据库中,下一次模拟就会比较快了。
调试方法
使用上述debug模式模拟固件后,有五种操作可供选择,选择2. connext to shell
连接到shell,可以看到/firmadyne
下已经准备好了一些工具。
以web服务程序goahead为例,在模拟固件的shell中执行以下命令:
# 在模拟的路由器shell里,启动gdbserver
ps | grep goahead # 获取goahead的pid
cd /firmadyne
./gdbserver --attach :1234 [pid]
在本地另起一个terminal,执行以下命令,用gdb-multiarch远程连接到模拟环境下的goahead进程,进行调试:
sudo apt install gdb-multiarch
gdb-multiarch
#下面的命令是在gdb中输入:
set arch mips:isa32r2
set follow-fork-mode child
target remote 192.168.0.1:1234
# 举例:在某system调用处下断点
break *0x423A14
continue
# 发送的数据触发断点后,即可进一步查看上下文
以下exp脚本可以触发0x423A14处的命令注入漏洞点:
import requests
IP='192.168.0.1'
payload = "something"
length = len(payload)
headers = requests.utils.default_headers()
headers["Content-Length"]=str(length)
headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.76 Safari/537.36"
headers["SOAPAction"] = '"http://purenetworks.com/HNAP1/GetClientInfo"'
headers["Content-Type"] = "text/xml; charset=UTF-8"
headers["Accept"]="*/*"
headers["Accept-Encoding"]="gzip, deflate"
headers["Accept-Language"]="zh-CN,zh;q=0.9,en;q=0.8"
r = requests.post('http://'+IP+'/HNAP1/', headers=headers, data=payload)
print(r.text)
作业要求:
分析goahead二进制并完善上述脚本,实现通过命令注入获得路由器shell的效果。(主要希望同学们能上手操作一下gdb调试)
(更多系统级模拟的例子可以参考:IoT-vulhub)
用户级固件模拟(Netgear XR300 )
以Netgear为例:构建Qiling+binary的环境,模拟的目标是upnpd。
课前准备
请大家提前安装好Qiling框架,推荐版本如下:
# python3.8及以上
pip3 install qiling==1.4.5
pip3 install pwntools
pip3 install ipdb
这部分附件的文件结构如下:
.
├── exp_release.py
├── README.md
├── sim_xr300.py
├── upnpd
└── XR300-V1.0.3.50_10.3.36.chk(固件)
可以先阅读附件中的Readme,尝试使用Binwalk对固件进行解包。
漏洞介绍
一般的攻击面:大部分目标仍以通过LAN/WAN实现远程代码执行 • httpd / net-cgi –web服务 • upnpd – 即插即拔服务 • ftpd / telnetd / …
我们这次选择upnpd,它的特点: • 无用户界面
如何进行漏洞挖掘: • 搜索协议规范相关字符串-> 定位请求处理流程 • 搜索关键函数-> 定位数据传入点
选择recvfrom函数(接收数据的函数):

第一个参数是fd,是一个全局变量,查看交叉引用可以看到其定义过程,绑定在了1900端口

回到数据的处理流程:程序对数据包内容要求带有:
- M-SEARCH
- *
- HTTP/1.1
- MAN:
- “ssdp:discover”
然后会进入以下的程序中,会取出传入数据的MX:至\r\n的数据内容并拷贝到栈上。

由于输入整个数据包的长度最大为0x1fff,而这里的栈上buffer大小明显小于我们所可控的数据长度,因此可发生栈溢出。
模拟过程
从哪里开始模拟?一种解决方案——snapshot
- 让程序先从入口点开始执行,执行到main函数开头后,保存当前的所有执行状态,然后直接修改pc寄存器到我们需要模拟的函数开头处
- 实质是一种构造函数call_state的方法
在本例当中,我们选择sub_199B4函数作为我们需要模拟的函数,因为这个函数没有参数,而且包含了socket的创建过程和整个漏洞函数。
Qiling能自己模拟libc库中的函数,但是无法模拟NVRAM硬件支持的函数,解决方案:hook修改返回值
- acosNvramConfig_get
- acosNvramConfig_match
调试方法
- 使用gdb进行远程调试(对异构的支持不太好,但是可以试试)
- 使用qdb进行调试(qiling内置的debugger)
- 使用pyhton的调试器ipdb,可以hook对应的地址选择对应的操作。
在Qiling脚本中添加:from ipdb import set_trace
在想要停止的位置address添加hook代码:ql.hook_address(callback, address)
在callback函数中添加set_trace()
然后通过python3 -m ipdb sim_xr300.py
来运行脚本
在callback函数中会停止运行,这个时候可以交互式地使用python代码来查询对应的寄存器和内存。比如以下的命令:
#从内存中读取字符串
ql.mem.string(address)
#在栈偏移offset的地方读取内存
ql.stack_read(offset)
#读取address地方的size大小的内存
ql.mem.read(address, size)
#读取sp寄存器
ql.arch.regs.sp
#读取map的区域分布
ql.mem.show_mapinfo()
作业要求:
分析upnpd二进制,在exp_release.py
的基础上完善利用脚本,构造ROP链执行system("/bin/sh")
以获取shell。
(可以在解包后的文件系统根目录下新建一个flag文件,利用成功的效果就是能够读取flag内容)
注意:通过ROP执行system("/bin/sh")
后,cat flag
的命令是在qiling模拟的那个窗口中输入的,和CTF中常见的pwn题环境不太一样。