The Apple HomePod Mini has never been jailbroken. The jailbreak community wrote it off after confirming its S5 chip (T8006) falls outside checkm8’s range (A5-A11 only). This writeup documents what I believe is the first publicly documented non-invasive security exploration of the HomePod Mini via its USB-C port, covering an interactive iBoot recovery shell session, full binary analysis of the firmware, and AirPlay protocol analysis on the running audioOS.

Target: AudioAccessory5,1, Apple S5 (T8006), board b520ap, audioOS 26.3 (Build 23K620), iBoot-13822.80.422.0.2.

USB reconnaissance

With the HomePod Mini plugged into a Mac via USB-C, ideviceinfo connects over usbmuxd and returns detailed device information. Notably, system_profiler SPUSBDataType returns empty. The Mini communicates exclusively through usbmuxd’s TCP-over-USB protocol, not as a raw USB device. Tools like irecovery cannot see it in this state.

Key identifiers from normal-mode enumeration:

FieldValue
ChipID32774 (0x8006, T8006/S5)
CPUArchitecturearm64e
HardwareModelB520AP
ProductTypeAudioAccessory5,1
ProductNameApple TVOS (audioOS is tvOS under the hood)
FirmwareVersioniBoot-13822.80.422.0.2

The device shows TrustedHostAttached: true, meaning lockdownd services are available. NVRAM was also readable: auto-boot = “true”, usbcfwflasherResult = “No errors”.

DFU mode attempts

No public method to enter DFU mode on the HomePod Mini has ever been documented. I tried multiple button/power combinations while monitoring USB events:

  1. Hold touch surface, plug USB-C, plug power (held 20 seconds)
  2. Plug USB-C, plug power, hold touch surface through recovery
  3. Rapid power cycling (3x) with USB connected, hold touch surface
  4. Touch surface hold before any power with USB connected
  5. Holding both + and - volume zones simultaneously during power-on

All attempts resulted in normal boot (white light), recovery mode (orange flashing light), or factory reset (3 beeps). Attempt 3 triggered Siri announcing a WiFi connection issue, confirming it was booting to userspace audioOS.

The orange flashing light during standard Finder restore is not iBoot recovery mode. It’s a userspace restore daemon running on minimal audioOS.

Entering iBoot recovery mode

Since lockdownd trusts the host Mac, ideviceenterrecovery can instruct the device to reboot into iBoot recovery mode over usbmuxd:

$ ideviceenterrecovery $(ideviceinfo -k UniqueDeviceID)
Telling device with udid 00008006-000629620103C02E to enter recovery mode.
Device is successfully switching to recovery mode.

The HomePod goes completely dark. No orange light, no white light, total silence. Consistent with every other Apple device’s bootloader behavior.

First irecovery session with a HomePod Mini

With the device in iBoot recovery mode, irecovery -q connects:

CPID: 0x8006
CPRV: 0x11
BDID: 0x22
ECID: 0x000629620103c02e
CPFM: 0x03
SCEP: 0x01
IBFL: 0x3d
SRTG: N/A
MODE: Recovery
PRODUCT: AudioAccessory5,1
MODEL: b520ap
NAME: HomePod mini

MODE: Recovery (not DFU). This is iBoot, not SecureROM. SRTG: N/A confirms it; DFU mode would show the SecureROM version tag. CPFM: 0x03 means production fused, both production and security bits set. No debug access.

Interactive iBoot shell

irecovery -s opens an interactive shell:

=======================================
::
:: Supervisor iBoot for b520, Copyright 2007-2025, Apple Inc.
::
::      Local boot, Board 0x22 (b520ap)/Rev 0x3
::
::      BUILD_TAG: iBoot-13822.80.422.0.2
::
::      BUILD_STYLE: RELEASE
::
=======================================
Entering recovery mode, starting command prompt

The shell accepts getenv queries. secure-boot returns 0x1, auto-boot returns false. Most variables return “false” (not found): boot-args, debug-enabled, firmware-version. The help command returns empty. bgcolor, version, meminfo, devicetree all silently fail. reboot works. irecovery -n returns the HomePod to normal operation.

iBoot binary analysis

Firmware acquisition

HomePod Mini IPSW files are standard ZIP archives from ipsw.me. The iBoot binary was decrypted using published firmware keys from The Apple Wiki for audioOS 16.6 (Build 20M73):

  • IV: 158dd8de55acbe7f475674aa98af825b
  • Key: a1b0fc7917caf43613086e68666139733ceefed3715a0aa4277b986af02118dd

The resulting binary is 1,397,928 bytes, arm64e, virtual base 0x180000000.

Command table

The RELEASE iBoot has an extremely minimal command set. Only infrastructure commands are registered: command, menu_loop, poweroff, idleoff, USB handlers (usb, usb req, usb-hi-current, usb-no-current, usb_serial, vbus poll, usb vbus), and boot flow (main, fsboot, upgrade, recover, recover-once, darkboot).

Dead strings present but unreferenced by code: getenv, setenv, bgcolor, bootx, memboot, reboot, reset, go. These are residual string data from shared object files, compiled out via preprocessor guards.

Completely absent: md (memory dump) and mw (memory write) don’t exist as strings at all. Not gated, removed entirely.

Security architecture

MitigationStatus
PAC (Pointer Authentication)Active, 1,683 functions
BLRAA (authenticated indirect calls)Active, 244 instances
Authenticated returns (RETAB)Active, 1,432 instances
Stack canariesActive, 167 functions
Image4 chain-of-trustActive
Debug commandsCompletely stripped

A11 vs S5: how Apple killed checkm8

To understand how Apple hardened the S5, I compared the HomePod Mini’s iBoot against an iPhone X (A11/T8015) iBoot from the same build train. The A11 is the last checkm8-vulnerable chip.

PAC enabled in iBoot

The A11 supports arm64e but Apple never enabled PAC in its iBoot. The S5 has 1,683 PAC-protected functions, 1,432 authenticated returns, and 244 authenticated indirect branches. In the USB code region alone, 455 TBZ checks on bit 62 (PAC signature region). The A11 has zero.

MetricA11 (iPhone X)S5 (HomePod Mini)
pacibsp instructions01,683
BLRAA instructions0244
RETAB instructions01,432
Bit 62 checks in USB region0455

USB handler shrunk 80x

The A11’s monolithic USB request handler spans ~16KB with 470 function calls, handling device tree setup, crypto, display init, and battery management inline. The S5 replaced this with a 196-byte thin wrapper making 4 delegated calls.

ComponentA11S5
USB req handler~16 KB, 470 BL calls196 bytes, 4 BL calls
DFU setup function~16 KB, 652 BL calls72 bytes, 2 BL calls

function-reset removed

The string function-reset exists in the A11 binary at 0x18004bd8c. This is the USB function reset handler that checkm8 directly exploits (the use-after-free occurs during USB function reset). This string and its associated code are entirely absent from the S5 binary. The functionality was either removed or refactored into a new ausb ctr (audio USB controller) abstraction layer that exists only on S5.

Deterministic stack canary

The iBoot stack canary value is 0x4752440044003631, and it is deterministic, not random. The canary is initialized at 0x1800021e0, which executes before the SVC handler, before MMU setup, and before any entropy subsystem is available. The random_fill() function at 0x18005022c checks a BSS flag that is guaranteed zero at this point, causing it to fall back to a hardcoded seed value.

The ASCII content (GRD\0D061) confirms this is a build tag, not cryptographic randomness. This value is identical across every HomePod Mini running this iBoot version.

If any stack buffer overflow exists anywhere in this iBoot build, the canary provides zero protection. This reduces the mitigation stack from three layers (canary + PAC + controlled value) to two.

Command parser bounds checking

Initial analysis suggested the command tokenizer at 0x180060288 had an out-of-bounds write. The token counter w21 appeared to increment past a CMP w21, 6 soft limit. Deeper analysis of the CCMP/CSEL pattern disproved this:

CMP   w21, 6           ; compare token count
CCMP  w8, 0, 4, le     ; if w21 <= 6: compare next_byte to null
                        ; if w21 > 6: force flags to nzcv=0100 (Z=1)
CSEL  w8, w25, wzr, eq ; if Z=1: state = 2 (dispatch/exit)

When w21 > 6, the CCMP’s le condition fails, forcing Z=1 regardless of w8. The CSEL picks w25 = 2 (command dispatch = function exit). Maximum 7 entries (indices 0-6) are written into a 10-slot array. Well within bounds.

The command parser is correctly bounded. The CMP + CCMP + CSEL pattern creates an effective hard limit.

Exhaustive iBoot attack surface audit

Image4 / DER / ASN.1 parser

The DER/ASN.1 parser at 0x180063e88 is Apple’s libDER. Every byte read has pointer range validation (start <= ptr < end), the multi-byte length accumulator has overflow detection at each iteration (LSR + CBNZ), indefinite length forms are rejected, non-minimal encodings are rejected, and the final content_start + content_length addition has an explicit overflow check. Strict mode is set for the DFU input path.

LZFSE / LZSS decompression

Three layers protect against decompression overflow: the IM4P header validation caps uncompressed size at ~190MB, the heap allocator adds 0x200 bytes of headroom, and the LZFSE decoder contains 40+ CMP x20 bounds checks.

ausb ctr USB controller

The S5 USB stack is fundamentally redesigned:

  • No use-after-free possible. The 6,144-byte USB transfer buffer is calloc()’d once at 0x180031d94, stored at BSS 0x18015b9a8, and never freed. Only 2 references exist in the entire binary.
  • Zero indirect calls. The entire USB region (0x18002f000-0x180033000) contains no BLR, BLRAA, or BRAAZ instructions. All calls are direct BL.
  • Fixed sub-buffer layout. 6,144 bytes partitioned at compile-time: EP0 setup 256B, EP0 data out 2048B, EP0 data in 1024B, bulk in 1024B, bulk out 1028B. No dynamic buffer sizing.
  • Minimal state machine. Single abort flag byte, single busy flag byte, single-threaded loop.

The bootloader attack surface is effectively closed through static analysis.

audioOS network attack surface

With iBoot exhausted, I shifted to the running audioOS.

The root filesystem contains 254 daemons and 3 apps. Four daemons bind network ports:

DaemonPortNotes
remotepairingdevicedDynamic (TCP)ExposedToUntrustedDevices: true
lockdownd62078Standard iOS lockdown protocol
mDNSResponder5353 (UDP)Bonjour/DNS-SD
racoon500 (IKE)IPSec VPN (likely disabled)

Live Bonjour reconnaissance confirmed AirPlay on port 7000 with acl=0 (no access control, any LAN device can connect).

OPACK wire format reverse engineering

Apple’s OPACK is a proprietary binary serialization format used by RemoteXPC and remotepairingdeviced. It is parsed before authentication during the pairing handshake. I reverse-engineered it from __OPACKDecodeObject at 0x19380c320 in CoreUtils.framework.

Tag RangeTypeEncoding
0x01NULLNo payload
0x02FALSENo payload
0x03TRUENo payload
0x04TerminatorEnd marker
0x05UUID16 bytes follow
0x06Date8 bytes follow
0x07-0x2FCached constantsIndex into pre-registered object table
0x30Int81 byte follows
0x31-0x36Int16/32/64, Float, Double2/4/8 bytes follow
0x40Empty stringNo payload
0x41-0x60Short stringLength = tag - 0x40 (1-32 bytes inline)
0x61-0x64String1-4 byte length prefix + data
0x65+Data, Array, Dictionary, UIDContainer types, recursive

Arrays and dictionaries are recursive, each call using ~0x70 bytes of stack. Recursion depth limit is 32 levels (CMP w8, 0x20 at 0x19380cd74). Tag 0x64 allows a 4-byte length prefix (up to ~4GB), creating potential allocation overflow in the CFStringCreate path.

remotepairingdeviced: less open than advertised

Despite the ExposedToUntrustedDevices: true flag, the daemon has multiple layers of gating:

  1. TCP listener disabled by default. The string "Not configuring launchd-managed TCP control channel due to 'deviceAllowTCPControlChannel' not being set to true" reveals the TCP listener requires an explicit flag.
  2. Requires prior pairing. TCP channel only activates after at least one host has been paired via USB or Bluetooth.
  3. Promptless pairing is USB-only and time-limited. "USB host disconnected; promptless pairing disabled".
  4. Network pairing requires consent via the Home app on a paired iPhone.
  5. Bluetooth-triggered listener. The primary discovery path is BLE proximity, not an always-on service.

For a LAN-only attacker, the TCP control channel is likely inactive on a stock HomePod Mini. The most promising network target is tvairplayd on port 7000.

AirPlay protocol analysis

Live HTTP probing of tvairplayd:

RequestResponseAuth Required
GET /info200 OK (full device info)No
POST /pair-setup400 (expects body)No, parses pre-auth data
POST /fp-setup400 (FairPlay setup)No, parses pre-auth data

The /pair-setup endpoint implements HomeKit pair-setup using SRP-3072. Sending a valid M1 message returned a valid M2 response with a 16-byte salt and 384-byte SRP server public key.

The classic SRP-zero bypass (A = 0) was blocked. HTTP 470, Apple validates the SRP public key before computation.

After a single failed M3 message, the HomePod imposed a 64,259-second lockout (~18 hours). Approximately 1.3 attempts per day per HomePod.

/pair-verify: Curve25519 key exchange

To establish a session, I implemented the complete pair-verify protocol. This is the standard AirPlay session establishment flow, documented in the pyatv and pair_ap libraries:

  1. M1 to M2: Sent Curve25519 public key, received server’s ephemeral key + encrypted data.
  2. Shared secret: Curve25519 ECDH exchange computed.
  3. Session key: HKDF-SHA-512 with salt "Pair-Verify-Encrypt-Salt" and info "Pair-Verify-Encrypt-Info".
  4. M2 decrypted: ChaCha20-Poly1305 with nonce "PV-Msg02" revealed TLV8 containing identifier 16F9D2EF-3E51-49C2-BD7B-2F145D422227 and a 64-byte Ed25519 signature.
  5. M3 sent: The server successfully decrypted M3 but rejected with Error=1 because the fabricated identifier was not in its paired peers list.

The /pair-verify endpoint accepts requests with no rate limiting, though the final authorization check (peer identity lookup) prevents unauthorized session establishment.

StepHKDF SaltHKDF InfoNonce
Session keyPair-Verify-Encrypt-SaltPair-Verify-Encrypt-Info-
Signing keyPair-Verify-ECDH-SaltPair-Verify-ECDH-Info-
M2 decrypt--\x00\x00\x00\x00PV-Msg02
M3 encrypt--\x00\x00\x00\x00PV-Msg03

AirPlay transient pairing: establishing a session

AirPlay 2 has two pairing families: HomeKit-based (SRP via /pair-setup) and legacy PIN (via /pair-pin-start). All prior testing targeted the HomeKit path.

The HomePod Mini in “Anyone on the Same Network” mode accepts transient pairing with PIN 3939. This is documented, intentional behavior for screenless AirPlay devices – the pair_ap library and pyatv both document it. Screenless devices cannot display a dynamic PIN, so Apple uses a well-known static value. Anyone running Home Assistant connects to their HomePod this way daily.

POST /pair-pin-start         → initiates PIN display (no-op on screenless HomePod)
POST /pair-setup (state=1)   → SRP M1, server returns M2 with salt + public key B
POST /pair-setup (state=3)   → SRP M3 with proof using PIN "3939"
                             → server returns M4, authenticated session established
POST /pair-verify            → derives encrypted session keys

All steps must occur on the same TCP socket. Session state is per-connection. Using separate HTTP requests fails because context is lost between connections. This is the standard mechanism for establishing an AirPlay session from a non-Apple device on the local network.

SendVoiceInput crashes MRP data channel

With the authenticated session, the HomePod exposes a Media Remote Protocol channel. Sending any MRP SendVoiceInput message (type 31), even empty, causes the data channel to terminate immediately.

Root cause: mediaremoted’s _handleVoiceDataReceivedMessage:fromClient: calls _MRVirtualVoiceInputProcessAudioData which requires an externalDevice context not initialized for AirPlay MRP tunnel connections. Null-reference bug in the device lookup path.

TLV8 parser truncation

The TLV8 parser returns HTTP 500 on truncated input:

InputResult
[type, 0x00] (length=0)HTTP 200
[type, 0x01] (length=1, no data)HTTP 500
[type, 0x01, data] (length=1, 1 byte)HTTP 200

Stable through 100+ consecutive requests. The exception is caught and does not cause memory corruption.

Findings

FindingCategory
Deterministic iBoot stack canary (0x4752440044003631)Bug (medium)
SendVoiceInput crashes MRP data channel (null-reference in mediaremoted)Bug (medium)
HTTP 500 on non-TLV8 input to /pair-verify (pre-auth)Bug (medium)
HTTP 500 on invalid X-Apple-HKP header (pre-auth)Bug (low-medium)
TLV8 parser HTTP 500 on truncated inputBug (low)
AirPlay /pair-verify no rate limit on key exchangeObservation
AirPlay transient pairing with PIN 3939By design (documented)
OPACK binary protocol fully reversedOriginal research
Complete A11 vs S5 iBoot architectural diffOriginal research
LayerSurfaceStatus
iBootUSB DFU, command parser, Image4, LZFSEAll hardened, closed
Kernel1,360 kexts, IOUserClients, WiFi driverRequires code exec first
AirPlay :7000/pair-setup, /pair-verify, /fp-setupTransient pairing via PIN 3939 (by design)
Companion :49153rapportd, OPACK, requires HomeKit trustSilent reject without trust
RemotePairing :49152OPACK protocol, TCPNeeds prior pairing or BLE
Lockdown :62078Standard lockdown protocolNeeds USB pairing

Every remote vector is either PIN-gated, rate-limited, or requires an established trust relationship. The real bugs are in the post-auth path: the MRP null dereference and the pre-auth parser crashes.

The research is reproducible by anyone with a HomePod Mini, a Mac, and brew install libimobiledevice libirecovery.

Tools

ToolVersionPurpose
libimobiledevicelatestUSB communication
libirecoverylatestRecovery mode interaction
pyimg40.8.8Image4 extraction and decryption
radare26.1.0Disassembly and binary analysis
ipsw3.1.664IPSW download and firmware extraction