【翻译】iBoot 安全机制 firebloom 简介

admin 2022年5月5日08:43:02评论141 views字数 13998阅读46分39秒阅读模式

本文来自 MSRC 的 Saar Amar(Twitter:@AmarSaar),同时也是 pasten CTF 的成员。他发布过一些关于 ARM 最新安全特性的研究,如 MTE 和 CHERI ISA,也对一些 iOS 内核漏洞有做分析。这篇文章是他对 iBoot 安全机制 firebloom 的逆向分析。

英文原文 saaramar.github.io/iBoot_firebloom/

本来想直接插入原文链接,但微信说这是一个非法域名,有点可怕。


(众所周知的 2019 年的 checkm8 漏洞之后)在 2021 年 2 月,Apple 在官网发布了关于 iBoot 内存安全的新内容,作为 Apple Security Platform 的一部分。文档中提到,Apple 修改了用于生成 iBoot 的 bootloader 代码的 C 编译器工具链,以提升安全性;对具体的实现还有一些笼统的描述。以下翻译自官方文档:

内存安全的 iBoot 实现

在 iOS 14 和 iPadOS 14 上,Apple 修改了用于生成 iBoot 的 bootloader 代码的编译器工具链来提升安全性。这些改动用于防止 C 语言程序常见的内存安全和类型安全问题。例如,它可以帮助预防大多数如下列出的漏洞类型:

  • 缓冲区溢出:确保所有的指针都引入边界信息,并在访问时验证
  • 堆利用:将堆数据和元数据进行隔离,并加强对程序错误(例如双重释放)的检测
  • 类型混淆:确保所有指针都包含运行时的类型信息,并在类型转换时校验
  • 释放后重用导致的类型混淆:基于静态类型将内存分配在不同的堆

这项技术在搭载了 A13 或更新芯片的 iPhone ,以及搭载 A14 或更新芯片的 iPad 上可用

我认为写一些文字来分析具体实现、格式以及 Apple 所做的令人振奋的工作再好不过了。除此之外,在 iBoot 的二进制文件上可以直接看到一些非常有用的字符串信息——有两位研究人员很早就在推上发过了。

【翻译】iBoot 安全机制 firebloom 简介

【翻译】iBoot 安全机制 firebloom 简介

我对这项工作很着迷,因为前文的描述让我觉得这就是一个“轻量级的纯软件实现的 CHERI”(译者注:CHERI 全称 Capability Hardware Enhanced RISC Instructions,是一种通过对指令集架构进行扩展,在非内存安全的程序语言上实现更安全的内存访问的技术)。根据 Apple 的描述,在新版的 iBoot 上,指针除了地址还包含了更多信息——包括边界和类型,从而让编译器可以在代码中引入新的内存安全检查。

我喜欢打破沙锅问到底,所以让我们深入分析,看看能学到什么。

这份分析在 iBoot.d53g.RELEASE.im4p 上完成,对应版本是 iPhone 12 的 iOS 14.4 (18D52)。

开始逆向

首先,让我们看看检测到内存安全问题时会如何处理。当内存访问违例发生时是有必要触发 panic 的,理所应当二进制里就会有一个 __firebloom_panic 字符串。根据这个特征,我们可以找到对应的函数。

iBoot:00000001FC1AA5A0 firebloom_panic
iBoot:00000001FC1AA5A0
iBoot:00000001FC1AA5A0 var_B8= -0xB8
iBoot:00000001FC1AA5A0 var_B0= -0xB0
iBoot:00000001FC1AA5A0 var_18= -0x18
iBoot:00000001FC1AA5A0 var_10= -0x10
iBoot:00000001FC1AA5A0 var_s0=  0
iBoot:00000001FC1AA5A0
iBoot:00000001FC1AA5A0 PACIBSP
iBoot:00000001FC1AA5A4 SUB             SP, SP, #0xD0
iBoot:00000001FC1AA5A8 STP             X20, X19, [SP,#0xC0+var_10]
iBoot:00000001FC1AA5AC STP             X29, X30, [SP,#0xC0+var_s0]
iBoot:00000001FC1AA5B0 ADD             X29, SP, #0xC0
iBoot:00000001FC1AA5B4 MOV             X19, X0
iBoot:00000001FC1AA5B8 ADD             X0, SP, #0xC0+var_B8
iBoot:00000001FC1AA5BC BL              sub_1FC1A9A08
iBoot:00000001FC1AA5C0 ADD             X8, X29, #0x10
iBoot:00000001FC1AA5C4 STUR            X8, [X29,#var_18]
iBoot:00000001FC1AA5C8 ADR             X1, aPasPanic ; "pas panic: "
iBoot:00000001FC1AA5CC NOP
iBoot:00000001FC1AA5D0 ADD             X0, SP, #0xC0+var_B8
iBoot:00000001FC1AA5D4 BL              do_trace
iBoot:00000001FC1AA5D8 LDUR            X2, [X29,#var_18]
iBoot:00000001FC1AA5DC ADD             X0, SP, #0xC0+var_B8
iBoot:00000001FC1AA5E0 MOV             X1, X19
iBoot:00000001FC1AA5E4 BL              sub_1FC1A9A48
iBoot:00000001FC1AA5E8 LDR             X0, [SP,#0xC0+var_B0]
iBoot:00000001FC1AA5EC BL              __firebloom_panic

这个函数有 11 处交叉引用。我将其中一个命名为 do_firebloom_panic,而这个调用者有另外 11 处交叉引用,每一处都负责处理一种不同的访问违例类型:

【翻译】iBoot 安全机制 firebloom 简介

不错,现在我们有了 firebloom 所要检测的程序错误的(一部分)列表,这些错误都会引发 panic。因为一些新的检查专门针对注入 memset 和 memcpy 这类特定的函数,我们可以期待看到这些函数将被改写为新的封装函数(wrapper),而异常检测就在这些调用里。根据交叉引用不断回溯程序流程,很容易就能找到这些封装函数。

然而我很好奇其他的访问违例是怎么样的——例如,我们将如何/在哪里找到 ptr_under 和 ptr_over(分别对应两种越界访问)?函数 panic_ptr_over 有 179 处调用,其中大部分都是简单的加入 hash 参数的包装。这些封装函数的一部分交叉引用指向真正的错误检测(panic)逻辑。继续跟下去,我们能看到一些不错的示例来展示 iBoot 是如何使用这些函数的。

多说无益,没有什么比代码更清晰,所以我们直接看这段程序:

iBoot:00000001FC05C5AC loop                                    ; CODE XREF: sub_1FC05C548+94↓j
iBoot:00000001FC05C5AC                 CMP             X10, X9
iBoot:00000001FC05C5B0                 B.EQ            return
iBoot:00000001FC05C5B4 ; 获取指针和下界
iBoot:00000001FC05C5B4                 LDP             X11, X13, [X0]
iBoot:00000001FC05C5B8 ; 将 ptr 循环递增到 ptr+offset
iBoot:00000001FC05C5B8                 ADD             X12, X11, X9
iBoot:00000001FC05C5BC                 CMP             X12, X13
iBoot:00000001FC05C5C0                 B.CC            detected_ptr_under
iBoot:00000001FC05C5C4 ; 获取数组上界
iBoot:00000001FC05C5C4                 LDR             X13, [X0,#0x10]
iBoot:00000001FC05C5C8                 CMP             X12, X13
iBoot:00000001FC05C5CC                 B.CS            detected_ptr_over
iBoot:00000001FC05C5D0 ; 真正解引用指针的地方
iBoot:00000001FC05C5D0                 LDR             W11, [X11,X9]
iBoot:00000001FC05C5D4                 STR             W11, [X8,#0x1DC]
iBoot:00000001FC05C5D8                 ADD             X9, X9, #4
iBoot:00000001FC05C5DC                 B               loop
iBoot:00000001FC05C5E0 ; ---------------------------------------------------------------------------
iBoot:00000001FC05C5E0
iBoot:00000001FC05C5E0 return                                  ; CODE XREF: sub_1FC05C548+68↑j
iBoot:00000001FC05C5E0                 LDUR            X8, [X29,#var_8]
iBoot:00000001FC05C5E4                 ADRP            X9, #a160d@PAGE ; "160D"
iBoot:00000001FC05C5E8                 NOP
iBoot:00000001FC05C5EC                 LDR             X9, [X9,#a160d@PAGEOFF] ; "160D"
iBoot:00000001FC05C5F0                 CMP             X9, X8
iBoot:00000001FC05C5F4                 B.NE            do_panic
iBoot:00000001FC05C5F8                 LDP             X29, X30, [SP,#0x70+var_s0]
iBoot:00000001FC05C5FC                 ADD             SP, SP, #0x80
iBoot:00000001FC05C600                 RETAB
iBoot:00000001FC05C604 ; ---------------------------------------------------------------------------
iBoot:00000001FC05C604
iBoot:00000001FC05C604 do_panic                                ; CODE XREF: sub_1FC05C548+AC↑j
iBoot:00000001FC05C604                 BL              call_panic
iBoot:00000001FC05C608 ; ---------------------------------------------------------------------------
iBoot:00000001FC05C608
iBoot:00000001FC05C608 detected_ptr_under                      ; CODE XREF: sub_1FC05C548+78↑j
iBoot:00000001FC05C608                 BL              call_panic_ptr_under_5383366e236c433
iBoot:00000001FC05C60C ; ---------------------------------------------------------------------------
iBoot:00000001FC05C60C
iBoot:00000001FC05C60C detected_ptr_over                       ; CODE XREF: sub_1FC05C548+84↑j
iBoot:00000001FC05C60C                 BL              call_panic_ptr_over_5383366e236c433
iBoot:00000001FC05C610 ; ---------------------------------------------------------------------------

代码很有意思。在使用 x9 寄存器提供的偏移量访问指针(对应 0x01FC05C5D0 的指令)之前,代码先检查 ptr+offset 是否越界。原始指针和数组的边界(上界、下界)信息从特定的结构体(稍后解释)中取出。在解释这一步之前,为了给读者感受一下这些函数是如何调用的,我们来看这个 panic 的封装:

iBoot:00000001FC05D384 call_panic_ptr_over_5383366e236c433     ; CODE XREF: sub_1FC05C548:detected_ptr_over↑p
iBoot:00000001FC05D384                                         ; DATA XREF: call_panic_ptr_over_5383366e236c433+24↓o
iBoot:00000001FC05D384
iBoot:00000001FC05D384 var_8           = -8
iBoot:00000001FC05D384 var_s0          =  0
iBoot:00000001FC05D384
iBoot:00000001FC05D384                 PACIBSP
iBoot:00000001FC05D388                 SUB             SP, SP, #0x20
iBoot:00000001FC05D38C                 STP             X29, X30, [SP,#0x10+var_s0]
iBoot:00000001FC05D390                 ADD             X29, SP, #0x10
iBoot:00000001FC05D394                 ADRL            X8, a5383366e236c43 ; "5383366e236c433"
iBoot:00000001FC05D39C                 STR             X8, [SP,#0x10+var_8]
iBoot:00000001FC05D3A0                 MOV             X8, X30
iBoot:00000001FC05D3A4                 XPACI           X8
iBoot:00000001FC05D3A8                 ADR             X16, call_panic_ptr_over_5383366e236c433
iBoot:00000001FC05D3AC                 NOP
iBoot:00000001FC05D3B0                 PACIZA          X16
iBoot:00000001FC05D3B4                 SUB             X2, X8, X16
iBoot:00000001FC05D3B8                 ADD             X0, SP, #0x10+var_8
iBoot:00000001FC05D3BC                 MOV             W1, #1
iBoot:00000001FC05D3C0                 BL              panic_ptr_over
iBoot:00000001FC05D3C0 ; End of function call_panic_ptr_over_5383366e236c433

还有这里:

iBoot:00000001FC1AA980 panic_ptr_over                          ; CODE XREF: sub_1FC04CBD0+3C↑p
iBoot:00000001FC1AA980                                         ; sub_1FC04EC2C+3C↑p ...
iBoot:00000001FC1AA980
iBoot:00000001FC1AA980 var_20          = -0x20
iBoot:00000001FC1AA980 var_10          = -0x10
iBoot:00000001FC1AA980 var_s0          =  0
iBoot:00000001FC1AA980
iBoot:00000001FC1AA980                 PACIBSP
iBoot:00000001FC1AA984                 STP             X22, X21, [SP,#-0x10+var_20]!
iBoot:00000001FC1AA988                 STP             X20, X19, [SP,#0x20+var_10]
iBoot:00000001FC1AA98C                 STP             X29, X30, [SP,#0x20+var_s0]
iBoot:00000001FC1AA990                 ADD             X29, SP, #0x20
iBoot:00000001FC1AA994                 MOV             X19, X2
iBoot:00000001FC1AA998                 MOV             X20, X1
iBoot:00000001FC1AA99C                 MOV             X21, X0
iBoot:00000001FC1AA9A0                 ADRP            X8, #0x1FC2F2270@PAGE
iBoot:00000001FC1AA9A4                 LDR             X8, [X8,#0x1FC2F2270@PAGEOFF]
iBoot:00000001FC1AA9A8                 CBZ             X8, do_panic
iBoot:00000001FC1AA9AC                 BLRAAZ          X8
iBoot:00000001FC1AA9B0
iBoot:00000001FC1AA9B0 do_panic                                ; CODE XREF: panic_ptr_over+28↑j
iBoot:00000001FC1AA9B0                 ADR             X0, aPtrOver ; "ptr_over"
iBoot:00000001FC1AA9B4                 NOP
iBoot:00000001FC1AA9B8                 MOV             X1, X21
iBoot:00000001FC1AA9BC                 MOV             X2, X20
iBoot:00000001FC1AA9C0                 MOV             X3, X19
iBoot:00000001FC1AA9C4                 BL              do_firebloom_panic
iBoot:00000001FC1AA9C4 ; End of function panic_ptr_over

好,很简单。

让我们看看同样的模式有没有出现在别的地方。例如这一处:

【翻译】iBoot 安全机制 firebloom 简介

在这段示例中,你可以看到一个循环遍历一个数组的所有元素(每一个的大小是 0x20),将每个元素作为参数调用一些函数。而且,不出我所料,所谓的“指针结构体”在这里被同样的方式使用了。

格式化和帮助函数

结合前文的分析,我们推测内存分配所使用的结构如下所示:

00000000 safe_allocation struc ; (sizeof=0x20, mappedto_1)
00000000 raw_ptr         DCQ ?                   ; offset
00000008 lower_bound_ptr DCQ ?                   ; offset
00000010 upper_bound_ptr DCQ ?                   ; offset
00000018 field_18        DCQ ?
00000020 safe_allocation ends

不错。我们可以将其视作“胖指针”(原文 Fat/Bounded Pointer,指带有访问边界信息的指针)。iBoot 并没有直接使用 64 位原始指针作为内存地址,而是使用一个结构体代表指针,并保存额外的元数据。

显而易见地,使用 32 字节(4 个 64bit 值)来表示一个指针,会对许多操作产生影响。考虑一个简单的指针赋值操作,不能再直接用 p2 = p; ,而是需要读写一个四元组(通常编译为两个 LDP 和两个 LDP 指令)。

我很想找到新的内存分配函数,也就是那些分配一个块并初始化元数据结构体的函数。我很轻松地通过继续向上查找调用栈发现了目标。

如果你查看 do_firebloom_panic 函数的交叉引用,可以看到一个很有意思的封装函数 call_panic_allocation_size_error。这个函数被调用得不多(不到五处),调用者是一组非常相似的函数。最简单的一处调用如下:

iBoot:00000001FC1A1CF0 do_safe_allocation                      ; CODE XREF: sub_1FC0523D8+8↑j
iBoot:00000001FC1A1CF0                                         ; sub_1FC05259C+70↑p ...
iBoot:00000001FC1A1CF0
iBoot:00000001FC1A1CF0 var_20          = -0x20
iBoot:00000001FC1A1CF0 var_18          = -0x18
iBoot:00000001FC1A1CF0 var_10          = -0x10
iBoot:00000001FC1A1CF0 var_s0          =  0
iBoot:00000001FC1A1CF0
iBoot:00000001FC1A1CF0                 PACIBSP
iBoot:00000001FC1A1CF4                 SUB             SP, SP, #0x30
iBoot:00000001FC1A1CF8                 STP             X20, X19, [SP,#0x20+var_10]
iBoot:00000001FC1A1CFC                 STP             X29, X30, [SP,#0x20+var_s0]
iBoot:00000001FC1A1D00                 ADD             X29, SP, #0x20
iBoot:00000001FC1A1D04 ; X8:需要初始化的结构体指针
iBoot:00000001FC1A1D04                 MOV             X19, X8
iBoot:00000001FC1A1D08 ; X0 和 X1 可能对应大小和数据指针
iBoot:00000001FC1A1D08                 UMULH           X8, X1, X0
iBoot:00000001FC1A1D0C                 CBNZ            X8, allocation_size_error_detected
iBoot:00000001FC1A1D10 ; X20:分配的大小
iBoot:00000001FC1A1D10                 MUL             X20, X1, X0
iBoot:00000001FC1A1D14                 ADRP            X8, #0x1FC2F50B8@PAGE
iBoot:00000001FC1A1D18                 ADD             X8, X8, #0x1FC2F50B8@PAGEOFF
iBoot:00000001FC1A1D1C                 STR             X8, [SP,#0x20+var_18]
iBoot:00000001FC1A1D20                 STR             WZR, [SP,#0x20+var_20]
iBoot:00000001FC1A1D24                 ADRP            X2, #0x1FC2F50B0@PAGE
iBoot:00000001FC1A1D28                 ADD             X2, X2, #0x1FC2F50B0@PAGEOFF
iBoot:00000001FC1A1D2C                 ADRL            X3, off_1FC2D6EC0
iBoot:00000001FC1A1D34                 ADRL            X1, qword_1FC2D6E80
iBoot:00000001FC1A1D3C ; 对分配 API 函数指针加 PAC 签名
iBoot:00000001FC1A1D3C                 ADR             X16, do_allocation
iBoot:00000001FC1A1D40                 NOP
iBoot:00000001FC1A1D44                 PACIZA          X16
iBoot:00000001FC1A1D48                 MOV             X6, X16
iBoot:00000001FC1A1D4C                 MOV             X0, #0
iBoot:00000001FC1A1D50                 MOV             X4, X20
iBoot:00000001FC1A1D54                 MOV             W5, #1
iBoot:00000001FC1A1D58                 MOV             X7, X1
iBoot:00000001FC1A1D5C ; 调用 API 分配一块内存
iBoot:00000001FC1A1D5C ; X19 就是返回值(X0)(X0)
iBoot:00000001FC1A1D5C ; 这个函数内部在返回时会 "MOV X0, X19"
iBoot:00000001FC1A1D5C                 BL              wrap_do_allocation
iBoot:00000001FC1A1D60                 ADRL            X8, off_1FC2D6EF8
iBoot:00000001FC1A1D68                 STR             X8, [X19,#0x18]
iBoot:00000001FC1A1D6C                 STR             X0, [X19]
iBoot:00000001FC1A1D70 ; 检查是否分配成功
iBoot:00000001FC1A1D70                 CBZ             X0, allocation_failed
iBoot:00000001FC1A1D74 ; X0:分配内存块的基址
iBoot:00000001FC1A1D74 ; X8:X0 + allocation_size,也就是上界
iBoot:00000001FC1A1D74                 ADD             X8, X0, X20
iBoot:00000001FC1A1D78 ; 保存上界和下界到结构体,分别对应
iBoot:00000001FC1A1D78 ; +0x8, +0x10 的成员
iBoot:00000001FC1A1D78                 STP             X0, X8, [X19,#8]
iBoot:00000001FC1A1D7C                 LDP             X29, X30, [SP,#0x20+var_s0]
iBoot:00000001FC1A1D80                 LDP             X20, X19, [SP,#0x20+var_10]
iBoot:00000001FC1A1D84                 ADD             SP, SP, #0x30 ; '0'
iBoot:00000001FC1A1D88                 RETAB
iBoot:00000001FC1A1D8C ; ---------------------------------------------------------------------------
iBoot:00000001FC1A1D8C
iBoot:00000001FC1A1D8C allocation_failed                       ; CODE XREF: do_safe_allocation+80↑j
iBoot:00000001FC1A1D8C                 ADD             X8, X19, #8
iBoot:00000001FC1A1D90 ; 分配失败,上界和下界置零
iBoot:00000001FC1A1D90                 STP             XZR, XZR, [X8]
iBoot:00000001FC1A1D94                 LDP             X29, X30, [SP,#0x20+var_s0]
iBoot:00000001FC1A1D98                 LDP             X20, X19, [SP,#0x20+var_10]
iBoot:00000001FC1A1D9C                 ADD             SP, SP, #0x30 ; '0'
iBoot:00000001FC1A1DA0                 RETAB
iBoot:00000001FC1A1DA4 ; ---------------------------------------------------------------------------
iBoot:00000001FC1A1DA4
iBoot:00000001FC1A1DA4 allocation_size_error_detected          ; CODE XREF: do_safe_allocation+1C↑j
iBoot:00000001FC1A1DA4                 BL              call_panic_allocation_size_error
iBoot:00000001FC1A1DA4 ; End of function do_safe_allocation

美妙!这就是我要的滑板鞋找的函数。这个函数分配内存块并设置对应的元数据,具体的结构在前文已经分析过。

你可能想知道其余的分配函数长什么样。其实,可能正如你假设的那样,有一个很相似的函数,唯一的区别是在结尾使用 memset0(也就是 calloc 调用的)初始化数据。

iBoot:00000001FC1AA58C memset_0                                ; CODE XREF: sub_1FC1A0890+3CC↑p
iBoot:00000001FC1AA58C                                         ; do_safe_allocation_and_zeroing:zero_the_allocation↑j ...
iBoot:00000001FC1AA58C                 CBZ             X1, return
iBoot:00000001FC1AA590
iBoot:00000001FC1AA590 loop                                    ; CODE XREF: memset_0+C↓j
iBoot:00000001FC1AA590                 STRB            WZR, [X0],#1
iBoot:00000001FC1AA594                 SUBS            X1, X1, #1
iBoot:00000001FC1AA598                 B.NE            loop
iBoot:00000001FC1AA59C
iBoot:00000001FC1AA59C return                                  ; CODE XREF: memset_0↑j
iBoot:00000001FC1AA59C                 RET
iBoot:00000001FC1AA59C ; End of function memset_0

以上三个新的内存分配 API 加起来有超过 100 处调用。看上去八*不离十了。

malloc

除了有超过100 处代码直接调用 do_safe_allocation 函数之外,我还看到一些旧版本见过的 malloc 调用被保留。有很多种办法可以识别 iBoot 中的 malloc,一个简单的方式就是查找出现字符串 %s malloc failed 的函数。接下来可以看到,实际上新的 malloc 内部直接调用了 do_allocation

如下是 malloc 的代码:

iBoot:00000001FC15ABF8 malloc                                  ; CODE XREF: sub_1FC19F50C+58↓p
iBoot:00000001FC15ABF8                                         ; sub_1FC19F77C+464↓p ...
iBoot:00000001FC15ABF8                 B               call_do_allocation
iBoot:00000001FC15ABF8 ; End of function malloc

以及 call_do_allocation

iBoot:00000001FC1A1B30 call_do_allocation
iBoot:00000001FC1A1B30
iBoot:00000001FC1A1B30 var_10= -0x10
iBoot:00000001FC1A1B30 var_8= -8
iBoot:00000001FC1A1B30 var_s0=  0
iBoot:00000001FC1A1B30
iBoot:00000001FC1A1B30 PACIBSP
iBoot:00000001FC1A1B34 SUB             SP, SP, #0x20
iBoot:00000001FC1A1B38 STP             X29, X30, [SP,#0x10+var_s0]
iBoot:00000001FC1A1B3C ADD             X29, SP, #0x10
iBoot:00000001FC1A1B40 MOV             X4, X0
iBoot:00000001FC1A1B44 ADRP            X8, #0x1FC2F50B8@PAGE
iBoot:00000001FC1A1B48 ADD             X8, X8, #0x1FC2F50B8@PAGEOFF
iBoot:00000001FC1A1B4C STR             X8, [SP,#0x10+var_8]
iBoot:00000001FC1A1B50 STR             WZR, [SP,#0x10+var_10]
iBoot:00000001FC1A1B54 ADRP            X2, #0x1FC2F50B0@PAGE
iBoot:00000001FC1A1B58 ADD             X2, X2, #0x1FC2F50B0@PAGEOFF
iBoot:00000001FC1A1B5C ADRL            X3, off_1FC2D6EC0
iBoot:00000001FC1A1B64 ADRL            X1, qword_1FC2D6E80
iBoot:00000001FC1A1B6C ADR             X16, do_allocation
iBoot:00000001FC1A1B70 NOP
iBoot:00000001FC1A1B74 PACIZA          X16
iBoot:00000001FC1A1B78 MOV             X6, X16
iBoot:00000001FC1A1B7C MOV             X0, #0
iBoot:00000001FC1A1B80 MOV             W5, #1
iBoot:00000001FC1A1B84 MOV             X7, X1
iBoot:00000001FC1A1B88 BL              wrap_do_allocation
iBoot:00000001FC1A1B8C LDP             X29, X30, [SP,#0x10+var_s0]
iBoot:00000001FC1A1B90 ADD             SP, SP, #0x20 ; ' '
iBoot:00000001FC1A1B94 RETAB
iBoot:00000001FC1A1B94 ; End of function call_do_allocation

这段对我很重要,有两个原因:

  1. 这从另一方面证明我们之前对内存分配 API 的理解是正确的
  2. 那些直接调用 malloc 而不是 do_safe_allocation 的代码,分配到的内存并不会保存 firebloom 的元数据

一个有趣的问题是,为什么这些代码没有更新到新的内存分配机制?一些可能的答案:

  • 也许 Apple 使用静态分析证明了这些这些地方可以不用 firebloom 的保护
  • 也许一些依赖库还没有适配 firebloom?可能这个缓解措施还在逐步推进中?

类型安全

好了,我们现在知道有这个带有原始指针和数组边界的新结构体。我们在二进制里识别到了相关代码,并分析了其工作原理。

然而,我本来期望能在这个结构体里看到类型信息,因为:

  1. 首先,我们有一些诸如 panic_memset_bad_type 的函数
  2. Apple 明确地在文档里提到了这点

好吧,如果你还有印象,前文的内存分配函数确实将一个值保存到结构体的 0x18 偏移上(所以我把结构体的尺寸定为 0x20)。如果我们跟进函数 panic_memset_bad_type 的调用,我们可以找到例如这样的代码:

iBoot:00000001FC15A9CC                 LDR             X23, [X20,#0x18]
iBoot:00000001FC15A9D0                 MOV             X0, X23
iBoot:00000001FC15A9D4                 BL              check_type
iBoot:00000001FC15A9D8                 TBNZ            W0, #0, call_memset
iBoot:00000001FC15A9DC                 CBNZ            W22, detected_memset_bad_type

对的!看上去 0x18 偏移就是用来存储类型信息的。有机会我将详细解释更多类型安全的实现细节。在这里只需要知道一点就够了,firebloom 的类型信息由 do_safe_allocation 的调用方传入并保存,这在代码里不难发现。

小结和思考

很高兴能看到更多内存安全技术上的工作,也很高兴能有新的东西可以折腾。

这项改动非常有趣。它显然能帮助缓解一些内存安全漏洞,但代价也相当昂贵,体现在一些不同的方面:

  1. 额外的内存开支:新的指针格式使用 0x20 个字节的内存,而不是 8 个字节。受保护的指针所占用内存是原来的 4 倍。
  2. 生成代码的长度:显而易见,采用了 firebloom 的二进制需要更多的代码。更多的指令来管理新的元数据,更多的条件分支,更多的检查等。
  3. 性能损耗:大量的解引用操作被替换成更多的指令(用以从内存中加载数据),对性能产生影响。

显然我没有实际测算过新老版本 iBoot 之间的开销差异,所以上面的分析都只是基于理论。但我有把握相信代价存在,而 Apple 找到了一个折中的方式让其顺利运行。

我知道这么说听上去很糟糕,但老实说,iBoot 正好是一个适合引入此类机制的地方。如果 Apple(或其他厂商)在内核中启用如此开销昂贵的保护,我将会感到非常诧异。iBoot 是一个非常轻量、可控的环境。它具有整个 DRAM 的访问权限,其设计具有有限的和非常明确的目的。这项机制对保护第二阶段的 bootloader 很有帮助,而后者是整个安全引导过程中至关重要的一环。

对于 Apple 而言这是一个非常不错的例子,表明其对大量的一阶原语开发针对性的缓解措施,从而提升软件的安全性。

原文始发于微信公众号(非尝咸鱼贩):【翻译】iBoot 安全机制 firebloom 简介

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年5月5日08:43:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【翻译】iBoot 安全机制 firebloom 简介https://cn-sec.com/archives/973949.html

发表评论

匿名网友 填写信息