导语:根据我们了解的情况,目前包括三星Galaxy S7,Galaxy S6和Galaxy A3等智能手机的引导加载程序都是基于三星特有的Exynos SBOOT。
根据我们了解的情况,目前包括三星Galaxy S7,Galaxy S6和Galaxy A3等智能手机的引导加载程序都是基于三星特有的Exynos SBOOT,其中,2011年2月,三星正式将自家基于ARM构架处理器品牌命名为Exynos,而SBOOT则是Samsung+bootloader的缩写,即三星特有的bootloader。
bootloader是什么?
简单地说,bootloader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。
为什么要对手机的bootloader进行bootloader逆向工程分析
Android系统基于Linux,所以bootloader部分也是与传统的嵌入式设备上运行的Linux没有什么区别。由于除Google外的大部分Android厂商都没有提供bootloader的源代码,所以分析手机设备的bootloader需要使用逆向工程
的手段,当然由于有了Google官方的开源bootloader代码做参考,能让分析工作轻松不少。
最近我们有机会评估了几款三星手机在可信执行环境TEE中实现逆向工程,引导加载SBOOT的过程。
不过本文的可信执行环境TEE(Trusted Execution Environment)是<t-base,<t-base是Trustonic提供的TEE解决方案,可以嵌入到移动设备的处理器中,供其它服务提供商通过开放的方式访问现有的高级安全特征。Trustonic将可信服务紧密的连接到可信设备上,如下图所示:
本文是关于SBOOT系列的第一篇,文中使用的分析方法为Blob分析(Blob Analysis),Blob分析是对图像中相同像素的连通域进行分析,该连通域称为Blob。Blob分析可为机器视觉应用提供图像中的斑点的数量、位置、形状和方向,还可以提供相关斑点间的拓扑结构。
针对的手机设备主要是三星Galaxy S6,
另外我们还会回顾一些ARMv8概念,以便讨论我们所使用的方法和所做的假设是否正确。
确定三星S6 SBOOT的基地址并将其加载到IDAPro中
在启动IDA Pro(交互式反汇编器专业版Interactive Disassembler Professional)之前,让我们先了解一下ARMv8的一些基本原理。
ARMv8的基本法则
在ARMv8中,取而代之的是4个固定的Exception level。在AArch64下,Exception level决定着执行特权的级别(level of privilege),与ARMv7中定义的privilege levels的方式相似,Exception level决定着privilege level,所以ELn对应PLn。由于Exception level本身就已经包好了privilege的信息,即ELn的privilege特权级别随着n的增大而增大,ELn处的执行对应于特权PLn,并且n越大,执行级别具有的特权越多。
ARMv8的异常级别(EL)
ARMv8是通过定义异常级别的概念引入了一个新的异常模型。异常级别会确定运行软件组件的特权级别(PL0到PL3)和运行软件的处理器模式(非安全和安全)。
ARM中,异常有级别之分,具体如下图所示:
普通的用户程序处于EL0,级别最低
内核处于EL1,HyperV处于EL2,EL1-3属于特权级别。
ARMv8的异常处理
Arm中的异常处理过程与X86比较相似,同样包括硬件自动完成部分和软件部分,同样需要设置中断向量,保存上下文,不同的异常类型的处理方式可能有细微差别。
不过要注意,用户态(EL0)不能处理异常,当异常发生在用户态时,异常级别(EL)会发生切换,默认切换到EL1(内核态)。
异常向量表
Arm64架构中的中断向量表有点特别,包含16个entry,这16个entry分为4组,每组包含4个entry,每组中的4个entry分别对应4种类型的异常:
1.SError 2.FIQ 3.IRQ 4.Synchronous Aborts
这4组的分类根据发生异常时是否发生异常级别切换、和使用的堆栈指针来区别。分别对应于如下4组:
1.异常发生在当前级别且使用SP_EL0(EL0级别对应的堆栈指针),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL0级别对应的SP。 这种情况在Linux内核中未进行实质处理,直接进入bad_mode()流程。
2.异常发生在当前级别且使用SP_ELx(ELx级别对应的堆栈指针,x可能为1、2、3),即发生异常时不发生异常级别切换,可以简单理解为异常发生在内核态(EL1),且使用EL1级别对应的SP。 这是比较常见的场景。
3.异常发生在更低级别且在异常处理时使用AArch64模式。 可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch64执行模式(非AArch32模式)。 这也是比较常见的场景。
4.异常发生在更低级别且在异常处理时使用AArch32模式。 可以简单理解为异常发生在用户态,且进入内核处理异常时,使用的是AArch32执行模式(非AArch64模式)。 这中场景基本未做处理。
运行在特定级别的软件组件可以使用专用指令与在底层异常级别上运行的软件交互。例如,用户模式进程(EL0)通过发出超级用户调用(SVC)来执行由内核(EL1)处理的系统调用,内核可以与管理程序调用(HVC)与管理程序(EL2)交互,或者直接与执行安全监视器调用(SMC)的安全监视器(EL3)等。这些服务调用生成由异常向量表同步处理程序之一处理的同步异常。
把SBOOT加载到IDAPro,并进行逆向工程
据我们所知,三星S6 SBOOT使用的是未记录的专有格式。
将SBOOT加载到IDA Pro
三星GALAXY S6搭配了自家的Exynos 7420八核处理器,主频为2.1GHz,采用64位指令。前面已经说过,ARMv8处理器可以运行为AArch32和AArch64构建的应用程序。因此,可以尝试将SBOOT加载为32位或64位ARM二进制文件。
我们假设BootROM没有切换到AArch32状态,并将其作为64位二进制文件首先加载到IDA Pro中,保留默认选项:
处理器类型:ARM Little Endian [ARM] 是否拆分为64位代码:是
这时许多AArch64指令就会被被自动识别。
怎样确定基地址
我们花了几天时间来确定正确的基地址。
通过在各个搜索引擎中搜索三星bootloader和SBOOT的资料。得出的相关结果很少,只有一份2015年3月发表的一篇有关reverseengineering.stackexchange.com线程的资料。
不过,这个线程主要给了我们2个提示:
1.J-Cho启动加载程序时的启动文件偏移量0x3F000 2.从0x10开始加载
由于我们的假设引导加载程序的基地址是0x00000000,它的代码总是从0x10开始,所以我们开始寻找在其他Exynos智能手机中使用的引导加载程序。魅族的智能手机SBOOT在0x10就没有给出有效的指令,这证实了我们的疑惑:
U-Boot存储库
U-Boot是开源的,支持几个Exynos芯片。例如,已经支持了Exynos 4和Exynos 5 5年多了。目前也已经开始支持Exynos 7了。
ARM文字池
如果你习惯于ARM操作系统的逆向工程,你应该会注意到大量使用文字池来保存要加载到寄存器中的某些常量值。此属性可以帮助我们查找加载SBOOT的位置,特别是当从文字池加载分支目标地址时。
于是,我们搜索到了IDA Pro在操作数中标记有错误的所有分支指令(在图中以红色显示)。由于引导加载程序中包含有代码,我们可以安全地假设大多数分支目标地址必须定位于引导加载程序本身的代码。有了这个假设,我可以大致找到bootloader的基地址。
我们从第一条指令中,发现了以下分支错误:
我们可以从图中这部分代码中,发现一些有趣的事实:
1.以BR开头的分支指属于无条件分支指令且跳转到寄存器后不会返回。
2.两个分支的操作数值相同,都是0x2104010,并且从一开始就位于引导加载程序。
3.最后一个字节是0x10,这正是bootloader的代码开始的偏移量。
在此,我们假定地址0x2104010是一个复位地址,然后尝试加载SBOOT二进制0x2104000,选择以下选项:
处理器类型:ARM Little Endian [ARM] 启动ROM地址:0x2104000 加载地址:0x2104000 是否拆分为64位代码:是
这时,我们会发现IDA Pro发现的错误比刚刚少了很多,这表明我们以上的假设可能是正确的。然而,还不能完全的确定这个基地址是正确的,我们还需要通过逆向工程进一步确定。
ARM系统寄存器
现在我们可能已经找到了潜在的基地址,通过继续逆向工程SBOOT来查看代码流是否异常。
这时我们需要通过搜索在安全监视器中执行的代码片段找到TEE操作系统。
我们只需寻找设置或读取只能从安全监视器访问的寄存器的指令就能找到安全监视器。
如前所述,安全监视器在EL3中运行。 我们可以通过EL3代码找到VBAR_EL3,因为VBAR_EL3保存了EL3异常向量表的基地址,并执行了SMC的处理程序。
你还记得本文开头介绍的异常向量表格式吗?它由16个0x80字节的条目组成,保存了异常处理程序的代码。在搜索结果中,0x2111000处的代码似乎导致一个异常向量表:
即使,所选择的基地址仍然不是正确的:(当验证设置VBAR_EL3的其他指令时,可以注意到0x210F000在函数的中间位置):
这些异常表明0x2104000不是正确的基地址。
服务描述符
三星Galaxy S6 SBOOT部分基于ARM可信固件。 ARM受信任的固件是开源的,并为ARMv8-A提供了安全软件防护,包括在异常级别3(EL3)执行的安全监视器。与安全监视器对应的汇编代码与ARM受信任的固件中的汇编代码完全相同。
这表明我们的逆向工程还能继续进行,接下来,我们试图在反汇编代码中找到另一个锚点,以便让我们来确定SBOOT的基地址。此时,可以看到结构体中的char*可以指向编译时定义其地址的字符串。
通过比较SBOOT反汇编代码和ARM可信固件源代码时,我们确定rt_svc_desc_t的结构具有逆向工程的属性:
根据ARM Trusted Firmware的源代码,rt_svc_descs是一个rt_svc_desc_t数组,用于保存服务导出时的服务描述符。它在函数runtime_svc_init中使用,通过调用函数bl31_main中的调试字符串可以轻松地将其放置在SBOOT中:
如上图所示,我们试图把二进制映射在不同的地址,并检查是否可以找到rt_svc_desc.name条目的有效字符串。下图就是一个处理脚本:
在分析的SBOOT上运行此脚本,可以输出以下内容:
逆向工程终于成功了!三星Galaxy S6 SBOOT的基地址为0x02102000。重新加载二进制到IDA Pro,这个基地址会纠正所有的反汇编代码。
虽然IDA Pro在处理常用文件格式方面做得很出色,但是在反转未知二进制文件时就不那么管用了。在这种情况下,一个常见的做法就是写一个脚本寻找序言指令,并声明函数存在于这些指令中。一个简单的AArch64函数序列看起来像这样:
指令mov x29,sp是AArch64序言的常用标记,搜索这个标记并且在发现公共序言指令(例如mov,stp,sub)时逆向。在IDA Python中搜索AArch64序言的函数看起来像这样:
ARM64 IDA插件
编译器有时会优化代码,这会让我们的搜索更加困难。使用IDA Pro的API,可以编写架构特定的代码简化器。下面是AArch64反汇编的一个样本:
然后进行更改,如下所示:
这时,你可能会注意到,MOVE不是一个有效的ARM64指令。不错,MOVE只是一个标记,告诉逆向工程师当前的指令已经被简化并被这条指令所取代。
结论
在本文中,我们描述了如何确定三星Galaxy S6 SBOOT的基地址为以及如何加载到IDA Pro。根据我们的推测,本文描述的方法应该适用于其他的三星智能手机,并可能适用于其他基于Exynos SoC的实际品牌。
接下来的一篇我们会向你介绍TEE操作系统是如何在三星Galaxy S6 SBOOT的逆向工程中工作的。