4、固件头分析与熵检测:固件头结构解析、熵值计算与可视化、大模型辅助判断加密/压缩区域
拿到一个陌生的固件,第一件事是什么?
我个人习惯,不是直接扔进反汇编器,而是先看看它的“身份证”——固件头。你想想看,一个固件少则几百KB,多则几十MB,里面藏着什么,总得有个目录吧?固件头就是这个目录。
4.1 固件头结构解析:从二进制里找规律
固件头说白了就是一段固定格式的元数据。不同芯片厂商、不同设备,格式千差万别。但万变不离其宗,常见的字段就那么几个:
- 魔数(Magic Number):比如 0xFEEDBEEF、0x12345678,用来标识固件类型
- 固件大小:整个固件或某个分区的长度
- 校验和/CRC:验证固件完整性
- 版本号:硬件版本或固件版本
- 入口地址:CPU 从哪开始执行
- 分区表:描述各个子区域的偏移和大小
我在项目中遇到过一台老式路由器,固件头只有 64 字节,但魔数居然是 0xDEADBEEF。嗯,一看就是工程师的恶趣味。但别笑,这个魔数帮我快速定位了固件类型,省了不少时间。
解析固件头,我一般用 Python 的 struct 模块。举个例子:
import struct
with open('firmware.bin', 'rb') as f:
header = f.read(64) # 假设头长度64字节
magic, size, crc, version = struct.unpack('<IIII', header[:16])
print(f'魔数: 0x{magic:08X}')
print(f'固件大小: {size} 字节')
print(f'CRC: 0x{crc:08X}')
print(f'版本: {version}')
当然,实际项目中头结构可能更复杂。我建议你先用 binwalk 扫一遍,它能自动识别很多常见固件头格式。但别完全依赖工具——手动解析能让你更清楚底层逻辑。
4.2 熵值计算与可视化:一眼看出加密区域
熵值,听起来很高大上,其实就是一个衡量数据“混乱程度”的指标。纯文本的熵值低,加密数据的熵值高。为什么?因为加密后的数据看起来像随机噪声,每个字节出现的概率差不多。
我刚开始做逆向时,遇到一个加密的固件分区,死活找不到解密算法。后来用熵值一看,好家伙,整个分区熵值接近 8.0(最大值),明显是加密了。这才意识到,原来不是算法藏得深,而是数据本身就没打算让你直接读。
计算熵值的公式很简单:
import math
from collections import Counter
def calculate_entropy(data):
if not data:
return 0
counter = Counter(data)
entropy = 0
for count in counter.values():
p = count / len(data)
entropy -= p * math.log2(p)
return entropy
但光看一个数字不够直观。我习惯把固件切成小块(比如每 256 字节),计算每块的熵值,然后画成热力图或折线图。这样一眼就能看出哪些区域是代码、哪些是数据、哪些是加密区。
- 熵值 < 4.0:大概率是文本、常量或未压缩的代码
- 熵值 4.0 ~ 6.5:可能是压缩数据或混合内容
- 熵值 > 6.5:高度随机,很可能是加密数据或已压缩
可视化我用 matplotlib,简单几行代码就能出图:
import matplotlib.pyplot as plt
def plot_entropy(data, block_size=256):
entropies = []
for i in range(0, len(data), block_size):
block = data[i:i+block_size]
entropies.append(calculate_entropy(block))
plt.figure(figsize=(12, 4))
plt.plot(entropies)
plt.xlabel('Block Index')
plt.ylabel('Entropy')
plt.title('Firmware Entropy Analysis')
plt.grid(True)
plt.show()
嗯,这里要注意:熵值高不一定就是加密,也可能是高强度压缩(比如 LZMA)。怎么区分?我后面会讲。
4.3 大模型辅助判断加密/压缩区域
说实话,光靠熵值判断加密还是压缩,有时候挺头疼的。两者熵值都很高,但结构完全不同。加密数据是“伪随机”,压缩数据其实有隐含的字典结构。
这时候,大模型就能派上用场了。我最近在尝试用 GPT-4 或本地部署的 LLaMA 来做辅助判断。怎么用?很简单:
- 把熵值高的区域截取一小段(比如前 512 字节)
- 用 hex 格式或 base64 编码后,丢给大模型
- 问它:“这段数据看起来是加密还是压缩?有什么特征?”
举个例子,我曾在项目中遇到一段熵值 7.8 的数据,自己看了半天没头绪。后来把前 128 字节喂给大模型,它回复说:“这段数据开头有 0x1F 0x8B,是 gzip 压缩的魔数。” 我这才恍然大悟——原来不是加密,是压缩!
我整理了一个简单的判断流程,用 SVG 画出来供你参考:
这个流程我用了大半年,准确率大概在 80% 左右。剩下的 20%,要么是自定义算法,要么是混合区域(比如一段代码里嵌了加密数据)。
4.4 实战:一个完整的分析示例
假设你拿到一个 4MB 的固件,叫 router_fw.bin。我们来走一遍流程:
- 第一步:用 hexdump 看前 64 字节,发现魔数 0x12345678,固件大小 0x400000(4MB),CRC 值 0xAABBCCDD。
- 第二步:计算全固件熵值,得到 6.2。嗯,不算特别高,但也不低。
- 第三步:分块计算熵值,发现偏移 0x100000 到 0x200000 的区域熵值高达 7.5,其他区域在 3.0~5.0 之间。
- 第四步:截取高熵区域前 256 字节,用 base64 编码后问大模型。它回复:“开头有 0x78 0x9C,是 zlib 压缩的魔数。”
- 第五步:用 Python 的 zlib 解压,成功得到明文数据——原来是一个文件系统镜像。
整个过程不到 10 分钟。如果没有熵值分析和模型辅助,我可能还在手动翻二进制呢。
好了,这一章就到这里。记住:工具是死的,思路是活的。多动手,多积累经验,你也能成为固件逆向的老手。
公众号:蓝海资料掘金营,微信deep3321