3. 固件熵分析

任务目标

对固件二进制文件执行熵分析,通过滑动窗口计算局部熵值,判断整个固件是否经过加密或压缩(整体高熵),并定位固件内部的高熵区域(如密钥存储、压缩段)与低熵区域(如向量表、文件系统头部)。

实现原理

熵衡量数据的随机程度,取值范围为0~8 bit/byte。普通代码和常量数据熵值通常较低(<6.5),而加密数据或已压缩内容的熵值接近8。通过分块计算熵值并绘制曲线,可以直观识别固件中不同性质的数据区域。

完整代码

import math
import sys
import struct
from collections import Counter

def shannon_entropy(data: bytes) -> float:
    """计算单块数据的香农熵(bit/byte)"""
    if not data:
        return 0.0
    counter = Counter(data)
    length = len(data)
    entropy = 0.0
    for count in counter.values():
        p = count / length
        entropy -= p * math.log2(p)
    return entropy

def analyze_firmware(filepath: str, block_size: int = 256, threshold: float = 7.5):
    """
    滑动窗口计算固件局部熵值。
    返回 (整体熵, 高熵区域列表, 低熵区域列表)
    """
    with open(filepath, 'rb') as f:
        firmware = f.read()

    total_size = len(firmware)
    if total_size == 0:
        print("文件为空")
        return

    # 整体熵
    overall_entropy = shannon_entropy(firmware)

    # 分块熵
    high_regions = []
    low_regions = []
    offset = 0
    block_index = 0

    while offset < total_size:
        block = firmware[offset:offset + block_size]
        ent = shannon_entropy(block)
        start = offset
        end = min(offset + block_size, total_size) - 1

        if ent >= threshold:
            high_regions.append((start, end, round(ent, 3)))
        elif ent < 5.0:
            low_regions.append((start, end, round(ent, 3)))

        offset += block_size
        block_index += 1

    # 输出结果
    print(f"文件大小: {total_size} bytes")
    print(f"整体熵值: {overall_entropy:.4f} bit/byte")

    if overall_entropy > 7.5:
        print("判定: 固件整体呈高熵,可能整体加密或压缩")
    elif overall_entropy > 6.0:
        print("判定: 固件混合区域,部分数据可能加密/压缩")
    else:
        print("判定: 固件整体低熵,可能为明文代码/数据")

    print(f"\n高熵区域 (熵值>={threshold}):")
    for start, end, ent in high_regions[:20]:
        print(f"  偏移 0x{start:08X} - 0x{end:08X}  熵值 {ent}")

    print(f"\n低熵区域 (熵值<5.0):")
    for start, end, ent in low_regions[:20]:
        print(f"  偏移 0x{start:08X} - 0x{end:08X}  熵值 {ent}")

    # 返回供外部使用的数据
    return {
        'overall_entropy': overall_entropy,
        'high_regions': high_regions,
        'low_regions': low_regions,
        'block_size': block_size,
        'total_size': total_size
    }

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("用法: python entropy_analysis.py <固件文件路径> [块大小,默认256]")
        sys.exit(1)
    path = sys.argv[1]
    bs = int(sys.argv[2]) if len(sys.argv) > 2 else 256
    analyze_firmware(path, bs)

运行方式

# 基本用法(块大小默认256字节)
python entropy_analysis.py firmware.bin

# 指定块大小(例如512字节)
python entropy_analysis.py firmware.bin 512

验证方法

使用已知明文固件和加密固件各一份进行对比测试:

  1. 明文固件(如未加密的ARM Cortex-M固件):整体熵值通常低于6.5,且低熵区域集中在起始向量表(偏移0x0附近)和只读数据段。
  2. 加密固件(如AES加密后的镜像):整体熵值应接近8.0,且所有分块熵值均高于7.5,不存在明显低熵区域。
  3. 混合固件(部分压缩/加密):熵曲线会呈现明显的高低起伏,高熵区域对应压缩或加密段,低熵区域对应明文头部或配置数据。

通过观察输出中高/低熵区域的偏移范围,即可判断固件是否加密/压缩,并定位具体区域。

预期输出示例

文件大小: 1048576 bytes
整体熵值: 7.9821 bit/byte
判定: 固件整体呈高熵,可能整体加密或压缩

高熵区域 (熵值>=7.5):
  偏移 0x00000000 - 0x000000FF  熵值 7.991
  偏移 0x00000100 - 0x000001FF  熵值 7.985
  ...

常见问题

  • 块大小选择:256字节适合大多数场景。块过小(<64)会导致熵值波动剧烈,产生大量误报;块过大(>4096)会掩盖小尺寸高熵区域(如256字节的AES密钥)。
  • 阈值调整:若固件包含大量UTF-8字符串,其熵值约6.0~6.5,可适当降低高熵阈值至7.0以识别压缩段。
  • 高熵区域定位精度:定位精度等于块大小。如需精确定位,可在发现高熵区域后,对该区域使用更小的块(如32字节)二次扫描。
固件熵值分布示意(块大小256B) 0 4 8 熵值(bit/byte) 固件偏移地址 0x0000 0x4000 0x8000 0xC000 0x10000 向量表/启动代码 加密/压缩段 明文代码段 文件系统/配置 高熵阈值 7.5 明文/低熵区 加密/高熵区 文件系统/中熵区

上图展示了典型混合固件的熵值分布模式:起始向量表区域熵值较低(约4~6),随后加密段熵值接近8,明文代码段熵值回落至5~7,末尾文件系统区域熵值中等。实际分析时,将脚本输出的高/低熵区域偏移与此模式对照,即可快速判断固件结构。