深入解析iOS Search Party:FindMy网络背后的数据追踪与取证分析

本文深入探讨了iOS系统中Search Party服务的工作机制,特别是其如何通过加密的Observations.db数据库记录周边FindMy兼容设备,分析了数据存储结构、加密方式以及取证方法,并与Android的追踪检测机制进行了对比。

深入观察——更多关于 iOS Search Party 的内容

iOS 正在观察。并且评判。

注意: 本文假定您已阅读上一篇关于 AirTags 的文章(可在此处找到),因为它引用了与 iOS searchpartyd 相关的项目,但几乎没有解释。强烈建议您在继续之前阅读前一篇文章。

在过去的几个月里,我一直在探索 iOS 的 Search Party。正是它让苹果的 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 版本不同,数据分散在三个表中:ObservedAdvertisementObservedAdvertisementBeaconInfoObservedAdvertisementLocation。为了帮助理解,我创建了一个快速的 SQLite 查询来汇总数据:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
SELECT
ObservedAdvertisement.scanDate AS "Seen_Time",
ObservedAdvertisementLocation.latitude AS "Latitude",
ObservedAdvertisementLocation.longitude AS "Longitude",
ObservedAdvertisementBeaconInfo.beaconIdentifier AS "Identifier",
ObservedAdvertisement.macAddress AS "MAC_Address",
ObservedAdvertisement.rssi AS "Signal_Strength",
ObservedAdvertisement.advertisementData AS "Advertised_Data"
FROM
ObservedAdvertisementLocation
LEFT JOIN ObservedAdvertisement ON ObservedAdvertisement.advId = ObservedAdvertisementLocation.advId
LEFT JOIN ObservedAdvertisementBeaconInfo ON ObservedAdvertisementBeaconInfo.advId = ObservedAdvertisementLocation.advId
ORDER BY "Seen_Time"

参见图 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_DataadvertisementData)列的 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 对比 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 以及一个 Xiaomi Poco X7 Pro(Android 15)外出。两个设备都看到了这个标签。图 8 显示了来自 Observations.db 的 MAC 地址,图 9 显示了来自 Xiaomi 设备 Android 追踪器检测数据库 personalsafety_db 的 MAC 地址。

图 8. 来自 Observations.db 的观察到的静态 MAC 地址。

图 9. 来自 Xiaomi (personalsafety_db) 的观察到的 MAC 地址。

注意,Xiaomi 设备似乎已将“保留供将来使用”地址转换为静态地址,因为没有引用以 8F 开头的地址,而 iPhone 观察到的负载中出现了以 8F 开头的地址(见图 10)。

图 10. 来自 AirTag 的观察到的负载。

为了再次说明位变化,参见图 11-1 和 11-2。

图 11-1. 以 CF 开头的静态地址。

图 11-2. 以 8F 开头的“保留供将来使用”地址。

关于 Observations.db 有三点说明。首先,我发现当设备静止或移动不大时,向此文件写入条目的频率更高。在我驾驶期间,与手机在我家里或其他我或手机不怎么移动的地方相比,它观察的频率似乎较低。静止时,手机经常看到信标。例如,一个信标被观察到的间隔从两秒到四秒不等。其他信标被看到的频率更高!这种频率很可能是手机所在观察环境的产物,但要知道它可能发生得非常频繁。

第二点说明,这是一个重大问题:这个数据库丢弃和清理记录的速度非常非常快。举个例子,其中一个测试环境是我家附近五分钟车程的一家本地餐馆。我带着一个我的“恶意” AirTag 在餐馆里坐了一个多小时。我直接开车回家并运行了提取。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 看到了很多记录,但有两个突出项。

图 14 中红框突出的值与 Observations.db 中其余数据相比是异常值,因为它们的 RSSI 信号。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 对比 20:08:22)。图 20 显示了映射的位置。注意位置出现在 4 号航站楼外,这是合理的,因为我在飞机上(起飞前)打开了 iPhone 11 的飞行模式。

图 20. 在飞机上。

再多一个观察

虽然我能够查看父 searchpartyd 目录中的其他数据库,但除了一个之外,大多数都是空的,并且没有包含任何有趣的数据:ItemSharingKeys.db。这个数据库很有趣。它包含与所检查设备上 iCloud 账户所有者共享的信标的广播数据,但这并不是它有趣的原因。有趣之处在于,它似乎包含共享信标的当前和未来的广播负载信息,包括一组私钥蓝牙 MAC 地址。为了提取必要信息,我略微修改了 SQL 查询,添加了来自 Observations.db 的 Advertisement Identifier。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
SELECT
ObservedAdvertisement.scanDate AS "Seen_Time",
ObservedAdvertisementLocation.latitude AS "Latitude",
ObservedAdvertisementLocation.longitude AS "Longitude",
ObservedAdvertisementLocation.advId AS "Advertisment_Identifier",
ObservedAdvertisementBeaconInfo.beaconIdentifier AS "Identifier",
ObservedAdvertisement.macAddress AS "MAC_Address",
ObservedAdvertisement.rssi AS "Signal_Strength",
ObservedAdvertisement.advertisementData AS "Advertised_Data",
ObservedAdvertisementBeaconInfo.sequence AS "Sequence_Number"
FROM
ObservedAdvertisementLocation
LEFT JOIN ObservedAdvertisement ON ObservedAdvertisement.advId = ObservedAdvertisementLocation.advId
LEFT JOIN ObservedAdvertisementBeaconInfo ON ObservedAdvertisementBeaconInfo.advId = ObservedAdvertisementLocation.advId
ORDER BY "Seen_Time"

此查询从 ObservedAdvertisementLocation 表中提取 Advertisement Identifier。此列中的值与表 ObservedAdvertisementBeaconInfo 中的列索引相关,这是感兴趣的列。接下来,参见图 21 和 22。注意图 21 右窗格中共享标签的突出显示的 MAC 地址负载。

图 21. Observations.db 中共享标签的记录条目,以及 Advertisement ID 和 MAC 地址。

图 22. Observations.db 中表 ObservedAdvertisementBeaconInfo 中的 Advertisement ID 和 Index 值。

ObservedAdvertisementBeaconInfo 中的列索引(图 22)与 ItemSharingKeys.db 中表 NearOwnerKeys 中同名的列相关。参见图 23。

图 23. 来自图 21 的广播的 MAC 地址。

图 23 中有趣的发现是,AirTag 的广播 MAC 地址列在 nearOwnerAdvertisement 列中。还有一些额外增加的索引号,每个都有其自己的 nearOwnerAdvertisment 值,所有这些看起来都是 MAC 地址。为了进一步调查,我等了几个小时,带上同一个“共享” AirTag,开车转了一会儿,然后重新提取了设备。图 24 显示了后来提取的 Observations.db

图 24. 后来提取的记录条目。注意 Advertisement Identifier。

查看表 ObservedAdvertisementBeaconInfo,在 index 列中找到值 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 到来时发生变化。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计