详解iOS零点击无线电近程漏洞 (二)

匿名者  1325天前

在这篇文章中,我们将为读者详细介绍近期在iOS中发现的一个零点击无线电近程漏洞,由于篇幅过大,我们将分多篇发表。更多精彩内容,敬请期待!

接上文

WiFi基本原理

WiFi标准已有20多年的历史,目前该协议已经涵盖了电磁频谱的不同频率范围,从802.11af协议的54MHz到802.11ad协议的60GHz。这种网络听上去非常深奥,消费设备使用的频率接近2.4 Ghz或5 Ghz。

这些频率范围通常进一步分为信道:例如,在802.11g协议中,信道6表示介于2.426 GHz至2.448GHz之间的22 Mhz频谱范围。

对于较新的5 GHz标准(诸如802.11ac),则允许使用高达160 MHz的带宽。因此,5 Ghz的信道编号可同时表示中心频率和信道宽度。例如,信道44的带宽为20 MHz,起始频率为5.210 Ghz,结束频率为5.230 Ghz;而信道46的带宽为40Mhz,其起始频率与5.210 GHz(与信道44相同),但是结束频率为5.250 GHz。

AWDL通常使用信道6和信道44来发送和接收帧。如果您的家庭WiFi网络还想使用其他信道的话,那该如何处理呢?

信道跳变与时分复用

为了让用户感觉同时连接到两个不同频率的网络,具有AWDL功能的设备会将时间分成16ms的小段,并让WiFi控制芯片在基础设施网络的信道和AWDL使用的信道之间快速切换。

1.png      

一个典型的AWDL信道跳变序列,即在AWDL的social信道和AP信道之间来回切换,并且呆在前者上面的时间较短,呆在后者上面的时间较长

实际的信道顺序是动态的。对等体广播它们的信道序列,并调整它们自己的序列以匹配它们希望与之通信的对等体。AWDL对等体在AWDL信道上监听的时间段被称为Availability Windows。

这样,设备看上去不仅已连接到接入点,同时还参与了AWDL网格。当然,接入点和AWDL网状网络中的帧可能会丢失,但无论如何,协议都将无线传输视为不可靠的传输,因此,这只会对吞吐量产生实际的影响。AWDL协议的很大一部分工作,就是同步对等体之间的信道切换,以提高吞吐量。

实际上,SEEMOO实验室的论文对AWDL信道跳变机制进行了更为详细的介绍。

AWDL帧的结构

以下是第一批通过WiFi帧进行无线传输的软件控制字段:

struct ieee80211_hdr {

  uint16_tfr ame_control;

  uint16_tduration_id;

  structether_addr dst_addr;

  structether_addr src_addr;

  structether_addr bssid_addr;

  uint16_tseq_ctrl;

} __attribute__((packed));

第一个字中存放的是定义帧类型的字段。帧的类型大致分为三种:管理、控制和数据类型。AWDL的构建模块使用的是管理帧的一个子类型,称为Action帧。

另外,802.11报头中的地址字段在不同的上下文中具有不同的含义;就这里来说,第一个字段是目标设备MAC地址,第二个字段是源设备MAC地址,第三个字段是基础设施网络接入点的MAC地址或BSSID。

由于AWDL是一个点对点网络,并不使用接入点,因此,AWDL帧的BSSID字段被设置为硬编码的AWDL BSSID MAC,即00:25:00:ff:94:73。当AWDL客户端试图寻找其他对等体时,它们其实就是在寻找BSSID。您的路由器是不会意外地使用这个BSSID的,因为苹果的OUI为00:25:00。

报头之后的字节的格式取决于帧的类型。对于一个Action帧,报头之后的字节是一个类别字段。实际上,有许多类别的帧允许设备交换各种类型的信息。例如,类别5运行交换各种类型的无线电测量数据,如噪声的频率分布等。

特殊类别值0x7f将帧定义为特定于供应商的动作帧,这意味着接下来的三个字节,是确定该自定义动作帧格式的供应商的OUI。

苹果公司拥有OUI 0x00 0x17 0xf2,这就是用于AWDL动作帧的OUI。在这之后,帧中的每个字节都是专有的,也就是由苹果公司定义,而不是由IEEE标准定义。

SEEMOO实验室团队已经在这方面做了大量的工作:逆向分析了了AWDL动作帧的格式,并开发了一个wireshark解析器。

AWDL动作帧(Action frame)具有一个固定长度的报头,后面是长度可变的TLV集合:

1.png

AWDL帧中的字段布局。802.11报头、动作帧报头、AWDL固定报头和可变长度的AWDL有效载荷

每个TLV都有一个单字节的类型字段,后面有一个两字节的长度字段,然后是长度可变的有效载荷长度字段,其单位为字节。

AWDL动作帧分为两种:主指示帧(MIF)和定期同步帧(PSF)。实际上,这两种类型只是在类型字段和及其TLV的集合方面存在差异。

每个AWDL网状网络都有一个通过选举决定的单一主节点。选举过程为:每个节点都广播一个包含主度量参数的MIF;拥有最高度量值的节点将成为主节点。这个主节点的PSF定时值应被作为所有其他节点同步的真实定时值;这样,它们的可用窗口就能重叠在一起,使得网络具有更高的吞吐量。

帧的处理

早在2017年,Project Zero研究员Gal Beniamini发表了题为“Over The Air”的系列文章,文章中介绍了一种开创性的方法:利用BroadcomWiFi芯片组的一个漏洞,在WiFi控制器上获得本地代码执行权限,然后通过chipset-to-Application Processor接口中的iOS内核漏洞,实现了任意的内核内存读/写原语。

在这个系列文章中,Gal利用的就是Broadcom固件在解析与TDLS相关的数据结构时出现的安全漏洞。这些数据结构的原始形式是由芯片组固件本身处理的,从未进入过应用处理器。

相比之下,AWDL帧的整个解析过程,似乎是由内核驱动在应用处理器上完成的。虽然这意味着我们可以探索很多AWDL代码,但也意味着我们必须在可以使用AWDL解析器构建的原语之上构建exploit,并且这些原语必须强大到足以远程入侵设备。由于苹果公司在每个iOS版本和硬件修订版中都会推出新的缓解措施,所以,我们当然会把目标放在最新的iPhone 11 Pro上,因为它汇集了最全面的缓解措施。

我们真的能打造如此强大的利器,仅凭WiFi帧解析器的线性堆溢就能远程挫败内核指针认证机制吗?挫败缓解措施的时候,通常都需要建立一个技巧库,以帮助建立更多、更强大的原语。实际上,我们可以从线性堆溢出漏洞下手,用它来构建一个任意的读取原语,然后,进一步利用该原语构建一个任意的比特翻转原语,依此类推。

实际上,我已经建立了一个类似的技巧库,可用于在iOS上进行本地提权;但对于这个全新的攻击面,我必须从头开始打造新的技巧库。

AWDL代码库简介

首先要熟悉的两个C++类是IO80211AWDLPeer和IO80211AWDLPeerManager。当设备从某个AWDL对等体接收帧后,都会为其创建一个IO80211AWDLPeer对象。同时,还有一个后台计时器,专门用于销毁不活跃的IO80211AWDLPeer对象。此外,还有一个IO80211AWDLPeerManager的单一实例,负责协调这个设备和其他对等体之间的交互。

注意,虽然我们手头上面有iOS 12 beta 1kernelcache和MacOS IO80211Family驱动程序中的某些函数的名称,但我们并不了解对象的布局信息。Brandon Azad指出,MacOS的预链接内核映像确实包含一些结构体布局信息,这些信息位于__CTF.__ctf节,我们可以通过trace ctfdump工具对这些信息进行解析。不幸的是,这里似乎只有来自开源XNU代码的结构体。

通过静态分析,我们可以轻松地确定出基于OSob ject的IOKit对象的长度,但单个字段的名称和类型,却无法通过这种方式获得。实际上,整个项目中最耗时的任务之一,就是对这些对象中大量的字段的类型和含义进行不厌其烦的逆向分析。例如,每个IO80211AWDLPeer对象的长度几乎有6KB;其中含有大量潜在的字段。如果拥有了结构体的布局信息,就能为我们节省几个月的时间。

之前,我曾经天真地认为现实世界中任何具有一定实力的exploit开发团队都拥有这些信息——这些信息可能来自于他们在经过或没有经过苹果公司授权情况下获得的具有完整调试符号的映像或设备,或者来自内部访问权限,甚至只是通过监测曾经公开发布的每一个固件映像来检查调试符号是否被意外公开。可笑的是,我曾经以为更大的团体甚至会有专门的人员来打造定制的逆向工具。

六年前,我曾寄希望于Project Zero能够获得这样的数据源的合法访问权。六年后的今天,为了逆向分析出结构体的布局和命名变量,我仍然不得不耗费好几个月的时间。

在这里,我们将把IO80211AWDLPeerManager::actionfr ameInput作为解析不受信任的原始AWDL帧数据的起点。实际上,WiFi芯片组驱动程序中有一个单独的早期处理层,但它的解析能力非常有限。

当设备在social信道上进行监听时,收到的每个帧都被发送到AWDL BSSID,并以actionfr ameInput结尾,并被封装到一个mbuf结构体中。实际上,mbuf是一种不合时宜的数据结构,它用于封装网络缓冲区集合。另外,mbuf API简直就像一场噩梦,但它不在本文的讨论范围内。

通常来说,mbuf缓冲区会被连接起来,从而在内存中得到一个连续的帧,以便进行解析,然后调用IO80211PeerManager::findPeer方法,从收到的帧中查找源MAC 地址:

IO80211AWDLPeer*

IO80211PeerManager::findPeer(struct ether_addr*peer_mac)

如果最近从这个源MAC收到过AWDL帧,那么这个函数就会返回一个指向现有的IO80211AWDLPeer结构体(该结构体表示使用该MAC地址的对等体)的指针。然后,IO80211AWDLPeerManager将使用一个相当复杂的优先级队列数据结构(称为IO80211CommandQueue),来存储指向这些当前处于活跃状态的对等体的指针。

如果在IO80211AWDLPeerManager的对等体队列中没有找到相应的对等体,那么将分配一个新的IO80211AWDLPeer对象来代表这个新的对等体,并将其插入IO80211AWDLPeerManager的对等体队列中。

一旦找到合适的对等体对象,IO80211AWDLPeerManager就会调用IO80211AWDLPeer对象的actionfr ameReport方法,以便处理动作帧。

这个方法负责大部分AWDL动作帧的处理工作,以及大部分不受信任的数据的解析过程。首先,该方法将更新一些时间戳,然后,使用IO80211AWDLPeerManager::getTlvPtrForType方法从帧内TLV读取各种字段,以便直接从mbuf中提取它们。进行初步解析之后,就会进入主循环,并依次对各个TLV进行解析。

首先,每个TLV将传递给IO80211AWDLPeer::tlvCheckBounds方法。这个方法具有一个硬编码的列表,其中记录了某些受支持的TLV类型的最小和最大TLV长度。对于没有明确列出的类型,它执行的最大长度是1024字节。我在前面说过,我经常遇到一些代码结构,它们看起来像是浅层的内存损坏,但后来却发现在很远的地方却存在相应的边界检查。这里遇到的正是这种结构,实际上也是苹果在补丁中添加边界检查的地方。

0x14类型(在解析器中存在安全问题)没有明确地列在tlvCheckBounds中,所以,它得到了1024字节的默认长度上限,这明显大于IO80211AWDLPeer结构中为目的缓冲区分配的60字节的缓冲区的长度。

这种将边界检查从解析代码中分离出来的模式是很脆弱的——为一个新的TLV类型添加代码时,人们很容易忘记或根本没有意识到还需要更新tlvCheckBounds函数。如果非要使用这种模式的话,那就必须设法强制要求新的代码必须在这里明确地声明一个上界。其中,一个方法是确保类型为枚举类型,并将tlvCheckBounds 方法封装到一个pragma中,以暂时将clang的-Wswitch-enum警告作为错误对待:

#pragma clang diagnostic push

#pragma diagnostic error "-Wswitch-enum"

 

IO80211AWDLPeer::tlvCheckBounds(...) {

 switch(tlv->type) {

    case type_a:

      ...;

    case type_b:

      ...;

  }

}

 

#pragma clang diagnostic pop

 如果switch语句没有为tlv->type枚举变量的每个值提供显式的case语句,就会导致编译错误。

在这里,也可以使用像Semmle这样的静态分析工具。此外,我们可以像这个示例代码中一样,使用EnumSwitch类来检查是否显式处理了所有枚举值。

如果tlvCheckBounds检查顺利通过,就会使用一个带有case子句的switch语句来解析受支持的TLV:

1.png

SyncTree漏洞

下面是包含该漏洞的parseAwdlSyncTreeTLV方法的相关部分的反编译结果:

int

IO80211AWDLPeer::parseAwdlSyncTreeTLV(awdl_tlv* tlv)

{

  u64new_sync_tree_size;

 

  u32old_sync_tree_size = this->n_sync_tree_macs + 1;

  if(old_sync_tree_size >= 10 ) {

   old_sync_tree_size = 10;

  }

 

  if(old_sync_tree_size == tlv->len/6 ) {

   new_sync_tree_size = old_sync_tree_size;

  } else {

   new_sync_tree_size = tlv->len/6;

   this->n_sync_tree_macs = new_sync_tree_size;

  }

 

 memcpy(this->sync_tree_macs, &tlv->val[0], 6 *new_sync_tree_size);

 

...

其中,sync_tree_macs是IO80211AWDLPeer结构体中的一个内联数组,长度为60字节,位于偏移量+0x1648处。所以,这里有足够的空间来存储10个MAC地址。IO80211AWDLPeer对象的大小为0x16a8字节,这意味着它将被分配在kalloc.6144区域。

此外,tlvCheckBounds将限制SyncTree TLV长度的最大值为1024。TLV解析器将把这个值向下取整到最接近的6的倍数,并将这个字节数复制到+0x1648处的sync_tree_macs数组中。这将是我们的内存破坏原语:长度为6字节的线性堆缓冲区发生溢出,从而破坏IO80211AWDLPeer对象中偏移量大于+0x16a8的所有字段,以及kalloc.6144区块末尾的几百字节。我们可以利用伪造的大量源MAC地址快速连续地发送AWDL帧,就能让IO80211AWDLPeer对象分配到连续的内存空间。这样的话,我们就能获得四个原语,它们虽然有些简陋,却可以为进一步的漏洞利用奠定基础:

1) 破坏IO80211AWDLPeer对象中sync_tree_macs数组后面的字段:

1.png

溢出到peer对象末尾的字段中

2) 破坏与这个对象相邻的IO80211AWDLPeer对象的低位字段:

1.png

溢出到与该对象相邻的peer对象开头的字段中

3) 破坏kalloc.6144中的另一个不同类型的对象(这个对象是我们精心构造的,并且专门跟在peer对象的后面)的低位字节:

1.png 

溢出到同一内存区域内的、位于这个peer对象之后的另一个不同类型的对象中去

4)通过精心构造元数据,让内存区域分配器将一个peer对象放到内存区域的边界处,这样的话,我们就可以破坏来自另一个内存区域中的对象的开头部分的字节了:

1.png  

溢出到来自另一个内存区域的不同类型的对象中去

对于上面的四种原语,我们将在下文中详细加以解读。

开启无线之旅

至此,我们已经对AWDL帧的格式有了足够的了解,下面,咱们开始尝试通过无线方式获得受控的任意数据并到达帧解析入口点。

本来,我还想省点劲——直接使用现成的学术性OWL开源项目,可惜的是,我花了很长时间都没能成功编译和运行其中的代码。于是,我决定从头开始编写自己的AWDL客户端。此外,实际上还有另一种方法,那就是编写一个MacOS内核模块,通过它与现有的AWDL驱动程序进行交互,虽然这种方法可能会简化漏洞利用的某些方面,但同时也会提高其他方面的难度。

于是,我找出了自己珍藏多年的NetgearWG111v2 WiFi适配器,因为它可以进入监控模式并完成帧注入,尽管它只能工作在2.4Ghz信道之上。需要说明的是,它使用了rtl8187芯片组。由于我想使用这些适配器的Linux驱动程序,所以,我特地买了一个Raspberry Pi 4B来运行exploit。

过去,我曾使用Scapy从头开始构造过网络数据包。我们知道,Scapy可以用来构造和注入任意的802.11帧,但由于我们需要对注入时间进行精确的控制,因此,它可能不是最合适的工具。实际上,Scapy是通过libpcap库与硬件进行交互来注入原始帧的,所以,我就顺带研究了一下libpcap库。在网上搜索过后,我发现了一篇非常细致的教程,它演示了如何使用libpcap来注入一个原始的802.11帧,详情我们将在下一篇文章中为大家介绍。

小结

在这篇文章中,我们将为读者详细介绍近期在iOS中发现的一个零点击无线电近程漏洞,由于篇幅过大,我们将分多篇发表。更多精彩内容,敬请期待!

未完待续

 原文地址:https://googleprojectzero.blogspot.com/2020/12/an-ios-zero-click-radio-proximity.html

最新评论

昵称
邮箱
提交评论