Breaking OS X signed kernel extensions with a NOP

For some reason Apple wants to change external kernel extensions location from /System/Library/Extensions to /Library/Extensions and introduced in Mavericks a code signing requirement for all extensions and/or drivers located in that folder. Extensions will not be loaded if not signed (those located in the “old” folder and not signed will only generate a warning [check my SyScan360 slides]). The signing certificates require a special configuration and to obtain them you need to justify it. You know, there are people out there coding rootkits and other nasty stuff ;).

A couple of days ago while trolling around in IRC someone complained about this and I decided to give it a quick try (I sort of already knew it would work this way but never really tried it). Kextd is the daemon responsible for processing requests to load kernel extensions. The message you get when trying to load a unsigned kernel extension is: “ERROR: invalid signature for %@, will not load”. Load IDA, disassemble and search for this string (the alternative is to go to opensource.apple.com and download 10.9 kext_tools package – my brain defaults to IDA!). What you can see is this code for one of the two hits:

    OSStatus  sigResult = checkKextSignature(theKext, true);
    if ( sigResult != 0 ) {
        if ( isInLibraryExtensionsFolder(theKext) ||
            sigResult == CSSMERR_TP_CERT_REVOKED ) {
            /* Do not load if kext has invalid signature and comes from
             *  /Library/Extensions/
             */
            CFStringRef         myBundleID;          // do not release
 
            myBundleID = OSKextGetIdentifier(theKext);
            result = kOSKextReturnNotLoadable; // see 13024670
            OSKextLogCFString(NULL,
                              kOSKextLogErrorLevel |
                              kOSKextLogLoadFlag | kOSKextLogIPCFlag,
                              CFSTR("ERROR: invalid signature for %@, will not load"),
                              myBundleID ? myBundleID : CFSTR("Unknown"));
        }

Nothing more than a simple call to the check signature function and then a simple test to see if it was valid or not. This is pretty common in (lame) software protection schemes. One of the usual ways to bypass it is to NOP the test. Or you can just patch the checkKextSignature and return always TRUE. Many different ways to solve it.

patchmebabyNow to the interesting part. I have no idea what was Apple thinking (sometimes they do not seem to think!) by implementing the code signature checks like this. It is very hard (to impossible) to protect kextd against something running at the same privilege level (kextload or something else require root to load kernel extensions). To launch the rootkit one just simply needs a binary that runtime patches kextd (task_for_pid, mach_vm_protect, mach_vm_write) and then loads the rootkit . Game over!
Of course it is easy to argue that if attacker got root everything is possible and the game is lost. Maybe! But making things like this is just security theatre and nothing else. It creates a useless artificial barrier to load unsigned code into the kernel and a false sense of security. This kind of checks needs to be done in the kernel, at a different privilege level. True that a rootkit can just patch files at disk and bypass a kernel protection but a non persistent rootkit just needs to patch kextd, get loaded and do its work. This is just (more?) careless and sloppy work from Apple.

Have fun,
fG!

2 thoughts on “Breaking OS X signed kernel extensions with a NOP

  1. Adding complexity into kernel is bad security practice. Having in userspace is always better in this regard (in the particular case of kextd, it does seem to make a difference though). Usually, you can mitigate root user processe compromise via MAC (TrustedBSD-like or SELinux-like), u simply cannot in kernel land. I don’t buy your argument about “This kind of checks needs to be done in the kernel”. Look at L4 kernels, they try to keep the kernel as small as possible and do ONLY the most vital stuff. Summary: putting the complexity in userspace is always better coz u’ll be able to use MAC when developing your platform further. You’re just adding complexity and increasing attack surface by adding code in kernel.

    1. How does your argument even make sense by calling TrustedBSD usage? You know that TrustedBSD is more kernel land code, right?
      There are times when complexity and code must and should be added to the kernel. You are afraid? Good luck protecting that daemon then.
      Funny thing is that they have in iOS the trusted cache (more due to reasons of booting a trusted state and races with the code signing daemon but still they have the concept).
      This is lazy stuff from Apple and they can do better. Like it’s done now it’s nothing more than bullshit.

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>