原文:hexdetective

作者:Nitay Artenstein (@nitayart) and Gilad Goldman (@gnull00)

译:Holic (知道创宇404安全实验室)

三星的 Android 安全引导程序(S-Boot)是其信任链概念的核心。 攻击者利用 S-Boot 可能会加载不受信任的内核和系统映像这点,以此绕过大多数手机的安全机制。

这是众所周知的攻击思路。它经常被 Android rooting 和 mod 社区使用,而我猜它大概更受执法和政府机构喜爱。

更有意思的是,S-boot 启动时存在几个内存破坏漏洞,其中一个可用于在引导加载程序中实现完整的代码执行。

我们目前在 Exynos 芯片上确认存在该漏洞。貌似在三星S5,S6,S7 上有 90% 使用了 Exynos 的 ROM。最新的 S7 的 ROM 似乎修复了这个漏洞,而我们会在这几天内确认这一点。

漏洞覆盖面很大,那么我们将把它分成两篇文章。在本文中,我们将重点介绍一些 S-Boot 内部机制,然后探索加载程序的攻击面并获得基本的调试功能。我们以发现一个有趣的攻击面作为结束。在下一篇文章中,我们将公开真正的漏洞并利用它得以在 S-boot 中代码执行。

我们不会深入太多逆向 S-Boot 的基础知识,比如 IDA 加载或查找基址。Fernand Lone Sang (@_kamino_) 会放出一篇关于本文的精彩文章,当它发表之时,我会提供链接。如果你需要其他帮助,私信我即可,我很乐意提供帮助。

理解 S-Boot

三星的 Android 启动过程开始于 Boot ROM 中运行的代码,使用了 OEM 公钥(在三星设备中被称为三星安全启动密钥(SSBK))验证下一阶段引导加载程序的完整性。然后它将两个独立进程加载到内存中:一个是 S-Boot 本身,另一个是在 TrustZone TEE (可信执行环境)中运行的 “安全世界”。

这两个进程协同工作。TEE 操作系统,在Exynos 处理器的情况下对应 Trustonic(之前的 MoboCore),由 S-Boot 调用,以验证镜像在加载之前的签名。因此 S-Boot 或 TEE 中的任何一个都可能成为整个系统的潜在风险。

S-Boot本身分为两部分:第一阶段 Bootloader BL1 由 Boot ROM 调用并初始化低级系统原语。BL1 在验证其签名后跳入 BL2,这是自身已经是最小的操作系统,完成了对 USB,显示器和 I/O 的驱动支持。

既然我们有兴趣找到 bug,那么让我们破坏启动的引导过程吧,我们决定找到它尽可能接近实际的内核启动方式。这是因为我们知道已经有了一个初始化的系统,,进一步的操作,比如磁盘 I/O - 我们需要做的是刷入自定义的镜像 - 做一点微小的工作。因此,我们决定在此阶段跳入 BL2 并忽略 BL1 (虽然我们确定此后会破坏这一阶段)。

此阶段下,没有任何调试功能,只是 sboot.bin 与三星 Exynos 标准镜像合并在了一起。那么我们在 IDA 中打开它,对 BL2 进行研究。

A typical function in BL2. Notice the quantity of strings

其实挺简单的:知道了 BL1 主要负责底层初始化,而 BL2 几乎是一个全功能的操作系统,我们可以由此得出结论,属于 BL2 的功能肯定更多,而且具有更多的调试字符串和其他函数引用。一旦我们确定 BL2 的位置,我们使用经典的逆向技巧即可确定内存中映像的基址。

在更高的层次上,BL2 有几个有趣的任务,包括且不仅限于:

  1. 启动内核
  2. 闪存刷入新的固件镜像
  3. 在固件更新期间显示基本的用户界面
  4. 调试(运气好的话)

BootLoader 阶段,加载新固件映像的机制通常是最好的攻击面,因为它涉及到与攻击者的输入直接交互以及各种复杂的逻辑。那么下面就是我们首先看到的。

Into Odin

对三星 Android 手机有过研究的人都知道 Odin,这是个可敬但有些笨重的软件,它的功能是将固件 ROM 刷入设备存储器中。

在设备端,刷入新的固件要先把手机切换到下载模式,这个在 S-Boot 中得以实现,然后通过 USB 连接到运行 Odin 客户端的主机。然后 Odin 客户端将选中的固件镜像发送到设备上运行的 Odin 服务器。当然,你不能刷入任何镜像,在有锁的三星设备上,BootLoader 将拒绝未签名的三星固件。

Download mode. Locked bootloaders reject unsigned images

在 BootLoader 一端,Odin 引导 BootLoader使用了相当全面的协议,通过 USB 接收传输数据。这就是我们率先发力之处。

如果想继续跟进分析,我们使用的 ROM 版本是 G930FXXU1APF2。这是三星 Galaxy S7 的 ROM,可以直接从 Sam Mobile 下载。

Odin 处理程序代码中有个关键函数是 process_packet(位于地址 0x8F00A0A4 处),它几乎处理了所有的 Odin 协议。刚读到函数代码我们就遇到一个问题:

The beginning of process_packet

如你所见,Odin 协议查找数据包 ID,并选择代码分支。数据包 ID 0x65 告诉 Odin 要进行一个 PIT 文件相关的操作(PIT 包含分区信息,可参考此 XDA 串)。

当代码运行至 ID 0x65,它会将当前 PIT 文件读取到缓冲区,或者写入一个新的 PIT 文件到保存 PIT 数据的特殊分区。如果第二个字节是 1,Odin 继续将当前 PIT 复制到缓冲区,然后传递至 Odin 客户端。客户端需要它来确认新的固件是否适合当前分区方案。

但是 PIT 被复制的目标缓冲区(xfer_data.pit_buf)在哪里被初始化呢?显然,它在这种情况下被分配:

The allocated of pit_buf

这意味着你必须在缓冲区分配之前发送初始化数据包(ID 0x64)。如果没有的话,缓冲区是指向 0 地址的。如果你试图在缓冲区分配之前复制 PIT ,代码会继续向 0 地址空间复制:经典的空指针解引用漏洞。

这个类似于我们在 Odin 中发现的其他漏洞,它能使 BootLoader 崩溃,但不一定能利用。这种情况下,鉴于 ARM64 架构,0 地址空间没有映射,任何尝试复制到这儿的操作都会使它马上崩溃,而在 ARM32 体系上就不是那么糟糕了,因为0地址可能包含被覆盖的异常向量表(EVT)。那么问题来了,我们写入的数据依然是不可控的,因为我们无法控制 PIT 数据。

但是这个漏洞确实给了我们很多额外的东西。当我们触发漏洞并崩溃 BootLoader 时,屏幕上会显示什么?

Inside Upload Mode

Dumping Memory

代码表明 BootLoader 处理的异常输出到了屏幕上面,然后输入参考引用为“上传模式”的东西。这个方面的开发就有意思了:上传模式是一个半密码引导的 BootLoader 模式,且已经困惑了mod社区多年。有些用户报告内核恐慌后得以触发,也有人说,它是由于 PMIC 问题。现在我们知道了可以在 BootLoader 异常的时候进入该模式。

观察对应代码,我们看到上传模式位于 usbd3_rdx_process(对应地址 0x8F028C1C),属于内联函数。我对代码进行了一些修改简化,以便观察。

mode_switch = p_board_info->mode_switch;
if ( mode_switch & UPLOAD_MODE )
{
  if ( !transaction_data.response_buffer )
  {
    transaction_data.response_buffer = (char *)malloc(0x80000);
    if ( !transaction_data.response_buffer )
    {
      printf("%s: buffer allocation failed.\n", "usbd3_rdx_process");
      goto INFINITE_LOOP;
    }
  }
  if ( !strcmp(packet_buf, "PoWeRdOwN") )
  {
    goto POWERDOWN;
  }
  if ( !strcmp(packet_buf, "PrEaMbLe") )
  {
    memcpy(transaction_data.response_buffer, "AcKnOwLeDgMeNt", 15);
    goto SEND_RESPONSE;
  }
  if ( !strcmp(packet_buf, "PrObE") )
  {
    memcpy(transaction_data.response_buffer, log_location_buf, log_location_buf_size);
    goto SEND_RESPONSE;
  }
  ...
  dump_start_addr = strtol(packet_buf, NULL, 16);
  dump_end_addr = strtol(packet_buf + 9, NULL, 16);
  ...
  (some length checks)
  ...
  memcpy(transaction_data.response_buffer, dump_start_addr,   dump_end_addr - dump_start_addr);
  goto SEND_RESPONSE;

这是个相当基础的协议,用于设备的内存转储。发送一系列初始化数据包后,只需再发送转储起始地址和结束地址,然后通过 USB 恢复转储。

这对调试和逆向分析很有用,因为我们可以在崩溃之后转储内存,查看寄存器和堆栈,然后弄清发生了什么。当然还可以 dump 全部内存以帮助我们逆向。我们将会看到这种能力在文章第二部分很有用。

由于我们没有找到公开的工具用来通过上传模式转储 RAM,然后我自己写了个

Fuzzing Odin

这时,我们回到 Odin 协议,希望找到一个可利用的 bug。进入新的攻击面之时,我们要写出基本的 fuzz 工具,以便尽快达成目标。

实施证明 S-Boot 还是有点难度的,因为它使用 CDC ACM(一种串口形式)的专有协议,很难使其正常运行。一些小细节至关重要:比如,你必须在每个标准数据包后发送一个空包,一些数据包需要 1024 字节,即使它实际只包含 4 个字节的有效数据。时间有限,从零开始 fuzz 就太慢了。

Benjamin Dobell 的 Heimdall 此处就该登场了。Heimdall 是个开源的 Odin 客户端协议交互工具,它辅助处理与 Odin BootLoader 交互的部分,所以我们将其作为 fuzzer 的基础,然后进行一些扩展。

我们添加了一个名为 “fuzz” 的命令行选项,仅需一些原始数据包,可以使用 Python 预先生成,然后将它们依次发送至设备,同时处理底层细节。点此下载

我们使用这种方法在 Odin 中发生了一些有趣的崩溃,但貌似不能利用。此时我们决定花一些时间扩展 Odin 应用,那么就需要更深入的研究 Odin。下面是我们的一些有趣的发现。

The UART Console

搜索二进制,我们在 0x8F08BD78 处找到一些可疑的字符串指针。

The possible command list

看起来像是配对的命令名称和描述,可能是某种终端用于诊断的命令 - 在其他嵌入式项目中挺常见的,但此处并不是我们想要的。

要是有某种串口,能使我们连接到这个终端就好了,我们发现 XDA 的成员已经做到了这一步

事实证明,三星已经留下了一个 UART 终端,可以通过 BootLoader 打开,通过它暴露了一些低级的命令用户服务诊断。其中一些命令可以使用特殊参数启动内核,从存储器读取或写入,并触发各种 USB 模式。

然而,自从 2012 年 XDA 发布以来,还没有人对这些终端的输入做公开报告,使很多研究人员以为这个借口已被切断。我们想要对这一假设进行测试。

进一步阅读后,尤其是 Michael Ossmann和Kyle Osborn 2013年的Black Hat 演示,我们意识到三星手机以及所有的 Google Nexus手机都有一个多路复用 IC(MUIC),它位于 USB 连接器和 USB 控制器之间。通过检测 USB 连接器上的 ID 和接地直接的电阻,多路复用器在设备上切换了不同的连接路径。

公开文档有记录这两个路径的是普通的 USB 和 USB OTG。另一种模式,在公共文档中没有提到的是 UART。

The Samsung Anyway

然后调整设置,以获取该没有公开记录的 UART 的连接方式。调用的第一个端口便是Samsung Anyway Jig,这是三星比较隐秘的一个设备。这个东西通常只有三星工程师使用,很难搞到,尽管它每一段时间就会出现在 eBay 上。

显然,Anyway 只是为ID 引脚设置了几个预定义的电阻电平,并将 D+/D- 线分接到 DBUS 连接器,然后就可以通过串口 USB 适配器连接到 PC。

在 eBay 上收了个二手的,然后我测试了各种开关组合,试着让 MUIC 切换到 UART 终端模式。这在旧三星机器上管用,而我们只是成功获取到输入交互 - 我们从 BootLoader 和内核获取日志,但是并没有得到终端。

在此阶段,我们决定自己制作一条临时的 UART 电缆,类似于 Joshua Drake 的 Nexus 4 UART 电缆。我们从 XDA 收集了一些关于 ID 引脚电阻以及相关制造商的各种数据,还从内核 DTS 文件中得到一些提示。下面假想电路图:

Our makeshift jig

由于需要控制一系列电阻,我们使用了一个可变电阻,将其设为需要的值(万用表测量),并将其连接到 S7。

jig 相当简单:RS232-to-USB 的TX / RX线连接到micro USB连接器的D/D- USB线,ID 引脚通过可变电阻连接接地引脚。

结果正确的电阻值是 619K 欧姆。设置成此电阻的时候,能够在启动设备时得到一些输出。但是并不完美,输出几行后就没反应了 - 我们仍然无法得到一个终端。

The initial UART output. Logs went silent after ifconn_com_to_open

为了深入理解问题的根源,来看看标记为 get_initial_uart_str(0x8F006ECC 的函数。似乎 UART 控制台只有在此函数返回非空时才会启动:

get_initial_uart_str

这里特别是 LABEL_9 ,我们可以看到 BootLoader 要求在进入控制台模式之前至少有四个连续的回车。

此时一目了然:通过连接 jig 启动时按下 “回车” 键,同时按下音量和电源按钮,我们试着清除了 ifconn_com_to_open 的检测和终端校验。

最后,我们的付出得到了回报:

如你所见,控制台暴露了一些非常有趣的命令。而下一篇文章将会更加有趣。


源链接

Hacking more

...