4. 固件加密识别与解密尝试

任务说明

本案例面向固件安全分析场景:拿到一个未知固件文件后,首先判断其是否经过加密或压缩处理,然后针对识别出的加密类型尝试常见密钥或弱密钥进行解密。目标不是破解强加密,而是识别出开发者可能使用的可预测密钥或弱加密方案(如硬编码密钥、ECB模式、空密钥、常见厂商默认密钥等),从而还原明文固件。

环境与依赖

  • Python 3.8+
  • pycryptodome:pip install pycryptodome
  • binwalk(可选,用于辅助识别):pip install binwalk 或系统包管理器安装

识别思路

固件加密识别主要依据以下特征:

  1. 熵值分析:加密或压缩后的数据熵值接近8.0 bit/byte,而明文代码/文本通常为4.5~7.0。高熵是加密的强信号,但无法区分加密与压缩。
  2. 文件头检测:检查常见压缩格式头(如1F 8B为gzip,50 4B为zip,FD 37 7A 58 5A为xz),若匹配则先解压再分析。
  3. 已知加密特征:部分厂商固件使用固定魔数标识加密区域(如某些路由器固件以#UIMAGE或自定义头开始),可搜索字符串或模式。
  4. 结构规律性:若固件头部存在明文元数据(如版本、长度、校验值),而主体数据高熵,则主体可能被加密。

关键完整代码

以下Python脚本实现:熵值计算、压缩格式识别、常见AES/DES/XOR解密尝试。代码使用最小可复现方式,针对单个固件文件执行完整分析流程。

import sys
import struct
import zlib
from math import log2
from collections import Counter
from Crypto.Cipher import AES, DES

# ---------- 熵值计算 ----------
def entropy(data: bytes) -> float:
    if not data:
        return 0.0
    freq = Counter(data)
    length = len(data)
    return -sum((c / length) * log2(c / length) for c in freq.values())

# ---------- 压缩格式检测与解压 ----------
def try_decompress(data: bytes):
    # gzip
    if data[:2] == b'\x1f\x8b':
        try:
            return zlib.decompress(data, 16 + zlib.MAX_WBITS)
        except Exception:
            pass
    # zlib
    if data[:2] in (b'\x78\x01', b'\x78\x9c', b'\x78\xda'):
        try:
            return zlib.decompress(data)
        except Exception:
            pass
    # xz (需要lzma模块)
    if data[:6] == b'\xfd7zXZ\x00':
        try:
            import lzma
            return lzma.decompress(data)
        except Exception:
            pass
    return None

# ---------- 常见密钥列表 ----------
COMMON_KEYS = [
    b'1234567890abcdef',      # 16字节
    b'0000000000000000',
    b'abcdefghijklmnop',
    b'0123456789abcdef',
    b'ffffffffffffffff',
    b'1234567890123456',
    b'\x00' * 16,
    b'\xff' * 16,
    b'0123456789abcdef0123456789abcdef',  # 32字节
    b'12345678901234567890123456789012',
    b'\x00' * 32,
    b'\xff' * 32,
]

# ---------- AES解密尝试 ----------
def try_aes_decrypt(data: bytes):
    # 尝试ECB模式(无IV)
    for key in COMMON_KEYS:
        for mode_name, mode in [('ECB', AES.MODE_ECB)]:
            try:
                cipher = AES.new(key[:16] if len(key) >= 16 else key.ljust(16, b'\x00'), mode)
                dec = cipher.decrypt(data[:len(data) - len(data) % 16])
                # 检查解密结果是否像明文(可打印字符比例高)
                printable = sum(1 for b in dec[:256] if 32 <= b <= 126 or b in (9, 10, 13))
                if printable / min(len(dec), 256) > 0.8:
                    return f'AES-{mode_name}', key, dec
            except Exception:
                continue
    return None

# ---------- DES解密尝试 ----------
def try_des_decrypt(data: bytes):
    for key in COMMON_KEYS:
        try:
            cipher = DES.new(key[:8], DES.MODE_ECB)
            dec = cipher.decrypt(data[:len(data) - len(data) % 8])
            printable = sum(1 for b in dec[:256] if 32 <= b <= 126 or b in (9, 10, 13))
            if printable / min(len(dec), 256) > 0.8:
                return 'DES-ECB', key, dec
        except Exception:
            continue
    return None

# ---------- XOR解密尝试(单字节/多字节重复密钥) ----------
def try_xor_decrypt(data: bytes):
    # 单字节XOR
    for k in range(256):
        dec = bytes(b ^ k for b in data[:512])
        printable = sum(1 for b in dec if 32 <= b <= 126 or b in (9, 10, 13))
        if printable / len(dec) > 0.85:
            return f'XOR-0x{k:02x}', bytes([k]), bytes(b ^ k for b in data)
    # 多字节重复密钥(长度2~16)
    for key_len in range(2, 17):
        for key in COMMON_KEYS[:8]:  # 用常见密钥作为多字节XOR密钥
            if len(key) < key_len:
                continue
            k = key[:key_len]
            dec = bytes(data[i] ^ k[i % key_len] for i in range(min(512, len(data))))
            printable = sum(1 for b in dec if 32 <= b <= 126 or b in (9, 10, 13))
            if printable / len(dec) > 0.85:
                return f'XOR-repeat-{key_len}', k, bytes(data[i] ^ k[i % key_len] for i in range(len(data)))
    return None

# ---------- 主流程 ----------
def analyze_firmware(filepath: str):
    with open(filepath, 'rb') as f:
        raw = f.read()

    print(f'文件大小: {len(raw)} bytes')
    print(f'整体熵值: {entropy(raw):.4f} bit/byte')

    # 1. 尝试解压
    decompressed = try_decompress(raw)
    if decompressed:
        print(f'检测到压缩格式,解压后大小: {len(decompressed)} bytes')
        print(f'解压后熵值: {entropy(decompressed):.4f} bit/byte')
        target = decompressed
    else:
        target = raw

    # 2. 熵值判断
    ent = entropy(target)
    if ent < 7.0:
        print('熵值较低,固件可能未加密(明文或轻压缩)')
        print('建议:直接使用binwalk或strings分析')
        return

    print('熵值较高,疑似加密或强压缩,尝试解密...')

    # 3. 尝试解密
    result = try_aes_decrypt(target)
    if not result:
        result = try_des_decrypt(target)
    if not result:
        result = try_xor_decrypt(target)

    if result:
        algo, key, plain = result
        print(f'[成功] 检测到加密算法: {algo}')
        print(f'[成功] 使用密钥: {key.hex()}')
        out_path = filepath + '.decrypted'
        with open(out_path, 'wb') as f:
            f.write(plain)
        print(f'解密结果已保存: {out_path}')
        print(f'解密后熵值: {entropy(plain):.4f} bit/byte')
    else:
        print('未匹配到常见密钥/算法')
        print('建议:尝试更长的密钥字典,或检查固件头部是否包含密钥/IV信息')

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print(f'用法: python {sys.argv[0]} <firmware_file>')
        sys.exit(1)
    analyze_firmware(sys.argv[1])

运行方式

python firmware_decryptor.py firmware.bin

验证方法与判定标准

  1. 构造测试样本:用以下命令生成一个AES-ECB加密的测试固件,验证脚本能正确识别并解密:
python -c "
from Crypto.Cipher import AES
import os
key = b'1234567890abcdef'
plain = b'Hello Firmware! This is a test.' + os.urandom(16)  # 填充到32字节
cipher = AES.new(key, AES.MODE_ECB)
enc = cipher.encrypt(plain[:32])
with open('test_fw.bin', 'wb') as f:
    f.write(b'\x00' * 64 + enc)  # 模拟头部+加密主体
print('测试固件已生成')
"
  1. 运行脚本:python firmware_decryptor.py test_fw.bin
  2. 通过条件:脚本输出包含[成功] 检测到加密算法: AES-ECB,且生成test_fw.bin.decrypted文件,该文件熵值显著低于加密数据(通常<5.0),且包含可读字符串Hello Firmware!。
  3. 真实固件验证:对实际固件运行后,若输出“未匹配”,说明固件使用了非弱密钥或非ECB模式,需进一步使用更完整的密钥字典或侧信道分析——这属于后续案例范围。

常见问题

  • 高熵但解密失败:可能使用了CBC/CTR等需要IV的模式,或密钥不在常见列表中。可检查固件头部是否明文存储IV。
  • 误报:压缩数据也可能被误判为XOR成功。验证时需检查解密结果是否包含文件系统特征(如sqfs、ubifs魔数)或可执行代码段。
  • 性能:对大于10MB的固件,建议先截取前1MB进行分析,确认算法后再全量解密。