How ipsw Parses Kernelcaches, Extracts Syscalls, and Analyzes Kexts: A Deep Dive into the blacktop/ipsw Source Code
The ipsw tool treats iOS kernelcaches as Mach-O images, decompressing Img4 containers to extract version strings from __TEXT.__const, BSD syscall tables from __DATA_CONST.__const via pattern matching, Mach traps using a tiny disassembler, and kext metadata from __PRELINK_INFO segments.
The blacktop/ipsw repository provides a comprehensive Go-based toolkit for analyzing Apple firmware images. At its core, the pkg/kernelcache package implements a sophisticated pipeline to parse kernelcaches, extract syscalls, and analyze kexts, enabling security researchers to automate kernel introspection and signature generation.
Understanding the Kernelcache Parsing Pipeline
The parsing pipeline begins with Img4 container handling and progresses through Mach-O fileset resolution to extract the actual kernel slice.
Decompressing Img4 Containers
The entry point kernelcache.Open (via img4.Open in pkg/img4/img4.go) reads the Img4 wrapper, validates the magic, and extracts the compressed payload. The Decompress and DecompressData functions handle LZFSE or LZSS compression to obtain a raw Mach-O file represented as *macho.File.
km, err := img4.Open(kcachePath) // read Img4 wrapper
kc, err := kernelcache.Decompress(km) // raw Mach-O (may be LZFSE/LZSS)
Handling Fileset Mach-Os
Modern kernelcaches use the MH_FILESET format containing multiple Mach-O slices. The code in pkg/kernelcache/kernelcache.go detects this header type and extracts the actual kernel slice using GetFileSetFileByName("kernel") or "com.apple.kernel" when analyzing kexts.
if kc.FileHeader.Type == macho.MH_FILESET {
kernel, err := kc.GetFileSetFileByName("kernel")
// proceed with kernel analysis
}
Extracting Kernel Version Information
The GetVersion function in pkg/kernelcache/kernelcache.go extracts build metadata by walking the __TEXT.__const section. It reads C-strings and applies two compiled regex patterns: one for the Darwin Kernel Version string and one for the Apple LLVM compiler string.
kv, err := kernelcache.GetVersion(kc)
fmt.Printf("Darwin %s (%s) – XNU %s\n",
kv.KernelVersion.Darwin,
kv.KernelVersion.Date,
kv.KernelVersion.XNU)
The function populates a Version struct containing the Darwin version, XNU version, build date, and LLVM compiler information.
How ipsw Extracts BSD Syscalls
The GetSyscallTable function in pkg/kernelcache/syscall.go reconstructs the BSD syscall table by combining binary pattern matching with embedded metadata.
Pattern Matching the Sysent Table
The function scans the __DATA_CONST.__const section for the binary syscall2Pattern to locate the sysent array. It reads the array entries, fixes up slide-relative addresses using m.SlidePointer, and extracts the syscall numbers and function pointers.
Augmenting with Embedded Metadata
The function loads embedded syscall metadata from data/syscall.gz via getSyscallData. It decorates each raw syscall entry with human-readable names and argument lists from the JSON master data, setting a New flag when a syscall appears in the current kernel but not in the embedded reference data.
syscalls, err := kernelcache.GetSyscallTable(kc)
for _, s := range syscalls {
newTag := ""
if s.New {
newTag = " ← NEW"
}
fmt.Printf("%3d %-20s %s%s\n", s.Number, s.Name, strings.Join(s.Args, ", "), newTag)
}
Parsing Mach Trap Tables
The GetMachTrapTable function in pkg/kernelcache/mach_trap.go extracts Mach trap entries using similar pattern-matching techniques but requires additional disassembly to locate sentinel values.
The routine uses the same embedded syscalls.gz file for Mach-trap metadata. It locates the trap table in __DATA_CONST.__const via patternMatch, then resolves the kern_invalid address using a tiny disassembler (disass.NewMachoDisass) to identify the end of the valid trap range.
For each machTrapT structure found, the function rebases function pointers using DyldChainedPtr64KernelCacheRebase and decorates entries with argument strings from the JSON metadata.
traps, err := kernelcache.GetMachTrapTable(kc)
for _, t := range traps {
fmt.Printf("%3d %s(%s) // %s\n", t.Number, t.Name, strings.Join(t.Args, ", "), t.Function)
}
Analyzing Kernel Extensions (Kexts)
Kext analysis involves parsing both the plist metadata and the raw kmod information tables embedded in the kernelcache.
Reading __PRELINK_INFO Segments
The GetKexts function in pkg/kernelcache/kext.go decodes the binary plist stored in the __PRELINK_INFO.__info section into a slice of CFBundle structures. This provides metadata such as bundle identifiers, versions, and dependencies.
Rebasing Kmod Addresses
The GetKextInfos function reads the __kmod_info table from the __PRELINK_INFO segment, dereferences each KmodInfoT structure, and rebases addresses using DyldChainedPtr64KernelCacheRebase. The KextList function merges both data sources, optionally printing VM start addresses or generating diff-friendly id (version) lists.
bundles, err := kernelcache.GetKexts(kc) // plist → []CFBundle
addrs, err := kernelcache.GetKextStartVMAddrs(kc) // __kmod_start table
list, err := kernelcache.KextList(kc, false) // human-readable
Typical output shows the virtual memory start address followed by the bundle identifier and version:
0xFFFFFFF0077A0000: com.apple.iokit.IOUSBHostFamily (1.0.0)
0xFFFFFFF0075A4000: com.apple.driver.AppleHDA (2.2.3)
Building Kernel Signatures
The signature package in pkg/signature/signature.go consumes the kernelcache APIs to build deterministic fingerprints. When a user runs ipsw kernel analyze, the engine executes:
kv, _ := kernelcache.GetVersion(kc)
sys, _ := kernelcache.GetSyscallTable(kc)
trap, _ := kernelcache.GetMachTrapTable(kc)
kext, _ := kernelcache.KextList(kc, true)
sig := signature.NewFromKernel(kv, sys, trap, kext)
The resulting Signature struct contains hashed representations of the version string, syscall table, Mach traps, and kext list. The sig.Hash() method produces a deterministic SHA-256 value suitable for storage in vulnerability databases or automated matching systems.
Practical Code Examples
Example 1: Extracting Version and LLVM Info
package main
import (
"fmt"
"github.com/blacktop/ipsw/pkg/img4"
"github.com/blacktop/ipsw/pkg/kernelcache"
)
func main() {
km, _ := img4.Open("MacOS14.0_20A5343a_Release.ipsw")
kc, _ := kernelcache.Decompress(km)
kv, _ := kernelcache.GetVersion(kc)
fmt.Printf("Darwin %s – XNU %s – LLVM %s (clang %s)\n",
kv.KernelVersion.Darwin,
kv.KernelVersion.XNU,
kv.LLVMVersion.Version,
kv.LLVMVersion.Clang)
}
Example 2: Listing BSD Syscalls with New Detection
kc, _ := kernelcache.Decompress(km)
sys, _ := kernelcache.GetSyscallTable(kc)
for _, s := range sys {
newTag := ""
if s.New {
newTag = " ← NEW"
}
fmt.Printf("%3d %-20s %s%s\n", s.Number, s.Name, strings.Join(s.Args, ", "), newTag)
}
Example 3: Enumerating Kexts with VM Addresses
kc, _ := kernelcache.Decompress(km)
kexts, _ := kernelcache.KextList(kc, false)
for _, line := range kexts {
fmt.Println(line)
}
Example 4: Generating Kernel Signatures
kv, _ := kernelcache.GetVersion(kc)
sys, _ := kernelcache.GetSyscallTable(kc)
traps, _:= kernelcache.GetMachTrapTable(kc)
kexts, _:= kernelcache.KextList(kc, true)
sig := signature.NewSignature(kv, sys, traps, kexts)
fmt.Println(sig.Hash())
Key Source Files Reference
| File | Primary Responsibility | Direct Link |
|---|---|---|
pkg/kernelcache/kernelcache.go |
Open, decompress, version extraction (GetVersion) |
kernelcache.go |
pkg/kernelcache/syscall.go |
Parse BSD syscall table, embed data, JSON master handling (GetSyscallTable) |
syscall.go |
pkg/kernelcache/mach_trap.go |
Parse Mach-trap table, pattern-matching, disassembly (GetMachTrapTable) |
mach_trap.go |
pkg/kernelcache/kext.go |
Read __PRELINK_INFO plist, decode kmod structures, list kexts (KextList) |
kext.go |
pkg/signature/signature.go |
Consumes the above APIs to create a kernel Signature object | signature.go |
pkg/img4/img4.go |
Handles Img4 container parsing used by kernelcache |
img4.go |
cmd/ipsw/cmd/kernel/*.go |
User-facing CLI wrappers that invoke the core functions | kernel command files |
Summary
- ipsw treats kernelcaches as Mach-O images (or filesets) wrapped in Img4 containers, using
img4.Openandkernelcache.Decompressto obtain raw binaries. - Version extraction occurs via
GetVersioninpkg/kernelcache/kernelcache.go, which scans__TEXT.__constwith regex patterns to parse Darwin and XNU version strings. - Syscall analysis uses
GetSyscallTableinpkg/kernelcache/syscall.goto pattern-match thesysentarray in__DATA_CONST.__const, rebase slide-relative pointers, and decorate entries with metadata from embeddeddata/syscall.gz. - Mach-trap extraction via
GetMachTrapTableinpkg/kernelcache/mach_trap.goemploys similar pattern matching plus a tiny disassembler to locate thekern_invalidsentinel, readingmachTrapTstructures and rebasing withDyldChainedPtr64KernelCacheRebase. - Kext enumeration through
KextListandGetKextInfosinpkg/kernelcache/kext.goparses the__PRELINK_INFO.__infoplist and__kmod_infotable, merging bundle metadata with rebased VM start addresses. - Signature generation in
pkg/signature/signature.goconsumes all these APIs to create deterministic SHA-256 fingerprints for automatic kernel identification.
Frequently Asked Questions
How does ipsw handle compressed kernelcaches?
The kernelcache.Open function (via img4.Open in pkg/img4/img4.go) reads the Img4 wrapper, extracts the compressed payload, and calls Decompress or DecompressData to handle LZFSE or LZSS compression. The result is a raw *macho.File ready for analysis.
What is the difference between BSD syscalls and Mach traps in ipsw's analysis?
BSD syscalls are extracted via GetSyscallTable in pkg/kernelcache/syscall.go by pattern-matching the sysent array in __DATA_CONST.__const. Mach traps are extracted via GetMachTrapTable in pkg/kernelcache/mach_trap.go using a similar pattern match but requiring a tiny disassembler to locate the kern_invalid sentinel address that terminates the trap table.
How does ipsw rebase pointers in modern kernelcaches?
Modern kernelcaches use chained fixups. The code utilizes DyldChainedPtr64KernelCacheRebase (from the underlying go-macho library) to resolve slide-relative addresses when reading sysent entries, machTrapT structures, and KmodInfoT records in __kmod_info tables.
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 →