一、概述
在本次研究中,我将主要分析Marvell WiFi FullMAC SoC的安全性。由于我们尚未完成对该产品中带有芯片的无线设备的研究,因此其中可能包含大量未经审计的代码,这可能会出现严重的安全问题。本文是基于我在ZeroNights 2018演讲整理而成,演讲的展示幻灯片请参见这里。此外,很多安全团队都针对无线SoC安全这一话题开展了研究。例如,Google Project Zero在2017年4月发布了一系列博客文章,描述了如何在智能手机上利用Broadcom Wi-Fi栈。这一话题同样也在2017年BlackHat大会上进行了讨论。一些智能手机基带漏洞利用的文章,可能有助于各位读者理解用于逆向无线SoC固件的技术。
二、无线设备工作原理
一般来说,Wi-Fi的加密狗可以分为两大类:FullMAC和SoftMAC。二者都需要固件映像,在每次设备启动时都应该上传。设备制造商会提供适当的固件映像和操作系统设备驱动程序,所以在启动期间,驱动程序可以上载固件,从而使其主要功能可以被Wi-Fi SoC使用。下图说明了这一过程。
启动SoftMAC和FullMAC加密狗之间的主要区别在于其固件功能。此外,FullMAC加密狗的固件具有MLME(MAC层管理实体,MAC Layer Management Entity)。换而言之,它能够在没有操作系统驱动程序任何支持的情况下,处理一些Wi-Fi管理帧和事件实体。显然,FullMAC加密狗的攻击面会更大,所以我们也倾向于寻找这些设备上潜在的漏洞。
三、Wi-Fi SoC与驱动程序之间的交互
在Linux内核中,有两个版本的驱动程序用于处理Marvell Wi-Fi:
1、mwifiex驱动程序(可以在官方的Linux repo中找到);
2、mlan和mlinux驱动程序(可以在官方的steamlink-sdk repo中找到)。
二者都具有一些调试功能,可以允许我们读写SoC内存。驱动程序使用内部格式,将信息发送到Wi-Fi SoC,并从SoC接收事件或响应。Wi-Fi SoC有如下几种特定类型的数据:
· COMMAND(命令)
· EVENT(事件)
· DATA(数据)
· SINGLE PORT AGGREGATED DATA(单端口汇总数据)
Wi-Fi SoC和设备驱动程序之间的交互模式如下所示:
我更倾向于考虑由固件实现的API命令。这些命令可分为以下几组:
1、SoC存储器的读/写命令;
2、来自固件的扩展版本信息(例如:适用于SteamLink的15.68.7.p206、w8897o-B0、RF8XXX、FP68)
3、与Wi-Fi相关的内容(例如:协作、扫描等)
其中的一些命令,可以通过驱动程序实现的IOCTL或者一些特殊的debugfs文件,从用户模式(Usermode)访问。驱动程序最有用的功能之一就是它可以进行固件内存转储。这将有助于调试我们的动态检测方法或漏洞利用方法。根据分析,超时机制似乎是在操作系统驱动程序中实现的。因此,当命令响应超时时,驱动程序将尝试转储Wi-Fi SoC内存,并将其存储在主文件系统中。在mwifiex和mlan+mlinx驱动程序中,内存转储具有不同的格式。经过对mwifiex PCI驱动程序进行研究,我发现它是以类似固件映像的格式存储了完整的Wi-Fi SoC内存转储。SDIO版本的mlan+mlinx驱动程序仅以二进制格式存储了ITCM、DTCM和SQRAM区域。
四、固件分析
如前所述,Marvell Avastar Wi-Fi芯片组系列使用了固件文件,并在这些文件中承载了大部分设备的功能。ROM中包含启动代码,并在将主固件加载到芯片RAM之前负责与主机进行交互。官方linux-firmware git repo提供了几个版本的固件。因此,我们首先需要研究驱动程序初始化Wi-Fi SoC所使用的固件映像。
4.1 静态固件文件分析
为了获得与固件RAM映像结构相关的信息,我们可以查看Marvell Wi-Fi驱动程序代码,该代码将固件加载到Wi-Fi SoC(drivers/net/wireless/marvell/mwifiex/fw.h)。
... struct mwifiex_fw_header { __le32 dnld_cmd; __le32 base_addr; __le32 data_length; __le32 crc; } __packed; struct mwifiex_fw_data { struct mwifiex_fw_header header; __le32 seq_num; u8 data[1]; } __packed; ...
各位读者可能会注意到,固件文件中包含一些带有头部和校验和(Checksum)的内存块。在块的头部,还包含SoC中的地址,该内存即将被加载。掌握了这些知识以后,我们可以在IDA Pro中查看Marvell Avastar的固件文件,从而进行进一步研究。
在初步逆向之后,我们可以发现88W8897是具有8个MPU区域的ARM946微控制器。所有内存都是RWX权限。固件文件中,还包含对ROM功能的引用。因此,为了进一步研究,我们需要一个ROM转储。下图展示了88W8897 Wi-Fi芯片的内存映射,未知的内存区域似乎是内存映射的寄存器区域。
4.2 动态固件分析 – ThreadX运行时结构恢复
我们可以利用读写命令,创建一个可以转储内存和指示固件的简单工具。利用该工具,我们可以获得一些运行时信息。值得注意的是,固件是一个非常大的二进制文件。其中只包含几个字符串,并且没有任何关于设备运行原理以及潜在的漏洞挖掘位置的提示信息。但是,在研究了ROM转储(利用我们的工具)之后,我们可以发现,这是一个基于ThreadX的固件。
ThreadX是一种广泛应用于智能设备的特有RTOS。可以使用许可证,获取此RTOS的源代码。ThreadX基本上只是一个运行时环境,是用于管理动态内存、线程以及线程之间通信的函数。ThreadX也是最受欢迎的RTOS之一,部署数量超过60亿(根据官方提供的数字)。根据其中的ID字段,我们可以在内存中搜索ThreadX运行时结构,如果该结构有效,那么ID字段中似乎包含特定值。例如,在线程结构中:
typedef struct TX_THREAD_STRUCT { /* The first section of the control block contains critical information that is referenced by the port-specific assembly language code. Any changes in this section could necessitate changes in the assembly language. */ ULONG tx_thread_id; /* Control block ID */ ... }
第一个4字节字段tx_thread_id中,必须包含值0x54485244(ASCII值为THRD)。这就给了我们更多的信息,因为其中一些ThreadX对象可以包含名称,这样我们就能部分猜测出它们的用途。我们将ThreadX运行时结构进行重构,作为IDA脚本。这样一来,它就可以用于研究另一个基于ThreadX的设备内存转储。ThreadX结构重构后的一些摘要信息请参见下表(地址是用于内部版本为w8897o-B0、RF8XXX、FP68、15.68.7.p206的默认Steamlink固件):
4.3 动态固件分析 – 动态固件检测
我们的目标是寻找固件中存在的漏洞,但我们关于目标的信息非常少,具体是由于如下原因:
1、源代码不可用;
2、处理或解析帧的代码是未知的;
3、Wi-Fi SoC包含少量内存,足以满足其目的,我们无法将代码放在Wi-Fi SoC中进行模糊测试或量化测试。
针对上面的第一点和第三点,我们找不到任何变通方法。因此,我们可以尝试运行固件,来寻找负责处理Wi-Fi帧的函数。借助读写命令,可以在Wi-Fi SoC上进行几种类型的运行时分析。
1、利用一些拼接技术,我们可以挂钩一个函数;
2、可以替换一些类似调试或日志的例程的指针;
3、可以跟踪池分配/释放;
4、甚至可以使用静态Thumb函数调用(例如:函数级别的DBI)来检测整个代码区域。
借助我们的Wi-Fi SoC研究工具,前三点非常简单。但最后一个问题可能看起来比较棘手。基本上,我们的工具使用capstone反汇编引擎来查找Thumb BL(用于函数调用)指令,并将其替换为对instrumentation stub的调用。该instrumentation stub负责调用我们的自定义DBI工具,具有正确参数的原始固件函数,并返回调用。各位读者可能会发现,它与具有函数级别检测的DBI框架非常相似。该算法非常简单:
我们可以从下图中,看到有关instrumentation stub工作流程的更多详细信息:
综上所述,为了在Wi-Fi SoC上设置代码,我们需要:
1、从Wi-Fi SoC读取待检测的存储区域。
2、使用capstone对其进行反编译。
3、编写修补代码,将其用于修补内存中的固件,并调用检测用户定义例程的结构。
4、将这些结构、特定的修补代码、stub和用户定义的例程复制到Wi-Fi SoC。
5、借助挂钩后的例程,并使用常规固件API对其进行调用,来执行修补程序。驱动程序很少会调用这一固件功能。这会确保我们可以安全地禁用中断处理和进程检测。
6、在检测工具收集到必要的运行时信息之后,使用固件/驱动程序功能访问Wi-Fi SoC内存,从Wi-Fi SoC内存中复制结果。
值得注意的是,该类型的instrumentation存在一些微架构(Microarchitectural)问题,其原因在于我们对instrumentation stub的新BL调用覆盖了Wi-Fi SoC上的指令。因此,在I/D-Cache缓存不一致的情况下,我们可能会丢失一些结果(某些调用可能无法执行,因为来自I-cache的旧原始指令仍然有效)。我们推测,其原因在于固件锁定ARM CP15协处理器(Coprocessor)寄存器,用于在初始化后执行写入。因此,要在Wi-Fi SoC上刷新I-cache并不是一件轻而易举的事。还有另外一种技术可以研究Wi-Fi SoC内部,就是静态固件instrumentation。然而,它需要每次重建Wi-Fi固件,以应用新的分析Payload。还需要设备的重新启动,以启动已检测的固件。有几种类型的DBI工具,可以在这里提供帮助:
1、能搜索函数参数(例如BSSID或MAC)中签名的工具;
2、能收集有关调用栈信息(该信息可以对逆向工程或固件模糊测试提供帮助)的工具;
3、能监视ThreadX块池状态的工具。
上述这些工具,都为我们提供了代码处理框架的信息,因为我们可以使用不同的客户端二进制文件自定义DBI工具。在没有源代码和任何逆向提示(例如:记录字符串、导出的函数名称)的情况下,这是向前迈出的一大步。在将这种类型的动态分析应用于运行中的固件之后,我们就可以了解哪些函数用于解析输入帧和参数,其中输入数据被传递到这些函数。之后,就可以使用多种类型的二进制分析和漏洞搜寻技术。
五、寻找漏洞
尽管我们已经在固件内存转储和Wi-Fi SoC内部,采用了各种类型的二进制分析(包括静态和动态),但仍然很难搜寻漏洞。
5.1 模糊测试
就目前而言,似乎只有两种类型的模糊测试可以使用:
1、无线随机模糊测试;
2、模拟环境中对固件进行模糊测试。
第一种类型的模糊测试,可以直接对Wi-Fi SoC进行。通常,可以使用包括JTAG、ARM ETM或Intel Process Tracing技术在内的处理器自身功能,来实现收集边缘覆盖的目标。但是,这一过程还需要芯片自身的硬件支持,以及一些硬件黑客方面的知识储备,才能在生产环境的设备中使用硬件调试功能。这是一项非常重要的工程任务,因此盲目地进行模糊测试是非常愚蠢的。接下来,我们就尝试第二种类型。第二种类型的模糊测试依赖于固件模拟,因此在一些反馈驱动算法(Feedback-driven Algorithm)的帮助下,收集突变输入(Mutating Input)的边缘覆盖就相对容易。这实际上是对无线设备进行SMART模糊测试。说起来可能令人惊讶,但是允许我们以这种方式来进行模糊测试的工具已经淘汰了。因此,我们可以使用由原始的AFL模糊工具和名为afl-unicorn的Unicorn CPU模拟器的混合工具,最初由Nathan Voss编写。我们可以查看相关文档以了解其工作原理,包括模糊任意代码的方法,以及CGC二进制示例。因此,要使用afl-unicorn工具来对Wi-Fi固件进行模糊测试,我们需要识别解析例程(例如:使用Wi-Fi SoC DBI工具),并编写一个将突变输入(Mutated Input,Wi-Fi帧)输入这些例程的模糊工具。大致来说,模糊工具应该完成以下工作:
1、使用修改版本的Unicorn映射必要的内存区域;
2、设置寄存器上下文;
3、读取改变后的输入文件,并将其映射到模拟器内存中;
4、开始执行代码;
5、通过发送特定信号,模拟固件崩溃。
看起来,这是一种简单有效的技术,但仍然存在一些缺点。最值得注意的是对全局状态的依赖性,这一点是在创建Wi-Fi SoC内存转储时捕获的。该状态可以包含一些已经保存的全局变量,这些变量可能会组织模糊工具访问某些执行路径。由于没有动态内存访问清理,很难找到并删除校验和(Checksum)验证代码。此外,无法实现RTOS任务之间的通信,因此这也会阻止一些潜在的有趣的执行路径。但是,使用这种模糊测试技术,还是有一些结果的:
借助这种技术,我成功在固件的某些部分识别出大约4个内存损坏问题。尽管如此,由于AFL能够以某种方式改变输入(例如:在模糊函数之前执行一些检查),所以就无法传递给模糊后的函数,因此很难研究由这些问题导致的潜在影响。我还尝试在不同版本的固件和不同版本的无线SoC上复现这些漏洞,看上去很多漏洞都存在于其中。
六、漏洞利用
其中的一个漏洞,是ThreadX块池溢出的特例。在扫描可用网络期间,无需与用户进行交互,即可触发这一漏洞。无论设备是否连接到某个Wi-Fi网络,该过程都将每分钟启动一次。这样一来,就充分说明了这一漏洞的严重性,它导致在任何无线连接状态下,即使设备没有连接到网络,也可以在无需交互的情况下,实现漏洞利用。例如,只需通过一台Samsung Chromebook,即可实现远程代码执行漏洞的利用。因此,总结一下我们发现的漏洞:
1、无需任何用户交互;
2、在GNU/Linux操作系统下,可以每隔5分钟触发一次;
3、无需知道Wi-Fi网络名称或密码/密钥;
4、设备可以不连接到任何Wi-Fi网络,只要打开电源即可触发漏洞。
在这里,我将详细描述如何在Wi-Fi SoC上实现任意代码执行。关于提升技术的详细信息也会在后面进一步介绍。
6.1 基本ThreadX块池溢出漏洞利用
ThreadX块池是一个连续的内存区域被分为较小的块。每个块池都由运行时结构表示,可以通过IDA脚本在内存转储中具体看到。在每个块的开头,都有一个指向下一个空闲块的指针。在最后一个空闲块之前,存在NULL指针。第一个空闲指针存储在ThreadX块池管理结构中。指向该结构的指针,将用于块池分配和销毁功能。
我们发现,攻击者可以覆盖指向下一个空闲块和控制位置的指针,从而分配下一个块。通过控制下一个块分配的位置,攻击者可以将此块放置到某些关键运行时结构或指针所在的位置,从而实现代码执行。
6.2 Marvell Avastar ThreadX块池溢出漏洞利用
在Marvell Avastar固件中的大多数内存管理例程,都依赖于特殊的包装函数(Wrapper Function)。该函数在每个ThreadX块的开头使用特殊元数据头。通过对此函数进行逆向工程,可以发现这些头中包含特殊指针,这些指针在释放块之前就被调用。因此,针对Marvell Avastar的固件,攻击者可以轻松地在无线SoC上实现代码执行。下面是允许执行人意指针的块解除分配器(Deallocator)的伪代码:
要执行代码,攻击者只需覆盖下一个块的更多额外空间(仅在它被使用的情况下):
6.3 二者结合的漏洞利用
因此,我们有两种技术可以实现ThreadX块池溢出漏洞的利用。一个是通用技术,可以应用于任何基于ThreadX的固件,另一种技术特定于Marvell Wi-Fi固件的实现。如果将二者结合在一起,那我们就可以实现可靠的利用。
七、Valve Steamlink提升至应用程序处理器的示例
Valve Steamlink是一个简单的桌面流媒体设备,可以让用户在计算机上运行电脑游戏,并将游戏界面以流媒体的方式传输到电视盒子。该设备的固件基于类似Debian的GNU/Linux操作系统,使用3.8.13-mrvl Linux内核,在ARM7l应用处理器上工作。该产品也包含Marvell 88W8897无线芯片组,可以连接SDIO总线、专有的mlan.ko以及mlinux.ko设备驱动程序。我们注意到,可能是由于其高性能的802.11ac和Bluetooth COMBO,大多数使用了Marvell Wi-Fi的都是游戏设备,例如PS4。因为这些设备具有DRM保护,所以我们很难对其进行研究。因此,我们选择了SteamLink,因为它没有DRM,并且可以轻松启动其工具和内核模块来研究无线SoC。值得一提的是,Microsoft Surface和Samsung Chromebook都使用了Marvell Wi-Fi。
7.1 升级攻击面
要在SteamLink的应用程序处理器上执行代码,我们需要进行第二次权限提升,因为SDIO总线没有设计从设备到主机的直接内存访问。像PCIe这样的总线是允许DMA的,因此提升过程会简单得多。在这种情况下,权限提升漏洞的利用类似于远程漏洞利用。唯一的区别就在于攻击者是通过SDIO总线,从受控Wi-Fi SoC发送数据,而不是通过网络发送数据。我们可以将典型的设备驱动程序,看作是设备与应用程序或操作系统之间的桥梁。因此,它应该从设备接收数据,并进行解析,然后将其发送到应用程序(操作系统)上,反之亦然。其中包含解析从设备接收的数据的代码。在Marvell Wi-Fi驱动程序中,这部分代码负责处理由信息元素(Information Elements)组成的许多类型的消息。事实上,升级后的攻击面非常广泛。
7.2 利用AP设备驱动程序漏洞
我们发现的漏洞也非常容易利用,是基于栈的缓冲区溢出漏洞。Linux内核“3.8.13-mrvl”中也没有二进制漏洞利用的缓解措施。但是,由于I/D-cache不连续,并且回写缓冲区延时提交(Write-back Buffer Deffer Commit),还需要一些准备阶段。此外,由于函数尾部无法控制栈,因此会从栈本身弹出(POP)栈指针:
LDMFD SP, {R4-R11,SP,PC}
要想成功利用权限提升漏洞,应该执行以下操作:
1、调用v7_flush_kern_cache_louis Linux内核函数;
2、执行ShellCode。
由于栈指针丢失,我们无法将小工具(Gadget)放在栈上。相反,我们可能依赖于寄存器R4-R11,它们也会在执行继续道回复PC位置之前从栈中恢复。首先,我们需要在一个基本块中找到一个包含两个不同寄存器调用的特殊小工具。这个小工具表示两个主要操作的调用:刷新缓存、调用ShellCode。
BLX R3 MOV R1, R4 MOV R2, R5 SUBS R3, R0, #0 MOV R0, R10 BNE loc_C00E7678 BLX R9
尽管它包含一个条件分支,但永远不会被占用,因为v7_flush_kern_cache_louis总会返回0。并且,它也不会破坏R9,这个地方可以被攻击者控制。然而,第一次调用是使用R3寄存器进行的,该寄存器不会从栈中恢复。在这种情况下,应该搜索小工具,以在调用主要值之前先在R3中放置受控制的值。例如,像这样:
MOV R3, R8 BLX R7
最后的小工具应该负责计算ShellCode的位置,并将执行转到相应位置。在这种情况下,我们可以使用R0、R1、R2、R3和R12,因为其中可以包含栈指针。对于Marvell的驱动程序来说,R12确实包含栈中的地址。因此,需要找到一个小工具,它将使用受控制的寄存器和R12来计算实际的ShellCode位置,并转到执行,如下所示:
LDR R6, [R12,R4,LSL#4] MOV R7, R0 ADD R4, R12, R4,LSL#4 MOV R8, R2 BLX R6
还应该注意的是,攻击者可以通过使用thumb指令编码,显著增加可以使用的小工具的数量。实际上,在溢出期间有几种不同的R12指针位置。我认为,这是取决于当前的扫描状态。我们可以研究如何正确地将事件缓冲区从Wi-Fi SoC发送到AP,最终的栈布局将始终相同。总体来说,利用的成功率在50%-60%左右。
7.3 利用Valve Steamlink的要求
在研究中,我在监控模式(Monitor Mode)下使用ALFA网络无线适配器,它是基于Realtek 8187无线芯片组。该漏洞可以使用Python Scapy框架实现。出于某种原因,Ubuntu GNU/Linux版本不太适合迅速注入Wi-Fi帧,我们最好使用Kali。我们可以在这个视频中,看到完整漏洞利用演示,演示的Payload只是周期性在内核日志中打印信息。
视频链接:https://youtu.be/syWIn62M72Y
八、总结
从这个故事中,我们可以学习到:
1、无线设备通常存在巨大的攻击面;
2、通常,在无线SoC上,没有漏洞利用的缓解措施;
3、设备驱动程序可能会暴露出更广泛的攻击面,以便从设备升级到主机应用程序处理器,即使在设备无法直接访问主机内存的情况下也是如此。