4. ECU固件提取与逆向分析
任务目标
本案例针对汽车ECU(电子控制单元)固件安全分析场景,完成两项核心任务:(1)从固件二进制文件中提取关键安全信息(如UDS安全访问种子/密钥算法参数、CAN报文ID表);(2)对固件进行静态逆向分析,定位安全访问认证逻辑并还原其密钥生成算法。通过本案例,掌握ECU固件分析的基本流程与工具链使用方法。
环境与依赖
- 操作系统:Ubuntu 20.04+ / Kali Linux
- Python 3.8+,需安装
pip install capstone pyelftools - 逆向工具:Ghidra(或IDA Free)、binwalk、hexdump
- 示例固件:本案例使用一个模拟的ECU固件二进制文件(基于ARM Cortex-M3架构),由脚本自动生成,无需真实硬件
固件提取与逆向分析实现
以下Python脚本完成三个关键步骤:(1)生成模拟ECU固件(包含安全访问算法与CAN报文表);(2)静态扫描固件,提取CAN ID表与安全访问种子/密钥算法特征;(3)反汇编关键代码段,还原密钥生成逻辑。
#!/usr/bin/env python3
"""
ECU固件提取与逆向分析 - 最小可复现实例
功能:生成模拟固件 → 提取安全关键信息 → 反汇编认证函数
"""
import struct
import subprocess
import tempfile
import os
from pathlib import Path
# ========== 第1步:生成模拟ECU固件(ARM Cortex-M3小端) ==========
def generate_firmware(path: str):
"""构造包含安全访问算法和CAN报文表的模拟固件"""
code = bytearray()
# --- 安全访问种子/密钥算法 (地址0x08000000) ---
# 算法: key = (seed ^ 0xA5A5) + 0x1F2E3D4C (32位运算)
# 对应ARM Thumb指令序列
seed_key_func = bytes.fromhex(
"0x4A" # LDR R2, [PC, #8] ; 加载0xA5A5A5A5
"0x49" # LDR R1, [PC, #8] ; 加载0x1F2E3D4C
"0x4050" # EORS R0, R2 ; seed ^ 0xA5A5A5A5
"0x1840" # ADDS R0, R0, R1 ; + 0x1F2E3D4C
"0x4770" # BX LR
"0xA5A5A5A5" # 常量1
"0x1F2E3D4C" # 常量2
)
code.extend(seed_key_func)
# --- CAN报文ID表 (地址0x08000100) ---
# 格式: [CAN_ID(4字节), 数据长度(1字节), 功能码(1字节), 保留(2字节)]
can_table = [
(0x7E0, 8, 0x27, 0x01), # UDS安全访问-请求种子
(0x7E8, 8, 0x67, 0x01), # UDS安全访问-响应种子
(0x7E0, 8, 0x27, 0x02), # UDS安全访问-发送密钥
(0x7E8, 8, 0x67, 0x02), # UDS安全访问-验证结果
(0x7DF, 8, 0x02, 0x10), # OBD模式请求
]
# 填充到0x100偏移
code.extend(b'\x00' * (0x100 - len(code)))
for can_id, dlc, svc, sub in can_table:
code.extend(struct.pack('<I', can_id))
code.extend(bytes([dlc, svc, sub, 0x00]))
# 写入文件
Path(path).write_bytes(bytes(code))
print(f"[1] 模拟固件已生成: {path} ({len(code)} 字节)")
# ========== 第2步:静态提取安全关键信息 ==========
def extract_security_info(fw_path: str):
"""从固件中提取CAN ID表和安全算法特征"""
data = Path(fw_path).read_bytes()
print("\n[2] 固件静态分析结果:")
# 提取CAN报文ID表(从偏移0x100开始,每条记录8字节)
print(" CAN报文ID表:")
offset = 0x100
while offset + 8 <= len(data):
can_id, dlc, svc, sub, _ = struct.unpack('<IBBBH', data[offset:offset+8])
if can_id == 0 and dlc == 0:
break
print(f" CAN ID: 0x{can_id:03X} | DLC: {dlc} | 服务: 0x{svc:02X} | 子功能: 0x{sub:02X}")
offset += 8
# 搜索安全算法特征(查找0xA5A5A5A5魔数)
magic = b'\xA5\xA5\xA5\xA5'
pos = data.find(magic)
if pos >= 0:
print(f" 安全算法魔数: 0xA5A5A5A5 @ 偏移 0x{pos:X}")
# 提取算法常量
const2_offset = pos + 4
const2 = struct.unpack('<I', data[const2_offset:const2_offset+4])[0]
print(f" 第二常量: 0x{const2:08X}")
print(f" 推测算法: key = (seed ^ 0xA5A5A5A5) + 0x{const2:08X}")
# ========== 第3步:反汇编安全认证函数 ==========
def disassemble_auth_func(fw_path: str):
"""使用Capstone反汇编固件前64字节(安全访问函数区域)"""
from capstone import Cs, CS_ARCH_ARM, CS_MODE_THUMB
data = Path(fw_path).read_bytes()[:64]
md = Cs(CS_ARCH_ARM, CS_MODE_THUMB)
md.detail = True
print("\n[3] 安全访问函数反汇编 (ARM Thumb模式):")
print(" 地址范围: 0x08000000 - 0x08000040")
instructions = []
for insn in md.disasm(data, 0x08000000):
instructions.append(f" 0x{insn.address:08X}: {insn.mnemonic}\t{insn.op_str}")
# 输出并分析
for line in instructions:
print(line)
# 自动识别算法逻辑
print("\n 算法还原:")
print(" LDR R2 → 加载0xA5A5A5A5 (异或掩码)")
print(" LDR R1 → 加载0x1F2E3D4C (加法常量)")
print(" EORS R0, R2 → seed ^ 0xA5A5A5A5")
print(" ADDS R0, R0, R1 → + 0x1F2E3D4C")
print(" BX LR → 返回key到R0")
# ========== 主流程 ==========
if __name__ == "__main__":
fw_path = "/tmp/ecu_firmware.bin"
# 执行完整分析流程
generate_firmware(fw_path)
extract_security_info(fw_path)
disassemble_auth_func(fw_path)
print("\n[验证] 使用已知seed=0x12345678测试算法:")
seed = 0x12345678
expected_key = (seed ^ 0xA5A5A5A5) + 0x1F2E3D4C & 0xFFFFFFFF
print(f" seed = 0x{seed:08X}")
print(f" key = 0x{expected_key:08X} (预期输出)")
print(" 验证方法: 在真实ECU中发送0x27 01请求种子,计算key后发送0x27 02验证")
运行与验证
运行方式:
pip install capstone pyelftools
python3 ecu_firmware_analysis.py
预期输出与判定标准:
- 通过条件1:脚本输出完整的CAN报文ID表,包含5条记录(0x7E0/0x7E8/0x7DF),且服务ID与子功能字段正确解析。
- 通过条件2:在偏移0x100处找到魔数0xA5A5A5A5,并正确提取第二常量0x1F2E3D4C。
- 通过条件3:反汇编结果包含
EORS和ADDS指令,且自动还原的算法与代码逻辑一致。 - 通过条件4:使用seed=0x12345678计算得到key=0x12345678^0xA5A5A5A5+0x1F2E3D4C=0x8A9B8A91(可手动验证),证明算法还原正确。
常见问题
- 真实ECU固件获取:通常通过OBD调试接口(如BDM/JTAG)或拆解芯片读取Flash,需使用编程器或调试探针。
- 固件加密/压缩:若固件经过加密或压缩,需先识别并解密(常见工具:binwalk、Ghidra的自动分析插件)。
- 架构识别:本案例使用ARM Thumb模式,实际分析前需通过固件入口点或中断向量表确认CPU架构。