曾几何时,人们对于苹果产品的信任程度就好像他们经常使用的冰箱、空调、洗衣机一样,但随着“苹果绝对安全”谣言的破灭,更多安全人员开始投身到苹果产品的漏洞挖掘研究之中。
今年年初,苹果手机软件FaceTime的一项重大漏洞被曝光,更是让人们深知世上并无绝对的安全可言。
据The Verge报道,该漏洞可以让用户通过 FaceTime 群聊功能(Group FaceTime)打电话给任何人,在对方接受或拒绝来电之前,就能听到他们手机里的声音,该漏洞的曝光把苹果推上了舆论的风口浪尖。
那么,这样的漏洞是如何被发现的呢?除了上述漏洞之外,FaceTime是否还存在着其他安全风险呢?
在此次ISC上,盘古团队联合创始人王铁磊、盘古实验室研究员黄涛就“苹果FaceTime零接触远程漏洞首次被还原”这一议题详细解密了FaceTime的逆向全程并分享了几个典型的漏洞案例。
内繁外简的FaceTime
但凡用过FaceTime的用户,一定对下面这张图不会陌生。它看起来要比微信、QQ的主界面更加清晰、简洁,这十分符合苹果的一贯风格。
然而,对FaceTime进行过逆向漏洞挖掘研究的黄涛却深知它的“内在”远没有我们看到的这么简单,其内部结构甚至可以使用“错综复杂”来形容。
通过逆向分析黄涛发现,FaceTime UI界面其实分成了通讯框和视频区两个部分。看似简单的UI区域实则是由多个进程在合作完成整个UI的运作,它们分别是callservicesd、avconferenced(macOS)和mediaserverd(iOS)三个部分组成。
其中,callservicesd进程主要负责渲染通讯框部分,以及拨打或者挂电话的功能。callservicesd进程主要管理了三个功能——第一个是FaceTime接听、拨打的音视频状态;第二个是反馈、响应一些FaceTime产生的UI上的数据;其次会为avconferenced(macOS)和mediaserverd(iOS)两个进程去提供信息传递的桥梁。
avconferenced和mediaserverd主要负责右边的视频区,它们负责处理通讯时的图像内容,其主要的功能就是将自身的音视频发给远端或者处理远端传递过来的音视频信息。
现在试想,当一个用户拨打FaceTime请求的时候,首先会点击左边的通讯框内容选择通话对象,这时UI会发送UINotification-Call通信,以此告知系统“我要打一个电话出去”。
这个时候callservicesd就会接受到系统发出的UINotification,它会通过一个函数开启通话模式的请求。然后,callservicesd就会调用【CSDFaceTimeProviderDelegateprovider:performStartCallAction】函数与avconferenced做交互,获取到lnviteData请求并以此建立通话渠道。
在通话渠道创建之后,其通讯数据会被返回给callservicesd。这时callservicesd会将其内部的通讯数据封装起来并传送给identityservicesd,这就是callservicesd的第三个动作,也是和avconferenced(macOS)以及mediaserverd(iOS)建立通信桥梁的过程(identityservicesd是一个系统进程,主要用来管理用户和iCould和MAC的连接)。
随后,identityservicesd会将通讯信息机型封装,并且同时把它传输给一个叫apsd(用于和苹果发射推送数据)的进程,后者和苹果保持了可行的安全连接,它们在持有交互证书的情况下运作以确保进程的安全性。
在apsd收到来自identityservicesd的封装信息之后,前者会再度将一层一层的数据打包成一个对象,同时将其推送给AppleServer。当然,苹果服务器不会简单地把消息推送过去,而是会对消息做一个简单的过滤。
“老实说,我们发现这种过滤机制并非真的能确保FaceTime整个运作进程的安全可靠,在尚未被过滤的信息中,我们仍可找到可能被攻击者远端控制的数据。”
原来,研究人员会人为发送一组数据进入到上述的进程流程当中,在经过苹果服务器过滤后,再通过对比这些数据和远端发送的数据,以此得知哪些数据是可控的。在这个流程完成后,苹果服务器就会把apsiMessage传递给远端的apsd,也就是被呼叫那个用户的设备上了。
在接听方设备收到数据后,便能够看到来自拨打方的音视频信息,为了实现双向交流,与此同时apsd收到的数据会采用与上述完全一致的封装结构来反序列化apsiMessage。为了方便观察apsiMessage中具体包含了哪些数据信息,通常会在其反序列的函数里面设置一个断点。
通过设置断点,可以看到其中几个比较重要的参数,它们标示了apsd数据包究竟会被传输到那里进行处理。apsd会通过两个参数把消息传递给mediaserverd,之后进一步进行反序列化。
“最终,我们在mediaserverd函数里面看到数据变了,其中有一个字段叫做C的值是232,其代表远端发来的是一个Notification。通过不同的C值,比如说232、233、234、235来调用不同的函数去处理Notification,所以即便不改变漏洞内容,只改变与之对应的处理函数会不一样。”
紧接着,mediaserverd会继续把数据解开然后发给callservicesd,其同样也会把数据进行反序列化,将其推送给远端的mediaserverd。如此以来,远程呼叫端的设备就获取到了呼叫端的媒体配置信息和加密信息。在这些数据处理完之后,呼叫端远端就会跳出一个UI上面显示——有一个人正在通过Facetime与你通话的音视频信息。
这样一来,一次FaceTime的通话连接便被建立起来了。
为了实现实时的通话需求,远端用户接通的时候同样会产生一个与上述FaceTime UI数据传递路径完全一致的反交换过程,这一过程同样会被远端的callservicesd去处理,接下来流程和之前几乎是一样。
“值得注意的是,在反向通讯的过程中之前的C值不是232而是233了,这时远端已经接受了对方设备视频的请求,这时会调用相应函数值来处理返回过来的数据。这种情况下,两端都会打开各自的avconferenced(macOS)和mediaserverd(iOS)端口,并且在穿墙协议的帮助下实现双方找到IP建立端对端链接。”
通过端对端的链接,identityservicesd负责处理和接收网络包,另一个函数会调用apsiMessage从udp端口拿到网络包传递给IDSGlobaLink,后者会判断该网络报是否符合seiten协议,如果是就会将其传送给对应的执行函数,进一步解析网络包,再次将其发送给更细分的执行函数。与此同时,identityservicesd还会响应一些其他的协议。
这张图是真正的在建立FaceTime之后其数据的音视频究竟是怎样的,到这里逆向的分析也就告一段落了。
可见,看似简洁的FaceTime其进行数据处理、执行及反馈的渠道多到令人咋舌的地步,这也注定FaceTime更容易存在攻击面。而对于如此庞杂的数据流通线路,其中需要用户进行操控的地方少之又少,这就意味着一旦攻击开始,其整个过程具有极强隐蔽性。
FaceTime的4个漏洞案例分享
从上面FaceTime逆向的过程可以看出,实际上从呼出到最终的双方看到音视频信息是一个非常长的处理流程。也就是说,一旦逆向分析有所疏漏,漏洞挖掘的过程只是在某一层进行,这就难免会使得研究数据完整性欠缺,甚至会损坏其格式。
那么,在搞清楚FaceTime的工作原理之后,关于其漏洞挖掘的过程才算真正开始。在这里,王铁磊分享了4个FaceTime的漏洞分析案例。
案例1
第一个案例介绍的是一个疑似的信息泄露,这个漏洞发生在从apsd到avconferenced之间,也就是在最初的信息数据包处理上。
通过查看数据流,发现攻击者的数据包中包含了一些U字段的报文数据。这个U字段数据实际上是一个UUID,其本身只是一些嵌入符号,并没有什么特别的含义。
“但是,我们发现通过在其前后插入额外的字段,可以让这些原本毫无意义的字段来强制执行一些命令。”
首先在Notification插入一些额外的字段,当远端收到之后U字段和人工植入的数据都在其中,于是强制返回包中会包含UUID——到目前为止整个流程上还没有什么问题,只是一方面是数据的发送另一方面是信息的返回。但是,因为这里有同样的字段,这也正是漏洞的所在。
首先,来看看这段代码的具体意思——收到一个Notification以后,远端会把整个字典数据做一个分析,它先试图去看有没有所谓的U字段,有的话就把U字段所对应的数据包给取出来,并假定其是一个AI Sdatae,然后将其作为参数传入一个以GWUUID开头的函数中,其本意是为了把AI Sdatae转换成一个CFStun,但是这个假定数据的长度是16字节,而因为其调用的一个函数的LOKCBfer是没有初始化的,这就意味这如果发出去的UUID的长度不到16字节,那么就有可能得到一个未初始化的栈溢出数据。
为了证明上述猜测,研究人员首先发送了一个远端的UUID。研究人员的最初想法,是想看到的输出一些值得关注的地址,但却发现改造后的Notification发过去后在远端收到的UUID不见了,这就意味着苹果服务器那端把短的UUID剥离掉了,从代码角度来看这是一个100%未初始化内存泄露的问题,但是真实POC构造过程中苹果服务器扮演了一个未知的角色,因此不确定是何时开始过滤的。
案例2
当FaceTime的连接建立之后,一个非常重要的一个角色就是mediaserverd,因为mediaserverd进程负责第一层网络报文的处理,FaceTime的所有数据或者其它控制的发送,这些报文在最初的未处理状态都被mediaserverd服务运行。
mediaserverd会根据包的内容来去选择是哪些其它的进程或者是其它的函数来处理,其中typeMessage攻击者就可以通过udp把攻击文件发送到远端的mediaserverd,远端的mediaserverd会识别typeMessage。
根据它的协议,typeMessage在偏移为“4”的地方会有执行。因此,从代码执行上可以看到mediaserverd就是做了一个比较,如果包里存在为“4”的值那就是typeMessage,然后将其一个函数除去,之后发送的报文会被反序列化成为一个叫IDSStunMessage的对象,而IDSStunMessage在这个反序列化构成中没有发现什么特别的漏洞,然后当typeMessage被反序列化成这个对象以后,会进一步处理函数。
实际上,期间过程中一共包含了20个属性,他们全部是从udp报文中拷贝过去的,也就是说它们在现在这一阶段是完全可以被攻击者控制的。如果typeMessage中的type数值为“0x17”,这时会阻断一个特殊函数,其功能是将类型为19的属性取出来,这一过程都属于安全流程,但是在随后的反序列化过程中就会暴露出很多问题。
反序列化的时候数据格式非常简单,它就是两字节的type两字节的长度。但实际上,这里面如果看到一个属性的type是0xF或者很多种类型的属性都会得到一个叫readIDSGLAttrBinaryData的函数,其过程会调用到memcpy。而这个memcpy的case参数完全可控,其执行难度不大,稍加长度布局的改动即可让其显示141414...的错误显示,这是一个典型的堆出漏洞。
案例3
callservicesd会把数据再转给avconferenced(macOS)或者mediaserverd(iOS),这其实进入到了音视频流的处理流程,而这里的这个漏洞是一个栈溢出。
该漏洞的原理是——当去做视频剪辑的时候,一帧数据可能会很大,这个时候一个处理单元就可能被分割成多个RTP包被进行传送,然后接受方接收了这些包之后再重组成完整数据包做处理。由于RTP包是通过UDP包来的,所以有前有后,为了维持如此布局就要将这些RTP都进行串联,在传送到之后才重组起来。
在每收到一个RTP包的时候会严格检查包的格式,但是有时候会出现丢包的情况,在苹果的处理方案中,其引入了一种容错机制——它可以通过已经收到的包去重构出一些已丢包的基本信息,这就是为什么在发生网络抖动的时候,FaceTime聊天的画面会糊一点,但还是能够保持能看到基本的内容的原因。
但是问题在于,在做包修复的时候,RTP里面会含一个叫做FEC的东西会重构出RTP包,重构出来的长度则是在FEC Header里面制定的。但是FEC Header是没有经过任何检查的,所以最后导致的后果就是容错机制被调度起来的时候,执行函数中的size是完全可控的,这是最经典的栈溢出。
那么,在2019年iOS上面,栈溢出还能不能利用?
实际上,像这种传统的经典栈溢出的最有效防范就是启用cookie。它的基本原理就是当函数值被调用的时候,在返回地址和局部变量之间插入一个cookie,在函数返回的时候从栈上把刚才的cookie拿出来作比较,如果二者不一致的话那么意味着整个cookie都被破坏掉了,很可能返回地址也已经被破坏掉了,所以这时该程序就可以主动地退出,避免整个返回地址被控制住。
这种连续溢出会破坏stack_cookie,这些指令在函数执行进来的时候,函数参数(地址)已经在栈上了,有些指令可以把一些寄存器暂时压在栈上,这几条指令在分配局部变量,其实也是在放置栈的cookie。
其中的问题是,栈cookie被放在了局部变量后面,这就意味着当局部变量发生栈溢出的时候它会直接覆盖到crashed而无需破坏stack_cookie。这个问题十分严重,stack_cookie根本没有放在一个合适的位置,自然也起不了正常的作用。
通过crash log的截图可以看出,在iOS上可以看到serverd34号线程已经发生了崩溃,整个PC已经完全被控制,变成了808080,与此同时其他的寄存器也都被控制了。在将该漏洞报告苹果后,后者在12.4版本中做了修复,但事情并没有就此告一段落:
在意识到stack_cookie出问题的时候,研究人员又去检查了其他的模块及函数,他们发现很多函数都有这个问题,因此他们意识到这里还有一个编译器的漏洞。因此,结论是这是一个在FaceTime栈溢出背后还有一个编译器漏洞问题。
实际上,苹果是LLVM编译器的一个最大推手,如果苹果编译器有问题那么多半意味着LLVM编译器有问题,最终这个问题苹果的安全团队在进行更详细的分析后发现确实如此,其影响的是整个品牌的编译器产品,这也是为什么之后该厂商为这样一个威胁程度为0的漏洞发布了公开声明的原因。
案例4
identityservicesd会根据收到的Notification里的一个函数决定是将消息发给callservicesd还是其它的服务。这是一个叫做Phone countinuity的漏洞,它原本是可以实现在MAC笔记本和iPhone在登陆统一Apple ID的情况启用通话功能的一项附加功能。雷锋网雷锋网雷锋网
实际上,这就意味着手机和电脑之间存在着一个数据通道。他们也做了一些逆向分析,最终发现这个还是在identityservicesd做的处理。
其实会存在这样的执行路径,IDSLinkManager最后会走到一个叫做IDSSockAddrWrapper initWithSockAddr:的函数。这个函数实际是根据UTP包中的内容去反序列化一些地址,这里也是最终出问题的地方。如果大家对去年的imptcp模块安全漏洞熟悉的话,iOS内核尝试着去把一个用户的数据反序列化成一个soft地址,问题是对于一个有效地址其长度是0X80,但一个soft结构体它的长度域可表达的范围是0XFF,这就与意味着当把一个地址进行拷贝时,要根据其第一字节的长度来进行调节,因此一定要做严格的长度检查。但是,这个函数里只对地址域进行了检查,没有检查长度,这意味着可以把第一个字节填称0XFF最多可以溢出0XFF-[字节长度]这么长,那这个函数和前面说到了漏洞利用十分接近。
总结
在整个分析中,FaceTime的大量攻击漏洞被逐一挖掘出来。
在对FaceTime进行分析时王铁磊称,首先,FaceTime的代码质量非常差,这个出乎他们的预期,整个FaceTime需要一个极大的提高,否则随着安全研究人员的不断挖掘会有更多漏洞被曝出来;其次,整个Message的接口有非常大的畸变,其本质都是通过identityservicesd做的一个转化,这么多功能糅杂在一起也就意味这复杂性很高,那么攻击面是非常大的;最后对于进行安全研究的人员来说,很多老的漏洞并没有过时,比如上面讲的栈溢出就是十分经典的例子。