固件漏洞挖掘技术:静态分析、动态分析与符号执行
固件漏洞挖掘,说白了就是跟二进制代码较劲的过程。我做了这么多年固件安全,最深的体会是——没有银弹。静态分析、动态分析、符号执行,这三板斧各有各的脾气,也各有各的坑。今天我就把这三种方法掰开了讲,把我踩过的坑、总结的经验都倒出来。
静态分析:从代码里翻出问题
静态分析是最基础的手段。你不需要跑固件,只需要对着二进制文件或者源码,一行一行地找问题。我个人习惯先从字符串扫描入手,再深入代码审计。
字符串扫描——最快的“嗅探”
固件里藏着大量字符串。硬编码密码、调试接口路径、私钥注释……这些东西往往是最容易发现的漏洞。我常用的工具有 binwalk、strings、firmwalker。
strings 扫到一条 admin:admin123!@#,结果发现是后门账号。这种“看起来像密码”的字符串,十有八九就是漏洞。
字符串扫描的典型流程:
- 解包固件,提取文件系统
- 用
strings -n 6 firmware.bin提取所有可打印字符串 - 用正则过滤:
password、secret、key、debug、admin - 人工审查高可疑条目
代码审计——静态分析的硬功夫
字符串扫描只是开胃菜。真正的静态分析,得深入到反汇编和源码审计。我常用的工具有 Ghidra、IDA Pro、Radare2。
重点关注这几类漏洞:
- 缓冲区溢出:
strcpy、sprintf、memcpy不带长度检查 - 命令注入:
system()、popen()、exec()参数可控 - 整数溢出:乘法或加法后未检查边界
- 格式化字符串:
printf(user_input)这种写法
避坑指南: 我曾经审计一个 IoT 摄像头固件,发现 system("ping " + user_ip)。乍一看没问题,但用户输入 ; rm -rf / 就直接炸了。静态分析时,一定要追踪所有外部输入到危险函数的路径。
动态分析:让固件跑起来再测
静态分析能发现很多问题,但有些漏洞只有运行时才暴露。动态分析的核心思路是:让固件在模拟环境或真实硬件上跑起来,然后喂数据、看反应。
Fuzzing——暴力但有效
Fuzzing 就是往固件接口里塞各种畸形数据,看它会不会崩溃。我常用的工具有 AFL、libFuzzer、Boofuzz。
固件 Fuzzing 的难点在于:很多固件没有源码,没法插桩。我的做法是:
- 用
QEMU模拟运行固件 - 用
unicorn或PANDA做指令级插桩 - 针对网络服务、Web 接口、串口协议分别写 Fuzzer
| Fuzzing 工具 | 适用场景 | 我的评价 |
|---|---|---|
| AFL | 有源码的固件模块 | 覆盖率好,但需要编译插桩 |
| Boofuzz | 网络协议 Fuzzing | 适合黑盒测试,配置灵活 |
| libFuzzer | 库函数级别 | 速度快,适合单函数测试 |
注意: Fuzzing 不是跑得越久越好。我见过有人跑了一周,结果发现是 QEMU 模拟器有 bug,不是固件崩溃。一定要先验证 crash 是否可复现、是否真实。
模拟执行——没有硬件也能跑
很多固件依赖特定硬件外设,比如 GPIO、UART、SPI。没有真实芯片,怎么跑?我的方案是:
- QEMU 系统模式:模拟整个 SoC,适合 Linux 内核级固件
- Unicorn:轻量级 CPU 模拟器,适合单函数或代码片段
- Avatar²:混合模拟,把真实硬件和模拟器结合起来
我记得有一次分析一个工控 PLC 固件,它依赖一个自定义的硬件定时器。QEMU 不支持,我就用 Unicorn 写了个 hook,模拟定时器中断。折腾了两天,但最终跑通了。
符号执行与污点分析:精确打击
Fuzzing 像地毯式轰炸,符号执行就像精确制导导弹。它能自动推导出触发特定路径的输入条件。
符号执行——把代码变成方程
符号执行的核心思想:把程序变量当作符号,而不是具体值。然后通过约束求解器(如 Z3)算出“怎么走才能到达目标代码”。
我常用的工具有 Angr、KLEE、Triton。以 Angr 为例,一个典型的漏洞利用路径探索:
import angr
proj = angr.Project('firmware_binary')
state = proj.factory.entry_state()
simgr = proj.factory.simulation_manager(state)
# 目标地址:漏洞触发点
target_addr = 0x400800
simgr.explore(find=target_addr)
if simgr.found:
solution = simgr.found[0]
print("触发漏洞的输入:", solution.posix.dumps(0))
我的经验: 符号执行最大的问题是路径爆炸。一个循环展开 100 次,约束求解器就炸了。我一般先做静态分析缩小范围,只对关键函数做符号执行。
污点分析——追踪数据流向
污点分析就是给数据打标签。用户输入是“脏数据”,一路追踪它流到哪里。如果脏数据到了危险函数(如 system()、memcpy()),就可能是漏洞。
污点分析分两种:
- 静态污点分析:不运行代码,通过数据流图分析。工具如
FlowDroid、TAJ - 动态污点分析:运行时跟踪。工具如
PANDA、Triton、libdft
我更喜欢动态污点分析。为什么?因为静态分析容易漏报。比如通过指针间接赋值,静态分析很难追踪。动态分析虽然慢,但准确。
实战技巧: 把符号执行和污点分析结合起来。先用污点分析找出“脏数据”流经的代码区域,再用符号执行精确计算触发条件。我在分析一个智能锁固件时,就是用这个组合找到了一个绕过 PIN 码验证的漏洞。
知识体系总览
下面这张图,是我对本章知识体系的总结。三种技术不是孤立的,而是互补的。
你看这张图,三种技术各有侧重。静态分析适合早期快速筛查,动态分析能发现运行时异常,符号执行和污点分析则擅长精确追踪。我个人的工作流是:先用静态分析扫一遍,圈出可疑函数;然后用 Fuzzing 批量测试;最后对 crash 做符号执行,写出 PoC。
嗯,这里要注意一点。很多人觉得符号执行是万能的,其实不是。遇到复杂循环、系统调用、浮点运算,约束求解器很容易超时。我的建议是:把符号执行当作手术刀,而不是大锤。只对关键路径用,别指望它覆盖整个固件。