4. OTA传输通道TLS加密实战

任务目标

本案例针对汽车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架构。