在TESLA MODEL S上實現MARVELL無線協議棧漏洞的利用

在過去的兩年里,騰訊科恩實驗室對特斯拉汽車的安全性進行了深入的研究並在Black Hat 2017與Black Hat 2018安全會議上兩次公開分享了我們的研究成果。我們的研究成果覆蓋了車載系統的多個組件。我們展示了如何攻入到特斯拉汽車的CID、IC、網關以及自動駕駛模塊。這一過程利用了內核、瀏覽器、MCU固件、UDS協議及OTA更新過程中的多個漏洞。值得注意的是,最近我們在自動駕駛模塊上做了一些有趣的工作。我們分析了自動雨刷和車道識別功能的具體實現細節並且在真實的世界中對其中的缺陷進行了攻擊嘗試。

為了更深入的了解特斯拉車載系統的安全性,我們研究了無線功能模塊(Model S上的Parrot模塊)並在其中找到了兩個漏洞。一個存在於無線芯片固件當中,另一個存在於無線芯片驅動當中。通過組合這兩個漏洞,攻擊者可以在Parrot模塊的Linux系統當中執行任意命令。

介紹

本文會揭示這兩個漏洞的細節並介紹漏洞的利用過程來證明這兩個漏洞是可以被攻擊者用來通過無線協議遠程攻入到特斯拉車載系統當中的。

Parrot 模塊

Tesla Model S上的Parrot模塊是一個第三方模塊,型號是FC6050W,它集成了無線及藍牙功能。Parrot通過USB協議與CID相連。Parrot運行着Linux系統並使用了USB Ethernet gadget,因此Parrot與CID在USB協議基礎之上實現了以太網連接。當Tesla Model S連接到無線網絡時,實際上Parrot模塊連接到該無線網絡中。這時,網絡流量被Parrot從CID路由到外部網絡。

從一份公開的資料[1]中,我們找到了Parrot模塊的硬件組成。

Parrot模塊的引腳定義也在這份datasheet中。Linux系統的shell可以通過Debug UART引腳得到。

其中的reset引腳連到到CID的GPIO上,因此CID有能力通過下列命令重置整個Parrot模塊

Marvell 無線芯片

Marvell 88W8688是一款低成本、低功耗、高度集成的支持IEEE802.11a/g/bMAC/基帶/射頻集無線和藍牙於一體的基帶/射頻系統級芯片[2]。

Marvell官方網站[3]提供了一份該芯片的設計框圖。

Marvell 88W8688包含了一個嵌入式高性能Marvell Ferocean ARM9處理器。通過修改固件,我們獲得了Main ID寄存器中的數值0x11101556,據此推斷88W8688使用的處理器型號可能是Feroceon 88FR101 rev 1。在Parrot模塊上,Marvell 88w8688芯片通過SDIO接口與主機系統相連。

Marvell 88W8688的內存區域如下:

芯片固件

固件的下載過程包含兩個階段。首先是輔助固件「sd8688_helper.bin」的下載,然後是主固件「sd8688.bin」的下載。輔助固件負責下載主固件及驗證主固件中每個數據塊是否正確。主固件中包含了很多的數據塊,每個塊的結構定義如下。

88w8688固件的運行基於ThreadX實時操作系統,該實時操作系統多用於嵌入式設備。ThreadX的代碼存在於ROM內存區域,因此固件「sd8688.bin」實際上作為ThreadX的應用運行。

在特斯拉上,固件「sd8688.bin」的版本ID是「sd8688-B1, RF868X, FP44, 13.44.1.p49」,本文的所有研究均基於此版本。

在逆向識別出所有的ThreadX API之後,各個任務的信息便可以得到。

同時,內存池的相關信息也可以得到。

日誌及調試

芯片固件沒有實現Data Abort、Prefetch Abort、Undefine和SWI等CPU異常向量的處理過程。這意味着,固件崩潰後處理器會停止工作。我們不知道固件在哪裡因何崩潰。

所以我們修改了固件,並自己實現了這些異常處理過程。這些處理過程會記錄固件崩潰時的一些寄存器信息,包括通用寄存器,系統模式及中斷模式下的狀態寄存器和鏈接寄存器。通過這種方式,我們可以知道崩潰時系統模式或中斷模式下的一些寄存器信息。

我們將這些寄存器信息寫到末使用的內存區域,例如0x52100~0x5FFFF。這樣,這些信息在芯片重置後仍然可以被讀取。

在實現了undefine異常處理過程及修改一些指令為undefine指令後,我們可以在固件運行時獲取或設置寄存器的內容。用這種方式,我們可以調試固件。

將新的固件下載到芯片中運行,可在內核驅動中發送命令HostCmd_CMD_SOFT_RESET到芯片。隨後芯片會重置,新的固件會下載。

固件中的漏洞

88w8688芯片支持802.11e WMM (Wi-Fi Multimedia)協議。在這個協議中,STA會通過Action幀來發送ADDTS request給其他設備。請求中包含有TSPEC信息。然後其他設備同樣通過Action幀返回ADDTS response。下面是該Action幀的具體格式。

ADDTS的整個過程如下:當系統想要發送ADDTS請求時,內核驅動會發送HostCmd_CMD_WMM_ADDTS_REQ命令給芯片,然後芯片將ADDTS請求通過無線協議發送出去。當芯片收到ADDTS response後,將該回複信息去掉Action幀頭部複製到HostCmd_CMD_WMM_ADDTS_REQ結構體,作為ADDTS_REQ命令的結果在HostCmd_DS_COMMAND結構體中返回給內核驅動。內核驅動來實際處理ADDTS response。

漏洞存在於將ADDTS response複製到HostCmd_CMD_WMM_ADDTS_REQ結構體的過程中。函數wlan_handle_WMM_ADDTS_response在複製時,需要複製的長度為Action幀的長度減去4位元組Action幀頭部。如果Action幀只有頭部且長度為3。那麼複製時的長度會變為0xffffffff。這樣,內存將會被完全破壞,導致穩定的崩潰。

驅動中的漏洞

在芯片與驅動之間,有三種數據包類型通過SDIO接口傳遞,MV_TYPE_DATA、 MV_TYPE_CMD和 MV_TYPE_EVENT。其定義可在源碼中找到。

命令處理的過程大致如下。驅動接收到用戶態程序如ck5050、wpa_supplicant發來的指令,在函數wlan_prepare_cmd()中初始化HostCmd_DS_COMMAND結構體,該函數的最後一個參數pdata_buf指向與命令有關的結構,函數wlan_process_cmdresp()負責處理芯片返回的結果並將相關信息複製到pdata_buf指向的結構中。

漏洞存在於函數wlan_process_cmdresp()處理HostCmd_CMD_GET_MEM的過程中。函數wlan_process_cmdresp()沒有檢查HostCmd_DS_COMMAND結構體中的成員size的大小是否合法。因此在把HostCmd_DS_COMMAND結構中的數據複製到其他位置時發生了內存溢出。

芯片內代碼執行

很顯然,固件中的漏洞是一個堆溢出。為了利用這個漏洞實現芯片內代碼執行,我們需要知道memcpy()函數是怎樣破壞內存的,以及芯片是怎樣崩潰的,在哪裡崩潰的。

為了觸發這個漏洞,action幀頭部的長度應該小於4。同時我們需要在Action幀中提供正確的dialog token,這意味着memcpy()接收的長度只能是0xffffffff。源地址是固定的,因為該內存塊是從內存池pool_start_id_rmlmebuf分配的,並且這個內存池只有一個內存塊。目的地址是從內存池pool_start_id_tx分配的,所以目的地址可能是四個地址中的某一個。

源地址及目的地址均位於RAM內存區域0xC0000000~0xC003FFFF,但是內存地址0xC0000000到0xCFFFFFFF都是合法的。結果就是,讀或寫下面這些內存區域會得到完全一樣的效果。

因為內存區域0xC0000000到0xCFFFFFFF都是可讀可寫的,所以複製過程幾乎不會碰到內存的邊界。在複製了0x40000個位元組後,整個內存可被看作是整體移位了,其中有些數據被覆蓋並且丟失了。

88w8688中的CPU是單核的,所以複製過程中芯片不會崩潰直到有中斷產生。因為這時內存已被破壞,在大多數情況下,芯片崩潰在中斷過程中。

中斷控制器給中斷系統提供了一個接口。當一個中斷產生時,固件可從寄存器中獲取中斷事件類型並調用相應的中斷處理過程。

中斷源有很多,所以漏洞觸發後,芯片可能崩潰在多個位置。

一個可能性是中斷0x15的處理過程中,函數0x26580被調用。0xC000CC08是一個鏈表指針,這個指針在漏洞觸發後可能會被篡改。然而,對這個鏈表的操作很難給出獲得代碼執行的機會。

另一個崩潰位置在時鐘中斷的處理過程中。處理過程有時會進行線程的切換,這時其他任務會被喚醒,那麼複製過程就會被暫停。然後芯片可能崩潰在其他任務恢復運行之後。在這種情況下,固件通常崩潰在函數0x4D75C中。

這個函數會讀取一個指針0xC000D7DC,它指向結構TX_SEMAPHORE。觸發漏洞後,我們可以覆蓋這個指針,使其指向一個偽造的TX_SEMAPHORE結構。

如果偽造的TX_SEMAPHORE結構中的tx_semaphore_suspension_lis指針剛好指向偽造的TX_THREAD_STRUCT結構。那麼當函數_tx_semaphore_put()更新TX_THREAD_STRUCT結構中的鏈表的時候,我們可以得到一次任意地址寫的機會。

我們可以直接將「BL os_semaphore_put」指令的下一條指令改成跳轉指令來實現任意代碼執行,因為ITCM內存區域是RWX的。困難在於我們需要同時在內存中堆噴兩種結構TX_SEMAPHORE和TX_THREAD_STRUCT,並且還要確保指針 tx_semaphore_suspension_list 指向TX_THREAD_STRUCT結構。這些條件可以被滿足,但是利用成功率會非常低。

我們主要關注第三個崩潰位置,在MCU中斷的處理過程中。指向struct_interface 結構的指針g_interface_sdio會被覆蓋。

結構中函數指針funB會被使用。如果g_interface_sdio被篡改,那麼就會直接實現代碼執行。

這是當函數interface_call_funB()中的指令「BX R3」 在地址0x3CD4E執行時的一份寄存器日誌信息。此時,g_interface_sdio被覆蓋成了0xabcd1211。

函數interface_call_funB()在地址0x4E3D0處被MCU中斷的處理過程使用。

當複製的源地址到達0xC0040000時,整個內存可被看作是做了一次偏移。當複製的源地址到達0xC0080000時,整個內存偏移了兩次。每次偏移的距離如下。

在多數情況下,漏洞觸發後再產生中斷時,這樣的內存偏移會發生3至5次。所以指針g_interface_sdio會被來自下列地址的數據所覆蓋。

地址0xC0024FAF、 0xC00237AF和0xC0021FAF剛好位於一個巨大的DMA buffer 0xC0021F90~0xC0025790之中。這個DMA buffer用於存儲無線芯片接收到的802.11數據幀。所以這個DMA buffer可以用來堆噴偽造的指針。

為了堆噴偽造的指針,我們可以發送許多正常的802.11數據幀給芯片,其中填滿了偽造的指針。DMA buffer非常大,因此shellcode也可以直接放在數據幀中。為了提高利用的成功率,我們用了Egg Hunter在內存中查找真正的shellcode。

如果g_interface_sdio被成功的覆蓋。Shellcode或egg hunter會非常的接近0xC000B818。我們所使用的偽造指針是0x41954,因為在地址0x41954+0x24處有一個指針0xC000B991。這樣,我們可以劫持$PC到0xC000B991。同時,指針0x41954可被作為正常的指令執行。

用這種方法有25%的成功率獲得代碼執行。

攻擊主機系統

內核驅動中的漏洞可通過由芯片發送命令數據包給主機系統來觸發。命令HostCmd_CMD_GET_MEM通常由函數wlan_get_firmware_mem()發起。

這種情況下,pdata_buf指向的buffer由kmalloc()分配,所以這是一個內核堆溢出。在真實環境中函數wlan_get_firmware_mem()不會被用到,並且堆溢出的利用較複雜。

然而,一個被攻陷的芯片在返回某個命令的結果時可以更改命令ID。因此漏洞可以在許多命令的處理過程中被觸發。這時,根據pdata_buf指向的位置,漏洞即可以是堆溢出也可以是棧溢出。我們找到了函數wlan_enable_11d(),它把局部變量enable的地址作為pdata_buf。因此我們可以觸發一個棧溢出。

函數wlan_enable_11d()被wlan_11h_process_join()調用。顯然HostCmd_CMD_802_11_SNMP_MIB會在與AP的連接過程中被使用。固件中的漏洞只能在Parrot已經加入AP後使用。為了觸發wlan_enable_11d()中的棧溢出,芯片需要欺騙內核驅動芯片已經斷開與AP的連接。接着,驅動會發起重連,在這個過程中HostCmd_CMD_802_11_SNMP_MIB會發送給芯片。於是,為了觸發重連過程,芯片需要發送EVENT_DISASSOCIATED事件給驅動。

當在芯片中觸發漏洞並獲得代碼執行之後芯片不能再正常工作。所以我們的shellcode需要自己處理重連過程中的一系列命令並返回相應的結果。在命令HostCmd_CMD_802_11_SNMP_MIB來到之前,唯一一個我們要構造返回結果的命令是HostCmd_CMD_802_11_SCAN。下面是斷開連接到觸發內核漏洞的整個過程。

SDIO接口上事件和命令數據包的發送可直接通過操作寄存器SDIO_CardStatus和SDIO_SQReadBaseAddress0來完成。SDIO接口上獲得內核發來的數據可藉助SDIO_SQWriteBaseAddress0寄存器。

Linux系統中命令執行

Parrot的Linux內核2.6.36不支持NX,所以可以直接在棧上執行shellcode。同時結構HostCmd_DS_COMMAND中的size是u16類型,所以shellcode可以足夠大來做許多事情。

在觸發棧溢出並控制$PC之後 ,$R7剛好指向內核棧,所以可以很方便的執行shellcode。

在shellcode中的函數run_linux_cmd調用了Usermode Helper API來執行Linux命令。

遠程獲取shell

在漏洞觸發後,芯片中的內存被完全破壞無法繼續正常工作。同時內核棧已損壞,無法正常工作。

為了讓Parrot的無線功能可以重新正常工作,我們做了如下事情:

1. 在向內核發送完payload之後,我們通過如下命令重置了芯片。在這之後,內核驅動會重新發現芯片然後重新下載固件。

2. 在shellcode的函數fun_ret()中調用內核函數rtnl_unlock()來解開rtnl_mutex鎖。否則Linux的無線功能會無法正常功能,導致Parrot被CID重啟。

3. 在shellcode的函數fun_ret()中調用do_exit()來終止用戶態進程wpa_supplicant並重新運行,這樣就不需要修復內核棧。

4. 殺掉進程ck5050並重新運行,否則稍後ck5050會因芯片重置而崩潰,導致Parrot被CID重啟。

為了遠程獲取shell,我們強制讓Parrot連入我們自己的AP並修改iptables規則。之後,便可通過23端口訪問到Parrot的shell。

最終拿到shell的成功率在10%左右。

完整的利用過程

1. 攻擊者發送DEAUTH幀給附近的所有AP。

2. 當Tesla重連至AP時,攻擊者可以嗅探到特斯拉的MAC地址。

3. 堆噴偽造的指針,然後發送Action幀來觸發固件中的漏洞。

4. 函數memcpy()會一直工作直到有中斷產生。

5. 在芯片內成功執行任意代碼。

6. 第一階段shellcode發送EVENT_DISASSOCIATED事件給驅動。

7. 第一階段shellcode處理一系列命令並等待命令HostCmd_CMD_802_11_SNMP_MIB。

8. 第一階段shellcode通過SDIO接口發送payload來觸發內核棧溢出。

9. 第二階段shellcode執行並調用call_usermodehelper()函數。

10. 成功執行Linux系統命令並嘗試修復Parrot的無線功能。

11. 攻擊者搭建自己的AP熱點及DHCP服務器。

12. 通過Linux命令強制Parrot加入攻擊者建立的AP熱點並修改iptables規則。

13. 攻擊者可通過Parrot的23端口獲得Parrot的shell。

演示視頻

視頻內容

總結

在這篇文章中,我們展示了Marvell無線芯片固件及驅動中漏洞的具體細節,並演示了如何利用這兩個漏洞僅通過發送無線數據包的形式遠程在Parrot系統內部實現命令執行。

負責任的漏洞披露

本文所提到的兩個漏洞已於2019年3月報告給Tesla,Tesla已經在2019.36.2版本中對該漏洞進行了修復。同時,Marvell也修復了該漏洞並針對該漏洞發佈了安全公告[4]。漏洞研究報告的披露已事先與特斯拉溝通過,特斯拉對我們的發佈知情。

你可以通過下列鏈接來跟蹤本文提到的漏洞。

1. https://www.cnvd.org.cn/flaw/show/CNVD-2019-44105

2. http://www.cnnvd.org.cn/web/xxk/ldxqById.tag?CNNVD=CNNVD-201911-1040

3. http://www.cnnvd.org.cn/web/xxk/ldxqById.tag?CNNVD=CNNVD-201911-1038

4. https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-13581

5. https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-13582

參考資料

[1] https://fccid.io/RKXFC6050W/Users-Manual/user-manual-1707044

[2] https://www.marvell.com/wireless/88w8688/

[3] https://www.marvell.com/wireless/assets/Marvell-88W8688-SoC.pdf

[4] https://www.marvell.com/documents/ioaj5dntk2ubykssa78s/