4、固件架构识别:CPU架构(ARM、MIPS、x86)
拿到一个陌生的固件,第一件事是什么?
别急着解包,也别急着跑模拟器。我个人的习惯是——先搞清楚它跑在什么CPU上。这就像你拿到一把钥匙,得先知道它开哪扇门。ARM、MIPS、x86,这三兄弟长得不一样,指令集不同,漏洞利用的方式也天差地别。
4.1 为什么架构识别这么重要?
说白了,你逆向分析时看到的汇编代码,完全取决于CPU架构。ARM用LDR/STR,MIPS用LW/SW,x86用MOV。如果你连目标架构都搞错了,那后面的分析就是缘木求鱼。
我在一次项目中就吃过这个亏。当时拿到一个路由器固件,想当然以为是ARM,结果用IDA反编译出来一堆看不懂的指令。折腾了半天才发现,这货居然是MIPS的。嗯,从那以后,我每次都会先确认架构,再动手。
核心要点:架构识别是固件分析的基石。选错工具链,后面的工作全是白费。
4.2 使用 file 命令快速识别
Linux下的file命令,是我最常用的第一道防线。它不看文件后缀,而是读文件头部的魔数(Magic Number),直接告诉你这是什么类型的文件。
用法很简单:
file firmware.bin
输出结果大概长这样:
firmware.bin: u-boot legacy uImage, Linux, ARM (ARM926EJ-S), OS Kernel Image (gzip)
看到没?ARM (ARM926EJ-S),架构信息直接写在脸上。如果是MIPS,它会显示MIPS (MIPS32)或者MIPS (MIPS64)。x86的话,通常是Intel 80386或者x86-64。
但这里有个坑——file命令只识别文件头。如果固件是加密的或者被混淆了,它可能什么都认不出来。我曾经遇到一个固件,file命令只返回了data,后来才发现是厂商自定义的头部格式。
小技巧:如果file命令没识别出来,可以试试binwalk -M,它能扫描固件中的已知签名,有时候能发现隐藏的架构信息。
4.3 使用 binutils 工具链深入分析
file命令只能看个大概,真要确认架构,还得靠binutils工具链。这套工具是GNU项目的一部分,专门用来处理二进制文件。我常用的几个命令:
- readelf:读取ELF文件头,信息最全
- objdump:反汇编,看指令集
- strings:提取可打印字符串,辅助判断
4.3.1 readelf:ELF文件的身份证
如果固件解包后得到的是ELF文件(比如Linux内核或应用程序),readelf就是最好的选择。
readelf -h vmlinux
输出示例:
ELF Header:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Machine: ARM
关键字段就是Machine。ARM、MIPS、x86都会直接显示。注意看Data字段,它告诉你字节序——ARM和x86通常是小端(little endian),MIPS则大小端都有。
注意:MIPS的字节序是个坑。我见过不少MIPS固件是大端的,如果你用小端的工具链去反汇编,出来的代码全是错的。一定要先确认字节序。
4.3.2 objdump:直接看指令
有时候文件头被破坏了,readelf读不出来。这时候我会用objdump直接反汇编一段代码,看指令特征。
objdump -d --start-address=0x1000 --stop-address=0x1100 firmware.bin
ARM的指令特征:
- 指令长度固定4字节(ARM模式)或2字节(Thumb模式)
- 常见指令:LDR、STR、MOV、ADD、SUB
- 条件执行:EQ、NE、GT等后缀
MIPS的指令特征:
- 指令长度固定4字节
- 常见指令:LW、SW、ADDU、SUBU、BEQ、BNE
- 延迟槽:分支指令后面紧跟的一条指令也会执行
x86的指令特征:
- 指令长度可变(1-15字节)
- 常见指令:MOV、PUSH、POP、CALL、RET
- 寄存器命名:EAX、EBX、ECX、EDX
我个人习惯是,先看几条指令,心里就有数了。ARM的指令看起来规整,MIPS的延迟槽很特别,x86的变长指令一眼就能认出来。
4.3.3 strings:辅助判断
strings命令提取固件中的可打印字符串,有时候也能提供线索。
strings firmware.bin | grep -i "arm\|mips\|x86\|intel"
如果固件里有类似/lib/ld-linux.so.3这样的路径,那基本就是ARM了。MIPS的链接器路径通常是/lib/ld.so.1。x86的话,/lib/ld-linux.so.2是32位,/lib64/ld-linux-x86-64.so.2是64位。
经验之谈:我曾经用strings在一个固件里找到了mips-linux-gcc的编译路径,直接确认了架构。有时候厂商的编译环境会留下痕迹,多留个心眼。
4.4 架构识别流程图
下面这张图是我自己总结的架构识别流程,按这个顺序走,基本不会出错:
4.5 实战案例:一个真实固件的架构识别
我记得有一次分析某品牌的路由器固件。拿到手是个.bin文件,大小16MB。先用file命令:
$ file firmware.bin
firmware.bin: data
嗯,没识别出来。厂商可能加了自定义头。接着用binwalk扫描:
$ binwalk -M firmware.bin
输出显示在偏移0x10000处有一个LZMA压缩的Linux内核。解包后得到vmlinux文件。再用readelf:
$ readelf -h vmlinux
Machine: MIPS R3000
确认了,是MIPS架构。但字节序呢?看Data字段:
Data: 2's complement, big endian
大端MIPS。好,这下工具链选对了。用objdump反汇编验证一下:
$ mips-linux-gnu-objdump -d vmlinux | head -20
看到lui、addiu、sw这些指令,心里踏实了。就是MIPS大端,没跑。
避坑指南:我曾经遇到一个固件,readelf显示Machine是ARM,但反汇编出来的指令全是乱的。后来发现是Thumb模式,需要用-M force-thumb参数。ARM的Thumb和ARM模式指令长度不同,这个坑我踩过,你们别踩。
4.6 架构识别速查表
| 架构 | 指令长度 | 常见指令 | 字节序 | file命令输出特征 |
|---|---|---|---|---|
| ARM | 4字节(ARM)/ 2字节(Thumb) | LDR, STR, MOV, ADD | 小端为主 | ARM, ARM926EJ-S, Cortex-A |
| MIPS | 4字节 | LW, SW, ADDU, BEQ | 大小端都有 | MIPS, MIPS32, MIPS64 |
| x86 | 1-15字节(可变) | MOV, PUSH, POP, CALL | 小端 | Intel 80386, x86-64 |
这张表我建议你保存下来。刚开始做固件分析时,我经常对着它对照。用多了就熟了,现在看一眼指令就能猜个八九不离十。
最后提醒一句:架构识别不是一锤子买卖。有时候固件里混着多个架构的代码(比如bootloader是ARM,内核是MIPS),或者有Thumb/ARM模式切换。多验证几次,别偷懒。
公众号:蓝海资料掘金营,微信deep3321