一个程序员的辩白

07 Sep 2018

HOWTO use private KPIs in kernel extension

grep(1) kpi upon kextstat(8), you will find a special kernel extension named com.apple.kpi.private:

$ kextstat | grep kpi
    1  106 0xffffff7f80a00000 0x9e30     0x9e30     com.apple.kpi.bsd (17.7.0) 8CF59A67-A723-4FB8-A127-09A4B32E1874
    2   10 0xffffff7f80f16000 0x3990     0x3990     com.apple.kpi.dsep (17.7.0) E085363B-71AD-4ED8-A63E-3EEC4F643ED9
    3  134 0xffffff7f80a29000 0x21150    0x21150    com.apple.kpi.iokit (17.7.0) 695D2CC5-AB90-4CD4-9862-2C50A3A989B8
    4  140 0xffffff7f80a0a000 0xd430     0xd430     com.apple.kpi.libkern (17.7.0) 26F1FC40-6578-4254-AF12-3D397814B5CB
    5  126 0xffffff7f80a18000 0x3f60     0x3f60     com.apple.kpi.mach (17.7.0) 05B0DB8B-BF52-4A99-B18A-BE623583B448
    6   81 0xffffff7f80a1c000 0xce70     0xce70     com.apple.kpi.private (17.7.0) 409868DB-B2D5-4249-A98A-3C2BC6E7A754
    7   82 0xffffff7f80a8e000 0x5ea0     0x5ea0     com.apple.kpi.unsupported (17.7.0) 37241BAB-747F-4D47-BBD5-C01F3577D845

Unlike com.apple.kpi.unsupported, com.apple.kpi.private is used solely by Apple itself.

It located at /System/Library/Extensions/System.kext/PlugIns/Private.kext, you can nm(1) it to check its symbol table.

 

Taking strnstr as an example, to show how you can use this private KPI function.

It’s OK to compile the following kext listing:

#include <mach/mach_types.h>
#include <mach/kmod.h>
#include <libkern/libkern.h>
#include <string.h>             /* strnstr is exported */

kern_return_t kext_null_start(kmod_info_t *ki, void *d)
{
    char *name = strnstr(ki->name, "", KMOD_MAX_NAME);
    char *ver = strnstr(ki->version, "", KMOD_MAX_NAME);
    printf(">> %s %s\n", name, ver);
    return KERN_SUCCESS;
}

kern_return_t kext_null_stop(kmod_info_t *ki, void *d)
{
    return KERN_SUCCESS;
}

Use kextlibs(8) check its dependencies, it output:

$ kextlibs -c -xml -unsupported kext-null.kext
	<key>OSBundleLibraries</key>
	<dict>
		<key>com.apple.kpi.libkern</key>
		<string>8.0d0</string>
		<key>com.apple.kpi.private</key>
		<string>8.0b1</string>
	</dict>

Which for sure strnstr exported by com.apple.kpi.private.

When you load them via kextload(8) or kextutil(8), the kextd(8)(kext backing daemon) will prompts you:

$ sudo kextload kext-null.kext
/private/tmp/kext-null.kext failed to load - (libkern/kext) dependency load failed; check the system/kernel logs for errors or try kextutil(8).

# System log
...
default	13:12:04.352316 +0800	kextd	Kext with invalid signatured (-67050) allowed: <OSKext 0x7ff6fdb2e590 [0x7fff8d099af0]> { URL = "file:///private/tmp/kext-null.kext/", ID = "cn.junkman.kext-null" }
default	13:12:04.396342 +0800	kextd	/private/tmp/kext-null.kext has an Apple prefix but no copyright.
default	13:12:04.396376 +0800	kextd	cn.junkman.kext-null's dependencies failed security checks; failing.
...

Apple certainly don’t want developers use the com.apple.kpi.private, yet still ways to jailbreak it.

 

If you dive deep into Apple source code and search upon the string “has an Apple prefix but no copyright”, you’ll find IOKitUser/IOKitUser/kext.subproj/OSKext.c’s __OSKextResolveDependencies snippet:

#ifndef IOKIT_EMBEDDED
    /* If the kext links against the private KPI, we want to make an effort
     * to ensure they are Apple-internal kexts.  We do this by verifying that
     * the kext's bundle identifier begins with "com.apple.", and by making
     * sure the kext's info dictionary contains an Apple copyright string
     * in either CFBundleGetInfoString (obsolete) or NSHumanReadableCopyright.
     */

    if (aKext->loadInfo->flags.hasPrivateKPIDependency)
    {
        CFStringRef     infoString                  = NULL;  // do not release
        CFStringRef     readableString              = NULL;  // do not release
        Boolean         hasApplePrefix              = false;
        Boolean         infoCopyrightIsValid        = false;
        Boolean         readableCopyrightIsValid    = false;

        hasApplePrefix = CFStringHasPrefix(aKext->bundleID, __kOSKextApplePrefix);

        infoString = (CFStringRef) OSKextGetValueForInfoDictionaryKey(aKext,
            CFSTR("CFBundleGetInfoString"));
        if (infoString) {
            char *infoCString = createUTF8CStringForCFString(infoString);
            infoCopyrightIsValid = kxld_validate_copyright_string(infoCString);
            SAFE_FREE(infoCString);
        }

        readableString = (CFStringRef) OSKextGetValueForInfoDictionaryKey(aKext,
            CFSTR("NSHumanReadableCopyright"));
        if (readableString) {
            char *readableCString = createUTF8CStringForCFString(readableString);
            readableCopyrightIsValid = 
                kxld_validate_copyright_string(readableCString);
            SAFE_FREE(readableCString);
        }

        if (!hasApplePrefix || (!infoCopyrightIsValid && !readableCopyrightIsValid)) {

            OSKextLog(aKext, kOSKextLogErrorLevel | kOSKextLogDependenciesFlag,
                "%s has an Apple prefix but no copyright.", kextPath);

            __OSKextSetDiagnostic(aKext, kOSKextDiagnosticsFlagDependencies,
                kOSKextDiagnosticNonAppleKextDeclaresPrivateKPIDependencyKey);
            result = false;
            goto finish;
        }
    }
#endif /* !IOKIT_EMBEDDED */

So it takes two criterions to load a private-dependent kext:

If the kext links against the private KPI, we want to make an effort to ensure they are Apple-internal kexts. We do this by verifying that the kext’s bundle identifier begins with “com.apple.”, and by making sure the kext’s info dictionary contains an Apple copyright string in either CFBundleGetInfoString (obsolete) or NSHumanReadableCopyright.

E.g.

  1. Modify your kext bundle identifier begin with “com.apple.”

  2. Make NSHumanReadableCopyright in form of “Copyright © XXXX Apple Inc. All rights reserved.”, which XXXX is a year number.

 

After change those properties in kext’s Info.plist, you can use private KPIs for sure.

$ cat Info.plist | grep -E "CFBundleIdentifier|NSHumanReadableCopyright" -A 1
	<key>CFBundleIdentifier</key>
	<string>com.apple.kext.3rd.kext-null</string>
--
	<key>NSHumanReadableCopyright</key>
	<string>Copyright © 0000 Apple Inc. All rights reserved.</string>

$ sudo kextload kext-null.kext
$ kextstat | grep kext-null
  188    0 0xffffff7f83705000 0x2000     0x2000     com.apple.kext.3rd.kext-null (1) 927FE5AE-4720-36EC-897C-2675A0FE8913 <6 4>

$ sudo dmesg | grep ">>"
>> com.apple.kext.3rd.kext-null 1

 

Yet, not all symbols exported by Apple, for example, vnode_isautocandidate is exported in Kernel.framework’s sys/vnode.h, yet vnode_usecount and vnode_iocount not exported.

In such cases, you can simply extern it, it will link against com.apple.kpi.private naturally:

#include <mach/mach_types.h>
#include <libkern/libkern.h>
#include <sys/vnode.h>

/* Those symbols not exported by Kernel.framework */
extern int hz;
extern struct vnode *rootvp;
extern int vnode_usecount(vnode_t vp);
extern int vnode_iocount(vnode_t vp);

kern_return_t kext_null_start(kmod_info_t *ki, void *d)
{
    printf(">> %s %s\n", ki->name, ki->version);
    printf(">> hz: %d\n", hz);
    printf(">> rootvp: %p use: %d io: %d\n",
            rootvp, vnode_usecount(rootvp), vnode_iocount(rootvp));
}

kern_return_t kext_null_stop(kmod_info_t *ki, void *d)
{
    return KERN_SUCCESS;
}
$ sudo kextload kext-null.kext
$ sudo dmesg | grep ">>"
>> com.apple.kext.3rd.kext-null 1
>> hz: 100
>> rootvp: <ptr> use: 2 io: 0

 

Though it takes no effort to jailbreak such restriction, I personally still not recommend you guys do this. Basically it for Apple internal use.

In an incoming article(If I’m free), I’ll illustrate HOWTO resolve kernel symbols even if they’re missing in KPIs.