文章總列表

ELM327 OBD-II 心得 (7),驗屍報告:中間人攻擊的時間分析

本來想炫耀地下室甩尾的美技暫時碰壁了:我鼓起勇氣拿Pi-WIRE接HUD上車,結果HUD不會動,幸好車子還可以開,所以這是一篇[驗屍報告]

我在紀錄檔看HUD發出封包,也有汽車的回應,應該不是通訊協定錯。直覺告訴我是中間人攻擊增加的延遲,讓HUD行為發生變化

先看延遲極小值
  • CAN@500kbps,一個8byte封包大概需要200us
    • 1bit需要2us,8byte封包約100bits,t_pkg = 2us*100bit = 200us
  • 中間人的延遲(t_delay)包括兩段:內部處理(t_mitm)和重新傳輸(t_pkg)
    • t_delay = t_mitm + t_pkg
      • t_mitm,中間人內部處理,想辦法壓低
      • t_pkg = 200us,重新發送封包也需要200us
        • 除非用FPGA重新設計CAN controller,一邊收一邊打,不然躲不掉
  • HUD發出詢問速度,等待汽車回應,一共要承受兩發t_delay
    • Before
      • t_roundtrip1 = t_pkg + t_pkg = t_pkg*2
    • After
      • t_roundtrip2 = (t_pkg + t_delay) + (t_pkg + t_delay) = t_pkg*4 + t_mitm*2
    • 因為中間人增加的延遲
      • t_roundtrip2 - t_roundtrip1 = t_pkg*2 + t_mitm*2
  • 以樹莓派來說,t_mitm的極小值是20us
    • CAN控制器MCP2515,使用10Mhz的SPI介面
      • 10Mhz,1bit = 0.1us
      • 從MCP2515讀回和寫入100bits的極速都是10us,相加得到20us
  • 假設HUD預期查詢要在1000us以內回覆
    • 1000 = t_pkg*4 + t_mitm*2 = 800us + t_mitm*2
      • 我得做到t_mitm < 100us才能讓HUD滿意
    • 其實我不知道HUD的需求,反正這裡就猜猜看了 :-)
  • 測量當前平台的t_mitm,並且努力壓縮他

第一版本軟體:Python-CAN,t_mitm高達1920us!!

我很喜歡Python,寫起來簡單流暢,生產力很高,而且Python-CAN簡單優美不用折騰Socket Programming。這次用Python3還順便學了asyncio,我也實際解掉幾個問題:
  • 收到Error Frame不能無腦轉發,要判斷一下
  • 接HUD的CANBUS可能還沒上電,呼叫API傳資料會把TX-buffer塞滿(傳不出去)
    • 所以發送時要加try...except避免CAN1還不能動,寫起來像底下那樣
      try:
          can_if.send(...)
      except can.Error:
          print('some message')
用新技術很開心,程式很優美,做Pi-WIRE,Pi-CAR都很容易,只可惜速度慢了點。下圖黃色是CAN信號抵達,藍色是CAN信號重新發出。兩條垂直白線的間距正是t_mith,數值寫在左上角框框的BX - AX = 1.92ms = 1920us。可以看到Python處理的速度真的很悲情,做這種即時處理不大合用




第二版軟體:candump串聯can0/can1,其實不用寫軟體,t_mitm還是570us

挑樹莓派最重要的原因是他有Linux,Linux最爽的是CAN-UTILS有現成免錢的工具。這套文字介面的工具簡單好用,好串接,想做什麼一道指令清楚明白。缺點是得自己折騰很多細節,學習的門檻高一點。
  • cansend(發送封包)
  • candump(把CAN的交通記錄下來)
  • cangen(產生隨機的CANBUS封包)
  • cansniffer(online針對CANBUS封包分析)
  • ISO-TP tools
其實透過candump就能做出中間人攻擊,不用刻上面的Python code,側錄封包也很方便
  • 兩道命令串接can0/ can1
    • candump -s2 -B can0 can1 &
    • candump -s2 -B can1 can0 &
      • -s2, silent mode, no output to terminal
      • -B, bridging mode, no loopback
  • 側錄下can0/ can1的封包
    • candump -t d -l can0 can1 > can_traffic.txt &
      • -t d, show timestamp in delta way
      • -l can0, can1, logging traffic of can0/can1
實驗結果,延遲整整壓縮剩四分之一。我以為這個延遲就能成功,結果還是失敗了



其他的嘗試
  • 調Linux的task priority(用nice)
    • 無效,系統本來就不忙,priority再高也沒救
  • 改Linux的scheduling(SCHED_FIFO),因為我有四核心,兩顆core各自跑單讀的core
    • 還是無效
  • 直接寫C code操作socketcan
    • 稍微改善,但是效果不大

問題來自MCP2515 Linux Device Driver

下圖有四條線,黃色是CANBUS接收信號,紫色信號是打出的時間;黃色到紫色約略是t_mitm。深藍色是MCP2515發出中斷,Linux kernel進場處理到深藍色拉成high。所以整個t_mitm都耗在Linux Kernel裡面了!!



在網路上撈Linux MCP2515 Latency關鍵字,看起來很多人遇過這題。關鍵是SPI本來就不快,透過他做handshaking效率很爛,加上Linux本身做context switch也耗時

挖出當年編譯核心的筆記重編MCP2515 driver,加滿printk測量執行時間,果然大多時間都耗在kernel space。我只能說樹莓派的SPI driver只有堪用的等級。到此我猶豫要不要繼續花時間在MCP2515,還是另外找程式用

  • https://github.com/msperl/mcp2515a
  • https://github.com/raspberrypi/linux/pull/147/files
  • https://linux-rpi-kernel.infradead.narkive.com/AcfW6GTP/arm-bcm2835-dma-driver-spi-optimize-message-some-questions-info
  • https://github.com/msperl/spi-bcm2835/wiki
  • https://elinux.org/images/2/20/Whats_going_on_with_SPI--mark_brown.pdf

其他可能的解法
  • 第一招花錢,從Linux kernel tree支援的裝置挑一個
    • 8devices的USB2CAN,一個65美金的看起來不錯
    • 不過USB可能不合用
      • USB 2.0 Full Speed,Frame period是1ms,USB本身會產生1000us的延遲
      • USB 2.0 High Speed,Frame period是125us,依舊不大好
  • 第二招是自己做,ESP32有CAN和WiFi,只要加個CAN Transceiver就能做CAN2WiFI
    • 可以做一個remote CANBUS logger,應該是滿好玩的
    • WiFi的延遲不可控,不適合做MITH,我猜最小也是ms等級
  • 第三招還是自己做,我買了Nucleo-F767ZI開發板有3個CAN,1個Ethernet,1個USB
    • 直接在MCU裡轉發,不可能比這個快了!
    • MCU@216Mhz,速度夠快,裡面想幹什麼都可以
    • 側錄的結果可以透過Ethernet寫出去,頻寬超大,延遲也低
    • 可以插USB OTG,插個隨身碟錄檔案也可

這篇寫完好像也和ELM327無關了...

留言

這個網誌中的熱門文章

幼犬書桌椅選擇心得 升降桌 兒童桌椅

STM32 UART + DMA,使用HAL實作TX/RX,以及不定長度接收

CANON G3000 廢墨瓶改裝