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.
Parsing the Prelink Info Section
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
ipswproject 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, whereGetKextsparses the__PRELINK_INFOsegment intoCFBundlestructs. - Address resolution uses
GetKextStartVMAddrsto map kexts to their kernel load addresses via the__kmod_startarray. - Output formats include diff-friendly text lists (
KextList), detailed address-inclusive lists, and JSON dumps (KextJSON). - CLI access is provided by
ipsw kernel kextsincmd/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →