series

  1. IOS Jailbreak Principles 0x01 – RootFS Remount R/W Principles
  2. iOS Jailbreak Principles 0x02 – codesign and amfid bypass

preface

In the previous article we introduced the codesign mechanism of amFID and its circumvention. Amfid is a daemon of coDesign logic in User mode, representing the Server in the C/S architecture. This article will introduce the kernel mode amfi.kext, which is the Client of AMFID and is loaded and registered in the kernel in the form of kernel Extension.

Kernel Extension

define

XNU is a features-rich kernel, including scheduling, memory management, I/O and other necessary services, but it is still difficult to directly adapt to the myriad of hardware and peripherals, even the macro kernel can not fully achieve this [1].

Just as the user mode application often contains dylib, there are kernel modules as extensions in kernel mode, which are called kernel Extensions in XNU and referred to as kext[1] for short.

Pre-Linking

From a conventional perspective, the operating system should boot kernel and then load kexts. In iOS, kernel and its extensions do not exist as separate files. Instead, kernel and Kexts are merged into a kernelCache file and loaded directly by the Boot Loader.

Kernelcache brings two benefits. One is that KexTS does not need to dynamically link like Dylib, which saves the process of external symbolic address resolution and speeds up the loading speed. Second, KernelCache can be signed completely to reduce the risk of kext tampering [1].

Analysis AppleMobileFileIntegrity kext

Separate from kernelCache

Since amfi.kext is prelinked into kernelCache, its info.plist and binary are included directly in the massive Kernelcache, We can isolate them from kernelCache for analysis purposes.

Split the Kext binary by joker

Use the joker (www.newosxbook.com/tools/joker.) Kext can be isolated and partially symbolized:

Specify the output directory
> cd /tmp/kext
> export JOKER_DIR=/tmp/kext

# Prepare kernelCache
> ls .
kernelcache

# separation amfi kext
> joker -K com.apple.driver.AppleMobileFileIntegrity kernelcache
Writing kext out to /tmp/kext/com.apple.driver.AppleMobileFileIntegrity.kext
Symbolicated stubs to /tmp/kext/com.apple.driver.AppleMobileFileIntegrity.kext.ARM64.E815A4DD-90E7-3A38-A4BA-EFA2425BC543

# View the product
> ls .
com.apple.driver.AppleMobileFileIntegrity.kext
com.apple.driver.AppleMobileFileIntegrity.kext.ARM64.E815A4DD-90E7-3A38-A4BA-EFA2425BC543
kernelcache
Copy the code

Since kext is separated from the kernel, like dyLIB from dyLD_shared_cache, a large number of external addresses do not resolve properly. The meaning and content of these addresses can be determined by using the symbol table or locating them in kernelCache.

Use jtool to separate PRELINK_INFO

Similar to how App describes key information through info.plist, Kext also has its info.plist to describe various information of Kext, including identifiers, loading addresses and other key information. To facilitate analysis, We also need to separate the info.plist of amfi from kernelCache. Here we use jtool (www.newosxbook.com/tools/jtool.) To accomplish separation:

Specify the output directory
export JTOOLDIR=/tmp/kext

# separation PRELINK_INFO
> jtool -e __PRELINK_INFO kernelcache
Requested segment found at offset 1e10000!
Extracting __PRELINK_INFO at 31522816, 2342912 (23c000) bytes into kernelcache.__PRELINK_INFO

# View the product
> ls .
com.apple.driver.AppleMobileFileIntegrity.kext
com.apple.driver.AppleMobileFileIntegrity.kext.ARM64.E815A4DD-90E7-3A38-A4BA-EFA2425BC543
kernelcache
kernelcache.__PRELINK_INFO
Copy the code

Open kernelCache.__prelink_info and you can see that it contains a lot of information about the kext in kernelCache prelinked to, mixed in with a lot of base64-encoded Data blobs.

Look for key information in PRELINK_INFO

Search in kernelcache.__prelink_info < key > _PrelinkBundlePath < / key > < string > / System/Library/Extensions/AppleMobileFileIntegrity kext < / string > can locate to amfi. Kext Info.plist, which contains some key information about amfi.kext:

<dict>
  <key>BuildMachineOSBuild</key>
  <string>18A391011</string>
  <key>_PrelinkExecutableLoadAddr</key>
  <integer ID="32" size="64">0xfffffff005ab1980</integer>
  <key>CFBundlePackageType</key>
  <string>KEXT</string>
  <key>_PrelinkExecutableSourceAddr</key>
  <integer IDREF="32"/>
  <key>CFBundleDevelopmentRegion</key>
  <string>English</string>
  <key>MinimumOSVersion</key>
  <string>13.1</string>
  <key>CFBundleVersion</key>
  <string>1.0.5</string>
  <key>DTXcodeBuild</key>
  <string>11L374m</string>
  <key>DTPlatformBuild</key>
  <string ID="33"/>
  <key>_PrelinkBundlePath</key>
  <string>/System/Library/Extensions/AppleMobileFileIntegrity.kext</string>
  <key>_PrelinkExecutableSize</key>
  <integer size="64">0x5211</integer>
  <key>_PrelinkKmodInfo</key>
  <integer size="64">0xfffffff0077e51c8</integer>
  <key>UIDeviceFamily</key>
  <array>
    <integer IDREF="10"/>
  </array>
  <key>OSBundleRequired</key>
  <string>Root</string>
  <key>CFBundleIdentifier</key>
  <string>com.apple.driver.AppleMobileFileIntegrity</string>
  <key>DTXcode</key>
  <string>1100</string>
  <key>CFBundleExecutable</key>
  <string IDREF="31"/>
</dict>
Copy the code

The fields starting with _Prelink are important:

<dict>
  <key>_PrelinkExecutableLoadAddr</key>
  <integer ID="32" size="64">0xfffffff005ab1980</integer>
  <key>_PrelinkExecutableSourceAddr</key>
  <integer ID="32" size="64">0xfffffff005ab1980</integer>
  <key>_PrelinkBundlePath</key>
  <string>/System/Library/Extensions/AppleMobileFileIntegrity.kext</string>
  <key>_PrelinkExecutableSize</key>
  <integer size="64">0x5211</integer>
  <key>_PrelinkKmodInfo</key>
  <integer size="64">0xfffffff0077e51c8</integer>
  <key>CFBundleIdentifier</key>
  <string>com.apple.driver.AppleMobileFileIntegrity</string>
</dict>
Copy the code

These fields have the following meanings [1] :

  • _PrelinkExecutableSourceAddr: kext starting address, namely kext Mach – O Header address;
  • _PrelinkExecutableLoadAddr: kext loaded in memory address for prelink kext this value equals _PrelinkExecutableSourceAddr commonly;
  • _PrelinkKmodInfo: Kext’s object model at the Mach Layer.

We generally have a look at the address below, the content of the first is _PrelinkExecutableSourceAddr, here is the loading of kext starting point, you can see this is a standard of the Mach – O Header structure:

The next is _PrelinkKmodInfo, which is a kmod_info_t structure:

typedef struct kmod_info {
    struct kmod_info  * next;
    int32_t             info_version;       // version of this structure
    uint32_t            id;
    char                name[KMOD_MAX_NAME];
    char                version[KMOD_MAX_NAME];
    int32_t             reference_count;    // # linkage refs to this
    kmod_reference_t  * reference_list;     // who this refs (links on)
    vm_address_t        address;            // starting address
    vm_size_t           size;               // total size
    vm_size_t           hdr_size;           // unwired hdr size
    kmod_start_func_t * start;
    kmod_stop_func_t  * stop;
} kmod_info_t;
Copy the code

Guess how modules are loaded

As a rule of thumb with user mode, the Mach-o Header here might contain structures like LC_MAIN to identify Entry points, or the key logic for registration in the start and stop functions in kmod_info.

Unfortunately, there is no Entry Point in amfi.kext’s Mach-o Header, and the start and stop functions in kmod_info are empty implementations, This means that there must be other ways to load the prelink kext that need to be explored.

AppleMobileFileIntegrity kext registration

After some analysis and research I found that the loading logic for Kext has been progressively moved to libkern. The key logic for maintaining kext is in libkern/c++/ oskext.cpp, while user mode can interact with kext through I/O Kit [1].

Based on I/O Kit kext as drivers mounted in IO device tree, through the Mach messages to the operation of the kext, for example, by OSKextLoadKextWithIdentifier to load a kext:

kern_return_t
OSKextLoadKextWithIdentifier(const char * bundle_id)
{
    return OSKext::loadKextWithIdentifier(bundle_id);
}
Copy the code

The key logic here is to find the corresponding OSKext object in a global registry sKextsByID and perform the load operation, then the key question becomes how kext is added to sKextsByID.

Prelink kexts uses PRELINK_INFO to log information. When initializing the I/O Kit during boot, _start -> _start_first_cpu -> arm_init -> machine_startup -> kernel_bootstrap -> kernel_bootstrap_thread -> PE_init_iokit -> StartIOKit -> bootstrapRecordStartupExtensions -> KLDBootstrap::readStartupExtensions -> ReadPrelinkedExtensions – > OSKext: : withPrelinkedInfoDict – > OSKext: : according to the Info in PRELINK_INFO initWithPrelinkedInfoDict Load Prelinked Kext one by one.

From startup to registration

Below we from OSKext: : initWithPrelinkedInfoDict method of kext way of loading to research:

bool
OSKext::initWithPrelinkedInfoDict(
	OSDictionary * anInfoDict,
	bool doCoalesedSlides) {
    // ...
    addressNum = OSDynamicCast(OSNumber, anInfoDict->getObject("_PrelinkKmodInfo"));
    if(addressNum->unsigned64BitValue() ! =0) {
        kmod_info = (kmod_info_t *) ml_static_slide((intptr_t) (addressNum->unsigned64BitValue()));
        kmod_info->address = ml_static_slide(kmod_info->address);
    }
    
    // ...
    flags.prelinked = true;
    sPrelinkBoot = true;
    result = registerIdentifier();
    
    // ...
    return result;
}
Copy the code

This is the main processing for kext’s info.plist, including initialization of kmod_info, setting kext’s binary, setting kext flags, etc. The key step here is to add yourself to the global registry via registerIdentifier:

bool
OSKext::registerIdentifier(void)
{
    // ...
    /* If we don't have an existing kext with this identifier, * just record the new kext and we're done! * /
    existingKext = OSDynamicCast(OSKext, sKextsByID->getObject(bundleID));
    if(! existingKext) { sKextsByID->setObject(bundleID,this);
    	result = true;
    	goto finish;
    }
    
    // ...
    return true;
}
Copy the code

The initial registration logic here is very simple, which is to add kext to the global registry sKextsByID with the bundleID key. The version resolution logic for registering the same kext twice is omitted.

AppleMobileFileIntegrity kext loading

Having said that, let’s look at loading Prelinked Kext. At first, I thought amfi.kext was loaded based on libKern’s kext_request. I looked for cross-references between code and bundleID, but found prelinked Kext loading was also hidden in the startup process. The bridge to load and register is the global registry sKextsByID.

Loader injection

As mentioned in the registration process, before we have a from StartIOKit – > bootstrapRecordStartupExtensions calls, the corresponding code in the StartIOKit as follows:

void (*record_startup_extensions_function)(void) = NULL;

void
StartIOKit( void * p1, void * p2, void * p3, void * p4 ) {
    // ...
    /* If the bootstrap segment set up a function to record startup * extensions, call it now. */
    if (record_startup_extensions_function) {
    	record_startup_extensions_function();
    }
    // ...
}
Copy the code

Here the record_startuP_extensions_function is injected into the KLDBootstrap constructor:

/********************************************************************* * Set the function pointers for the entry points into the bootstrap * segment upon C++ static constructor invocation. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
KLDBootstrap::KLDBootstrap(void)
{
    if (this! = &sBootstrapObject) { panic("Attempt to access bootstrap segment.");
    }
    record_startup_extensions_function = &bootstrapRecordStartupExtensions;
    load_security_extensions_function = &bootstrapLoadSecurityExtensions;
}
Copy the code

Record_startup_extensions_function StartIOKit call is the registration process to realize the bootstrapRecordStartupExtensions, Here also for load_security_extensions_function injected bootstrapLoadSecurityExtensions as implementation. Here is bootstrapLoadSecurityExtensions kext loading logic, and registered logic bootstrapRecordStartupExtensions corresponds.

Caller of the loader

So who is responsible for calling bootstrapLoadSecurityExtensions to load these kext? We can find the logic in the MAC by searching the code:

/* Function pointer set up for loading security extensions. * It is set to an actual function after OSlibkernInit() * has been called, and is set back to 0 by OSKextRemoveKextBootstrap() * after bsd_init(). */
void (*load_security_extensions_function)(void) = 0;

/* * Init after early Mach startup, but before BSD */
void
mac_policy_initmach(void)
{
    /* * For the purposes of modules that want to know if they were * loaded "early", set the mac_late flag once we've processed * modules either linked into the kernel, or loaded before the * kernel startup. */
    
    if (load_security_extensions_function) {
    	load_security_extensions_function();
    }
    mac_late = 1;
}
Copy the code

The FULL MAC name here is Mandatory Access Control, which is a more granular operating system security model based on the Trusted BSD implementation to provide object-level security Control. The Caller of mac_policy_initmach is kernel_bootstrap_thread:

/* * Now running in a thread. Kick off other services, * invoke user bootstrap, enter pageout loop. */
static void
kernel_bootstrap_thread(void)
{
    // ...
#ifdef  IOKIT
    kernel_bootstrap_log("PE_init_iokit");
    PE_init_iokit();
#endif

    // ...
#if CONFIG_MACF
    kernel_bootstrap_log("mac_policy_initmach");
    mac_policy_initmach();
    // ...
}
Copy the code

You can see that registering PE_init_iokit and loading mac_policy_initmach are called in order to ensure that mac_policy_initMach can fetch the registered Security Kexts.

Load the logic

Mentioned before loading logic lies in bootstrapLoadSecurityExtensions:

static void
bootstrapLoadSecurityExtensions(void)
{
    sBootstrapObject.loadSecurityExtensions();
    return;
}

void
KLDBootstrap::loadSecurityExtensions(void)
{
    // ...
    // OSKext::copyKexts()
    extensionsDict = OSDynamicCast(OSDictionary, sKextsByID->copyCollection());
    // ...
    keyIterator = OSCollectionIterator::withCollection(extensionsDict);
    // ...
    while ((bundleID = OSDynamicCast(OSString, keyIterator->getNextObject()))) {
        const char * bundle_id = bundleID->getCStringNoCopy();
        
        /* Skip extensions whose bundle IDs don't start with "com.apple.". */
        if(! bundle_id || (strncmp(bundle_id, "com.apple.", CONST_STRLEN("com.apple.")) != 0)) {
        	continue;
        }
        
        theKext = OSDynamicCast(OSKext, extensionsDict->getObject(bundleID));
        if(! theKext) {continue;
        }
        
        if (kOSBooleanTrue == theKext->getPropertyForHostArch(kAppleSecurityExtensionKey)) {
    	    OSKext::loadKextWithIdentifier(bundleID->getCStringNoCopy(),
        	    /* allowDefer */ false); }}// ...
}
Copy the code

Here the loadKextWithIdentifier method is executed by iterating through sKextsByID, OSKext:: Load -> OSKext::loadExecutable (register kmod_info) and OSKext::start -> OSRuntimeInitializeCPP.

OSKext::load contains some C++ environment initializations for libkern registered with IOKit and OSRuntimeInitializeCPP.

AppleMobileFileIntegrity. Register to IOKit kext

Register and start the service

Load: OSKext:: Load: OSKext::load

/* If not excluding matching, send the personalities to the kernel. * This never affects the result of the load operation. * This is a bit of a hack, because we shouldn't be handling * personalities within the load function. */
OSReturn
OSKext::load(
	OSKextExcludeLevel   startOpt,
	OSKextExcludeLevel   startMatchingOpt,
	OSArray            * personalityNames) 
{
    // ...
    if (result == kOSReturnSuccess && startMatchingOpt == kOSKextExcludeNone) {
        result = sendPersonalitiesToCatalog(true, personalityNames);
    }
    // ...
}
Copy the code

The so-called Personalities, or IOKitPersonalities, are used to describe the characteristics of the drivers so that IOKit can correctly load and match services.

OSKext: : sendPersonalitiesToCatalog will then call to gIOCatalogue – > addDrivers (personalitiesToSend, startMatching), The gIOCatalogue here is a global IOCatalogue object, a database of all IOKIt driver personalities that IOKIt uses to match related services [2].

GIOCatalogue – > addDrivers will then call to IOService: : catalogNewDrivers – > IOService: : startMatching – > IOService: : doServiceMatch:

void
IOService::doServiceMatch( IOOptionBits options )
{
    // ...
    while (keepGuessing) {
    	matches = gIOCatalogue->findDrivers( this, &catalogGeneration );
        // the matches list should always be created by findDrivers()
        if (matches) {
            if (0 == (__state[0] & kIOServiceFirstPublishState)) {
                getMetaClass()->addInstance(this);
                // ...
            }
            
            if (keepGuessing && matches->getCount() && (kIOReturnSuccess == getResources())) {
                if ((this == gIOResources) || (this == gIOUserResources)) {
                    if (resourceKeys) {
                        resourceKeys->release();
                    }
                    resourceKeys = copyPropertyKeys();
                }
                probeCandidates( matches );
            }
            // ...}}// ...
}
Copy the code

GetMetaClass ()->addInstance(this); probeCandidates(matches)

/* Class global data */
OSObject::MetaClass OSObject::gMetaClass;

const OSMetaClass *
OSObject::getMetaClass() const
{
    return &gMetaClass;
}
Copy the code

GMetaClass = gMetaClass = gMetaClass = gMetaClass = gMetaClass = gMetaClass = gMetaClass = gMetaClass;

void
OSMetaClass::addInstance(const OSObject * instance, bool super) const
{
    if(! super) { IOLockLock(sInstancesLock); }if(! reserved->instances) { reserved->instances = OSOrderedSet::withCapacity(16);
        if (superClassLink) {
            superClassLink->addInstance(reserved->instances, true);
        }
    }
    reserved->instances->setLastObject(instance);
    
    if (!super) {
        IOLockUnlock(sInstancesLock);
    }
}
Copy the code

GMetaClass -> Reserved ->instances get IOService instances for Service Matching.

Look at the next probeCandidates (matches) this call, it will be called to IOService: : startCandidate – > IOService: : start, start to finish amfi IOService.

AMFI startup process

In amfi.kext we can find the IOService::start method:

bool __cdecl AMFI::start_IOService(uint64_t *a1)
{
  uint64_t *v1; // x19

  v1 = a1;
  if(! (* ((unsigned int(* *),void))IORegistryEntry::gMetaClass + 88) (() ())void(*) (void))loc_FFFFFFF006075D18)();
  initializeAppleMobileFileIntegrity();
  if ( *(_DWORD *)cs_debug )
    IOLog("%s: built %s %s\n"."virtual bool AppleMobileFileIntegrity::start(IOService *)"."Sep 3 2019"."22:15:18"); (* (void (__fastcall **)(uint64_t *, _QWORD))(*v1 + 672))(v1, 0LL);
  return 1;
}
Copy the code

Here at the heart of the initialization method is initializeAppleMobileFileIntegrity, which contains the codesign related MAC Policy Module with the registered Handler, These handlers verify specific system calls in faceted form. For example, mPO_vnode_check_signature uses the In-kernel signature cache and amFID to verify the code signature of a file. The specific logic and the interaction with amfid initializeAppleMobileFileIntegrity we will in the next article introduced in detail.

Initialize the libkern C++ environment

The registration of the libkern C++ environment consists of pre, in and post phases:

kern_return_t
OSRuntimeInitializeCPP(
	OSKext                   * theKext)
{
    // ...
    /* Tell the meta class system that we are starting the load */
    metaHandle = OSMetaClass::preModLoad(kmodInfo->name);
    
    // ...
    /* Scan the header for all constructor sections, in any * segment, and invoke the constructors within those sections. */
    for(segment = firstsegfromheader(header); segment ! =NULL && load_success;
        segment = nextsegfromheader(header, segment)) {
    	/* Record the current segment in the event of a failure. */
    	failure_segment = segment;
    	load_success = OSRuntimeCallStructorsInSection(
    		theKext, kmodInfo, metaHandle, segment,
    		sectionNames[kOSSectionNameInitializer],
    		textStart, textEnd);
    } /* for (segment...) * /
    
    // ...
    /* Now, regardless of success so far, do the post-init registration * and cleanup. If we had to call the unloadCPP function, static * destructors have removed classes from the stalled list so no * metaclasses will actually be registered. */
    result = OSMetaClass::postModLoad(metaHandle);
    // ...
}
Copy the code

The Pre phase

The pre stage is used to prepare the loading context of Kext’s Main Class. The context is stored in a global variable and the serial queue is secured by a lock:

/* * While loading a kext and running all its constructors to register * all OSMetaClass classes, the classes are queued up here. Only one * kext can be in flight at a time, guarded by sStalledClassesLock */
static struct StalledData {
    const char   * kextIdentifier;
    OSReturn       result;
    unsigned int   capacity;
    unsigned int   count;
    OSMetaClass ** classes;
} * sStalled;
IOLock * sStalledClassesLock = NULL;

void *
OSMetaClass::preModLoad(const char * kextIdentifier)
{
    IOLockLock(sStalledClassesLock);
    
    assert(sStalled == NULL);
    sStalled = (StalledData *)kalloc_tag(sizeof(*sStalled), VM_KERN_MEMORY_OSKEXT);
    if (sStalled) {
    	sStalled->classes = (OSMetaClass **)kalloc_tag(kKModCapacityIncrement * sizeof(OSMetaClass *), VM_KERN_MEMORY_OSKEXT);
    	if(! sStalled->classes) { kfree(sStalled,sizeof(*sStalled));
            return NULL;
    	}
    	OSMETA_ACCUMSIZE((kKModCapacityIncrement * sizeof(OSMetaClass *)) +
    	    sizeof(*sStalled));
    
    	sStalled->result   = kOSReturnSuccess;
    	sStalled->capacity = kKModCapacityIncrement;
    	sStalled->count    = 0;
    	sStalled->kextIdentifier = kextIdentifier;
    	bzero(sStalled->classes, kKModCapacityIncrement * sizeof(OSMetaClass *));
    }
    
    // keep sStalledClassesLock locked until postModLoad
    
    return sStalled;
}
Copy the code

In phase

Then all of the code through OSRuntimeCallStructorsInSection scanned kext __mod_init_func sections and calls the initialization function, Here we can open IDA to see what initialization functions __mod_init_func contains:

__mod_init_func:FFFFFFF006DF41A0 ; Segment type: Pure data
__mod_init_func:FFFFFFF006DF41A0   AREA __mod_init_func, DATA, ALIGN=3
__mod_init_func:FFFFFFF006DF41A0 ; ORG 0xFFFFFFF006DF41A0
__mod_init_func:FFFFFFF006DF41A0   DCQ InitFunc_0
__mod_init_func:FFFFFFF006DF41A8   DCQ InitFunc_1
__mod_init_func:FFFFFFF006DF41B0   DCQ InitFunc_2
__mod_init_func:FFFFFFF006DF41B0 ; __mod_init_func ends
Copy the code

InitFunc_1 is used to initialize global variables, InitFunc_0 and InitFunc_2 are used to initialize Main classes of amfi. Let’s focus on InitFunc_2:

_QWORD *InitFunc_2()
{
    _QWORD *result; // x0
    result = (_QWORD *)OSMetaClass::OSMetaClass(&some_this, "AppleMobileFileIntegrity", some_inSuperClass, 136LL);
    *result = some_vtable;
    return result;
}
Copy the code

OSMetaClass::OSMetaClass is the core constructor of the class, which in effect added the class to OSMetaClass global context spassable classes for use in post processes Here we omit the Grow logic when classes are not large enough:

/********************************************************************* * The core constructor for a MetaClass (defined with this name always * but within the scope of its represented class). * * MetaClass constructors are invoked in OSRuntimeInitializeCPP(), * in between calls to OSMetaClass::preModLoad(), which sets up for * registration, and OSMetaClass::postModLoad(), which actually * records all the class/kext relationships of the new MetaClasses. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /

OSMetaClass::OSMetaClass(
	const char        * inClassName,
	const OSMetaClass * inSuperClass,
	unsigned int        inClassSize)
{
    // ...
    sStalled->classes[sStalled->count++] = this;
    // ...
}
Copy the code

The Post stage

The POST phase is about maintaining the relationship between Kext and classes:

OSReturn
OSMetaClass::postModLoad(void * loadHandle)
{
    // ...
    // static OSDictionary * sAllClassesDict;
    sAllClassesDict = OSDictionary::withCapacity(kClassCapacityIncrement);
    sAllClassesDict->setOptions(OSCollection::kSort, OSCollection::kSort);
    myKextName = const_cast<OSSymbol *>(OSSymbol::withCStringNoCopy(
				    sStalled->kextIdentifier));
    myKext = OSKext::lookupKextWithIdentifier(myKextName);
    
    /* First pass checking classes aren't already loaded. If any already * exist, we don't register any, and so we don't technically have * to do any C++ teardown. * * Hack alert: me->className has been a C string until now. * We only release the OSSymbol if we store the kext. */
    IOLockLock(sAllClassesLock);
    for (unsigned int i = 0; i < sStalled->count; i++) {
        const OSMetaClass * me = sStalled->classes[i];
        
        unsigned int depth = 1;
        while ((me = me->superClassLink)) {
            depth++;
        }
        
        // static unsigned int sDeepestClass;
        if (depth > sDeepestClass) {
            sDeepestClass = depth;
        }
    }
    IOLockUnlock(sAllClassesLock);
    
    IOLockLock(sAllClassesLock);
    for (unsigned int i = 0; i < sStalled->count; i++) {
        const OSMetaClass * me = sStalled->classes[i];
        OSMetaClass * me = sStalled->classes[i];
        me->className = OSSymbol::withCStringNoCopy((const char *)me->className);
        sAllClassesDict->setObject(me->className, me);
        me->reserved->kext = myKext;
        myKext->addClass(me, sStalled->count);
    }
    IOLockLock(sAllClassesLock);
    
    sBootstrapState = kCompletedBootstrap;
    sStalled = NULL;
    return kOSReturnSuccess;
}
Copy the code

After the POST process is complete, all OSMetaClass instances of Kext are logged as name2Instance in the global registry sAllClassesDict, Each OSMetaClass instance also maintains the instance2kext mapping (me->reserved->kext = myKext). Each kext maintained all of its own instances (myKext->addClass(me, scost ->count)). This ensures that an instance can be found by class name, the corresponding OSKext object can be found by instance, and all OSMetaClass instances can be obtained by OSKext object.

Get AppleMobileFileIntegrity kext service

If we search kernelCache for the “AppleMobileFileIntegrity” string cross-reference, it’s not hard to find the code that accesses the AMFI service through IOService, For example com. Apple. Security. In the sandbox initAMFI:

__int64 initAMFI(a)
{
  OSDictionary *matchDict_1; // x0
  OSDictionary *v1; // x19
  IOService *v2; // x0
  __int64 v4; // x0
  __int64 matchDict; // [xsp+8h] [xbp-18h]

  matchDict = 0LL;
  matchDict_1 = (OSDictionary *)IOService::nameMatching("AppleMobileFileIntegrity".0LL);
  // ...
  v1 = matchDict_1;
  v2 = IOService::waitForMatchingService(matchDict_1, 0xFFFFFFFFFFFFFFFFLL);
  matchDict = OSMetaClassBase::safeMetaCast(v2, *(_QWORD *)qword_FFFFFFF006F9D038);
  // ...
}
Copy the code

IOService::nameMatching: IOService::nameMatching: IOService::nameMatching

{
    "IONameMatch": "AppleMobileFileIntegrity"
}
Copy the code

Then by IOService: : waitForMatchingService matching service, comb core logic is as follows:

IOService *
IOService::waitForMatchingService( OSDictionary * matching,
    uint64_t timeout) {
    // ...
    do {
    	result = (IOService *) copyExistingServices( matching,
    	    kIOServiceMatchedState, kIONotifyOnce );
    	// ...
}

OSObject *
IOService::copyExistingServices( OSDictionary * matching,
    IOOptionBits inState, IOOptionBits options ) {
    // ...
    IOServiceMatchContext ctx;
    ctx.table   = matching;
    ctx.state   = inState;
    ctx.count   = 0;
    ctx.done    = 0;
    ctx.options = options;
    ctx.result  = NULL;
    
    IOService::gMetaClass.applyToInstances(instanceMatch, &ctx);
    // ...
}

void
OSMetaClass::applyToInstances(OSMetaClassInstanceApplierFunction applier,
    void * context) const
{
    IOLockLock(sInstancesLock);
    if (reserved->instances) {
        applyToInstances(reserved->instances, applier, context);
    }
    IOLockUnlock(sInstancesLock);
}
Copy the code

Can see the final by traversal IOService: : gMetaClass. Reserved – > all instances of the IOService instance matching, And IOService: : gMetaClass reserved – > instances is just in our OSKext: : load – > OSKext: : sendPersonalitiesToCatalog phases are registered.

conclusion

At this point, the entire Prelinked Kext registration, loading, startup, and acquisition process is complete. In order to better study the code signature mechanism, the author first analyzed the working mechanism of AMFID, and then analyzed the interaction logic between amfi. kext and AMFID, and then the loading of amfi. kext. It took a lot of time to analyze the entire loading mechanism, and this article is a roundup. In the following articles, we will focus on analyzing the MAC Policy Module registered by AMFI and its working mechanism, which will involve more complex logic.

The resources

  1. Jonathan Levin: Mac OS X and iOS Internals: To the Apple’s Core
  2. Apple: Darwin – XNU – 6153.11.26