欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

对Samsung S6 的SBOOT逆向工程 - 第一部分

对Samsung S6 的SBOOT逆向工程 - 第一部分

原文地址:http://blog.quarkslab.com/reverse-engineering-samsung-s6-sboot-part-i.html#id17

相当一部分基于Exynos处理器的智能手机都用了一个名为SBOOT的专有引导加载程序。 比如三星Galaxy S7,Galaxy S6和Galaxy A3,可能还有更多已上市的三星Exynos的智能手机[1]。 我有幸在评估各种TEE实现时逆向了这个bootloader程序的一部分。 本文是一系列关于SBOOT文章的第一篇。 文中回顾一些ARMv8概念,讨论了我所遵循的方法以及我做出的或正确或错误的假设,同时,分析了这个没有文档的专门使用在三星Galaxy S6上的blob。


上下文


最近,我有幸参与到可信执行环境(TEE)的几个实现中全职来排查bug。 作为一个边缘项目,我开始挖掘更多的TEE实现,特别是在我的个人或者工作使用的智能手机上,巧合的是,他们来自同一个软件编辑器,即由Trust共同创办的Trustonic [2] G&D和金雅拓。 我手头上智能手机唯一的共同点就是是它们都是基于Exynos处理器。


Trustonic的TEE,又名<t-base,从Mobicore,G&D的TEE演变而来。 据我所知,在这个或以前的版本的TEE中公开的技术细节很少。 分析它突然变得比我最初想的更有挑战性、更有趣。 让我们聚焦三星Galaxy S6深入调查吧!


虽然在文件系统中识别受信任应用程序是这个挑战最简单的一部分, 但在我分析的Exynos智能手机上寻找这个TEE操作系统无异于海底捞针。事实上,你能在其他一些智能手机(比如基于高通SoC的手机)上找到的存储TEE操作系统镜像的专有分区,在这部手机上却找不到。它极有可能存储在其他地方,可能在bootloader上,这就是为什么我开始逆向SBOOT。本文是叙述我的TEE操作系统之旅系列文章的第一篇。我会把重点放在如何确定三星S6 SBOOT基地址还有把它加载到IDA上。


ARMv8 的概念


在启动IDA之前,让我们回顾下ARMv8的一些基础。这些可能对熟悉ARMv7,刚接触ARMv8的同学们有些用处。需要明确完整的文档的,请参阅ARMv8开发者指南[3]。我不是ARMv8专家,如果你看到任何错误或需要搞清楚的地方,随君评论。


异常级别


ARMv8通过定义异常级别的概念来引入一个新的异常模型。 异常级别决定运行软件木块的权限级别(PL0到PL3)和运行它的处理器模式(非安全和安全)。 ELn的运行对应PLn的权限,n越大,运行的权限就越大。

异常向量表


出现异常时,处理器切换到一个异常向量表,运行相应的程序。 在ARMv8中,每个异常级别都有自己的异常向量表。 对于那些习惯于反向工程ARMv7bootloaders的人,你会注意到它的格式与ARMv7完全不同:

聪明的读者可能已经注意到,在ARMv8上异常向量表占用128(0x80)个字节长,而在ARMv7上每个条目只有4字节宽,并且每个条目保存一系列异常处理指令。 虽然异常向量表的位置由ARMv7上的VTOR(向量表偏移寄存器)确定,但ARMv8使用三个VBAR(基于向量的地址寄存器)VBAR_EL3,VBAR_EL2和VBAR_EL1。 请注意,一定程度上,这些句柄(或表入口)的运行将依赖于:


1.异常类型(SError, FIQ, IRQ 或者 Synchronous)。

2.如果异常异常级别相同,则使用堆栈指针(SP0或SPx)。

3.如果异常的级别较低,则执行下一个较低级别(AArch64或AArch32)的执行状态。


一个运行在特定权限的软件模块可以使用专用指令和在底层异常级别上运行的软件交互。 例如,用户模式下的进程(EL0)通过发出超级用户调用(SVC)来执行内核(EL1)的系统调用,内核可以与管理程序调用(HVC)与管理程序(EL2)交互,或者直接与 执行安全监视器调用(SMC)的安全监视器(EL3)等。这些service的调用生成由一个异常向量表同步句柄来处理的同步异常。


我会在接下来的文章中更多地介绍我对这篇文章的一些见解。 让我们试着把SBOOT加载到IDA Pro,然后逆向它吧。


反编译SBOOT


据我所知,SBOOT用的是未知的特有格式。


在IDA Pro中加载SBOOT


三星Galaxy S6是由1.5GHz 64位八核三星Exynos 7420 的CPU驱动。请记住,ARMv8处理器可以运行AArch32和AArch64架构的应用程序。 因此,可以将SBOOT加载为32位或64位ARM二进制。


我先预设,BootROM不切换到AArch32架构,先将它作为64位二进制文件加载到IDA Pro中,保留默认选项:


处理器类型:小端ARM架构[ARM]

作为64位代码反编译: 是的


许多AArch64位指令被自动识别。 解开反汇编的指令,基本模块是有意义的,我感觉我真的在处理AArch64代码:

确定基地址


我花了几天时间来确定正确的基地址。 鉴于给你直接的解决方案是毫无意义的,我首先详细描述我所有的尝试,直到做出能给我真正的基地址的正确假设。 正如谚语所说:“授人以鱼不如授人以渔“。


谷歌一下,你就知道


我刚开始在网上搜索和三星bootloader SBOOT相关的东西。 不幸的是,这个问题的结果很少,只有reverseengineering.stackexchange.com上一个可追溯到2015年3月的线索[5]和这个相关。


这个线程主要给我了两个提示。 J-Cho直觉上认为,这个bootloader从偏移量为0x3F000处开始,还断定它实际上从0x10开始。


因为我想排除我的假设,bootloader的基地址是0x00000000,并且其代码总是从0x10开始,我开始寻找在其他Exynos智能手机中使用的引导加载程序。 魅族智能手机的SBOOT在0x10没有给出有效的指令,证实了我的怀疑:

我还分析了在其他引导程序是否有遗留的任何调试字符串,能给我提示SBOOT通常在内存中哪里加载。 并没有彩头:(但是我有另一个:在魅族的SBOOT的一些字符串建议使用U-Boot,即使U-Boot不使用三星Galaxy S6,这是一个值得探索的方向,我开始进一步挖掘。


U-Boot仓库


U-Boot是开源的,支持一系列Exynos芯片。 例如,Exynos 4和Exynos 5已经支持了5年多了。 Exynos 7的支持尚未完全登陆,但根据其邮件列表[6],Exynos 7 ESPRESSO的开发板存在一些补丁。


我可能错过了,但是遍历ESPRESSO开发板的补丁一无收获:(我尝试了从Exynos 4到Exynos 7板多个已知的基地址没有成功,是时候从另一个角度尝试了。


ARM字节池


如果您熟悉逆向ARM汇编,您一定注意到了大量使用字节池来保存要加载到寄存器中的某些常量值。 这个特性可以帮助我们查找大约在何处加载SBOOT,特别是当从文字池加载分支目标地址时。


我搜索了IDA Pro在操作数中标记有错误的所有分支指令(红色高亮显示)。 由于bootloader程序的代码是自包含的,我可以明确假定大多数分支目标地址必然命中在bootLoader程序本身的代码。 有了这个假设,我可以估算出bootloader的基地址。

从第一条指令,我注意到以下分支错误:

这些代码片段上的有趣的事实是:

跳转指令BR(跳转到寄存器)是没有条件的,而且它不会return。

两个分支的操作数值相同(0x2104010),并且位于引导加载程序的早期。

最后一个字节是0x10,这似乎也正是bootloader的代码开始的偏移量。


我任意假定地址0x2104010是一个复位地址,我试图在0x2104000处加载SBOOT二进制,具有以下选项:


处理器类型: ARM小端[ARM]

ROM起始地址: 0x2104000

加载地址: 0x2104000

作为64位代码反编译: 是的


至少,IDA Pro找出的错误少了一些,表明我的假设可能是正确的。 当然,我还不能确定这个基地址是正确的,我需要逆向来进一步确定。 画外音:我几乎认定它正确:)


ARM系统寄存器


鉴于我可能要得到潜在的基地址了,我继续逆向SBOOT,苍天保佑代码流中没有异常。


当我想找到TEE OS时,我开始搜索在安全监视器中执行的代码片段。 找到安全监视器的一个相当简单的技术在于寻找设置或读取只能从安全监视器访问的寄存器的指令。 如前所述,安全监视器在EL3中运行。 VBAR_EL3是找到EL3代码的一个很好的选择,因为它保存EL3异常向量表的基地址,而且指向SMC句柄。

查找设置VBAR_EL3的指令

你还记得本文开头介绍的异常向量表格式吗? 它是由16个0x80字节的条目组成,保存异常处理程序的代码。 在搜索结果中,0x2111000的代码似乎指向一个有效的异常向量表:

在0x2111000的异常向量表

即使如此,所选择的基地址仍然不是正确的:(当验证设置VBAR_EL3的其他指令时,可以注意到0x210F000在函数的中间:

异常向量表(不在)在0x210F000

这些异常表明0x2104000不是正确的基地址。 让我们试试别的。


Service描述符


三星Galaxy S6 SBOOT部分基于ARM可信固件[7]。 ARM受信任的固件是开源的,并为ARMv8-A提供了安全领域软件的参考实现,包括一个在异常级别3(EL3)执行的安全监视器。 与安全监视器对应的汇编代码与ARM受信任的固件中的汇编代码完全相同。 这是一个好消息,因为它会给我的逆向工程节省时间和精力。


我试图在反汇编代码中找到另一个锚点以确定SBOOT的基地址。 在结构体中的char *类型的成员特别有趣,因为它们指向在编译时定义其地址的字符串。 在比较SBOOT反汇编代码和ARM可信固件源代码时,我确定了一个结构体,rt_svc_desc_t,它具有我正在寻找的属性:

typedef struct rt_svc_desc {

    uint8_t start_oen;

    uint8_t end_oen;

    uint8_t call_type;

    const char *name;

    rt_svc_init_t init;

    rt_svc_handle_t handle;

} rt_svc_desc_t;

根据ARM Trusted Firmware的源代码,rt_svc_descs是一个rt_svc_desc_t数组,用于保存service导出的运行时service描述符。 它在函数runtime_svc_init中使用,通过调用函数bl31_main中的调试字符串可以轻松地将其在SBOOT中定位:

我试图在不同的地址上映射这个二进制,并检查是否可以找到rt_svc_desc.name条目的有效字符串。 这里是一个短小精悍的脚本:

import sys

import string

import struct

RT_SVC_DESC_FORMAT  = "BBB5xQQQ"

RT_SVC_DESC_SIZE    = struct.calcsize(RT_SVC_DESC_FORMAT)

RT_SVC_DESC_OFFSET  = 0xcb50

RT_SVC_DESC_ENTRIES = (0xcc10 - 0xcb50) / RT_SVC_DESC_SIZE

if len(sys.argv) != 2:

    print("usage: %s <sboot.bin>" % sys.argv[0])

    sys.exit(1)

sboot_file = open(sys.argv[1], "rb")

sboot_data = sboot_file.read()

rt_svc_desc = []

for idx in range(RT_SVC_DESC_ENTRIES):

    start = RT_SVC_DESC_OFFSET + (idx << 5)

    desc = struct.unpack(RT_SVC_DESC_FORMAT,

                         sboot_data[start:start+RT_SVC_DESC_SIZE])

    rt_svc_desc.append(desc)

strlen = lambda x: 1 + strlen(x[1:]) if x and x[0] in string.printable else 0

for base_addr in range(0x2100000, 0x21fffff, 0x1000):

    names = []

    print("[+] testing base address %08x" % base_addr)

    for desc in rt_svc_desc:

        offset = desc[3] - base_addr

        if offset < 0:

            sys.exit(0)

        name_len = strlen(sboot_data[offset:])

        if not name_len:

            break

        names.append(sboot_data[offset:offset+name_len])

    if len(names) == RT_SVC_DESC_ENTRIES:

        print("[!] w00t!!! base address is %08x" % base_addr)

        print("    found names: %s" % ", ".join(names))

在分析的SBOOT上运行此脚本给出以下输出:

$ python bf_sboot.py sboot.bin

[+] testing base address 02100000

[+] testing base address 02101000

[+] testing base address 02102000

[!] w00t!!! base address is 02102000

    found names: mon_smc, std_svc, tbase_dummy_sip_fastcall,

                 tbase_oem_fastcall, tbase_smc, tbase_fastcall

[...]

成功了! 三星Galaxy S6 SBOOT的基地址为0x02102000。 用这个基地址重新加载二进制文件到IDA Pro似乎纠正了所有迄今为止我已经看到的怪异的反汇编代码。 现在我们确定找到了!


反编译强化


逆向工程过程就像解决一个谜题。 一个人试图通过将一些信息放回一起来理解一个软件如何工作。 因此,你拥有的信息越多,解谜就越容易。 这里有一些提示,帮助我迟早找到正确的基地址。


丢失的函数


虽然IDA Pro在反编译常用文件格式方面做得很出色,但是在逆向未知二进制文件时可能会丢失很多函数。 在这种情况下,一个常见的习惯是写一个脚本寻找开始指令,并声明函数存在于这些点。 一个简单的AArch64函数序列看起来像这样:

// AArch64 PCS assigns the frame pointer to x29

sub     sp, sp, #0x10

stp     x29, x30, [sp]

mov     x29, sp

mov x29,sp 指令是AArch64开端的相当可靠的标记。 找到函数的开始的思路是搜索这个标记并且在发现常见的开始指令(例如mov,stp,sub)时向后反向。 在IDA Python中搜索AArch64开始函数看起来像这样:


import idaapi

def find_sig(segment, sig, callback):

    seg = idaapi.get_segm_by_name(segment)

    if not seg:

        return

    ea, maxea = seg.startEA, seg.endEA

    while ea != idaapi.BADADDR:

        ea = idaapi.find_binary(ea, maxea, sig, 16, idaapi.SEARCH_DOWN)

        if ea != idaapi.BADADDR:

            callback(ea)

            ea += 4

def is_prologue_insn(ea):

    idaapi.decode_insn(ea)

    return idaapi.cmd.itype in [idaapi.ARM_stp, idaapi.ARM_mov, idaapi.ARM_sub]

def callback(ea):

    flags = idaapi.getFlags(ea)

    if idaapi.isUnknown(flags):

        while ea != idaapi.BADADDR:

            if is_prologue_insn(ea - 4):

                ea -= 4

            else:

                print("[*] New function discovered at %#lx" % (ea))

                idaapi.add_func(ea, idaapi.BADADDR)

                break

    if idaapi.isData(flags):

        print("[!] %#lx needs manual review" % (ea))

mov_x29_sp = "fd 03 00 91"

find_sig("ROM", mov_x29_sp, callback)

ARM64 IDA Plugins


AArch64 mov 简化


编译器有时会优化代码,使得读取更难。 使用IDA Pro的API,可以编写架构特定的代码简化器。 我发现由@xerub共享的AArch64代码简化器非常有用。 下面是AArch64反汇编的示例:

ROM:0000000002104200                 BL              sub_2104468

ROM:0000000002104204                 MOV             X19, #0x814

ROM:0000000002104208                 MOVK            X19, #0x105C,LSL#16

ROM:000000000210420C                 MOV             X0, X19

@xerub's "AArch64 mov simplifier" [8] changes the disassembly as follows:

ROM:0000000002104200                 BL              sub_2104468

ROM:0000000002104204                 MOVE            X19, #0x105C0814

ROM:000000000210420C                 MOV             X0, X19

聪明的读者可能会注意到,MOVE不是一个有效的ARM64指令。 MOVE只是一个标记,告诉逆向工程师当前的指令已经被简化并被这条指令所取代。


FRIEND插件


逆向工程IDA Pro中的ARM低级代码一直很乏味。 确定与系统控制协处理器相关的指令是一个可怕的经历,因为IDA Pro在没有寄存器别名的情况下反汇编指令。 如果让你选择,你最喜欢读哪一个:

msr vbar_el3, x0

or

msr #6, c12, c0, #0, x0


ARM帮助插件有助于改进IDA Pro的反汇编。 IDA AArch64 Helper插件[9]由Stefan Esser(@ i0n1c)就是一个这样的插件。 不幸的是,它不是公开的。 Alex Hude(@getorix)为MacOS写了一个类似的插件FRIEND [10]。 如果你密切关注这个项目,我最近push了一些修改[11],上周已经合并,来让它跨平台。 现在,您有Windows,Linux和MacOS版本的FRIENDs了 :)

签名


如前所述,SBOOT部分基于ARM可信固件[12]。 由于源代码可用,可以通过浏览源代码,重新编译它并做二进制比较(或签名匹配)来节省大量的逆向工程工作,以便尽可能多地恢复symbol。


我通常结合多个二进制diffing工具来生成二进制之间的符号:


Rizzo [13] from Craig Heffner (devttys0)

Bindiff [14] from Zymanics

Diaphora [15] from Joxean Koret (@matalaz)


它们有时会产生互为补充的结果。


结论


在本文中,我描述了如何为三星Galaxy S6确定SBOOT的基地址,如何加载到IDA Pro。 这里描述的方法应该适用于其他三星的智能手机,可能适用于其他制造商的使用了Exynos SoC的产品。

TEE操作系统之旅将在下一篇文章中继续。 敬请关注!


链接


[1]http://www.samsung.com/semiconductor/minisite/Exynos/w/showcase/smartphones/
[2]https://www.trustonic.com/about-trustonic
[3]http://infocenter.arm.com/help/topic/com.arm.doc.den0024a/DEN0024A_v8_architecture_PG.pdf
[4]http://quoteinvestigator.com/2015/08/28/fish/
[5]http://reverseengineering.stackexchange.com/questions/10995/problem-with-ida-pro-6-8-disassemble-galaxy-s6-sboot
[6]http://lists.denx.de/mailman/listinfo/u-boot
[7]https://github.com/ARM-software/arm-trusted-firmware
[8]https://gist.github.com/xerub/c6936d219db8e6635d25
[9]https://youtu.be/dg6byIiAwtc
[10]https://github.com/alexhude/FRIEND
[11]https://github.com/alexhude/FRIEND/pull/6
[12]https://github.com/ARM-software/arm-trusted-firmware
[13]https://github.com/devttys0/ida/blob/master/plugins/rizzo/rizzo.py
[14]https://www.zynamics.com/software.html
[15]https://github.com/joxeankoret/diaphora


致谢


jb为我们所有的讨论的帮助。

André“sh4ka”Moulu鼓励我写这一系列的文章,描述我的到TEE操作系统之旅。

Quarkslab的同事们对本文的反馈。


注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功