ELM327 OBD-II 心得 (7),驗屍報告:中間人攻擊的時間分析
本來想炫耀地下室甩尾的美技暫時碰壁了:我鼓起勇氣拿Pi-WIRE接HUD上車,結果HUD不會動,幸好車子還可以開,所以這是一篇[驗屍報告]
我在紀錄檔看HUD發出封包,也有汽車的回應,應該不是通訊協定錯。直覺告訴我是中間人攻擊增加的延遲,讓HUD行為發生變化
先看延遲極小值
下圖有四條線,黃色是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,還是另外找程式用
其他可能的解法
我在紀錄檔看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無關了...
留言