How to Extract and Analyze Kernel Extensions (Kexts) from a Kernelcache

The ipsw toolkit parses the __PRELINK_INFO segment of a Mach-O kernelcache to extract kernel extension metadata, providing both Go library functions (GetKexts, KextList, KextJSON) and a CLI command (ipsw kernel kexts) to output kext bundle IDs, versions, and load addresses.

Extracting and analyzing kernel extensions (kexts) from a kernelcache is essential for iOS and macOS security research and firmware analysis. The blacktop/ipsw open-source project provides a complete toolkit for parsing kernelcache files, reading the __PRELINK_INFO section, and converting binary plist data into structured CFBundle objects that represent each kext.

Understanding the Kernelcache Structure

iOS and macOS kernelcaches are Mach-O files that store prelinked kernel extensions in the __PRELINK_INFO segment. This segment contains a plist-encoded dictionary mapping bundle identifiers to kext metadata, including version strings, dependencies, and load addresses. The ipsw library reads this section to reconstruct the original CFBundle structures without requiring the XNU source code.

Extracting Kexts with the ipsw Go Library

The core extraction logic resides in pkg/kernelcache/kext.go. This file provides functions to parse the prelink info, resolve virtual memory addresses, and format output for both human reading and automated processing.

The GetKexts function reads the __PRELINK_INFO.__info section, trims null bytes, and decodes the binary plist into a PrelinkInfo struct containing a slice of CFBundle objects.

import "github.com/blacktop/ipsw/pkg/kernelcache"

// f is an open *macho.File
kexts, err := kernelcache.GetKexts(f)
if err != nil {
    log.Fatal(err)
}
for _, kext := range kexts {
    fmt.Printf("Found kext: %s (%s)\n", kext.ID, kext.Version)
}

Resolving Virtual Memory Addresses

For detailed analysis, GetKextStartVMAddrs reads the __kmod_start array from the kernelcache and converts raw pointers into file offsets using Mach-O utilities. This maps each kext to its actual load address in kernel memory.

addrs, err := kernelcache.GetKextStartVMAddrs(f)
if err != nil {
    log.Fatal(err)
}
// addrs slice corresponds to the kexts returned by GetKexts

Generating Lists and JSON Output

The KextList function provides a unified interface that returns either a diff-friendly list of bundle IDs and versions, or a detailed list including load addresses when diffable is set to false.

// Diff-friendly output: "com.apple.driver.X (1.0.0)"
simpleList, err := kernelcache.KextList(f, true)

// Detailed output: "0xfffffe0007004000: com.apple.driver.X (1.0.0)"
detailedList, err := kernelcache.KextList(f, false)

For programmatic analysis, KextJSON marshals the complete CFBundle slice to JSON format, preserving all metadata fields from the original plist.

jsonData, err := kernelcache.KextJSON(f)
fmt.Println(jsonData)

Using the ipsw CLI to Analyze Kexts

The command-line interface in cmd/ipsw/cmd/kernel/kernel_kexts.go wraps the library functions for quick inspection without writing Go code.

First, extract the kernelcache from an IPSW file:

ipsw extract kernelcache -d iPhone12,8 MyDevice_13.5.1_20F66_Restore.ipsw

Then list all kexts with their bundle identifiers and versions:

ipsw kernel kexts -i kernelcache.release.iPhone12,8

For detailed analysis including load addresses and JSON export:

ipsw kernel kexts -i kernelcache.release.iPhone12,8 -j

Comparing Kexts Between Kernelcaches

Security researchers often compare kext sets between iOS versions to identify new drivers or removed functionality. The internal/diff/diff.go file demonstrates this pattern by calling kernelcache.KextList on two different kernelcaches and computing the difference.

package main

import (
	"fmt"
	"log"

	"github.com/blacktop/go-macho"
	"github.com/blacktop/ipsw/pkg/kernelcache"
)

func main() {
	f1, err := macho.OpenFile("kernelcache1")
	if err != nil {
		log.Fatal(err)
	}
	defer f1.Close()
	
	f2, err := macho.OpenFile("kernelcache2")
	if err != nil {
		log.Fatal(err)
	}
	defer f2.Close()

	k1, _ := kernelcache.KextList(f1, true)
	k2, _ := kernelcache.KextList(f2, true)

	fmt.Println("Only in kernelcache1:")
	for _, k := range diff(k1, k2) {
		fmt.Println("  -", k)
	}
}

func diff(a, b []string) []string {
	bset := make(map[string]struct{}, len(b))
	for _, x := range b {
		bset[x] = struct{}{}
	}
	var out []string
	for _, x := range a {
		if _, ok := bset[x]; !ok {
			out = append(out, x)
		}
	}
	return out
}

This approach enables automated detection of kext additions, removals, or version changes between firmware updates.

Summary

  • The ipsw project provides complete tooling to extract and analyze kernel extensions (kexts) from iOS/macOS kernelcaches via both a Go library and CLI.
  • Core extraction happens in pkg/kernelcache/kext.go, where GetKexts parses the __PRELINK_INFO segment into CFBundle structs.
  • Address resolution uses GetKextStartVMAddrs to map kexts to their kernel load addresses via the __kmod_start array.
  • Output formats include diff-friendly text lists (KextList), detailed address-inclusive lists, and JSON dumps (KextJSON).
  • CLI access is provided by ipsw kernel kexts in cmd/ipsw/cmd/kernel/kernel_kexts.go, supporting quick inspection without code.
  • Comparative analysis is possible by diffing outputs from two kernelcaches, as demonstrated in internal/diff/diff.go.

Frequently Asked Questions

How does ipsw extract kext metadata without loading the kernel?

The ipsw library reads the static __PRELINK_INFO segment stored within the Mach-O kernelcache file. This segment contains a serialized plist dictionary that maps bundle identifiers to kext metadata. The GetKexts function in pkg/kernelcache/kext.go decodes this plist into Go structs without executing any kernel code.

What is the difference between KextList and KextJSON?

KextList returns a slice of strings representing kext identifiers, optionally prefixed with their load addresses when the diffable parameter is set to false. This format is optimized for human reading or line-based diffing. KextJSON returns a complete JSON serialization of the []CFBundle slice, preserving all metadata fields from the original plist for programmatic analysis.

Can I extract the actual kext binary files from the kernelcache?

The current implementation in pkg/kernelcache/kext.go focuses on metadata extraction from __PRELINK_INFO rather than binary slicing. While the kernelcache contains the linked kext binaries within segments like __TEXT and __DATA, extracting individual Mach-O files would require additional logic to split the prelinked image using the load addresses and size information from the plist metadata.

How do I compare kexts between two different iOS versions?

Load both kernelcaches using macho.OpenFile, then call kernelcache.KextList with diffable=true on each file to get comparable string slices. Use a set difference algorithm to identify kexts present in one version but not the other, as demonstrated in internal/diff/diff.go. This approach quickly reveals added, removed, or updated drivers between firmware releases.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →