DEF CON 29 技术分享 | 如何解锁高通骁龙660上的安卓引导加载程序
2021-08-17 12:55:00 Author: www.4hou.com(查看原文) 阅读量:47 收藏

本文是我们在DEF CON 29大会上的演示视频的配套文章,视频的链接地址为https://www.youtube.com/watch?v=z4gIxdFfJDg。

几个月前,我购买了一部安卓手机,以便对某系列的NFC芯片展开相应的研究,为此,我必须获得手机的root权限,因为只有这样才能访问所有的硬件功能。

要想在安卓手机上获得root权限,通常需要解锁引导加载程序,禁用手机的签名验证,这样的话,我们就可以部署一个修改过的安卓引导映像了。在Qualcomm芯片组上,这是一个标准化的过程,可以使用安卓引导加载程序中的命令来执行解锁。智能手机制造商经常修改引导加载程序以增加特定的限制,但是,这通常要求使用他们自家的工具。

这些自定义的限制通常包括:强迫创建一个用户账户来请求解锁,并在允许解锁之前强制等待一段时间,等等。

那么,手机制造商为什么要添加这些限制呢?原因如下所示:

◼    防止没有经验的用户因为受骗而削弱手机的安全性;

◼    防止第三方在售前给设备加载恶意软件(如供应链攻击);

◼    制造商可以跟踪是谁解锁了他们的引导加载程序。

我购买的手机就有这些限制,要求我等待七天后才能获得设备的root权限。由于这个原因,我决定调查我是否可以绕过同一制造商生产的旧智能手机的等待期。我给自己设定了一个挑战,即在七天等待期结束前绕过引导加载程序的保护。火狐截图_2021-08-14T05-17-12.940Z.png

标准绕过方法

实际上,现在已经有一些标准方法可以绕过这些限制,然而,它们都具有相应的风险。

其中,最常用的方法就是进入高通公司紧急下载模式。这是一种低级别的紧急状态启动模式,可以使用诊断工具将具有签名的“加载器”有效载荷上传至芯片,这样,就可以直接修改设备的分区了。虽然这种方法是有效的,但这要求用户先引导进入该模式(并非所有用户都可以直接进入该模式),并且需要访问可能无法用于该设备的已签名加载程序ELF文件。

第二种常见的方法是在硬件层面上攻击设备。通过拆卸手机,并连接电路板上的EMMC芯片,就能在配置分区中设置“解锁”位,从而获得解锁权限。现在,已经有一些这方面的公开资料,其中说明了应该连接到哪些引脚,但是这通常需要一些硬件知识和动手能力。此外,硬件一旦受到物理损坏,手机就可能永远变砖了。

因此,我可不想使用这两种方法,相反,我的目标是攻击第二阶段的引导加载程序。这是因为,虽然通过第一阶段的引导加载程序可以进入紧急下载模式,但是在引导过程的下一个阶段,却可以获得额外的诊断和管理工具。

SDM660安卓手机

目标设备是2017年发布的一款中档手机,它使用Qualcomm Snapdragon 660作为核心芯片组。

对制造商实现的自定义解锁功能的分析表明,它会将一个较小的唯一值从手机发送到解锁工具,并在其服务器上生成一个签名。七天后,这个签名将被发送到手机上并进行验证,从而完成解锁。我是借助于Windows主机和USBPCAP USB分析软件对这个过程进行分析的。

火狐截图_2021-08-14T05-15-50.927Z.png

我发现,对唯一值的请求、签名的发送以及对引导加载程序解锁的请求,都是通过安卓引导加载程序的Fastboot界面完成的。通过手机上的ADB重新启动到引导加载程序模式,或者在启动时按住音量下降键,就可以访问这个界面。然后,我们可以使用命令行工具“fastboot”,通过USB访问该界面。

Fastboot工具

这个工具能够简化对标准和定制的功能的访问,包括闪存分区、获得OEM特定的数据,或引导至不同的模式。对这个工具的分析表明,fastboot协议非常简单,可以用基本的C++代码和LibUSB进行实现。

火狐截图_2021-08-14T05-24-07.635Z.png

火狐截图_2021-08-14T05-24-50.282Z.png

分析表明,这个工具的所有的命令都是通过一个USB端点以ASCII文本形式发送的,而对命令的响应则是由另一个端点异步发送的。同时,虽然可以借助于某些程序库来简化对fastboot界面的访问,但是,我还是决定使用LibUSB,因为这样能够更好地控制通信过程。

ABL引导加载程序

实际上,fastboot界面是由安卓引导加载程序提供的,它属于第二阶段的引导加载程序,存储在手机的“abl”分区中。这个引导加载程序的作用,就是验证和加载手机的安卓映像和恢复映像,以实现其标准功能,或接收fastboot命令。需要说明的是,这个引导加载程序的高通版本是完全开源的,以便于手机制造商对其进行修改。

火狐截图_2021-08-14T05-21-23.952Z.png

由于我想攻击这个引导加载程序,所以,我希望从底层分析其功能。另外,由于手机制造商添加的自定义命令不会出现在源代码中,所以,我决定直接分析编译后的引导加载程序。为此,我们可以下载最新的OTA更新文件,然后解压缩,并访问存储在其中的“abl.img”文件,这样,我们就能够访问手机“abl”分区的内容了。通过对这个映像进行分析发现,虽然这是一个ELF文件,但是其中并没有包含任何可执行代码。

相反,通过文件分析工具“binwalk”对该文件的分析结果表明,该ELF文件包含一个EFI系统分区。这是一种用于在嵌入式和非嵌入式设备上启动操作系统的标准格式,并且,我们可以使用“uefi-firmware-parser”工具将其提取出来。

火狐截图_2021-08-14T05-25-31.190Z.png

实际上,存储在其中的“LinuxLoader”文件是一个PE格式的可执行文件,其内容就是引导加载程序的代码。这种标准的格式可以直接加载到IDA中,并进行反汇编。

对该引导加载程序的简单分析表明,所有的fastboot命令都存储在一个表中,该表由ASCII命令和该命令的函数回调组成。通过这个表,不仅可以快速分析任何潜在的隐藏命令,同时,还有助于分析具体的功能。我们还发现,该引导加载程序中包含大量的调试字符串,这使得对代码的理解更加容易。

火狐截图_2021-08-14T05-26-42.372Z.png

我的主要目标是找到一个内存损坏漏洞,以便通过它绕过解锁限制。为此,我决定把重点放在“flash:”命令上。我之所以选择这个命令,是因为它需要从PC主机接收大型有效载荷,并将其保存到闪存设备分区。

传统上,当引导加载程序被锁定时,“flash:”命令是不允许对设备上的分区执行写操作的,但是我注意到,制造商已经修改了他们的引导加载程序,以允许在锁定状态下刷写特定的、自定义的分区。由于这些分区对上传的数据进行了额外的解析,因此,这就增加了存在缺陷的可能性。

火狐截图_2021-08-14T05-27-24.823Z.png

因为我是从逆向工程的角度来研究fastboot协议的,而不是通过阅读文档来了解它的,因此,我对数据上传的方式做了一些假设。上传的正确顺序如下所示:

◼download:< payload size >

◼< send full payload >

◼flash:< partition >

但是,我尝试的是以下顺序:

◼flash:< partition >

◼< send payload >

除此之外,我在代码中的这个序列后面,故意放入了一个额外的“flash:”命令。因此,当运行这个序列时,将导致引导加载程序在执行第二个“flash:”命令后发生崩溃,从而导致USB接口不再接收任何命令。

崩溃分析

将设备从USB端口上拔下来,再插回去,我们发现它就不再通过USB进行枚举了,这意味着它完全无法正常工作了。按住电源和音量减弱按钮10秒钟,导致硬重启,我们发现上述操作并没有对设备产生永久性的损坏。

为了确认我偶然发现的是一个有效的缓冲区溢出,我决定尝试一个较小的有效载荷大小。我从一个10兆字节的有效载荷开始,并尝试用4千字节的有效载荷进行同样的序列。这个较小的有效载荷并没有使手机崩溃,它继续正常运行。

在阅读了如何在fastboot中使用“download:”命令上传数据之后,我猜测可能是发生了缓冲区溢出,因为我发送的大量有效载荷被视为fastboot命令,而不是要上传的分区。这意味着崩溃是由于高通引导加载程序的命令处理功能的漏洞所致,而不是由手机制造商实现的任何自定义功能所致。

为了确认我偶然发现的是一个有效的缓冲区溢出漏洞,我决定尝试一个较小的有效载荷长度。我从一个10兆字节的有效载荷开始,并尝试了长度为4千字节的相同序列的有效载荷。这个较小的有效载荷并没有导致手机崩溃,相反,手机一切正常。

由于较小的有效载荷不会导致崩溃,所以,我选择进行二分搜索,以确定不会使手机崩溃的最大有效载荷。我把要发送的有效载荷的长度设为最小值和最大值之间的中间值,然后更具手机是否崩溃,来减少有效载荷长度的最大值,或增加有效载荷长度的最小值。这使我找到了不会使设备崩溃的最大有效载荷,其长度为0x11bae0(1161952)字节。

由于这是一个不寻常的内存大小,这让我相信这肯定是某种类型的缓冲区溢出,然而,由于我无法接触到内部硬件,无法进行调试,对内存的映射情况一无所知,所以,具体是哪种类型的溢出,我还是拿不准。另外我注意到,引导加载程序使用随机值实现了堆栈金丝雀,这意味着如果我溢出堆栈并击中了金丝雀,那么,这种溢出漏洞很可能是无法利用的。

我决定发送一个长度为0x11bae1(1161953)字节的有效载荷,并将最后一个字节的值从0x00递增为0xff。如果手机没有因为其中某个特定的值而崩溃,那就说明并没有碰到堆栈金丝雀,而是找到了序列中的下一个字节。在这个位置,我们发现有效字节的值是0xff。

通过不断地循环加电,寻找有效的字节值,然后移动到序列中的下一个字节,就可以生成该位置上内存中数据的合理表示。当然,它不一定是准确的数据,但会足够接近,并且不会使引导加载程序崩溃。一旦生成这个序列,就有可能使用它来在引导加载程序中执行代码,但是如果不能自动化的话,这将是一个非常漫长的过程。

实现自动循环加电

实际上,加电循环的自动化可以通过拆除手机电池,使用主机PC供电,并使用USB继电器不断切断电源来实现自动化,但这需要拆卸手机并去除外壳周围的胶水。另外,如果我拆解手机,我可以直接进入EMMC,用基于硬件的方法解锁引导加载程序。

在寻找解决方案的时候,我不断按住手机的电源和音量下降按钮。这导致手机处于引导循环状态:它将重新启动并加载引导加载程序几秒钟,然后再次重新启动。我注意到,在这个过程中,USB接口会工作一段时间,并且这段时间足以让我发送fastboot命令。

于是,我在导致引导循环的两个按钮上缠了一条发带,并发现这种方法非常管用,所以,这样就实现了这一过程的自动化。

火狐截图_2021-08-14T05-28-26.020Z.png

然后,我修改了自己编写的fastboot工具,以便于内存转储。它将等待USB接口出现,尝试这个序列,然后验证引导加载程序是否崩溃,以及是否收到了 “Flashing is not allowed ”响应。我们对这两件事情进行了验证,因为这更有可能获得准确的内存转储。每次尝试都需要10-30秒,这意味着内存转储将需要一段时间。

火狐截图_2021-08-14T05-30-30.917Z.png

于是,我让手机连夜尝试这种内存转储方法,并发现了0x34字节不会导致引导加载程序发生崩溃的数据:

FF 43 02 51 60 02 00 0C 60 02 00 0C 60 02 00 0C 60 02 00 0C E8 00 00 B0 34 00 00 10 01 00 00 0A 08 0D 40 F9 00 00 00 08 C0 00 04 0B 60 02 00 0A D3 9F FF 97

我注意到其中有许多重复的值,但这些值与默认的堆栈金丝雀值0xC0C0C0C0并不一致,这意味着它们可能是不相关的。另外,这些数据看起来并不像在堆栈中常见的东西,但是我们发现每个32位的字都是一个有效的ARM64操作码。

火狐截图_2021-08-14T05-31-11.503Z.png

这些操作码中的大多数虽然有效,但不一定与引导加载程序中的操作码相同,但是所有的堆栈管理和分支操作必须相当准确,这样引导加载程序才不会崩溃。我试图利用IDA在引导加载程序的反汇编代码中搜索“Sub WSP”和“BL”指令,但是,并没可有找到它们。

当然,导致这种情况的原因有多种。例如,ARM64操作码通常具有相同或类似的功能,即使操作码中的位被翻转,寄存器也可以在32位(Wx)和64位(Xx)模式下被访问,同时,分支指令也可能带有某些条件,而现在蛮力攻击恰好无法满足这些条件。

由于这些外观上的差异,我决定尝试寻找类似的操作码,选择我已经确定的“BL”指令,因为这依赖于相对寻址,而且地址必须相当相似,才能正确执行分支。我对操作码的32位值进行了文本搜索,但删除了第一个四位组。这样的话,就可以在前面试图刷写到那个分区的解析器中寻找类似的分支,并确定了一条有效的指令。

火狐截图_2021-08-14T05-31-43.610Z.png

对其他操作码的进一步检查表明,这些操作也极其相似。这意味着,我的引导加载程序缓冲区的溢出内容正在覆盖缓冲器溢出本身。这也意味着引导加载程序是从EFI文件系统中提取并从RAM中执行的。

对所用地址的进一步分析表明,引导加载程序代码在0x101000字节之后被覆盖了,并允许使用从PE可执行文件中提取的代码覆盖整个引导加载程序,即用引导加载程序覆盖其自身。这样做的好处是可以防止随后的崩溃,并可以通过修补引导加载程序本身来修改所需的任何功能,包括引导加载程序的解锁代码。

解锁引导加载程序

找到引导加载程序代码是为了验证解锁工具提供的RSA签名,以便进行解锁。我想跳过这个验证,直接进行解锁。我注意到引导加载程序的解锁功能是通过转移并连接指令(Branch and Link instruction)实现的,它是由编译器为函数调用而生成的,因此,我决定修改最初用于缓冲区溢出的代码,直接跳转到这个指令。我为它生成了正确的相对地址,并使用一个在线ARM64编译器生成了适当的BL指令。然后,我把它添加到引导加载程序中,以实现这个跳转。

火狐截图_2021-08-14T05-32-44.534Z.png

虽然很难调试这个过程,但成功解锁是显而易见的:手机会重新启动,擦除用户数据分区,并引导至解锁状态。这说明该方法的确是有效的,因为设备成功解锁了。

漏洞的影响

现在,我可以在不借助制造商的工具的情况下,对我的旧手机进行刷机,也不必等待七天。然而,试图对较新的设备进行类似的攻击是无效的。因此,我认为它只适用于基于SDM660的设备。

当然,如果对引导加载程序进行额外的修补的话,则可以实现更多的功能,例如为调试目的从设备上转储一些有限的RAM,但是由于引导加载程序可以访问的RAM区域是受限的,所以,这无法实现冷启动攻击。

此外,高通芯片还可以对手机的“userdata”分区进行加密,并且无需用户输入密码或个人识别码(PIN)。所以,这将阻止未签名的Android映像和解锁的引导加载程序直接访问它,这意味着即使执行了此攻击,用户的数据仍将受到保护。此外,在默认情况下,引导加载程序会解锁并擦除此分区。

复现漏洞

为了证实我的理论,即所有SDM660设备都可能出现这种情况,我购买了一部由不同制造商生产的、但具有相同芯片组的手机。这部手机的发布时间比第一部晚了好几年,并且已经完全禁用了引导器解锁功能。

制造商通过类似于第一台设备上发现的引导器解锁的签名保护机制来实现这一点,但没有发布相关的工具。

我使用新设备的OTA映像来提取引导加载程序,具体如前所述。

我试图重现与以前相同的缓冲区溢出,不幸的是,这次并没有成功。相反,通过发送一个更大的有效载荷,新设备确实崩溃了。通过使用与之前相同的二分搜索方法,可以确定这个引导加载程序的内容是在0x403000字节之后被覆盖的,而第一个引导加载程序被覆盖的内容位于第0x101000字节之后。有了这些信息,就可以迅速地开发出一个解锁的引导加载程序。

我找到了一条分支指令,如果签名未通过验证,该指令将跳过解锁过程。

火狐截图_2021-08-14T05-34-16.755Z.png

通过使用一个NOP操作码,这个分支可以被移除,从而在设备上实现引导加载程序解锁。

由于该漏洞可能存在于所有基于SDM660的手机上,因此,我们直接向Qualcomm公司报告了该漏洞。

对于不允许用户进行解锁的情况下,由于无需引导加载程序的访问权限,因此,可以完全禁止针对fastboot的访问,以防止针对它的攻击。然后,可以通过主安卓系统中的工程应用程序重新激活fastboot。实际上,禁止消费者解锁引导器的制造商经常使用这种方法。

绕过Qualcomm公司的用户数据保护

如前所述,Qualcomm公司的芯片支持对“userdata”分区进行加密,这样就可以通过使用内部密钥来保护用户数据,而不需要在启动时输入密码。这个特性的好处在于,可以防止攻击者通过chip-off分析,进而通过未签名的安卓映像访问数据。我想试试是否能绕过这种保护,即通过修改引导加载程序的功能来访问用户数据分区。

我使用Qualcomm公司的源代码来确定如何访问加密密钥和解密分区。我发现,他们是故意不让引导加载程序访问这些密钥的,相反,要想访问密钥,必须使用相应的内部API,而这个API并没有被前面的攻击所修改。实际上,这个API是用来验证引导安卓的映像的,以及验证引导加载程序是否被解锁。

火狐截图_2021-08-14T05-35-02.731Z.png

我注意到,这个函数调用并没有引导相应的映像,这意味着验证引导安卓映像的函数与实际启引导系统的函数是相互独立的。

我决定研究一下fastboot命令“boot”,该命令用于引导并执行从主机上传的Android映像。我注意到,验证映像和开始执行映像的函数,实际上是两个不同的函数。

火狐截图_2021-08-14T05-35-37.364Z.png

然后,我决定看看能否可以修改引导加载程序,以便在这两个调用之间交换已签名和未签名的Android映像。如果成功,我将能够执行一个未签名的Android映像,而无需解锁引导加载程序,从而获得对加密的userdata分区的完全访问权。

修改boot命令

命令“boot”可以通过fastboot的“download:”命令接收一个完整的Android“boot”映像,然后,在RAM中验证并执行该映像。我决定修改这个工具,使其可以接收多个Android映像。

我修改了fastboot工具,使它不再发送一个映像,而是发送:

◼    一个四字节的、相对于未签名映像的偏移量;

◼    一个已签名的映像;

◼    一个经过修改的未签名映像。

完成上述任务之后,我还需要修补引导加载程序,以便使其可以正确使用这个有效载荷。我需要做的第一件事,就是绕过解锁检查。传统上,“boot”命令只在引导加载程序被解锁的情况下,才会引导上传的映像,即使该映像已经签名。我决定在代码中用一个操作码来覆盖这个检查,将新的有效载荷的指针向上移动四个字节——将其指向已签名的Android映像。

火狐截图_2021-08-14T05-36-16.859Z.png

这将导致在代码到达“LoaderImageAndAuth”函数时对已签名的映像进行验证。

在此之后,我需要在这个函数和“bootlinux”函数之间留出足够的空间,以便交换映像。我注意到,在这两者之间调用的函数只是出于内部管理的目的,提供“OK”响应并关闭引导加载程序的某些功能,一旦Android映像启动,这些功能就将被禁用。因此,我决定用交换映像所需的少量操作码来覆盖它们。

火狐截图_2021-08-14T05-37-00.579Z.png

为此,我只需要在这些指令的基础之上另外添加四条即可,这意味着有足够的空间来做我需要的事情。具体来说,这四条指令及其作用如下所示:

◼    将指针移回有效载荷的起点位置:sub x19, x19, 4;

◼    读取偏移值:ldr w22, [x19];

◼    将偏移值添加到映像的指针中:add x19, x19, x22;

◼    将新的指针值压入“Info”结构体的“ImageBuffer”指针中:str x19, [x21,#0xa0]。

这足以让指针从已签名的映像指向未签名的映像,这将有助于从检查时间到使用时间的攻击,并允许在不解锁引导加载程序的情况下运行无签名的映像。

通过锁定的引导器来运行未签名的代码

在许多情况下,我们都希望能在自己的设备上运行未签名的安卓映像,尤其是希望获得Tethered的root权限的时候。通过使用Magisk引导未签名的映像,人们可以随意访问其设备上的全部数据,包括他们的照片、信息和联系人,而不需要备份、删除和恢复这些数据。此外,这将允许研究人员以root权限对其个人设备进行安全研究,然后,通过重启移除权限,再引导至已签名的映像。这种做法的好处在于,几乎没有证据表明他们获得了访问该设备的特权。

这不仅对研究而言很有用,同时,攻击者也可以利用这一点来发动攻击。例如,通过这个漏洞和一个自定义的映像,攻击者就可以访问用户的所有文件,或只是禁用锁屏就能访问用户的应用程序。具有物理访问权限的攻击者也可以获得与用户获得的绑定型root访问权限(tethered root access)相同的访问权限。

最后,安卓允许用户通过开发者选项菜单进一步对手机进行加密。这就增加了一个额外的保护层,要求在引导时输入PIN码或密码,以便解密用户数据分区。这与设备的解锁屏幕不同,后者只在软件层面保护设备。

火狐截图_2021-08-14T05-37-54.616Z.png

虽然这这种方法确实能够保护用户数据,但它无法保护核心的安卓引导映像,一个获得临时物理访问某人的手机的攻击者可以很容易地上传一个后门,一旦用户输入了他们的密码,这个后门就会被激活。虽然这不是一种非常可行的攻击,但的确很有趣。

火狐截图_2021-08-14T05-38-31.171Z.png

小结

在引导加载程序中发现的所有漏洞都已披露给Qualcomm公司,并与进行了协调后才给予披露。虽然这些漏洞确实使SDM660面临风险,但在打补丁之前,我没有发现任何其他Qualcomm Snapdragon芯片是易受攻击的。目前,所有使用该芯片的设备都已部署了补丁,因此,该漏洞已不复存在。

最后,该漏洞被分配的CVE编号为CVE-2021-1931。

本文翻译自:https://www.pentestpartners.com/security-blog/breaking-the-android-bootloader-on-the-qualcomm-snapdragon-660/如若转载,请注明原文地址


文章来源: https://www.4hou.com/posts/5VBq
如有侵权请联系:admin#unsafe.sh