在完成了对 FaceTime 的一系列漏洞挖掘与研究后,我们决定对微信的音视频通信做一些分析。经分析后发现,当微信语音通话连接建立成功之后,微信客户端将解析远端发来的网络报文并还原成多媒体流。在还原解析的过程中,如果处理远端数据的代码存在问题时就会形成一个远程的攻击面。
在针对这个攻击面进行深入挖掘后我们发现了若干可以造成远程内存破坏的漏洞。本篇文章我们将选择一个比较有趣且复杂的漏洞进行深入的分析。该漏洞可以造成远程写溢出从而导致崩溃,其root cause隐藏的非常深,触发流程也比较复杂。研究与分析该漏洞无论是对安全研究还是软件开发的角度都有一定的价值。我们将在文章中详细的分析漏洞成因和触发流程。微信已经在最新版7.0.12中修复了该漏洞。
首先我们先介绍两个比较简单的漏洞,一个属于本地代码执行,一个属于远程溢出。
Mac版本的微信客户端处理粘贴操作时,没有有效检查粘贴板对象中内容,导致不安全的对象反序列化。当本地其他恶意应用设置粘贴板时,用户在微信客户端粘贴操作时,会导致任意对象的创建。
如下面截图所示,Mac 版本的微信在反序列化粘贴板对象的过程中,并没有使用secure coding 以及白名单等设置,导致任何可以响应 [initwithcoder:] 函数的 objective-c 对象都能被创建并使用,会引起很大的攻击面。
Mac版本微信对剪切板的处理
具体攻击结果可以参考[Google Project Zero在iMessage中发现的大量不安全反序列化攻击] (https://www.blackhat.com/us-19/briefings/schedule/#look-no-hands—-the-remote-interaction-less-attack-surface-of-the-iPhone-15203).
Mac版本微信已经对该漏洞进行了完全正确的修复,调用了 setRequiresSecureCoding: 函数,并作出了安全设置。
修复后的剪切板处理
微信视频通话接通后,通话两端建立网络直连传递RTP报文。微信客户端传输RTP包过程中,采用了一套加密机制。但是微信客户端在RTP解密之前,没有很好验证RTP包长度。当攻击者发送很短的RTP包的时候,会引起接受端处理RTP包过程中长度计算的整数下溢出,进而导致内存越界访问。
RTP包长度验证减法下溢出
有趣的是,GP0 研究员在微信 CAudioJBM::InputAudioFrameToJBM 函数中发现了类似的错误 (https://bugs.chromium.org/p/project-zero/issues/detail?id=1948)。这说明微信在在包长度验证时存在一定共性缺陷。
这是一个非常明显的下溢出,但是通过对这个问题的分析,我们认为远程的攻击面中可能存在风险更高的漏洞。
跳过前期复杂的协商互联流程,我们在已经通过微信语音通话的状态下,微信客户端将收到远端发送来的音频数据。收到的原始数据会被层层分解处理,并根据不同的类型分发到不同的处理函数上。
在收到远端的网络数据后,RTP 数据包将被 RecvRtpPacketCng(__int64 XVEChannel, unsigned int *pData, __int16 len, void *a4) 函数处理,这里的参数 pData内容是语音通话的远端完全可控的。该函数会根据网络包中指定的过不同的代码解析
当pkType类型为7或8时,该网络包的类型为 RTPwithRsMd
当网络包头部的 subpkt 解析完成后会调用 ParaseRemoteLostRateParam 函数:
ParaseRemoteLostRateParam 函数中,根据远端的 pData 中数据设置了XVEChannel+72 处对象的内部数据。通过参数 a2,在 pData 中读取两个字节,并最终设置到 m_RemoteLrParam 和 nFrmCnt 两个成员变量中。
在接收远端的语音数据的同时,也需要将自己的语音数据通过`XVEChannel`对象发送给远端。
在 readRemoteLrParam 函数中,会将刚刚设置的 m_RemoteLrParam 和 nFrmCnt 读取到栈上变量v92中。
在读取`RemoteLostRateParam`到局部变量v92后,需要设置到相应的本地成员变量中
当数据准备好后将调用函数 CAudioRS::RsMdEncProcessCng,写溢出就发生在这个函数中。
当 CAudioRS::RsMdEncProcessCng 刚开始执行时会通过 XVEChannel_72+9 作为 index 写一个 byte.
并在 RsMdEncQueueSourcePktCng 函数中 XVEChannel_72 + 9 将做一次自增。
当 CAudioRS::RsMdEncProcessCng 退出前会根据当前的状态更新成员变量。
[1] 通过`update_data`根据`LocalExpectRSPara`的值修改成员变量
[2] 如果XVEChannel_72+9处的值与XVEChannel_72+4处的值相同,则会触发[3]处的代码将XVEChannel_72+9处写0.
因为 XVEChannel_72 + 9 可以根据 pData 中的数据设置成攻击者可控的数据,当 XVEChannel_72 + 9 被设置为大于 XVEChannel_72 + 4 时,就必须一直自增且产生整数溢出后重新与 XVEChannel_72 + 4 相等时, 才能将 XVEChannel_72 + 9清零。
所以 XVEChannel_72 + 9 的取值范围是0-255。又因为` *(_BYTE *)(XVEChannel_72 + *(char *)(XVEChannel_72 + 9) + 1668) = a7;` 使用的是有符号数作为`index`。最终覆盖范围是 `XVEChannel_72+1668`处的`-128`到`127`处超过原本数据结构包含的内存。
要特别感谢 TSRC 的认真负责。他们在我们上报漏洞后对漏洞响应及时,收到报告的次日就确认了漏洞并给出危险评级。并且在后续的漏洞修复与修复版本更新的工作中和我们保持联系。
2019/11/28 发现漏洞
2019/12/02 完成漏洞分析并上报TSRC
2019/12/03 TSRC确认漏洞并修复
2020/03/23 文章发布
Credit:漏洞由盘古实验室黄涛、王铁磊发现和分析。
文由盘古实验室