推荐语:
这是一篇关于如何绕过安全启动,然后实现破解BootRom的文章。通过这篇文章,可以让你对于ATF、安全启动等有个更深刻的影响哦。
往期经典:
- 万字长文带你搞懂安全启动及ATF
- 硬核!大佬通过Intel CPU的JTAG接口,DUMP微软原始Xbox的加密BootROM。
- 硬的不行来软的,我还破解不了你?看老哥如何使用软件Dump你的BootRom。
- 牛掰!这老哥用显微镜摄取芯片ROM,还原了芯片的二进制固件。
Amlogic S905 系统级芯片是一款专为视频应用设计的 ARM 处理器。
它广泛用于 Android/Kodi 媒体盒子中。该 SoC 实现了 TrustZone 安全扩展,以运行一个可信执行环境(TEE),该环境支持数字版权管理(DRM)和其他安全功能:
该 SoC 包含一个安全启动机制,用于在将受信执行环境(TEE)映像加载到 TrustZone 之前对其进行身份验证。安全启动链的第一个环节是 BootROM 代码,该代码直接存储在芯片中。
本文介绍了如何从基于 Android 的 Inphic Spot i7 设备中的此 SoC 提取 BootROM 代码。
技术文档
Amlogic 在 Hardkernel 的帮助下发布了 S905 数据手册的公开版本。然而,该版本被大量删减,其中大部分关于安全启动或 TrustZone 的内容已被移除。
但我们仍然可以在 Amlogic 和原始设备制造商(OEM)发布的 GPL 源代码包中找到大量技术信息。
例如,我们可以找到 BootROM 代码的一个潜在地址:
#define ROMBOOT_START 0xD9040000
#define ROM_SIZE (64 * 1024)
#define ROMBOOT_END (ROMBOOT_START + ROM_SIZE)
通过 UART 获取 root 访问权限
我们首先从连接串口(或 UART)开始,因为这个接口可以快速轻松地访问引导程序和 Linux 内核上的调试消息和串行控制台。
由于板上有一个带有引脚布局标签的端口接头,因此识别该板上的串口相当简单:
我们将 USB 到 UART 适配器连接到这个端口。一旦 Linux 内核启动过程完成,我们就可以直接访问 root shell。
我们可以开始探索系统(的非安全侧)。例如,我们可以转储分区:
root@p200:/# ls -l /dev/block/platform/d0074000.emmc/
lrwxrwxrwx root root 2015-01-01 00:00 boot -> /dev/block/boot
lrwxrwxrwx root root 2015-01-01 00:00 bootloader -> /dev/block/bootloader
drwxr-xr-x root root 2015-01-01 00:00 by-num
lrwxrwxrwx root root 2015-01-01 00:00 cache -> /dev/block/cache
lrwxrwxrwx root root 2015-01-01 00:00 crypt -> /dev/block/crypt
lrwxrwxrwx root root 2015-01-01 00:00 data -> /dev/block/data
lrwxrwxrwx root root 2015-01-01 00:00 env -> /dev/block/env
lrwxrwxrwx root root 2015-01-01 00:00 instaboot -> /dev/block/instaboot
lrwxrwxrwx root root 2015-01-01 00:00 logo -> /dev/block/logo
lrwxrwxrwx root root 2015-01-01 00:00 misc -> /dev/block/misc
lrwxrwxrwx root root 2015-01-01 00:00 mmcblk0 -> /dev/block/mmcblk0
lrwxrwxrwx root root 2015-01-01 00:00 mmcblk0boot0 -> /dev/block/mmcblk0boot0
lrwxrwxrwx root root 2015-01-01 00:00 mmcblk0boot1 -> /dev/block/mmcblk0boot1
lrwxrwxrwx root root 2015-01-01 00:00 mmcblk0rpmb -> /dev/block/mmcblk0rpmb
lrwxrwxrwx root root 2015-01-01 00:00 recovery -> /dev/block/recovery
lrwxrwxrwx root root 2015-01-01 00:00 reserved -> /dev/block/reserved
lrwxrwxrwx root root 2015-01-01 00:00 rsv -> /dev/block/rsv
lrwxrwxrwx root root 2015-01-01 00:00 system -> /dev/block/system
lrwxrwxrwx root root 2015-01-01 00:00 tee -> /dev/block/tee
虽然 tee 分区(Trusted Execution Environment,受信执行环境)为空,但引导程序分区包含多个引导程序。
但 BootROM 不在其中,因为它存储在 SoC 中,而不是闪存中。
(尝试失败)读取 BootROM
由于我们拥有 root 权限和 BootROM 的潜在内存地址,我们可以尝试直接读取它。
提供的 Android ROM 包含一个方便的 debugfs 接口,用于从用户空间窥探和访问物理内存:
root@p200:/# echo "d0070000" >/sys/kernel/debug/aml_reg/paddr
root@p200:/# cat /sys/kernel/debug/aml_reg/paddr
[0xd0070000] = 0x1000254
这个 aml_reg 驱动程序使用 ioremap 内核函数来为请求的地址设置适当的内核页表映射。
但是,如果我们尝试读取假定的 BootROM 区域:
root@p200:/# echo "d9040000" >/sys/kernel/debug/aml_reg/paddr
root@p200:/# cat /sys/kernel/debug/aml_reg/paddr
[ 376.546491@0] Unhandled fault: synchronous external abort (0x96000010) at 0xffffff80001aa000
[ 376.549396@0] Internal error: : 96000010 [#1] PREEMPT SMP
[ 376.554712@0] Modules linked in: dwc_otg dhd(O) aml_thermal(O) mali(O) aml_nftl_dev(PO)
内核崩溃了。所以要么是 BootROM 地址错误,要么是这个内存区域被设置为安全的。
由于我们没有 BootROM 地址的其他候选者,我们可以说从非安全世界无法访问 BootROM 区域。
进入安全世界
理论上,安全启动链会阻止在安全世界中加载未授权的代码。
在启动的早期阶段,通过 UART 快速检查调试日志表明,这些引导程序基于 ARM Trusted Firmware(ATF)参考实现。
现在我们将探索一些进入安全世界的方法。
U-Boot 引导程序
通过 UART 连接到控制台,我们可以中断 U-Boot 的启动序列以访问提示符。从这里,我们可以运行任意的 U-Boot 命令:
Hit any key to stop autoboot: 0
gxb_p200_v1#help
? - alias for 'help'
aml_sysrecovery- Burning with amlogic format package from partition sysrecovery
amlmmc - AMLMMC sub system
amlnf - aml nand sub-system
amlnf_test- AMLPHYNAND sub-system
autoping- do auto ping test
autoscr - run script from memory
然而,U-Boot 引导程序(在 ATF 设计中称为 BL33)在非安全模式下运行,这可以从 UART 控制台的启动日志中看出:
INFO: BL3-1: Preparing for EL3 exit to normal world
INFO: BL3-1: Next image address = 0x1000000
INFO: BL3-1: Next image spsr = 0x3c9
U-Boot 2015.01-ga9e9562-dirty (May 06 2016 - 03:36:02)
所以在这个点上,我们已经被安全世界锁定在外了。接下来我们尝试其他方法。
SMC 接口
安全世界和非安全世界可以通过 ARM 安全监视器调用(SMC)进行通信。当核心执行 SMC 指令时,它会切换到安全监视器模式(异常级别 EL3)。
在 ATF 设计中,在 EL3 下运行的代码被称为引导程序阶段 3-1(BL31)。我们可以在之前转储的引导程序分区中找到这个映像。此代码对 TrustZone 安全至关重要,因此我们应该对其进行研究。
BL31 映像中的开源 ATF 代码库通过逆向工程促进了分析,因为我们可以快速恢复 ATF 代码结构。
以下是处理来自正常世界的 SMC 中断的已注册服务列表:
sip_svc 服务很有趣,因为它包含 Amlogic 开发的一些自定义函数:
乍一看,hdcp22_sec_read_reg 和 hdcp22_sec_write_reg 函数很有希望,因为它们是用于安全内存的读写原语。但是,它们严格限制了对特定内存范围的访问:
对其他函数的快速(不完整)分析并未在参数清理(任意读写漏洞)方面发现任何明显的缺陷。其中一些函数相当复杂,特别是加密函数,因此我们尚未对其进行任何检查。
如果我们发现这些函数中的某个存在漏洞,我们可能能够从正常世界触发安全世界内存损坏,从而实现特权提升到安全世界。然而,这需要一些专业技能来实际利用这些漏洞。因此,让我们探索另一种攻击向量。
绕过安全启动链
访问安全世界的另一种解决方案是在安全启动链的某个阶段打破/绕过/欺骗/请求安全启动链。安全启动链的一个常见攻击面是下一阶段的加载、解析和认证步骤。
由于BL1代码存储在SoC中,因此我们(目前)无法访问它。但我们拥有之前转储的引导程序分区中的BL2映像。因此,我们将分析BL2用于解析和认证BL31映像的机制,希望能找到有趣的漏洞。
现在,我们将开始一个漫长的逆向工程过程,这个二进制文件没有任何系统调用,只有极少量的字符串来指导我们的工作。幸运的是,BL2映像相当小,只有约40KB。而且我们有一些节省时间的方法:
与BL31一样,BL2映像遵循ATF(Arm Trusted Firmware)代码逻辑,因此逆向工程工作得到了一定程度的简化:我们可以快速找到ATF代码库中定义的主要函数和结构。
另一个逆向工程技巧是通过识别函数访问的内存映射设备来推断它们的作用。这些内存区域的几个地址范围可以在SoC数据手册和GPL源代码中找到。例如,我们可以预期加密函数会访问专用于硬件加密引擎的内存寄存器。
最后,我们不想花时间去逆向开源代码,尤其是加密代码,因为这项任务相当艰巨。而且从开发者的角度来看,这也很复杂,所以我们可以推测他们使用了一个可能是开源的库。因此,我们在BL2代码和几个潜在的开源软件(OSS)库之间寻找函数原型、调用序列和上下文结构初始化的相似性。在我们的案例中,我们很快发现加密代码来自OSS PolarSSL/mbed TLS项目。
分析BL2认证例程
一旦BL2从NAND加载了BL3映像,就会解析其头部。我们目前还没有关于头部结构的任何信息(ATF代码中不存在),但我们可以注意到它以一个常量魔术值"@AML"开头。这有助于我们在BL2二进制文件中快速定位解析代码。
我们结合“猜测”(即在十六进制编辑器中查看BL31头部)和对BL2解析代码的逆向工程,来找出头部结构的一些成员:
struct aml_img_header {
unsigned char magic[4];// "@AML"
uint32_t total_len;
uint8_t header_len;
uint8_t unk_x9;
uint8_t unk_xA;
uint8_t unk_xB;
uint32_t unk_xC;
uint32_t sig_type;
uint32_t sig_offset;
uint32_t sig_size;
uint32_t data_offset;
uint32_t unk_x20;
uint32_t cert_offset;
uint32_t cert_size;
uint32_t data_len;
uint32_t unk_x30;
uint32_t code_offset;
uint32_t code_len;
uint32_t unk_x3C;
} aml_img_header_t;
头部表明映像被分为4个部分:
- 头部:始终为64字节
- 签名:RSA-1024、RSA-2048、RSA-4096或SHA-256
- 证书:x509证书
- 代码:有效载荷
在我们的目标设备上,BL31映像的签名类型(头部中的sig_type)是SHA-256。这很有趣,因为仅SHA-256哈希本身并不足以提供认证。
以下是BL2中认证例程的简化算法伪代码:
int auth_image(aml_img_header_t *img){
validate_header(img); // checks on magic value & header length
hash = hash_sha256(img);// hash whole image except signature
if(img->sig_type == RSA) {
return check_rsa_signature(img, hash)
}else{
return memcmp(hash, (char*)img + (img->sig_offset));
}
}
我们可以确认,SHA-256选项仅会对加载的映像进行哈希处理,并将结果与同一映像中预计算的哈希值进行比较。我们本可以设想一个更复杂的解决方案,如HMAC,但实际上在这种情况下,仅检查了完整性,而没有进行认证。
即使加载的映像使用RSA签名,我们仍然可以将签名类型更改为SHA-256并重新生成正确的哈希值。
如果签名类型由eFuse强制执行,则可以避免此问题。
这意味着我们可以轻松地修改BL31映像,这是TrustZone中最具特权的代码。
自定义BL31映像
在前面的部分中,我们描述了SMC函数hdcp22_sec_read_reg,该函数可以从普通世界读取安全内存的受限范围。
现在是时候练习我们的NOP技术(NOP是一种汇编语言指令,代表“无操作”,用于填充代码空间或延迟执行),以摆脱这些限制,从而完全访问安全内存。
我们还需要扩展页表,因为BootROM内存区域没有映射。MMU(内存管理单元)初始化是在ATF代码库中实现的,因此再次在BL31二进制文件中很容易找到并分析。
默认情况下,以下内存区域被映射:
我们将其中一个的大小扩展以覆盖BootROM区域:
映射区域0xD9000000的新大小为0x80000,因此它包括了BootROM区域0xD9040000-0xD9050000。
我们几乎完成了对BL31的修改:我们还需要更新SHA-256哈希值。
aml_bootloader_tool:解析并重新计算Amlogic引导加载程序的SHA-256
此工具可以解析并重新生成引导加载程序分区中包含的引导加载程序的SHA-256。源代码在GitHub上。
每个引导加载程序都通过UUID进行标识,这些UUID在ATF源代码中定义。在我们的案例中,BL31映像是第2个条目:
$ ./aml_bootloader_tool ./dump/bootloader.img H 2
fip_toc_header.name: aa640001
fip_toc_header.serial_number: 12345678
fip_toc_header.flags: 0
TOC ENTRY #2
fip_toc_entry.uuid: 47D4086D4CFE98469B952950CBBD5A00
fip_toc_entry.offset_address: 14000 (absolute: 0x20000)
fip_toc_entry.size: 0x11130
fip_toc_entry.flags: 0x0
magic[@0x0]: @AML
total_len[@0x4]: 0x11130
header_len[@0x8]: 0x40
unk_xC[@0xC]: 0x5eec9094
sig_type[@0x10]: 0x0
sig_offset[@0x14]: 0x40
sig_len[@0x18]: 0x20
data_offset[@0x1c]: 0x60
unk_x20[@0x20]: 0x0
cert_offset[@0x24]: 0x60
cert_len: 0x0
data_len: 0x110d0
unk_x30[@0x30]: 0x0
code_offset[@0x34]: 0x60
code_len[@0x38]: 0x110d0
unk_x3C[@0x3C]: 0x0
signature: 263BEFAFC5A051C550D31791EC1212576BE65DB8AD365074560F0BABC076D3CA
computed_sha256: 35AD6B284EE2D6B5672DD0958592028D5BF455A6DCD1EB086D8336FB86533853
BL31映像的哈希值已更新,我们现在可以在设备上重新刷新转储:
$ dd if=./bootloader.img of=/dev/block/bootloader
最后,我们重启设备以在TrustZone中加载我们自定义的BL31映像。
转储BootROM
SMC系统调用只能从EL1(执行级别1)及以上级别调用。因此,我们创建了一个简单的内核模块,该模块将在EL3级别执行SMC调用到我们修改后的函数hdcp22_sec_read_reg。
这个快速而“肮脏”的hack是基于Amlogic的debugfs驱动程序reg_access。源代码在GitHub上。
一旦加载,为了启动SMC调用,我们将参数写入文件/sys/kernel/debug/aml_smc/smc。第一个参数是被调用的SMC函数的ID(在hdcp22_sec_read_reg的情况下为0x82000018)。第二个参数(对于这个特定的SMC ID)是读取的内存地址。结果DWORD(双字)直接打印在内核日志中(我们说它“肮脏”)。
$ insmod ./smc_access.ko
$ echo 82000018 D9040000 > /sys/kernel/debug/aml_smc/smc
[ 219.092948@0] smc_access: SMC call 82000018 returns: aa1f03e0
结果aa1f03e0很有希望,它对应于ARM指令:MOV X0, XZR
为了自动提取整个BootROM内存区域,我们创建了一个简单的脚本:
$ seq -f %1.f 0xD9040000 0x4 0xD9050000 | xargs printf "echo \"82000018 %x\" > /sys/kernel/debug/aml_smc/smc\n"
echo "82000018 d9040000" > /sys/kernel/debug/aml_smc/smc
echo "82000018 d9040004" > /sys/kernel/debug/aml_smc/smc
echo "82000018 d9040008" > /sys/kernel/debug/aml_smc/smc
[...]
echo "82000018 d904fff8" > /sys/kernel/debug/aml_smc/smc
echo "82000018 d904fffc" > /sys/kernel/debug/aml_smc/smc
最后,我们将所有这些DWORD(双字)合并到一个名为bootrom.bin的文件中。
$ ls -l ./bootrom.bin
-rw-r--r-- 1 user user 65537 juil. 8 12:43 ./bootrom.bin
$ sha1sum bootrom.bin
bff0c7fb88b4f03e732dc7a4ce504d748d0d47dd bootrom.bin
$ strings bootrom.bin |tail -22
BL1:
FEAT
READ
EMMC
NAND
LOOP
auth failed, reboot...
08dafda0fd31778
glacier.amlogic
qian
04/14/15_14:23:08
gcc version 4.8
08dafda0fd31778
boot@USB
boot@SDC
BAD PASSWORD
!!!!
vRQ>
8STs
LwH'
Err:sha
0!0
结论
S905 SoC提供了支持安全启动的硬件特性,但OEM(原始设备制造商)仍然可以选择是否启用它。但即使在强制执行安全启动的情况下,Amlogic当前版本的BL2中的一个缺陷仍然可以绕过它。因此,可信执行环境(TEE)并不可信。好消息是,与BootROM不同,BL2可以进行修补。
我要感谢@Karnalzi的帮助!
披露时间表
-
2016-08-08:发现漏洞
-
2016-08-08:通过电子邮件联系Amlogic和一些受影响的OEM以查找安全证明概念(PoC)
-
2016-08-10:OEM #1回复说“Amlogic不提供任何直接联系”
-
2016-08-20:第二次尝试通过电子邮件联系Amlogic
-
2016-09-05:与Amlogic共享漏洞报告
-
2016-09-13:请求状态更新
-
2016-09-25:请求状态更新
-
2016-10-05:公开披露