Sitemap

Snake&Apple VI — AMFI

22 min readMar 25, 2024

Introduction to the Apple Mobile File Integrity on the macOS with Python

Press enter or click to view image in full size

INTRO

Welcome to another article in the series on macOS security internals!

In Snake&Apple II — Code Signing, I explained how Code Signing works and introduced AMFI’s Trust Caches without further explanation.

Later, in Snake&Apple IV—Dylibs, I introduced the Dynamic Library concept and protections against Dynamic Library Hijacking vulnerability.

Then, in Snake&Apple V—Dyld, I presented Dyld Environment Variables, of which DYLD_INSERT_LIBRARIES has special meaning in macOS security as it allows for injecting custom code into a process memory using Dylib.

This article extends the above topics and explains (AMFI) the Apple Mobile File Integrity kernel extension, which plays a huge part in the process of Code Signature validation and protects against unsigned code injections.

The major part of this article describes the startup of the Kernel Extension from its early boot loading until it registers as a MAC policy.

Then, it shows how specific AMFI policy can be called by other system components focusing on protection against DYLD_INSERT_LIBRARIES.

Lastly, the article explains how AMFI communicates with amfid via Mach messages with the MIG subsystem to validate the Code Signature.

Press enter or click to view image in full size

Please note that some topics have been intentionally omitted and will be addressed in future articles. However, leave a comment if you have any questions or need clarification about anything written here while reading. I guarantee a response and will use your feedback for future articles.

The Snake&Apple VI. AMFI repository contains all of the code used.

AMFI

Apple Mobile File Integrity is a subsystem consisting of a user-mode daemon amfid and AppleMobileFileIntegrity.kext kernel extension.

# User-mode daemon:
/usr/libexec/amfid
# Kernel-space kext:
/System/Library/Extensions/AppleMobileFileIntegrity.kext

Its main duty is to protect the code integrity through the Mandatory Access Control Framework policy handlers responsible for the following tasks:

  • Code Signature validation
  • In-kernel entitlement support
  • Mach task port protection
  • Debug restriction
  • Platform mismatch check (running iOS app on macOS)
  • Memory mapping protection (against mmap +x | unsigned code)
  • Library validation (against unsigned code)
  • Launch Constraints enforcement (running from a different directory)
  • Dyld Environment Variable restriction (prune DEV in Dyld on load)

The possibility of the AMFI subsystem’s enforcing different things is limitless. We are likely to see it grow in the future.

We will look into some of the above-mentioned tasks by reversing the components of the Apple Mobile File Integrity. The article aims to show entry points to the security mechanisms rather than reversing its functionalities in depth.

Kernel Extension

The AppleMobileFileIntegrity.kext is a kernel extension (.kext) whose fully resolved name is com.apple.driver.AppleMobileFileIntegrity:

Press enter or click to view image in full size

The binary could be found in a casual place /System/Library/Extensions but due to optimization on macOS Sonoma, we cannot see it there:

Press enter or click to view image in full size

To analyze it we must first extract it from the Kernelcache.

Kext binary extraction

The kernel extensions on macOS Sonoma are loaded into a kernelcache. We can use ipsw to extract any kext we want, like AMFI below:

# Decompressing Kernelcache
ipsw kernel dec $(ls /System/Volumes/Preboot/*/boot/*/System/Library/Caches/com.apple.kernelcaches/kernelcache) -o kernelcache
# Extracting com.apple.driver.AppleMobileFileIntegrity
ipsw kernel extract $(ls kernelcache/System/Volumes/Preboot/*/boot/*/System/Library/Caches/com.apple.kernelcaches/kernelcache.decompressed) com.apple.driver.AppleMobileFileIntegrity --output amfi_kext

It is good to dump the __PRELINK_INFO,__info section that stores the key information about kexts (some of them normally reside in Info.plist):

Press enter or click to view image in full size

I created a tool for that, but we can also just use dd here:

# Using CrimsonUroboros
CrimsonUroboros -p kernelcache.decompressed --dump_prelink_info
# Using dd
dd if=kernelcache.decompressed of=prelink_info bs=1 skip=$((0x04908000)) count=$((0x04c5bff0 - 0x04908000))
Press enter or click to view image in full size
dumpPrelink_info

It is enough to decompress Kernelcache once to extract different kexts.

Dependent kexts

In the BSD manual pages of the kextstat we can read the Linked Against column stores info about API exported from other kernel extensions:

Press enter or click to view image in full size
Source

We can utilise this information to get all kernel extensions that reference to AppleMobileFileIntegrity.kext (in other words, that uses AMFI API):

kextstat | grep "<.* 19 .*>" | cut -c53- | cut -d "(" -f 1

The most interesting one here is sandbox, the AMFI’s big brother. Both of them are policy modules of the Mandatory Access Control Framework.

Press enter or click to view image in full size

I will describe the Mandatory Access Control Framework (MACF) and the other kernel extension in detail in the following articles.

Mach-O analysis

The binary file type is a KEXT_BUNDLE built for arm64e architecture:

Press enter or click to view image in full size

Decompressed AMFI kext binary does not have Code Signature because it is part of kernel cache, which SIP secures on the Preboot volume:

Additionally, AMFI kext validation is performed by the mac_kext_check_load during loading, to check if the extension is not loaded from userland.

Press enter or click to view image in full size

Yet, we can observe the CodeResources file inside the _CodeSignature, but it only stores hash for MacOS/AppleMobileFileIntegrity_kasan which is absent:

Press enter or click to view image in full size

The header flags are TWOLEVEL, NOUNDEFS, DYLDLINK:

Press enter or click to view image in full size

It does not import any symbols from dynamic libraries, but as we will see later, it depends on other kexts and import symbols from there:

Press enter or click to view image in full size

As we saw in Dependent kexts (section of this article) it also exports some symbols for other Kernel Extensions (list of imported | exported symbols).

There is no __PAGEZERO segment, and we can observe a non-standard __TEXT_EXEC. Additionally, it is loaded into a high-memory region:

Normally, the __text section where the code lies is stored in the __TEXT segment. In the Kernel Extensions, this code is stored in __TEXT_EXEC instead.

Press enter or click to view image in full size

There is __LINKEDIT segment, which is linked by kxld(dyld for kernel). Dependencies are specified in Info.plist & LC_SYMTAB | LC_DYSYMTAB.

There are also some non-standard sections, such as __os_log, __mod_init_func, __mod_term_func, __kaloc_type and __kaloc_var:

Press enter or click to view image in full size

__os_log stores format strings for logging API’s, __mod_init_func/ __mod_term_func stores constructors/destructors, for kalocs read this:

Kexts are initialised by the bootloader and do not contain LC_MAIN or LC_UNIXTHREAD load commands, but the Kernel Cache does have it:

Press enter or click to view image in full size

The AMFI entrypoint is described below.

Kext Information Property List

The extension is loaded during the early boot process, as indicated by the OSBundleRequired property in the Info.plist file, which is set to Root informing the bootloader that kext is necessary when mounting the root.

Press enter or click to view image in full size

In the OSBundleLibraries we may find all the required dependencies:

Press enter or click to view image in full size

In IOKitPersonalities we can see personalities AMFI kext expose:

Press enter or click to view image in full size
CFBundleIdentifier    com.apple.driver.AppleMobileFileIntegrity
IOClass AppleMobileFileIntegrity
IOMatchCategory AppleMobileFileIntegrity
IOProviderClass IOResources
IOResourceMatch IOBSD
IOUserClientClass AppleMobileFileIntegrityUserClient

We can use ioreg command or IORegistryExplorer.app to see them after the kext is registered (described in the AMFI Startup below):

# #List all registered kexts
ioreg -l
Press enter or click to view image in full size

One more important property in Info.plist is AppleSecurityExtension, which is set to true, marking AMFI kext as a security extension:

Press enter or click to view image in full size

When loaded from the kernel cache, some of the above information from Info.plist are stored inside the __PRELINK_INFO,__info section.

__PRELINK_INFO

Inside the kernel cache __PRELINK_INFO,__info section, we may find the kext loading address in the _PrelinkExecutableLoadAddr property:

Press enter or click to view image in full size

We must convert the negative number to two’s complement representation if we want to use it in the disassembler to find where it points to:

hex((-2198901033600 + (1<<64)) % (1<<64)) #0xfffffe000748f580

Then, we can inspect this address in both kernelcache.decompressed or extracted AMFI kext and observe it is pointing to the AMFI’s Mach Header:

Press enter or click to view image in full size

Moreover, we can calculate the offset of the prelinked AMFI kext in the kernel cache file. We just need the base vm address of __TEXT segment:

Press enter or click to view image in full size

Then, we can calculate it manually or with CrimsonUroboros:

base = 0xfffffe0007004000
amfi = 0xfffffe000748f580
file = amfi - base # 0x48b580
Press enter or click to view image in full size
calcRealAddressFromVM

We can double-check if the file address is valid in Hopper. As we can see, the address is valid, and kext resides in the __PRELINK_TEXT segment:

Press enter or click to view image in full size

The exact information we could get from the Kernel Cache load command LC_FILESET_ENTRY: 0xfffffe000748f580 and 0x48b580:

Press enter or click to view image in full size

The kext here is stored as a prelinked object file. We can dump it using:

Press enter or click to view image in full size
dumpKernelExtensionFromPRELINK_TEXT

This is a kext Mach-O before kxld linked it during kernel loading in loadExecutable. We may observe that all sections are nulled|FF-ed:

Press enter or click to view image in full size

I also created a parser for the _prelinkinfo properties for given kext:

Press enter or click to view image in full size
parsePRELINK_INFO_plist

Yeah, but where is the entry point in the kernel extension? To answer this question, let’s look where the _PrelinkKmodInfo points to.

kmod_info

The _PrelinkKmodInfo stores the pointer to kmod_info, which in its structure have start, which can be the kext entry point address:

Press enter or click to view image in full size
kmod.h

Decompilers do not handle kmod_info structure parsing:

Press enter or click to view image in full size

I made a parser for this structure. Some values are dynamically overwritten during kext loading. For instance, next here or reference_count here.

Press enter or click to view image in full size
parsekmod_info

I do not know why 4 LSB in start: 0x8010e4fe and stop: 0x8018e4fe are set, but we need only the 4 MSB to calculate AMFI’s entry point:

kernelcache__TEXT = 0xfffffe0007004000
start = 0x02acbc44
stop = 0x02acbc68
entrypoint = kernelcache__TEXT + start # 0xfffffe0009acfc44
exitpoint = kernelcache__TEXT + stop # 0xfffffe0009acfc68

I created a parser for entrypoint and exitpoint calculation:

Press enter or click to view image in full size
calcKextEntryPoint

The __start can be the kext entry point and is used in OSKext::start and the __stop is called in OSKext::stop on exit when stopping the kext:

Press enter or click to view image in full size

By saying it CAN BE, I mean there are (maybe?) some implementations of realmain that runs here, but AMFI startup looks differently, as explained later.

When we inspect the referenced __realmain we will find out it points to 0:

Press enter or click to view image in full size

So, for the AMFI kernel extension, this code does nothing because we can translate the above assembly to the below pseudo-code, which means we will just return 0 here, and 0 is KERN_SUCCESS:

In the context of the OSKext::start which is the only place where the __start is used, we will just proceed with the execution:

Press enter or click to view image in full size
OSKext.cpp

To get to the real entrypoint, let’s analyse how the AMFI kext is loaded.

AMFI STARTUP

The AMFI startup takes place during the MACF initialization. Let’s follow the XNU code inside the kernel_bootstrap function to see what happens.

First, we initialize the MACF policy subsystem using mac_policy_init and the Mach IPC subsystem using kernel_startup_initialize_upto:

Press enter or click to view image in full size
startup.c

Then, in the kernel_bootstrap_thread we execute mac_policy_initmach:

Press enter or click to view image in full size
startup.c

The mac_policy_initmach ensures that any security extensions are loaded using load_security_extensions_function:

Press enter or click to view image in full size
mac_base.c

The below assignment links load_security_extensions_function to bootstrapLoadSecurityExtensions function:

Press enter or click to view image in full size
bootstrap.cpp#

The bootstrapLoadSecurityExtensions serves as a bridge between the MAC policy initialization process and the actual loading of security extensions by calling sBootstrapObject.loadSecurityExtensions:

Press enter or click to view image in full size
bootstrap.cpp

When we call the loadSecurityExtensions, we iterate over various kernel extensions and load the ones marked as Security Extensions:

As we saw in the AMFI Info.plist, the AppleSecurityExtension, was set to true so it will be loaded (initialized) in this step:

Press enter or click to view image in full size
bootstrap.cpp

The OSKext::loadKextWithIdentifier calls the OSKext::load. Describing it here as a whole would be too much, but a few things need to be mentioned:

Press enter or click to view image in full size
OSKext.cpp

After a few more functions, we finally get to the OSKext::start:

Press enter or click to view image in full size
OSKext.cpp

At line 8308 we use OSRuntimeInitializeCPP to search for and execute the initializers and then at the line 8310 we call the kext entry point:

Press enter or click to view image in full size
OSKext.cpp

However, we only execute the startfunc if we successfully run the initializers. This is the __start address 0xfffffe0009acfc44we calculated earlier using kmod_info and it just informs that the kext started, but it is not its entry point.

The OSRuntimeInitializeCPP uses OSRuntimeCallStructorsInSection to scan for __mod_init_func section in AMFI kext and calls these 4 constructors:

The __GLOBAL__sub__I_filename is a common naming convention for metaclass constructors, while __GLOBAL__D_a is used for all destructors.

Press enter or click to view image in full size
Decompiled KernelCache + all kexts in Hopper

Strangely, when I decompiled only the extracted AMFI kext, I could see the unresolved addresses below:

Press enter or click to view image in full size
Decompiled AMFI kext

I added a flag for extracting constructors to the SnakeI module:

Press enter or click to view image in full size
printConstructors

It looks like we need to add the 4 MSB (02aab2fc) to the Kernel Cache __TEXT segment base address to calculate the valid pointers:

kernelcache_text = 0xfffffe0007004000
init_1_MSB == 0x10000002aab2fc & 0xFFFFFFFF # 0x2aab2fc
init_1 = kernelcache_text + init_1_MSB # 0xfffffe0009aaf2fc

This is the same situation as with kmod_info->start. After all, they are not imported, so why? If you know the answer to this magic, please comment ^^.

With the calculated address 0xfffffe0009aaf2fc we can see the proper decompiled code in both Kernel Cache and the solely decompiled AMFI:

Press enter or click to view image in full size

In summary, these 4 functions initialize metadata related to the AppleMobileFileIntegrityUserClient, AppleMobileFileIntegrity and OSEntitlements classes with OSMetaClass and sets vftable accordingly.

  • AppleMobileFileIntegrityUserClient inherited from the IOUserClient
Press enter or click to view image in full size
Press enter or click to view image in full size
  • AppleMobileFileIntegrity inherited from the IOService
Press enter or click to view image in full size

Additionally, TrustedKeys initialize global symbols related to trusted keys by creating OSSymbol instances for specific strings:

Press enter or click to view image in full size

It is important to note that we do not initialize instances of AMFI classes, only the gMetaClass global object related to them with OSMetaClass. This allows for the registration of the kext and its personalities in IORegistry in OSKext::load.

It is used later by the IOService::start to complete AMFI’s IOService startup.

The above-mentioned registration is a long chain that starts just after returning from the OSKext::Start to OSKext::Load here:

Press enter or click to view image in full size
OSKext.cpp

The whole chain with permalinks to the corresponding jump lines in each function can be seen below. I will not describe the whole process. Just want to get to the point where the actual AMFI’s IOService startup begins:

  • First, we obtain the IOService instance corresponding to AMFI:

OSKext::load -> OSKext::sendPersonalitiesToCatalog -> IOCatalogue::addDrivers -> IOService::catalogNewDrivers -> IOService::startMatching -> IOService::doServiceMatch -> OSMetaClass::addInstance <- Here we utilise the initialized earlier gMetaClass by the AMFI initializers withOSMetaClass and after finishing this function, we return to the →IOService::doServiceMatch.

  • AMFI’s IOService startup:

IOService::doServiceMatch -> IOService::probeCandidates -> IOService::startCandidate -> IOService::start

Press enter or click to view image in full size
IOService.cpp

The IOService::start is an interface method when called, it executes the start method for IOService in KEXT_NAME::start(IOService*):

Press enter or click to view image in full size

This is the real AMFI entry point: AppleMobileFileIntegrity::start.

Finally!

The AMFI loading started during the early boot in the MACF initialization. However, we were still looking for the registration method for its mac policy.

Entrypoint

We will now execute the initializeAppleMobileFileIntegrity which, in the end, registers its Mac policies to MACF. Yet before that happens, there are many different things. Let’s analyze them in general:

Press enter or click to view image in full size

I reversed the initializeAppleMobileFileIntegrity and made it more human-friendly below (the original Ghidra decompiled code is here).

First, we initialize locks for thread safety and check for boot arguments, and then, according to them, we set various security-related settings.

Press enter or click to view image in full size

We initialise trust cache, entitlements support, AMFI interface and various handlers that other components can use to call AMFI functions:

The pointers_list contains all AMFI MAC policies that will be registered. Each points to a function (hook) executed by the Mac entry point on __mac_syscall.

Press enter or click to view image in full size

Policies name convention in the MACF is: mpo_<object>_<operation> or mpo_<object>_check_<operation> for instance: mpo_cred_label_init_t.

Finally, we register a MAC policy by calling mac_policy_register, configure security-related settings based on boot arguments and system properties using configurationSettingsInit and release locks:

Registering MAC policy — means adding it to a linked list of MACF policies. They can be later called by other system components using __mac_syscall and other mpo_* functions that trigger hooks/handlers in a given service.

Press enter or click to view image in full size

The AMFI startup is completed, policies are registered and can be used through policy handlers. In this article, I will introduce only the ones responsible for Code Signature validation and DYLD_INSERT_LIBRARIES env variable removal.

Turning off AMFI

This is not recommended from a security perspective. However, during research, we may want to remove AMFI. It can be done the following way:

# SIP off
sudo nvram boot-args="amfi_get_out_of_my_way=1"
# + reboot
# Turn ON
sudo nvram boot-args=""
## or reset nvram
sudo nvram -c

To turn the AMFI on again, we can clear the boot arguments or delete all of the firmware variables: sudo nvram boot-args="" | sudo nvram -c.

MAC policy syscall

I described thoroughly how the policy_syscall handler in AMFI kext can be triggered by __mac_syscall inside the Dyld during the program startup.

The handler is crucial for setting AMFI flags used by the Dyld to remove env variables, so the DYLD_INSERT_LIBRARIES cannot be used to inject code via Dylib.

PROTECTIONS

After reading DYLD — Do You Like Death? (VI) we know how to interact with the AMFI handlers registered as MAC policies. I described only policy_syscall there, but the idea is similar to other hooks.

These policies can be used to enforce different things. The one described in the article cleared the Dyld Environment Variables on certain conditions.

Code Injection

These conditions are specified in the below functions, which utilise state of the calling process gathered by macos_dyld_policy_collect_state to set AMFI flags in Dyld that decide whether clear Dyld Environment Variables or not:

Press enter or click to view image in full size

Below, we can see the AMFI flags that allow for code injection using DYLD_INSERT_LIBRARIES and conditions when they are not set:

If these flags are not set the Dyld Environment Variables are cleared.

Press enter or click to view image in full size
DYLD — Do You Like Death? (VI)

I created a Proof of Concepts for these injection scenarios (and a few more below). In the case of a valid injection with the DYLD_INSERT_LIBRARIES, we should see the string crimson_constructor called in the terminal stdout:

Press enter or click to view image in full size
Press enter or click to view image in full size
Code of the injected Dylib
  • Binaries with SGID or SUID bit set:
Press enter or click to view image in full size
Press enter or click to view image in full size
Press enter or click to view image in full size

In the case of CS_REQUIRE_LV Dyld Environment Variables are not cleared! Only the injected library Code Signature will be validated.

# We can see the validation error in the log:
log stream | grep -i "amfi\|hello\|Integrity\|suid\|crimson_constructor"
2024-03-22 18:10:19.979575+0100 0x601cc Error 0x0 0 0 kernel: (AppleMobileFileIntegrity) Library Validation failed: Rejecting '/Users/karmaz95/snake_apple/a' (Team ID: none, platform: no) for process 'hello(23543)' (Team ID: N/A, platform: no), reason: mapping process and mapped file (non-platform) have different Team IDs
Press enter or click to view image in full size
  • Binaries with the __RESTRICT,__restrict segment:
Press enter or click to view image in full size

Additionally, AMFI will stop binaries that have CS_RUNTIME | CS_RESTRICT | CS_REQUIRE_LV __restrict section && at the same time, uses relative paths for dylibs loading && cs.allow-relative-library-loads is not used.

DEV Exceptions

Binaries with com.apple.security.cs.allow-dyld-environment-variables entitlement will allow for Dyld Environment Variables but:

Press enter or click to view image in full size
We can see only one initializer
  • Reject code injections and clear the Dyld Environment Variables if has CS_RESTRICT, __RESTRICT,__restrict segment or SUID|SGID bit set:
Press enter or click to view image in full size
Press enter or click to view image in full size
The contents of entitlements.plist

So, as we can see, the entitlement itself is not very strong for the attacker. However, if combined with one of the entitlements that disable library validation, it can bypass the protection in applications with CS_RUNTIME:

com.apple.security.cs.disable-library-validation
com.apple.private.security.clear-library-validation
Press enter or click to view image in full size
Binary with Hardened Runtime

Fortunately, SUID | GUID binaries are still protected. Even if they have both entitlements mentioned above and are signed without CS flags:

Press enter or click to view image in full size

Conclusion — if the binary Code Signature has CS_RUNTIME | CS_RESTRICT | CS_REQUIRE_LV bits set, or binary has the __RESTRICT,__restrict segment or file has SUID|SGID bit set, then DYLD_INSERT_LIBRARIES will not work.

Press enter or click to view image in full size
checkDyldInsertLibraries & testDyldInsertLibraries

As we could see the policy_syscall handler wrap a strong protection logic. Yet, there is one more policy handler (described in the next point) and was used much earlier before Dyld and this is probably the most important AMFI policy.

Signature Validation

All executed code on macOS must be validly signed. This is the first place the AMFI protects us when executing apps:

Press enter or click to view image in full size

The core functionality lies in vnode_check_signature handler, which can be triggered by mpo_vnode_check_signature_t in XNU on binary load:

Press enter or click to view image in full size
Source

The second stage of signature validation takes place on access to the binary code (page faults) using cs_validate_page:

Press enter or click to view image in full size
Source

The vnode_check_signature takes 10 arguments from the caller:

Press enter or click to view image in full size
mac_policy.h

The whole verification process can be summed up to checking for CDHash in Trust Caches and, if not found, delegating the check to amfid daemon:

If an Apple-signed binary is not in the Trust Cache, AMFI will refuse to load it.

Press enter or click to view image in full size
Source

Actually, there are a lot more steps in the vnode_check_signature which a big part takes entitlements validation and magic directories check:

Press enter or click to view image in full size
GHIDRA_vnode_check_signature.c

Paths matched by directories in the magic set are automatically trusted, even if the code signature is invalid or self-signed:

Press enter or click to view image in full size

However, AMFI will still reject them if they have restricted entitlements.

Additionally, there is a check for entitlement, which allows the dynamic loading of unsigned code com.apple.private.amfi.can-execute-cdhash:

Press enter or click to view image in full size
GHIDRA_vnode_check_signature.c
# List of all entitlements strings in vnode_check_signature
com.apple.rootless.storage.cvms
jit-codesigning
com.apple.security.get-task-allow
com.apple.private.oop-jit.loader
com.apple.private.amfi.can-execute-cdhash
com.apple.dyld_sim
com.apple.private.oop-jit.runner

There is a great paper about CVE-2022–42855 from Project Zero where we can find how the entitlements are checked by vnode_check_signature.

Launch Constraints

Applications with CDHash found in any Trust Caches are subject to one more validation: Launch Constraints. They control who, how and from where can launch a process (also work for third-party apps).

They are handled by _proc_check_launch_constraints only if SIP is enabled:

Press enter or click to view image in full size

The hook can be triggered by mpo_proc_check_launch_constraints_t.

There is an excellent article on that from Csaba Fitzl.

Amfid

If the given CDHash is not found in the Trust Caches, the full signature is validated by sending a Mach message to the userspace amfid daemon.

However, if a CMS Blob is present, the CoreTrust is called to validate the Certificate Chain of Trust using CTEvaluateAMFICodeSignatureCMS before amfid validation (I did not check, but I guess trustd is involved).

# Binary
/usr/libexec/amfid
# Plist
/System/Library/LaunchDaemons/com.apple.MobileFileIntegrity.plist
Press enter or click to view image in full size

It starts in the verify_code_directory called in the vnode_check_signature:

Press enter or click to view image in full size

It communicates with amfid via IPC using the host's special port 18 (0x12) which is assigned to the amfid with plist (shown below):

Press enter or click to view image in full size
host_special_ports.h

The special port is required because, from kernel space, there is no way to query Mach-named ports like from user mode using XPC API. The special ports are restricted to launchd only if SIP is enabled to defend against MiTM.

Press enter or click to view image in full size

To find out the MIG subsystem in amfid we can use this script in Hopper:

Press enter or click to view image in full size

The structure _MIG_subsystem_1000 is described by mig_subsystem:

Press enter or click to view image in full size
mig.h
# sizeof(_MIG_subsystem_1000) == 0x28 || 40
server = 0x0000000100007e74
start = 0x0000003e8 # First message == 1000
end = 0x0000003f0 # Last message == 1008 - 1
maxsize = 0x0000000000001070
reserved = 0x0000000000000000
routine[0] = 0x0000000000000000

After that routine_descriptor structures follow. They start from the function pointer, which points to handlers:

Press enter or click to view image in full size
mig.h

Unfortunately, the script cannot distinguish routine descriptions, but knowing that each structure has a size of 40B we can divide them:

Press enter or click to view image in full size

The first address in each routine is a pointer to the function (_MIG_msg_X) that handles the call (renamed by the script from sub_100007ea8 etc.):

Press enter or click to view image in full size

I created a tool to detect MIG subsystems and its message handlers:

Press enter or click to view image in full size
parseMIG

We can observe that 0x100007ea8 is a pointer to msg_1000. This handler will eventually call the verify_code_directory function:

Press enter or click to view image in full size

It first distinguishes the platform on which the function is called (macOS|iOS) and runs the adequate functions. Here, the validation begins:

Press enter or click to view image in full size

The core framework that conducts CS validation is Security.framework:

Press enter or click to view image in full size

After the validation, a response is carried out to the AMFI using reply port 1100 (request_port + 100). We can trace it in lldb on mach_msg call:

Press enter or click to view image in full size
Attach to amfid and set a breakpoint on mach_msg

After attaching to amfid and setting a breakpoint, we must run any third-party app or self-signed binary for the first time.

We will break on the first message — request. We should skip it to get the response.

Press enter or click to view image in full size

Then, we should see another breakpoint hit, which stores the reply port number in $x0+0x10. We can see here 0x44c which in decimal is 1100:

Press enter or click to view image in full size

The final decision on whether the code is allowed to run is made by AMFI kext and it depends on the response its received on port 1100 from amfid.

FINAL WORDS

It was quite a read, covering not only AMFI itself but many other internal mechanisms of this beautiful system. As a result of this article, I added a lot of new options to CrimsonUroboros, which are displayed below:

Press enter or click to view image in full size
AMFI module of the CrimsonUroboros

The --injectable_dyld — test_insert_dylib and --test_insert_dylib flags are very handy when checking for potential code injections.

I have certainly not exhausted the topic. I have not even explained all the MIG handlers in amfid, let alone all in the kernel extension itself.

However, after reading this article, I hope everyone interested will know where to start their adventure with AMFI research.

Below is also a list of articles that I think are worth reading if you would like to deepen your knowledge about Code Signing validation and AMFI.

References

The following list of articles covers the topic discussed here. There are also various vulnerabilities that the root problem lies in AMFI & Code Signing.

Also, self-reference, in the article, you will find precisely where Dyld uses AMFI DYLD — Do You Like Death? (VI)

What’s next?

In the upcoming articles, we will explore different approaches for injecting code into a process. We will also learn about other protection mechanisms and system components, such as the Sandbox kext, Gatekeeper and TCC.

If you enjoyed the article, please share it. If you have any suggestions or find a bug, please let me know in the comments.

Sssssssssstay tuned.

--

--

No responses yet