Native KDK extraction
KDKExtraction reconstructs a KDK directly from a decompressed kernel
collection: it recovers references, synthesizes cross-image symbols, writes
standalone Mach-Os with KDKImageExport, and recreates bundle plists and KPI
pseudo-extensions. KernelCacheMixer uses this native implementation and
in-process gzip decompression. It does not execute an external ipsw.
The current contract is stripped collections with address-only synthetic binds.
Passing addressOnlyBinds: false fails explicitly. Original-symbol C++ alias/PAC
name inference and general shared-cache dylib export are outside this stage.
Nonempty export tries, code signatures, code-signing DRs and linker optimization
hints are rejected rather than preserved with obsolete LINKEDIT offsets.
Export an image
The internal API takes a decompressed MH_FILESET and an image parsed from it:
Use zero as the KEXT base, and the collection's preferred load address for the
kernel. Before exporting com.apple.kernel, replace its LC_UNIXTHREAD in place
with the collection's LC_UNIXTHREAD, matching extract.go's
fixUnixThreadAddress. This belongs to the caller, not the generic image writer.
Nonempty references select contiguous segment packing. Without references the writer preserves cache VM addresses while packing file offsets. Input images and collections stay immutable; repeated exports from the same writer reset its scratch state and produce the same bytes.
Reference and evidence
The port follows the MIT-licensed File.Export in go-macho PR #94. Its license
is retained in FirmwarePatcher/KernelCache/KDK/LICENSE and packaged as
Contents/Resources/Licenses/native-kdk-MIT.txt. The external reference
build uses these pinned mpass99 commits, not the released upstream ipsw:
On 2026-10-23 the writer reproduced all 264 reference exports byte for byte:
144 fileset images from kernelcache.release.vphone600 and 120 from
kernelcache.release.vresearch101, including both kernels. The comparison
feeds the identical references/symbols/base/bind mode into Swift and Go. It does
not use the reference output to construct Swift output. This proves the writer
for these inputs; a separate native recovery test now reconstructs all inputs
from the collections and checks reference sets, symbol arrays and final bytes.
Both collections match all 264 images with zero unresolved imports. Full KDK
tree tests also match 313 donor files and 263 research files: executable bytes
are identical, while plist fields and values are equal (XML formatting is not
part of the contract). No new kmutil link or VM boot was performed.
Corpus source: cached cloudOS 26.4 beta IPSW
c0ecdb4b310cf5239ab2b248dd3098eec297dc5aa3bbe6ad-b80d96a0b616.ipsw.
The decompressed input hashes are:
The source bytes, captured inputs, and Go exports are external test data. No Apple firmware, Go runtime, Python, or Homebrew dependency is added to the shipped bundle. The Go reference is only a development oracle.
Run the tests
From the repository root, build through the workspace so shared Kit targets are
resolved, using the owning FirmwarePatcherTests scheme:
KDKExportFixtureTests creates an entirely synthetic collection. It checks
contiguous layout, restored auth stubs and calls, authenticated binds/rebases,
indirect-symbol fallback, function-start remapping, repeatability, the
cache-address path, and malformed-command rejection. KDKImageExportTests
checks all four pointer layouts, signed bind addends, BRAA and OFF12 encodings,
Capstone decoding, and ADRP at high kernel addresses.
BRAA is checked against Apple clang because Keystone 0.9.2 rejects PAC
mnemonics. Independent assembly produced braa x16, x17 = 0xD71F0A11,
braa x0, x1 = 0xD71F0801, and braa x30, sp = 0xD71F0BDF.
The real-corpus test is opt-in. Set KDK_CORPUS to an external corpus root with
this layout (the fixture labels are deliberately stable):
With KDK_CORPUS set in the shell, run:
Xcode strips the TEST_RUNNER_ prefix for the test process. To narrow a failure,
set TEST_RUNNER_KDK_PARITY_ENTRY=com.apple.kernel; the test then expects two
exports, one per collection. A mismatch reports the first byte offset and saves
swift-<corpus>-<entryID> in the corpus root. The default test run skips the
external corpus rather than silently treating absent firmware as success.
The final native integration validation on 2026-10-23 passed 86 tests in 12
suites, including the mixer's shipped gzip symbol-map path, all native
recovery/export/tree comparisons and the patch-set contract suites. The
writer-stage validation also passed the ARM64 encoder suite. Its full scheme
run reported 134 issues among 696 tests: existing suites require missing ipsws/ref_extract DSC/Mach-O fixtures
and ipsws/patch_refactor_input payloads in this new worktree. The main
checkout's DSC directory is empty and the patch-refactor inputs are absent too.
That run is recorded as failed, not as a clean full-suite validation.
The complete VPhone scheme build passed, including Bundle admission passed.
Local acceptance data is retained under .build/KDKParity (ignored by Git),
with focused/full-test and bundle logs in its validation directory. Set
KDK_CORPUS="$PWD/.build/KDKParity" to replay the corpus from this worktree.
Rebuild the external reference
Place the four pinned repositories in sibling directories named ipsw,
go-macho, arm64-cgo, and go-dwarf, outside this checkout. Apply
native_kdk_reference_inputs.diff to the
external ipsw checkout. It adds KDK_DUMP_INPUTS, which serializes each
Export call's entry ID, output path, base, address-only flag, extra symbols and
references before writing the reference binary. It changes no export behavior.
From that external ipsw checkout:
Run ipsw-ref kernel extract --kdk for each decompressed collection, adding
--symbols followed by the symbol-map filename when one is available,
with KDK_DUMP_INPUTS pointing to that corpus's inputs directory and the output
root pointing to its out directory. Retain any explicit symbols input used
when building the reference corpus; the dump records the resulting writer
inputs, so writer parity does not depend on re-running symbol synthesis.
Layout invariants
- Build the segment map, resize
__auth_stubs, and move later segments before copying commands. A containing segment wins over another segment's end; section positions move with their own segment to resolve adjacent ranges. - Keep the source header's
ncmdsandsizeofcmds, adjusting only for explicit command additions/removals, as the reference does. Place chained fixups after the last segment and split info immediately before function starts. - Copy source segment bytes through the collection's address map. Write auth
stubs, relocate references, reconnect external branches, clear the packed
_kmod_infoaddress/size fields, then restore the FIPS HMAC-SHA256 seal. - Reconstruct chained fixups with 4 KiB pages and a four-byte next stride. Preserve PAC diversity/address-diversity/key; use low43/high8 for plain rebases, 16-bit ordinals and signed19 embedded addends for binds. Import order follows sections/atoms, including destructor-pair atom boundaries.
- Rebuild LINKEDIT in reference order, align blobs to eight bytes, renumber symbols and indirect symbols, and truncate the obsolete original tail. Symbol records are local/defined/undefined; strings are defined/undefined/local.
Native recovery and integration
KDKRecovery decodes collection pointer chains, LC_FUNCTION_STARTS, ADRP and
cross-section branches, unwind image offsets, static-if/copyio deltas and EH
frame references. OFF12 recovery follows register copies, conditional paths,
jump tables and outlined helpers. Leaf callees that clobber a tracked register
end the path; outlined ADRPs can hand their value back to callers. Foreign
noreturn analysis prevents a call to a terminating function from inventing
consumers in the next block. The pinned reference's query order and failed
jump-table-reader behavior are preserved for reproducibility.
KDKLinkSymbols applies transitive dependency visibility to kernel KPI names,
keeps duplicate known KEXT names local, reserves vtable/metaclass and alias
names, and supplies every KEXT's _kmod_info. Production extraction refuses
unresolved imports. Metadata paths are constrained to the newly created KDK;
partial exports are removed on failure and the final directory is published
only after success. Host artifacts use the repository's 0777 permission rule.
KernelCacheMixer revision 4 keys the mix and donor extraction on
KDKExtraction.revision, so pre-native cache entries are rebuilt. It records
extractor.version instead of the external executable hash. Staging remains
unprivileged; root only links a staged volume through the existing descriptor
based copy. checkMixable reports missing staging before a root pipeline starts.
fw kernelcache --ipsw <path> and the Swift stage(ipsw:) argument are accepted
but ignored throughout 2.12.x. VPHONE_IPSW is not read. The legacy Launchpad
UI still exposes its ipsw selector and can pass the path; Core ignores it.
Removing that UI and the compatibility argument across both products belongs
to the planned 2.13 transition with its minimumBundleComponents update.
Validation covers the two pinned 26.4 beta collections. Other recipe generations and a fresh native-extraction -> kmutil -> guest boot remain separate acceptance gates; old boot evidence is not evidence of this new extraction path.