0%

noone的车联网-CAN总线模拟数据读取与篡改

既然已经通过 hackrf 做到了车辆位置信息的动态修改。而车企仍然会检测车辆里程,对于当前品牌车辆来说,行驶5K km后将被认定为二手车,不再受企业平台防止出口的监督。

而车辆里程并非通过行驶gps计算,而是由 tbox 接收到 CAN 总线发来的数据(应该是轮胎转速吧)计算而来,同时由 tbox 发送给服务器,具体内容参考关于Tbox分析的博客

那么如果要伪造里程,最简单的办法就是进行中间人攻击,在CAN总线和Tbox中插入一个中间人设备,其负责读取CAN总线传输的内容,改变特定内容(如这里的转速),再打包并发送给 Tbox 让其发送,从而欺骗服务端来伪造里程

由于没有DBC文件无法对CAN总线内容进行解码,所以需要抓取信息并进行分析猜测,这块是最麻烦的内容

而中间人设备使用的是树莓派+CAN模块(MCP2515)

  • 数据读取实验

    现在电脑上模拟一下读取数据,采用的是 Linuxcan-utils ,借助 USB-CAN 向外输出,同时CAN模块连接到树莓派的SPI接口,以及 CAN_HCAN_L

    等设备到再写

    • 树莓派连接基于 MCP2515CAN 总线模块

      对于MCP2515 SPI转CAN的控制器通信模块,其基本针脚如下:

      与树莓派的接线如下:

      1
      2
      3
      4
      5
      6
      7
      8
      RPi Pin    RPi Label     CAN Module
      01---------3.3V----------VCC
      14---------GND-----------GND
      19---------GPIO10--------MOSI (SI)
      21---------GPIO9---------MISO (SO)
      22---------GPIO25--------INT
      23---------GPIO11--------SCK
      24---------GPIO8---------CS

      在树莓派上(我的内核是6.2):

      boot/firmware/config.txt 中增加:

      1
      2
      dtparam=spi=on
      dtoverlay=mcp2515-can0,oscillator=8000000,interrupt=25

      波特率对应的是 MCP2515 上控制器的晶振频率 8MHz,后面的中断引脚对应的是上面接线接的 22---INT ,即GPIO25

      然后重启,ls /sys/bus/spi/devices/spi0.0/net 查看 mcp2515 是否成功加载,可以看到 can0

      或者用 dmesg | grep mcp 来看是否成功初始化

      1
      2
      sudo ip link set can0 type can bitrate 500000 loopback on #设置can0网卡波特率250k,回环模式
      sudo ip link set can0 up #开启can0网卡

      环回模式允许器件内部的发送缓冲器和接收缓冲器之间进行报文的自发自收,而无需通过CAN 总线。此模式可用于系统开发和测试。

      现在再查看 ifconfig 可以看到 can0 的状态为 UP,RUNNING

      • 测试

        1
        apt install can-utils

        一个终端运行 candump can0 开始监听

        另一个运行 cansend can0 123#1122334455667788can ID0x123 ,数据为 1122334455667788 的报文

    • 电脑USB转CAN总线发送数据

      随便买的USB-TO-CAN的工具,CANH和CANL对应连接到 MCP2515 的H和L,GND接到树莓派上的GND来保证与 MCP2515 共地

      在树莓派上开启监听:

      1
      2
      ip link set can0 up type can bitrate 500000
      candump can0

      linux 上:

      1
      ip link set can0 up type can bitrate 500000

      保证两者波特率相同,此时 ifconfig 能看到 can0

      然后跟之前一样

      1
      cansend can0 123#114514

      就能在树莓派上收到了

    • 连接两个MCP2515

      知乎上那篇文章讲的问题挺大的,建议看[这篇](https://www.jianshu.com/p/b0

      1
      2
      3
      4
      5
      6
      7
      8
      RPi Pin    RPi Label     CAN Module
      17---------3.3V----------VCC
      06---------GND-----------GND
      38---------GPIO20--------MOSI (SI)
      35---------GPIO19--------MISO (SO)
      37---------GPIO26--------INT
      40---------GPIO11--------SCK
      12---------GPIO18---------CS

      配置为

      1
      2
      3
      4
      5
      dtparam=spi=on
      dtoverlay=spi1-1cs
      dtoverlay=spi0-1cs
      dtoverlay=mcp2515,spi0-0,oscillator=8000000,interrupt=25
      dtoverlay=mcp2515,spi1-0,oscillator=8000000,interrupt=26

      可以在 /boot/overlay/README 中看到具体信息,简单来说就是 can0 连接到了 spi0 总线上,使用第0个片选信号(CS), can1 连接到了 spi1 总线上,使用第0个片选信号。

      而这两个片选信号默认是 GPIO8 / GPIO18 ,即上面接线接到的,当然也可以手动配置,比如:

      1
      dtoverlay=spi1-1cs,cs0_pin=18

    • 电脑发送并读取

      跟连一个的时候同理:

      1
      2
      sudo ip link set can0 up type can bitrate 500000
      sudo ip link set can1 up type can bitrate 500000

      在树莓派上运行以下脚本:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      import can
      # 打开 can0 和 can1 接口
      can0 = can.interface.Bus(channel='can0', bustype='socketcan')
      can1 = can.interface.Bus(channel='can1', bustype='socketcan')

      print("开始 CAN 转发: can0 -> can1")

      while True:
      msg = can0.recv() # 接收 can0 消息
      if msg is not None:
      print(f"收到来自 can0 的消息: {msg}")
      can1.send(msg) # 发送到 can1
      print(f"已转发到 can1: {msg}")

      唯一的问题就是linux那边是反着的,也是就树莓派的 can0 在那边是 can1 啊嗯

      至于更改就随便写一个试一下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      import can

      # 打开 can0 和 can1 接口
      can0 = can.interface.Bus(channel='can0', bustype='socketcan')
      can1 = can.interface.Bus(channel='can1', bustype='socketcan')

      print("开始 CAN 转发: can0 -> can1")

      while True:
      msg = can0.recv()
      if msg is None:
      continue

      print(f"收到 can0 消息: ID={hex(msg.arbitration_id)}, Data={msg.data.hex()}")

      # 判断是否为指定条件
      if msg.arbitration_id == 0x119 and msg.data[:3] == bytes([0x11, 0x45, 0x14]):
      # 构造新消息
      new_data = bytes([0x01, 0x91, 0x98, 0x10])
      new_msg = can.Message(
      arbitration_id=msg.arbitration_id,
      data=new_data,
      is_extended_id=False
      )
      print(f"匹配到目标消息,已替换为: Data={new_data.hex()}")
      can1.send(new_msg)
      else:
      # 原样转发
      print(f"原样转发")
      can1.send(msg)

      附一张实物图

      然后再配一个自动配置脚本

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      #!/bin/bash

      set -e

      echo "==> 加载 mcp251x 驱动模块..."
      sudo modprobe mcp251x
      sudo modprobe can_dev
      sudo modprobe can_raw

      echo "==> 关闭可能存在的旧接口..."
      sudo ip link set can0 down 2>/dev/null || true
      sudo ip link set can1 down 2>/dev/null || true

      echo "==> 配置 CAN 接口速率并启用..."

      # 配置 can0(挂载在 SPI0)
      if ip link show can0 &>/dev/null; then
      sudo ip link set can0 type can bitrate 500000
      sudo ip link set can0 up
      echo "✅ can0 启动成功"
      else
      echo "❌ can0 不存在"
      fi

      # 配置 can1(挂载在 SPI1)
      if ip link show can1 &>/dev/null; then
      sudo ip link set can1 type can bitrate 500000
      sudo ip link set can1 up
      echo "✅ can1 启动成功"
      else
      echo "❌ can1 不存在"
      fi

      echo ""
      echo "📡 当前 CAN 接口状态:"
      ip -br link | grep can
    • 两端相互传数据的情况下做监听:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      import can
      import datetime0

      # 打开 can0 和 can1 接口
      can0 = can.interface.Bus(channel='can0', bustype='socketcan')
      can1 = can.interface.Bus(channel='can1', bustype='socketcan')

      # 打开日志文件
      log_file = open("can_relay_log.txt", "a")

      print("开始双向 CAN 转发: can0 <-> can1")

      try:
      while True:
      # 检查 can0 是否有消息
      msg0 = can0.recv(timeout=0.01)
      if msg0 is not None:
      try:
      can1.send(msg0)
      print(f"[{datetime.datetime.now()}] can0 → can1: {msg0}")
      log_file.write(f"[{datetime.datetime.now()}] can0 → can1: {msg0}\n")
      log_file.flush()
      except can.CanError as e:
      print(f"发送到 can1 失败: {e}")

      # 检查 can1 是否有消息
      msg1 = can1.recv(timeout=0.01)
      if msg1 is not None:
      try:
      can0.send(msg1)
      print(f"[{datetime.datetime.now()}] can1 → can0: {msg1}")
      log_file.write(f"[{datetime.datetime.now()}] can1 → can0: {msg1}\n")
      log_file.flush()
      except can.CanError as e:
      print(f"发送到 can0 失败: {e}")

      except KeyboardInterrupt:
      print("退出转发程序")
      finally:
      log_file.close()
      can0.shutdown()
      can1.shutdown()
  • OBD 调试接口数据读取

    OBDTbox 的 CAN总线并不是同一个,并且 OBD 是询问应答机制,需要先给其发送要查询的数据,其才会返回相关结果

    询问gpt得到遵循CAN协议的OBDⅡ指令集,其请求帧如下:

    • CAN ID: 0x7DF(通用广播 ID,发送到所有 ECU)

    • 数据字节:

      • Byte 0: 长度(如 0x02)

      • Byte 1: 模式(如 0x01)

      • Byte 2: PID(如 0x0C)

      • Byte 3-7: 填充 0x55 或 0x00

      例如请求车速:

      1
      2
      CAN ID: 0x7DF  
      DATA: 02 01 0D 00 00 00 00 00

      第一个是回路接收到的发送命令

      2-4是来自多个 ECU 的响应,都表示车速 0D 的值是0 00

      最后两个是异常响应,表示不支持mode01服务? 不用在意那个

      然后偷了个专用的OBD调试机器来试,找了一圈,比较重要的数据就是车速和累计里程,二者都是整数

  • CAN 总线数据尝试找资料

    能确定时速在P-CAN上,累计里程的话看他们调表的时候拆的芯片在仪表盘上,可能在I-CAN上传输

    只找到了奇瑞星途 (Chery Exeed) 的很小一部分dbc

    下下来是这个b公司加密过的 ref 格式,还好就在两周前有好人写了转换脚本

    转化一下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    VERSION ""
    NS_ :
    CM_
    BA_DEF_
    BA_
    VAL_
    CAT_DEF_
    CAT_
    FILTER
    BA_DEF_DEF_
    EV_DATA_
    ENVVAR_DATA_
    SGTYPE_
    SGTYPE_VAL_
    BA_DEF_SGTYPE_
    BA_SGTYPE_
    SIG_TYPE_REF_
    VAL_TABLE_
    SIG_GROUP_
    SIG_VALTYPE_
    SIGTYPE_VALTYPE_
    BO_TX_BU_
    BA_DEF_REL_
    BA_REL_
    BA_DEF_DEF_REL_
    BU_SG_REL_
    BU_EV_REL_
    BU_BO_REL_
    SG_MUL_VAL_

    BS_:

    BU_: Vector__XXX

    BO_ 165 CAN_MSG_165: 6 VECTOR__XXX
    SG_ Engine_Speed : 16|16@0+ (0.25,0) [0|16383.75] "rpm" Vector__XXX

    BO_ 167 CAN_MSG_167: 6 VECTOR__XXX
    SG_ Accelerator_Pedal_Position : 40|8@0+ (0.396825,0) [0|101.190476190476] "%" Vector__XXX

    BO_ 168 CAN_MSG_168: 8 VECTOR__XXX
    SG_ Brake_Position : 40|8@0+ (0.5,0) [0|127.5] "%" Vector__XXX

    BO_ 296 CAN_MSG_296: 8 VECTOR__XXX
    SG_ Gear_Requested : 20|4@0+ (1,0) [0|15] "" Vector__XXX
    SG_ Gear : 16|4@0+ (1,0) [0|15] "" Vector__XXX

    BO_ 299 CAN_MSG_299: 6 VECTOR__XXX
    SG_ Steering_Angle : 24|14@0+ (-0.1,720) [-918.3|720] "°" Vector__XXX

    BO_ 315 CAN_MSG_315: 6 VECTOR__XXX
    SG_ Wheel_Speed_FL_kph : 32|16@0+ (0.0088,0) [0|576.708] "km/h" Vector__XXX
    SG_ Wheel_Speed_FR_kph : 16|16@0+ (0.0088,0) [0|576.708] "km/h" Vector__XXX
    SG_ Wheel_Speed_FL_mph : 32|16@0+ (0.0054648,0) [0|358.135668] "mph" Vector__XXX
    SG_ Wheel_Speed_FR_mph : 16|16@0+ (0.0054648,0) [0|358.135668] "mph" Vector__XXX

    BO_ 318 CAN_MSG_318: 6 VECTOR__XXX
    SG_ Wheel_Speed_RL_kph : 32|16@0+ (0.0088,0) [0|576.708] "km/h" Vector__XXX
    SG_ Wheel_Speed_RR_kph : 16|16@0+ (0.0088,0) [0|576.708] "km/h" Vector__XXX
    SG_ Wheel_Speed_RL_mph : 32|16@0+ (0.0054648,0) [0|358.135668] "mph" Vector__XXX
    SG_ Wheel_Speed_RR_mph : 16|16@0+ (0.0054648,0) [0|358.135668] "mph" Vector__XXX

    BO_ 338 CAN_MSG_338: 8 VECTOR__XXX
    SG_ Side_Lights : 24|1@0+ (1,0) [0|0] "on/off" Vector__XXX
    SG_ Headlights : 28|1@0+ (1,0) [0|0] "on/off" Vector__XXX
    SG_ Full_Beam : 30|1@0+ (1,0) [0|0] "on/off" Vector__XXX
    SG_ Indicator_Left : 18|1@0+ (1,0) [0|0] "on/off" Vector__XXX
    SG_ Indicator_Right : 20|1@0+ (1,0) [0|0] "on/off" Vector__XXX

    BO_ 456 CAN_MSG_456: 5 VECTOR__XXX
    SG_ Gear_Switch : 26|3@0+ (1,0) [0|7] "" Vector__XXX

    处理一下:

| CAN ID | 信号名                          | 起始位 | 长度 | 字节序   | 缩放因子 (Factor) | 偏移量 | 单位     |
| ------ | ---------------------------- | --- | -- | ----- | ------------- | --- | ------ |
| 0x0A5  | Engine\_Speed                | 16  | 16 | Intel | 0.25          | 0   | rpm    |
| 0x0A7  | Accelerator\_Pedal\_Position | 40  | 8  | Intel | 0.396825      | 0   | %      |
| 0x0A8  | Brake\_Position              | 40  | 8  | Intel | 0.5           | 0   | %      |
| 0x0128 | Gear\_Requested              | 20  | 4  | Intel | 1             | 0   | —      |
| 0x0128 | Gear                         | 16  | 4  | Intel | 1             | 0   | —      |
| 0x012B | Steering\_Angle              | 24  | 14 | Intel | -0.1          | 720 | °      |
| 0x013B | Wheel\_Speed\_FL\_kph        | 32  | 16 | Intel | 0.0088        | 0   | km/h   |
| 0x013B | Wheel\_Speed\_FR\_kph        | 16  | 16 | Intel | 0.0088        | 0   | km/h   |
| 0x013B | Wheel\_Speed\_FL\_mph        | 32  | 16 | Intel | 0.0054648     | 0   | mph    |
| 0x013B | Wheel\_Speed\_FR\_mph        | 16  | 16 | Intel | 0.0054648     | 0   | mph    |
| 0x013E | Wheel\_Speed\_RL\_kph        | 32  | 16 | Intel | 0.0088        | 0   | km/h   |
| 0x013E | Wheel\_Speed\_RR\_kph        | 16  | 16 | Intel | 0.0088        | 0   | km/h   |
| 0x013E | Wheel\_Speed\_RL\_mph        | 32  | 16 | Intel | 0.0054648     | 0   | mph    |
| 0x013E | Wheel\_Speed\_RR\_mph        | 16  | 16 | Intel | 0.0054648     | 0   | mph    |
| 0x0152 | Side\_Lights                 | 24  | 1  | Intel | 1             | 0   | on/off |
| 0x0152 | Headlights                   | 28  | 1  | Intel | 1             | 0   | on/off |
| 0x0152 | Full\_Beam                   | 30  | 1  | Intel | 1             | 0   | on/off |
| 0x0152 | Indicator\_Left              | 18  | 1  | Intel | 1             | 0   | on/off |
| 0x0152 | Indicator\_Right             | 20  | 1  | Intel | 1             | 0   | on/off |
| 0x01C8 | Gear\_Switch                 | 26  | 3  | Intel | 1             | 0   | —      |


以左轮胎速度为例:

1
2
3
BO_ 315 CAN_MSG_315: 6 VECTOR__XXX
SG_ Wheel_Speed_FL_kph : 32|16@0+ (0.0088,0) [0|576.708] "km/h" Vector__XXX
SG_ Wheel_Speed_FR_kph : 16|16@0+ (0.0088,0) [0|576.708] "km/h" Vector__XXX
| 字段 | 含义 | | -------------------- | ------------------------------ | | `BO_ 315` | CAN ID 为 **0x13B** 的帧(十进制 315) | | `Wheel_Speed_FL_kph` | 左前轮速度,单位为 km/h | | `起始位 = 32` | 在第 4 个字节起始 | | `长度 = 16` | 占 2 字节 | | `@0+` | Intel(小端)字节序,正数 | | `Factor = 0.0088` | 缩放因子 | | `Offset = 0` | 无偏移 | | `长度 = 6` | 该 CAN 帧的 Payload 长度为 6 字节 | 比如抓到了
1
13B#6B 00 52 00 91 01
则 `Wheel_Speed_FL_kph` 是 `byte[4:6]`,即 $91 01$(小端) = $0x0191$ = $401$ 实际速度 = $401 * 0.0088 = 3.528 km/h$
  • 待测试区

    • 测比特率

      1
      ip -details -statistics link show can0

      显示这个:RX packets: 0 → 没收到任何帧,说明总线上无数据或波特率不对

    • 抓汽车点火的时候的情况?是否只在每次点火的时候才上传数据

      没有

    • 挂挡的时候?

      没有

    • 抓Tbox的包,据维修手册可能存在响应应答

      没有

  • 悲报

    不知道为什么一直读不出数据,暂时转到Tbox攻击上了