3. 固件熵分析

固件熵分析 学习卡片

任务目标

对固件二进制文件进行熵值分析,通过滑动窗口计算局部熵值,识别固件中可能存在的加密或压缩区域,并判断整个固件是否经过加密处理。

实现原理

熵值衡量数据的随机程度。未加密的固件通常包含大量可执行代码和结构化数据,熵值较低(约4.0~6.5);而加密或压缩后的数据接近均匀分布,熵值趋近8.0。通过计算整个文件的全局熵和分块局部熵,可以定位高熵区域并判断固件是否整体加密。

完整实现代码

import math
import sys
from collections import Counter

def calculate_entropy(data: bytes) -> float:
    """计算字节序列的香农熵"""
    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 global_entropy(filepath: str) -> float:
    """计算整个文件的全局熵"""
    with open(filepath, 'rb') as f:
        data = f.read()
    return calculate_entropy(data)

def sliding_window_entropy(filepath: str, block_size: int = 256, 
                           step: int = 128) -> list:
    """
    滑动窗口计算局部熵
    返回 [(offset, entropy), ...]
    """
    results = []
    with open(filepath, 'rb') as f:
        data = f.read()
    
    total_len = len(data)
    pos = 0
    while pos + block_size <= total_len:
        block = data[pos:pos + block_size]
        ent = calculate_entropy(block)
        results.append((pos, ent))
        pos += step
    
    # 处理尾部不足一个块的数据
    if total_len % step != 0 and total_len > block_size:
        tail_start = total_len - block_size
        if tail_start > results[-1][0]:
            tail_block = data[tail_start:]
            results.append((tail_start, calculate_entropy(tail_block)))
    
    return results

def analyze_firmware(filepath: str, block_size: int = 256, 
                     threshold: float = 7.5) -> dict:
    """综合分析固件熵特征"""
    # 全局熵
    g_entropy = global_entropy(filepath)
    
    # 局部熵
    local_entropies = sliding_window_entropy(filepath, block_size)
    
    # 统计高熵区域
    high_entropy_blocks = [(offset, ent) for offset, ent in local_entropies 
                          if ent > threshold]
    
    # 高熵区域占比
    high_ratio = len(high_entropy_blocks) / len(local_entropies) if local_entropies else 0
    
    # 判断是否整体加密
    is_encrypted = g_entropy > 7.8 and high_ratio > 0.9
    
    return {
        'file_size': len(open(filepath, 'rb').read()),
        'global_entropy': g_entropy,
        'block_count': len(local_entropies),
        'high_entropy_blocks': high_entropy_blocks[:10],  # 只显示前10个
        'high_entropy_ratio': high_ratio,
        'likely_encrypted': is_encrypted
    }

def print_report(result: dict) -> None:
    """打印分析报告"""
    print(f"文件大小: {result['file_size']} 字节")
    print(f"全局熵值: {result['global_entropy']:.4f}")
    print(f"分块数量: {result['block_count']}")
    print(f"高熵块占比: {result['high_entropy_ratio']*100:.1f}%")
    print(f"整体加密判定: {'是' if result['likely_encrypted'] else '否'}")
    
    if result['high_entropy_blocks']:
        print("\n高熵区域示例(偏移, 熵值):")
        for offset, ent in result['high_entropy_blocks'][:5]:
            print(f"  0x{offset:08X}  {ent:.4f}")

if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("用法: python entropy_analysis.py <固件文件路径>")
        sys.exit(1)
    
    fw_path = sys.argv[1]
    report = analyze_firmware(fw_path)
    print_report(report)

运行方式

# 基本用法
python entropy_analysis.py firmware.bin

# 自定义块大小和阈值(可选)
python entropy_analysis.py firmware.bin  # 默认块大小256字节,阈值7.5

验证方法与判定标准

使用已知样本进行验证:

  1. 未加密固件:取一个正常的ARM Cortex-M固件(如STM32编译产物),全局熵值通常在5.0~6.8之间,高熵块占比低于50%,判定结果为“否”。
  2. 加密固件:将上述固件用AES或XOR加密后保存为新文件,全局熵值应接近8.0,高熵块占比超过90%,判定结果为“是”。
  3. 局部加密固件:仅加密固件中某一段(如偏移0x1000~0x2000),该区域局部熵值应明显高于其他区域,通过输出的高熵块偏移可定位加密范围。

通过条件:对已知加密/未加密样本,程序输出的判定结果与实际情况一致;对局部加密样本,高熵块偏移与加密起始位置吻合。

常见问题

  • 块大小选择:256字节适合大多数场景;块过小(<64)导致熵值波动大,块过大(>1024)会掩盖小范围加密区域。
  • 阈值调整:7.5作为高熵阈值适用于大多数情况;若固件包含大量常量数据(如查找表),可适当降低至7.0。
  • 尾部处理:当文件大小不是步长的整数倍时,代码会额外检查最后一个完整块,避免遗漏尾部高熵区域。