4、静态分析工具链:IDA Pro/Ghidra基础使用、字符串分析、交叉引用与函数识别
静态分析,说白了就是不动代码,只看二进制文件本身。你拿到一个固件,先别急着上调试器,先看看它里面藏了些什么。我做了这么多年逆向,发现很多关键信息其实就明晃晃地写在文件里,只是你没去看。
这一章,我们聊聊静态分析的两把利器——IDA Pro 和 Ghidra。它们就像你的左右手,各有千秋。我会结合自己的使用习惯,带你走一遍基础操作,重点放在字符串分析、交叉引用和函数识别上。这些东西,是后续分析升级机制的基础。
4.1 工具选择:IDA Pro vs Ghidra
先说说我个人的选择。IDA Pro 是老牌劲旅,功能强大,但价格不菲。Ghidra 是 NSA 开源的,免费,而且这几年发展很快。你问我用哪个?我两个都用。看场景。
| 特性 | IDA Pro | Ghidra |
|---|---|---|
| 价格 | 商业软件,价格高 | 免费开源 |
| 反编译质量 | 非常成熟,尤其对 ARM | 优秀,持续改进中 |
| 脚本支持 | IDAPython,生态丰富 | Python 3,Jython,插件多 |
| 协作功能 | 较弱 | 内置协作服务器 |
| 学习曲线 | 中等 | 稍陡,但文档好 |
| 对嵌入式支持 | 处理器模块丰富 | 通过插件扩展,社区活跃 |
我个人习惯是:快速分析用 Ghidra,因为它免费且协作方便。深度逆向,尤其是遇到混淆或复杂结构时,我会切回 IDA Pro。你想想看,工具是死的,人是活的。别纠结哪个更好,关键是哪个更适合你手头的活儿。
4.2 加载固件:第一步
拿到一个固件,第一步就是把它加载到工具里。这里有个坑,我踩过好几次。
在 IDA Pro 里,加载裸固件时,选择 "Binary file"。然后手动指定处理器类型(比如 ARM Little-Endian 或 Cortex-M)。基地址(Base Address)很关键。我记得有一次分析一个 IoT 设备的固件,默认基地址是 0x0,结果所有函数调用都指向了错误的位置。后来我通过分析启动代码,发现正确的基地址是 0x08000000。改过来之后,整个世界都清晰了。
Ghidra 的处理方式类似。导入时选择 "Raw Binary",然后设置语言(Language)和基地址。Ghidra 的自动分析功能很强大,但有时候也会过度分析,导致一些数据被误识别为代码。这时候,就需要手动干预了。
4.3 字符串分析:寻找关键线索
字符串,是固件里的「明文宝藏」。升级固件里,通常会包含版本号、错误信息、日志输出、URL 地址、密钥提示等等。这些信息,能帮你快速定位关键函数。
在 IDA Pro 里,打开 Strings 窗口(Shift + F12)。你会看到所有被识别出的字符串。我一般会先过滤一下,找找看有没有 "upgrade"、"update"、"flash"、"version"、"error" 这些关键词。
在 Ghidra 里,同样有 "Defined Strings" 窗口。你可以按长度、类型排序。我个人习惯是,先看那些长度适中、看起来像英文或拼音的字符串。那些全是乱码的,通常是加密数据或代码,先放一边。
我曾经在一个路由器固件里,通过搜索 "admin" 和 "password",直接找到了硬编码的后门账户。这种低级错误,在 IoT 设备里并不少见。
4.4 交叉引用:谁在调用谁?
交叉引用(Xrefs),是逆向分析的核心。它能告诉你,一个函数被谁调用了,一个数据被谁使用了。说白了,就是帮你理清代码的调用关系。
在 IDA Pro 里,选中一个函数名,按 Ctrl + X,就能看到所有交叉引用。Ghidra 里,右键点击函数,选择 "References" -> "Show References to..."。效果是一样的。
我举个例子。你找到了一个字符串 "Start upgrade",然后通过交叉引用,找到了调用它的函数 upgrade_start()。再对 upgrade_start() 做交叉引用,你可能会发现它被一个网络回调函数调用了。这样,你就把「收到升级命令」和「执行升级流程」这两个点串起来了。
交叉引用还能帮你识别库函数。比如,你看到 memcpy、strlen 这些标准函数,它们通常有大量的交叉引用。通过分析这些引用,你可以快速了解固件中哪些地方在处理数据。
4.5 函数识别:从汇编到逻辑
函数识别,就是把一段连续的汇编代码,识别成一个独立的逻辑单元。好的工具会自动完成大部分工作,但总有一些边界情况需要手动处理。
在 IDA Pro 里,它通过分析函数序言(prologue)和尾声(epilogue)来识别函数。比如 ARM 下的 PUSH {LR} 和 POP {PC}。但有些固件为了节省空间,会使用尾调用(tail call)或者内联函数,这时候 IDA 就可能识别失败。
Ghidra 的识别算法更激进一些,它会尝试分析所有可能的执行路径。但这也意味着,它可能会把一些数据误识别为函数。遇到这种情况,你需要手动取消定义(Undefine),或者重新创建函数(Create Function)。
我个人的经验是:不要完全信任工具的自动识别。尤其是在分析启动代码(Reset_Handler)和中断向量表时,一定要手动确认。启动代码里通常包含了内存布局、堆栈初始化等关键信息,搞错了,后面的分析全白费。
4.6 知识结构图:静态分析流程
下面这张图,是我自己总结的静态分析基本流程。你可以把它当作一个检查清单。
4.7 实战案例:定位升级函数
光说不练假把式。我们用一个虚拟的案例,把上面的知识串起来。
假设你拿到一个 IoT 设备的固件 fw.bin。你把它加载到 Ghidra 里,基地址设为 0x10000000。然后打开字符串窗口,搜索 "upgrade"。
你找到了几个字符串:
"upgrade_start""upgrade_process""upgrade_finish"
双击 "upgrade_start",跳转到它的地址。然后右键 -> References -> Show References to...。你发现它被一个地址为 0x10001234 的函数引用了。
跳转到 0x10001234,按 D 键(在 Ghidra 里是 F 键)创建函数。Ghidra 会尝试反编译它。你看到反编译结果里,调用了 flash_erase() 和 flash_write()。嗯,这基本可以确定,就是升级流程的入口了。
接下来,你可以继续分析 upgrade_process 和 upgrade_finish,逐步还原整个升级逻辑。这个过程,就是静态分析的核心。
4.8 避坑指南
最后,分享几个我踩过的坑,希望能帮你省点时间。
- 基地址错误: 这是最常见的问题。加载固件前,先看看启动代码,或者搜索已知的向量表模式(比如 ARM 的
0x00 0x00 0x00 0x20开头的栈指针)。 - 字符串编码: 有些固件用 UTF-16 或 ASCII,但工具可能只识别了一种。在 IDA 里,可以手动设置字符串类型(
Alt + A)。 - Thumb/ARM 切换: ARM 固件经常在 Thumb 和 ARM 模式间切换。如果反汇编看起来不对,检查一下处理器模式设置。
- 花指令: 有些固件会插入花指令(junk code)来干扰反汇编。遇到这种情况,需要手动修复,或者用脚本去除。