4. 固件加密检测与识别

任务目标

给定一个未知固件镜像文件,通过熵分析、字符串搜索等方法判断其是否经过加密或压缩处理,并在判定为加密后尝试识别可能的加密算法类型。本案例以典型的嵌入式固件(如U-Boot、RTOS镜像)为分析对象,输出可复现的检测流程与判定结论。

环境与依赖

  • Python 3.8+,需安装 numpy(熵计算)与 binwalk(可选,用于辅助文件签名扫描)
  • 示例固件:自行构造或使用真实固件,本案例使用一个模拟加密的二进制文件作为输入

核心实现

以下代码实现三个核心功能:分块熵计算、字符串可打印性检测、常见加密算法特征匹配。输入为固件文件路径,输出为判定结果与可能的算法候选。

import math
import re
import struct
from collections import Counter

def calculate_entropy(data: bytes) -> float:
    """计算字节序列的香农熵"""
    if not data:
        return 0.0
    freq = Counter(data)
    entropy = -sum((count / len(data)) * math.log2(count / len(data)) for count in freq.values())
    return entropy

def block_entropy_analysis(data: bytes, block_size: int = 4096) -> list:
    """分块计算熵值,返回每块的熵列表"""
    entropies = []
    for i in range(0, len(data), block_size):
        block = data[i:i+block_size]
        if len(block) < 64:  # 忽略过小的尾部块
            continue
        entropies.append(calculate_entropy(block))
    return entropies

def printable_string_ratio(data: bytes) -> float:
    """计算可打印ASCII字符串占比(长度≥4的连续可打印字符)"""
    printable_chars = set(bytes(range(0x20, 0x7f)))
    strings = re.findall(rb'[\x20-\x7e]{4,}', data)
    total_string_len = sum(len(s) for s in strings)
    return total_string_len / len(data) if data else 0.0

def detect_encryption_patterns(data: bytes) -> list:
    """检测常见加密/压缩算法特征"""
    candidates = []
    # AES: 查找可能的S盒特征(不精确,仅作提示)
    if b'\x63\x7c\x77\x7b\xf2\x6b\x6f\xc5' in data[:4096]:
        candidates.append('AES (S-box pattern found in header)')
    # XOR: 检测重复密钥长度(通过周期性自相关简单判断)
    if len(data) > 1024:
        sample = data[:1024]
        best_period = 0
        best_score = 0
        for period in range(2, 65):
            score = 0
            for i in range(period, len(sample)):
                if sample[i] == sample[i - period]:
                    score += 1
            if score > best_score:
                best_score = score
                best_period = period
        if best_period > 0 and best_score / len(sample) > 0.6:
            candidates.append(f'Possible XOR with key length {best_period}')
    # LZMA/gzip 压缩特征
    if data[:6] == b'\xfd7zXZ\x00':
        candidates.append('XZ/LZMA compressed')
    elif data[:2] == b'\x1f\x8b':
        candidates.append('gzip compressed')
    elif data[:4] == b'\x28\xb5\x2f\xfd':
        candidates.append('zstd compressed')
    return candidates

def analyze_firmware(filepath: str) -> dict:
    """主分析函数:返回判定结果"""
    with open(filepath, 'rb') as f:
        data = f.read()
    
    result = {
        'file_size': len(data),
        'overall_entropy': calculate_entropy(data),
        'block_entropies': [],
        'printable_ratio': 0.0,
        'is_encrypted': False,
        'likely_algorithm': [],
        'notes': []
    }
    
    # 1. 整体熵判断
    overall_ent = result['overall_entropy']
    if overall_ent > 7.5:
        result['is_encrypted'] = True
        result['notes'].append('High overall entropy suggests encryption or compression')
    elif overall_ent < 5.0:
        result['notes'].append('Low entropy: likely plaintext or lightly structured data')
    else:
        result['notes'].append('Medium entropy: further analysis required')
    
    # 2. 分块熵分析(检测是否均匀高熵)
    block_ents = block_entropy_analysis(data)
    result['block_entropies'] = block_ents
    if block_ents:
        avg_block_ent = sum(block_ents) / len(block_ents)
        if avg_block_ent > 7.2 and max(block_ents) - min(block_ents) < 0.5:
            result['is_encrypted'] = True
            result['notes'].append('Uniform high block entropy: consistent with encryption')
    
    # 3. 字符串检测
    printable_ratio = printable_string_ratio(data)
    result['printable_ratio'] = printable_ratio
    if printable_ratio < 0.05 and result['is_encrypted']:
        result['notes'].append('Very few printable strings, consistent with encrypted payload')
    elif printable_ratio > 0.3:
        result['is_encrypted'] = False
        result['notes'].append('High printable ratio: likely plaintext firmware')
    
    # 4. 算法特征匹配
    if result['is_encrypted']:
        result['likely_algorithm'] = detect_encryption_patterns(data)
        if not result['likely_algorithm']:
            result['likely_algorithm'] = ['Unknown: consider AES, ChaCha20, or custom XOR']
    
    return result

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 2:
        print('Usage: python fw_encrypt_detect.py <firmware_file>')
        sys.exit(1)
    res = analyze_firmware(sys.argv[1])
    print(f'File size: {res["file_size"]} bytes')
    print(f'Overall entropy: {res["overall_entropy"]:.4f} bits/byte')
    print(f'Printable string ratio: {res["printable_ratio"]:.4f}')
    print(f'Encrypted: {res["is_encrypted"]}')
    if res['likely_algorithm']:
        print('Possible algorithms:')
        for alg in res['likely_algorithm']:
            print(f'  - {alg}')
    for note in res['notes']:
        print(f'Note: {note}')

运行与验证

  1. 保存上述代码为 fw_encrypt_detect.py
  2. 准备测试固件:
    dd if=/dev/urandom of=test_enc.bin bs=1024 count=64(生成随机数据模拟加密固件)
    echo "U-Boot 2024.01" > test_plain.bin && dd if=/dev/urandom bs=1 count=2048 >> test_plain.bin(生成含明文头的混合固件)
  3. 运行检测:
    python3 fw_encrypt_detect.py test_enc.bin
    python3 fw_encrypt_detect.py test_plain.bin

通过条件:

  • 对随机数据文件,程序输出 Encrypted: True,整体熵值 > 7.5,可打印字符串占比 < 0.05
  • 对含明文头的文件,程序输出 Encrypted: False(因可打印字符串占比高于阈值)或熵值处于中等区间并给出相应说明
  • 若固件头部包含AES S盒特征字节,程序能识别出AES候选

判定逻辑说明

熵值接近8.0表示数据接近随机分布,是加密或压缩的强信号;均匀的高分块熵进一步排除局部明文区域。可打印字符串占比是区分明文与密文的直接指标。算法识别基于特征字节匹配与统计模式,对于未知算法只能给出可能性提示,实际算法确认需结合固件加载地址、已知固件格式或硬件加密引擎文档进一步分析。

固件加密检测决策流程 输入固件镜像 计算整体熵 + 分块熵 熵 > 7.5 且分块均匀 → 疑似加密 可打印字符串占比检测 占比 < 5% → 强化加密判定 是否加密? 综合熵+字符串 是 算法特征匹配 AES S盒 / XOR周期 压缩头 / 统计模式 否 明文固件 可直接进行字符串提取 函数定位与反汇编 输出算法候选列表 输出明文判定