BLE安全初探之HACKMELOCK
0x00 環境搭建
低功耗藍芽技術(Bluetooth Low Energy)作為一種無線通訊技術,其設計目標和實現與經典藍芽技術有很大的不同,關於其的概述和技術細節可以參考文末的連結和著作。本文會結合書本知識對其中的協議資料包進行備註,以加深對主從裝置互動流程的理解,進一步探索針對某BLE應用的攻擊方式。
環境主從裝置的選取是參考 ofollow,noindex" target="_blank">BLUETOOTH SMART HACKMELOCK 提供的模擬環境,其在樹莓派中用nodejs搭建了一個虛擬的BLE門鎖,專門寫了一個Android app來對這個門鎖進行操作,兩端都遺留了一些安全問題供我們後續探索學習。
UnicornTeam曾經講過無線通訊的攻擊手段可以分為監聽、重放、欺騙和劫持攻擊。個人感覺先要嗅探相關流量進行理解分析才能知己知彼有所突破,厚著臉皮向大佬團隊借了一個 nRF51422 來對BLE進行嗅探,其文件 nRF-Sniffer-UG-v2 也寫得很清楚,所以最終構建的環境如圖所示:
0x01 流程探索
上文搭建的虛擬環境中APP點選相關功能,服務端響應後在控制檯也可以看到一定的log輸出,方便我們理解協議的互動,接下來我會配合捕獲的流量進行解釋,資料包流量也已備份至 Github 。低功耗藍芽的體系結構如下:
廣播建立連線和發現服務特性
建立起虛擬門鎖從裝置後,其就在不停地廣播。廣播報文的型別有7種,用途比較廣泛的型別是ADV_IND通用廣播指示,廣播報文的大致結構如下:
在資料包中也可以看到很多樹莓派的廣播報文:
開啟手機App在被動掃描接收到所需的廣播報文後,便會發起連線請求:
主從裝置在進入連線態後就會發送資料報文進行通訊,資料報文格式和廣播報文格式略有不同:
資料報文中的邏輯鏈路識別符號LLID把資料報文分成三種類型,其中鏈路層控制報文(11)用於管理連線,如下的資料包便是在管理連線中的版本交換:
不僅是隻有鏈路層的資料包,兩個裝置的上層服務還是會通過L2CAP通道(資料包序列),其結構如下:
低功耗藍芽一共使用3條通道,如下的L2CAP資料包則是低功耗信令通道的資料包,用於主機層的信令:
屬性層和通用屬性規範層作為BLE的核心概念,一個是抽象協議一個是通用配置檔案。屬性通俗地來講就是一條有標籤的、可以被定址的資料,其結構如下:
在低功耗藍芽中特性是由一種或多種屬性組成,服務是由一種或多種特性組成,並且是由服務宣告來對服務進行分組,用特性宣告來對特性進行分組。服務和特性的發現由通用屬性規範規定,具體則表現為不同型別的屬性協議,如下的資料包便是按組型別讀取請求來讀取首要服務宣告:
響應則是所有首要服務宣告的屬性控制代碼、該首要服務中最後一個屬性以及首要服務宣告的數值:
類似的,對於每一個服務也會有發現特性的請求和響應:
在資料包中分開看請求的服務和特性可能不是太方便,可以藉助 bleah 直接列舉裝置上的所以屬性:
門鎖初始化配置
門鎖的初始化配置在服務端控制檯的輸出如下:
在資料包上的表現就是先對從裝置的0x0013 handler進行讀取請求,得到響應值後開始對0x000c handler進行一系列的寫入請求,一共寫入了24個序列完成初始化階段:
開關鎖操作
開關鎖的操作在服務端控制檯的輸出上看,貌似是有一個內部的認證過程:
首先讀取0x0013 handler讀取一個random challenge,將響應寫入0x000c handler,如果通過了認證則可以進行開關鎖的操作,並且開關鎖向handler中寫入的值也是固定的:
認證憑據重置
這個功能在服務端上被稱為Data transfer,通過接收一條命令觸發,並重新生成了24個序列通知客戶端:
在資料包上可以看到還有對0x0010 handler的寫入請求,向0x000c寫入的則是資料重傳命令:
0x02 攻擊方式
流程探索
流程中比較感興趣的就是內部實現的認證和資料重傳部分,首先猜測不經過認證直接寫入資料重傳指令是否可以重置門鎖,這裡藉助gatttool進行BLE的連線和請求:
很遺憾是需要認證的,那我們就需要分析服務端或者客戶端的程式,逆向出認證的具體流程。上jeb反編譯apk,根據auth字串定位至認證相關邏輯。可知在接收Challenge後,和v7一起傳入 hackmelockDevice.calculateResponse
方法,正常的開鎖流程會使v7為1,通過二維碼分享的開鎖流程會使v7為2:
跟進去可知,根據不同的keyID對Challenge進行兩次AES加密計算出響應:
而其中的keys陣列則是在最開始初始化門鎖中傳遞的23個序列:
對於keyID為0的序列tohex為12個位元組,後面用空字元補齊16位元組,進行兩次AES加密用python程式碼還是很簡單就實現了:
import sys from Crypto.Cipher import AES from binascii import a2b_hex, b2a_hex def calc(key, challenge): plaint_1 = a2b_hex(challenge) key_1 = a2b_hex(key) aes_1 = AES.new(key_1, AES.MODE_ECB) cipher_1 = aes_1.encrypt(plaint_1) print b2a_hex(cipher_1) plaint_2 = a2b_hex("DDAAFF03040506070809101112131415") key_2 = cipher_1 aes_2 = AES.new(key_2, AES.MODE_ECB) cipher_2 = aes_2.encrypt(plaint_2) print b2a_hex(cipher_2) if __name__ == '__main__': if len(sys.argv) > 2: calc(sys.argv[1], sys.argv[2])
服務端後門
服務端程式碼 是用nodejs寫的,看起來比安卓逆向輕鬆多了,在服務端留下了一個後門可以使用特定密碼直接通過認證:
if ( (authResponse === fin_16.toString('hex')) || (authResponse === '4861636b6d654c6f636b4d6173746572')) { console.log('AUTHENTICATION OK!'.green); this.authenticated = true; this.status = statusAuthenticated; }
認證程式碼缺陷
最開始按照正常的加密邏輯,向0x000c handler寫入response總是認證不通過,對比在app上操作的控制檯輸出,發現其在計算出的response後多加了一個 00
,幡然醒悟最後一個寫入的字元就是用來指示keyID的。而在服務端程式碼中,其不僅載入了初始化時傳遞的23個key,還以 00
擴充套件至128個:
Hackmelock.prototype.loadConfig = function(configFile) { this.config = fs.readFileSync(configFile).toString().split("\n"); //pop last empty line this.config.pop(); for (i=this.config.length; i<128; i++) { this.config.push('000000000000000000000000') }
如果我們將keyID指示得過大,那麼第一輪AES加密的key就已經確定了,相應的認證措施也就失效了:
二維碼資訊洩露
App中還有個Share功能,旨在向他人提供臨時開關鎖的許可權:
從App逆向的結果來看,二維碼中會儲存keyID為1的序列,有了任意的key就不存在許可權和時間的限制了。如上的二維掃出的結果就是 576C0603:4CE495E48D0BF00BF1BC85F3:1:1542885650:1542902400
,與之前資料傳輸的記錄相符:
其他
- 服務端程式碼 中使用
Math.random()
來生成隨機數,但這種方法並不是 cryptographically-secure ,可能會被預測但我個人暫未想出來合適的攻擊場景。 - 作者還提示存在命令注入的問題,我對nodejs和安卓瞭解的不多,感興趣的同學可以探索一下。
0x03 總結參考
總結
- Android上也可以對藍芽進行 抓包 ,不過是主裝置上HCI通道的資料包,看起來可能不是太直接。
- 上面的虛擬門鎖的使用的是預設安全級別,鏈路沒有加密和認證配對的操作,深入探究的話可以使用工具進行中間人和重放攻擊的嘗試,smartlockpicking團隊提供的 培訓講義 還是很值得學習一下的。
- 換一種角度看,喜歡做練習的同學可以嘗試一下 BLE CTF ,當然挖掘BLE相關的 漏洞 也是有可能的。