自 iOS 14 系统起,苹果公司在设备状态栏中设置了颜色指示器。
当应用调用摄像头时会显示绿色圆点,调用麦克风时会显示橙色圆点。
这是一项关键隐私功能,用于提醒用户设备可能正处于被监视状态。
本研究详细阐述了由 Intellexa/Cytrox 公司开发的 Predator 间谍软件,如何通过技术手段绕过该指示器,实现隐秘监视。
通过对 Predator 间谍软件 iOS 样本的逆向工程分析,jamf发现了多项此前未公开的 Predator间谍软件技术机制,具体如下:
精准锁定了 iOS 系统中私有框架的特定 API 接口作为攻击目标,明确了该间谍软件要劫持的系统核心函数。
苹果公司于 2020 年发布的 iOS 14 系统中,首次引入录制指示器这一隐私保护机制,具体功能如下表所示:
这些指示器会在设备状态栏中显示,且合法应用无法对其进行屏蔽。该功能由 iOS 系统的主屏幕及用户界面控制进程 SpringBoard 负责管理,通过私有框架类对传感器的活动状态进行实时监测。

图 1:左侧橙色圆点表示麦克风正在使用;右侧绿色圆点表示摄像头正在使用
2022 年 1 月,现隶属于 Jamf 的 ZecOps 公司曾发布了一项名为 “NoReboot” 的技术研究。该技术展示了恶意软件如何模拟设备关机状态,同时维持后台监视功能。其实现原理如下:
通过以上手段,恶意软件可营造出设备已关机的假象,而摄像头和麦克风仍在后台持续工作。
Predator 采用了与 NoReboot 完全不同的技术路径。它不会模拟设备关机,而是仅选择性屏蔽录制指示器,同时保证设备其他功能正常运行。这种攻击方式更为隐蔽,用户使用设备时不会发现任何异常,但实际上设备已处于被监视状态。
两种技术的对比如下表所示:
Predator 间谍软件的辅助模块具备四项独立功能,具体如下表所示:
每个模块均通过一套简单的命令协议进行控制:
HiddenDot::setupHook () 函数的攻击目标是 SpringBoard 进程中的传感器活动数据提供器,具体为 SBSensorActivityDataProvider 类的_handleNewDomainData: 方法。

图 2:
HiddenDot::setupHook () 函数对 SBSensorActivityDataProvider._handleNewDomainData: 方法进行挂钩
该_handleNewDomainData: 方法的作用是,每当设备传感器活动状态发生变化时,如摄像头开启、麦克风激活等,iOS 系统就会调用此方法进行状态更新。Predator 通过对这一个方法挂钩,可在传感器状态信息传递至指示器显示系统之前,对所有状态更新请求进行拦截。
指示器屏蔽的核心机制十分简洁。反编译后的回调函数核心逻辑如下:

图3:HiddenDot 回调函数伪代码 —— 通过 **a2 = 0 操作将 self 指针置空
在汇编语言层面,上述逻辑仅需一条 STR XZR 指令即可实现,该指令的作用是将线程状态寄存器的值置零。

图 4:HiddenDot 回调函数汇编代码 ——STR XZR, [X8] 指令将线程状态中的 x0 寄存器置零
该技术利用了 Objective-C 语言的一项核心特性:向空对象(nil)发送消息时,系统会自动忽略该消息,且不会产生任何报错。
在 ARM64 架构中,函数调用遵循特定约定,self 指针会被存储在 x0 寄存器中。当系统调用 Objective-C 方法时,例如:
[SBSensorActivityDataProvider _handleNewDomainData:newData]寄存器的赋值情况如下:
Predator 在该方法执行前,将 x0 寄存器的值置为 0(即空指针),此时上述方法调用就会变为:
[nil _handleNewDomainData:newData]在 Objective-C 语言中,该调用会直接返回空值(nil/0),且不会执行方法内的任何代码。传感器活动状态的更新请求会被静默丢弃,SpringBoard 进程无法获知摄像头或麦克风已被激活,因此不会显示任何指示器。
回调函数返回值为 2,该值在 DMHooker 框架的异常挂钩系统中,表示 “使用修改后的线程状态继续执行程序”。返回值的完整枚举定义如下:
本研究的一项关键发现是:这一个Hook函数即可同时屏蔽绿色(摄像头)和橙色(麦克风)两种指示器。SBSensorActivityDataProvider 类的作用是,在将传感器活动信息分发至用户界面层之前,对所有传感器的活动状态进行汇总。
因此,通过拦截_handleNewDomainData: 方法,Predator 可利用一个Hook函数,实现对所有类型传感器状态更新的拦截。
这种实现方式比在无效代码中发现的另一种方案更高效。黑鸟认为主要原因在于下面的废弃方案需要为每种指示器分别设置Hook函数。
在分析过程中,还发现了一个名为 CSWatcherSpawner::TestHooker () 的函数。
该函数实现了另一种指示器屏蔽机制,直接Hook SBRecordingIndicatorManager 类:

图 5:TestHooker () 函数中的无效代码展示了被弃用的 SBRecordingIndicatorManager 挂钩方案 —— 该函数无任何交叉引用
该函数不存在任何交叉引用,即从未被调用过。这表明它是一个开发 / 测试阶段的代码,最终被更简洁高效的 SBSensorActivityDataProvider 挂钩方案所取代。
被弃用的技术方案需要满足以下条件:
而最终投入使用的_handleNewDomainData: 方法挂钩方案更为简洁,它从数据源层面直接阻断状态更新,而非在用户界面层进行干预。
VoIP 录音模块不具备指示器屏蔽功能,仅对音频处理函数进行挂钩:

图 6:
Voip::setupHooks () 函数挂钩 AudioConverterNew 和 AudioConverterConvertComplexBuffer+52 方法 —— 无任何指示器屏蔽相关代码
该模块的音频捕获流程具体如下:
但该模块未包含任何屏蔽橙色麦克风指示器的代码。这一设计意味着,需要先执行一次全局指示器屏蔽操作,之后 VoIP 录音功能及可能的摄像头捕获功能,才能在不触发可见指示器的情况下运行。
CameraEnabler 模块采用了另一种技术手段 —— 指针认证码(PAC)重定向。
该模块并未对已知符号进行挂钩,而是通过 ARM64 指令模式匹配技术,定位目标函数的地址:

图 7:CameraEnabler::findFunctionAddress () 函数利用 memmem () 函数,在 FigVideoCaptureSourceCreateWithSourceInfo 函数附近搜索 ARM64 指令头模式
这种技术可让 Predator 间谍软件定位到未导出的内部函数,使Hook函数能够有效应对 iOS 系统更新。即使系统对函数名称或导出结构进行调整,该技术依然有效。
CameraEnabler 模块的回调函数会检查 x0 寄存器的值,并根据检查结果执行条件性重定向操作:

由于原文已经被删除(未知原因),因此内容仅供参考,为避免内容流失,特此整理记录。
https://www.jamf.com/blog/predator-spyware-ios-recording-indicator-bypass-analysis/