作者 | 绿盟科技格物实验室 李东宏
高通公司IPQ40xx芯片系列,包括IPQ4018,IPQ4019,IPQ4028和IPQ4029,是面向消费者和企业网络产品流行的片上系统(SoC)解决方案。ASUS RT-AC58U,Cisco MerakiMR33和Aruba AP-365等许多设备在设计中都使用IPQ40xx芯片作为主要的片上系统(SoC)。高通安全执行环境包含安全引导和可信执行环境(TEE),后面简称QSEE。在许多基于Qualcomm的设备(例如手机)上都可以找到此受信任的执行环境。
2021年1月高通在其官网发布漏洞通告,QSEE中存在最高权限的代码执行漏洞,任何攻击者能够通过安全监视调用(SMC)利用这些漏洞,相关信息如下:
• CVE-2020-11256:tzbsp_blow_fuses_and_reset任意代码执行。
• CVE-2020-11257:usb_calib任意代码执行。
• CVE-2020-11258:tzbsp_version_set任意代码执行。
• CVE-2020-11259:tzbsp_version_get任意代码执行。
以 Linksys的EA8300为例进行说明,硬件黑客攻击通常始于打开设备,之后是确定串行接口,希望可以提供一个(root)shell。在印刷电路板(PCB)上清楚标记串行接口的情况并不少见,Linksys EA8300将串行接口暴露在未填充的连接器J3上,如下图所示。
使用串行接口进行连接,上电后可以在控制台中看到如下信息,由高通公司开发的PBL和SBL1引导程序完成的,可能是作为IPQ40xx SDK的一部分提供给OEM(即Linksys),关键的地方以红色标注。
+ Format: Log Type – Time(microsec) -Message – Optional Info
+ Log Type: B – Since Boot(Power OnReset), D – Delta, S – Statistic
– S -QC_IMAGE_VERSION_STRING=BOOT.BF.3.1.1-00108
+ S – IMAGE_VARIANT_STRING=DAACANAZA
+ S – OEM_IMAGE_VERSION_STRING=CRM
+ S – Boot Config, 0x00000025
+ S – Reset status Config, 0x00000010
+ S – Core 0 Frequency, 0 MHz
– B – 261 – PBL, Start
+ B – 1339 – bootable_media_detect_entry, Start
+ B – 2612 – bootable_media_detect_success,Start
+ B – 2626 – elf_loader_entry, Start
+ B – 4036 – auth_hash_seg_entry, Start
+ B – 6190 – auth_hash_seg_exit, Start
+ B – 74253 – elf_segs_hash_verify_entry, Start
– B – 196174 – PBL, End
– B – 196198 – SBL1, Start
+ B – 288239 – pm_device_init, Start
+ D – 7 – pm_device_init, Delta
+ B – 289733 – boot_flash_init, Start
+ D – 87192 – boot_flash_init, Delta
+ B – 381232 – boot_config_data_table_init, Start
+ D – 13975 – boot_config_data_table_init, Delta – (419 Bytes)
+ B – 397970 – clock_init, Start
+ D – 7587 – clock_init, Delta
+ B – 408990 – CDT version:2,Platform ID:8,Major ID:1,Minor ID:0,Subtype:6
+ B – 412402 – sbl1_ddr_set_params, Start
+ B – 417495 – cpr_init, Start
+ D – 2 – cpr_init, Delta
+ B – 421878 – Pre_DDR_clock_init, Start
+ D – 4 – Pre_DDR_clock_init, Delta
+ D – 13170 – sbl1_ddr_set_params, Delta
+ B – 435181 – pm_driver_init, Start
+ D – 2 – pm_driver_init, Delta
+ B – 504960 – sbl1_wait_for_ddr_training, Start
+ D – 28 – sbl1_wait_for_ddr_training, Delta
+ B – 520319 – Image Load, Start
+ D – 143867 – QSEE Image Loaded, Delta – (269176 Bytes)
+ B – 664611 – Image Load, Start
+ D – 2116 – SEC Image Loaded, Delta – (2048Bytes)
+ B – 674745 – Image Load, Start
+ D – 187171 – APPSBL Image Loaded, Delta – (444263 Bytes)
– B – 862309 – QSEE Execution, Start
+ D – 56 – QSEE Execution, Delta
– B – 868531 – SBL1, End
+ D – 674334 – SBL1, Delta
+ S – Flash Throughput, 2087 KB/s (715906 Bytes, 342873 us)
+ S – DDR Frequency, 672 MHz
+ U-Boot 2012.07 [Chaos Calmer15.05.1,r35193] (Nov 02 2017 – 16:33:09)
+
– CBT U-Boot ver: 1.2.9
+
+ smem ram ptable found: ver: 1 len: 3
+ DRAM: 256 MiB
+ machid : 0x8010006
+ NAND: ID = 9590daef
+ Vendor = ef
+ Device = da
+ ONFI device found
+ SF NAND unsupported id:ff:ff:ff:ffSF:Unsupported manufacturer ff
+ ipq_spi: SPI Flash not found(bus/cs/speed/mode) = (0/0/48000000/0)
+ 256 MiB
+ MMC: qca_mmc: 0
+ PCI0 Link Intialized
+ In: serial
+ Out: serial
+ Err: serial
+ machid: 8010006
+ flash_type: 2
+ Net: MAC0 addr:0:3:7f:ba:db:ad
+ PHY ID1: 0x4d
+ PHY ID2: 0xd0b1
+ ipq40xx_ess_sw_init done
+ eth0
+
+ Updating boot_count … done
+
– Hit any key to stop autoboot: 0
– (IPQ40xx) #
+ (IPQ40xx) # smeminfo
+ flash_type: 0x2
+ flash_index: 0x0
+ flash_chip_select: 0x0
+ flash_block_size: 0x20000
+ flash_density: 0x100000
+ partition table offset 0x0
+ No.:Name Attributes Start Size
+ 0: 0:SBL1 0x0000ffff 0x0 0x100000
+ 1: 0:MIBIB 0x0000ffff 0x100000 0x100000
- 2: 0:QSEE 0x0000ffff 0x200000 0x100000
+ 3: 0:CDT 0x0000ffff 0x300000 0x80000
+ 4: 0:APPSBLENV 0x0000ffff 0x380000 0x80000
+ 5: 0:ART 0x0000ffff 0x400000 0x80000
+ 6: 0:APPSBL 0x0000ffff 0x480000 0x200000
+ 7: u_env 0x0000ffff 0x680000 0x80000
+ 8: s_env 0x0000ffff 0x700000 0x40000
+ 9: devinfo 0x0000ffff 0x740000 0x40000
+ 10: kernel 0x0000ffff 0x780000 0x5800000
+ 11: rootfs 0x0000ffff 0xa80000 0x5500000
+ 12: alt_kernel 0x0000ffff 0x5f80000 0x5800000
+ 13: alt_rootfs 0x0000ffff 0x6280000 0x5500000
+ 14: sysdiag 0x0000ffff 0xb780000 0x100000
+ 15: syscfg 0x0000ffff 0xb880000 0x4680000
+ (IPQ40xx) #
首先启动基于python的TFTP服务器ptftpd。
$ ptftpd -p 6969 eth0 /home/raelize/Desktop
(IPQ40xx) # setenv serverip 192.168.1.130
(IPQ40xx) # nand read 0x890000000x200000 0x100000
(IPQ40xx) # tftpput 0x89000000 0x100000QSEE.bin
QSEE分区实际上是一个可以直接分析的二进制文件。不幸的是其中没有元数据可以显示它的结构。但是根据IPQ4019SoC使用ARMv7架构推测里面应该有ARM AArch32小端(LE)代码。将QSEE二进制文件加载到IDAPro中,并选择ARM32小端架构。通过分析代码中全局变量的使用,很快可以确定QSEE二进制文件的加载地址是0x87E80000。
ARMv7异常向量位于QSEE二进制文件的开头,用于处理处理器的异常,包括在执行SMC指令时引起的异常。异常向量是标准化的,因此可以自己简单地定义正确的名称。
通过软件异常处理程序可以很容易地识别出负责处理SMC指令的代码。这段代码从寄存器r0中提取SMC ID,以确定应该调用哪个SMC处理程序,所有SMC处理程序都定义在地址为0x87EB465C的表中。
通过唯一的SMC ID可以调用SMC处理程序,这个ID也在表中显示。例如,可以使用SMC ID 0x805调用SMC处理程序tzbsp_pil_init_image_ns。该表还包含对代码进行逆向分析时的其他有用信息,如SMC处理程序的名称。
当TEE初始化时,硬件控制器会将内存划分为安全内存和非安全内存两部分,这个步骤在引导期间由SBL1引导加载程序完成的。所有与QSEE相关的代码和数据,包括任何受信任的应用程序(TA),都应该存储在安全内存中。换句话说,REE(Rich Execution Environment)不应该访问QSEE使用的任何代码和数据。
REE通过寄存器传递SMC处理程序的参数。例如,ARG1存储在寄存器R1中,ARG2存储在R2中,以此类推。缓冲区是通过引用来传递的,使用的内存可以被REE和TEE访问,通常这是不安全的内存。由于QSEE不知道REE的虚拟映射,因此REE传递的所有指针都应该指向物理内存。
QSEE主要负责检查从REE接收到的参数。QSEE应该检查REE传递的缓冲区(由指针和size这2个参数描述)是否在安全内存中。在分析SMC处理程序时已经确定了负责执行范围检查的函数。
函数命名是经过分析后完成的,函数tzbsp_is_nsec_range使用is_allowed_range函数验证REE传递的缓冲区。is_allowed_range函数使用一个具有安全范围的表来确定哪些内存应该被认为是安全内存,同时会检查缓冲区的开始(即指针)和结束(即指针+大小)是否与安全内存重叠。
该表定义了三个安全范围,如下所示。
无论何时使用tzbsp_is_nsec_range函数来清理SMC处理程序的参数,REE传递的缓冲区都不能与0x0到0x7FFFFFFF、0x90000000到0xFFFFFFFF和0x87E80000到0x87FFFFFF这三个范围重叠。也就是说,只有当缓冲区在0x80000000到0x87E80000和0x88000000到0x90000000之间时,REE才认为是一个合法的缓冲区范围。
目前已经发现了4个易受攻击的代码路径,其中传递给SMC处理程序的参数没有经过正确的“消毒”。范围检查要么根本没有使用,要么使用不正确。
CVE-2020-11256tzbsp_blow_fuses_and_reset
名为tzbsp_blow_fuses_and_reset()的SMC处理程序需要两个参数,即arg1和arg2。程序的反编译代码如下图所示。
参数arg1是一个输入指针,可以使用函数is_allowed_range() 对其进行处理。参数arg2是一个输出指针,只会检查它是否为NULL。通过构造arg2的值可以将一个数值写到任意的内存地址上,写入的值是1或2,这取决于arg1是否为空。
该漏洞允许攻击者将1或2写入QSEE可访问的任意地址,包括QSEE内存,即使不可能写任意的内存,这类受限写入也可能会导致任意的QSEE代码执行(即损害TEE)。
CVE-2020-11257usb_calib
名为usb_calib()的SMC处理程序需要一个参数,即arg1。反编译代码如下所示。
参数arg1是一个未经初始化的输出指针。这意味着不会检查arg1是否指向REE内存。为了将存储在0x580e0的值写入传递的内存地址,对于目标设备,这个值可以被设置为0x787。
此漏洞允许攻击者将0x787写入QSEE可访问的任意地址,包括QSEE内存。即使不可能写入任意值,这类受限写入也可能会导致任意QSEE代码执行(即损害TEE)。
CVE-2020-11258tzbsp_version_set
SMC处理程序tzbsp_version_set() 需要四个参数,即arg1、arg2、arg3和arg4。反编译代码如下所示。
四个参数都被传递给一个函数(即sub_87E90564),该函数根据arg1、arg2和arg4的值返回不同的值。参数arg3是一个输出指针,代码不能保证它只指向REE内存。为了将sub_87E90564的返回值(即至少0,5和0xfffffffc)或0x7fffffff写入传入的任意内存地址,需要对arg3进行构造。
该漏洞允许攻击者将各种值写入QSEE可访问的任意地址,包括QSEE内存。即使不可能写入任意值,这类受限写入可能会导致任意QSEE代码执行(即损害TEE)。
CVE-2020-11259tzbsp_version_get
SMC处理程序tzbsp_version_set() 需以下几个参数:arg1、arg2和arg3。这段代码的反编译如下所示。
参数arg1用作输入,可以用来影响写入输出指针arg2的值。参数arg2和arg3都是输出指针,通过代码无法确定他们是否指向REE内存区域。
为了将sub_87E904CE()或sub_87E90370() | 0xf0000的返回值写入任意内存地址,可以对arg2进行构造。这包括不同的值,比如0x0、0x000f0000和0xfffffffc。而且,也可以构造arg3,以便向传递的内存地址写入0x0。
此漏洞允许攻击者将各种值写入QSEE可访问的任意地址,包括QSEE内存。即使不可能写入任意值,这类受限写入可能会导致任意QSEE代码执行(即损害TEE)。
上述漏洞要求能够直接或间接地向QSEE发出SMC请求。“直接”可以通过执行REE中任何具有执行SMC指令权限的代码来实现(例如内核权限或更高权限)。“间接”可以通过利用设备上已经存在的功能(如驱动程序)来实现。
换句话说,一个控制Linux内核或U-Boot的攻击者,能够直接利用漏洞。攻击者拥有用户特权是不够的,甚至root特权也不允许执行SMC指令。尽管如此,具有root特权的攻击者可能有能力加载内核模块或通过/dev/(k)mem注入代码。
重要的是,非特权攻击者仍然能够利用与QSEE通信的功能,发出SMC调用。这些类型的驱动程序通常可以用于非特权应用程序。漏洞是否可利用,实际上取决于攻击者对SMC参数的控制。
攻击者可以利用这些漏洞执行如下内容:
1.获得对底层硬件的无限制访问。
2.完全控制QSEE及其保护的资产。
3.在REE中升级特权(例如从用户到内核)。
4.绕过由QSEE实现的任何安全特性(如IPS、AV)。
LinksysEA8300在运行时没有使用QSEE做任何相关的事情。不存在受信任的应用程序(TA)。这意味着来自无特权应用程序的攻击面可能很小。
最大的风险是已经完全控制了REE的远程攻击者(即恶意软件)可能会将其代码的执行隐藏在安全的位置,这意味着REE将无法检测设备是否被破坏。假设安全引导是安全实现的,那么首先需要利用REE,然后才能利用QSEE漏洞。尽管存在严格的限制,但是在某些条件下肯定是有可能的。
在产品开发的过程中,针对信任链需要充分考虑数据以及代码的安全验证逻辑,在定义函数时需要全面考虑参数的输入情况,对函数的输入参数做有效性验证,避免出现因为野指针的使用导致的拒绝服务或者远程代码执行安全问题。信任区域一定要处在安全可控的范围内。
参考链接:
https://raelize.com/blog/qualcomm-ipq40xx-analysis-of-critical-qsee-vulnerabilities/#cve-2020-11256-tzbsp_blow_fuses_and_reset
转载请注明来源:关键基础设施安全应急响应中心