第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控制器给搞挂了,得断电重启才行。正确的做法是:
- 先断开ADAS系统与真实传感器的连接
- 用CANoe做网关,隔离测试流量
- 在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做更精细的报文注入测试,到时候再细聊。