DER Entitlements:Psychic Paper的(短暂)回归
作者:Ivan Fratric,Project Zero
注:本文讨论的漏洞 CVE-2022-42855 已在 iOS 15.7.2 和 macOS Monterey 12.6.2 中修复。虽然该漏洞在 iOS 16 和 macOS Ventura 上似乎不可利用,但 iOS 16.2 和 macOS Ventura 13.1 仍然发布了相关的加固更改。
去年,我花了大量时间研究基于 XMPP(一种基于 XML 的即时消息协议)构建的应用程序的安全性。更具体地说,我的研究重点是 XML 解析中的细微差异如何被用来破坏此类应用程序的安全性。(如果您想了解更多关于该研究的信息,我在 Black Hat USA 2022 上就此做过演讲。幻灯片和录音可以在这里和这里找到)。
在某个时候,当我的一部分研究发表后,人们指出了其他(与 XMPP 无关的)例子,其中 XML 解析的差异导致了安全漏洞。其中一个例子是一个名为 Psychic Paper 的漏洞,这是 Apple 操作系统检查应用程序拥有哪些权限(entitlements)的方式中一个非常巧妙的漏洞。
权限(entitlements)是 Apple 操作系统的核心安全概念之一。正如 Apple 文档所解释的:“权限是一种权利或特权,授予可执行文件特定的能力。”例如,Apple 操作系统上的应用程序如果没有适当的权限,就无法调试另一个应用程序,即使这两个应用程序以同一用户身份运行。即使是作为 root 运行的应用程序,如果没有适当的权限,也无法执行所有操作(例如访问某些内核 API)。
Psychic Paper 是权限检查方式中的一个漏洞。权限以 XML 格式存储在应用程序签名块(signature blob)中,因此操作系统自然需要在某个时刻使用 XML 解析器来解析它们。问题在于,操作系统并非使用单一的解析器,而是使用了多达四个不同的解析器,它们分别用在操作系统的不同位置。一个解析器用于初始检查,确保应用程序只拥有允许的权限,而另一个解析器则在稍后检查应用程序是否拥有执行特定操作的权限时使用。
在我关于 XMPP 的演讲中,我给听众提出了一个挑战:找到两个不同的 XML 解析器,它们对于每个输入总是产生相同的输出。这之所以困难,是因为 XML 虽然旨在成为一种简单的格式,但实际上却一点也不简单。因此,当发现一种方法能让 Apple 的一个 XML 解析器返回一组权限,而另一个解析器在解析相同的权限块时看到另一组不同的权限时,这并不令人意外。
Psychic Paper 漏洞的修复:最初,问题出现是因为 Apple 在操作系统中使用了四个 XML 解析器,所以令人惊讶的是,修复方法是添加第五个解析器。
因此,在我的 XMPP 研究之后,当我了解到 Psychic Paper 漏洞时,我决定仔细研究这些 XML 解析器,看看我是否能在修复后以某种方式找到另一种触发该漏洞的方法。在尝试了各种 Apple XML 解析器之后,我有了一个想要测试的 XML 片段。然而,当我实际尝试在应用程序中使用它时,我发现检查权限的系统行为与我想象的完全不同。这是因为……
DER 权限
根据 Apple 开发者文档,“从 iOS 15、iPadOS 15、tvOS 15 和 watchOS 8 开始,系统会检查一种新的、更安全的签名格式,该格式使用可辨别编码规则(Distinguished Encoding Rules,DER)将权限嵌入到应用程序的签名中”。正如 另一篇 Apple 文章大胆宣称的那样,“未来属于 DER”。
那么,什么是 DER?
与之前基于文本的 XML 格式不同,DER 是一种二进制格式,也常用于数字证书。该格式在 X.690 标准中规定。
DER 遵循相对简单的类型-长度-数据编码规则。规范中的一张图说明了这一点:

标识符(Identifier)字段编码对象类型,可以是基本类型(例如字符串或布尔值),也可以是构造类型(包含其他对象的对象,例如数组/序列)。长度(Length)字段编码内容中的字节数。长度可以根据内容长度以不同方式编码(例如,如果内容小于 128 字节,则长度字段仅占用一个字节)。长度字段之后是内容本身。对于构造类型,内容也使用相同的编码规则进行编码。
Apple 开发者文档中的一个示例展示了 DER 编码的权限可能是什么样子:
appl [ 16 ]
INTEGER :01
cont [ 16 ]
SEQUENCE
UTF8STRING :application-identifier
UTF8STRING :SKMME9E2Y8.com.example.apple-samplecode.ProfileExplainer
SEQUENCE
UTF8STRING :com.apple.developer.team-identifier
UTF8STRING :SKMME9E2Y8
SEQUENCE
UTF8STRING :get-task-allow
BOOLEAN :255
SEQUENCE
UTF8STRING :keychain-access-groups
SEQUENCE
UTF8STRING :SKMME9E2Y8.*
UTF8STRING :com.apple.token
每个单独的权限都是一个包含两个元素的序列:一个键和一个值,例如 “get-task-allow”:boolean(true)。所有权限也是构造类型的一部分(在列表中表示为 “cont [ 16 ]”)。
DER 旨在具有唯一的二进制表示形式,并且用 DER 替换 XML 很可能至少部分是为了防止诸如 Psychic Paper 之类的问题。但它是否一定能成功实现这一目标呢?
权限如何被检查
要理解权限如何被检查,最好也从更宏观的角度来看,了解 Apple 操作系统在运行二进制文件之前(在某些情况下,运行期间)对其执行哪些安全/完整性检查。
Apple 二进制文件中的完整性信息存储在一个称为 嵌入式签名块(Embedded Signature Blob)的结构中。该结构是一个容器,包含在完整性检查中起作用的其它各种结构:数字签名本身,以及权限和一个称为代码目录(Code Directory)的重要结构。代码目录包含二进制文件中每一页(直到签名块)的哈希值,还包含其他信息,包括权限块的哈希值。代码目录的哈希值称为 cdHash,用于唯一标识二进制文件。例如,数字签名实际签名的就是 cdHash。由于代码目录包含权限块的哈希值,请注意对权限的任何更改都会导致 cdHash 不同。
正如 Psychic Paper 分析文章中也指出的,二进制文件可能有几种签名方式:
- 二进制文件的 cdHash 可能位于系统的信任缓存(Trust Cache)中,该缓存存储系统二进制文件的哈希值。
- 二进制文件可能由 Apple App Store 签名。
- 二进制文件可能由开发者签名,但在这种情况下,它必须引用由 Apple 签名的“供应配置文件(Provisioning Profile)”。
- 仅在 macOS 上,二进制文件也可以是自签名的。
我们主要对最后两种情况感兴趣,因为只有它们允许我们提供自定义权限。然而,在这些情况下,二进制文件拥有的任何权限必须是 Apple 创建的供应配置文件所允许的权限的子集(在“供应配置文件”情况下),或者权限必须仅包含操作系统白名单中的权限(在自签名情况下)。也就是说,除非存在像 Psychic Paper 这样的漏洞。
文件完整性检查的一个入口点是 AppleMobileFileIntegrity 内核扩展中的 vnode_check_signature 函数。AppleMobileFileIntegrity 是完整性检查的内核组件,但还有一个用户空间守护进程:amfid,AppleMobileFileIntegrity 使用它来执行某些操作,如下文所述。
我们不会分析 vnode_check_signature 的完整行为。但是,我将重点介绍最重要的操作以及与理解 DER 权限工作流程相关的操作:
首先,vnode_check_signature 检索 DER 格式的权限。如果当前加载的二进制文件不包含 DER 权限,而只包含 XML 格式的权限,AppleMobileFileIntegrity 会调用 transmuteEntitlementsInDaemon,该函数使用 amfid 将权限从 XML 转换为 DER 格式。转换本身通过 CFPropertyListCreateWithData(将 XML 转换为 CFDictionary)和 CESerializeCFDictionary(将 CFDictionary 转换为 DER 表示)完成。
使用 CoreTrust 库通过调用 CTEvaluateAMFICodeSignatureCMS 来执行签名检查。这个过程最近在另一篇漏洞分析文章中有所记录,由于不直接涉及权限,这里不详细研究。
通过 verify_code_directory 函数在 amfid 中对签名和权限进行额外检查。稍后将深入分析此调用。关于此交互的一个有趣细节是,amfid 接收可执行文件的路径作为参数。为了防止在内核加载文件和 amfid 检查文件之间文件被更改的竞争条件,amfid 返回被检查文件的 cdHash。然后验证此 cdHash 是否与内核中文件的 cdHash 匹配。
检索供应配置文件信息,并检查二进制文件中的权限是否是供应配置文件中权限的子集。这是通过 CEContextIsSubset 函数完成的。此步骤似乎不存在于运行在 Intel CPU 上的 macOS 中,但即使在这种情况下,权限仍然会在 amfid 中检查,如下所示。
amfid 的 verify_code_directory 函数执行(除其他外)以下操作:
解析二进制文件并提取完整性检查所需的所有相关信息。执行此操作的代码是开源 Security 库的一部分,这里两个最相关的类是 StaticCode 和 SecStaticCode。此代码还负责提取 DER 编码的权限。再次强调,如果只存在 XML 编码的权限,它们会被转换为 DER 格式。这再次通过 CFPropertyListCreateWithData 和 CESerializeCFDictionary 这对函数完成。此外,为了后续使用,权限也会被转换为 CFDictionary 表示。这种从 DER 到 CFDictionary 的转换是使用 CEManagedContextFromCFData 和 CEQueryContextToCFDictionary 函数对完成的。
检查签名是否签署了正确的 cdHash 并检查签名本身。数字签名证书链的检查实际上不是由 amfid 进程完成的。amfid 为此调用 trustd。
在 macOS 上,二进制文件中包含的权限被过滤为受限和非受限权限。在 macOS 上,非受限权限是 com.apple.application-identifier、com.apple.security.application-groups*、com.apple.private.signing-identifier 和 com.apple.security.*。如果二进制文件包含上述未列出的任何权限,则需要根据供应配置文件进行检查。此处的检查依赖于之前在 amfid 中提取的权限 CFDictionary。
之后,当二进制文件运行时,如果操作系统想要检查二进制文件是否包含执行特定操作的权限,它可以通过几种方式实现。执行此操作的“旧方法”是检索权限的字典表示,并查询字典中的特定键。然而,还有一种用于查询权限的新 API,调用者首先创建一个 CEQuery 对象,其中包含他们想要查询的权限键(以及可选的预期值),然后通过调用 CEQuery 对象作为参数的 CEContextQuery 函数来执行查询。例如,IOTaskHasEntitlement 函数(它接受一个权限名称并返回布尔值)就依赖于后一种 API。
libCoreEntitlements
您可能已经注意到,许多与 DER 权限交互的函数都以字母“CE”开头,例如 CEQueryContextToCFDictionary 或 CEContextQuery。在这里,CE 代表 libCoreEntitlements,这是一个专门为 DER 编码的权限创建的新库。libCoreEntitlements 同时存在于用户空间(作为 libCoreEntitlements.dylib)和操作系统内核中(作为 AppleMobileFileIntegrity 内核扩展的一部分)。因此,任何与解析 DER 权限格式相关的漏洞都将位于那里。
“这里没有漏洞”,我在一次 Project Zero 团队会议上宣称。当时,我基于以下几点做出这一断言:
只有一个用于解析 DER 权限的库,不像用于 XML 权限的四/五个库。虽然该库有两个版本,分别位于用户空间和内核中,但它们似乎是相同的。因此,不存在像 Psychic Paper 那样的解析差异漏洞的可能性。除此之外,二进制格式首先就不太容易受到此类问题的影响。
该库非常小。我既审查了该库是否存在内存损坏漏洞,也对其进行了模糊测试,但没有发现任何问题。
事实证明我完全错了(就像有人宣称代码没有错误时经常发生的那样)。但首先……
插曲:SHA1 碰撞
在我未能找到好的 libCoreEntitlements 漏洞后,我转向了其他想法。在研究 Apple 完整性检查时,我注意到 SHA1 仍然作为代码目录 / cdHash 的有效哈希类型被支持。由于实用的 SHA1 碰撞攻击已经为人所知一段时间了,我想看看是否可以通过某种方式应用它们来破坏权限检查。
应该注意的是,有两种 SHA1 碰撞攻击:
相同前缀攻击(Identical prefix attacks),攻击者从一个共同的文件前缀开始,然后构造碰撞块,使得 SHA1(前缀 + 碰撞块1) == SHA1(前缀 + 碰撞块2)。
选择前缀攻击(Chosen prefix attacks),攻击者从两个由攻击者选择的不同前缀开始,并构造碰撞块,使得 SHA1(前缀1 + 碰撞块1) == SHA1(前缀2 + 碰撞块2)。
虽然这两种攻击目前对 SHA1 都是实用的,但第一种类型的成本明显低于第二种。根据 2020 年的论文 SHA-1 is a Shambles,作者报告“相同前缀碰撞的成本为 1.1 万美元,选择前缀碰撞的成本为 4.5 万美元”。所以我想看看是否可以对 DER 权限进行相同前缀攻击。特别感谢 Ange Albertini 让我了解了 SHA1 碰撞攻击的最新情况。
我的总体思路是:
- 构造两组具有相同 SHA1 哈希值的权限
- 由于权限将具有相同的 SHA1 哈希值,只要相应的代码目录使用 SHA1 作为哈希算法,相应二进制文件的 cdHash 也将相同。
- 利用竞争条件,在内核打开文件和 amfid 打开文件之间切换二进制文件
- 如果一切顺利,amfid 将看到一组权限,而内核看到另一组权限,同时认为二进制文件没有更改。
为了构造相同前缀碰撞,我的计划是让一个权限文件包含一个长字符串。该字符串将声明一个 16 位的大小,并且相同前缀将在存储字符串长度的位置结束。这样,当找到碰撞时,两个文件中字符串的长度将不同。长度之后将是更多的碰撞数据(垃圾),但这将被视为字符串内容,并且文件的解析仍将成功。由于两个文件中字符串的长度不同,解析将从两个文件中的不同位置继续。这个想法如下图所示。

针对 DER 权限利用相同前缀 SHA1 碰撞的(失败)想法。文件的蓝色部分相同,而绿色和红色部分代表(不同的)碰撞块。
但它不会起作用。
它不起作用的原因是 DER 中的每个对象,包括包含其他对象的对象,都有一个大小。再次感谢 Ange Albertini,他立即指出这将是一个问题。
由于每个单独的权限都存储在一个单独的序列对象中,通过我们的碰撞,我们可以更改序列对象内字符串的长度(例如某个权限的值),但不能更改序列对象本身的长度。根据解析器的实现方式,序列长度与序列内内容长度不匹配可能导致解析失败。或者,如果解析器更宽松,解析会成功,但会在当前序列结束后继续解析,因此碰撞不会导致在两个不同文件中看到不同的权限。
除非序列长度被完全忽略。
所以我再次查看了该库,特别是序列长度在库的不同部分是如何处理的,那时我发现了实际的漏洞。
实际漏洞
libCoreEntitlements 并未在单一位置实现遍历 DER 权限。相反,遍历在(至少)三个不同的地方实现:
在 recursivelyValidateEntitlements 内部,从 CEValidate 调用,用于在加载之前验证权限(例如,CEValidate 从之前提到的 CEManagedContextFromCFData 调用)。除其他外,CEValidate 确保权限按字母顺序排列且没有重复权限。
der_vm_iterate,在将权限转换为字典时使用(CEQueryContextToCFDictionary),也在 CEContextIsSubset 中使用。
der_vm_execute_nocopy,在使用 CEContextQuery API 查询权限时使用。
实际的漏洞在于这些遍历算法的行为略有不同,特别是在处理权限序列长度方面。der_vm_iterate 行为正确,在处理单个键/值序列后,继续处理序列结束之后的位置(如序列长度所示)。然而,recursivelyValidateEntitlements 和 der_vm_execute_nocopy 完全忽略了序列长度(除了初始检查它是否在权限块的边界内),并且在处理单个权限(键/值序列)后,会在值结束后继续处理。这种遍历权限的差异如下图所示。

说明 libCoreEntitlements 内不同算法如何遍历相同的 DER 结构。der_vm_iterate 显示为左侧的绿色箭头,而 der_vm_execute 显示为右侧的红色箭头。文件的红色部分代表一个“隐藏”权限,仅对第二种算法可见。
这允许通过将权限嵌入为其他权限的子项来隐藏权限,如下所示。这样的权限将对 CEValidate 和 CEContextQuery 可见,但对 CEQueryContextToCFDictionary 和 CEContextIsSubset 隐藏。
SEQUENCE
UTF8STRING :application-identifier
UTF8STRING :testapp
SEQUENCE
UTF8STRING :com.apple.private.foo
UTF8STRING :bar
SEQUENCE
UTF8STRING :get-task-allow
BOOLEAN :255
幸运的是(对于攻击者而言),用于检查二进制文件中所有权限是否是供应配置文件中权限子集的函数不会看到这些隐藏的权限,这些权限只会在内核使用 CEContextQuery API 查询特定权限是否存在时出现。
由于 CEValidate 也会看到“隐藏”权限,这些权限需要与“正常”权限一起按字母顺序排列。然而,因为通常允许的权限(如 iOS 上的 application-identifier 和 macOS 上的 com.apple.application-identifier)在字母顺序中出现得相当早,这一要求并不会对利用构成障碍。
利用
为了尝试利用这些问题,需要一些工具。首先,需要工具来创建精心设计的 DER 权限。为此,我创建了一个名为 createder.cpp 的小实用程序,可以这样使用:
./createder entitlements.der com.apple.application-identifier=testapp -- com.apple.private.security.kext-collection-management
其中 entitlements.der 是输出的 DER 编码权限文件,“--”之前的权限是“正常”权限,“--”之后的是隐藏权限。
接下来,我需要一种方法将这种精心设计的权限嵌入到签名块中。通常,用于对二进制文件签名的是 Apple 的 codesign 实用程序。然而,该实用程序只接受 XML 权限作为输入,用户无法提供 DER 块。在某个时刻,codesign 将用户提供的 XML 转换为 DER,并将两者嵌入到生成的二进制文件中。
我认为嵌入自定义 DER 到二进制文件中最简单的方法是修改 codesign 的行为,特别是 XML 到 DER 的转换过程。为此,我使用了 TinyInst,这是一个我与贡献者共同开发的动态插桩库。TinyInst 目前支持 macOS 和 Windows。
为了嵌入自定义 DER,我挂钩了 libCoreEntitlements 中用于 XML 到 DER 转换的两个函数:CESizeSerialization 和 CESerialize(在 macOS 13 / iOS 16 上是 CESerializeWithOptions)。第一个计算 DER 的大小,第二个输出 DER 字节。
最初,我使用 TinyInst 的通用低级插桩 API 编写了这些钩子。然而,由于这个过程比必要的更繁琐,在此期间我创建了一个专门用于函数挂钩的 API。因此,与其链接到我随漏洞报告发送给 Apple 的初始实现,不如让我展示如何使用新的 TinyInst Hook API 来实现这些钩子:
cehook.h
#include "hook.h"
class CESizeSerializationHook : public HookReplace {
public:
CESizeSerializationHook() : HookReplace("libCoreEntitlements.dylib",
"_CESizeSerialization", 3) {}
protected:
void OnFunctionEntered() override;
};
class CESerializeWithOptionsHook : public HookReplace {
public:
CESerializeWithOptionsHook() : HookReplace("libCoreEntitlements.dylib",
"_CESerializeWithOptions", 6) {}
protected:
void OnFunctionEntered() override;
};
// 主客户端
class CEInst : public TinyInst {
public:
CEInst();
protected:
void OnModuleLoaded(void* module, char* module_name) override;
};
cehook.cpp
#include "cehook.h"
#include
char *der;
size_t der_size;
size_t kCENoError;
// 将 entitlements.der 读入缓冲区
void ReadEntitlementsFile() {
std::ifstream file("entitlements.der", std::ios::binary | std::ios::ate);
if(file.fail()) {
FATAL("Error reading entitlements file");
}
der_size = (size_t)file.tellg();
file.seekg(0, std::ios::beg);
der = (char *)malloc(der_size);
file.read(der, der_size);
}
void CESizeSerializationHook::OnFunctionEntered() {
// 第3个参数(输出)是指向大小的指针
printf("In CESizeSerialization\n");
size_t out_addr = GetArg(2);
RemoteWrite((void*)out_addr, &der_size, sizeof(der_size));
SetReturnValue(kCENoError);
}
void CESerializeWithOptionsHook::OnFunctionEntered() {
// 第5个参数指向输出缓冲区
printf("In CESerializeWithOptions\n");
size_t out_addr = GetArg(4);
RemoteWrite((void*)out_addr, der, der_size);
SetReturnValue(kCENoError);
}
CEInst::CEInst() {
ReadEntitlementsFile();
RegisterHook(new CESizeSerializationHook());
RegisterHook(new CESerializeWithOptionsHook());
}
void CEInst::OnModuleLoaded(void* module, char* module_name) {
if(!strcmp(module_name, "libCoreEntitlements.dylib")) {
// 我们必须获取 kCENoError 的值,
// 这是 libCoreEntitlements 在没有错误时的返回值
size_t kCENoError_addr =
(size_t)GetSymbolAddress(module, (char*)"_kCENoError");
if (!kCENoError_addr) FATAL("Error resolving symbol");
RemoteRead((void *)kCENoError_addr, &kCENoError, sizeof(kCENoError));
}
TinyInst::OnModuleLoaded(module, module_name);
}
HookReplace(我们两个钩子的基类)是一个辅助类,用于用钩子完全替换函数实现。CESizeSerializationHook::OnFunctionEntered() 和 CESerializeWithOptionsHook::OnFunctionEntered() 是钩子的主体。需要额外的代码来读取替换的权限 DER,并检索 kCENoError 的值,这是 libCoreEntitlements 函数成功时返回的值,我们必须从我们的钩子版本中返回它。请注意,在 macOS Monterey 上,CESerializeWithOptions 应替换为 CESerialize,并且第3个参数将是输出地址,而不是第4个。
这样,我就可以使用 entitlements.der 文件中提供的 DER 来签署二进制文件。
现在,工具就绪后,问题变成了如何利用它。或者更确切地说,针对哪个权限。为了利用这个问题,我们需要找到一个最终使用 CEContextQuery API 进行的权限检查。不幸的是,这排除了大多数用户空间守护进程——我查看的所有守护进程仍然以“旧方式”进行权限检查,即首先获取权限的字典表示。这就“只剩下”操作系统内核进行的权限检查。
幸运的是,内核中的许多检查都是使用易受攻击的 API 进行的。其中一些检查由 AppleMobileFileIntegrity 自身使用 OSEntitlements::queryEntitlementsFor、AppleMobileFileIntegrity::AMFIEntitlementGetBool 或 proc_has_entitlement 等函数执行。但还有其他 API。例如,根据 macOS Monterey 内核缓存,在内核中超过一百个地方使用的 IOTaskHasEntitlement 和 IOCurrentTaskHasEntitlement 函数最终会调用 CEContextQuery(首先调用 amfi->OSEntitlements_query())。
另一个可能易受攻击的 API 是 IOUserClient::copyClientEntitlement,但仅限于 Apple silicon,其中 pmap_cs_enabled() 为 true。虽然我没有测试过,但有一些迹象表明在这种情况下使用了 CEContextQuery API 或类似的东西。IOUserClient::copyClientEntitlement 最显著地被设备驱动程序用来检查权限。
我尝试在 macOS 和 iOS 上利用这个问题。在 macOS 上,我发现了可以利用这种权限绕过来解锁以前无法触及的攻击面的地方,但没有什么能立即提供独立的漏洞利用。最后,为了在寻找更好的原语时报告一些东西,我报告说我能够调用 kext_request API(内核扩展 API)的功能,而我通常不应该能够这样做,即使作为 root 用户。当时,我在 macOS Monterey 版本 12.6 和 12.6.1 上进行了测试。
在 iOS 上,利用这样的问题可能更有趣,因为它可以作为越狱的一部分。我尝试了 iOS 16.1,但我尝试的所有方法似乎都不起作用,但由于 iOS 平台缺乏内省能力,我当时无法弄清楚原因。
直到我收到 Apple 对我 macOS 报告的回复,询问我是否可以在 macOS Ventura 上复现。macOS Ventura 就在我向 Apple 发送报告的那一周发布,我不愿意在 macOS Monterey 上有一个正常工作的设置时升级到新的主要版本。但确实,当我升级到 Ventura,并确保我的工具仍然工作后,DER 问题不再复现。这可能也是我所有 iOS 实验都不成功的原因:iOS 16 与 macOS Ventura 共享代码库。
似乎这个问题是由于重构而修复的,而不是作为安全问题修复的(如果是安全问题,macOS Monterey 也会更新,因为 Apple 不仅为最新主要版本的 macOS 发布安全补丁,也为前两个版本发布)。查看代码,libCoreEntitlements 用于进行低级 DER 解析的 API 似乎发生了一些变化——在 macOS Monterey 上,libCoreEntitlements 主要使用 ccder_decode* 函数与 DER 交互,而在 macOS Ventura 上,更常使用 ccder_blob_decode* 函数。后者还将父 DER 结构作为输入,因此使分层 DER 结构的处理更不容易出错。由于这一变化,der_vm_execute_nocopy 在当前键/值序列结束后继续处理下一个键/值序列(这是预期行为),而不是像以前那样在值对象结束后继续处理。此时,由于我无法在最新的操作系统版本上复现该问题,我不再有动力编写漏洞利用程序,并停止了努力,以便专注于其他项目。
Apple 于 2022 年 12 月 13 日将该问题列为 CVE-2022-42855。虽然 Apple 将该 CVE 应用于其所有受支持版本的操作系统,包括 macOS Ventura 和 iOS 16,但在这些最新的操作系统版本上,补丁似乎作为一个额外的验证步骤。具体来说,在 macOS Ventura 上,补丁应用于函数 der_decode_key_value,并确保键/值序列除了键和值之外不包含其他元素(换句话说,它确保序列的长度与键 + 值对象的长度匹配)。
结论
很难声称某个特定的代码库没有漏洞。虽然对于特定类型的漏洞来说可能是可能的,但预测复杂代码库可能具有的所有类型的漏洞是困难的,甚至可能是不可能的。毕竟,你无法推理你不知道的东西。
虽然 DER 编码在解析逻辑问题方面无疑是比 XML 更安全的选择,但这篇博文表明,即使使用 DER 格式,此类问题仍然可能发生。
那么 SHA1 碰撞呢?虽然相同前缀攻击不应该对 DER 起作用,但这是否仍然留下了选择前缀攻击的可能性?也许吧,但这是以后要探索的事情。或者也许(希望)Apple 会移除其签名块中的 SHA1 支持,那样就没什么可探索的了。