IOS 超级签名概述

(1)全年稳定,告别签名特殊签名机制,无签名烦恼,每个超级签名应用可使用一年左右。

(2)无需上架,无需越狱可直接分享下载链接下载安装,无需上架AppStore,无需越狱手机&二维码不变,永久唯一.

(3) 应用越多越划算在同一台设备上下载多个不同的应用也是一次设备收费。

(4) 签名保证稳定性严控证书质量,精准控制每个证书签名的应用数量,确保每个应用的稳定性。内容有点多老铁们要耐心读完.任何一门技术都是有门槛的,尤其是ios开发的门槛,所以如果不能静下心来认真研究是很难成功的.本篇也是技术博主纸飞机@cheng716051花了多年心血,综合git和Apple官方文献做的概述和理论支撑.以及全流程希望对我的粉丝们能有较大帮助.如果有任何帮助和疑问欢迎评论区留言和私信.我将尽可能大的努力帮助大家解决问题.上干货.

介绍

对于 iOS 开发人员来说,代码签名是应用程序开发和发布过程中必不可少的一步。它向用户保证应用程序的身份和完整性,Xcode IDE 完全处理生成代码签名并将其附加到应用程序的过程。过去,Android 有一个 v1 签名漏洞,允许在不影响签名的情况下将内容注入 APK。同样,微软在 Authenticode 中也存在一个漏洞,允许插入未经验证的代码和数据。作为一名安全从业者,我很好奇 Apple 用来确保应用程序身份和完整性的具体技术。因此,我对 iOS 中的代码签名进行了一些研究和逆向工程,以更深入地了解其结构和嵌入其中的数据。

代码签名

从参考资料中,我将代码签名的结构描绘成这样:

打开网易新闻 查看更多图片

对您的应用程序进行代码签名可向用户保证它来自已知来源并且自上次签名后未被修改。在您的应用程序集成应用程序服务、安装到设备上或提交到应用程序商店之前,必须使用 Apple 颁发的证书对其进行签名。https://developer.apple.com/support/code-signing/

让我们通过在 MacOS 上选择一个可执行文件并检查其内容来开始代码签名的分析。在下面的屏幕截图中,我在 Safari 可执行文件上使用了 MachOView.app 工具。

我们还对可执行文件进行十六进制转储并检查它。

hexdump -C /Applications/Safari.app/Contents/MacOS/Safari | 较少的

打开网易新闻 查看更多图片

您可以从图 2 中看到,代码签名位于已签名的可执行文件的末尾。从图 3 中的“0xFA 0xDE 0x0C 0x01”开始,您可能还会观察到十六进制输出中有多个十六进制模式“0xfa 0xde”的实例。这些条目在本文中称为“数据块”。

在 Mach-O 可执行文件中,我们知道位于 mach_header 之后的加载命令指定其布局和链接特性。图 4 中已签名的 Mach-O 可执行文件有一个额外的命令类型 LC_CODE_SIGNATURE 的 load_command,其中包含对上述“数据 blob”的引用。

(Mach-O 是在 Apple 操作系统上运行的可执行文件的文件格式。您可以查阅有关 Mach-O 可执行格式的文章以了解更多信息。)

在允许可执行文件运行之前,其代码签名必须由操作系统验证。我问自己的两个问题是:1) 在代码签名中发现了什么,它是如何构造的?2)代码签名中是否有任何私人/敏感信息?

为了回答这些问题,我需要进一步分解代码签名。Apple 有一个开源门户网站,其中发布了一些用于代码签名的相关代码/库(尽管不是最新版本),它们提供了重要线索。下面是库libsecurity_codesigning中的代码片段。

typedef struct __SuperBlob {
uint32_t 魔法;/* 幻数 */
uint32_t length; /* SuperBlob 的总长度 */
uint32_t count; /* 后面的索引条目数 */
CS_BlobIndex index[]; /* (计数)条目 */ /* 后跟索引中偏移量指示的无特定顺序的 Blob */
} CS_SuperBlob;

从代码中,我们可以看出代码签名由一个由多个 blob 组成的 superblob 表示(例如,代码目录 blob、权利 blob、需求 blob、blob 包装器 blob)。这些 blob 中的每一个都有自己的魔法标头。我们可以通过检查其他代码片段来更多地了解它们的内容。

代码目录 Blob

代码目录blob包含offsets、hashtype、identifier、teamid等信息:

类 CodeDirectory: public Blob
public:
Endian// 兼容版本
Endian// 设置和模式标志
Endian// 哈希槽元素在索引零处的偏移
Endian// 标识符字符串的偏移
Endian// 特殊哈希槽的数量
Endian// 普通(代码)哈希槽的数量
Endian// 限制在主图像签名范围内
uint8_t hashSize; // 每个散列摘要的大小(字节)
uint8_t hashType;// 哈希类型(kSecCodeSignatureHash* 常量)
uint8_t 平台;// 平台标识符;如果不是平台二进制
uint8_t pageSize,则为零;// log2(以字节为单位的页面大小);0 => 无限
Endian// 未使用(必须为零)
Endian// 可选散点向量的偏移量(如果不存在则为零)
Endian// 可选 teamID 字符串的偏移量
Endian// 未使用(大多数为零)
Endian// 限制主图像签名范围,64 位
Endian// 可执行段的偏移
Endian// 可执行段的限制
Endian// 执行段标志 Endian// 运行时版本编码为 un
signed int
Endian// 预加密哈希槽的偏移量 // 使用版本字段;参见上面的评论
static const uint32_t currentVersion = 0x20500; // “版本 2.5”
static const uint32_t compatibilityLimit = 0x2F000; // “版本 3 有回旋余地”
static const uint32_t earliestVersion = 0x20001; // 最早支持的版本
static const uint32_t supportsScatter = 0x20100; // 第一个支持分散选项的版本
static const uint32_t supportsTeamID = 0x20200; // 第一个支持团队 ID 选项的版本
static const uint32_t supportsCodeLimit64 = 0x20300; // 第一个支持 codeLimit64 的版本
static const uint32_t supportsExecSegment = 0x20400; // 第一个支持 exec base 和 limit 的版本
static const uint32_t supportsPreEncrypt = 0x20500; // 第一个支持预加密哈希和运行时版本的版本...
}

version;

flags;

hashOffset;

identOffset;

nSpecialSlots;

nCodeSlots;

codeLimit;

spare2;

scatterOffset;

teamIDOffset;

spare3;

codeLimit64;

execSegBase;

execSegLimit;

execSegFlags;

运行时;

preEncryptOffset;

图 5 显示了代码目录 blob 的外观。

'flags' 用于指定选项标志,这些标志可以在签名期间嵌入到代码签名中并管理签名的使用。https://developer.apple.com/documentation/security/seccodesignatureflag'hashOffset' 指向 codeSlots 的开始。根据指定的版本,它还会启用不同的选项(scatter、teamid、codelimit64 等)。随着版本号的增加,将来可能会支持其他选项。

在属性列表之后,将跟随包含特定类型哈希的槽。如 CodeDirectory 类中所示,有两种类型的插槽 - specialSlots 和 codeSlots。

specialSlots 中的哈希用于验证代码签名中其他 blob(例如权利、要求、资源目录、info.plist)的完整性。

CSSLOT_INFOSLOT = 1,
CSSLOT_REQUIREMENTS = 2,
CSSLOT_RESOURCEDIR = 3,
CSSLOT_APPLICATION = 4,
CSSLOT_ENTITLEMENTS = 5,// 更多 CSSLOT_* 可以在这里找到:https://developer.apple.com/documentation/kernel/2869934-anonymous/

至于 codeSlots 中的哈希值,它们用于验证 Mach-O 可执行文件的完整性,即确保可执行文件在签名后未被修改。

pageSize = 12 == log2(page_size_bytes),因此:
page_size_bytes = 2^12 = 0x1000

验证是通过首先将整个 Mach-O 文件分成大小相等的块来完成的。如上所示,此大小源自指定的 pageSize 变量 (12)。因此,对于每 0x1000 个字节,对数据字节进行哈希处理并将 shasum 存储在 codeSlots 中。

以下是可能的哈希类型列表(https://github.com/apple-oss-distributions/dyld/blob/main/common/CodeSigningTypes.h):CS_HASHTYPE_SHA1 = 1,
CS_HASHTYPE_SHA256 = 2,
CS_HASHTYPE_SHA256_TRUNCATED = 3,
CS_HASHTYPE_SHA384 = 4,

多个代码目录 blob 也可以一起存在,每个存储不同的 SHA 哈希类型以用于验证。

从 CodeDirectory blob 版本 0x20500 开始,有称为预加密哈希的可选条目。根据我的观察,这些哈希值出现在 specialSlots 和 codeSlots 中的哈希值之前,它们似乎与 codeSlots 中的哈希值相同。目前,我不确定为什么哈希值会重复。您可以使用代码签名工具(由 Apple 提供,用于在命令行上执行代码签名)和隐藏的 --generate-pre-encrypt-hashes选项生成这些哈希值。

需求 Blob

需求 blob 将一个或多个不同的内部需求包装在需求集中。需求集定义了代码签名需要根据其进行评估的标准。在允许可执行文件运行之前,代码签名必须满足定义的要求。要查看 Mach-O 可执行文件的要求,您可以使用如下代码设计工具:

codesign -d --requirements - /Applications/Safari.app/Contents/MacOS/SafariExecutable=/Applications/Safari.app/Contents/MacOS/Safari 指定 => 标识符“com.apple.Safari”和锚苹果

我们可以从 Apple 对需求语言的文档中了解到不同的常量、变量、运算符、匹配表达式和约束是如何指定的:

https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/RequirementLang/RequirementLang.html

通过研究需求语言的解析器和进一步的逆向工程,对于在需求 blob 中找到的需求集(如果其中有 2 个单独的内部需求),我们可以推导出图 6 中的以下结构:

打开网易新闻 查看更多图片

如果您有兴趣,使用MacOS 上的Security.framework ,您还可以使用需求语言从描述需求的字符串生成内部需求,如下面的代码片段所示。

char *requirement_str = "
静态 SecRequirementRef requirement_ref = 无;
CFDataRef 请求数据;
CFStringRef req_ref = CFStringCreateWithCString(NULL, requirement_str, kCFStringEncodingUTF8);
SecRequirementCreateWithString(req_ref, kSecCSDefaultFlags, &requirement_ref);
SecRequirementCopyData((SecRequirementRef)requirement_ref, kSecCSDefaultFlags, &req_data);

权利 Blob

Apple将权利定义为授予可执行文件访问和使用系统功能的特定能力的权利或特权。权利被配置为二进制/xml 格式的属性列表 (plist),可以通过以下方式提取:

codesign -d --entitlements :- /Applications/Safari.app/Contents/MacOS/Safari

以下是 iOS/MacOS 上的授权示例:

< true/>

平台应用

com.apple.private.skip-library-validation

com.apple.private.security.no-container

钥匙串访问组

com.apple.cfnetwork

com.apple.safari.credit-cards

com.apple.safari.history

com.apple.ProtectedCloudStorage

com.apple.webinspector

com.apple.passd

com.apple.sharing.safaripasswordsharing

com.apple.webkit.webauthn

lockdown-identities

com.apple.password-manager

图 7 显示了权利 blob 的外观:

一个有趣的小实验。尝试从 plist 添加或删除其中一些权利,看看它是否会导致不同的行为。在越狱设备上,具有“com.apple.private.security.no-container ”授权的可执行文件通常可以从移动用户允许的任何目录读取和写入;但不能没有它。确保在进行任何更改后退出可执行文件;更改权限后,您必须更新相应的代码签名。(有关对可执行文件进行代码签名的示例,请参阅文章的“总结”)。

还存在 ASN.1 (DER) 编码形式,这是iOS15 的要求。这种编码形式以不同的魔法开始(0xFADE7172 而不是 0xFADE7171)并且可以使用--generate-entitlement-der选项生成。

杂七杂八的琐事。2020 年,一名研究人员此前发现了权利解析中的漏洞,并撰写了一篇相关文章。

Blob 包装器 (CMS) Blob

最后,让我们看看使用 Apple 签名证书生成的 blob 包装器 blob 中的 CMS(加密消息语法)签名。

要通过 Xcode 在 iPad/iPhone 上安装您自己的 iOS 应用程序,您将被要求登录您的 Apple 开发者帐户,以根据您的 AppleID 身份创建用于代码签名的 Apple 签名证书。这些证书随后会添加到您 MacOS 上的 Keychain.app 中,您的 AppleID 身份现在可以用于代码签名,如下所示:

security find-identity -p 代码签名策略:代码签名匹配身份
1) 01234567890ABCDEF01234567890ABCDEF012345 "Apple Development: yourname@email.com (0123456789)"
2) 01234567890ABCDEF01234567890ABCDEF012344 "Apple Development: yourname2@28identities
found"6 (021identity)65仅限有效身份
1) 01234567890ABCDEF01234567890ABCDEF012345 "Apple Development: yourname@email.com (0123456789)"
2) 01234567890ABCDEF01234567890ABCDEF012344 "Apple Development: yourname2@email.com (0182345)
identities"6

在代码签名期间,所选身份将检索其证书并用于生成 CMS 签名。图 8 说明了流程:

通过进一步的逆向工程,我能够解析 CMS 签名。下面是解析 Safari 应用的 CMS 签名的结果:

/Applications/Safari.app/Contents/MacOS/SafariBlobWrapper:CMS (RFC3852) 签名(4450 字节)
Subject:/C=US/O=Apple Inc./OU=Apple Certification Authority/CN=Apple Code Signing Certification Authority
Issuer:/C=US/O=Apple Inc./ OU=Apple Certification Authority/CN=Apple Root CA
不早于:2011 年 10 月 24 日 17:39:41 GMT
不晚于:2026 年 10 月 24 日 17:39:41主题:/C=US/O=Apple Inc./OU=Apple Software/CN=Software Signing
Issuer:/C=US/O=Apple Inc./OU=Apple Certification Authority/CN=Apple Code Signing Certification Authority
Not Before :格林威治标准时间 2020 年 10 月 29 日 18:32:38
不晚于:格林威治标准时间 2026 年 10 月 24 日 17:39:41主题:/C=US/O=Apple Inc./OU=Apple Certification Authority/CN=Apple Root CA
颁发者:/C=US/O=Apple Inc./OU=Apple Certification Authority/CN=Apple Root CA
Not Before :格林威治标准时间 2006 年 4 月 25 日 21:40:36
不晚于:格林威治标准时间 2035 年 2 月 9 日 21:40:36在 signer_info NID 中找到 5 个属性
:50 contentType (1.2.840.113549.1.9.3)
NID:52 signingTime (1.2.840.113549.1.9.5)
signingTime:Jun 3 23:32:45 2021 GMT
NID:51 messageDigest (1.2.840.113549 .1.9.4)
NID: 0 1.2.840.113635.100.9.2 (1.2.840.113635.100.9.2)
NID: 0 1.2.840.113635.100.9.1 (1.2.840.113635.100.9.1)

它包含证书的信任链、所用证书的有效性和过期时间戳,以及可执行文件的签名时间。

使用开发者证书签名的应用程序将以“Apple Worldwide Developer Relations Certificate Authority”作为颁发者。否则,Apple 发布的可执行文件将以“Apple Code Signing Certificate Authority”作为颁发者,如上例所示。请注意,Apple 会不时更新中间证书(如此处所述)。

如果可执行文件是在打开 kSecCodeSignatureAdhoc 标志的情况下签名的,则会生成 CMS 签名的不同变体,称为临时签名。这可以通过在使用协同设计工具时为身份参数指定破折号来完成。生成的签名将由零组成,不能用作可执行文件身份和完整性的加密证明以进行验证。

相反,通过将 SHA-1 哈希值与存储在内核内部静态信任缓存中的“已知良好”哈希值列表进行比较,来检查临时签名的二进制文件。这允许仅具有临时签名的受信任的 Apple 可执行文件被允许运行,尽管没有加密证明。

一个有趣的琐事。内核中的静态信任缓存已被旧款 Apple 手机上的公开越狱滥用,使用户能够运行他们自己的仅经过临时签名的可执行文件。

总结起来

既然您知道代码签名的样子,请尝试自己对可执行文件进行代码签名并使其在 iOS 设备上运行。

// 使用 identity/adhoc + entitlements codesign 对可执行文件进行代码签名
--force -s // 有关如何使用协同设计工具
man codesign的更多信息// 您还可以“字符串”协同设计以查看可用的可能 arg 选项。

--entitlements

旁注:您可能希望将新的可执行文件保存在与旧可执行文件不同的位置,或者在用新的替换之前删除旧的可执行文件。否则,由于内核中存在先前的代码签名 blob,即使在代码签名之后,您也可能会收到“Killed: 9”错误。

我从研究中得出的两个主要结论是:

  • 代码签名确实确保了可执行文件本身以及其他重要数据(如权利和要求)的身份和完整性,这些数据强制执行 iOS 的安全模型。
  • 由于需求是由专有语言定义的,并且需要安全地解析该语言,因此深入研究并了解更多信息将会很有趣。

我还尝试(部分)实现了协同设计工具的功能。随意使用代码并根据需要进行扩展。目前,在从 iOS 可执行文件中转储 CMS 签名时,该实现提供了比代码签名更详细的信息(例如证书的有效期)。

另外,在这里要感谢 Jonathan Levin 和他的MacOS 和 *OS Internals 三部曲。这些是了解 iOS 内部结构的宝贵资源。如果您对移动安全研究(还有 Android)感兴趣,我强烈建议您阅读他的书籍。

如果您想了解有关代码签名的更多信息,您也可以参考下面的资源(在上面已经提到的资源之上)。

快乐学习!