I recently came across a persistence feature in macOS that’s tied to Dock tile plugins.
我最近在 macOS 中发现了一个与Dock 磁贴插件相关的持久性功能。
Dock tiles are the small icons that appear on your Dock when an application runs. Plugins for these Dock tiles have been available since macOS Snow Leopard (10.6). In its developer documentation, Apple says about them:
Dock 磁贴是应用程序运行时出现在 Dock 上的小图标。自 macOS Snow Leopard (10.6) 起,这些 Dock 磁贴的插件就已可用。苹果在其开发者文档中谈到了它们:
A set of methods implemented by plug-ins…allow an app’s Dock tile to be customized while the app is not running.
一组由插件实现的方法…允许在应用程序未运行时自定义应用程序的 Dock 磁贴。
For example, thanks to such plugins, Facetime’s dock tile can display recent calls:
例如,借助此类插件,Facetime 的 Dock 磁贴可以显示最近的通话:
The documentation also says:
文档还说:
The plugin is loaded in a system process at login time or when the application tile is added to the Dock.
该插件会在登录时或将应用程序磁贴添加到 Dock 时加载到系统进程中。
“Loaded in a system process at login time” means persistence, no matter how it’s framed. If these plugins have a vulnerability, such persistence means it could be exploited.
“在登录时加载到系统进程中”意味着持久性,无论它是如何构建的。如果这些插件存在漏洞,这种持久性意味着它可以被利用。
See Kandji in Action 查看 Kanji 的实际应用
Experience Apple device management and security that actually gives you back your time.
体验 Apple 设备管理和安全性,真正为您节省时间。
Link to this sectionThe Vulnerability 漏洞
If an application with a Dock tile plugin is placed in a location—such as /Users/Shared—where every user can “see” it, the plugin will be recognized and loaded into each user’s process. If a standard user places the app in such a directory, and an admin user logs in, the plugin will be executed in the admin user’s context, thus achieving standard-to-admin user privilege escalation.
如果带有 Dock 磁贴插件的应用程序放置在每个用户都可以“看到”它的位置(例如 /Users/Shared),则该插件将被识别并加载到每个用户的进程中。如果标准用户将应用程序放在这样的目录中,并且管理员用户登录,则该插件将在管理员用户的上下文中执行,从而实现标准到管理员用户的权限提升。
Here are some logs from a proof-of-concept that I developed. We can see that the plugin is loaded into different processes (1605 and 1606).
以下是我开发的概念验证的一些日志。我们可以看到插件被加载到不同的进程中(1605和1606)。
lizard@vm ~ % log stream | grep BEYOND 2023-10-02 10:58:46.049386-0700 0x5ad0 Default 0x0 1606 0 com.apple.dock.external.extra.arm64: (DuckDockTilePlugin) BEYOND setDockTile was called 2023-10-02 10:58:57.619373-0700 0x5aba Default 0x0 1605 0 com.apple.dock.external.extra.arm64: (DuckDockTilePlugin) BEYOND doSomething was called
If we check the processes in Activity Monitor, we find that they belong to two separate users: rookie (standard) and lizard (admin):
如果我们检查活动监视器中的进程,我们会发现它们属于两个不同的用户:rookie(标准)和lizard(管理员):
Using Task Explorer, we can confirm that the plugin was indeed loaded from /Users/Shared:
使用任务资源管理器,我们可以确认插件确实是从 /Users/Shared 加载的:
VM Escape and its Limitations
VM Escape 及其局限性
Since these plugins are automatically found and executed by the system process, if a malicious actor were to drop such an application to a system, they could get code execution. (Autorun only works if the quarantine attribute is not present, so we can’t use it for Gatekeeper bypass). If shared folders are enabled on a virtual machine, someone could drop an app with a plugin to the host system, and it would be executed; this is essentially a universal VM escape.
由于这些插件是由系统进程自动找到并执行的,因此如果恶意行为者将此类应用程序放入系统,他们就可以执行代码。 (自动运行仅在隔离属性不存在时才起作用,因此我们不能将其用于绕过网守)。如果在虚拟机上启用了共享文件夹,则有人可以将带有插件的应用程序放入主机系统,并且该应用程序将被执行;这本质上是一个通用的VM转义。
It does matter where you place the application. That’s because Dock tile plugins are searched and loaded by the Dock, which checks only certain locations. In the app’s LPAppSource commonInit
method, we can find the directories that are scanned:
将应用程序放置在何处确实很重要。这是因为 Dock 磁贴插件是由 Dock 搜索并加载的,它仅检查某些位置。在应用程序的LPAppSource commonInit
方法中,我们可以找到扫描的目录:
void __cdecl -[LPAppSource commonInit](LPAppSource *self, SEL a2) { NSString *path; // r14 __CFString *v4; // rax NSString *v5; // rax NSString *v6; // rax NSString *v7; // rax NSString *v8; // rdi NSOperationQueue *v9; // rax NSOperationQueue *processQueue; // rdi id v11; // rdi _QWORD block[7]; // [rsp+8h] [rbp-38h] BYREF switch ( self->_location ) { case 0LL: path = self->_path; self->_path = 0LL; goto LABEL_10; case 1LL: path = self->_path; v4 = CFSTR("/System/Applications"); goto LABEL_9; case 2LL: path = self->_path; v4 = CFSTR("/Applications"); goto LABEL_9; case 3LL: v5 = NSHomeDirectory(); path = objc_retainAutoreleasedReturnValue(v5); a2 = "stringByAppendingPathComponent:"; v6 = -[NSString stringByAppendingPathComponent:](path, "stringByAppendingPathComponent:", CFSTR("Applications")); v7 = objc_retainAutoreleasedReturnValue(v6); v8 = self->_path; self->_path = v7; objc_release(v8); goto LABEL_10; case 4LL: path = self->_path; v4 = CFSTR("/Users/Shared"); goto LABEL_9; case 5LL: path = self->_path; v4 = CFSTR("/AppleInternal/Applications"); goto LABEL_9; case 6LL: path = self->_path; v4 = CFSTR("/System/Volumes/Preboot/Cryptexes/App/System/Applications"); LABEL_9: self->_path = &v4->isa; LABEL_10: objc_release(path); break; default: break; }
It scans all standard /Application folders and /Users/Shared. Since I use that directory for virtual machine shared folders, I could use this for VM escape. While privilege escalation will always work on a multiuser system, a VM escape is more limited.
它扫描所有标准 /Application 文件夹和 /Users/Shared。由于我将该目录用于虚拟机共享文件夹,因此我可以使用它来进行虚拟机转义。虽然权限升级始终适用于多用户系统,但虚拟机逃逸却受到更多限制。
Link to this sectionThe Fix 修复方法
Apple fixed this vulnerability in macOS Sonoma 14.4. A new AppDataContainer
class was created, whereby the plugin is checked in the initWithAppBundleURL:
method.
Apple 在 macOS Sonoma 14.4 中修复了此漏洞。创建了一个新的AppDataContainer
类,在initWithAppBundleURL:
方法中检查插件。
AppDataContainer *__cdecl -[AppDataContainer initWithAppBundleURL:](AppDataContainer *self, SEL a2, id a3) ... container_query_set_class(v13, 2LL); container_query_operation_set_flags(v14, 0x900000002LL); container_query_set_persona_unique_string(v14, CONTAINER_PERSONA_PRIMARY); container_query_set_identifiers(v14, v12); single_result = container_query_get_single_result(v14); if ( !single_result ) { NSLog((NSString *)CFSTR("No data container for app bundle %@"), v3); container_query_free(v14); goto LABEL_31; } v16 = single_result; v32 = v12; path = (const char *)container_get_path(); if ( !path ) { NSLog(&CFSTR("No data container path for app bundle %@").isa, v3); goto LABEL_30; } v18 = path; v29 = strlen(path); if ( !v29 ) { NSLog((NSString *)CFSTR("Zero length data container path for app bundle %@"), v3); goto LABEL_30; } ...
Using the container_query*
function, a call is made to containermanagerd
to see if a container exists for the application or not. This check will be passed only if the main application itself has been executed by the user at least once. If another user has executed the app, a container will only exist for that user and not others. Thus, we can’t use it for privilege escalation. Since the plugin is not auto-executed, we can’t use it for VM escape, either.
使用container_query*
函数,调用containermanagerd
以查看应用程序是否存在容器。仅当主应用程序本身已被用户执行至少一次时,此检查才会通过。如果另一个用户执行了该应用程序,则容器将仅为该用户而存在,而不会为其他用户而存在。因此,我们不能使用它来进行权限升级。由于该插件不会自动执行,因此我们也无法将其用于虚拟机逃逸。
原文始发于Csaba Fitzl:Dock Tile Plugins Could Be Used to Escalate Privileges
转载请注明:Dock Tile Plugins Could Be Used to Escalate Privileges | CTF导航