4、固件结构分析:固件文件格式、加载地址与入口点、向量表分析、段解析、固件解包与打包
做固件逆向,第一步就是跟固件文件打交道。你拿到的可能是个.bin,也可能是个.hex,或者是个.elf。这三种格式,说白了就是不同阶段的产物。我刚开始接触时也犯过迷糊,以为它们都一样,结果分析半天发现地址全不对。嗯,这里咱们得捋清楚。
4.1 固件文件格式:bin、hex、elf
这三种格式,我习惯这么理解:
- .bin:最纯粹的二进制镜像。没有地址信息,没有元数据,就是裸数据。你把它烧到Flash的哪个地址,它就在哪个地址执行。我遇到过不少新手,拿到.bin直接反汇编,结果不知道加载基址,反出来的代码全是错的。
- .hex:Intel HEX格式,带地址信息。每一行都记录了数据应该放在哪个地址。说白了,就是给烧录器看的。我个人觉得,做逆向时.hex比.bin友好,因为地址信息直接写在文件里。
- .elf:可执行与链接格式。这是最完整的,包含段信息、符号表、调试信息。如果你能拿到.elf文件,那逆向工作至少省一半力气。我在项目中遇到过客户只给了.bin,我硬着头皮猜加载地址,折腾了两天才搞定。
核心区别一句话总结:.bin是裸数据,.hex是带地址的裸数据,.elf是带符号和段信息的完整可执行文件。
4.2 固件加载地址与入口点
这是逆向的第一个拦路虎。你拿到一个.bin,不知道它该加载到哪个地址,反汇编出来的代码全是错的。为什么会这样?因为ARM的跳转指令(比如B、BL)用的是相对偏移,但数据访问指令(比如LDR)用的是绝对地址。
我一般这么找加载地址:
- 看向量表:ARM的向量表前四个字节是栈指针,后四个字节是复位向量。复位向量指向的地址,通常就是入口点。
- 查中断向量:如果固件里有中断向量表,向量地址往往能反推加载基址。
- 用工具猜:比如binwalk的“-A”选项可以尝试自动分析架构和入口点。
个人经验:我习惯先用binwalk扫一遍,看看有没有已知的固件头或者文件系统。如果没有,就手动看前16个字节。ARM的向量表很有特征——第一条指令通常是LDR PC, [PC, #offset]或者直接B指令。找到复位向量后,减去偏移就能算出加载基址。
4.3 向量表分析
ARM的向量表,说白了就是一张“异常处理跳转表”。它固定在地址0x00000000(或者0xFFFF0000,取决于系统配置)。每个向量占4个字节,一共8个向量。
| 偏移 | 异常类型 | 说明 |
|---|---|---|
| 0x00 | 复位 | 上电或复位后执行的第一条指令 |
| 0x04 | 未定义指令 | CPU遇到不认识的操作码 |
| 0x08 | 软件中断(SWI) | 系统调用入口 |
| 0x0C | 预取中止 | 指令预取失败 |
| 0x10 | 数据中止 | 数据访问失败 |
| 0x14 | 保留 | 未使用 |
| 0x18 | IRQ | 普通中断请求 |
| 0x1C | FIQ | 快速中断请求 |
我曾经分析过一个IoT设备的固件,发现它的向量表里复位向量指向0x08000101。注意这个地址的最后一个bit是1——这表示Thumb模式。嗯,这里要注意,ARM的向量表里存的地址,最低位用来指示指令集模式(0=ARM,1=Thumb)。
避坑指南:我曾经因为没注意向量表里地址的LSB,把Thumb代码当成ARM代码反汇编,结果出来的指令全是乱的。记住:地址最低位是模式标志,不是真正的地址位。
4.4 段(.text, .data, .bss)解析
一个典型的ARM固件,内存布局大概是这样:
- .text:代码段。只读,存放指令和常量。我习惯从这里开始分析主逻辑。
- .data:数据段。存放已初始化的全局变量和静态变量。这部分在Flash里有个初始值,启动时会被拷贝到RAM里。
- .bss:未初始化数据段。存放未初始化的全局变量。启动时会被清零。
怎么在.bin里找到这些段的边界?我一般这么干:
- 先找到向量表,确定复位向量指向的地址。
- 从复位向量开始,反汇编启动代码。启动代码里通常会有拷贝.data和清零.bss的循环。
- 找到这些循环里用的源地址和目的地址,就能确定段的边界。
实用技巧:如果你看到类似“LDR R0, =_sdata”、“LDR R1, =_edata”这样的指令,那恭喜你——这是编译器生成的段边界符号。直接记下这些地址就行。
4.5 固件解包与打包
很多固件不是裸的.bin,而是被封装过的。常见的有:
- 文件系统固件:比如SquashFS、JFFS2、CramFS。用binwalk可以自动识别和提取。
- 加密固件:整个固件被加密,需要先解密才能分析。我遇到过AES加密的,也遇到过简单的XOR加密。
- 压缩固件:用LZMA、gzip等压缩。binwalk可以自动解压。
- 自定义格式:厂商自己定义的封装格式,有头部、校验、分段等。
我常用的解包工具链:
- binwalk:第一道工序,扫描固件里的已知签名。
- dd:手动切割固件,提取指定偏移和大小的数据块。
- hexdump / xxd:查看固件头部结构。
- sasquatch:专门解SquashFS文件系统。
- jefferson:解JFFS2文件系统。
打包逆向:有时候你需要修改固件再打包回去。我建议先分析原厂的打包工具,或者自己写脚本模拟打包过程。注意校验和——很多设备会校验固件的完整性,改了内容不更新校验和,设备会拒绝启动。我曾经因为漏了CRC校验,把设备刷成砖,最后用JTAG才救回来。
4.6 知识体系结构图
下面这张图,是我自己梳理的固件结构分析流程。你跟着这个思路走,基本不会迷路。
这张图从左到右,从上到下,就是我分析固件的标准流程。你跟着这个思路走,基本不会漏掉关键步骤。
最后提醒一句:固件分析没有银弹。每个厂商都有自己的“小九九”,有的会在固件头里藏校验,有的会加混淆。我遇到过最离谱的一个固件,厂商把整个向量表都挪到了别的位置,还在原位置放了假数据。嗯,这种时候就只能靠经验硬啃了。
公众号:蓝海资料掘金营,微信deep3321