PS:[其中一些加括号()的内容为使文章流畅而补充的连接语及注释]
1:12~实在胃疼的厉害,所以只编辑完一点,算了,明天下午再继续了!
2:16~还好有“胃安宁”!不能断,已经一半了,坚持就是胜利!
2:43~写完了,药劲儿才发挥出来。睡觉!
今天先在此祝大家元宵节快乐!
不足之处,还请多多指出!
国外最新安全推文整理(第19期)最后一篇
Exploiting Qualcomm EDL Programmers
利用高通EDL的程序员
原文链接:https://alephsecurity.com/2018/01/22/qualcomm-edl-1/
原作By:罗伊·海伊 (@roeehay) 和哈达
目前网上存在许多指南,可能分为7部分,(这里我也不太清楚,欢迎大佬指点)在互联网中关于'unbricking'基于高通公司的移动设备。所有这些指南都使用紧急下载模式(EDL),这是Qualcomm Boot ROM(主引导加载程序)的备用引导模式。要使用这种模式,其操作人员只能是掌握了OEM签名的相关程序员,这些程序员似乎可以公开获得各种这样的设备。虽然他们的公开可用性的原因是未知的,但我们最好的猜测可是这些程序员可能是经常从OEM设备维修实验室泄密一些东西。除此之外,其他如一些OEM(如小米)也在官方论坛上发布。
在下面列出了我们设法获得的一些公开可利用程序员(社工)的部分列表:(表格一个一个输入实在费劲,只好以图片形式表示)
在这篇由5部分组成的博客文章中,我们将讨论泄密的程序员所引发的安全影响。第一部分介绍了PBL,EDL,Qualcomm Sahara和程序员的一些内部组成部分,重点介绍了Firehose。在第2部分中,我们将讨论利用EDL程序员功能的基于存储的攻击 - 我们将看到一些具体的例子,例如解锁小米Note 5A(代号ugglite)引导加载程序,以便安装和加载恶意启动映像,的信任。第3部分,第4部分和第5部分致力于我们研究的主要焦点 - 基于内存的攻击。在第3部分中,我们利用Firehose程序员的隐藏功能来执行具有最高特权的代码(EL3),例如,允许我们转储各种SoC的Boot ROM(PBL)。然后,我们介绍我们的exploit框架,firehorse,它为流水线程序员实现了一个运行时调试器(第4部分)。我们以诺基亚6的完整安全启动旁路攻击结束,该攻击MSM8937使用我们的漏洞利用框架。我们在PBL(或更准确地说,在PBL克隆中)实现代码执行,使我们能够击败信任链,在引导加载链的每个部分获得代码执行,包括TrustZone和高级操作系统(Android)本身。
我们的(具体)研究成果如下:
1、我们描述了Qualcomm EDL(Firehose)和撒哈拉协议。(第1部分)
2、我们创建了firehorse,这是一个基于Firehose的程序员公开的研究框架,能够调试/跟踪程序员(以及其他引导加载程序链,包括某些设备上的Boot ROM本身)。(第3部分和第4部分)
3、我们使用Firehose程序员和我们的研究框架获得并逆向设计了各种Qualcomm芯片组的PBL(MSM8994/MSM8917/MSM8937/MSM8953/MSM8974)。(第3部分)
4、我们获得了Nexus 6P(MSM8994)的RPM和调制解调器PBL。(第3部分)
5、我们设法使用基于存储的攻击解锁并根据各种Android Bootloader(例如小米Note 5A)进行解锁。(第2部分)
6、我们成功地对运行Snapdragon 425(MSM8937)的诺基亚6设备发起了端到端的攻击。我们认为这种攻击也适用于诺基亚5,并可能甚至可以扩展到其他设备,虽然未经验证。(第5部分)
高通安全启动
高通MSM设备启动过程的摘要概述如下:
[Primary Bootloader (PBL)]
|
`---NORMAL BOOT---.
[Secondary Bootloader (SBL)]
|-.
| [Android Bootloader (ABOOT)]
| `-.
| [boot.img]
| |-- Linux Kernel
| `-- initramfs
| `-.
| [system.img]
|
`-[TrustZone]
在PBL从ROM-在装置之后是启动的。它很快将数字签名加载SBL到内部存储器(imem),并验证其真实性。我们所有提取的PBL都是32位(运行aarch32),其中SBLs是aarch32或者aarch64,其中PBL负责转换。某些设备具有XBL(可扩展引导加载程序)而不是SBL。所述SBL初始化DDR和负载数字签名的图像,例如ABOOT(它实现fastboot接口)的TrustZone,并再次验证其真实性。签名证书具有锚定在硬件中的根证书。
ABOOT然后验证的真实性boot或recovery图像,加载Linux内核和initramfs从boot或recovery图像。initramfs是一个cpio(gzipped)归档rootfs文件,/在Linux内核初始化期间被加载到(安装在RAM文件系统中)。它包含init二进制文件,第一个用户空间进程。在设备树Blob()中ABOOT准备initramfsLinux内核的内核命令行和参数DTB,然后将执行转移到Android(Linux)内核。
紧急下载模式(EDL)
基于MSM的设备包含一种特殊的操作模式 - 紧急下载模式(EDL)。在这种模式下,设备Qualcomm HS-USB 9008通过USB标识自己。EDL由PBL实施。由于PBL是ROM常驻,所以EDL不能被软件破坏。EDL模式本身实现了Qualcomm撒哈拉协议,该协议通过USB接受OEM数字签名的编程器(ELF最新设备中的二进制文件,MBN旧版设备中的二进制文件),充当SBL。现代这样的程序员实施该Firehose协议,接下来分析。
[Primary Bootloader (PBL)]
|
`---EDL---.
[Programmer (Firehose)]
`- Commands (through USB)
获得EDL访问权限
有几种方法将该设备强制转换为EDL。
许多设备在其电路板上公开所谓的测试点,如果在启动过程中缩短,会导致PBL将其执行转向EDL模式。(使用我们的研究框架,我们设法找出了负责评估这些测试点的PBL的确切位置,但接下来的内容更多。)
例如,以下是我们的小米Note 5A主板上的测试点:
补充:涉及到了安卓硬件类,不太了解
同样的原理,(下图是)诺基亚6:
此外,如果PBL无法验证SBL,或者未能初始化闪存,(那么)它将返回到EDL中,并且再一次,通过使用我们的研究工具,我们在PBL中找到了实现此功能的相关代码部分。如果一些SBL无法验证它们负责加载的图像,它们也可能重新启动EDL。
我们还遇到了在启动时测试USB D+/GND引脚的SBLs(例如:诺基亚6/5和旧小米SBLs),如果这些引脚被缩短,我们会重新启动到EDL。这就是众所周知的EDL或‘深闪’USB电缆。(没有查到相关设备,先直译了) 除此之外的其他设备,如OnePlus系列,在启动时测试硬件密钥组合,以此(来)实现类似的行为(结果)。
重新启动到EDL也可能发生在平台操作系统本身(如果已实施)以及允许adb访问(通过运行)adb reboot edl。我们向一些供应商报告了这种风险,包括OnePlus(CVE-2017-5947)和Google(Nexus 6 / 6P设备) -CVE-2017-13174。Google已在2017年12月的安全子弹入侵中修补CVE-2017-13174。(Nexus 6P需要root访问上下文,请参阅我们的漏洞报告以获取更多详细信息)。使用相同的机制,一些设备(主要是小米的设备)也允许/允许通过发行或通过专有命令重启EDL(即没有sysfsfastbootfastboot oem edlfastboot edloem)。有趣的是,在锁定的Android Bootloader中阻止这些命令有一个积极的趋势。
毋庸置疑,能够使用软件重新启动进入EDL的方式或仅使用此类USB电缆(描述缩短引脚的充电器)可启用危险的攻击媒介,如恶意USB端口(例如充电器)。
PBL内部
为了验证我们的基于经验的知识,我们使用了我们的调试器(第4部分)和IDA,以确定我们提取的PBL中的确切例程(第3部分),它决定了引导模式(正常或EDL)。在此之前,我们对MSM8937/MSM8917PBL做了一些初步分析,以便从高层角度了解其布局。我们相信其他PBLs没有那么不同。
0x100094PBL的重置处理程序(地址)大致如下所示(为便于阅读,省略了一些伪代码)
int init()
{
int (__fastcall *v5)(pbl_struct *); // r1
__mcr(15, 0, 0x100000u, 12, 0, 0);
if ( !(MEMORY[0x1940000] & 1) )
{
if ( !reset_MMU_and_other_stuff() )
infinite_loop();
memzero_some_address();
timer_memory_stuff();
init_pbl_struct();
v4 = 0;
while ( 1 )
{
v5 = *(&initVector + v4);
if ( v5 )
v3 = v5(&pbl);
if ( v3 )
pbl_error_handler("./apps/pbl_mc.c", 516, 66304, v3);
if ( ++v4 >= 0x14 )
{
while (1);
}
}
}
}
该init功能负责以下内容:
它设置VBAR为0x100000
它将堆栈重置为0x205400。
它在我们命名的函数中重置MMU和一些其他系统寄存器reset_MMU_and_other_stuff,位于0x110004。
它认为pbl_struct,这节省了PBL上下文数据,并且在PBL的整个循环周期内都存在。它的一部分接着传递给SBL。
此结构包含以下字段:(所示的符号当然是我们自己的估计。)
00000000 pbl结构;(大小=0xA4,mempdto_26)
00000000
00000000 hwinitfunction输入DCD?
00000004引导模式DCD?
0000000008字段_8 dcd?
00000000C闪存ReadDstStart DCD?
00000010闪存DstEnd DCD?
00000014 FlashReadCCallCallReadSize DCD?
00000018回调18 dcd?
0000001C字段_1Cdcd?
0000000020字段?
00000024 sbl_入DCD?
00000024 sbl_m_flash结构??
00000050回调50 dcd?
00000054字段_54 dcd?
00000058定时器0 dcd?
0000005C timer5c dcd?
00000060 flash_init_timer dcd?
00000064字段_64 dcd?
00000068字段_68 dcd?
0000006C字段_6C DCD?
0000000070 Timer70dcd?
[...]
000000A4 PBL_结构结束
(其中)一些值得注意的字段包括SBL条目之后,将其设置为SBL的入口点,并且pbl2sbl数据(中)它(所)包含传递给即将跳转到SBL的参数(参见下一个)。
它迭代例程列表(init向量),位于0x10CE0C.
ROM:0010CE0C init_vector
ROM:0010CE0C
ROM:0010CE0C
ROM:0010CE10 DCD 0
ROM:0010CE14 DCD pbl_hw_init
ROM:0010CE18 DCD 0
ROM:0010CE1C DCD pbl_initialize_pagetables
ROM:0010CE20 DCD 0
ROM:0010CE24 DCD pbl_stack_init
ROM:0010CE28 DCD 0
ROM:0010CE2C DCD pbl_copy_some_data
ROM:0010CE30 DCD 0
ROM:0010CE34 DCD pbl_sense_jtag_test_points_edl
ROM:0010CE38 DCD 0
ROM:0010CE3C DCD pbl_flash_init
ROM:0010CE40 DCD 0
ROM:0010CE44 DCD pbl_load_elf_sahara_stuff
ROM:0010CE48 DCD 0
ROM:0010CE4C DCD pbl_mc_init5
ROM:0010CE50 DCD 0
ROM:0010CE54 DCD pbl_jmp_to_sbl
ROM:0010CE58 DCD 0
这些例程中的每一个在PBL的操作中都扮演着重要(不可替换)的角色。他们中的一些将(借此)得到我们的报道(从而)贯穿整个系列的博客文章。
基于PBL&EDL模式仲裁的SBL加载
探测是否进入EDL的例程是PBL传感器JTAG测试点EDL:
int __fastcall pbl_sense_jtag_test_points_edl(pbl_struct *pbl)
{
[...]
pbl->bootmode_reason = bootmode_reason_0;
if ( !(MEMORY[0xA601C] & 8) ) // jtag fuse
{
if ( MEMORY[0xA606C] & 0x8000 ) // check test points
{
pbl->bootmode = edl;
pbl->bootmode_reason = tp;
return 0;
}
v4 = MEMORY[0x193D100];
v5 = MEMORY[0x193D100] & 0xF;
switch ( v5 )
{
case 1:
v6 = bootmode_reason_2;
pbl->bootmode = edl;
break;
case 2:
pbl->bootmode = bootmode_80;
pbl->bootmode_reason = bootmode_reason_3;
goto LABEL_17;
case 3:
pbl->bootmode = bootmode_81;
v6 = bootmode_reason_4;
break;
default:
goto LABEL_17;
}
pbl->bootmode_reason = v6;
LABEL_17:
MEMORY[0x193D100] = v4 & 0xFFF0;
if ( pbl->bootmode_reason )
return 0;
}
v2 = (MEMORY[0xA602C] >> 1) & 7;
if ( v2 >= 8 )
pbl_error_handler("./apps/pbl_hw_init.c", 227, 262656, ((MEMORY[0xA602C] >> 1) & 7));
pbl->bootmode = v2;
return 0;
}
通过对此代码的跟踪,我们得出了以下结论:0xA606C包含测试点状态(0x8000<=>‘缩短’)。此外,通过软件重新启动EDL是通过断言0x193D100登记(亦称tcsr-启动-misc-检测 例程设置自举模式字段在PBL上下文中。稍后,PBL实际上将跳过SBL图像加载,进入EDL模式。
首先,pbl将通过设置>闪存结构->初始化=0xA...。这是在里面做的撒哈拉的东西如果PBL->引导模式是EDL,或者闪存初始化失败:
int __fastcall pbl_flash_init(pbl_struct *pbl)
{
[...]
v3 = pbl->bootmode;
if ( v3 == edl )
{
if ( some_sahara_stuff(v1) )
pbl_error_handler("./apps/pbl_flash.c", 134, 66048, v2);
if ( !flashStructLocation )
pbl_error_handler("./apps/pbl_flash.c", 138, 66304, 0);
return 0;
}
[...]
v2 = pbl_flash_sdcc(pbl);
if ( v2 != 3 && some_sahara_stuff(pbl) )
pbl_error_handler("./apps/pbl_flash.c", 134, 66048, v2);
if ( !flashStructLocation )
pbl_error_handler("./apps/pbl_flash.c", 138, 66304, 0);
[...]
return 0;
}
int __fastcall some_sahara_stuff(pbl_struct *pbl)
{
[...]
if ( !pbl )
pbl_error_handler("./apps/pbl_sahara.c", 911, 819200, 0);
if ( !a->flash )
pbl_error_handler("./apps/pbl_sahara.c", 915, 819200, 0);
[...]
pbl->flash->initialized = 0xA;
return 0;
稍后,当PBL实际尝试从闪存驱动器加载SBL时,它将考虑PBL->FLASH->初始化字段并使用撒哈拉协议:
unsigned int __fastcall pbl_jmp_to_sbl(pbl_struct *pbl)
{
pbl_struct *v1; // r4
signed int v2; // r0
v1 = pbl;
if ( !pbl->sbl )
pbl_error_handler("./apps/pbl_mc.c", 420, 98304, 0);
pbl->pbl2sbl_data->timer = get_timer();
sub_10F364();
set_vbar(infinite_loop);
v2 = initialize_secondary_pt_for_sbl(pbl);
if ( v2 )
pbl_error_handler("./apps/pbl_mc.c", 100, v2 | 0x20000, 0);
(pbl->sbl)(pbl->pbl2sbl_data);
return -1;
}
Qualcomm Firehose程序员内部
如上所述,现代EDL程序员实施Qualcomm Firehose协议。分析几个程序员的二进制文件很快就会发现命令通过XML(通过USB)传递。例如,以下XML使程序员刷新一个新的辅助引导加载程序(SBL)映像(也通过USB传输)。
<?xml version="1.0" ?>
<data>
<program SECTOR_SIZE_IN_BYTES="512" file_sector_offset="0" filename="sbl1.bin"
label="sbl1" num_partition_sectors="1024" physical_partition_number="0"
size_in_KB="512.0" sparse="false"
start_byte_hex="0xc082000" start_sector="394256"/>
</data>、
<?xml版本=“1.0”?>
<数据>
<程序 扇区尺寸IN字节="512" 文件扇形偏移量="0" 文件名“sbl1.bin”
标号=“sbl1” num分区扇区="1024" 物理分区数="0"
尺寸_in_KB="512.0" 稀疏=“假”
起始字节十六进制=“0xc082000” 起动扇区="394256"/>
</data>
可以看到,指示程序员刷新新图像的相关标签是program。
挖掘程序员的代码(uggliteaarch32本例中的小米Note 5A程序员)表明它实际上是某种扩展的SBL。其主要程序如下:
int __cdecl SBLMain(pbl2sbl_struct *pbl2sbl_data)
{
sbl_struct *vSbl; // r6
[...]
SBLStart(&off_805C070, vSbl);
sub_801D0B0(off_805C078);
sub_8040A2C(vSbl);
initSBLStruct(&sblStruct, &unk_80678A0);
sub_80408F4(&sblStruct);
[...]
return callImageLoad(&sblStruct, 0x15, imageLoad);
}
pbl2sbl_data是在pbl_jmp_to_sbl函数的最后从PBL传递给SBL的数据。sbl维护SBL上下文数据,其第一个字段指向副本pbl2sbl_data。
ImageLoad是负责加载下一个引导程序的功能,包括ABOOT:
int __fastcall ImageLoad(sbl_struct *sbl, image_load_struct *aImageLoad)
{
[...]
loop_callbacks(sbl, aImageLoad->callbacks);
[...]
if ( imageLoad->field_14 == 1 )
{
v5 = sub_801CCDC();
uartB("Image Load, Start");
v8 = imageLoad->field_C;
if ( v8 == 1 )
{
if ( !boot_pbl_is_flash() )
{
[...]
ERROR("sbl1_sahara.c", 816, 0x1000064);
while ( 1 )
;
}
[...]
boot_elf_loader(v9, &v27);
[...]
loop_callbacks(sbl, imageLoad->callbacks2);
[...]
return result;
}
ImageLoad首先调用(使用loop_callbacks例程)一系列初始化函数:
LOAD:0805C0C8 callbacks DCD nullsub_35+1
LOAD:0805C0CC DCD boot_flash_init+1
LOAD:0805C0D0 DCD sub_801ACB0+1
LOAD:0805C0D4 DCD sub_804031C+1
LOAD:0805C0D8 DCD sub_803FF08+1
LOAD:0805C0DC DCD sub_803FCD0+1
LOAD:0805C0E0 DCD firehose_main+1
LOAD:0805C0E4 DCD sub_8040954+1
LOAD:0805C0E8 DCD clock_init_start+1
LOAD:0805C0EC DCD sub_801B1AC+1
LOAD:0805C0F0 DCD boot_dload+1
firehose_main最终落入主流门环路中,永不返回。有趣的是,在实际的SBL中ugglite,这一系列的初始化回调如下所示:
LOAD:0805B0C8 callbacks DCD nullsub_36+1
LOAD:0805B0CC DCD boot_flash_init+1
LOAD:0805B0D0 DCD sub_8018E8C+1
LOAD:0805B0D4 DCD sub_8039C80+1
LOAD:0805B0D8 DCD sub_803986C+1
LOAD:0805B0DC DCD sub_8039634+1
LOAD:0805B0E0 DCD nullsub_37+1
LOAD:0805B0E4 DCD sub_803A2B8+1
LOAD:0805B0E8 DCD clock_init_start+1
LOAD:0805B0EC DCD sub_8019388+1
LOAD:0805B0F0 DCD boot_dload+1
因此,他们仅在firehose_main回调方面有所不同!
探索Firehose程序员的能力
深入研究兔子洞,分析firehose_main及其后代揭示了所有Firehose接受的XML标签。
void __fastcall firehose_main(sbl_struct *sbl)
{
firehoseInit();
while ( 1 )
{
handle_input();
OUT("logbuf@0x%08X fh@0x%08X");
}
}
int handle_input()
{
[...]
do
{
[...]
result = sub_802C764(&v3, SHIDWORD(v0), v0, SHIDWORD(v0), &v4);
}
while ( !v4 );
if ( !result )
{
v2 = sub_804A55E(&unk_806DCC0, v3);
if ( sub_8049CEC(v2) == 1 )
{
OUT("XML (%d bytes) not validated");
[...]
}
else if ( v4 <= 0x1000 )
{
[...]
do
result = parse_command();
while ( result != 1 );
}
else
{
OUT("XML file cannot fit in buffer");
result = sub_804A55E(&unk_806DCC0, 0);
}
}
return result;
}
int firehose_parse_command()
{
[...]
v1 = get_next(&xmlCommand);
memzero(&v4, 0x200u);
switch ( v1 )
{
case 0:
v2 = 1;
goto LABEL_46;
case 4:
sub_8026D40(&xmlCommand, &v4, 0x200uLL);
if ( get_element_name(&xmlCommand, "data") )
{
v2 = 1;
goto LABEL_46;
}
break;
case 1:
if ( get_element_name(&xmlCommand, "configure") )
{
v2 = sub_80157B4();
goto LABEL_46;
}
if ( get_element_name(&xmlCommand, "program") )
{
v2 = fh_program();
goto LABEL_46;
}
[...]
if ( get_element_name(&xmlCommand, "read") )
{
v2 = fh_read();
goto LABEL_46;
}
if ( get_element_name(&xmlCommand, "getstorageinfo") )
{
v2 = fh_getstorageinfo();
goto LABEL_46;
}
if ( get_element_name(&xmlCommand, "erase") )
{
v2 = fh_erase();
goto LABEL_46;
}
[...]
if ( get_element_name(&xmlCommand, "peek") )
{
v2 = fh_peek();
goto LABEL_46;
}
if ( get_element_name(&xmlCommand, "poke") )
{
v2 = fh_poke();
goto LABEL_46;
}
[...]
}
(对于)即将到来的下一步:攻击Firehose的程序员,
(先)简单地浏览一下这些标记就足以让人们意识到,Firehoss程序员已经远远超出了分区闪烁(循环)的范围。 这些强大功能中的一些功能在接下来的部分中(将会)得到(更为)广泛的介绍。
具体而言,在下一章中,我们将使用并继续这里提出的(相关)研究来开发:
1、基于存储的攻击(利用program/update&read)。这些在第2部分中介绍。
2、基于内存的攻击(利用peek和poke)。这些被覆盖在第3部分,第4部分与第5部分。
未完待续……期待后面的内容。
阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开
发者可享99元/年,续费同价!
最后于 2018-12-25 12:48
被挽梦雪舞编辑
,原因: 重新排版