您的位置首页>硬件>

使用WebRTC开发Android Messenger的方法解析

导读 大家好,我是极客范的本期栏目编辑小友,现在为大家讲解使用WebRTC开发Android Messenger的方法解析问题。WebRTC是一个开源的视频会议解决

大家好,我是极客范的本期栏目编辑小友,现在为大家讲解使用WebRTC开发Android Messenger的方法解析问题。

WebRTC是一个开源的视频会议解决方案,可以在各种软件中使用。包括浏览器、消息客户端和流媒体服务。虽然Zero Project过去在WebRTC中报告过很多bug,但目前还不清楚这些bug是否可以被利用,尤其是浏览器之外的bug。我调查了最近流行的安卓消息应用程序中的两个bug,我不知道它们是否可以被利用。我第一次尝试使用的bug有两个:CVE-2020-6389和CVE-2020-6387。这两个bug都在WebRTC的远程传输协议(RTP)过程中。RTP是WebRTC用来点对点传输音视频内容的协议。RTP支持扩展,扩展是一个额外的数据段,可以包含在每个数据包中,告诉目标对等体如何显示或处理数据。例如,有一个扩展名包含有关发送设备屏幕方向的信息,而另一个扩展名包含音量级别。这两个错误都发生在2019年实现的WebRTC的扩展中。CVE-2020-6389出现在帧标记扩展中,它包含如何将视频内容拆分成帧的信息。BUG在于处理层信息的方式:WebRTC只支持五层,但层号是扩展中的三位字段,也就是说可以高达七层。这导致在下面的代码中编写越界代码。从扩展中的层数设置时态_idx。

if(layer _ info _ it-second[temporal _ idx]!=-1 AheadOf(layer _ info _ it-second[temporal _ idx],frame-id.picture_id)) { //不是较新的帧。后续图层信息无需更新。打破;} .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是因为视频计时类型的扩展在被清除之前没有被验证为具有预期的长度。导致此错误的代码如下:

案例RTPExtensionType: { //废掉最后3个条目:打包延迟和2个网络时间戳。//每个都是2字节。uint 8 _ t * p=Writeat(extension . offset)videoSendTiming : kpacerexiitdeltaoffset;memset(p,0,6);打破;}

video send timing : kpa cereixietaoffset的值为7,因此此代码将从数据包扩展的开头开始,在offset 7和offset 13之间写入六个零。但是,它不会检查扩展数据的长度是否超过13个字节,甚至不会检查该字节数是否留在数据包中。由于这个BUG,当可变大小的堆缓冲区偏移多达7个字节时,攻击者可以向堆中写入多达6个零。这个BUG在某些方面比CVE-2020-6389好,但在其他方面更差。更好的是,可能溢出的堆缓冲区大小是可变的,这提供了更多关于这个BUG如何覆盖堆的选项。Offset也为写零的位置提供了一定的灵活性,写的时候不需要对齐。而CVE-2020-6389要求64位对齐。这个错误更严重,因为写入的值必须为零,并且可写区域的大小很小(6字节对24字节)。移动指令指针我首先检查是否有可能使用这些bug中的一个来移动指令指针。现代安卓使用的是平面分发器jemalloc,它不使用内联堆头,所以销毁堆元数据不是一个选项。相反,我用符号为安卓编译了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 attrs_; ... 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_;

我希望有可能使指向该结构的裁剪指针指向堆上的其他对象,该对象在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都无法轻易做到。

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(layer_info_it->second[temporal_idx], frame->id.picture_id)) { // 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: { // 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 attrs_; ... 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_;

我希望有可能使指向该结构的裁剪指针指向堆上的其他对象,该对象在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都无法轻易做到。

.dfma { position: relative; width: 1000px; margin: 0 auto; } .dfma a::after { position: absolute; left: 0; bottom: 0; width: 30px; line-height: 1.4; text-align: center; background-color: rgba(0, 0, 0, .5); color: #fff; font-size: 12px; content:"广告"; } .dfma img { display: block; }
郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。