![图片]()
简介
2021年2月,苹果公司发布了关于iBoot内存安全的新举措,并将其纳入苹果安全平台的一部分。他们的描述中提到,“苹果公司修改了用于构建iBoot引导程序的C编译器工具链,以提高其安全性”,并对其工作进行了一些概述。以下是引自该文件中的相关内容:
![图片]()
内存安全的iBoot实现
在iOS 14和iPadOS 14中,苹果公司修改了用于构建iBoot引导程序的C编译器工具链,以提高其安全性。修改后的工具链实现了旨在防御C程序中常见的内存和类型安全问题的代码。例如,它有助于防止以下类型的安全漏洞:
缓冲区溢出,通过确保所有指针携带边界信息,在访问内存时进行验证。
堆的利用,通过将堆数据与元数据分开,并准确地检测错误条件,如重复释放错误。
类型混淆,通过确保所有指针携带运行时的类型信息,并在指针类型转换操作中进行相应的检查。
通过将所有的动态内存分配按静态类型进行隔离,避免由释放后使用错误引起的类型混淆。
这项技术适用于配备Apple A13 Bionic或后续芯片的iPhone,以及配备A14 Bionic芯片的iPad。
我觉得,把一些关于实现、格式和苹果在这方面所做的令人兴奋的工作的信息放在一起可能会更好。顺便说一句,在iBoot二进制文件中,有一些非常有用的信息字符串,它们很快就被发布到了Twitter上。
我对这项工作非常着迷,因为上述描述给人的印象是用软件实现的“轻量级的CHERI版本”。根据苹果公司的描述,在新版本的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个交叉引用,并且每个交叉引用都能捕捉到不同类型的违规行为。
好的,现在我们就有了一个(部分)清单,列出了firebloom会明确检测并引起恐慌的错误。因为其中一些新的检查是针对已知的定义良好的函数(memset, memcpy),所以,接下来可以期待看到新的memset和memcpy的封装函数,并在其中加入新的检查。通过跟踪交叉引用链并不断逆向该流程,我们很容易就能找到这些封装函数。
然而,我很好奇其余的验证会是什么情况:例如,我们在哪里/如何看到ptr_under/ptr_over?好吧,函数panic_ptr_over有179处交叉引用,其中很多只是带有一些哈希值的封装函数。这些封装函数也有一些交叉引用,不过这些是来自实际的代码,并且当内存安全违规发生时会触发恐慌。通过跟进执行流程,我们可以发现很多示例,可以帮我们搞清楚它们的使用情况。
我只相信实际的例子,因为没有什么比代码更能说明一切了,所以,我们就通过一个执行流程,来举例说明:
iBoot:00000001FC05C5AC loop ; CODE XREF: sub_1FC05C548+94↓j
iBoot:00000001FC05C5AC CMP X10, X9
iBoot:00000001FC05C5B0 B.EQ return
iBoot:00000001FC05C5B4 ; fetch ptr and lower bounds
iBoot:00000001FC05C5B4 LDP X11, X13, [X0]
iBoot:00000001FC05C5B8 ; advance the ptr to ptr+offset, it's a loop
iBoot:00000001FC05C5B8 ADD X12, X11, X9
iBoot:00000001FC05C5BC CMP X12, X13
iBoot:00000001FC05C5C0 B.CC detected_ptr_under
iBoot:00000001FC05C5C4 ; fetch upper bounds
iBoot:00000001FC05C5C4 LDR X13, [X0,#0x10]
iBoot:00000001FC05C5C8 CMP X12, X13
iBoot:00000001FC05C5CC B.CS detected_ptr_over
iBoot:00000001FC05C5D0 ; actually dereference the pointer
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+偏移量是否越界。其中,原始指针和边界指针(下界和上界)是从某个结构体中检索的(稍后我将对其进行定义)。在此之前,为了让更好地了解相关的函数,让我们先看看相关的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
很好,看起来非常简单。
让我们看看同样的模式是否在其他地方重复出现;例如,下面这个:
在这个例子中,你可以看到一个循环语句在遍历一个元素数组(每个元素大小为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
很好。我们可以把它看成是一种“胖/有界指针”。这里并没有直接使用指向内存的原始64位指针,而是使用了一个结构体来代表含有额外元数据的指针。
显然,如果使用32个字节(即4个64位的值)来表示一个指针的话,会对许多操作都产生影响。考虑一下复制指针的简单操作——我们现在需要读/写含有4个值得元组(通常包含2个LDP和2个STP),这样就无法通过一行代码,如p2 = p;来完成读写任务了。
我希望能够找到这样的内存分配函数:它能够分配一个内存块并在结构体中初始化这些边界。我是通过反向查找更多的调用堆栈来寻找这种函数的,但是回头看,貌似有一个更加简单的方法可用!
如果检查do_firebloom_panic的交叉引用,就会发现一个非常有趣的封装函数:call_panic_allocation_size_error。这个函数的交叉引用数量不多(不到5个),并且调用方是一组非常类似的函数,其中最简单的一个如下所示:
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 - the structure to initialize
iBoot:00000001FC1A1D04 MOV X19, X8
iBoot:00000001FC1A1D08 ; X0 and X1 are probably `count` and `bytes`
iBoot:00000001FC1A1D08 UMULH X8, X1, X0
iBoot:00000001FC1A1D0C CBNZ X8, allocation_size_error_detected
iBoot:00000001FC1A1D10 ; X20 - size of the allocation
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 ; PAC-sign the allocation API
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 ; call the allocation API, allocates a chunk
iBoot:00000001FC1A1D5C ; the return value (X0) is X19, this function
iBoot:00000001FC1A1D5C ; has "MOV X0, X19" in its return
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 ; check if allocation succeeded
iBoot:00000001FC1A1D70 CBZ X0, allocation_failed
iBoot:00000001FC1A1D74 ; X0 - based of the allocation
iBoot:00000001FC1A1D74 ; X8 - X0 + allocation_size, upper bound
iBoot:00000001FC1A1D74 ADD X8, X0, X20
iBoot:00000001FC1A1D78 ; store the based (i.e. lower bound) and the upper bound
iBoot:00000001FC1A1D78 ; to the structure, at offsets +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 ; allocation failed, set NULL to both
iBoot:00000001FC1A1D90 ; lower and upper bounds
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:
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
加上这3个新的内存分配API,貌似有100多个交叉引用,哈哈,成功就在眼前了!
![图片]()
Malloc函数
虽然在iBoot中有100多处代码直接调用了do_safe_allocation函数,但仍然存在对malloc的调用。实际上,有很多方法可以发现iBoot中的malloc,其中一个简单的方法是先寻找“%s malloc failed”字符串,然后检查在它前面调用的函数。通过这种方法,我们可以看到do_allocation实际上就是malloc调用的!
下面给出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
对我来说,发现这一点很重要,原因有二:
它能佐证我们对内存分配API的理解是正确的。
那些调用malloc而不是do_safe_allocation的地方无法得到Firebloom的安全保护。
这是一个有趣的问题:为什么这些代码没有更新到新的机制呢?答案可能是:
也许苹果公司通过静态分析表明,这些地方即使提供Firebloom安全机制也不会出现安全问题?
也许有些库还没有支持Firebloom?也许这种缓解机制正处于过渡阶段?
![图片]()
类型安全
到目前为止,我们已经知道这个结构体含有原始指针和下限/上限指针。通过分析二进制文件,我们对这一点进行了确认,并搞清楚了其工作机制。
然而,我确实期望在这里看到一些类型信息,因为:
首先,我们有panic_memset_bad_type这样的函数;
苹果公司在其声明中明确提到了这一点。
好吧,大家可能还记得,前面讲过的内存分配函数确实会将一个值存储到我们的结构体的偏移量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就是用于存储类型的。我将在后续文章中详细说明类型安全的具体实现。就这里来说,我们只需知道type字段由各个调用方设置为do_safe_allocation就足够了,通过代码很容易看出这一点。
有关类型指针的更多信息,请访问第二篇文章:Firebloom (iBoot) - the type descriptor。
![图片]()
小结
很高兴看到更多关于内存安全方面的工作,并且有更多新玩意可供研究总是很棒的。
这项改动非常有趣:它肯定有助于缓解一些内存安全漏洞;然而,相应的代价也是很大的,主要体现在:
内存开销:这些新指针占用0x20字节的内存,而不是0x8。以这种方式保护的内存指针消耗的内存空间是原来的4倍。
代码长度:显然代码的长度增加了不少——因为管理新元数据需要更多的指令、更多的分支、更多的检查等。
性能影响:现在,由于数量众多的解除引用操作都封装了额外的指令(从内存中加载数据),肯定会导致性能降低。
当然,我还没有测量这些开销在新旧版本iBoot之间的差异,所以,这都是理论上的分析。但我可以肯定,这种成本是的确存在的,并且苹果公司找到了将其控制在可以接受范围内的方法。
我知道,上述情况听起来很糟糕,但说实话,iBoot正是适合进行这种修改的地方。如果苹果(或任何其他厂商)公司能够在内核中进行如此昂贵的修改,我将感到非常惊讶,但iBoot是一个非常轻量级的、受控的环境。它可以访问整个DRAM,而且它有一个非常有限和具体的目的。而且,它对于保护第二阶段的引导程序是非常有用的,而后者是安全引导过程的关键部分。
这是一个很好的例子,表明苹果公司进行的另一项努力,以通过缓解大量的一阶原语来提高安全性。
参考及来源:https://saaramar.github.io/iBoot_firebloom/
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论