第10章:CAN总线模糊测试实战

模糊测试,说白了就是给系统喂乱七八糟的数据,看它会不会崩溃。我刚开始做ADAS测试时,总觉得这方法有点「野蛮」。直到有一次,一个看似正常的CAN报文,居然让前向碰撞预警系统直接死机了。嗯,从那以后,我再也不敢小看模糊测试了。

10.1 模糊测试的核心思路

你想想看,ADAS系统每天要处理成千上万条CAN报文。如果某个报文格式不对,或者ID冲突了,系统会怎么反应?

我个人的习惯是,先搞清楚几个关键点:

  • 哪些CAN ID是ADAS系统在监听的
  • 这些ID的报文周期是多少
  • 数据域里哪些字节是关键的(比如车速、转向角)

举个例子,我曾经测试过一个LKA(车道保持辅助)系统。正常情况下,它只响应0x1A0这个ID的报文。但当我用模糊测试工具往总线上狂发0x1A0的随机数据时,系统居然开始误判车道线了。为什么会这样?因为模糊数据里恰好包含了「转向请求」的位域组合。

关键点:模糊测试不是乱发数据,而是有策略地攻击系统的「盲区」。

10.2 硬件环境准备

做CAN模糊测试,硬件其实不复杂。我常用的组合是:

设备 型号举例 作用
USBCAN 周立功USBCAN-II 发送/接收CAN报文
CANoe VN1610/VN1640 监控总线状态
雷达模拟器 罗德与施瓦茨AREG 模拟目标物数据

这里有个坑,我提醒一下:千万别直接用USBCAN往ADAS的CAN总线上发大量报文。我曾经这么干过,结果把ECU的CAN控制器给搞挂了,得断电重启才行。正确的做法是:

  1. 先断开ADAS系统与真实传感器的连接
  2. 用CANoe做网关,隔离测试流量
  3. 在CANoe里设置过滤规则,只让特定ID通过

警告:模糊测试时,务必确保车辆处于安全状态(比如架起轮子、断开高压)。我见过有人测试时把ACC自适应巡航给触发了,车直接往前窜...还好拉了手刹。

10.3 软件工具链配置

软件方面,我一般用三件套:

  • Python + python-can库:写模糊测试脚本
  • Wireshark:抓包分析异常报文
  • CANalyzer:实时监控总线负载和错误帧

先说说Python环境怎么搭。我个人习惯用虚拟环境,避免依赖冲突:

# 创建虚拟环境
python -m venv can_fuzz_env
source can_fuzz_env/bin/activate  # Linux/Mac
# 或 can_fuzz_env\Scripts\activate  # Windows

# 安装依赖
pip install python-can
pip install numpy  # 用于生成随机数据

然后写一个简单的模糊测试脚本。嗯,这里要注意,不要一上来就全速发报文。我建议先慢速试探:

import can
import time
import random

# 初始化CAN接口
bus = can.interface.Bus(bustype='socketcan', channel='can0', bitrate=500000)

# 目标ID列表(从CANalyzer里抓到的)
target_ids = [0x1A0, 0x1A1, 0x2B0, 0x2B1]

def fuzz_single_id(can_id):
    """对单个ID进行模糊测试"""
    # 生成随机8字节数据
    data = bytes([random.randint(0, 255) for _ in range(8)])
    
    # 构造报文
    msg = can.Message(
        arbitration_id=can_id,
        data=data,
        is_extended_id=False
    )
    
    # 发送
    bus.send(msg)
    
    # 记录日志
    print(f"Sent: ID=0x{can_id:03X}, Data={data.hex()}")

# 慢速试探阶段
print("Starting slow fuzz...")
for _ in range(100):
    can_id = random.choice(target_ids)
    fuzz_single_id(can_id)
    time.sleep(0.1)  # 100ms间隔,安全第一

小技巧:我习惯在模糊测试的同时,用CANalyzer的「Error Frame」计数器观察总线状态。如果错误帧突然增多,说明你的模糊数据可能破坏了CAN协议(比如位填充错误),这时候要降低发送频率。

10.4 实战案例:雷达信号模糊测试

我记得有一次,客户反馈说AEB(自动紧急制动)系统在高速上会莫名其妙地急刹车。我们排查了很久,最后发现是雷达模拟器发出的目标物信号里,有一个字节的校验和算错了。

这个案例让我意识到,模糊测试不能只盯着CAN层,还要关注应用层的数据语义。比如雷达目标物报文,通常包含:

  • 目标ID(0-255)
  • 相对距离(0-200米)
  • 相对速度(-50到+50 m/s)
  • 目标类型(车辆、行人、自行车)

我写了个更高级的模糊测试脚本,专门针对这些字段做变异:

def fuzz_radar_target():
    """模糊雷达目标物报文"""
    # 正常范围
    normal_distance = random.uniform(5, 150)
    normal_speed = random.uniform(-20, 20)
    
    # 变异策略
    mutation_type = random.choice(['boundary', 'overflow', 'negative'])
    
    if mutation_type == 'boundary':
        # 边界值测试
        distance = 0.0  # 距离为0,看系统怎么处理
        speed = 50.0    # 最大速度
    elif mutation_type == 'overflow':
        # 溢出测试
        distance = 999.9  # 超出范围
        speed = 100.0     # 超出范围
    else:
        # 负值测试
        distance = -10.0
        speed = -60.0
    
    # 构造报文(假设使用0x2B0 ID)
    data = struct.pack('<Bff', 0x01, distance, speed)
    # 这里省略了校验和计算...
    
    return data

经验之谈:我发现很多ADAS系统的漏洞都出在「边界值」上。比如距离为0米时,系统会怎么处理?是触发紧急制动,还是直接忽略?我测试过3款不同厂家的AEB系统,有2款在距离为0时会出现逻辑混乱。

10.5 测试结果分析

模糊测试跑完后,你会得到一堆日志。怎么分析呢?我一般关注这几个指标:

指标 正常值 异常值 可能原因
总线负载率 <30% >80% 报文风暴或重传
错误帧计数 0 >10/秒 CAN协议违规
ADAS响应时间 <100ms >500ms CPU过载或死锁

举个例子,有一次我跑模糊测试时,发现总线负载率突然飙到95%。用Wireshark一抓包,发现是某个ECU在疯狂重传错误帧。说白了,就是我的模糊数据把它的CAN控制器搞懵了,一直在报错。

避坑指南:我曾经连续跑了8小时模糊测试,结果第二天发现CANoe的存储卡满了。建议你设置日志轮转,比如每1小时生成一个新文件,或者限制单个日志文件不超过100MB。

10.6 本章小结

CAN总线模糊测试,说白了就是「以毒攻毒」。你给系统喂乱七八糟的数据,它才能暴露出平时隐藏的缺陷。我个人觉得,这是ADAS安全测试里性价比最高的方法之一——不需要复杂的设备,一个USBCAN加几行Python代码就能发现不少问题。

但记住,模糊测试不是万能的。它只能发现「输入异常」导致的问题,对于逻辑漏洞、时序问题,还得靠其他测试方法。嗯,下一章我会讲怎么用CANoe做更精细的报文注入测试,到时候再细聊。