iOS正在观察。也在判断。
在过去的几个月里,我一直在探索iOS的搜索派对。正是这个东西让苹果的FindMy网络运转起来,而理解其底层机制以及哪些数据可用于取证分析花费了一些时间。主要的障碍是加密,对此我的感受很矛盾。作为用户,我欣赏数据保护。作为从业者,我则不喜欢数据保护。去年在为SANS演讲做准备时,我与之斗争了一段时间,最终才弄清楚如何获取关于不受欢迎的AirTags的信息(您可以在此处阅读相关努力)。
事实证明,searchpartyd还有其他可用数据,这与Android处理蓝牙追踪器检测的方式一致。为了测试,我使用了运行iOS 17.5.1的iPhone 14和运行iOS 18.3的iPhone 11,后者是对去年研究的更新。请注意,本文描述的文件仅在iOS 16.x及更高版本中看到。
加密。又一次。
在研究关于野生AirTags的文章时,我发现自己很好奇iOS是如何记录它在环境中看到的FindMy兼容设备的。毕竟,苹果在谈论该功能时喜欢吹嘘网络上兼容设备的数量。显然,iOS必须将数据存储在某个地方,我认为这可能类似于Android处理恶意设备的方式。在同一个父目录(/private/var/mobile/Library/com.apple.icloud.searchpartyd)中有几个数据库看起来是可能的候选者,但我发现这些文件当然是被加密的。其中一个候选者是一个名为Observations.db的SQLite数据库。参见图1。
图1. com.apple.icloud.searchpartyd中的Observations.db。
在十六进制下查看此文件揭示了一些有趣的信息。参见图2。
图2. 十六进制下的Observations.db。
乍一看,这个文件像是加密了,确实如此;然而,这里有一个非常细微的线索,我起初错过了,直到我开始查看父searchpartyd目录中的其他数据库。这个线索,在图2的红框中高亮显示,是字节16-23没有被加密。这些字节是SQLite头文件的一部分,并提供了一些关于数据库的信息。它们代表以下内容(读作大端序):
| 字节 | 值 |
|---|---|
| 16 – 17 | 页面大小(字节) |
| 18 | 格式写入版本(1 = 传统,2 = WAL) |
| 19 | 格式读取版本(1 = 传统,2 = WAL) |
| 20 | 页面末尾的保留字节(填充) |
| 21 | 最大嵌入式负载(必须为64) |
| 22 | 最小嵌入式负载(必须为32) |
| 23 | 叶子负载分数(必须为32) |
检查同一目录路径下的其他数据库发现,这种行为(以及字节值)在所有数据库中都是相同的。我观察到每个数据库的以下值:
| 字节 | 值 |
|---|---|
| 16 – 17 | 每页4096字节 (x1000) |
| 18 | WAL写入版本 (x02) |
| 19 | WAL读取版本 (x02) |
| 20 | 12字节填充 (x0C) |
| 21 | 64字节最大负载 (x40) |
| 22 | 32字节最小负载 (x20) |
| 23 | 32字节叶子负载分数 (x20) |
多个文件的字节相同,这排除了SQL Cipher作为加密方法。事实证明,Observations.db(以及其他文件)是使用SQLite加密扩展(SEE)加密的。SEE规范声明这些字节作为该方法的一部分不被加密。此外,该方法在RC4、AES-128(OFB或CCM模式)或OFB模式下的AES-256中运行,填充为12字节;在这种情况下,填充是数据库中每个页面的初始化向量(IV或nonce)。知道了这些信息,我前往钥匙串查看可能与searchpartyd关联的其他凭证。参见图3。
图3. 按searchpartyd过滤的钥匙串。
图3显示了来自iPhone 14的钥匙串,过滤了与searchpartyd相关的项目(红框中高亮显示)。令人惊讶的是,我在searchpartyd目录中看到的每个数据库(见图1)都有它们自己对应的钥匙串条目,名称完全一致。Observations.db的密钥长度为32字节,这意味着很可能使用了SEE的AES-256 OFB变体。有了密钥、页面大小和填充,我开始尝试解密数据。虽然我不是它的最大粉丝,但我得承认ChatGPT在这里实际上派上了用场,在最终呈现出一个可用的数据库之前尝试了不同的方法。我过去不信任(现在仍然不信任)它,所以我寻求了Ian Whiffin的帮助,他发现了一些关于-WAL文件加密的额外问题,花了一些时间才解决。不过,最终的结果是,我们得到了解密的数据库及其关联的-WAL文件。
观察
Observations.db的行为确实如我所料:它记录所有看到的FindMy兼容设备(稍后会详细介绍其外观)。与它的Android对应物不同,数据分散在三个表中:ObservedAdvertisement、ObservedAdvertisementBeaconInfo和ObservedAdvertisementLocation。为了方便,我创建了一个快速的SQLite查询来汇总数据:
|
|
参见图4查看执行后的查询。
图4. 执行后的SQL查询。
与Android一样,iOS记录了它看到FindMy兼容设备的时间以及检测到它们时手机的位置。iOS在位置数据来源方面并不缺乏,但这是另一个可靠的来源(这些经纬度值非常准确)。还有一个信号强度(RSSI)值,这有助于理解与被检查设备的接近程度。该值越接近0,信号越强,因此理论上设备离手机越近;然而,重要的是要记住,环境条件如结构、地形、人员和电磁干扰都可能影响RSSI值。
查询还显示了信标标识符。由手机上登录的iCloud账户拥有或与账户共享的信标(指任何参与FindMy网络的东西)将有一个标识符。关于这些UUID的信息可以在~/BeaconNamingRecords/或~/SharedBeacons中的plist文件中找到。这些在我之前的AirTag文章中已经介绍过。图4中看到的每个条目都有一个UUID值,这表明看到的信标与手机上登录的iCloud账户相关联,包括拥有的和共享的。
查询返回的最后两列涉及观察到的设备的MAC地址和相应的广播负载。由于在FindMy网络上运行的设备使用轮换的蓝牙MAC地址(根据规范),在信标与账户无关的情况下(即数据库记录中没有UUID),这些MAC地址基本上是无用的。然而,在不受欢迎追踪的情况下可能有应用。例如,如果相同的MAC地址在一段时间内(可能是24小时左右)持续出现在Observations.db记录中,但手机尚未将其识别为WildMode项目。
广播信息很有趣。首先,参见图5。
图5. 来自Advertised_Data(advertisementData)列的BLE广播数据。
Advertised_Data列中的数据应该包含某些信息,但我发现,至少对于AirTags和AirPods,只有部分数据。由苹果和谷歌共同编写的公共规范规定,广播其数据的项目应在其广播负载中提供以下信息:
| 字节: | 值 |
|---|---|
| 0 – 5 | MAC地址 |
| 6 – 8 | 标志TLV(可选) |
| 9 – 12 | 服务数据TLV(可选) |
| 13 | 网络ID |
| 14 | 靠近所有者位(LSB + 7个保留位 (1=是, 0=否) |
| 15 – 36 | 专有负载(可选) |
关于此规范和我观察到的负载,有三点需要注意。首先,此规范仍处于草案阶段,因此可能不完整。例如,据我所知,网络ID尚未在IANA注册,因此目前很难判断设备属于哪个网络(谷歌或苹果)。我在上述字节13中没有发现任何一致性。其次,我还发现广播负载中的靠近所有者位在不同设备之间不一致。有时字节14中的最低有效位被设置为“1”,但它并不是测试账户拥有的信标,测试手机会发出不受欢迎的警报。这引发了关于两家公司目前是否遵循规范的疑问。我完全预计这会改变,但要知道,就目前而言,事情似乎并不一致,这使得它们不可靠。
第三,“Advertised_Data”列中广播负载数据中的MAC地址与“MAC_Address”列中看到的MAC地址存在差异。参见图6。
图6. 图5中看到的信标的MAC地址。
注意,图6中第一个字节的第一个半字节与图5中看到的(87 versus C7)不同。这是由于网络上运行的设备使用的随机化方式。蓝牙规范有详细说明(参见规范第1.3.2节),但要知道,根据苹果-谷歌规范,两个最高有效位应以某种方式切换,变成私有的、不可解析的MAC地址;然而,这并不是我观察到的(再次强调,规范是草案)。在上面的例子中,第二个最高有效位从1变为0,导致MAC地址中的第一个八位字节从C7(0b11 – 静态地址)变为87(0b10 – 保留供私人使用)。
图7-1和7-2显示了位变化如何影响第一个八位字节。
图7-1. 以C7开头的静态地址。
图7-2. 以87开头的“保留供将来使用”地址。
为了提供一个例子,我带着一个AirTag、iPhone 11和小米Poco X7 Pro(Android 15)外出。两个设备都看到了这个标签。图8显示了来自Observations.db的MAC地址,图9显示了来自小米设备的Android追踪器检测数据库personalsafety_db中的MAC地址。
图8. 从Observations.db观察到的静态MAC地址。
图9. 从小米(personalsafety_db)观察到的MAC地址。
请注意,小米设备似乎已经完成了从“保留供将来使用”地址到静态地址的转换,因为没有引用以8F开头的地址,而这在iPhone观察到的负载中可以看到(见图10)。
图10. 从AirTag观察到的负载。
为了再次说明位变化,参见图11-1和11-2。
图11-1. 以CF开头的静态地址。
图11-2. 以8F开头的“保留供将来使用”地址。
关于Observations.db的三点说明。首先,我发现当设备静止或移动不多时,条目更频繁地写入此文件。在我开车期间,与手机在我家或其他我或手机不怎么移动的地方相比,它似乎观察频率较低。静止时,手机经常看到信标。例如,一个信标被观察到的间隔从两秒到四秒不等。其他的被观察得更频繁!频率可能是手机观察环境的产物,但要知道它可以非常频繁地发生。
第二点说明,这一点非常重要:这个数据库非常、非常快地丢弃和清理记录。例如,一个测试环境是离我家五分钟路程的一家当地餐馆。我带了一个我的“恶意”AirTags,在餐厅里坐了一个多小时。我直接开车回家并运行提取。Observations.db完全没有任何关于餐厅里AirTag的信息;然而,在我把车开进车库到我让手机进入飞行模式以运行提取(在我家)之间,它确实看到了它十次。另一个测试涉及AirTag和iPhone 14,当时我正在做一些纹身工作(大约三个小时)。在我离开纹身店之前,我将手机设置为飞行模式,确保蓝牙无线电关闭,然后开车回家。这次,Observations.db有大量记录条目,似乎是纹身店的AirTag(基于RSSI值),离我家开车近40分钟,此外还有在我从家到纹身店的路线上偶尔记录的位置。
我提到这两种情况是为了说明:如果你想从这个数据库中获取不涉及你的实验室/工作地点的位置数据,必须在设备被扣押的地方(即犯罪现场)确保蓝牙无线电被切断。否则,Observations.db将丢弃和清理可能与你的检查目标相关的记录。这里的例外是与拥有/共享信标相关的记录。这些记录往往保留得更久。
最后,检查此文件时务必同时检查关联的-WAL文件,这一点极其重要。为了说明原因,参见图12和图13。
图12. 没有-WAL的Observations.db。只有264条记录…
图13. …而有-WAL。多了四十条记录!
从-WAL中提取的记录数量可能相当大,并且很可能包含主数据库中不包含的数据,因此请确保你使用的任何工具都能处理-WAL条目。由于记录写入和移除的速度,检查文件时必须带上-WAL。
一个例子
为了说明Observations.db如何处理未知信标,我带着iPhone 11和一个AirTag去了一个人口密集的地点:纽约市的JFK机场。有这么多人(和i-设备),iPhone应该有大量可观察的对象,这应该会让Observations.db非常繁忙。AirTag和iPhone 11都在我背包的口袋里,实际上接触着,因此理论上RSSI信号应该相对较高,这可能有助于识别AirTag。事实证明,这些想法是正确的。参见图14。
图14. iPhone 11看到了日志,但有两个突出项。
由于它们的RSSI信号,图14中红框高亮的值与Observations.db中的其余数据相比是异常值。RSSI值为-37和-38都是接近距离的良好指标。与这两个条目关联的广播负载是相同的。图15高亮了图14中-38 RSSI记录条目的广播负载。图15中还高亮了广播的MAC地址(字节0-5 – 蓝框)。
图15. 广播负载。
Observations.db中有多条具有此负载的记录,这表明AirTag在机场“跟随”我。最终,AirTag被识别为不受欢迎,这在searchpartyd目录的~/WildModeAssociationRecord中触发了一个plist文件的创建,但没有通知。iPhone 11为AirTag分配了标识符13BB48EF-F050-4013-A36B-309821C999ED。解密plist发现了以下信息。参见图16。
图16. 测试AirTag的WildModeAssociation记录。
加密plist文件中的广播数据(图16中的红框)与Observations.db中广播负载中看到的内容匹配。此外,负载中的MAC地址在plist中有自己的键/值对(图16中的蓝框高亮),并且与广播负载中的内容匹配。此外,第一个位置条目(未高亮)将手机定位在JFK的4号航站楼,这正是我所在的位置。参见图17。
图17. WildModeAssociation记录中看到的第一个位置。
.plist文件的底部包含一些信息。即,给用户(我)的(可能是最终的)通知状态,以及在.plist文件创建之前最后看到的位置。参见图18。
图18. 观察到的状态和最后看到的位置。
上面,通知状态“staged”在红框中可见,以及最后看到的位置(未高亮)。映射的位置见图19。我当时静止在4号航站楼的B48登机口等待登机。
图19. 通知前最后看到的位置。我在B48登机口。
Observations.db中的位置略有不同,因为关联的时间戳晚于.plist文件中找到的时间戳;Observations.db记录发生在.plist文件中最后位置之后大约1小时20分钟(21:29:21 versus 20:08:22)。图20显示了映射的位置。请注意,位置出现在4号航站楼外,这是合理的,因为我在飞机上(就在起飞前)打开了iPhone 11的飞行模式。
图20. 在飞机上。
还有一个观察
虽然我能够查看父searchpartyd目录中的其他数据库,但它们中没有一个包含任何感兴趣的数据,而且大多数是空的,除了一个:ItemSharingKeys.db。这个数据库很有趣。它包含已与受检设备上的iCloud账户所有者共享的信标的广播数据,但这并不是它有趣的地方。它有趣的地方在于,它似乎包含共享信标的当前和未来广播负载信息,包括一组私钥和蓝牙MAC地址。为了提取必要的信息,我稍微修改了SQL查询,添加了来自Observations.db的广告标识符。
|
|
这个查询从ObservedAdvertisementLocation表中拉回广告标识符。此列中的值与表ObservedAdvertisementBeaconInfo中的列索引相关,这是感兴趣的列。接下来,参见图21和图22。注意图21右窗格中共享标签的高亮MAC地址负载。
图21. Observations.db中共享标签的记录条目,以及广告ID和MAC地址。
图22. Observations.db中表ObservedAdvertisementBeaconInfo中的广告ID和索引值。
ObservedAdvertisementBeaconInfo中的列索引(图22)与ItemSharingKeys.db中表NearOwnerKeys中的同名列相关。参见图23。
图23. 来自图21的广播MAC地址。
图23中有趣的发现是,AirTag的广播MAC地址列在nearOwnerAdvertisement列中。还有额外的索引号,数字递增,每个都有自己的nearOwnerAdvertisment值,所有这些都看起来像是MAC地址。为了进一步调查这一点,我等了几个小时,带着同一个“共享”AirTag,开车转了一会儿,然后重新提取了设备。图24显示了后来提取的Observations.db。
图24. 后来提取的记录条目。注意广告标识符。
查看表ObservedAdvertisementBeaconInfo,在索引列中找到一个值126603。参见图25。
图25. 来自后来提取的新索引值。
跳回第一次提取的ItemSharingKeys.db,查找126603,找到下面图26中看到的内容。
图26. nearOwnerAdvertisement中匹配的MAC地址。
广播的MAC地址匹配。本质上,与AirTag共享的手机正在为共享的AirTag保存一批广播MAC地址,很可能供将来在“看到”AirTag时使用。NearOwnerKeys表中有230条记录,因此理论上,可能可以在合理范围内(即,只要NearOwnerKeys表中有记录)预测共享AirTag的广播MAC地址。显然,这需要超出本文范围的额外研究,但能够预测地址序列的规范要求确实为这一理论提供了可信度。
到此为止。暂时。
这是一个不错的发现,因为它在一定程度上证实了我的想法,即iOS设备正在记录它们看到的所有FindMy兼容设备,很像Android那样。Observations.db为检查员提供了iOS上另一个位置数据来源,尽管保留时间还有很多不足之处。尽管如此,它是另一个来源,如果处理得当,可以利用它。
虽然在iOS 17和18之间事情似乎相当一致,但我完全预计一旦公共规范发布,事情会发生变化。当然,这并不排除今年晚些时候iOS 126到来时事情会发生变化。