4、固件识别:从文件类型到基地址的全链路分析

拿到一个思科路由器固件,第一件事是什么?

不是急着解包,也不是直接扔进反汇编器。我个人的习惯是——先做固件识别。这一步就像医生看病前的望闻问切,看似基础,但能帮你避开后面80%的坑。

4.1 file命令:第一眼判断文件类型

在Linux终端里,我最常用的命令就是 file。它通过读取文件头部的Magic Number来判断文件类型,准确率相当高。

$ file c2960s-universalk9-mz.152-4.E7.bin
c2960s-universalk9-mz.152-4.E7.bin: data

嗯,这里显示的是 data,说明file命令没识别出来。别慌,这很正常。思科的固件往往经过特殊封装,不是标准的ELF或PE格式。

换个思路,我们看看是不是压缩过的:

$ file c2960s-universalk9-mz.152-4.E7.bin
c2960s-universalk9-mz.152-4.E7.bin: gzip compressed data, from Unix

看到了吧?有时候固件本身就是gzip压缩包。我在分析Cisco 2960系列时,遇到过好几次这种情况。直接解压就能拿到真正的固件镜像。

小技巧: 如果file命令返回"data",试试用 hexdump -C | head 看看文件头。思科固件常见的Magic Number包括0x2000、0x4000、0x6000等,这些往往对应不同的平台架构。

4.2 binwalk熵分析:发现隐藏的宝藏

file命令只能看表面,但固件内部可能藏着多个文件系统、压缩块、甚至加密分区。这时候就要请出我们的老朋友——binwalk

binwalk的熵分析功能,说白了就是检测数据块的随机性。高熵区域通常代表加密或压缩数据,低熵区域则可能是明文代码或文件系统。

$ binwalk -E c2960s-universalk9-mz.152-4.E7.bin

这条命令会生成一张熵图。我一般会重点关注那些熵值突然跳变的位置——那里往往是不同文件系统的分界线。

举个例子,我在分析Cisco 3800系列时,熵图显示在0x200000处有一个明显的熵值下降。后来验证,那里正是Squashfs文件系统的起始位置。

核心要点: 熵分析不是万能的,但结合binwalk的签名扫描(-B参数),能帮你快速定位固件中的文件系统、内核镜像、Bootloader等关键组件。

4.3 Magic Number:固件头的身份证

每个固件都有自己独特的头部标识,也就是Magic Number。思科不同系列的固件,Magic Number差异很大。

固件系列 Magic Number 说明
Cisco IOS (传统) 0x2000 或 0x4000 常见于老款路由器,如2500、2600系列
Cisco IOS (新) 0x6000 用于7200、7500等高端平台
Cisco IOS-XE 0x7F454C46 (ELF头) 基于Linux内核,本质是ELF文件
Cisco NX-OS 0x1F8B (gzip头) 通常先解压再分析

怎么找Magic Number?用hexdump看一眼文件开头:

$ hexdump -C c2960s-universalk9-mz.152-4.E7.bin | head -5
00000000  1f 8b 08 00 00 00 00 00  00 03 ec bd 07 60 1c 47  |.............`.G.|
00000010  96 27 8a aa ea ee 9e 99  9e 99 99 99 99 99 99 99  |.'..............|
...

看到开头的 1f 8b 了吗?这就是gzip的Magic Number。所以这个固件其实是压缩包,解压后才能看到真正的IOS镜像。

注意: 有些固件会在头部添加自定义校验或加密,导致Magic Number被隐藏。我曾经遇到过Cisco ASA的固件,头部被XOR加密,需要先解密才能看到真正的Magic Number。这种情况下,binwalk的熵分析反而更可靠。

4.4 基地址分析:找到代码的落脚点

基地址,说白了就是固件在内存中的加载地址。没有它,反汇编出来的地址全是错的,根本没法看。

怎么找基地址?我一般用两种方法:

  1. 查看Bootloader输出: 如果固件是从Flash启动的,Bootloader通常会打印加载地址。比如Cisco 2600系列的Bootloader会显示 Loading at 0x80001000...
  2. 分析固件头: 有些固件头里直接包含了基地址信息。比如IOS的固件头,在偏移0x10处通常存放着加载地址。

举个例子,用binwalk扫描后,我们找到了一个疑似内核的镜像块:

$ binwalk -Me c2960s-universalk9-mz.152-4.E7.bin
...
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             gzip compressed data
...
0x200000      0x200000        Squashfs filesystem, little endian, version 4.0
...

对于Squashfs文件系统,基地址通常是0x200000。但如果是内核镜像,基地址就需要根据架构来推断。MIPS架构的基地址一般是0x80000000,ARM架构则可能是0xC0000000或0x00008000。

我的经验: 如果实在找不到基地址,可以试试用 strings 命令搜索内核符号表。比如搜索 start_kernelinit_task,然后根据符号地址反推基地址。这个方法我在分析Cisco 3800时用过,效果不错。

知识体系总览

下面这张图,是我自己总结的固件识别流程。每次拿到新固件,我都会按这个步骤走一遍:

固件识别全链路流程图 固件文件 file命令识别文件类型 检测Magic Number,判断是否为压缩包 binwalk熵分析 检测高熵/低熵区域,定位文件系统 Magic Number特征识别 确认固件系列(IOS/IOS-XE/NX-OS) 基地址分析 确定加载地址,为反汇编做准备 常见Magic Number 0x2000 → IOS传统 0x4000 → IOS传统 0x6000 → IOS高端 0x7F45 → IOS-XE 0x1F8B → NX-OS 常见基地址: MIPS: 0x80000000 ARM: 0xC0000000 x86: 0x00100000

这张图把整个流程串起来了。从拿到固件开始,先用file命令看表面,再用binwalk做熵分析挖内部结构,接着通过Magic Number确认固件系列,最后确定基地址。每一步都有它的价值,缺一不可。

总结一下: 固件识别不是一锤子买卖,而是一个层层递进的过程。我见过太多人跳过这一步直接解包,结果解出来的文件根本没法用。花10分钟做好识别,能省下后面10小时的调试时间。

好了,这一章的内容就到这里。记住,固件识别是逆向分析的地基,地基打不牢,后面全是空中楼阁。


专注资料整理