
Root temporaire (uid 0) sur un ASUS Zenfone 9 verrouillé au bootloader via CVE-2025-21479 + une fuite d'adresse physique basée sur perf. GPLv3.
Code de recherche et notes pour obtenir un root temporaire (uid 0) sur un ASUS Zenfone 9 (AI2202) dont
le bootloader ne peut pas être déverrouillé — parce qu'ASUS a abandonné son outil de déverrouillage, retiré
l'option de déverrouillage OEM des options pour les développeurs, et refuse catégoriquement fastboot oem unlock / flashing unlock sur le
firmware actuel.
Il ne s'agit pas d'un déverrouillage de bootloader et cela ne flashe rien. C'est un root à l'exécution : une chaîne de primitives côté appareil qui se termine avec le processus appelant détenant les identifiants root. Un redémarrage l'efface.
Vérifié sur : 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 verrouillé.
Autorisation / périmètre. Tout ce qui suit s'exécute contre un appareil que l'opérateur possède, via une connexion ADB autorisée. Aucun serveur constructeur n'est attaqué, aucune clé de signature ni jeton de déverrouillage n'est contourné, et rien n'est écrit sur une quelconque partition. Le téléphone utilisé pour le développement est un appareil de rechange, sauvegardé et maintenu hors ligne. Testez sur du matériel que vous possédez et dont vous pouvez supporter la perte.
Le résultat est uid 0 dans le contexte SELinux u:r:kernel:s0 (il hérite du SID de init_cred), c'est-à-dire
effectivement sans restriction. SELinux doit être en mode Permissive pour cette étape — sous Enforcing le processus est
tué à la place, car l'échange d'identifiants contourne le hook SELinux.
Définissez d'abord ZF9_SERIAL avec le numéro de série de votre appareil (tous les scripts le lisent) :
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
Le script restaure toujours le texte original du kernel et l'état SELinux (protégé par trap), et vérifie la restauration par relecture.
uid=0(root) gid=0(root) context=u:r:kernel:s0,
capset(NULL,NULL) -> 0.gpu fault threshold exceeded 3 faults in 3000 msecs), après quoi les commandes suivantes échouent avec
EPERM. Le succès de patch observé varie entre 13/13 et 2/13 dwords selon les exécutions.screenrecord en cours. Les scripts démarrent
leur propre charge pour chaque opération (les lectures font aussi la course), mais ne font jamais tourner les internes de ces outils
à nu.capset peut exécuter du code indésirable). Les scripts
écrivent l'instruction d'entrée en dernier, vérifient chaque dword, et restaurent en cas d'échec — mais si une exécution se dégrade,
redémarrez avant de réessayer.Règles de sécurité à conserver : lire avant chaque écriture, toujours restaurer ce que vous patchez, ne jamais laisser une
entrée de table de pages injectée active, ne pas toucher à la mémoire physique sécurisée/TZ (c'est fatal), et redémarrer pour
récupérer d'un état dégradé. ROADMAP.md contient la liste complète des pièges avec les preuves derrière chacun.
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
Les images de firmware, les paquets OTA et les journaux de l'appareil ne sont délibérément pas commités (voir .gitignore).
GPLv3 — voir LICENSE.
| Étape | Ce qu'elle fait | Où |
|---|
| 1 | CVE-2025-21479 (Adreno KGSL) : un paquet SDS est mal classifié comme paquet ringbuffer, permettant à l'espace utilisateur d'émettre CP_SMMU_TABLE_UPDATE et de pointer le TTBR0 du GPU vers une adresse physique choisie par l'attaquant | src/cheese.c |
| 2 | Fuite d'adresse physique : perf_event_paranoid = -1 sur ce build, donc un watchpoint matériel sur une page que nous possédons renvoie PERF_SAMPLE_PHYS_ADDR — l'adresse physique de cette page. Remplace la fuite via pagemap sur laquelle s'appuyait la version amont (les PFN sont mis à zéro ici) | src/pa_leak.{c,h} |
| 3 | Lecture/écriture physique arbitraire : construire la fausse table de pages à une adresse physique connue (issue de l'étape 2), donc pas de loterie de spray et pas de parcours sauvages | src/cheese_pa.c |
| 4 | Root : résoudre les symboles du kernel hors ligne à partir de l'image du firmware, dériver le décalage KASLR sur l'appareil, patcher __do_sys_capset avec un stub commit_creds(&init_cred), appeler capset() | src/call_capset.c, scripts/ |