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.

patch me baby

Now 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!