4. 固件加密识别与解密尝试
任务说明
本案例面向固件安全分析场景:拿到一个未知固件文件后,首先判断其是否经过加密或压缩处理,然后针对识别出的加密类型尝试常见密钥或弱密钥进行解密。目标不是破解强加密,而是识别出开发者可能使用的可预测密钥或弱加密方案(如硬编码密钥、ECB模式、空密钥、常见厂商默认密钥等),从而还原明文固件。
环境与依赖
- Python 3.8+
- pycryptodome:
pip install pycryptodome - binwalk(可选,用于辅助识别):
pip install binwalk或系统包管理器安装
识别思路
固件加密识别主要依据以下特征:
- 熵值分析:加密或压缩后的数据熵值接近8.0 bit/byte,而明文代码/文本通常为4.5~7.0。高熵是加密的强信号,但无法区分加密与压缩。
- 文件头检测:检查常见压缩格式头(如
1F 8B为gzip,50 4B为zip,FD 37 7A 58 5A为xz),若匹配则先解压再分析。 - 已知加密特征:部分厂商固件使用固定魔数标识加密区域(如某些路由器固件以
#UIMAGE或自定义头开始),可搜索字符串或模式。 - 结构规律性:若固件头部存在明文元数据(如版本、长度、校验值),而主体数据高熵,则主体可能被加密。
关键完整代码
以下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
验证方法与判定标准
- 构造测试样本:用以下命令生成一个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('测试固件已生成')
"
- 运行脚本:
python firmware_decryptor.py test_fw.bin - 通过条件:脚本输出包含
[成功] 检测到加密算法: AES-ECB,且生成test_fw.bin.decrypted文件,该文件熵值显著低于加密数据(通常<5.0),且包含可读字符串Hello Firmware!。 - 真实固件验证:对实际固件运行后,若输出“未匹配”,说明固件使用了非弱密钥或非ECB模式,需进一步使用更完整的密钥字典或侧信道分析——这属于后续案例范围。
常见问题
- 高熵但解密失败:可能使用了CBC/CTR等需要IV的模式,或密钥不在常见列表中。可检查固件头部是否明文存储IV。
- 误报:压缩数据也可能被误判为XOR成功。验证时需检查解密结果是否包含文件系统特征(如
sqfs、ubifs魔数)或可执行代码段。 - 性能:对大于10MB的固件,建议先截取前1MB进行分析,确认算法后再全量解密。