Component: AppleKeyStore kernel extension — _LibSer_SEPControl_Deserialize Affected: iOS / iPadOS 26.6 (23G71) and earlier Fixed in: iOS / iPadOS 26.6.1 (23G83) Type: Out-of-Bounds read in SEP control message deserialization Impact: Kernel pointer leak → KASLR defeat; triggerable from any process with direct AKS IOKit access (root / jailbreak / post-sandbox-escape)


Root Cause

_LibSer_SEPControl_Deserialize in the AppleKeyStore kernel extension publishes a (payload_ptr, declared_length) pair from an ACM (Credential Manager) message buffer to userspace via copyout(), without validating that declared_length ≤ remaining.

By supplying declared_length = 0x800, the driver copies approximately 0x7E8 bytes beyond the end of the kernel ACM message buffer — reading into adjacent kernel heap allocations. Those adjacent regions contain kernel pointers (0xfffffff0xxxxxxxx) which can be used to compute the KASLR slide.

; _LibSer_SEPControl_Deserialize (affected path, 26.6 / 23G71)
ldr  w2, [acm_msg + declared_length_offset]  ; user-controlled 0x800
; NO check: w2 <= (acm_msg_end - payload_ptr) ← MISSING
bl   copyout                                  ; copies w2 bytes to userspace

ACM Handle Capture

Direct IOServiceOpen("AppleKeyStore") is blocked by the sandbox on sideloaded apps.

The PoC includes a DYLD_INTERPOSE hook on IOConnectCallMethod that attempts to capture a real ACM session handle from an in-process SE key-signing call — working in environments where Security.framework makes the IOKit call directly (non-sandboxed / debug / older iOS configurations).

Physical device note (iOS 26, tested on iPhone 12 / 23G71): On current iOS 26 builds, Security.framework routes all Secure Enclave operations through ctkd (CryptoTokenKit daemon) via XPC. The IOConnectCallMethod call to AKS happens inside ctkd, never in the calling process. The DYLD_INTERPOSE hook therefore does not fire, and direct IOServiceOpen is sandbox-blocked. Triggering the OOB on a physical device requires breaking out of the sandbox first (e.g. via a UAF in IOGPUFamily → kernel task port → open AKS directly).

Chain:

  1. Arm the interpose capture flag
  2. SecKeyCreateRandomKey(kSecAttrTokenIDSecureEnclave)SecKeyCreateSignature() → capture in-process IOConnectCallMethod if available
  3. Capture (conn, handle[16]) — or open AKS directly if sandbox is bypassed
  4. Replay with declared_length = 0x800 across 163 AKS selectors
  5. Scan output for 0xfffffff0xxxxxxxx kernel pointers → compute KASLR slide

PoC Behaviour

poc_aks_oob.m implements the full ACM handle capture and OOB probe chain:

  • Creates a Secure Enclave P-256 key (kSecAttrAccessibleAfterFirstUnlock, no biometric)
  • Signs a 32-byte message to trigger the in-process AKS IOKit call
  • Replays with declared_length = 0x800 across all 163 AKS selectors
  • Prints any kernel pointers found at KPTR @+XXXX = 0xfffffff0YYYYYYYY
  • Attempts to compute the KASLR slide from a known AKS symbol offset

If the SE key sign is routed through secd XPC instead of in-process, the fallback probe uses a zero handle (confirms OOB path reachability; all selectors will fail ACM validation, but the VNOP path is confirmed).


iOS 26 Note: fishhook Blocked

On iOS 26, __DATA_CONST (which contains the GOT) is mapped read-only before any in-process code runs. Runtime GOT writes (as used by fishhook) trigger KERN_PROTECTION_FAILURE → SIGBUS. DYLD_INTERPOSE via __DATA,__interpose works because dyld processes the interpose table at image-load time, before the kernel enforces the __DATA_CONST protection.


Requirements

  • iOS 26.6 (23G71) or earlier
  • Secure Enclave access (kSecAttrTokenIDSecureEnclave) — available to any sideloaded app without entitlements
  • Sandbox escape required on physical iOS 26 devices: SE key signing routes through ctkd XPC (not in-process). Direct IOServiceOpen("AppleKeyStore") is sandbox-blocked. The OOB is reachable from root/jailbreak context; as a chain step it follows a sandbox escape (e.g. IOGPUFamily UAF → kernel task port).
  • In non-sandboxed / debug environments the in-process DYLD_INTERPOSE path works without any escape.

Build

# Xcode project — link Security.framework and Foundation.framework
clang -arch arm64 \
      -isysroot $(xcrun --sdk iphoneos --show-sdk-path) \
      -framework Security -framework Foundation \
      -o poc poc/poc_aks_oob.m

codesign -s "Apple Development" --entitlements ent.plist poc

Minimum entitlements (ent.plist):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
    "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
    <key>keychain-access-groups</key>
    <array><string>$(AppIdentifierPrefix)com.research.poc65343</string></array>
</dict></plist>

Timeline

Date Event
2026-08-17 iOS 26.6.1 released with fix
2026-08-17 Apple credits published in security advisory

References