-
借助增强安全功能保护你的 App
了解 Xcode 中的增强安全功能如何提供强大的工具,来保护你的 App 免受内存安全漏洞的侵害。探索 Apple 如何在各个平台上应用防御策略,包括攻击面缩减、硬件缓解措施,以及通过增强安全扩展实现的隔离防护。探究如何在 Xcode 项目中启用这些功能,并制定高效的安全工程策略。
这个讲座最初作为“与 Apple 会面交流”活动“加固你的 App:关键策略助你增强安全性”的一部分讲授。请观看完整视频,以了解更多见解和相关讲座。资源
-
搜索此视频…
大家早上好。我是 Apple 公司安全、 工程与架构团队的马克 · 米切尔, 今天和我一起的是我的同事德文。 今天,我将介绍一个框架,帮助大家 保护应用免受内存安全漏洞的侵害。
德文和我今天将探讨的这些 策略经过精心分层和组合, 正是这些策略让 iPhone 赢得了“ 目前最安全的消费级设备”这一美誉。
在今天的会议中,我们将介绍 这些功能的具体内容及其用途, 并举例说明 Apple 如何 利用它们来保护我们的应用、 操作系统以及用户。
借助 Xcode, 您现在可以使用苹果所采用的相同 技术来保护您应用的用户。
应用程序渗透到我们生活的方方面面。 它们是不可或缺的工具, 每个人都会将自己的私人信息、位置、浏览历史、 照片、信息、财务信息等托付给它们。 与此同时,这些应用和用户都连接着互联网, 因此其中的安全漏洞可能会使用户面临攻击风险。 此类攻击的后果从欺诈和身份盗用, 到敲诈勒索,甚至在极少数情况下, 还会引发现实世界中的威胁。
人们期望自己的数据能够得到私密和安全的保护, 如果这一承诺未能兑现,便是对信任的背叛。 安全是保障隐私的技术基础。
我将首先概述“内存安全”的含义, 以及它可能导致的各类漏洞。
接下来,我将高屋建瓴地介绍一些可 用于保护应用程序的安全工程策略, 并说明我们如何在自家应用中成功运用这些策略。 最后, Devin 将向大家演示如何 利用 Xcode 的增强安全功能, 将这些策略付诸实践。
那么,我们所说的“内存安全”究竟指什么?
其实,内存安全漏洞是软件 中最常见的漏洞类别之一, 攻击者通常利用内存损坏来改变 或破坏程序的行为。
在苹果公司,安全工程师 会从程序中“预期行为”和“非 预期行为”两个角度来思考内存安全问题。
在典型使用场景下, 程序会按照开发者的预期运行, 并且只执行合理的操作。 因此,你可以将程序的执行流程想象成一个迷宫。 迷宫中的路径就是代码。 开发者希望特定路径用于某些操作, 而其他路径则代表未被 使用或无法触及的代码路径。
这些无法触及的路径 可能存在于你未使用的框架中,例如用 于开启摄像头或发送电子邮件的代码。
攻击者可以利用这些内存安全漏洞, 试图诱使程序进入一种意外状态, 从而执行与开发者实际意图截然不同的操作。
在这个例子中,预定路径 上的任何位置若存在内存安全漏洞, 都会导致通过创建或采取新路径, 从而产生完全出乎意料的新 方法来解决迷宫或运行代码。 这可能意味着调用该代码, 迫使应用程序开启摄像头或 发送电子邮件。
内存安全是安全的基础,如果没有它, 我们就无法保证更高层次的安全属性。 例如,无论你的密码哈希 方案有多强大都无济于事。 如果攻击者只需 利用一个内存安全漏洞就能访问系统。
下面是一个示例。这是一个简单的登录函数。 它从用户的文本输入框中获取密码的副本, 检查是否为调试版本,并跳过身份验证。 这样,开发者或许能 在自己的工位上更快地进行测试, 并调用一个函数,将密码的副本 与某个硬编码的密钥进行比对, 然后返回登录结果。 其中特别细心的读者。 你可能已经注意到了那个巨大的红色“X”。 这段代码中显然存在内存安全漏洞, 你真的不应该在这里复制它。 开发者在栈上为密码副本分配了 32 个字符的空间, 但复制 API 并不知道可用空间有多少, 它会将文本框中的所有字符都 复制到给定的缓冲区中,结果导致栈缓冲区溢出。
在内存的幕后,大致就是这样发生的。 本地密码存储区域紧邻用于存放 调试登录标志的内存区域。
如果密码长度为 18 个字符, 当然可以安全地容纳其中。
但如果攻击者发送了超过32个字符的密码呢?
在这种情况下,由于该程序使用的是不具备 内存安全性的编程语言, 它将开始覆盖相邻的内存区域。 而这里正是用于存储调试登录标志的内存区域。
结果是,无论开发者的初衷如何, 也无论该函数接收了什么参数, 当程序执行到这个 if 语句时, 如果该值已被攻击者覆盖或篡改, 攻击者就能强行绕过身份验证, 并让程序返回 true。
但实际上,这段代码中还存在一个更严重的问题。 覆盖调试标志确实能让攻击者无 需认证即可登录,但在内存中, 紧随调试登录标志之后的是 程序将用于执行比较操作的函数指针地址。
如果攻击者提供一个更长的密码, 他们可以利用本地密码缓冲区溢出, 覆盖调试登录标志, 并将该函数指针修改为任意内容。 这意味着当程序尝试调用其比较函数时, 实际上将进行的是…… 实际上,它将调用攻击者任意选择的函数。 此时,攻击者已完全控制该进程, 可以进入任何其他代码路径, 并可能窃取用户数据。
因此,该示例属于缓冲区溢出, 这违反了内存安全性的其中一项属性。 但实际上共有五个安全维度。 空间安全确保所有 内存访问都发生在内存分配的边界内。
生命周期安全确保内存仅在有效期间被使用, 且不会被重复用于其他用途。
类型安全确保程序员不会意外地 通过与预期不同的类型访问内存。
保证初始化确保内存在使用前已完成初始化。
最后,线程安全确保不同线程不会 互相覆盖对方的内存。
那么,攻击通常是这样的。 通常,针对任何应用的攻击者 不仅会试图访问该应用的数据 或入侵该应用并获取数据,还会将其作为第一步, 利用这一漏洞作为攻击链的起点, 进而攻击其他系统组件, 甚至提升权限至内核级别, 以破坏平台的安全模型。 因为他们的目标不仅是访问 系统中的文件,还包括位置数据、 照片、联系人、麦克风等其他资产。
因此,应用程序往往是更大规模攻击的入口。
与此同时,现代应用程序提供了 如此丰富的功能,以至于其中许多在正常使用时 已被授予访问上述大部分资源的权限。 随着操作系统不断完善其安全防护机制, 我们可以合理地预期, 未来攻击者可能会满足于仅利用您的应用程序, 并收集其在此环境中能够访问的数据, 而无需进一步渗透到平台的其他部分。 正因如此,我们现在齐心协力、 同步推进安全工作才显得尤为重要。
接下来,我将详细介绍 Apple 公司为保护 “信息”、“Safari”和“邮件 ”等应用及服务免受内存 安全漏洞影响而采取的策略。
在任何足够复杂的代码库中, 人们普遍认为漏洞总是难以完全避免。 这已经走得太远了。
因此,在苹果,我们依赖多层安全 工程机制,这些机制相互叠加、 互为补充。 今天我将介绍的许多 技术,后续都会有更深入的探讨, 但在此我主要介绍这些概念、 苹果如何思考其应用, 以及大家如何评估其有效性, 以及在应用程序中使用它们所需付出的努力。 我将探讨五种策略。
我将介绍如何通过使用 Swift 等内存安全语言, 让内存安全漏洞在您的代码中根本无法出现。
如何通过缩小可攻击面, 使漏洞无法被利用。
如何通过缓解措施, 使漏洞即使存在也难以被利用。
如何在发生漏洞利用时控制其危害。 最后,还将介绍一些用 于发现并消除代码中漏洞的技术。
防范内存安全漏 洞最全面的方法, 是使用一种从源头就几乎杜绝 此类漏洞产生的语言。
Apple 在 Swift 语言上投入了 大量资源,使其既安全又高效, 并且开箱即用就具备内存安全性。 它不仅驱动着许多应用程序, 还支撑着我们操作系统的部分组件, 其中包括越来越 多对安全至关重要的代码库, 例如 WebKit。 复杂的解析过程, 甚至像安全隔区这样的嵌入式环境, 都处理固件。
当然,许多应用都拥有大量用 C 语言家族编写的现有代码库。 这些代码并不具备内存安全性。
增强的安全性确实提供了诸如F 带 安全及其注解等工具, 用于强化C 和 C+ + 标准库, 以帮助您降低带安全错误的风险。 但请将它们视为通往内存 安全之路上的一个踏脚石。 它们并非最终目标, 因为它们并未解决其余四个维度的问题。
若从这五个维度比较基于 C 的语言和 Swift, 基于C 的语言不提供任何此类保护, 而 Swift 则通过使用 带边界检查的数组以及其他 Swift 标准库抽象来提供边界安全。 它通过自动引用计数和所有 权机制提供生命周期安全。 它通过要求在运行时检查 类型转换来保证类型安全。
Swift 通过要求在使用变量前对其 进行初始化来保证初始化安全; 借助 Swift 并发机制, 它还能通过防止数据竞争来提供线程安全。
以上就是关于如何 彻底杜绝内存安全漏洞的一个简要概述。 如需了解更多信息, 请稍后参加“在 Swift 中编写 安全敏感代码”的专题会议。
Apple 在安全关键型 代码库中大量采用的第二种 策略被称为“攻击面缩减”。 其理论基础在于:作为开发者和安全工程师, 你们无法确切知道代码以及所 依赖的框架中可能存在哪些漏洞。 但你可以将攻击者可能接触到的代码量限制 在实现功能所需的最小范围内。 这大大降低了应用中实际 使用的代码中存在漏洞的概率。
Apple 在整个系统中都采用了这一技术, 以缩小攻击者可利用的活动空间, 其范围涵盖了从限制可 解析的复杂文档格式, 到基于以下条件实现的功能: 如果对方是可信的,甚至可以在锁定模式 下禁用整个功能。
以格式限制为例, 如果你知道你的应用只会 发送和接收 JPEG 图片, 那么允许接收端代码 处理任何其他格式的图片, 就会向攻击者暴露数百万行解析代码, 使其有机会在根本不需要的地方寻找漏洞。 因此,在这种情况下,要立即见效并 提升安全性的最佳方式, 是检查图像是否为格式正确的 JPEG, 或者配置 Core Graphics 组件仅解析该格式, 若数据不符合预期则直接丢弃该流量。
您的应用还可能在以下情况下 无意中暴露了并非严格必要的攻击 面:通过 WebView 渲染 Web 内容 (例如直接用于应用内浏览), 以及通过从第三方(如广告 SDK)导入的框架渲染内容。
从 iOS 26.4 开始, WebKit 提供了两个强大的新选项, 即“增强安全 WebView ”( Enhanced Security Webviews ),这些模式允许您控制应用, 控制应用处理网络内容的方式, 并提供额外的安全强化措施。
默认情况下, WebView 在您的应用 内部提供了 WebKit 的全部 功能、性能和能力, 同时仍具备 Safari 的所有 安全缓解措施和架构。 第一种新模式是“限制模式”, “最大化兼容性”模式在保持 WebKit 当前 支持的完整网络兼容性的同时, 移除了大部分复杂的框架代码, 并增加了对最新硬件安全缓解措施的利用。
第二种新模式“限制模式:锁定”则更进一步, 它使您能够将应用中的 WebView 设置为与“锁定”模式相同的极高保护级别, 并移除鲜少使用的网络技术, 从而进一步限制攻击者的可利用途径。
在苹果,我们已开始在整个 操作系统中采用这些增强 安全性的 WebView, 包括邮件应用。 快速预览: iOS 26.4 中的“ 捕获门户”快捷方式和苹果广告。 现在正是您尝试这些功能的绝佳时机。
缩小攻击面能为安全投资带来更大的回报。 但我们仍必须假设,在任何使用 不安全语言编写的剩余代码中, 都可能存在内存安全漏洞。
在这种情况下,其次有效的防御 策略就是削弱恶意行为者利用这些漏洞的能力。
我们通过结合使用被称为“安全缓解 措施”的一系列技术来实现这一点, 而“缓解措施”本身是一个概念。 抱歉。作为概念,“缓解措施”并非新鲜事物。 它们在过去 30 到 40 年间 不断演进并一直存在, Apple 公司也在不断致力 于研发和提供新的缓解措施, 其中许多已可供您的应用使用。 当我们确信这些措施安全可靠时, 在许多情况下甚至会将其设为默认启用, 而无需您进行任何操作。
缓解措施的范围很广,从针对缓冲区 溢出的早期防御机制(如 clang 栈保护、 不可执行栈 和 ASLR),一直到现代硬件辅助技术, 例如内核完整性保护和快速权限限制。 在 Xcode 26 中,有。 这里提供了新的、 强大的软件和硬件辅助缓解措施, 供您保护应用程序。
指针认证是一项由编译器辅助的硬件技术。 它能够检测并防止整类漏洞被利用, 并确保即使 发生漏洞利用,应用程序的代码 流完整性也能得到维持。
随 iPhone 17 和 iPhone 17 Pro 推出的“内存完整性强制执行”功能, 是历经五年研究、 建模和工程开发的成果, 它基于安全分配器提供的坚实基础, 并结合了增强的内存标记、 扩展以及被称为“标记保密 性强制执行”的安全策略。 内容相当丰富,后续的专题会议 中还将深入探讨这些缓解措施。 在稍后关于“采用内存完整性 强制执行与指针认证”的会议中, 将对这些缓解措施进行更深入的探讨。 您还可以在 security.apple.com 博客上阅读关于我的全部内容。
Apple 认为,内存完整性 强制执行代表了消费级操作系统 历史上对内存安全性的最重大升级。
不过,在极少数情况下, 技术娴熟的攻击者仍可能找到一个漏洞, 尽管我们采取了缓解措施, 但该漏洞仍可被触及并利用。
在这种情况下,我们的最后 一道防线被称为“隔离”。
回顾前文提到的攻击链。 假设攻击者已经发现 并利用了应用中的内存安全漏洞。 此时,他们已完全控制了该应用。
理想的目标是将攻击者困在这样的环境中, 使其无法接触应用数据或平台的其他部分。
自 iPhone 问世之初, 应用程序便一直在沙盒中运行,这有助于防止 恶意访问并保护用户数据。 当然,应用程序仍然可以访问自身数据, 并使用提供这些功能的系统服务。 尽管面对高度复杂的攻击者时, 应用程序所依赖的框架需要更极端的隔离级别, 但不可能像这样简单地将整个应用 程序与系统完全断开。 因为应用需要过多的访问权限才能 在该环境中执行绘制用户界面或访问资源等操作。 因此,对于“信息”和“ Safari”等安全关键型应用, 必须采取新的方法。 Apple 设计了一种多进程架构, 将对攻击者提供的复杂数据的处理 转移到一个高度沙盒化的环境中, 该环境几乎无法访问系统资源, 例如其他服务甚至内核。
其结果是,我们将未知漏洞的风险 转移到了一个特殊环境中—— 即使攻击成功,攻击者也会被困在该环境中, 无法接触信息或 Cookie 等资产。 该环境还极大地限制了攻击者寻找提升权限 和访问用户数据的途径,从而增强了安全性。 在 Xcode 26 中, 您的应用也可以利用这些高度受限的安全环境, 它们被称为“增强安全扩展”。 我将详细说明“信息”应用 在接收照片时如何使用这种架构。
因此,“信息”应用会调用该安全扩展。 防护门和安全策略非常简单。 将所有接收到的资源均视为恶意资源, 直到它们被解析并分解为基本类型, 或在“防爆门”内部被引爆为止。 这一策略意味着, 权限较高的“消息”应用只需验证 从“防爆门”接收回来的简单类型, 而复杂且存在风险的解析 操作则在最低权限级别进行。
因此,消息通过互联网传入信息应用后, 该应用不会尝试对其进行任何解析或处理, 而是直接将其发送至安全“ 防爆门”进程内的“防爆门”。 现在是时候开始处理和解析图像了——请记住, 该图像可能是攻击者发送的。 显然,在绝大多数情况下,图像都是有效的, 信息应用会将其从一种可能非常复杂的类型 转码为更简单的格式, 以便进行验证并显示给用户。 如果图片无效,则会发生错误。 信息会直接丢弃该流量。 如果攻击者能够成功利用 blast 中的漏洞—— 尽管该漏洞位于第二个进程内, 且该进程无法访问信息数据库或系统的其他部分。
因此,blast 现在需要将图片返回给信息。 这里需要记住的一点是,我们应 考虑到该安全隔离进程可能已被突破。 因此,任何从该进程流出并 返回给信息的数据都不可信。 这可能是已成功转码的图像, 也可能是完全由攻击者控制的恶意数据。
正因如此,该架构最关键的一点 在于:`信息 ` 必须安全 可靠地验证所接收的数据是否为结构正确的图像 且符合预期格式, 这结合了前文提到的内存安全、 Swift 安全以及缩小攻击面这三项策略。 要安全地完成此 验证,并仅暴露最少的必要代码。 一旦图像通过验证, 便可安全地将其显示在对话记录中。
这只是苹果在安全敏感性最高的代码 库中如何运用隔离机制的一个例子。
最后,第五种可选方案是 在独立实施上述其他策略的同时, 尽可能多地发现并消除代码中的漏洞。 虽然这不足以完全防止攻击, 但能有效找出最薄弱的环节, 并像攻击者那样识别出最容易被利用的漏洞。
在苹果,我们结合了 手动和自动技术来帮助发现代码中的漏洞。 我们在开发过程中和事后都会对代码进行审核, 并使用 clang 静态分析器来辅助 人工审查以及支持持续集成工作流。
我们利用模糊测试( fuzzing)——这是一种由工具 自动为程序生成海量输入以 尝试发现漏洞的技术;此外, 地址净化器( address sanitizer) 和线程净化器( thread sanitizer) 等工具不仅有助于调试代码, 还能在开发过程中以及与模糊测试 等技术结合使用时,主动发现漏洞。
因此,这构成了苹果公司关于分层实现 内存安全代码的思路框架。您可以利用我刚 才提到的所有工具,通过使用 Swift 这样的内存安全语言,
理想情况下彻底杜绝漏洞; 通过缩小可利用的攻击面, 使漏洞无法被利用; 通过在代码中应用缓解措施, 使漏洞即使被发现也难以被利用;
若发生漏洞利用, 则通过使用增强型安全扩展来处理 复杂的解析任务,从而控制损害范围。 最后,在实施 上述其他策略的同时, 尽可能多地识别并消除代码中的漏洞。
现在,我将把发言权交给 德文(Devon),他负责领导 Apple 在安全领域语言和工具的开发工作。 谢谢。
谢谢,马克。大家好,我是德文 · 考夫林 (Devin Coughlin)。 我负责领导苹果公司的开发者安全工具团队。
Xcode 提供了一系列 经过精心挑选的保护措施, 旨在为您的应用提供最先进的安全保障。 我将详细介绍这些 具体的保护措施、如何启用它们, 并说明如何将它们结合起来以提供最大的保护。
安全的核心在于权衡内存安全方面的取舍。 最关键的权衡在于安全效益 与工程投入(例如代码修改和测试)之间。
不妨将这种权衡视为一张坐标 图:纵轴代表安全收益, 横轴代表实施难度。
有些保护措施易于实施,但带来的收益较少。
另一些则实施难度较大,但能提供更高的安全性。
权衡取舍的要义在于:以最小的投入 获得最大的收益。
这就是位于右上角的“最佳平衡点”。
在 Apple, 我们的目标 是紧密协同设计硬件、 操作系统和编程语言, 在保持良好性能的同时, 提供接近该“最佳平衡点”的保护措施。
例如,内存完整性强制机制不仅安全性强, 而且与仅依靠软件的保护 措施相比,其采用成本大幅降低,性能也更高。
我将介绍四类不同的保护措施。
漏洞检测工具,可帮助您 在产品发布前发现安全漏洞, 并为采用更强的安全措施铺平道路。
全应用保护,通常只需点击一下按钮, 即可提升整个应用的安全性。
针对C 和 C+ +的代码加固, 可帮助您为现有不 安全的代码库添加边界安全机制;
而 Swift 则提供了全面的内存安全保障。
这些正是 Mark 所描述的策略, 但我将深入探讨可用于实现这些策略的具体技术。
马克将这些策略按对新代码 库的适用优先级从高到低进行了排序。 这就是入手点。 从一开始就为整个应用提供最高级别的保护。
但在为已遭受 攻击的现有应用加装安全防护时, 必须有策略地考虑:哪些保护措施可以立即实施, 哪些则需要一定时间。
我将按照实施 难易程度进行讲解,从部署更简单、 能快速提升安全性的方案开始, 到虽然极其强大但需要更长 时间才能付诸实践的防护措施。
我在此旨在帮助大家了解这些不同的技术, 以便让您的应用尽可能安全。
我将从漏洞检测工具开始讲起。
这些并非在已发布的应用中运行的主动防护措施。 相反,它们是在构建和调试 阶段使用的,旨在帮助您在应用发布前发现漏洞。 通过早期发现漏洞, 它们也为更广泛地采用安全技术铺平了道路。
这些工具位于右下象限。
它们使用起来非常简单。 只需运行它们,然后开始修复问题即可。
它们能发现可修复的漏洞,但无法发现所有漏洞。 因此,尽管它们是一种极好的预防性措施, 但采用我稍后将介绍的其他安全措施至关重要。
好消息是,运行这些工具能让 实施这些防护措施变得更加容易。
我将首先介绍的漏洞检测工具是“ 净化器”(sanitizers)。
它们会在应用程序中加入额外的仪器化代码, 并在应用程序运行时帮助发现漏洞。 这些工具适用于基于 C 语言的编程语言和 Swift。
净化器的最大优点在于误报率极低。 如果净化器报告了漏洞,那就确实存在漏洞。
地址 sanitizer 通过 追踪哪些内存位置有效、 哪些无效,来发现内存安全漏洞。
它非常擅长发现 Mark 之前 演示的那种缓冲区溢出, 以及“释放后 使用”漏洞——即程序员释放内存后, 却意外留下了悬空指针。
它能检测堆、栈以及全局变量中的内存损坏。
它还会提供内存分配和释放位置的回溯信息。 以帮助您快速修复漏洞。
线程净化器(Thread Sanitizer )可检测数据 竞争(data races )——当一个线程在未与另 一个线程进行同步的情况下, 干扰另一个线程访问的内存时, 就会发生这种情况。
即使在自动引用计数机制下, 这也会导致内存损坏。
接下来是 clang 静态分析器。
它无需运行您的应用程序即可发现漏洞。 相反,它会模拟程序中的可能执行路径。 这意味着您无需测试覆盖率即可发现错误。 但相应的代价是,该工具可能会产生误报。 也就是说,它可能会在实际上 不存在错误的情况下报告错误。
该分析器支持 C、 C + + 和 Objective-C。
它能发现许多与安全相关的错误, 包括缓冲区溢出、冻结后使用、 使用未初始化内存以及不安全的 API 使用。
请在目标的构建设置中启用这些检查, 然后从“产品”菜单中 选择“分析”来运行分析器。
当分析器发现错误时, 它会显示该问题以及错误出现的控制流路径。
箭头标示了导致该漏洞的程序路径中的每个步骤,
注释则描述了路径上的关键事件。
例如,在“释放后使用”场景中, 该路径会显示内存的分配、释放以及 后续使用位置。
这使得理解和修复该漏洞变得非常容易。
Address Sanitizer、 Thread Sanitizer 和 Clang 静态分析器是 帮助您开发代码的关键漏洞检测工具。 请在构建和测试阶段使用它们。 它们能发现部分漏洞,但并非全部。
运行这些工具是迈出的重要第一步, 有助于您更轻松地在应用 中采用更强大的保护措施。 需要明确的是,还有更多工作需要完成。 但这些工具确实为添加其他 必要的保护措施铺平了道路。
接下来,我将介绍针对整个应用的保护措施。 这些措施为整个应用程序(包括代码、 库和系统框架)提供了出色的基础级安全强化。 它们易于添加,并能极大提升安全性。
我将介绍五种不同的全应用保护 措施:类型化分配器、硬件保护、 内存标记、指针认证、 只读内存以及增强型安全扩展。
第一种全应用保护措施是类型化分配器。
这是一种新的系统内存分配器, 可防范“释放后使用”攻击。 它非常出色,因为它位于 右上角的“最佳点”附近。
它不仅防护效果良好,而且极其易于采用。
“释放后使用”漏洞是一种 内存损坏形式:程序分配内存并将其释放后, 却意外地留下了悬空指针。 被释放内存的类型被称为“受害者”。
攻击者通过操纵程序在同一位置 分配另一种类型的“侵略者”内存, 从而利用该悬空指针, 进而导致“受害者” 指针将“侵略者”中的数据误认为“ 受害者”类型并进行访问。 这就是类型混淆。 如果“侵略者”能够诱使 “受害者”将攻击者控制的数据作为指针读取, 攻击者便可以修改非目标内存, 甚至调用意外的代码。
借助类型化分配器, 编译器和操作系统协同工作, 通过将不同类型的内存分配到不同的内存桶中, 从而以概率方式防止类型混淆。
攻击者分配的内存中的“受害者” 很可能被放置在不同的内存桶中, 因此攻击者无法依赖类型混淆。
Apple 安全 研究博客上有一篇关于下一代 Xnu 内存安全性的精彩文章, 其中详细介绍了将此方法应用于内核的具体细节。
要启用类型化分配器: 进入“签名与功能”编辑器。 添加“增强安全”功能,并启用构建设置。
立即启用该功能。它能为“释放后 使用”漏洞提供非常强大的防护。 操作非常简单,只需勾选一个复选 框并重新编译应用即可。
第二类全应用保护机制是硬件内存标记。 它对缓冲区溢出、释放后使用漏洞以及堆 分配内存具有极强的防护能力。
这是一项 CPU 功能,旨在与类型 化分配器及内核的标记保密性协同工作, 以强制执行内存完整性。
该功能可在 iPhone 17、 iPhone Air 以及基于M5的 Mac 和 Vision Pro 上使用。
硬件内存标记功能非常接近右上角的“最佳点”。 它既具备高防护性,又易于采用, 同时保持较低的性能开销。
以下是它如何防范内存损坏的。
类型化分配器会为每个指针 和每次内存分配关联一个标记。
随后, CPU 会确保该标记、 指针以及内存中的标记相互匹配。 若不匹配,则表明存在“ 释放后使用”漏洞或缓冲区溢出, 此时硬件会安全地触发中断, 而非放任内存损坏发生。
以下是一个缓冲区溢出的示例。
受害者指针的标记与其所指向的内存匹配, 攻击者指针的标记也与其所指向的内存匹配。 在缓冲区溢出攻击中, 攻击者通过“攻击者”指针 向“受害者”内存溢出数据。 但由于“攻击者”指针的标签 与“受害者”内存的标签不匹配, CPU 会检测到这种不一致, 从而阻止攻击继续进行。
内存标签不匹配会导致应用程序崩溃。 因此,在启用保护测试之前, 请先开启内存标签诊断功能, 并在“地址净化器”和“线程净化器”的检测下,
修复任何内存损坏问题。
要了解如何启用硬件内存标记, 请在 developer.apple.com 上观看《通过内存完整性 强制执行保护您的应用》。
现在,即使启用了安全分配器和硬件内存标记, 某些内存损坏漏洞仍然可能被利用。
第三种全应用保护机制是指针认证。
通过指针认证,硬件、 编译器和操作系统协同工作, 通过强制执行应用中的控制 完整性来提供多层防御。
该功能适用于 iPhone 10 及更新机型, 以及所有 Apple 芯片 Mac 电脑。
在控制流完整性攻击中, 攻击者利用内存损坏来劫持应用的控制权。
这会使程序进入开发者从未预期的异常状态。
回想一下 Mark 之前描述的栈缓冲 区溢出案例:攻击者通过使密码缓冲区溢出, 覆盖了附近的函数指针。
通过提供一个超长的密码, 攻击者可以将函数指针的值 更改为任意内容。 随后,他们可以调用程序中的任何函数, 从而导致程序进入异常状态。
借助指针认证,
CPU 会在指针被使用前 对其进行加密签名并验证其真实性。 如果指针的签名与预期不符, 硬件会安全地进行捕获,而不是调用缓冲
区溢出示例中的意外代码。 这将阻止攻击者调用任意函数。
指针认证与内存完整性强制机制配合得当, 可作为保护的最后一道防线。
它在“效益-采用”图表中占据核心位置。 该机制防护效果良好, 但采用时可能需要一些工作, 特别是在复杂的 C+ + 代码库中。
因此,启用后请彻底测试您的应用程序, 以确保不会发生崩溃。
而且,由于该功能与类型化分配器和硬件内存 标记结合使用时效果最佳, 请先采用这些技术,再着手处理指针认证。
这需要构建一个包含 arm64 和 arm64-e 切片的通用二进制文件, 以便您的应用能在没有硬件支持的旧设备上运行。
这意味着您应用中的所有库也必须是通用的。
因此,如果您依赖供应商提供的二进制库或框架, 则需要与他们合作以获取该依赖项的通用版本。
第四项保护措施是只读平台内存。
动态加载器负责加载应用及其库中的代码。 如果攻击者能够篡改动态加载器的元数据, 它将成为极具吸引力的攻击目标。 他们可以利用它完全控制您的应用。
只读平台内存可防止攻击者修改这些数据, 因此只有动态加载器本身才能对其进行更改。 它提供了宝贵的安全保护,且极其易于采用。
大多数应用无需进行任何更改
即可确保兼容性。请使用系统 API 来修改 De Wilde 和 Objective-C 运行时元数据。 切勿直接修改它们。
最后一种全 应用保护方式是“进程外增强安全”扩展。
正如 Mark 所描述的, 这种方法是一种隔离机制。 尽管其实现需要一定投入, 但能提供非常强大的安全保障。
应用时,将处理不可信数据的代码移至扩展中。
然后使用安全的系统 API 将数据从主应用中传输出来。 在扩展中处理这些不受信任的数据, 将结果传递回主应用程序,并确保对其进行验证。
现在,隔离机制假设攻击 者能够利用内存损坏来破坏扩展。
其目标是将攻击者限制在该进程内, 从而使其无法访问主应用程序中的数据。
要采用此方法,请评估应用程序中 哪些部分会处理不受信任的数据。
重构代码库,将这些部分分离到不同的进程中, 并对扩展程序返回的响应进行验证。 不要轻信它。
这只是对其他防护策略的补充。 不能替代它们。 因此,请在为整个应用启用内存 完整性强制执行后再采用此方案。 由于代码拆分可能需要一些时间。 请在实施指针认证的同时并行推进此项工作。
全应用保护措施仅是其中几个步骤。 或者,只需在 Xcode 中 执行几个步骤即可采用: 启用类型化分配器、硬件内存标记、 指针认证和只读内存。 通过采用增强安全功能,
然后创建一个增强安全扩展。
采用这些全应用保护措施, 为您的整个应用提供强有力的基础安全保障。
这些保护措施固然很好, 但如果您的应用中存在用 C 语言编写的攻击面, 则需要更强的保护。
在采用全应用保护措施后, 对于处理不可信输入的不 安全代码库,还应应用更强有力的防护措施。
Xcode 为 C 和 C++ 均 提供了保护措施。
我将从 C++ 开始讲起。
大多数代码库都会使用标准库中的容器 类及其他广泛使用的抽象类。
C+ + 标准库加固可防范标准 `span` 和 `vector` 等类中的越界访问。 对于大量使用标准库的代码库,
该机制会安全地捕获异常而非放任内存损坏发生; 在安全采用权衡图中,加固措施位于右下象限。 该措施非常易于采用,且能提供可靠的保护。
若需更强的保护, Xcode 的“C + + 中使用边界安全 缓冲区”选项提供了更强的保障。
在此模式下,编译器会拒绝 不安全的原始指针运算。 取而代之的是,它要求使用标准 `span`、 `string` 和 `vector` 等符合惯例的库抽象。
通过这种方式,结合用于防止 原始指针运算的编译时检查、 运行时保护以及强化后的 C+ + 标准库, 为该语言带来了边界安全性。
然而,与 C+ + 不同, C 语言缺乏诸如运算 符重载等高级语言特性,无法轻松使用库 抽象来实现边界安全的指针运算。
为弥补这一缺口, Apple 为 C 语言创建了一个新的语言扩展, 该扩展直接内置了 边界安全机制,并正致力 于将此扩展纳入语言标准。
其工作原理如下。 要安全地对指针进行索引, 需要了解该指针所指向的内存边界信息。 因此,为了确保安全, 当编译器无法确定指针的边界时, 会通过报错来阻止对该内存区域的索引操作。 随后,您可以在注解中 添加边界信息,告知编译器如何确定边界,
编译器便会插入边界检查,以便 在运行时安全地捕获越界访问。
通过这种方式,程序员提供的注解与编译 器生成的运行时边界检查相结合, 实现了边界安全。
Xcode 中 C和 C+ +的边界 安全功能位于象限的左上角。 它们提供了强有力的保障,消除了整类漏洞, 但确实需要对源代码进行修改。 建议在已采用内存完整性 强制等全应用保护措施后, 再应用这些功能, 从而为最敏感的 C 和 C+ + 代码增添更多安全保障。
到目前为止, 我主要讨论了那些旨在为不 安全的 C、 C + + 和 Objective-C 代码库加装保护措施的工具。 这些都是强有力的保护措施, 但要实现完全的内存安全, 则需要一种内存安全的语言。
Swift 具备内存安全性, 并充分利用了操作系统硬件 和语言层面的平台安全保护机制。
Swift 6.2 提供了 一组全新的轻量级库抽象, 专为低级应用、解析器以及 其他对安全性和性能要求极高的场景而设计。
例如,新的 span 类型家族允许 访问连续的未拥有内存。
Span 完全具备内存安全性。 它利用高级特性 和 Swift 标准库, 同时保证生命周期安全与边界安全。
Span 还具有高速运行特性。 如果您需要确保生命周期安全与高度 优化的边界检查在运行时零开销, 它将是一个绝佳的选择。
借助 Swift Span, 编译时对生命周期安全的检查 与运行时对边界安全的检查相结合, 既保证了安全性,又保持了低开销。
有两种策略可以保障您的应用 安全:采用 Swift 编写新代码,以及重写现有的安全敏感代码。
请同时采用这两种策略。
Swift 让编写内存 安全的代码变得轻而易举。 如果您尚未开始用 Swift 编写新代码,请立即行动。
您今天编写的代码,将成为未来攻击者的目标。 所以,没错。Swift 的新特性。 当您重构或现代化现有子系统时, 请抓住这个机会, 将它们也用 Swift 重写。
为了缩小您的安全攻击面。 不要等待机会主义式的重构。 相反,应主动将这些组件重写为 Swift。
最需要专注的关键领域包括解析 器(尤其是媒体和协议解析器)、复杂的状态机、 对象生命周期控制, 以及任何处理不可信输入的代码。
现在您已经知道如何保护 应用免受恶意攻击者的侵害。
这些正是 Apple 为其 自有应用采用的防护措施。 当这些措施经过精心设计、 分层部署并应用于代码库的关键位置时, 将为您提供业界顶尖的安全防护。
要采用这些措施,请在 Xcode 中启用增强安全功能, 包括内存完整性、强制执行 和指针认证,为整个应用提供基础级别的保护。
限制对不可信输入的处理, 并使用增强型安全扩展; 同时利用绑定安全扩展来保护您不 安全的 C 和 C+ + 代码库。
此外,请使用 Swift 语言— —它能为所有新 代码以及针对安全面的重写 提供全面的内存安全性。
如今,安全性比以往任何时候都更为重要。 请立即采取这些措施,以保护您的应用、 用户及其敏感数据。谢谢。
-