- Makefile 41.9%
- Python 27.8%
- Shell 22.1%
- C 8.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
apple-h11p is a small DRM driver on drm_simple_display_pipe: it reads the mode out of 0x50080 and the stride out of 0x50060, takes the plane, and flips by writing surface, end, stride and commit through the write aperture at 0x230200000. The register offsets are the ones verified against AppleMobileDispH11P and against the hardware. It gets as far as taking the screen off simpledrm, card1-DSI-1 with no card0, and then stops, so the node ships disabled. What blocks it is dart-disp0. Put that in the device tree and apple-dart binds, resets it and installs empty page tables, which throws away the mapping the running scanout is using: the screen turns to red flicker and the phone falls over soon after. That driver has no way to keep a mapping it did not make. And the registers do not look like what it assumes anyway. With iBoot's mapping live, TCR says translate, not bypass, while TTBR0 reads 0x0d100744, which is not a page table address anywhere in this phone's DRAM however it is decoded. Three of my own bugs are written down too, since each cost a boot: the read and write views cannot both claim 0x2f0000 or the second request fails with EBUSY, power domains must be claimed after everything that can fail or the devm unwind powers them off under a live panel and the screen goes red, and this kernel needs DRM_FBDEV_DMA_DRIVER_OPS or fbdev setup fails with -22. Next target is AppleDART in the kernelcache, for this DART's real layout. |
||
| dts | ||
| include/dt-bindings | ||
| LICENSES | ||
| patches | ||
| tools | ||
| .gitignore | ||
| LICENSE | ||
| Makefile | ||
| NOTES.md | ||
| NOTICES.md | ||
| README.md | ||
Bad Apple
Mainline Linux on the Apple A12 (T8020), on an iPhone XS Max (iPhone11,6, D331pAP).
The target is a console and a shell, the same finish line as the A10 and A11 ports. No display driver, touch, wifi, cellular or GPU.
The phone's NAND holds ext4, not iOS. The boot is tethered and reads nothing off the flash, so wiping it does not break anything.
Registers, dead ends and the debugging history are in NOTES.md.
Boot chain
SecureROM
-> usbliter8 on a Pico 2 W jig, fires the bootrom exploit, leaves pwned DFU
-> iBSS, patched to stop checking signatures, uploaded by tools/dfuboot.py
-> device tree and an empty trustcache, over Apple's recovery protocol
-> m1n1, handed to bootx where a restore kernelcache would go
-> Linux, 16K pages, t8020-d331p.dtb, initramfs
iBSS stays in the chain on purpose. It brings up DRAM, the ADT and the display,
and console=tty0 is the framebuffer it leaves behind.
Dependencies
| Host | For |
|---|---|
clang, ld.lld |
kernel and m1n1, no aarch64 GCC needed |
Rust with rust-src |
m1n1 (BUILDSTD=1). On Gentoo a USE flag on rust-bin |
dtc, cpp |
make check |
python3 |
tools/, m1n1's proxyclient |
git, curl, unzip |
make deps, make initramfs |
ipsw, pyimg4 |
unpacking the IPSW, wrapping images in img4 |
irecovery |
talking to iBSS in recovery mode |
| usbliter8ra1n-patch | the jig firmware, built and flashed once |
make deps clones m1n1, the Hoolock kernel and docs into build/, makes
build/venv with pyusb, pyserial and construct, and applies patches/.
tools/dfuboot.py needs that venv, since pyusb is what talks to DFU.
The jig firmware is built separately, see
NOTES.md.
You also need an iPhone11,6 IPSW. Everything below takes files out of it.
Build
make deps # clone, venv, apply patches/
make check # compile the device tree on its own
make m1n1 # build the m1n1 payload
make initramfs # download Hoolock's test initramfs
make kernel # build Image.gz with Hoolock's 16K config
make boot rebuilds m1n1, the device tree and the kernel as needed, so those
are only separate targets when you want one of them on its own.
make check is the only target that works on a bare checkout. make patch
re-applies patches/ and is safe to run again: an applied patch is skipped, a
real conflict stops the build.
cpufreq is disabled by the build, not by you. Frequency requests are accepted and then never completed, because the coprocessor that arbitrates DVFS is not running, see NOTES.md.
First time setup
Done once, from an unpacked IPSW. It patches iBSS so it will load a payload
Apple never signed, and wraps the device tree and an empty trustcache for
bootx.
make images IPSW=/path/to/unpacked/ipsw
Everything lands in build/iboot/. Any manifest does once iBSS is patched, so
the IM4M is Hoolock's 0x8015.im4m. There is no 0x8020 template and there
cannot be: from A11 on a manifest is tied to the device's own ECID.
Run
Put the phone in DFU on the jig. The exploit fires by itself in about a second. Move the cable to the PC, then:
make boot
That packs m1n1, the device tree, the kernel and the initramfs into one fake
restore kernelcache and hands the whole chain to the phone. make kernelcache
builds it without booting anything.
Within about half a minute the phone is 05ac:4142, hands the PC
172.16.42.2 over DHCP, and answers telnet on 172.16.42.1.
tools/tsh.py "cmd" runs one command there from a script.
The first USB attach usually fails and the gadget retries by itself, so the link can take a few tries to appear. Nothing to do but wait, see NOTES.md.
If it says the phone is not pwned, check its USB serial for PWND:[usbliter8].
Without it the exploit has not run: unplug the jig from USB for a few seconds,
a firmware reset is not enough, and put the phone back in DFU.
Implementation status
- Boot chain, tethered, nothing read off the NAND
- Patched iBSS that takes an unsigned payload
- m1n1 as a fake restore kernelcache, with a real BootArgs
- Device tree for T8020, PMGR domains generated from the ADT
- Console
- Framebuffer, handed over by iBoot
- Display driver. Written, and the pipe takes a new scanout address, but it needs dart-disp0 and Linux's DART driver destroys the mapping iBoot's scanout runs on, see NOTES.md
- Brightness. It is a DSI command to the panel, so it needs the pipe
- Notch trimmed out of the framebuffer, 2688 down to 2556
- AIC interrupts, including fast IPIs
- SMP on all six cores
- Chicken bits reapplied per core by the kernel, m1n1 does it once
- NVMe through ANS2, reads and writes, ext4 mounted
- SMC
- Battery: capacity, health, cycles, voltage, current, temperature
- Charger detection and the power button
- Poweroff, with the PMU scratchpad over SPMI so it stays off
- Reboot, through the watchdog, lands back in recovery
- Die temperature sensors, eight channels through hwmon
- USB
- Gadget: telnet, DHCP, getty on
/dev/ttyGS0 - DART, in m1n1 and through
apple-dartin Linux - Comes up on every boot, the gadget replugs itself until the host takes it
- Why the first attach fails at all. Worked around, not understood
- Gadget: telnet, DHCP, getty on
- cpufreq. Needs the PMP/CLPC coprocessor that completes transitions
- Touch, wifi, cellular, GPU, audio
- Anything upstream
License
GPL-2.0+ OR MIT, see LICENSE. patches/ keeps the license of the
code it patches: linux-* GPL-2.0, m1n1-* MIT. Nothing third party is
vendored here. What the boot depends on is listed in NOTICES.md.