使用WebRTC开发Android Messenger:第1部分

Natalie Silvanovich 2020年9月3日

这是一个由三部分组成的系列文章,内容涉及:利用WebRTC中的BUG和利用Messenger应用程序。本系列文章重点阐述了当应用程序不能应用于WebRTC补丁程序以及通信和安全问题通知中断时可能出问题的方面。


文 / Natalie Silvanovich


原文链接:

https://googleprojectzero.blogspot.com/2020/08/exploiting-android-messengers-part-1.html


Part 1: First Attempts



WebRTC是一种开放源代码视频会议解决方案,可用于各种软件。包括浏览器,消息客户端和流媒体服务。虽然Zero Project过去曾报道过WebRTC中的多个BUG,但尚不清楚这些BUG是否可利用,尤其是在浏览器之外的BUG。我调查了流行的Android消息传递应用程序中最近的两个不知能否利用的bug。

The Bugs



我首先尝试利用两个BUG:CVE-2020-6389和CVE-2020-6387。

这两个BUG都在WebRTC的远程传输协议(RTP)的处理中。RTP是WebRTC用于从点对点传输音频和视频内容的协议。RTP支持扩展,扩展是可以包含在每个数据包中的额外数据段,以便告诉目标对等方如何显示或处理数据。例如,存在一个扩展,其中包含有关发送设备的屏幕方向的信息,而其中另一个包含音量级别。这两个BUG都发生在2019年实现的WebRTC的扩展中。

CVE-2020-6389发生在帧标记扩展中,该扩展包含有关如何将视频内容拆分为帧的信息的内容。BUG在于处理层信息的方式:WebRTC仅支持五层,但是层号在扩展中是一个三位字段,这意味着它可以高达七层。这导致在以下代码中写越界。从扩展名中的层号设置temporal_idx。


if (layer_info_it->second[temporal_idx] != -1 &&
AheadOf
     // Not a newer frame. No subsequent layer info needs update.
     break;
  }
 ...
  layer_info_it->second[temporal_idx] = frame->id.picture_id;

代码的最后一行是发生越界时写入的地方,因为该数组仅包含五个元素。这个BUG也有一些局限性,虽然从上面的代码中看得不太明显。首先,在写的操作之前先进行检查,检查内存的当前值(转换为16位无符号整数)是否大于当前序列号。仅在为真时才执行写的操作。实际上,这并不是什么限制,当我测试它时,崩溃通常发生在两到三遍之后。一个更为严重的限制是layer_info_it-> second字段具有64位整数类型,而frame-> id.picture_id是16位整数。这意味着尽管此BUG使攻击者可以在固定大小的堆缓冲区之外最多写入三个64位整数,但是可以写入的值非常有限,并且太小而无法表示指针。

CVE-2020-6387是前向纠错(FEC)如何处理视频定时扩展的错误。  FEC复制传入RTP数据包,然后在尝试更正错误时清除某些扩展名。发生此BUG的原因是:在清除视频定时类型的扩展名之前,未验证它们是否具有预期的长度。导致此错误的代码如下:


case RTPExtensionType::kRtpExtensionVideoTiming: {
      // Nullify 3 last entries: packetization delay and 2 network timestamps.
      // Each of them is 2 bytes.
      uint8_t* p = WriteAt(extension.offset) + VideoSendTiming::kPacerExitDeltaOffset;
      memset(
          p,
          0, 6);
      break;
    }

VideoSendTiming :: kPacerExitDeltaOffset的值为7,因此此代码从数据包扩展名的起始位置开始,将在偏移量7到偏移量13之间写入六个零。但是,却不检查扩展数据的长度是否超过13个字节,甚至不检查数据包是否剩下此字节数。该BUG的结果是,攻击者可以在一个可变大小的堆缓冲区最多偏移七个字节的情况下,向堆中写入最多六个零。该BUG在某些方面优于CVE-2020-6389,但在其他方面则更糟。更好的是,可以溢出的堆缓冲区是可变大小的,这提供了更多关于此BUG可以覆盖堆的选项。偏移量还为写入零的位置提供了一定的灵活性,并且写入时也不必对齐。而CVE-2020-6389需要64位对齐。该错误更严重,因为写入的值必须为零,并且可以写入的区域的大小较小(六个字节对24个字节)。

Moving the Instruction Pointer



我首先查看是否有可能使用这些BUG之一来移动指令指针。现代Android使用jemalloc,这是一个平板分配器,它不使用内联堆头,因此破坏堆元数据不是一种选择。相反,我使用符号编译了适用于Android的WebRTC,并将其加载到IDA中。然后,我浏览了可用的对象类型,以查看是否存在明显可用于移动指令指针或改善错误功能的东西。结果,我什么都没找到。

我以为也许我可以使用CVE-2020-6389覆盖长度并导致更大的溢出,但这存在一些问题。首先,该BUG会写入一个64位整数,而很多长度字段都是32位整数,这意味着该写入操作还会覆盖其他内容,并且如果长度是64位对齐的,则只能写入一个非零值。BUG在处理中的位置也是有问题的,因为它会在即将处理的传入数据包的末尾进行覆盖,这意味着在此之后许多对象将不再被访问,因此任何覆盖的内存都将不再使用。  CVE-2020-6389还覆盖了固定大小为80的堆缓冲区,这限制了可能受此错误影响的对象类型。我也不认为CVE-2020-6387可以达到这个目的,因为它只能写零,而这只能使长度变短。
我不确定现在要进行什么操作,所以我在Android上触发了数十次CVE-2020-6389,以查看是否存在超过16位宽的地址崩溃,希望它们能为我提供一些方法在除了覆盖无效的16位值的指针之外,此错误可能会影响代码的行为。令我惊讶的是,它崩溃了,而且指令指针设置为一个值,该值显然已从堆中读取了大约20次。

分析崩溃后,结果发现在溢出区域之后分配了一个StunMessage对象。  StunMessage类的成员如下。



protected:
 std::vector<std::unique_ptr
...
private:
 ...
 uint16_t type_;
 uint16_t length_;
 std::string transaction_id_;
 uint32_t reduced_transaction_id_;
 uint32_t stun_magic_cookie_;

因此,在vtable之后,第一个成员是向量。向量如何在内存中布置?原来它的前两个成员如下。



 pointer __begin_;
 pointer __end_;

这些指针指向内存中向量内容的开头和结尾。在崩溃期间,__end_成员被一个小的16位整数覆盖。向量迭代的工作方式是从__begin_指针开始,然后递增直到达到__end_指针,因此,此更改意味着通常下次在析构函数中对向量进行迭代时,它将超出范围。由于此向量包含StunAttribute类型的虚拟对象,因此它将对每个元素执行虚拟调用,以调用它的析构函数。对越界内存的虚拟调用正是为什么移动指令指针的原因。

除以下的这个问题外,这似乎是控制指令指针的一种合理方法:在典型配置中,WebRTC连接一端的攻击者无法将STUN发送给另一端的用户,而是他们各自与自己的STUN服务器进行通信。我问webrtchacks的Philipp Hancke是否知道某种方法。他建议使用此方法,该方法涉及将攻击者控制的TCP服务器指定为两个对等方(称为ICE候选方)之间潜在的可路由路径。然后,攻击者和目标设备都将通过此服务器进行通信,包括STUN消息。

这使我能够发送具有异常大量属性的STUN消息。这是必要的,因为为了控制指令指针,我将需要能够控制STUN属性向量之后在内存中显示的内容。  jemalloc分配相似大小的分配,这由连续内存运行中的预定义大小类确定。大小类使用的次数越少,相同大小类的两个对象被一个接一个地分配的可能性就越大。

通常,STUN消息具有少量属性,这些属性转换为32或64字节的向量缓冲区大小,它们都是非常常用的大小类。相反,我发送了具有128个属性的STUN消息,这些消息转换为1024字节的向量缓冲区大小,而这恰好是WebRTC中不常用的大小类。通过发送许多具有此数量属性的STUN消息,同时发送大小为1024的RTP数据包,其中包含所需的指针值,并散布着包含BUG的数据包,我能够对该指针值进行约1的虚拟调用五次。这足以在BUG利用中使用,所以我决定继续攻克ASLR。


Breaking ASLR



在此攻击中,有两种可能的方法可以破解ASLR。 一种是使用上述BUG之一读取内存,然后以某种方式将其发送回攻击者设备或TCP服务器,另一种是使用某种故障预兆来确定内存布局。
我首先查看是否有可能使用这些BUG之一从目标设备远程中读取内存。马克·布兰德(Mark Brand)建议,可以使用CVE-2020-6387来实现这一点,方法是将指向传出数据的指针的低位字节设置为零,从而导致发送越界数据而不是实际数据。这似乎是一种很有希望的方法,因此我使用IDA寻找潜在的对象。

结果发现有不少,他们都有问题。我花了一些时间在SendPacketMessageData和DataReceivedMessageData上。这些对象用于在队列中存储指向传出RTP数据的指针。它们包含一个CopyOnWriteBuffer对象,并且它的第一个成员是指向rtc :: Buffer对象的引用计数的指针。使用CVE-2020-6387可以将此指针的最低字节设置为零。不幸的是,rtc :: Buffer的结构使以这种方式显示内存具有挑战性。



RefCountedObject vtable;
size_t size_;
size_t capacity_;
std::unique_ptr



我希望有可能使指向该结构的裁剪指针指向堆上的其他对象,该对象在data_指针的位置具有一个指针,而该数据将被发送。但是,事实证明,在发送数据的过程中,上面对象的所有四个成员都可以访问,并且需要合理有效。我遍历了与rtc :: Buffer类相同大小的所有可用对象,但是找不到具有这些确切属性的对象。

然后,我考虑使用一个已经释放的rtc :: Buffer对象,而不是使用其他对象,而使用特定的后备缓冲区大小,可以使用堆操作将其替换为包含指针的对象。这也没有解决。这在很大程度上是可靠性的问题。首先,一个rtc :: Buffer对象是36个字节,这在jemalloc中转换为48个大小类,这意味着分配了48个字节。想象一下这种类型的一些连续分配,地址如下:



0x[...]0000      buffer 0
0x[...]0030      buffer 1
0x[...]0060      buffer 2
0x[...]0090      buffer 3
0x[...]00c0      buffer 4
0x[...]00f0       buffer 5
0x[...]0120      buffer 6
...



如果该BUG将缓冲区0到5的第一个字节设置为零,则它们将落在有效缓冲区上,但是如果缓冲区6设置为零,则它将不起作用,因为256不会平均分配为48。所以结果是,每次BUG碰到SendPacketMessageData对象时,只有三分之一的机会最终会指向有效的rtc :: Buffer。首先,击中对象也是不可靠的,因为WebRTC正在进行许多其他类似大小的分配。通过使用TCP服务器使连接非常慢,可以增加堆上这些对象的数量和发送它们之前的时间量,但即使这样,我也只能在不到10%的时间内命中结构。必须操纵堆,以便首先在一行中有许多释放的rtc :: Buffer对象,并且支持已被包含指针的东西替换。但这却增加了更多的不可靠性。我最终放弃了这种方法,因为我认为我可能既无法做到足够可靠,也无法通过合理的努力将其用于BUG利用程序中。同样地,被攻击的应用程序的崩溃行为也很重要。这可能可以适用于在崩溃的情况下立即重生的应用程序,但是对于停止重生的应用程序实用性却要差很多,除非存在一定的延迟,而这在Android上很常见。

我还大量研究了WebRTC如何生成传出数据包,尤其是对等端始终发送的远程传输控制协议(RTCP),即使它只是接收音频或视频。但是,大多数传出数据包都是在堆栈上生成的,因此无法使用堆损坏BUG对其进行更改。

我还考虑过使用崩溃Oracle来破解ASLR,但我认为使用这些特定的错误不太可能成功。首先,与它们进行堆分配是不可靠的,因此很难判断是由于特定情况还是仅由于BUG失败而导致崩溃。考虑到这些BUG的功能有限,我还不确定是否有可能创建可检测的条件。

我还考虑过使用CVE-2020-6387更改vtable或函数指针以读取内存,导致崩溃Oracle可以检测到的行为或执行不需要破坏ASLR的基于偏移的利用。我决定不走这条路,因为最终结果将取决于哪些函数和vtables在以零结尾的位置上加载,而这在各个版本之间差异很大。使用此方法编写的BUG利用程序需要进行大量修改才能在WebRTC的稍微不同的版本上运行,并且无法保证它完全可以运行。

我决定在这一点上,我需要寻找可能破坏ASLR的新BUG,因为我最近发现的两个BUG都无法轻易做到。
还可输入800
全部评论
作者介绍

LiveVideoStack

音视频技术社区

文章

粉丝

视频

阅读排行
  • 2周
  • 4周
  • 16周