跳到主要内容

IoT 固件漏洞:从输入可达到真实设备上的利用条件

固件里存在危险函数,只能提供研究线索。要判断设备是否能被攻击,需要沿输入入口追到实际处理逻辑,再确认攻击者能够影响什么,以及异常会造成崩溃、数据泄露还是代码执行。

源码实操:沿 CVE-2020-7982 追踪一次校验失效​

OpenWrt 的这个历史漏洞发生在 opkg 软件包管理器。设备使用签名索引取得软件包摘要,但摘要解析失败后没有建立预期校验值,后续安装分支因而跳过比较。这里使用真实修复提交及其父提交核对调用链;总览中的 OpenWrt 24.10.5 只用于认识固件结构,不作为本漏洞的受影响样本。

取得同一修复前后的四份源文件​

在自己的 iot-research 目录执行。两个提交分别固定到修复前与修复后,避免跟随移动中的主分支:

export OPKG_BEFORE=80d161eb5b95ceb51db989196405eaa00950e03b
export OPKG_AFTER=c09fe2098718807ddbca13ee36e3e38801822946
mkdir -p opkg-before opkg-after
for part in file_util.c pkg.c pkg_parse.c opkg_install.c; do
curl -fL "https://raw.githubusercontent.com/openwrt/opkg-lede/$OPKG_BEFORE/libopkg/$part" -o "opkg-before/$part"
curl -fL "https://raw.githubusercontent.com/openwrt/opkg-lede/$OPKG_AFTER/libopkg/$part" -o "opkg-after/$part"
done
diff -u opkg-before/file_util.c opkg-after/file_util.c

diff 发现差异时退出码为 1,是这里的正常结果。应看到 s 从函数入口初始化,改为在跳过空白之后、进入解码循环时初始化。按上游修复 c09fe20对照,不自行重写解析器作为目标。

补丁中决定解码起点的两行如下,省略了没有变化的循环体:

- for (p = buf, *len = 0;
+ for (s = (unsigned char *)src, p = buf, *len = 0;

从索引字段跟到实际安装判断​

rg -n -A12 -B4 'SHA256sum|pkg_set_sha256' opkg-before/pkg_parse.c opkg-before/pkg.c
rg -n -A28 'checksum_hex2bin' opkg-before/file_util.c
rg -n -A22 -B4 'pkg_expected_size =|pkg_sha256 =' opkg-before/opkg_install.c

依次检查以下四处,把输入、状态变化与分支连接起来:

位置实际处理为什么会影响下一步
pkg_parse.c 的 SHA256sum 分支将冒号后的内容交给 pkg_set_sha256正常索引字段中保留了前导空白
checksum_hex2bin推进 src 跳过空白,解码指针 s 却仍指向原处十六进制判断在空白处结束,返回长度为 0
pkg_set_sha256长度不是 32 字节就返回空值包对象没有保存预期 SHA-256
opkg_install.c 的校验分支仅在取到预期摘要时比较文件摘要缺少摘要被当作“不需要比较”,未阻止后续处理

这里的危险输入不要求先伪造签名索引。真实索引通过签名验证,解析后的安全状态却丢失了;文件大小检查仍在,因此替换包还需要满足索引记录的大小。攻击者还必须能够影响软件下载链路,且设备实际执行安装。OpenWrt 公告

解释修复改变了什么​

修复把解码指针初始化放到去除空白之后,使正常 SHA256sum 字段能够得到 32 字节摘要;安装阶段便能执行原有比较。继续审计时还应检查摘要缺失、非法十六进制、摘要计算失败和显式强制选项各自走哪条分支,确认错误是否默认拒绝。修复前 file_util.c、pkg.c、opkg_install.c

将这两个提交的原始转换函数分别独立编译,使用相同的 64 个字符的十六进制字符串对照,得到以下函数级结果。这里只验证上游函数,未执行完整 opkg 安装:

输入形式修复前解码长度修复后解码长度
无前导空白的 64 个十六进制字符32 字节32 字节
同一字符串前增加一个空格0 字节32 字节

研究者从异常包进入解包器这一现象继续追到摘要校验失效。审阅其原始报告时,区分校验绕过、后续解包器内存错误及恶意内容执行;源码差异本身只证明控制流变化,不等于已在设备完成利用。

把这条分析方法迁移到无源码固件​

先提取根文件系统,用 file 与 readelf -h -l 确定目标程序的架构、解释器和装载段。总览镜像内的 /usr/sbin/uhttpd 是 x86-64 PIE,已移除节表;Ghidra 导入时按 ELF 装载段分析,不能只靠节名寻找逻辑。

在 Ghidra 建立工程、导入 ELF 并运行 Auto Analyze 后,从实际请求关联的错误字符串、接口路径或导入函数查看交叉引用。像本案一样沿“字段读取 → 状态保存 → 校验条件 → 危险副作用”记录每个实际函数;有版本匹配源码时,再回到源码核对反编译类型。网络服务的正常请求与运行进程对应方法见管理接口。

成立前提与能力变化​

漏洞必须位于攻击者能够触达的实际构建中,输入能够进入缺陷分支,并导致可观察的越界、状态错误或危险调用。获得代码执行还要满足具体架构、编译与内存保护条件。研究应沿“入口 → 解码与解析 → 危险操作 → 执行身份与可用资源”追踪,不能把危险函数名称直接当作完整利用链。

从攻击者可达的输入开始​

同一设备可能接收 Web 请求、蓝牙报文、MQTT 消息、USB 数据、配置文件和云端下发内容。入口的认证状态决定了攻击者起点:公网未认证、局域网已登录、无线近距离、已取得本地权限,不能相互替代。

解析链也可能跨进程。网关先解包,业务进程再解释字段,驱动最后向外设写入数据。前一层确认消息格式,并不自动保证后一层的长度、索引和业务参数安全。审计应记录每次转换后的长度单位,特别是字符数与字节数、压缩前后大小、分片与重组后的总长度。

内存破坏需要继续追踪结果​

典型问题包括长度加法溢出后分配过小缓冲区、复制长度大于目标容量、释放后继续使用对象,以及状态机在异常顺序下访问未初始化数据。越界读、越界写和可控控制流是不同能力,报告不能把一次重启直接写成远程代码执行。

嵌入式环境的利用条件还受处理器架构、指令集模式、字节序、编译选项、内存布局和保护机制影响。某型号上成立的返回地址覆盖,不代表换一个 Flash 布局或 SDK 版本仍能执行。就算执行成功,也要确认是受限协议任务、普通用户进程,还是拥有广泛硬件访问权的固件上下文。

AMNESIA:33 展示了基础库进入设备的路径​

Forescout 于 2020 年公布的 AMNESIA:33 研究涉及 uIP、FNET、picoTCP 和 Nut/Net 四类开源 TCP/IP 栈,公开材料把影响区分为内存破坏、代码执行、拒绝服务和信息泄露。它说明漏洞可能发生在应用认证之前的网络解析层,而不只是设备的 Web 页面。AMNESIA:33 原始研究入口

这类问题的关键工程难点是识别实际构建:产品品牌、操作系统名称和协议栈来源未必一致。厂商可能使用多年以前的 SDK 分支,并在本地改动后重新打包。上游发布修复,不证明现场设备已经包含修补后的代码。

类似地,2020 年 SweynTooth 原始披露区分了不同 BLE SDK 中的崩溃、死锁和安全绕过,也指出终端产品需要自行集成芯片厂商补丁。这里引用的是当时研究覆盖的实现,不是对所有 BLE 产品的判断。SweynTooth 原始披露

沿真实解析路径检查长度与容量​

在目标程序中标出长度字段的读取、类型转换、总长度计算、分配与复制位置。若程序先用窄整数计算总长度,再分配缓冲区,长度加法溢出可能使分配值变小;后面的复制却仍使用原始声明长度,形成越界写。判断需要落到实际反编译与汇编路径。

沿这条路径逐项检查:实际输入是否足够包含头部,声明长度能否用目标类型表达,是否同时受剩余输入与协议上限约束,总长度计算是否溢出,以及分配和复制是否使用同一个已验证的长度。修复需要覆盖这些条件。

若消息允许分片,还需要在重组层约束累计长度、片段数量、重复片段和等待时间。单个片段都合法,不证明组合后的对象仍在预算内;业务授权也不应在重组完成前被默认继承。

复现记录应把输入中可控字段、进入的解析函数、发生异常的操作与实际权限连接起来。对比修复版本时,还要核对同类入口是否共用同一解析器,避免只修 HTTP 路径,却保留相同的消息队列入口。

仿真和模糊测试分别能证明什么​

文件系统解包适合发现程序、配置、版本和秘密材料。用户态仿真适合研究独立解析函数,但缺少真实内核、驱动与外设时,可能无法复现完整设备行为。全系统仿真覆盖面更广,也可能因为缺少硬件模型而走不到危险分支。

模糊测试应围绕明确入口构造输入,并记录覆盖与崩溃。一个高质量样本需要说明触发了哪条代码路径、错误发生在哪里、是否能在实际目标版本复现。仅在人为替换依赖后出现的异常,需要保留环境差异。

这些方法形成互补证据:静态分析解释根因,受控运行证实异常,真实样机确认入口与影响。引用的设备漏洞来自对应原始披露;自己的复现应另外保留目标版本、请求与运行证据。

把修复落实到输入和权限边界​

输入层应在复制、解压、索引和状态转换之前检查边界;处理变长字段时,使用能够表达实际容量的类型,并检查整数计算是否溢出。对业务命令,参数列表和明确操作类型通常比把字符串拼成 Shell 命令更容易约束。

运行层则应缩小协议解析任务的权限,减少其对密钥、设备控制接口与文件系统的直接访问。资源受限 MCU 上未必能使用完整进程隔离,但仍可评估 MPU、任务边界和硬件访问限制。隔离失效的残余风险需要与芯片能力对应。

修复验证应从原始触发样本开始,再补充分片、截断、最大长度和异常顺序用例;同时核对设备固件哈希与构建来源。通过认证页面或看到“已升级”的 UI,无法证明底层解析代码已经改变。

需要继续分析危险解释器与进程权限时,可对照命令执行;文件上传后调用解析器的跨组件路径见GitLab ExifTool RCE。

关联阅读:设备协议攻击、资源耗尽与恢复设计。