
Temporärer Root (uid 0) auf einem bootloader-gesperrten ASUS Zenfone 9 über CVE-2025-21479 + ein perf-basiertes Physical-Address-Leak. GPLv3.
Forschungscode und Notizen, um temporären Root (uid 0) auf einem ASUS Zenfone 9 (AI2202) zu erlangen, dessen
Bootloader nicht entsperrt werden kann — weil ASUS sein Entsperr-Tool eingestellt, den OEM-Entsperr-Schalter
aus den Entwickleroptionen entfernt hat und fastboot oem unlock / flashing unlock auf aktueller
Firmware hart verweigert.
Dies ist keine Bootloader-Entsperrung und es wird nichts geflasht. Es ist ein Laufzeit-Root: eine Kette geräteseitiger Primitive, die damit endet, dass der aufrufende Prozess Root-Credentials hält. Ein Neustart löscht es.
Verifiziert auf: ASUS Zenfone 9 (AI2202), Android 14, Build 34.0304.2004.145, SPL 2024-07-05,
Kernel 5.10.205-android12-9-00029-g3f12df86bfdb-ab11799032, SM8475 / Adreno 730, Bootloader gesperrt.
Autorisierung / Umfang. Alles hier läuft gegen ein Gerät, das der Betreiber besitzt, über eine autorisierte ADB-Verbindung. Kein Herstellerserver wird angegriffen, kein Signaturschlüssel oder Unlock-Token wird umgangen, und nichts wird in irgendeine Partition geschrieben. Das für die Entwicklung verwendete Telefon ist ein Ersatzgerät, gesichert und offline gehalten. Teste auf Hardware, die du besitzt und deren Verlust du dir leisten kannst.
Das Ergebnis ist uid 0 im SELinux-Kontext u:r:kernel:s0 (es erbt die SID von init_cred), also
effektiv uneingeschränkt. SELinux muss für diesen Schritt Permissive sein — unter Enforcing wird der Prozess
stattdessen getötet, weil der Credential-Austausch den SELinux-Hook umgeht.
Setze zuerst ZF9_SERIAL auf deine Geräteseriennummer (alle Skripte lesen sie):
export ZF9_SERIAL=<your-device-serial>
# 0. one-time: build and push the device binaries (needs an Android NDK)
# see scripts/ for the exact clang invocations used
adb push cheese_pa call_capset /data/local/tmp/
# 1. full cycle: SELinux -> permissive, patch, run a command as root, restore everything
scripts/root-now.sh id
scripts/root-now.sh sh # root shell
Das Skript stellt stets den ursprünglichen Kernel-Text und den SELinux-Zustand wieder her (trap-geschützt) und verifiziert die Wiederherstellung durch Rücklesen.
uid=0(root) gid=0(root) context=u:r:kernel:s0,
capset(NULL,NULL) -> 0.gpu fault threshold exceeded 3 faults in 3000 msecs), wonach weitere Befehle mit
EPERM fehlschlagen. Beobachteter Patch-Erfolg variiert zwischen 13/13 und 2/13 Dwords über verschiedene Läufe.screenrecord-Last. Die Skripte starten
ihre eigene Last für jede Operation (auch Reads racen), führen aber niemals die Interna dieser Tools blank aus.capset-Aufrufer kann Müll ausführen). Die Skripte
schreiben die Einstiegsinstruktion zuletzt, verifizieren jedes Dword und stellen bei Fehlschlag wieder her — aber wenn ein Lauf degradiert,
reboote, bevor du es erneut versuchst.Sicherheitsregeln, die es wert sind, beibehalten zu werden: vor jedem Schreiben lesen, immer wiederherstellen, was gepatcht wurde, niemals
einen injizierten Seitentabelleneintrag aktiv lassen, keinen sicheren/TZ-physischen Speicher berühren (er ist fatal) und rebooten, um
einen degradierten Zustand wiederherzustellen. ROADMAP.md enthält die vollständige Liste der Fallstricke mit den Belegen für jeden.
ROADMAP.md durable handoff: verified constants, procedure, gotchas, open paths
STATUS.md current state + verification receipts
src/ pa_leak.{c,h} · cheese.c · cheese_pa.c (workhorse: PROBE/POKE/SELFTEST/ROOT modes)
call_capset.c · host_kallsyms.c (offline symbol resolver)
scripts/ root-now.sh · patch-dwords.sh · demo-root.sh · verify-backup.sh
tools/ btf_offsets.py
Firmware-Images, OTA-Pakete und Gerätelogs werden bewusst nicht eingecheckt (siehe .gitignore).
GPLv3 — siehe LICENSE.
| Stufe | Was sie tut | Wo |
|---|
| 1 | CVE-2025-21479 (Adreno KGSL): Ein SDS-Paket wird fälschlich als Ringbuffer-Paket klassifiziert, wodurch Userland CP_SMMU_TABLE_UPDATE ausgeben und die TTBR0 der GPU auf eine vom Angreifer gewählte physische Adresse zeigen lassen kann | src/cheese.c |
| 2 | Leak der physischen Adresse: perf_event_paranoid = -1 in diesem Build, sodass ein Hardware-Watchpoint auf einer Seite, die uns gehört, PERF_SAMPLE_PHYS_ADDR zurückgibt — die physische Adresse dieser Seite. Ersetzt das pagemap-Leak, auf das sich Upstream stützte (PFNs sind hier genullt) | src/pa_leak.{c,h} |
| 3 | Beliebiges physisches Lesen/Schreiben: Erstelle die gefälschte Seitentabelle an einer bekannten physischen Adresse (aus Stufe 2), sodass kein Spray-Lotteriespiel und keine wilden Walks nötig sind | src/cheese_pa.c |
| 4 | Root: Kernel-Symbole offline aus dem Firmware-Image auflösen, den KASLR-Slide auf dem Gerät ableiten, __do_sys_capset mit einem commit_creds(&init_cred)-Stub patchen, capset() aufrufen | src/call_capset.c, scripts/ |