视频会议的开发与探索(二):享受 FaceTime

FaceTime是Apple给iOS和Mac提供的视频会议App.它是闭源的,核心功能未使用任何第三方库。我想知道模糊化FaceTime音频和视频流的内容是否会导致与WebRTC相同的结果。

模糊化准备

Philipp Hancke 在2015年对FaceTime的结构进行了出色的分析。与WebRTC相似,以SDP格式交换发信信息,接着使用RTP传输音频视频流,观察FaceTime在Mac上的具体实现,发信功能受到了称作avconferenced的保护进程,它包括一个称谓SRTPEncryptData的函数,这个函数继续调用CCCryptorUpdate,用来加密标题下的RTP信息包。

为了快速测试模糊化是否有效,我修改了底层加密数据。一般来说,这可以通过设置DYLD_INSERT_LIBRARIES环境变量实现,但是因为avconferenced是一个受保护的进程,当终结时,会自动重启,我们不能轻易设置环境变量。我最终使用insert_dylib对其进行改变,使它会载入某些library,并且重启进程。被载入的library使用DYLD_INTERPOSE,将CCCryptorUpdate替换成一个模糊了每一个输入buffer的版本。这个方案存在诸多问题:它模糊化加密和解密,每一次从avconferenced到CCCryptorUpdate的通话都受到了影响,不仅仅是包含 SRTP 的那一次,并且无法复制崩溃案例。但是使用修改过的FaceTime与iPhone 通话破坏了视频输出,手机在几分钟之后就崩溃了。这就说明当 FaceTime 通话被加密时,这个函数也包含在内,因此模糊化会出现一些问题。

我对函数做了一些修改。我将模糊化输入buffer限制到两个线程,分别向RTP写入音频和视频输出,这同时还解决了解密信息被模糊化的问题,因为这些线程只是开始被加密了。我接着添加了一个功能:向一系列日志文件写入每一个信息包被加密,模糊化后的内容,这样测试案例就可以重现。这需要改变avconferenced的沙盒,才能向日志位置写入文件,并且添加线程锁,因为调用CCCryptorUpdate虽然是线程安全的,但是记录信息却不是线程安全的操作。

通话重放

我接着写了第二个library,连接CCCryptorUpdate并且重现第一个library记录的信息包,方法是按顺序拷贝记录过的信息到一个packet buffer,这个buffer是函数的输入。不幸的是,这需要对AVConference binary进行一些小的修改,因为SRTPEncryptData函数与CCCryptorUpdate返回的长度不匹配。它会假设加密过的信息与明码文本的长度相同。因为SRTPEncryptData总是使用较大的定长buffer来加密信息,并且加密信息是in-place的,我修改了函数,就可以在buffer末尾得到加密信息长度,这个buffer是在hooked CCCryptorUpdate中设定的。不幸的是,即使相同的加密数据被重现,它也不能被接收设备正确处理。

为此需要解释一下RTP的工作原理,一个RTP信息包具有如下形式。

1

它包括许多fields,会决定payload如何被解析。SSRC是一个随机的ID用来区分一个流。例如,在FaceTime中音频和视频流具有不同的SSRC。在用户可能发出大量流的时候,SSRC还可以用来区分流。例如多方视频通话。RTP信息包也具有payload类型(表中的PT),用来区分payload中不同类型的信息。对于特定数据类型,它的payload类型是不变的。在FaceTime中,视频流对于视频数据具有单一payload类型,但是音频流有两种payload类型,有可能一个是音频数据,另一个用来同步。RTP标记(表中M)用来表示什么时候信息被碎片化,并且需要重新整理。

由此显而易见,仅仅拷贝日志信息到当前的加密信息包不起作用,因为信息还需要正确的SSRC,payload类型和标记,否则就不能被正确解析。在WebRTC中这是没有必要的,因为我们对它有足够的控制,可以建立一个具有单一SSRC的连接和payload类型的连接,作为模糊化需要。但是FaceTime中我们不能这样做。静音视频通话,会导致可以发送音频信息但是却不能发送音频流的情况。所以这些值需要手动更改。

RTP的扩展性特点使得更正这些数值很困难。扩展是一个标题,可以选择性添加到RTP信息包中。它不应该依赖RTP payload被解析,并且经常用来传输网络或显示特色。例子包括定向扩展,和静音扩展,告知端点接收设备是否被静音。

扩展意味着即使有可能确定payload类型,标记和数据的SSRC,这些也不足以重现发送的信息包。更进一步,FaceTime在信息包加密之后创建扩展,所以不可能通过hook CCCryptorUpdate创建完整的RTP信息包,因为扩展可以在以后添加。

这样就有必要同时hooksendmsg和CCCryptorUpdate。这允许发出的RTP标题被修改。这里存在一些挑战。首先,音频和视频信息在FaceTime里是通过不同线程发送的,并且可以在加密和被sendmsg发送之间重新排序。所以我不能假设如果sendmsg加收了RTP信息,它就一定是最后一个被加密的。这里还存在一个问题,SSRC是动态的,使用相同SSRC重现RTP信息不能实现,下一次需要新的SSRC或音频视频流。

在MacOS Mojave中,FaceTime可以通过AVConference binary 或IDSFoundation binary调用sendmsg,取决于网络配置。所以为了在新系统中抓取重现未加密RTP流量,有必要hook AConference中的CCCryptorUpdate和IDSFoundation中的sendmsg。 否则,进程就会与旧系统相同。

我最终通过记录未加密payload来记录信息,接着记录RTP标题,使用一小部分加密payload来对标题与未加密payload配对。接着重现信息包,CCCryptorUpdate中的加密信息被替换成登录信息,一旦加密payload通过sendmsg,标题被替换成payload对应的日志信息。幸运的是,FaceTime中具有特定SSRC的两条流不会共享任何payload类型,所以通过等待具有正确payload类型的输入信息来确定每条流的新SSRC是有可能的。接着在之后的信息中,SSRC被替换。

但这还是不能正确重现FaceTime通话,并且通话经常解密失败。我最后决定音频视频使用不同密钥解密,并且基于它是视频还是音频,更新重放脚本对CCCryptorUpdate函数使用的CCCryptor排队。接着在sendmsg,全部的日志RTP信息,包括解密payload被拷贝到输出信息,SSRC固定,payload使用下一个CCCryptor加密。如果不能得到CCCryptor,输出信息就会被丢弃,直到下一个被创建。在这一点上,停止使用修改过的AVConference binary是可能的,因为所有的信息修改都发生在sendmsg。这个实现方案依然存在可靠性问题。

更进一步了解FaceTime如何加密,信息在CTS模式中加密,这需要一个计数器。对每一个信息,计数器被初始化为一个特别的值。在RTP流初始化过程中,两端交换两个16byte的随机token,分别对应音频和视频。接着,计数器的值通过信息中的SSRC和序列号,使用token计算出来。只有序列号在每一个信息包中改变。这样通过初始化计数器的值和序列号就可以计算信息包对应的计数器值,序列号可以通过hook CCCryptorCreateWithMode取得。当FaceTime建立计数器时,序列号与随机16进制的token进行异或运算,所以把位置与初始序列号和信息序列号进行异或运算,就可以计算信息的计数器值。密钥还可以通过hook CCCryptorCreateWithMode取得.这允许我发放队列密码,因为现在我已经得到了需要建立对应信息密码的所有信息。这样信息加密速度可以更快,更准确。

序列号依然存在问题,因为初始RTP流的序列号在通话开始时,是随机产生的,与接下来的通话不同。并且,序列号用来有序重建视频流,所以需要保证序列号的正确性。我改变了重放工具,确定每一个流的初始序列号,并且计算每一个日志流的初始序列号和日志信息的初始序列号的差,最后加上这个值。这两个变化最终使重放工具正确工作,尽管由于信息丢失,重放速度更慢。

结果

最终我模糊化了FaceTime通话,复制了崩溃。发现了三个bug,这三个问题已经在最近的更新中被修复。

CVE-2018-4366是一个只在Mac中出现的视频过程中的读取越界问题。

CVE-2018-4367是一个堆栈损坏漏洞,影响了iOS和Mac。

CVE-2018-4384是一个视频过程中,内堆核损坏的问题,影响了iOS。

在第三部分,我们会探索WhatsApp中的视频通话。

 

原文标题:Adventures in Video Conferencing Part 2: Fun with FaceTime
链接:https://googleprojectzero.blogspot.com/2018/12/adventures-in-video-conferencing-part-2.html

填写常用邮箱,接收社区更新

WebRTC 中文社区由

运营