在之前的一篇博文中,我们描述了一个影响所有基于 AppKit 的 macOS 应用程序的进程注入漏洞。这项研究在Black Hat USA 2022、DEF CON 30和Objective by the Sea v5上进行了展示。这个漏洞实际上是我们向 Apple 报告的第二个通用进程注入漏洞,但它比第一个漏洞修复得更早。因为它与第一个漏洞共享了漏洞利用链的某些部分,所以我们不得不在之前的文章和演示文稿中跳过一些步骤。现在第一个漏洞已经在 macOS 13.0(Ventura)中得到修复,并在 macOS 14.0(Sonoma)中得到改进,我们可以详细介绍第一个漏洞,从而填补上一篇文章的空白。
此漏洞由 Adam Chester 独立发现,并以“DirtyNIB”为名在此处撰写。虽然 Adam 演示的漏洞利用链与我们的漏洞利用链有很多相似之处,但我们的攻击是自动触发的,不需要用户单击按钮,因此更加隐蔽。因此,我们决定也发布我们自己的此文版本。
通过替换资源进行进程注入
概括来说,进程注入是指一个进程能够像另一个进程一样执行代码(从操作系统的角度来看)。这授予了该进程其他进程的权限和权利。如果该进程具有特殊权限(例如,如果用户授予了对麦克风或网络摄像头的访问权限,或者该进程具有权利),那么恶意应用程序现在也可以滥用这些特权。
进程注入技术的一个著名示例涉及 Electron 应用程序。Electron 是一个框架,可用于将 Web 应用程序与 Chromium 运行时相结合以创建桌面应用程序,从而允许开发人员为其 Web 应用程序和桌面应用程序使用相同的代码库。
在 macOS 13.0 (Ventura) 之前,macOS 上应用程序包的代码签名工作方式如下。有两种不同的方法可以检查应用程序的代码签名:浅层代码签名检查和深度代码签名检查。
当应用程序从互联网上下载后(即被隔离),Gatekeeper 会执行深度代码签名检查,这意味着应用程序包中的所有文件都经过验证。对于大型应用程序(例如 Xcode)和慢速磁盘,此过程可能需要几分钟。
当应用程序未被隔离时,只会执行浅层代码签名检查,这意味着只检查可执行文件本身的签名。如果可执行文件启用了强化运行时功能“库验证”,则在加载所有框架时还会对其进行检查,以验证它们是否由同一开发人员或 Apple 签名。这意味着,对于最近未被浏览器下载的应用程序,应用程序包中的非可执行资源不会在启动时通过代码签名检查进行验证。
Electron 应用程序的部分代码包含在 JavaScript 文件中,因此这些文件未通过浅层代码签名检查。这允许针对这些应用程序进行以下进程注入攻击:
-
将应用程序复制到可写位置。
-
用恶意 JavaScript 文件替换 JavaScript。
-
启动修改后的应用程序。
现在恶意 JavaScript 可以使用原始应用程序的权限,例如在未经用户批准的情况下访问网络摄像头或麦克风。(Electron 在支持视频通话的应用程序方面尤其受欢迎!)
由于这次攻击众所周知,我们开始思考:应用程序包中可能还包含哪些其他资源,可能导致进程注入?然后,我们发现该MainMenu.nib文件隐藏在许多 macOS 应用程序中。事实证明,该文件也可以交换,并且浅层代码签名检查仍会通过。如果我们替换该文件,会有什么影响?
Nib 文件背景
Nib(NeXT Interface Builder 的缩写)文件主要用于设计原生 macOS 应用程序的用户界面。引用 Apple 的文档:
nib 文件描述应用程序用户界面的可视元素,包括窗口、视图、控件等。它还可以描述非可视元素,例如应用程序中管理窗口和视图的对象。最重要的是,nib 文件对这些对象的描述与 Xcode 中的配置完全一致。在运行时,这些描述用于重新创建应用程序内的对象及其配置。在运行时加载 nib 文件时,您将获得 Xcode 文档中对象的精确副本。nib 加载代码会实例化对象、配置它们,并重新建立您在 nib 文件中创建的任何对象间连接。
过去,nib 文件是直接用一个名为“Interface Builder”的应用程序编辑的,因此得名。如今,它已集成到 Xcode 中,不再直接编辑 nib 文件,而是编辑基于 XML 的文件(xib 文件),然后将其编译为 nib 文件,因为基于文本的文件在版本控制系统中更易于管理。虽然现在有创建 GUI 的新选项(StoryBoards 和 SwiftUI),但许多应用程序仍然包含至少一个 nib 文件。
Nib 反序列化利用
现在,我们将逐步评估如果将应用程序中的 nib 文件替换为恶意版本,我们可以做些什么。每一步都会增加一点我们能做的事情,最终导致执行与本机代码相同的代码。
步骤 1:掌控局面
让我们看一下使用 macOS“App”模板新创建的 Xcode 项目,并使用 xibs 作为界面。
我们最终得到一个名为的文件,main.m包含以下内容:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
}
return NSApplicationMain(argc, argv);
}
正如评论所暗示的那样,应用程序开发人员可以在此处实现一些设置,但默认情况下它仅调用NSApplicationMain。该函数执行许多不同的步骤,将流程转变为应用程序。此功能的文档解释了它的作用:
创建应用程序,从应用程序的主包中加载主 nib 文件,然后运行应用程序。您必须从应用程序的主线程调用此函数,并且通常只从应用程序的main函数中调用一次。您的main函数通常由 Xcode 自动生成。
Info.plist它通过解析应用程序包中的文件并查找键的值来确定主 nib 文件NSMainNibFile。然后加载并实例化具有该名称的 nib 文件。
文件内容Info.plist:
...
<key>NSMainNibFile</key>
<string>MainMenu</string>
...
默认模板包含一个新类的实现,AppDelegate该类由 nib 文件实例化。在方法中-applicationDidFinishLaunching:,开发人员可以执行加载 nib 文件后应进行的进一步设置。在大多数应用程序中,这是将控制权交还给应用程序自己的代码并开始自定义初始化的地方。
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (BOOL)applicationSupportsSecureRestorableState:(NSApplication *)app {
return YES;
}
@end
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end
虽然 nib 文件类似于使用 序列化的数据NSCoder,但这表明无需实现或NSCoding即可NSSecureCoding将对象实例化为 nib 的一部分。事实上,(几乎)所有类的对象都可以通过将它们包含在 nib 文件中并实例化 nib 文件来创建。
回到Apple 文档页面证实了这一点:
[底层 nib 加载代码] 解压 nib 对象图数据并实例化对象。它如何初始化每个新对象取决于对象的类型以及它在存档中的编码方式。nib 加载代码使用以下规则(按顺序)来确定使用哪种初始化方法。
a. 默认情况下,对象会收到一条initWithCoder:消息。[…]
b. OS X 中的自定义视图会收到一条initWithFrame:消息。[…]
c. 除上述步骤中描述的对象之外的自定义对象会收到init消息。
有几个类不支持这三种方法中的任何一种,而只有专门的 init 函数或构造函数。除这些之外,任何类的对象都可以在 nib 文件中创建,即使是像NSTask或 这样的“危险”类NSAppleScript。
此时我们可以:
-
停止应用程序自身的代码执行。
-
创建任意类的对象。
步骤 3:调用零参数方法
调用不带任何参数的方法的技巧与上一篇文章中相同:通过创建绑定。例如,使用对象的 keypath 进行绑定launch将在对象实例化并创建绑定后立即NSTask调用该方法-launch(请参阅前面提到的 Apple 文档页面)。
从 Xcode 创建这些绑定并不总是可行的,因为应该只绑定到模型的属性。但是,xibs 的 XML 格式使得手动创建与任何对象和任何键路径的绑定变得非常容易。此外,这允许指定方法的调用顺序,因为绑定是按照这些绑定的“id”属性的排序顺序创建的。
例如,以下 XML 将窗口的“title”属性绑定到 NSTask 对象的“launch”键路径:
<objects>
<customObject id="N6N-4M-Hac" userLabel="the task" customClass="NSTask">
[...]
</customObject>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g">
[...]
<connections>
<binding destination="N6N-4M-Hac" name="title" keyPath="launch" id="cy5-GO-ArU"/>
</connections>
</window>
[...]
</objects>
虽然我们可以调用-launch,但我们还没有为此设置可执行路径或参数NSTask,所以这还不是很有用。
此时我们可以:
-
停止应用程序自身的代码执行。
-
创建任意类的对象。
-
在这些对象上调用零参数方法。
步骤 4:调用任意方法
对于按钮或菜单项,可以使用 绑定target。selector这决定了当用户单击它时它将调用什么方法以及在哪个对象上调用。这些绑定非常灵活,甚至允许Argument2为调用指定任意数量的参数( 等)。
这样,我们就可以调用用户点击后任意方法。但是,我们不想等待。我们希望所有代码在 nib 加载后自动运行。
事实证明,如果我们设置目标和菜单项参数的绑定,然后调用私有方法_corePerformAction,它将执行其操作的方法,就像用户单击它时一样。因为该方法本身不需要参数,所以我们可以使用前一个原语调用它。这意味着我们创建两个绑定(如果我们想传递参数,则可以创建更多绑定):首先设置目标和选择器,然后设置参数的绑定,最后一个调用_corePerformAction。这使我们能够调用具有任意数量参数的任意方法。此调用的参数可以是任何我们可以在 nib 中绑定的对象(可以选择带有键路径)。
这仍然有两个局限性:
-
我们无法保存通话结果。
-
我们不能传递任何非对象值,例如整数或任意指针。
实际上,这些限制对我们来说并不重要。
此时我们可以:
-
停止应用程序自身的代码执行。
-
创建任意类的对象。
-
在这些对象上调用零参数方法。
-
使用任意对象作为参数调用方法,而不保存结果。
步骤 5:字符串常量
对于某些方法,我们希望引用某些常量值,例如字符串。虽然NSString实现了NSSecureCoding它们,因此我们应该能够将它们作为序列化对象包含在 nib 文件中,但我们不清楚如何将它们写入 xib。幸运的是,我们找到了一个简单的技巧:我们可以创建一个NSTextView,在 Xcode 中用文本填充它,然后使用 keypath 绑定到此视图title。(具有讽刺意味的是,这意味着我们现在使用绑定的方向与它们的预期使用方向相反,我们不是将视图绑定到模型,而是将“模型”绑定到视图!)
将所有这些放在一起,我们现在可以在任何应用程序中执行任意 AppleScript:
-
我们将该类的一个对象添加NSAppleScript到xib中。
-
我们向 xib 添加一个NSTextField,其中包含我们想要执行的脚本。
-
我们设置了两个NSMenuItems,并绑定调用对象上的方法-initWithSource: 1和。-executeAndReturnError:NSAppleScript
-
对于-initWithSource:绑定,我们绑定一个参数,即元素title的NSTextField。对于-executeAndReturnError:我们不添加任何参数,因为我们不关心错误结果(因为我们已经完成了)。
-
我们创建两个额外的菜单项(也可以是任何其他对象)来绑定到_corePerformAction其他菜单项上的属性以触发其操作。绑定的顺序设置为先绑定目标和参数,然后创建两个_corePerformAction绑定。
一旦此 nib 文件在任何应用程序中加载,它就会在该进程内运行我们自定义的 AppleScript。
我们现在已经将 Xcode 的 xib 编辑器变成了我们的 AppleScript IDE!
执行任意 AppleScript 可以让我们实现以下目的:
-
使用应用程序的权限读取或修改文件(例如,如果我们攻击 Mail.app 或具有完全磁盘访问权限的应用程序,则读取用户的电子邮件)。
-
执行 shell 命令。这些命令继承了应用程序的 TCC 权限,因此,如果原始应用程序具有该权限,我们就可以访问麦克风和网络摄像头。
这是一个很好的结果,并且已经展示了漏洞。但是我们想要展示一个特权提升漏洞,但目前我们还无法利用我们拥有的原语来实现。我们需要更进一步。
插曲:在 macOS 中编写运行时脚本
对于我们想要实现的三条漏洞利用路径之一,评估 AppleScript 是不够的。我们需要滥用未被任何子进程继承的权限,并且它所需的 API 无法从 AppleScript 访问。此外,由于强化运行时的库验证,我们无法将新的本机代码加载到应用程序中。
总结一下我们到目前为止可以做的事情:
-
停止应用程序自身的代码执行。
-
创建任意类的 Objective-C 对象。
-
在这些对象上调用零参数方法。
-
使用任意对象作为参数调用方法(不保存结果)。
-
创建字符串文字。
我们还希望能够做到以下几点:
-
调用 C 函数。
-
创建 C 结构。
-
使用 C 指针(例如char *)。
我们可以做的一件事是加载更多由 Apple 签名的框架。因此,我们研究了 macOS 中包含的动态语言,以确定它们是否允许我们执行一些我们尚无法执行的操作。(请注意,这是在 Apple 决定从 macOS 中删除 Python.framework 之前。)
我们在 Apple 签名的框架中发现了以下运行时:
-
AppleScript
-
JavaScript
-
Python
-
Perl
-
Ruby
(我们后来也发现了 Tcl 和 Java,但是当时我们并没有研究它们。)
其中大多数在某种程度上是不合适的。AppleScript 没有 FFI,JavaScript 需要明确向脚本公开特定方法。Perl 和 Ruby 确实有 C FFI 库,但这些库需要编写编译的小存根。加载这些库将被库验证阻止。
Python.framework 是唯一合适的选择:该ctypes模块(包含在 macOS 中)满足了我们所有的需要,并且即使启用了强化运行时它也能正常工作2。
我们可以将 Python.framework 加载到应用程序中,但这不会立即开始执行任何 Python 代码。为了运行 Python 代码,我们需要先调用 C 函数,因为 Python.framework 只有一个 C API。在调用 Python 之前,我们还需要另一个中间步骤。
步骤 6:AppleScriptObjC.framework
我们最初错过了一个语言选项:带有 AppleScriptObjC 桥的 AppleScript。这允许执行 AppleScript,就像 一样NSAppleScript,但可以访问 Objective-C 运行时。它允许定义完全在 AppleScript 中实现的新类,并且(对我们来说很重要)它允许调用 C 函数。
这需要加载一个额外的框架:AppleScriptObjC.framework。由于这是由 Apple 签名的,我们可以将其加载到任何应用程序中。然后,尽管强化运行时不允许加载新的本机代码,但我们可以通过调用 仅从(未签名的)包中加载 AppleScriptObjC 脚本-loadAppleScriptObjectiveCScripts。
我们可以调用的 C 函数是有限的:我们只能传递原始值(整数等)或 Objective-C 对象指针。我们不能使用任意指针,因此传递结构或char*值是不可能的。因此,这还不够,我们确实需要评估 Python。
我们现在可以:
-
停止应用程序自身的代码执行。
-
创建任意类的对象。
-
在这些对象上调用零参数方法。
-
使用任意对象作为参数调用方法(不保存结果)。
-
创建字符串文字。
-
调用 C 函数(使用 Objective-C 对象或原始值作为参数)。
步骤 7:调用 Python
如果您查看Python C 接口,您会发现所有要传递一些 Python 来执行的函数都需要 C 字符串 ( char*/ wchar_t*):脚本的文件路径或脚本本身。如上所述,我们无法使用 AppleScriptObjC 桥传递这些类型的对象。
我们通过调用设置为 的Py_Main(argc, argv)函数来绕过它。这是可执行文件通过命令行调用时调用的相同函数,这意味着不带参数调用它会启动 REPL。通过这样调用它,我们可以在受感染的应用程序中启动 Python REPL。可以通过将 Python 代码写入进程来传递要执行的代码。argc0pythonstdin
我们实现此目的的 AppleScriptObjC 代码是:
use framework "Foundation"
use scripting additions
script Stage2
property parent : class "NSObject"
on initialize()
tell current application to NSLog("Hello world from AppleScript!")
-- AppleScript seems to be unable to handle pointers in the way needed to use SecurityFoundation. Therefore, this is only used to load Python.
current application's NSBundle's alloc's initWithPath_("/System/Library/Frameworks/Python.framework")'s load
current application's Py_Initialize()
-- This starts Python in interactive mode, which means it executes stdin, which is passed from the parent process.
tell current application to NSLog("Py_Main: %d", Py_Main(0, reference))
end initialize
on encodeWithCoder_()
end encodeWithCoder_
on initWithCoder_()
end initWithCoder_
end script
有了ctypes,我们现在可以运行 Python 代码,其功能基本上与本机代码相同:调用任何 C 函数、创建结构、取消引用点、创建 C 字符串等。
我们执行的 Python 脚本是对来自Unauthd – Logic bugs FTW 的权限提升漏洞的直接改编,作者是 A2nkF:将特定的 Apple 签名的软件包文件安装到 RAM 磁盘,然后以 root 身份从该磁盘执行脚本。
影响
与之前博客文章中的漏洞一样,此漏洞可以在 macOS Big Sur(报告时处于测试阶段)上以不同的方式应用:
-
窃取应用程序的 TCC 权限和授权,从而允许访问网络摄像头、麦克风、地理位置、敏感文件(如 Mail.app 数据库)等。
-
system.install.apple-software使用权利和将权限升级到 root macOSPublicBetaAccessUtility.pkg。
-
com.apple.rootless.install.heritable通过滥用“macOS Update Assistant.app”的权限绕过 SIP 的文件系统限制。
以下视频演示了在 macOS Big Sur 测试版中提升权限和绕过 SIP 的文件系统限制。(请注意,此视频的速度为 200%,因为安装软件包的速度非常慢且不可见。)
与之前的博文不同,我们没有找到逃离沙盒的方法,因为沙盒应用程序无法复制另一个应用程序并启动它。虽然 nib 文件也用于 iOS 应用程序,但我们没有找到在那里应用这种技术的方法,因为 iOS 应用程序沙盒也使得修改另一个应用程序的包变得不可能。除此之外,漏洞利用还需要遵循完全不同的路径,因为绑定、AppleScript 和 Python 不存在于 iOS 上。
修复
macOS Ventura 中通过为 macOS 添加新保护修复了此漏洞。首次打开应用程序时(无论是否被隔离),都会执行深度代码签名检查。之后,应用程序包将受到保护。此保护意味着只有来自同一开发人员的应用程序(或在应用程序Info.plist文件中明确允许的应用程序)才允许修改包内的文件。添加了新的 TCC 权限“应用程序管理”,以允许其他应用程序也可以修改其他应用程序包。据我们所知,Apple 尚未将这些更改反向移植到早期的 macOS 版本(Apple 已澄清他们不再将所有安全修复反向移植到当前主要版本之前的 macOS 版本)。此更改不仅解决了此问题,还解决了在 Electron 应用程序中替换 JavaScript 的问题。
请注意,Homebrew要求您授予终端“应用程序管理”(或“完全磁盘访问”)权限。这是一个坏主意,因为它会让您再次容易受到这些攻击:任何非沙盒应用程序都可以通过在(例如)中添加恶意命令来使用您终端的 TCC 权限执行代码~/.zshrc。授予终端“应用程序管理”或“完全磁盘访问”应被视为与完全禁用 TCC 相同。
由于此问题需要一段时间才能修复,其他更改也影响了此漏洞和我们的漏洞利用链。我们最初针对 macOS Catalina (10.15) 和 macOS Big Sur (11.0) 测试版开发了漏洞利用。
在 macOS 11.0 中,苹果添加了签名系统卷 (SSV),这意味着 SSV 上的应用程序的资源完整性已经被 SSV 的签名所覆盖。因此,SSV 上的应用程序的代码签名不再覆盖资源。
在 macOS 12.3 中,Apple 删除了捆绑的 Python.framework。这打破了用于提升权限的漏洞利用链,但并未解决核心漏洞。此外,可以使用 Xcode 中捆绑的 Python3 框架。
在 macOS 13.0 中,Apple 引入了启动限制,使得无法启动与操作系统捆绑在一起并复制到其他位置的应用程序。这意味着不再可能复制和修改 macOS 中包含的应用程序。但是,许多具有有趣权利的不受限制的应用程序仍然存在。
CVE-2021-30873
那么,我们如何利用上一篇博文中提到的漏洞呢?事实证明,NSNib代表 nib 文件本身的类可以使用 进行序列化NSCoding,因此我们可以在序列化对象中包含完整的 nib 文件。
因此,我们只需要序列化数据中的三个对象链:
-
NSRuleEditor,设置绑定到-draw下一个对象。
-
NSCustomImageRep,配置为在调用-instantiateWithOwner:topLevelObjects:该方法时在下一个对象上调用的选择器。-draw
-
NSNib使用本页描述的其中一个有效载荷。
AsNSCustomImageRep调用其选择器时不带任何参数,owner和topLevelObjects指针是调用时这些寄存器恰好包含的内容。AstopLevelObjects是NSArray **(它是指针的输出变量NSArray *),它将尝试写入此变量,当发生此写入时,它将崩溃。但是,我们的漏洞利用程序已在此时执行,因此这对于演示目的而言并不重要。
时间线
2020 年 9 月 28 日:报告至[email protected]。报告、代码和视频以 Mail Drop 链接的形式附加,这是 Apple 的“报告安全或隐私漏洞”页面建议共享大附件的方式。
2020 年 10 月 28 日:应产品安全部门的要求,我们重新发送了附件,因为 Mail Drop 链接已在 30 天后过期。
2020 年 12 月 16 日:应产品安全部门的要求,我们再次重新发送了附件,因为 Mail Drop 链接在 30 天后再次过期。
2021 年 10 月 21 日:产品安全电子邮件称修复计划于 2022 年春季进行。
2022 年 5 月 26 日:产品安全电子邮件称,计划的修复导致性能下降,并计划于 2022 年秋季进行不同的修复。
2022 年 6 月 6 日:macOS Ventura Beta 1 发布,具有应用程序管理权限。
2022 年 8 月 29 日:告知 Apple macOS Ventura Beta 中存在多处应用程序管理权限绕过问题。
2022 年 10 月 24 日:macOS Ventura 发布,具有应用程序管理权限和启动限制。
2023 年 9 月 26 日:macOS Sonoma 发布,修复了应用程序管理权限的绕过之一(CVE-2023-40450)。
1.这意味着我们实际上初始化了这个对象两次,就像-init之前调用的一样,现在我们调用-initWithSource:。虽然这在 Objective-C 中是不正确的,但看起来大多数类在这样做时都不会崩溃 。↩︎
https://sector7.computest.nl/post/2024-04-bringing-process-injection-into-view-exploiting-all-macos-apps-using-nib-files/#fnref:1
2.该模块的一个功能ctypes不是:为了在 C 中将 Python 函数作为回调传递,它会尝试映射 WX 内存,这是不允许的。由于我们的漏洞利用不需要此功能,因此禁用此功能很容易 。↩︎
https://sector7.computest.nl/post/2024-04-bringing-process-injection-into-view-exploiting-all-macos-apps-using-nib-files/#fnref:2
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里
原文始发于微信公众号(Ots安全):将进程注入引入视图:利用所有使用 nib 文件的 macOS 应用程序