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:
| Field | Value |
|---|---|
| ChipID | 32774 (0x8006, T8006/S5) |
| CPUArchitecture | arm64e |
| HardwareModel | B520AP |
| ProductType | AudioAccessory5,1 |
| ProductName | Apple TVOS (audioOS is tvOS under the hood) |
| FirmwareVersion | iBoot-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:
- Hold touch surface, plug USB-C, plug power (held 20 seconds)
- Plug USB-C, plug power, hold touch surface through recovery
- Rapid power cycling (3x) with USB connected, hold touch surface
- Touch surface hold before any power with USB connected
- 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
| Mitigation | Status |
|---|---|
| PAC (Pointer Authentication) | Active, 1,683 functions |
| BLRAA (authenticated indirect calls) | Active, 244 instances |
| Authenticated returns (RETAB) | Active, 1,432 instances |
| Stack canaries | Active, 167 functions |
| Image4 chain-of-trust | Active |
| Debug commands | Completely 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.
| Metric | A11 (iPhone X) | S5 (HomePod Mini) |
|---|---|---|
| pacibsp instructions | 0 | 1,683 |
| BLRAA instructions | 0 | 244 |
| RETAB instructions | 0 | 1,432 |
| Bit 62 checks in USB region | 0 | 455 |
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.
| Component | A11 | S5 |
|---|---|---|
| USB req handler | ~16 KB, 470 BL calls | 196 bytes, 4 BL calls |
| DFU setup function | ~16 KB, 652 BL calls | 72 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 at0x180031d94, stored at BSS0x18015b9a8, 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:
| Daemon | Port | Notes |
|---|---|---|
| remotepairingdeviced | Dynamic (TCP) | ExposedToUntrustedDevices: true |
| lockdownd | 62078 | Standard iOS lockdown protocol |
| mDNSResponder | 5353 (UDP) | Bonjour/DNS-SD |
| racoon | 500 (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 Range | Type | Encoding |
|---|---|---|
| 0x01 | NULL | No payload |
| 0x02 | FALSE | No payload |
| 0x03 | TRUE | No payload |
| 0x04 | Terminator | End marker |
| 0x05 | UUID | 16 bytes follow |
| 0x06 | Date | 8 bytes follow |
| 0x07-0x2F | Cached constants | Index into pre-registered object table |
| 0x30 | Int8 | 1 byte follows |
| 0x31-0x36 | Int16/32/64, Float, Double | 2/4/8 bytes follow |
| 0x40 | Empty string | No payload |
| 0x41-0x60 | Short string | Length = tag - 0x40 (1-32 bytes inline) |
| 0x61-0x64 | String | 1-4 byte length prefix + data |
| 0x65+ | Data, Array, Dictionary, UID | Container 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:
- 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. - Requires prior pairing. TCP channel only activates after at least one host has been paired via USB or Bluetooth.
- Promptless pairing is USB-only and time-limited.
"USB host disconnected; promptless pairing disabled". - Network pairing requires consent via the Home app on a paired iPhone.
- 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:
| Request | Response | Auth Required |
|---|---|---|
GET /info | 200 OK (full device info) | No |
POST /pair-setup | 400 (expects body) | No, parses pre-auth data |
POST /fp-setup | 400 (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:
- M1 to M2: Sent Curve25519 public key, received server’s ephemeral key + encrypted data.
- Shared secret: Curve25519 ECDH exchange computed.
- Session key: HKDF-SHA-512 with salt
"Pair-Verify-Encrypt-Salt"and info"Pair-Verify-Encrypt-Info". - M2 decrypted: ChaCha20-Poly1305 with nonce
"PV-Msg02"revealed TLV8 containing identifier16F9D2EF-3E51-49C2-BD7B-2F145D422227and a 64-byte Ed25519 signature. - 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.
| Step | HKDF Salt | HKDF Info | Nonce |
|---|---|---|---|
| Session key | Pair-Verify-Encrypt-Salt | Pair-Verify-Encrypt-Info | - |
| Signing key | Pair-Verify-ECDH-Salt | Pair-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:
| Input | Result |
|---|---|
[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
| Finding | Category |
|---|---|
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 input | Bug (low) |
AirPlay /pair-verify no rate limit on key exchange | Observation |
| AirPlay transient pairing with PIN 3939 | By design (documented) |
| OPACK binary protocol fully reversed | Original research |
| Complete A11 vs S5 iBoot architectural diff | Original research |
| Layer | Surface | Status |
|---|---|---|
| iBoot | USB DFU, command parser, Image4, LZFSE | All hardened, closed |
| Kernel | 1,360 kexts, IOUserClients, WiFi driver | Requires code exec first |
| AirPlay :7000 | /pair-setup, /pair-verify, /fp-setup | Transient pairing via PIN 3939 (by design) |
| Companion :49153 | rapportd, OPACK, requires HomeKit trust | Silent reject without trust |
| RemotePairing :49152 | OPACK protocol, TCP | Needs prior pairing or BLE |
| Lockdown :62078 | Standard lockdown protocol | Needs 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
| Tool | Version | Purpose |
|---|---|---|
| libimobiledevice | latest | USB communication |
| libirecovery | latest | Recovery mode interaction |
| pyimg4 | 0.8.8 | Image4 extraction and decryption |
| radare2 | 6.1.0 | Disassembly and binary analysis |
| ipsw | 3.1.664 | IPSW download and firmware extraction |