点击上方蓝字“Ots安全”一起玩耍
这篇博文是我对一个在 2021 年初被利用并修补的漏洞的分析。就像上周发布 的关于 ASN.1 解析器错误的文章一样,这篇博文基于我在分析补丁时所做的笔记并尝试了解 XNU 凭证子系统。我希望这篇文章可以作为缺失的文档,说明凭证子系统的某些内部如何工作以及导致此漏洞的怪癖。
CVE-2021-1782 已在 iOS 14.4 中修复,正如推特上的@s1guza 所述:
此漏洞已于 2021 年 1 月 26 日修复,Apple 于 2021 年 5 月 28 日更新了 iOS 14.4 发行说明,表明该问题可能已被积极利用:
Vouchers
什么是voucher?
内核代码有一个简洁的描述:
凭证是对特定资源管理器属性值(它们本身是引用计数的)的引用计数的不可变(一次创建)索引集。
这个定义在技术上是正确的,尽管它本身可能并不是那么有用。
要真正了解此漏洞的根本原因和可利用性,需要涵盖大量凭证代码库。XNU 的这一部分非常晦涩难懂,而且非常复杂。
凭证是键和值的引用计数表。指向所有已创建凭证的指针都存储在全局ivht_bucket 哈希表中。
对于一组特定的键和值,应该只有一个凭证对象。在创建凭证期间,有一个重复数据删除阶段,新凭证将与哈希表中的所有现有凭证进行比较,以确保它们保持唯一性,如果发现重复,则返回对现有凭证的引用。
这是凭证的结构:
struct ipc_voucher {
iv_index_t iv_hash ; /* 校验和哈希 */
iv_index_t iv_sum ; /* 值的校验和 */
os_refcnt_t iv_refs ; /* 引用计数 */
iv_index_t iv_table_size ; /* 凭证表的大小 */
iv_index_t iv_inline_table [ IV_ENTRIES_INLINE ];
iv_entry_t iv_table ; /* 凭证属性条目表 */
ipc_port_t iv_port ; /* 代表凭证的端口 */
queue_chain_t iv_hash_link ; /* 哈希链上的链接 */
};
凭证代码库是以非常通用、可扩展的方式编写的,尽管它的实际使用和支持的功能集非常少。
Key
凭证中的密钥不是任意的。键是 凭证的iv_table的索引;一个值在iv_table 表中的位置决定了它存储在哪个“键”下。虽然凭证代码库支持在运行时添加新的密钥类型,但并未使用此功能,并且只有少量固定的、众所周知的密钥:
/* 其他知名键将被添加到这里 */
ipc_voucher 中的iv_inline_table有 8 个条目。但其中只有四个实际受支持并具有任何相关功能。ATM 凭证属性已被弃用,支持它们的代码已不存在,因此只有 IMPORTANCE (2)、BANK (3)、PTHPRIORITY (4) 和 USER_DATA (7) 是有效密钥。关于何时应该使用术语键以及何时使用属性存在一些混淆(也许是我自己);我将交替使用它们来指代这些键值和它们管理的相应值的“类型”。稍后再谈。
价值观
凭证iv_table中的每个条目 都是一个iv_index_t :
typedef natural_t iv_index_t ;
每个值又是一个索引;这次进入每个键 的值缓存,抽象为由以下结构表示的“凭证属性缓存控制对象”:
struct ipc_voucher_attr_control {
os_refcnt_t ivac_refs ;
boolean_t ivac_is_growth ; /* 是正在增长的表 */
ivac_entry_t ivac_table ; /* 凭证属性值条目表 */
iv_index_t ivac_table_size ; /* attr 值表的大小 */
iv_index_t ivac_init_table_size ; /* attr 值表的大小 */
iv_index_t ivac_freelist ; /* 第一个空闲元素的索引 */
ipc_port_t ivac_port ; /* 访问缓存控制的端口 */
lck_spin_t ivac_lock_data ;
iv_index_t ivac_key_index ; /* 该值的键索引 */
};
这些是通过另一个全局表间接访问的:
static ipc_voucher_global_table_element
iv_global_table[MACH_VOUCHER_ATTR_KEY_NUM_WELL_KNOWN];
(同样,代码中的注释表明,将来这个表可能会变大并允许在用户空间中管理属性,但现在它只是一个固定大小的数组。)
该表中的每个元素都具有以下结构:
typedef struct ipc_voucher_global_table_element {
ipc_voucher_attr_manager_t ivgte_manager ;
ipc_voucher_attr_control_t ivgte_control ;
mach_voucher_attr_key_t ivgte_key ;
} ipc_voucher_global_table_element ;
iv_global_table 和每个凭证的iv_table都由(key-1) 索引,而不是key ,因此 userdata 条目是[6] ,而不是[7] ,即使数组仍然有 8 个条目。
ipc_voucher_attr_control_t 提供了一个用于管理“值”的抽象接口,而ipc_voucher_attr_manager_t提供 了“特定于类型”的逻辑来实现每种类型的语义(这里的类型是指“key”或“attr”类型。)让我们更具体地看一下那是什么意思。这是ipc_voucher_attr_manager_t的定义:
static ipc_voucher_attr_manager {
ipc_voucher_attr_manager_release_value_t ivam_release_value ;
ipc_voucher_attr_manager_get_value_t ivam_get_value ;
ipc_voucher_attr_manager_extract_content_t ivam_extract_content ;
ipc_voucher_attr_manager_command_t ivam_command ;
ipc_voucher_attr_manager_release_t ivam_release ;
ipc_voucher_attr_manager_flags ivam_flags ;
};
ivam_flags 是一个 包含一些标志的int ;其他五个字段是定义特定 attr 类型语义的函数指针。这是user_data 类型的ipc_voucher_attr_manager 结构:
const struct ipc_voucher_attr_manager user_data_manager = {
. ivam_release_value = user_data_release_value ,
. ivam_get_value = user_data_get_value ,
. ivam_extract_content = user_data_extract_content ,
. ivam_command = user_data_command ,
. ivam_release = user_data_release ,
. ivam_flags = IVAM_FLAGS_NONE ,
};
这五个函数指针是从通用凭证代码到特定类型代码的唯一接口。界面可能看起来很简单,但其中有一些微妙之处;我们稍后再谈!
让我们回到通用的ipc_voucher_attr_control 结构,它以与类型无关的方式维护每个键的“值”。最重要的字段是ivac_entry_t ivac_table ,它是ivac_entry_s的数组。它是该表的索引,存储在每个凭证的iv_table中。
这是该表中每个条目的结构:
struct ivac_entry_s {
iv_value_handle_t ivace_value ;
iv_value_refs_t ivace_layered : 1 , /* 分层有效入口 */
ivace_releasing : 1 , /* 正在发布 */
ivace_free : 1 , /* 在freelist */
ivace_persist : 1 , /* 保留条目,不要
count made refs */
ivace_refs :28 ;/* 引用计数 */
union{
iv_value_refs_t ivaceu_made ; /* 计数(非分层) */
iv_index_t ivaceu_layer ; /* 下一个有效层
(layered)*/
} ivace_u ;
iv_index_t ivace_next ; /* 散列或空闲列表 */
iv_index_t ivace_index ; /* 哈希头(独立) */
};
iace_refs 是此表索引的引用计数。请注意,此条目是内联 在数组中的;所以这个引用计数变为零不会导致ivac_entry_s 被释放回内核分配器(例如区域分配器)。相反,它将这个表索引移动到一个空条目的空闲列表中。表可以增长但永远不会缩小。
非免费的表条目在ivace_value中存储特定类型的“句柄” 。这是该类型的 typedef 链:
iv_value_handle_t ivace_value
typedef mach_voucher_attr_value_handle_t iv_value_handle_t ;
typedef uint64_t mach_voucher_attr_value_handle_t ;
句柄是一个uint64_t 但实际上 attrs 可以(并且确实)在那里存储指针,隐藏在强制转换后面。
attr_control做出的保证是 对于特定的ivac_value 将永远只有一个(实时)ivac_entr y_s 。这意味着每次新的ivac_value 需要ivac_entry时 ,都需要搜索attr_control的ivac_table 以查看是否已经存在匹配的值。为了加快这一速度,使用中的ivac_entries 在哈希桶中链接在一起,以便可以搜索(希望显着)更短的条目链接列表,而不是对整个表进行线性扫描。(请注意,它不是指针的链表;链中的每个链接都是表中的索引。)
用户数据属性
user_data 是四种支持的、已实现的凭证属性类型之一。它的唯一目的是管理任意用户控制数据的缓冲区。由于attr_control 仅对ivace_value (它是一个指针)执行重复数据删除,因此 userdata attr 管理器负责确保具有相同缓冲区值(匹配长度和字节)的 userdata 值具有相同的指针。
为此,它维护了一个user_data_value_element 结构的哈希表,该结构包装了一个可变大小的字节缓冲区:
struct user_data_value_element {
mach_voucher_attr_value_reference_t e_made ;
mach_voucher_attr_content_size_t e_size ;
iv_index_t e_sum ;
iv_index_t e_hash ;
queue_chain_t e_hash_link ;
uint8_t e_data [];
};
每个内联e_data 缓冲区最大可达 16KB。e_hash_link 存储哈希表桶列表指针。
e_made 不是一个简单的引用计数。查看代码,您会注意到它没有减少的地方。由于应该(几乎)总是在ivace_entry 和user_data_value_element之间存在 1:1 映射,因此 该结构不需要进行引用计数。然而,有一个非常复杂的竞争条件(这不是 导致漏洞的竞争条件!)它需要e_made 字段。这种竞争条件是有记录的,我们最终会到达那里......
原文始发于微信公众号(Ots安全):CVE-2021-1782,iOS 凭证中的野生漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论