Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
amazon-mustang-hack — Recherche d'exploit noyau permettant d'obtenir un root temporaire sur Amazon Fire 7 (Fire OS 7.3.3.1) via la faille use-after-free du JIT Mali kbase CVE-2022-38181, avec une chaîne de réécriture de modprobe_path. | Kitploit
Outils/GitHubGitHub/artur9010/amazon-mustang-hack
Sécurité AndroidEscalade de PrivilègesAnalyse des VulnérabilitésExploitationRétro-ingénierieSécurité MobileArticles et RechercheDéveloppement de Charges UtilesExploitation de Binaires

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager
GitHubartur9010/amazon-mustang-hack

amazon-mustang-hack

Recherche d'exploit noyau permettant d'obtenir un root temporaire sur Amazon Fire 7 (Fire OS 7.3.3.1) via la faille use-after-free du JIT Mali kbase CVE-2022-38181, avec une chaîne de réécriture de modprobe_path.

Voir le dépôtSite web
il y a 14h 3mPas encore vérifié

Projet assisté par IA. Cette recherche, le développement de l'exploit et la documentation ont été produits avec l'assistance de l'IA en utilisant les modèles GLM-5.3 et DeepSeek V4.1 Flash.

amazon-mustang-hack

Recherche d'exploit root pour l'Amazon Fire 7 9e gén (mustang, MT8163, Mali-T720) sur le firmware final — Fire OS 7.3.3.1, PS7331.4463N, kernel 4.9.117 (compilé le 2025-05-03, SPL 2024-08-01).

Objectif : LineageOS. La voie du bootloader est morte sur cette unité (bootrom patchée — preloader uniquement via court-circuit CMD), donc la seule route restante est un exploit logiciel du kernel.

Démarrage rapide```

one-shot: build, run the exploit (retries across the probabilistic reclaim),

install a setuid-root su and verify it as an unprivileged user

nix-shell -p android-tools --run './run.sh' # add -p zig too if no zig

root@kitploit:~
En cas de succès :```
/data/metrics/su id     # run a command as root
/data/metrics/su        # interactive root shell

Le reclaim réussit environ 1 démarrage sur 3 et un échec provoque un panic/redémarrage de la tablette ; run.sh attend simplement le redémarrage et réessaie. SELinux est forcé en Permissive dans le cadre de l'exploit, donc root est uniquement à l'exécution — un redémarrage restaure l'état d'origine et vous relancez run.sh.

Les binaires précompilés st3 et su (armv7 statique) sont inclus, donc aucune chaîne d'outils n'est nécessaire pour exécuter. ./run.sh --build les reconstruit à partir de poc/*.c si vous avez zig.

Tout ce qui suit n'est qu'un journal du travail effectué par le modèle, aucune intervention humaine ci-dessous.

CIBLE PRINCIPALE (depuis la session 5) : kbase CVE-2022-38181 — étape 2 PROUVÉE

GhostLock (ci-dessous) est en pause : la variante BUG_ON rtmutex de MTK + aucune divulgation d'adresse kernel depuis le shell = impasse architecturale sur cette build (sessions 2-4). L'UAF JIT de kbase a été re-diagnostiqué (le « panic inconditionnel » du destroy-worker était le déréférencement JIT_FREE, log perdu à cause de la mort d'adbd en plein panic) et l'étape 2 est désormais prouvée par oracle — voir la section SESSION 5.

EN PAUSE : GhostLock, CVE-2026-43499

UAF de pile futex-PI dans remove_waiter() de rtmutex (divulgation NebuSec 2026-07, correctif 3bfdc63936dd intégré 2026-04). Plage vulnérable 2.6.39–7.1 → notre 4.9.117 (mai 2025) est affecté.

Vérifié sur notre build exacte :

  • CONFIG_FUTEX=y, rtmutex compilé en dur, bug présent mot pour mot : rtmutex.c:1108-1111 utilise current->pi_lock/current->pi_blocked_on (devrait être waiter->task) ; site d'appel bogué rtmutex.c:1723 (chemin d'erreur de rt_mutex_start_proxy_lock)
  • Surface de déclenchement = purs appels système futex (WAIT_REQUEUE_PI/CMP_REQUEUE_PI), aucun nœud de périphérique, rien de contrôlé par SELinux — les obstacles fatals du chemin kbase n'existent pas ici
  • Consommateur : sched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) à sched/core.c:4706 — déréférence un obsolète ✓

TODO (plan de portage)

  1. Écrire le déclencheur (interblocage requeue-PI à 3 threads, cœurs 0-3) — portage de exp32/main.c
  2. Géométrie du marquage : offset de la frame rt_waiter vs zone fd_set de do_sys_select — désassembler notre vmlinux (do_sys_select stack_fds vs frame futex_wait_requeue_pi), exposer STAMP_NFDS/STAMP_WAITER_OFF comme paramètres ajustables
  3. Encodage fake-writer pour waiter arm32 de 48 octets → slots « écrire V à ADDR »
  4. 2 slots → modprobe_path, déclencher, script root
  5. Solutions de repli si le marquage select ne peut pas atteindre : marquage setsockopt(MCAST_JOIN_SOURCE_GROUP)

Statut

  • Bootrom (méthode matérielle amonet) — corrigé sur cette unité, impasse
  • mtk-su (CVE-2020-0069) — corrigé, Failed critical init step 3
  • Étude de la surface d'attaque — /dev/mali0 world-RW + SELinux gpu_device, kbase r26p0-01rel0
  • CVE-2022-38181 confirmé dans le source de la build exacte ; le déclencheur de l'étape 1 fonctionne
  • CVE-2026-43499 (GhostLock) vérifié mais bloqué : variante BUG_ON rtmutex de MTK + aucune divulgation d'adresse kernel depuis le shell (sessions 2-4)
  • CVE-2022-38181 étape 2 PROUVÉE (session 5) : le panic du destroy-worker était un mauvais diagnostic ; redirection UAF sur la région sprayée, vérifiée par oracle
  • Étape 1 : déclencheur + marquage + consommateur (crash = chaîne active)
  • Étape 2 (chemin kbase) : redirection UAF sur la région sprayée — PROUVÉE session 5
  • Étape 2b : contrôle de slot octet brut (rotation de marquage xattr) → écriture unlink
  • — détournement du hook nf LOCAL_OUT, chaîne à 2 paquets : mise à zéro de , réécriture de l'entrée fake vers . , SELinux Permissive.

Constatations clés

Appareil / firmware

  • Modèle KFMUWI, appareil mustang, Fire OS 7.3.3.1 PS7331.4463N/0031575863040
  • Kernel 4.9.117-g08fe75b-dirty, compilé le sam. 3 mai 01:25:15 UTC 2025 (Linaro GCC 6.3-2017.05)
  • Amazon a silencieusement réédité 7.3.3.1 en mai 2025 (nouvel incrémental, même chaîne de version)
  • Révision Bootrom post-2020 : court-circuit vers GND sur eMMC CMD donne le préchargeur uniquement (corrigé)
  • /dev/kb, /dev/dkb (partitions de sauvegarde kernel Amazon) root:drmrpc 0660 — verrouillés

Pourquoi CVE-2022-38181 s'applique

  • Pilote : mali_kbase r26p0-01rel0 (Midgard, Mali-T720), dans la plage affectée NVD r4p0–r31p0
  • La reconstruction d'Amazon de mai 2025 a livré le bug de 2018 mot pour mot — aucun backport
  • Code vulnérable exact, vérifié dans le source :
    • mali_kbase_mem.c:2721 kbase_jit_destroy_worker libère la région, n'efface jamais kctx->jit_alloc[id]
    • mali_kbase_softjobs.c:1270 kbase_jit_free_finish déréférence le jit_alloc[ids[j]] obsolète
    • mali_kbase_mem.c:3138 kbase_jit_backing_lost → chemin de destruction (se déclenche pendant le reclaim)

Climat d'exploitation (tout vérifié à partir du dump de config live + vmlinux OTA)

  • armv7 32 bits, non-LPAE → pas de KASLR (kernel à VA fixe 0xc0008000 / PA 0x40080000)
  • Pas de ARM_SW_DOMAIN_PAN → ret2usr viable ; CONFIG_PANIC_ON_OOPS=y (tentatives échouées = redémarrage)
  • Pas de SLAB_FREELIST_RANDOM/HARDENED, pas de CONFIG_USER_NS/USERFAULTFD/NF_TABLES
  • CONFIG_MODULES=y, pas de STATIC_USERMODEHELPER → écrasement de modprobe_path = root
  • 1 Go de RAM → reclaim direct (nécessaire pour l'éviction) trivialement atteignable ; une pression >~1 Go provoque un panic du kernel à elle seule (bug lowmem/OOM sans rapport) — garder le spray ≤ 900 Mo, utiliser ~700 Mo

Bizarreries UAPI rencontrées lors du développement du PoC (r26p0, _IOC_TYPE 0x80)

  • L'union MEM_ALLOC fait 32 octets (in a 4 × u64 dont extent)
  • les flags doivent inclure BASE_MEM_PROT_GPU_RD|WR (bits 2|3), pas le R|W hérité
  • mmap de la tracking-page requis avant toute allocation : mmap(fd, offset=3<<12, PROT_NONE)
  • Le stride de JOB_SUBMIT doit être égal à sizeof(base_jd_atom_v2) = 48 (base_jd_prio/base_jd_dep_type sont des typedefs u8)
  • JIT : MEM_JIT_INIT (nr 14, struct v2), alloc/free sont des soft jobs via JOB_SUBMIT (BASE_JD_REQ_SOFT_JIT_ALLOC=0x209, ...FREE=0x20a ; =ptr utilisateur, =compte)

Différentiel de l'étape 1 (preuve que le bug se déclenche)

Le panic survient pendant l'éviction elle-même (evictable_reclaim_scan_objects → backing_lost → destroy worker) — les références pendantes (jit_alloc[], liste d'éviction) sont parcourues avant même que nous soumettions JIT_FREE. L'étape 2 doit gagner la course : réallouer le kbase_va_region libéré avec notre propre spray MEM_ALLOC pendant que la pression est encore en cours.

Artefacts

  • poc/stage2.c — exploit de l'étape 2 (modes : step/uaf/spstep/spfree/spray/keys) — spray 700 = exécution oracle complète ; survit et se met en pause (tuer pour nettoyer)
  • poc/mustang_jit_uaf.c — PoC de l'étape 1 (modes : jit N / control N / pressure N)
  • poc/build.sh — cross-build zig (musl statique armv7)
  • kernel/vmlinux — symboles récupérés depuis la build OTA exacte (vmlinux-to-elf)

Compilation et exécution```

nix-shell -p zig --run 'zig cc -target arm-linux-musleabihf -static -O2 -o juaf poc/mustang_jit_uaf.c' adb push juaf /data/local/tmp/juaf && adb shell chmod 755 /data/local/tmp/juaf adb shell /data/local/tmp/juaf jit 700 # full trigger (~reboots device) adb shell /data/local/tmp/juaf control 700 # no-JIT control adb shell /data/local/tmp/juaf pressure 700 # raw memory-pressure control

root@kitploit:~
## Références

- Avis GHSL-2022-054 : https://securitylab.github.com/advisories/GHSL-2022-054_Arm_Mali/
- Writeup d'exploitation du Pixel 6 de Mo : https://github.blog/2023-01-23-pwning-the-all-google-phone-with-a-non-google-bug/
- Précédent du Fire HD 10 (trona), même famille de bug : ericpardee.github.io/fire-hd-ownership
- Portail OSS d'Amazon : amazon.com gp/help/customer/display.html nodeId=200203720
- Fil de déverrouillage XDA (obsolète pour cette révision matérielle) : xdaforums.com/t/fire-7-2019-mustang-unbrick-downgrade-unlock-root.3944365/


## Addendum de la session 3 (étude approfondie des empreintes d'appels système)

Profondeurs de copie-source mesurées (absolues vs sp0 à l'entrée de l'appel système ; le waiter s'étend de -0x1d8 à -0x1a8) :
- sendto sockaddr @ -0xc4 | process_vm iov @ -0x104 | recvmsg iov @ -0x11c
- sendmsg iov @ -0x12c | recvmmsg iov @ -0x154 | select fds @ -0x174
- pselect6 fds @ -0x19c | **sendmmsg iov @ -0x1bc (MEILLEUR — à 0x1c sous waiter+0x00)**
- poll entries @ -0x3e0 (entièrement en dessous ; mauvais côté)

Écartés cette session :
- chaîne io_submit trop superficielle (~-0x130) | semtimedop : CONFIG_SYSVIPC=n (stub)
- configfs monté mais ZÉRO sous-système enregistré (aucune cible mkdir)
- /sys/kernel/debug, /config : refusés par SELinux pour le shell
- /proc/sys/kernel : getdents fonctionne (29 entrées listées), seul pid_max est OUVRABLE ;
  les lectures de kptr_restrict/hotplug/hostname/domainname sont toutes refusées
- La valeur écrite est TOUJOURS waiter+0 (adresse de pile noyau, exécutable, shellcode à
  +0x1c) : rb_link_node *link = node, insert_color écrit parent-color — aucune
  variante à valeur contrôlée possible sans estampillage de champ d'arbre (écart -0x1d8..-0x1bc)
- Les champs de dispatch à double déréférencement lisent *(waiter+0)=1, *(waiter+4/+8)=0 — BLX 1/0
  (nf_hooks, net_families, inet[6]_protos, seq_file->op tous morts)
- timer_list.function@+0xc et work_struct.func@+0xc liraient *(waiter+0xc) =
  pi_tree self-ptr = waiter+0xc EXÉCUTABLE — mais aucun chemin ne met waiter+0 en
  timer/work (corruption de lien / aucune source de file indirecte)
- Tree-root-nonzero (slot de handler sysctl) = pointer-chase déterministe à travers
  .text en tant qu'rb-tree ; se termine sur un mot nul — simulable hors ligne, mais atterrir
  dans un slot inscriptible utile est invraisemblable

Pistes restantes pour la session 4 :
1. chemins ioctl profonds : copie ifreq de dev_ioctl (40 o de données utilisateur) — mesurer
   la profondeur de la chaîne SyS_ioctl→sock_ioctl→dev_ioctl vs -0x1d8
2. Toute autre copie 0x1c plus profonde que sendmmsg (rien trouvé pour l'instant)
3. Si la chasse à la surface d'estampillage échoue : reconsidérer les constructions en chaîne de parcours ou
   chasser les classes de slots inscriptibles appelés-zéro pas encore énumérées


## SESSION 4 — LES DEUX PERCÉES

### 1. Le rtmutex_common.h de MTK est tout le mystère
MTK a remplacé le rt_mutex_top_waiter sûr vis-à-vis de NULL d'amont par :```c
w = rb_entry(lock->waiters_leftmost, struct rt_mutex_waiter, tree_entry);
BUG_ON(w->lock != lock);   // compiled to: ldr sb,[lock+8]; ldr r3,[sb+0x1c]; cmp; bne→udf#0x12

PAS DE VÉRIFICATION NULL + BUG_ON. Chaque ancre tout-à-zéro meurt à *(NULL+0x1c) ; les ancres poubelles meurent à l'udf. LE PARCOURS EXIGE : lock->waiters_leftmost (lock+8) doit pointer vers un faux waiter W (inscriptible) avec W->lock (+0x1c) == lock.

Flux du parcours entièrement cartographié (rt_mutex_adjust_prio_chain @ 0xc0189a58) :

  • 9b44-9b54 : tête de retry ; pi_blocked_on==NULL → sortie propre ret 0
  • 9abc-9adc : orig_waiter==NULL → saute les vérifications pi_waiters (adjust_pi passe toujours NULL)
  • 9b10-9b28 : vérification prio (prio==task->prio + MIN → sortie 9b58)
  • 9b2c-9b38 : trylock(lock+0) — ticket ; échoue → boucle retry avec bailout compteur (9a90-9aa8, limite @ *(0xc11189c8))
  • 9ba4-9bc0 : vérifications deadlock
  • 9bcc-9bd8 : LE BUG_ON (leftmost→W→W->lock==lock ou mort)
  • 9bdc-9c04 : dequeue (arbre restant RB_CLEAR_NODE'd = VIDE → saut sûr), écriture prio/deadline
  • 9c04 bl : rt_mutex_enqueue → *link = waiter+0 à lock+4 ← L'ÉCRITURE
  • 9c3c+ : owner==NULL → chemin de sortie propre

2. La fuite d'adresse de pile noyau (tue l'exigence sans-adresse)

/proc/self/task//stat champ 28 (kstkesp) renvoie le VRAI SP noyau pour les threads bloqués en syscall depuis le contexte SHELL (vérifié : valeurs non nulles observées).

  • waiter se bloque dans read(blocking_pipe) → stat → kstkesp
  • base de pile = kstkesp & ~0x1fff (arm32 THREAD_SIZE=8192)
  • adresse abs de rt_waiter = base + delta fixe (calculable : sp0 = base+0x2000-0x48 pt_regs ; waiter = sp0-0x1d8)
  • TOUTES les valeurs d'estampille auto-référentielles deviennent calculables !

Estampille complète auto-cohérente (après fuite) :

  • L = waiter+0x24 (faux lock DANS LA FENÊTRE — les 4 mots contrôlables)
  • iov[0].base (waiter+0x1c lock) = L
  • iov[0].len (waiter+0x20 prio) = 1 (≠139)
  • W = waiter+0x1c ; estampille *(W+0x1c) = *(waiter+0x38) = L (BUG_ON passe)
  • lock+0 (waiter+0x24) = 0 ; lock+4 (waiter+0x28) = 0 (l'écriture atterrit ici) ; lock+8 (waiter+0x2c) = W ; lock+0xc (waiter+0x30) = 0 (owner NULL)

Statut de l'oracle

  • crash pendant le parcours = le parcours s'est exécuté (sonde dead-lock : crash déterministe, code propre)
  • parcours propre + pas d'écriture = trylock-fail retry-bailout (ancre kptr : mot runtime non nul)
  • Tout est maintenant déterministe après correction de la pollution.

TODO prochaine session

  1. Implémenter la fuite : waiter se bloque sur pipe, main lit stat, calcule base
  2. Estampiller la fenêtre auto-cohérente, déclencher le parcours → achèvement sans crash = écriture prouvée
  3. Weaponiser : l'écriture atterrit toujours à lock+4 (rb_link_node) — lock doit vivre dans la fenêtre (seule mémoire entièrement contrôlée), donc recherche de sélection de cible : soit trouver une astuce de slot-appelé-dans-la-fenêtre, soit construction en deux étapes.

ÉTAT FINAL SESSION 4 — LE MUR (précisément caractérisé)

Le tableau complet

Le parcours se déclenche de manière déterministe (sonde dead-lock : crash à chaque fois, code propre). L'écriture ne peut pas atterrir à cause d'une coïncidence de durcissement noyau à 3 voies :

  1. Variante BUG_ON rtmutex MTK : lock+8 (leftmost) DOIT pointer vers W avec *(W+0x1c)==lock. Les ancres tout-à-zéro/poubelle meurent. Aucun motif auto-référentiel statique n'existe (6571 candidats scannés, 0 hits). Pointeurs runtime inconnus.
  2. Aucune divulgation d'adresse noyau depuis le shell :
    • kstkesp sur arm32 = SP UTILISATEUR (task_pt_regs->ARM_sp) — pas la pile noyau. MORT.
    • dmesg/pstore/pagetypeinfo/kallsyms/stack — tous refusés.
    • kptr_restrict=1 au runtime (les mots d'ancre fops sont aussi runtime-non nuls — le run d'ancre kptr s'est terminé via trylock-fail retry-bailout, pas trylock succès)
  3. Tables fops en rodata : trylock strex avorte (crashs du balayage session-3).

La fenêtre d'estampille (waiter+0x1c..0x5b) est la seule mémoire contrôlée+à-contenu-connu, mais son ADRESSE est l'inconnue dont nous avons besoin. Les constructions auto-référentielles exigent toutes d'estampiller une adresse noyau comme constante — circulaire sans fuite.

Comparaison gitchw (pourquoi leur écriture ARM32 a fonctionné, la nôtre pas encore)

Leur noyau 5.4 a rtmutex_top_waiter UPSTREAM (NULL-safe : if (!leftmost) return NULL) — les ancres d'arbre vide survivent, leur écriture a atterri sur null_fops (inscriptible sur leur noyau). Même EUX sont bloqués au dispatch ("ioctl reboot"). L'arbre MTK 4.9.117 de Mustang a la variante BUG_ON — les tablettes Fire OS 8 (GhostLock-5.10) ont réussi car leurs noyaux 5.10 sont de style upstream.

Faits vérifiés session-4

  • La boucle retry du parcours a un bailout compteur (limite @ *(0xc11189c8)) ; trylock-fail sur mots d'ancre runtime-non nuls → sortie retry-bailout propre (runs d'ancre kptr)
  • Le chemin no-requeue (9ce4, parcours COMPLET) déréférence aussi leftmost à 9d64 — pas d'échappatoire
  • Les auto-pointeurs RB_CLEAR_NODE existent comme restes dans la fenêtre (waiter+0 et +0xc contiennent leurs propres adresses) mais aucune comparaison de vérification ne les utilise d'une manière qui évite d'estampiller des adresses connues
  • Candidats mutex réels (le mutex de chaîne a un waiter vivant = BUG_ON passerait) — mais &chain_mutex est une adresse heap, inaccessible sans fuite

OPTIONS PROCHAINE SESSION (classées)

  1. Chasse aux pointeurs noyau via logcat : les HALs/daemons Amazon sont bavards ; tout pointeur noyau loggé (même périmé) débloque la construction. Peu coûteux à tester.
  2. Comportement /proc/net %pK sur CETTE build : certains arbres 4.9 impriment des pointeurs non hachés dans /proc/net/tcp,udp,unix pour lecteurs non privilégiés. Tester en direct.
  3. Chemins de sortie de thread sur pi_blocked_on pendant (one-shot, déréférencements différents).
  4. Revisiter le bug JIT kbase mis de côté avec les connaissances 4.9 accumulées.

ADDENDUM SESSION 4 — CHASSE À LA FUITE : ÉPUISÉE (définitif)

Testés et morts depuis le domaine shell :

  • /proc/net/{tcp,unix,packet,netlink,ptype} : %pK-hachés à 00000000 (kptr_restrict=1)
  • /proc/timer_list : LISIBLE mais pointeurs %pK-zéroés (symboles visibles, pas d'addrs)
  • logcat : aucun pointeur noyau dans le bavardage Amazon/wpa
  • kstkesp (stat f28) : SP UTILISATEUR sur arm32 (task_pt_regs->ARM_sp)
  • Nœuds MTK (/proc/ged, mtk_cmdq_debug, mtktz, ptp, chip, aed, driver/*) : tous refusés par SELinux
  • /proc/{iomem,vmstat,kmsg,keys,crypto,slabs...} : refusés
  • /sys/kernel/notes : refusé
  • CONFIG_VECTORS_BASE=0xffff0000 (vecteurs hauts — NULL+0x1c faute)
  • CONFIG_KUSER_HELPERS=y (kuser à 0xffff0000, pas page 0)

CONCLUSION : GhostLock sur mustang exige une divulgation d'adresse noyau que ce noyau n'expose pas au domaine shell. Le faux lock auto-référentiel ne peut pas être construit sans elle.

POINT DE DÉCISION

(a) Grind déterministe au boot : reboot → calibrer l'adresse de pile via l'oracle de crash (~20-30 reboots), vérifier la reproductibilité. Long shot — l'allocation de pile de thread en fin de boot peu probablement stable. (b) PIVOT vers kbase CVE-2022-38181 avec les actifs accumulés : vmlinux exact-build + source complète + toolchain + discipline de trace O_SYNC + connaissance approfondie de 4.9. Le bloqueur original (panic destroy-worker pendant l'éviction JIT) est un problème de timing de spray, maintenant mieux compris. (c) S'arrêter à un honnête ~45% : déclencheur prouvé, parcours cartographié à l'instruction, écriture bloquée par BUG_ON MTK + pas-de-fuite.

Recommandé : (b) — le bug kbase est vérifié-présent dans cette source exacte, avait un déclencheur fonctionnel, et son bloqueur est mécanique, pas architectural.

SESSION 5 — ÉTAPE 2 PROUVÉE (option b exécutée)

Re-diagnostic : le "panic destroy inconditionnel" n'a jamais existé

Le mode step (alloc id=1 → DONT_NEED → pression 700MB → MEM_QUERY, PAS de free) survit : query=-1 (région libérée par le destroy worker, rbtree-propre). Le chemin worker est octet-identique au flux légal JIT_FREE-sous-pression. Le crash de la session-1 était toujours le déréférencement pendant de JIT_FREE ; sa ligne de log a été perdue car le panic tue adbd en pleine vidange. Vérifié deux fois de plus avec le mode uaf (free nu → panic, même coupure de log). Le flux GHSL-2022-054 est pleinement vivant sur cette build.

Inventaire complet des primitives (désassemblage vmlinux exact)

kbase_jit_free(kctx, reg) @ 0xc058495c avec faux reg entièrement contrôlé :

  • reg->cpu_alloc NULL → taille backed 0 → bloc trim sauté (0xc0584978)
  • décrément bin : kctx+0x147dd (octet) + kctx+0x147de+bin_id (octet)
  • mark_reclaim(reg->gpu_alloc) @ 0xc059b158 : chaîne K=*(gpu_alloc+0x38) → *(K+0x1429c)==0 saute les mm-atomics → atomic_sub nents@K+0x141c8, D=*(K+4) → atomic_sub nents@D+0x538. Avec nents=0 toutes les écritures sont des stores no-op (strex de même valeur).
  • reg->flags |= 0x100000 (écriture dans le faux, bénin)
  • shrink_cpu_mapping sort tôt quand new==old (nents=0 → return)
  • list_add(gpu_alloc->evict_node, &kctx->evict_list) : tête evict_list @ kctx+0x1427c ; écrit dans gpu_alloc+0x18/0x1c (doit être inscriptible)
  • Le chemin WARN (0xc0584bd4) est non fatal (pas de panic_on_warn) et CONTINUE
  • UNLINK @ 0xc0584b08/b0c : r3=*(reg+0x3c) prev, r2=*(reg+0x38) next → *(next+4)=prev; *(prev+0)=next — deux écritures-quoi-où arbitraires, puis relink de reg+0x38 dans jit_pool_head @ kctx+0x148e8

Chaîne fake-gpu_alloc statique (scan vmlinux hors ligne, /tmp/opencode/scan_s.py)

9 candidats ; S=0xc118b7ec (données xfrm, dormantes sur cet appareil) : *(S+8)=0 (nents), *(S+0x18)=S+0x18 (evict_node vide → pas de WARN), K=*(S+0x38)=0xc118b820 → *(K+0x1429c)=0, K/D+0x141c8/+0x538 tous en données inscriptibles. Cibles oracle préparées : init_uts_ns.name.nodename=0xc110d561 ("(none)", lisible via uname), scratch P=0xc118bd58 (zéros xfrm). Éviter S=0xc111cba4 (adjacent tracepoint). CONFIG_DEBUG_RODATA=y → toutes les cibles d'écriture doivent être en .data/.bss (bss 0xc11d9000-0xc12d9000).

Ingénierie du spray (ce qui a marché, ce qui n'a pas)

  • add_key (CONFIG_KEYS=y) : refusé par SELinux pour shell. Mort.
  • buffer de valeur setxattr : kvmalloc(96)+copy_from_user se produit AVANT la vérification SELinux → la danse d'alloc est SELinux-proof même quand l'appel échoue ; transitoire (libéré à la fin du syscall), les octets persistent à +4..95 (le ptr freelist écrase +0..3 = rblink, inutilisé par kbase_jit_free)
  • kbase_va_region lui-même : kzalloc(72) → kmalloc-96 ! MEM_ALLOC(va=0x40, commit=0x10) place SEULEMENT la région dans kmalloc-96 (alloc phy → 384) → type de reclaim déterministe. Une victime région-réelle fait que kbase_jit_free se termine à travers un état pleinement-légal (jit_node vide → auto-unlink).
  • Spray séquentiel post-pression : rate TOUJOURS — le worker libère le slot en pleine pression dans des slabs partiels (SLUB : free vers slab non-actif ≠ cpu freelist) ; sous pression nos allocs échouent → volume net nul → pas de rotation
  • Sprayers épinglés + caps : ratent encore (cap 512 épuisé avant éviction ; le worker peut tourner sur n'importe quel cpu)
  • GAGNANT : spray commit_pages=0 — pas de pages physiques → les MEM_ALLOCs réussissent à travers toute la tempête de pression → ~6000 allocations nettes → rotation de liste partielle garantie. 8 threads (2/cpu, cpus 0-3 codés en dur — /proc/cpuinfo est filtré shell à 1 cœur, utiliser Cpus_allowed_list) + enfant de pression épinglé sur cpu0 + lot de rétention de 16 allocs après join.
  • Piège query(jit_va) : post-reclaim, les régions de spray réutilisent la VA libérée dans la zone custom → query=0 est ambigu (live-original vs spray-couvrant-VA)

HIT ORACLE — redirection vérifiée par machine

Run spray 700 2026-09-11 : 5895 régions sprayées pendant la pression, JIT_FREE sur id=1 pendant s'est terminé sur une région récupérée, puis JIT_ALLOC(0x40, bin 0) a parcouru jit_pool_head et renvoyé la VA de la région sprayée #4251 (0x142701000) — la région exacte que le pointeur pendant a consommée. Kill du processus après : teardown kctx propre, pas de crash. Étape 2 complète : redirection UAF déterministe avec type d'objet + contenu contrôlés.

Plan étape 3 (unlink octets bruts)

Le reclaim de type région donne la survie à la danse légale mais jit_node est INIT'd self → pas de primitive unlink. Besoin d'octets bruts à +0x38/+0x3c :

  1. estampillage xattr : alterner rafales d'alloc-région (volume net → rotation slab) avec tempêtes xattr (estampiller chaque slot de tête, octets persistent post-free) → fenêtre calme → déréférencement
  2. ou cmsgs sendmsg épinglés (optmem_max=10240 → ~106 × 96B retenus)
  3. puis : W1 *(N+4)=P avec P=page shellcode userland (pas de PAN !) — candidats : les fops const sont .rodata (DEBUG_RODATA) → cibler un fn ptr non-const en .data, ou la tête de liste formats binfmt, ou proc_handler sysctl (vérifier l'inscriptibilité de la table). fallback : modprobe_path via écritures chaînées par octets (les valeurs doivent être des addrs inscriptibles — utiliser des cibles en forme de pointeur)
  4. pas de KASLR + vmlinux exact : prepare_kernel_cred 0xc0149e3c, commit_creds 0xc014993c

SESSION 5B — ÉTAPE 3 : arme construite, course au reclaim pas encore gagnée

Fait

  • Arme étape-3 complète & préparée (poc/stage3.c) :
    • Cible : kern_table[pid_max].proc_handler @ 0xc1113f40 (inscriptible .data, vérifié via scan de pointeur-chaîne + handler == proc_dointvec_minmax)
    • N = entrée shellcode 0x11111112 (mmap 0x11111000 ; W1 écrase entry+4, sauté par b +8 ; W2 écrit N à P = champ handler)
    • shellcode ring0 arm32 encodé à la main : prepare_kernel_cred(0) + commit_creds + ret 0 ; déclencheur = read /proc/sys/kernel/pid_max (lisible depuis shell) ; s'exécute dans son propre contexte de tâche → les creds s'appliquent à nous
    • mode oracle bénin écrit uts nodename (0xc110d561, non aligné ok)
  • Sélection de primitive de spray :
    • pins sendmsg NETLINK_USERSOCK (copie msg_control kmalloc-96, retenue pendant le blocage, jamais parsée) : refusé par SELinux (création socket EACCES)
    • sendmsg unix/UDP : le parsing cmsg empoisonne les octets de payload ✗
    • événements inotify : inotify_handle_event kmalloc name_len+0x1d, octets du nom (entièrement contrôlés, contrainte sans NUL/slash) à event+0x1c ; name_len=60 → kmalloc-96 ; mis en file → retenus ; 4 instances → 4 événements par rename ; SELinux-OK depuis shell. Faux redessiné sans NUL : cpu_alloc pointe vers S (nents@S+8 = 0 → même sémantique que NULL)
  • Chaîne mécanique validée de bout en bout (drain4, run sans éviction) : 20K événements drainés + 13.5K renames multi-cpu en fin + JIT_FREE + oracle + pause, tout propre. Le log O_SYNC (/data/local/tmp/s3.log) survit aux panics — forensique exacte du point de crash.

Tentatives de reclaim sur le slot libéré (toutes ratées jusqu'ici)

varianterésultat

Hypothèse de travail : course aux déchets-de-tempête — entre le destroy worker qui libère le slot (en pleine tempête) et le kill-enfant/calme, une activité de reclaim résiduelle prend le seul-slot-libre du slab victime avec des octets non-payload. Le spray de régions (étape 2) gagne car il alloue continuellement PENDANT la tempête ; les renames ne peuvent pas.

Pièges rencontrés

  • bug du toggle de spray : la source du rename doit être le nom du payload (c'était le nom temp → ENOENT après 2 vagues → seulement 512 événements au total)
  • le hostname de l'appareil est "localhost"/varie — l'oracle compare avant/après
  • st3 en pause + pkill → appareil BLOQUÉ (teardown avec 33K événements ?!) — ne tuer les processus en pause que via reboot ; second hard-wedge de la session
  • /proc/cpuinfo montre 1 cpu au shell ; utiliser Cpus_allowed_list

Prochains mouvements (classés)

  1. drain4 @ 500MB (seuil d'éviction confirmé là), poll 1ms, kill instantané, traîne 4-cpu × 3200 — rétrécir la fenêtre de tempête
  2. churn d'estampillage xattr (l'alloc-copie setxattr se produit AVANT la vérification SELinux — SELinux-proof) concurrent avec tempête + rotation de régions, finir-par-estampille
  3. accepter le reclaim-région (prouvé) + trouver une primitive de second étage sur l'état région-victime (analyse double jit_free négative jusqu'ici)

SESSION 5C — le bloqueur, précisément caractérisé

Résultats empiriques cette session

  • drain4@500 (poll 1ms, kill instantané, +4.5K renames) : crash encore au déréférencement
  • chevauchement traîne avec kill (drain5) : crash PLUS TÔT (les renames pendant la tempête de récupération-kill frappent une faute système) — chevauchement abandonné
  • EXPÉRIENCE D'ISOLATION (mode iso) : timing identique, traîne avec RÉGIONS commit-0 + oracle de réutilisation de pool étape-2 → HIT ORACLE → timing/atteignabilité sont BONS ; les événements sont le problème
  • événements à masques cyclés (rotation MOVED_TO/CREATE/DELETE pour défaire inotify_merge) : crash encore au déréférencement
  • CONFIG_MEMCG=n → événements et régions partagent UN SEUL kmalloc-96 (théorie memcg morte) ; inotify_merge compare aussi les noms (théorie merge morte — nos noms basculants ne se sont jamais fusionnés ; les événements étaient en file et retenus depuis le début)
  • /proc/slabinfo absent ; /proc/self/pagemap lisible mais PFN-zéroé (masquage post-4.0, pas de CAP_SYS_ADMIN)

LE VRAI BLOQUEUR (deux parties, toutes deux prouvées)

  1. La fin de soft-job s'exécute dans le contexte WORKER du job-scheduler kbase (jd_run_atom ← dispatch js, mali_kbase_jd.c:81-112/677), pas inline dans l'ioctl submit → current->mm est celui d'un thread noyau → le design élégant "pointer les pointeurs du faux vers notre propre mmap userspace" (pas de PAN !) FAUTE de manière non déterministe. 5/5 crashs avec un faux par ailleurs parfait.
  2. Donc gpu_alloc doit pointer vers de la mémoire NOYAU avec une chaîne runtime survivable : K=*(S+0x38) lisible, *(K+0x1429c)==0 au runtime (saute les mm-atomics), K+0x141c8 inscriptible, D=*(K+4) → D+0x538 inscriptible, nents *(S+8) de préférence 0. Les 9 candidats-S hors ligne ont été validés contre les octets du FICHIER — la dérive runtime (init xfrm/tracepoint) les rend non vérifiés. Une mauvaise chaîne = crash = reboot (~3 min de cycle).

Plans session-6 (tous deux entièrement spécifiés)

A. Faux par spray-physmap (ret2dir, classique arm32 sans-PAN) : sprayer ~450MB de pages utilisateur contenant chacune le motif faux cuit pour UNE adresse devinée G (G&0xfff = 0x141 pour les octets de nom sans NUL ; S=G ; K=G-0x141b4 pour que K+4 atterrisse dans la page ; K+0x141c8/+0x1429c → G+0x10/+0xd4 dans la page ; D=G+0x300 ; les stores sub-0 errants frappent de la RAM mappée aléatoire - inoffensif avec nents=0). Le spray sert aussi de pression d'éviction (anon sale = non évictable → seulement ~100-200MB supplémentaires nécessaires). Probabilité ≈ 45% (hit de page) × ~50% (le mot K+0x1429c étranger est zéro... si K+0x1429c est gardé dans la page selon la disposition ci-dessus, probabilité = hit de page seulement). Raté = crash = reboot, retry. B. Brute-forcer les 9 candidats-S statiques (xfrm 0xc118b7ec d'abord, adjacent-tracepoint 0xc111cba4 ensuite...) : 1 reboot par candidat, payload oracle-bénin d'abord, arme sur hit. C. G exact basé sur pagemap (mort : PFNs masqués) — ne pas revisiter.

SESSION 5D — faux physmap construit ; balayage-G 0/3 ; confusions éliminées

Établi cette session (tout vérifié binaire/appareil)

  • Identité de cache CONFIRMÉE identique : région = kmem_cache_alloc_trace( kmalloc_caches[7], GFP|0x8000, 0x48) [désasm kbase_alloc_free_region] ; événement = __kmalloc(89, GFP) → kmalloc-96. Événements et régions PEUVENT partager le cache de la victime. (MEMCG off ; un seul jeu de cache.)
  • file inotify : ≥5000 événements retenus, pas de débordement, pas d'effondrement de merge (runs qmeas 1000 & 5000) — le spray d'événements persiste
  • iso2 (spray physmap + traîne région + oracle) : HIT — le spray physmap ne casse PAS le reclaim de région ; machinerie saine
  • événements vs régions reclaim : régions 3/3 (iso, iso2, étape-2), événements 0/10 MAIS les 3 runs pmap sont expliqués par des ratés-G à 0.3-0.45 de probabilité chacun (P(3 ratés|événements-marchent) ≈ 0.2-0.3 — pas concluant)
  • mode mix confondu : spray 480MB étouffe les renames post-kill (+0) ; 350MB OK (+4452) ; les threads de rename-échoué en spin perturbent aussi le reclaim de région (mix a crashé, iso2 propre)
  • query_commit renvoie -1 aussi sur EINVAL — instrumenté ; EINVAL observé = raté de lookup rbtree = réellement libéré ✓ (pas de faux positif)

Design pmap actuel (dans stage3.c, mode pmap/mix/iso2)

  • G cuit dans chaque page sprayée (offset 0x2a4) : nents@+0x2ac=0, evict self@+0x2bc/0x2c0, K=G+0x100@+0x2dc, D=G+0x200@+0x3a8 ; K+0x141c8/-0x1429c atterrissent ~20 pages plus haut (store sub-0 inoffensif / la lecture doit être 0-ou-valide). PM_SPRAY_MB 350, balayage-G essayé : c2a412a4, c2f4b2a4, c2a7d2a4 — tous crash au déréférencement
  • Sanité de la plage physmap : RAM 1GB → physmap ~0xc0000000-0xc3fffffff ; les carveouts MTK (GPU/M4U/secure) peuvent occuper des morceaux — mines-G

TODO session-6 (classés)

  1. Extraire la source complète du noyau (tarball 2.2GB à ~/Desktop/amazon-mustang/ — platform.tar) : obtenir arch/arm + mm/ + drivers/of + mappings réservés MTK → calculer la carte des carveouts → cibler G dans des sous-plages physmap RAM-vérifiées ; vérifier aussi la géométrie du cache kmalloc (ARCH_KMALLOC_MINALIGN !) et le bit GFP 0x8000
  2. Balayage-G avec des suppositions informées par le placement (plusieurs reboots, varier la taille du spray pour décorréler)
  3. Si le balayage-G s'épuise : reconsidérer un payload multi-G ou des candidats-S à partir de statiques runtime-plausibles (les champs pointeur adjacents-uts ont échoué : NULL-K)

SESSION 6 — découverte zram, extraction de source, question des événements toujours ouverte### Code source complet du noyau désormais extrait

/tmp/opencode/ksrc2/kernel/mediatek/mt8163/4.9/ (arch/arm incl. mustang.dtsi, mm/, fs/eventpoll.c, fs/notify) depuis ksrc/platform.tar. Constats :

  • bit GFP 0x8000 = ___GFP_ZERO (simple kzalloc ; pas de découpage de cache)
  • kmalloc-96 est un vrai cache de 96 octets (pas de HWCACHE_ALIGN sur les caches kmalloc)
  • mustang.dtsi : nœud mémoire 0x40000000/512MB — étendu par le preloader (l'appareil affiche MemTotal 977MB) ; CONFIG_VMSPLIT_3G, HIGHMEM=y
  • zram0 ACTIF (SwapCached > 0) → « dirty anon = unevictable » était FAUX : le spray physmap est swappé sous pression → l'alias G devient obsolète → ajout de physmap_retouch() après le kill (recharge toutes les pages du spray avant le trailing/deref)

Exécutions de cette session (toutes en O_SYNC, ~6 redémarrages)

Verdict sur les événements (bayésien, honnête)

Les régions reclaim : 3/3. Les événements : 0/~12 tentatives incluant 28K allocations exclusives avec avance et fake correct par construction. Si les événements reclaim avec p_hit(G)≈0.35, cinq échecs pmap ≈ 11.6% — possible mais désormais improbable (~10-15%). Soit les événements ne peuvent structurellement pas prendre ce slot (raison inconnue — même cache, même contexte, même timing), soit nos estimations de G ratent systématiquement (biais de frontière highmem, placement de l'allocateur).

Arbre de décision de la session 7

  1. Trancher G d'abord (peu coûteux, sans exploit) : instrumenter temporairement le flux ISO2 — trailing de région + oracle — mais faire de l'événement PAYLOAD un fake physmap et vérifier si UN G quelconque dans une plage balayée produit un changement de nodename sans que les régions entrent en course (pur pmap, balayage G sur ~0xc1500000-0xc2a00000 centre lowmem, 1 G par redémarrage, 4-5 redémarrages)
  2. Si le balayage G s'épuise → événements déclarés morts → chercher d'autres allocateurs kmalloc-96 à octets bruts accessibles depuis le shell (audit : seq_file, tty ldisc, fdtable, sk_filter (bloqué : champ code à +0x38), netlink nlmsg (skb ✗), keys (refusé)) — ou revisiter les primitives de région en deux étapes (analyse négative jusqu'ici)
  3. Envisager un déverrouillage UART/ramoops via root plus tard ; ne pas s'y acharner

SESSION 7 — bombes dans la cmdline ; le mystère des événements désormais précisément borné

CONFIG_CMDLINE de mustang_defconfig (vérité terrain pour le placement) :

vmalloc=496M slub_max_order=0 slub_debug=O loglevel=8 initcall_debug

  1. vmalloc=496M → physmap = 0xc0008000..~0xc2080000 UNIQUEMENT (les 520MB bas de la RAM) — TOUTES les estimations G précédentes (0xc2a4xxxx+) étaient dans L'ESPACE VMALLOC. Toutes les conclusions de « G-miss » des sessions 5D/6 sont invalidées ; l'interprétation du crash tient mais le balayage visait la mauvaise map.
  2. slub_max_order=0 : toutes les pages slab en order-0
  3. KMALLOC_MIN_SIZE = ARCH_KMALLOC_MINALIGN = 64 (L1_CACHE_SHIFT 6) → les cas spéciaux de kmalloc_index() pour 96/192 sont DÉSACTIVÉS → région kzalloc(0x48=72) → caches[7] ; événement __kmalloc(89) → caches[7] (disasm + include/linux/slab.h:287 vérifiés) — LES DEUX dans le cache fusionné de 128 octets « kmalloc-128/96 ». Identité du cache : RE-CONFIRMÉE égale.
  4. zram actif → remplacement du spray physmap anon par kbm_spray : 160 × 2MB régions kbase MEM_ALLOC (GFP_KERNEL → ZONE_NORMAL → lowmem uniquement, épinglé → immunisé contre zram), motif écrit via mmap CPU — plage correcte, ~65% de couverture lowmem

Exécutions (O_SYNC)

  • kbm + G=0xc16412a4 : crash au deref (+4831 renommages)
  • kbm + G=0xc1c4b2a4 @200MB : PAS d'éviction (query=16 — la calibration de pression varie avec le spray épinglé ; libre légal, a survécu)
  • kbm + G=0xc1c4b2a4 @400MB : éviction ✓, +4053 renommages, crash au deref
  • Bilan G en plage valide : 0/2. Si les événements fonctionnent avec ~65% de couverture : P(2 échecs) ≈ 12%. Question toujours ouverte mais plus étroite que jamais.

La question, forme finale

Les régions prennent le slot victime 3/3 ; les événements 0/13. Même cache (prouvé au niveau source + disasm), même contexte de processus, mêmes cpus épinglés, même timing post-kill, des milliers d'allocations avec avance exclusive. Mécanisme inconnu. Suspects restants : corrélation taux/fréquence d'allocation avec la rotation de la liste partielle (ioctls de région ~1ms d'écart vs renommages d'événements ~100µs d'écart — directions opposées ?), ou détails d'ordonnancement de la freelist SLUB sous slub_max_order=0 qui favorisent... flou.

TODO de la session 8

  1. Expérience de qualité instrumentation : DEUX variantes de payload d'événement avec des valeurs N/P DIFFÉRENTES alternées (deux jeux de noms) — si le nodename change un jour, le DERNIER gagnant est identifié ; balayer G dans [0xc1200000..0xc2000000] avec spray kbm, budget de 3-4 redémarrages
  2. Si toujours 0/N : abandonner les événements. Alternatives classées : a. tableaux pipe_buf via F_SETPIPE_SZ(4096 → 1 buf ? non — 16 bufs = kcalloc(16, 28)=448→512 ✗) — mort b. auditer fs/notify + fs pour d'autres objets kmalloc-128 porteurs de nom/données (événements fanotify ? mq off ; fanotify nécessite des groupes...) c. tampons seq_file (kmalloc(PAGE_SIZE) ✗) d. filtres sock (collision du champ code à +0x38 ✗) e. accepter le reclaim de type région + enchaîner un SECOND bug/technique
  3. Réexaminer POURQUOI les régions gagnent — peut-être instrumenter via plusieurs jit ids : N slots pendants, course région-vs-événement par slot, l'oracle détecte quel spray a pris quel slot → empreinte statistique du mécanisme

SESSION 8 — ÉCRITURE ARBITRAIRE OBTENUE SUR L'APPAREIL ; mystère du dispatch restant

LE JALON```

[+] uname.nodename="X?lhost" (was "localhost") <<< ORACLE HIT

root@kitploit:~
**La chaîne complète en octets bruts fonctionne sur l'appareil en direct** : l'événement récupère
l'emplacement de région libéré → kbase_jit_free déréférence notre faux (S=page physmap issue
du spray kbm, G=0xc154b2a4) → l'unlink exécute nos deux écritures.
Vérifié de façon répétée avec la charge utile bénigne (écriture de nodename).

### Chaîne de découvertes de la session
1. les pages kbm étaient GFP_HIGHUSER → HIGHMEM, invisibles pour physmap
   (mali_kbase_mem_pool.c:164 !) — corrigé par le débordement de zonelist : spray de 260 × 2MB
   > highmem-free → le surplus atterrit dans ZONE_NORMAL (physmap)
2. Les premières tentatives d'arme ont planté : la cible W1 N+4 était une page USER —
   l'unlink s'exécute dans le contexte kworker (pas de mm) → faute. Corrigé en intégrant le
   shellcode ring-0 DANS le motif physmap à page+0x600 (direct map RWX sur
   arm32 non-LPAE) — code résident en noyau, pas besoin de ret2usr
3. Bug d'offset ctl_table : proc_handler est à entry+0x14, pas +0x18 (le
   scan original l'avait correctement ; ma définition était fausse) — écrivait extra1
4. Régression de surpression trouvée et annulée : budget kid 20×100MB +
   trailing 10s cassait le reclaim ; la config qui fonctionne est 2 kids/200MB +
   5s/+3200 trailing (touché bénin 1/1 après annulation)
5. **diag2 : W2 → &pid_max global (0xc1114d7c) → la lecture renvoie notre valeur
   (-1055861411 = 0xc110d55d en int32) — écriture + relecture PROUVÉES**

### Le mystère restant (à une expérience de la résolution)
diag1 avec l'adresse de handler CORRECTE (0xc1113f3c) : W1 se déclenche (nodename
change), W2 a forcément été exécuté (instruction suivante) — pourtant les lectures de pid_max
renvoient toujours des valeurs propres → le champ handler que nous écrivons n'est pas celui
par lequel l'inode dispatche. diag3 (en file d'attente ; nécessite un boot avec hit) : W2 →
champ entry->data (0xc1113f2c) pointant vers nodename — si la lecture montre alors
nodename-en-tant-qu'int, notre entrée EST vivante et seul l'offset du handler est
d'une manière ou d'une autre erroné ; si inchangé, l'inode utilise une copie fantôme
de la table et nous traquons la vraie.

### Réalité du taux de réussite
Pile ou face par boot (~25-40%), groupé ; plusieurs boots safe-miss/crash
d'affilée sont normaux. Environ 1 boot sur 3-4 est un hit. Garder la config qui fonctionne
EXACTEMENT (2 kids, 5s trailing, kbm 260-régions, G=0xc154b2a4).

### TODO Session-9
1. Terminer diag3 sur un boot avec hit (relancer jusqu'à « W1 FIRED »)
2. Si l'entrée est vivante : revérifier l'offset du handler empiriquement (écrire
   l'adresse de proc_dostring comme handler via... N doit être égal à une valeur
   utile — utiliser l'unlink pour écrire entry->data à la place et pivoter : par ex.,
   data=selinux_enforcing-adjacent...)
3. Si table fantôme : localiser la vraie — kallsyms n'a pas de symboles de données ;
   candidats : scanner le comportement de /proc/sys, ou trouver une seconde région ctl_table
   via le motif de liste d'en-têtes dans .data (entrées à pas de 0x20
   avec handler=proc_dointvec_minmax et maxlen=4 — énumérer TOUTES et
   faire un diag-write sur chacune)
4. Classe de cible alternative qui évite entièrement le dispatch : pointeurs de fonction
   .data appelés depuis des chemins accessibles au shell (audit nécessaire)
5. La primitive d'écriture elle-même est TERMINÉE — toute cible fiable en adresse noyau
   suffit désormais pour root

## SESSION 9 — nf-WEAPON : reclaim+unlink+hit-G PROUVÉS DANS L'ARME ; seul le parcours de hook reste

### La nouvelle conception du déclencheur (remplace entièrement le chemin sysctl-handler)
L'idée de l'utilisateur traduite en mémoire noyau : pas de fichier SUID (le système est
dm-verity RO ; la primitive écrit dans la RAM noyau). À la place : **faux hook netfilter**.
Ce noyau a le backport Android-common de la NOUVELLE API
nf_hook_entries, mais implémentée comme une LISTE CHAÎNÉE (vérifié par
désassemblage de nf_hook_slow + helper 0xc09897d4) :
- `__ip_local_out(net, sk, skb)` charge la CELLULE entries depuis
  **[net+0x58c]**, la stocke dans state+0x1c, appelle nf_hook_slow
- parcours : `entry = *cell`; while(entry){ if (state->[4] <= entry->[0x20])
  call entry->[0xc](entry->[0x14], skb, state); entry = entry->[0]; }
  — c'est-à-dire **fn@entry+0x0c, priv@entry+0x14, priority@entry+0x20,
  next@entry+0x00** ; state+4 = seuil INT_MIN (passe toujours)
- init_net = **0xc1104548** (CONFIG_NET_NS=n → sock_net() inline la
  constante ; 6678 références movw/movt, champion de l'histogramme ; recoupé par
  le littéral de nf_hook_slow lui-même). CIBLE : **[init_net+0x58c] = 0xc1104ad4**
- Le hook LOCAL_OUT s'exécute dans le contexte du processus ÉMETTEUR → le commit_creds(prepare_kernel_cred(0)) de notre hookfn
  donne root au processus qui a envoyé le paquet. Déclencheur = sendto(127.0.0.1:9) UDP.

### Disposition du mode nf (poc/stage3.c, mode `nf 200`)
- pages de motif kbm (relatives à la page, source unique de vérité — l'ancienne
  arme avait TROIS bugs désormais corrigés : proc_handler@+0x14 et non +0x18 ; la cible W1
  doit être de la mémoire NOYAU (contexte kworker, pas de mm) ; code intégré à
  page+0x600 vs incohérence entry G+0x600=page+0x8a4) :
  - +0x2ac/+0x2bc/+0x2c0/+0x2dc/+0x3a8 : fausse chaîne phy-alloc (inchangée)
  - +0x600 : hookfn nf_code (stockage de marqueur + prepare_kernel_cred +
    commit_creds + return NF_ACCEPT(1))
  - +0x700 : fausse entrée {next=0, fn=PM_PAGE+0x600, priv=0, prio=0x100}
  - +0x740 : cellule → PM_PAGE+0x700
- charge utile : N = PM_PAGE+0x740 (0xc154b740), P = 0xc1104ad4
  → W1 : *(cell+4)=P (atterrit dans notre page), W2 : *(init_net+0x58c)=cell

### Instrumentation AUTO-DIAGNOSTIQUANTE (kbm_scan_for)
Les mappings CPU kbm sont conservés ; après la libération nous scannons chaque page
sprayée à la recherche d'un mot connu :
- scan(HOOKS_PTR_ADDR) à page+0x744 → prouve reclaim + unlink + révèle
  quelle page physique soutient la supposition G
- scan(0x600d600d) à page+0x7f0 → prouve que la hookfn S'EST EXÉCUTÉE
  (nf_code écrit ce marqueur comme 2e action)

### LE RUN QUI COMPTE (2026-09-11, fin de session 9)```
[+] W1 CONFIRMED: region 135 page+0x185000 <- 0xc1104ad4 (reclaim + G hit!)
[+] nf trigger done, uid=2000 euid=2000

La chaîne d'armes complète s'est déclenchée sur un démarrage réel : l'événement a récupéré le slot, le faux a été exécuté, l'unlink a été exécuté, la page G-guess (0xc154b000) était véritablement la nôtre (région 135 page 389). W2 = *(0xc1104ad4)=cell est l'instruction adjacente — elle a dû s'exécuter. Pourtant UDP sendto ne nous a pas donné root → l'échec est À L'INTÉRIEUR du chemin du hook : sémantique de walk, plomberie [state+0x1c], comparaison de priorité, ou les champs d'entrée. (L'expérience de marqueur pour discriminer hookfn-exécuté-vs-non a été ajoutée ; seuls des crash-boots avant la fin de session — pas encore de données propres.)

Apprentissages sur le taux de réussite / l'état de boot (durement acquis)

  • CONFIGURATION FONCTIONNELLE (ne pas toucher) : 2 kids × 100MB pression par paliers, 100×50ms/+3200 renames en fin de séquence, spray kbm 260×2MB, G=0xc154b2a4, pré-drain ~5000 renames
  • Sur-pression (20 kids / 10s en fin de séquence) CASSE la récupération — annulé
  • Le temps de stabilisation du boot compte : les exécutions lancées immédiatement après boot_completed entrent en course avec les allocations de démarrage du système → séries froides ; laisser stabiliser 60-90s après le boot avant de lancer
  • « W1 ne s'est pas déclenché » (vérification du nodename) n'a AUCUN SENS pour le payload nf — W1 écrit dans notre page ; utiliser kbm_scan_for à la place
  • Les boots avec crash-au-free ≈ slot poubelle ou conversions G-miss ; le contrôle bénin (pmap 200) est la vérification de santé de l'environnement (4/4 réussites à chaud ; échec-crash à froid)
  • Les répertoires /data/local/tmp/.w* sont nettoyés au début de l'exécution (les répertoires accumulés dégradent la récupération)

TODO de la session 10 (arbre de décision, dans l'ordre)

  1. Lancer nf 200 sur des boots à stabilisation retardée jusqu'à ce que la ligne W1-CONFIRMED apparaisse, puis lire la ligne MARKER : a. marqueur PRÉSENT, uid!=0 → les creds du shellcode ont échoué (vérifier les adresses prepare_kernel_cred/commit_creds ; les encodages blx) b. marqueur ABSENT → le walk ne nous a jamais appelés : vérifier avec un SECOND marqueur écrit par... prochains diagnostics : hookfn qui écrit UNIQUEMENT le marqueur et retourne 1 (pas de creds) — si toujours absent :
    • dumper nos octets d'entrée réels via le mapping CPU juste avant le déclenchement (ils sont à nous, on peut les lire !)
    • vérifier si [init_net+0x58c] est même consulté : utiliser l'écriture pour à la place corrompre quelque chose d'observable (par ex. le pointer vers une cellule dont *cell = entrée avec fn = une fonction noyau comme kfree → crash immédiat au déclenchement = le champ EST consulté)
    • revérifier l'offset 0x58c : peut-être que hooks_ipv4[NF_INET_LOCAL_OUT] se trouve à un index différent (NF_INET_POST_ROUTING=4 ?)
  2. Si le walk nous appelle : corriger les creds → root → puis le plan de l'utilisateur : setenforce 0 ; cp /system/bin/sh /data/local/tmp/su ; chown root ; chmod 6755 ; vérifier ls -la ; laisser les fichiers marqueurs
  3. Persistance (post-root) : patch de l'image de boot via /dev/block/by-name/boot
    • désactiver dm-verity, ou style Magisk ; su sur /data seul est uid0-dans- le-domaine-shell après redémarrage (SELinux de nouveau enforcing) — setenforce 0 n'est que pour l'exécution
  4. Notes de nettoyage : les processus st3 en pause ont un état kctx corrompu — tuer uniquement via redémarrage ; le détournement nf casse les hooks LOCAL_OUT pour tout le trafic — redémarrer après root pour restaurer

Ajouts d'assets de ce soir

  • modes de poc/stage3.c : pin/root/drain{,2,3,4,5}/iso — arme complète + oracle + harnais d'isolation, forensique de point de crash O_SYNC
  • infrastructure de faux en espace utilisateur (ufake_prep) — CONSERVER mais utilisable uniquement si un chemin de déréférencement inline de contexte est un jour trouvé
  • tools/ : analyse vmlinux hors ligne kdis/scan_s/resolve/dumpb/findsysctl

SESSION 10 — ROOT OBTENU (2026-09-11)

Les trois bugs qui bloquaient l'arme-nf, tous corrigés

  1. Mauvais init_net : 0xc1104548 est __stack_chk_guard (l'histogramme movw/movt était pollué par les chargements du canari de pile — 2025 a bâti tout le plan nf dessus). Vrai init_net = 0xc1185040 (confirmé : ip_send_skb(net,...) appelé avec ce littéral ; ~994 références toutes dans la pile net). Cellule IPv4 LOCAL_OUT = init_net+0x58c = 0xc11855cc.
  2. Bug de double déréférencement : nf_iterate traite [init_net+0x58c] comme le pointeur nf_hook_ops lui-même — il lit fn@+0xc, priv@+0x14, prio@+0x20 directement depuis cette valeur. L'entrée factice de la session 9 vivait à PM_PAGE+0x700 avec la cellule pointant vers elle (champs next/ inutilisés au niveau de la cellule → → crash). L'entrée factice DOIT vivre (0xc154b740). Avec ceci corrigé, l'appel du hook a été prouvé (mode = SAFE_FN traite les 200 sendtos proprement).

Le mur SELinux et le contournement en 2 paquets

commit_creds(prepare_kernel_cred(0)) / override_creds(&init_cred) donne l'uid 0 mais atterrit dans le SID SELinux kernel, que cette politique Fire OS n'autorise pas à écrire dans /data ou /sys/fs/selinux/enforce (vérifié : EACCES). Le vrai SID init (7) est aussi refusé (test avec faux struct cred). enforcing_setup est __init (libéré → crash). L'atomic_sub de mark_reclaim nécessite nents=1 ce qui casse la sortie anticipée de shrink_cpu_mapping.

La victoire : le processus d'exploit conserve les mappings CPU kbm, donc la fausse entrée nf peut être réécrite sur place entre les paquets :

  • mode selroot : entrée = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}.
  • Paquet 1 : *(enforcing)=0 → SELinux Permissive.
  • Réécrire l'entrée via kbm_cpu[reg]+off vers {fn=commit_creds, priv=&init_cred}.
  • Paquet 2 : commit_creds(&init_cred) dans la tâche de l'émetteur → uid 0 avec un SELinux permissif → root utilisable, tout en une seule récupération, aucune chaîne nécessaire.

Vérifié sur l'appareil (2026-09-11)```

[+] W1 CONFIRMED: region 67 page+0xad000 <- 0xc11855cc (reclaim + G hit!) [*] sendto 0 -> -1 errno=1 uid=0 euid=0 [+] nf trigger done, uid=0 euid=0 <<< HOOK RAN - commit_creds OK [+] ROOT: uid=0 euid=0 [+] setenforce write=1 [+] su copied bytes=236220

root@kitploit:~
- `getenforce` → **Permissive** ; le st3 en pause est `Uid: 0 0 0 0`,
  `CapEff: 3fffffffff`.
- `/data` est monté **nosuid** donc un `su` setuid ne peut pas fonctionner. Un petit
  `rootshell` (envoi UDP → `commit_creds` sur soi-même → `execl sh`) donne un
  shell root interactif : `uid=0(root) context=u:r:kernel:s0`.
- Root peut lire/écrire `/dev/block/by-name/*` (`dd if=boot ...` OK).

### État du code Stage-3 (`poc/stage3.c`)
- modes : `nf <mb> [probe|uprobe|oc|chain|fc <sid>|notrig|selroot]`
- `selroot` est l'arme fonctionnelle. Statiques clés : `init_net=0xc1185040`,
  `HOOKS_PTR_ADDR=0xc11855cc`, `ENFORCING_ADDR=0xc1213ea8`,
  `ZERO_GADGET=0xc01d503c`, `commit_creds=0xc014993c`,
  `init_cred=0xc1114f54`.
- Le post-exploit utilise des syscalls directs (pas de `system()`) ; garder le kctx vivant
  (`pause()`) pour éviter un crash au démontage.

### Restant (Stage 4/5)
- Persistance après redémarrage (verity / image boot / recovery), car le
  détournement de cellule + SELinux permissif ne sont que runtime et relancer l'exploit nécessite
  le pile ou face de récupération à ~1/3.
- `su` aura besoin d'un home non-nosuid (`/system`) ou d'un lanceur qui le redéclenche.

## SESSION 11 — RECON DE PERSISTANCE (Track B + Track A) et le passage au RE

L'objectif était un root persistant. Deux pistes ont été définies :
- **Track B** : désactiver le verified boot (dm-verity / SELinux) pour pouvoir patcher `/system`.
- **Track A** : relancer l'exploit au démarrage.

Les deux se réduisent au même blocage : **faire que LK traite l'appareil comme `eng`/`unlocked`.**

### Faits sur le verified-boot (build exact)
- Bootloader verrouillé, AVB `green`, `ro.boot.unlocked_kernel=false`, `ro.boot.secure_cpu=1`,
  `rpmb_state=1`. Bootrom patché (pas de BROM) ; preloader uniquement via CMD short.
- `/system` est monté par **Android dm-verity depuis la cmdline kernel construite par lk** :
  `root=/dev/dm-0 dm="system none ro,0 1 android-verity PARTUUID=b6404ef3-… "`,
  `veritykeyid=id:f3530e18f64d11fc25eb2dd762979f078de990bf`, `androidboot.veritymode=eio`,
  `skip_initramfs` (system-as-root). `dm-0` = périphérique verity nommé `system` ; `dm-1` = `/vendor`.
- LK : Amazon **UFBL**, `ro.boot.lk_version=0x0006`, build `0db73c9-20231025_030009` ;
  preloader `pl_version=0x000a`, build `80c6fcb-20230523_065640`. `/dev/block/by-name/lk` = mmcblk0p5 (1 Mo).
- GPT complet (16 partitions, pas de `persist`/`seccfg`/`nvram`/`protect`/`para`) :
  `proinfo` p0, `PMT` p1, `kb` p2, `dkb` p3, `lk` p4, `tee1` p5, `tee2` p6, `metadata` p7,
  `MISC` p8, `reserved` p9, `boot` p10, `recovery` p11, `system` p12, `vendor` p13,
  `cache` p14, `userdata` p15. eMMC boot0 (1 Mo) = preloader (magic `EMMC_BOOT`) ;
  boot1 (4 Mo) = stockage IDME.

### Constats statiques sur LK (UFBL)
En-tête `lk.img` : `88 16 88 58 | 00052974 | "LK"` ; table de vecteurs ARM à 0x200, le reste en Thumb-2,
position-independent/relocalisé (les literal pools utilisent `ldr+add pc`, donc un désassemblage naïf base-relative échoue).
Chaînes pertinentes (offsets fichier) : `amzn_image_verify`, `amzn_verify_unlock`, `amzn_verify_code_internal`,
`unlock_code`, `unlock code error`, `unlock failed`, `$Common Kernel Signing Engineering CA0`,
`seccfg`, `para`, `ENV_v1`, `LK_ENV`, `Kfos_flags`/`Kdev_flags`/`Kusr_flags`/`Kunlock_code`/`Kunlock_version`,
`FOS_FLAGS_{NONE,ADB_ON,ADB_ROOT,CONSOLE_ON,RAMDUMP_ON,VERBOSITY_ON,ADB_AUTH_DISABLE,FORCE_DM_VERITY,DM_VERITY_OFF,BOOT_DEXOPT}`,
`[DM-VERITY] verify for system(root) is enabled`, `[DM-VERITY] verify off by fos_flags`,
`[DM-VERITY] disabled by fos_flags on eng devices or unlocked device`,
`[SELINUX] set to permissive mode by dev_flags`, `androidboot.prod=1|0`, `androidboot.unlocked_kernel=%s`.
**Conclusion : LK conditionne les effets de sécurité de `fos_flags`/`dev_flags` à eng/unlocked.**

### Stockage IDME (eMMC **boot1**) — inscriptible, persistant, lu par LK et Android
- Magic `beefdeed` + `"2.1\0"` + count(0x19=25) à 0x0 ; items à partir de 0x10.
- Format d'item : `char name[16]; u32 size; u32 type(=1); u32 magic(=0x124); u8 data[size] (pad4)`.
- Offsets des items (pristine) : `board_id@0x10 serial@0x3c mac_addr@0x68 mac_sec@0x94 bt_mac_addr@0xd0
  bt_mfg@0xfc product_name@0x198 productid@0x1d4 productid2@0x210 region@0x24c bootmode@0x26c
  postmode@0x28c bootcount@0x2ac manufacturing@0x2d0 unlock_code@0x4ec sensorcal@0x908 alscal@0x9c4
  KB@0xa00 DKB@0x1e1c device_type_id@0x2238 dev_flags@0x2274 fos_flags@0x2298 usr_flags@0x22bc
  wifi_mfg@0x22e0 unlock_version@0x26fc`. Les valeurs sont en ASCII (les flags sont des **chaînes hex**).
- Lecture runtime : `/proc/idme/<name>` (lecture seule). Les valeurs du dernier boot sont mises en cache ; une écriture sur boot1 prend
  effet au boot suivant. Le chemin d'écriture nécessite de vider `/sys/block/mmcblk0boot1/force_ro` (root).
- **Confirmé : LK lit boot1** : changer `serial` a modifié `ro.boot.serialno` au boot suivant.
  Mais LK **tronque serial à 16 octets** et a ignoré `fos_flags=0x80`, `dev_flags=0xff`,
  all-ones, etc. — verity/selinux/`prod` inchangés. Donc l'injection par cmdline via serial échoue.

### Consommateurs côté Android des flags IDME
- `/init.fosflags.sh` (service `fosflags`, `u:r:fosflags:s0`) : `FOS_FLAGS_ADB_ON=0x1`,
  `CONSOLE_ON=0x4`, `RAMDUMP_ON=0x8`, `VERBOSITY_ON=0x10`, `ADB_AUTH_DISABLE=0x20`,
  `BOOT_DEXOPT=0x100`. Vérifié : définir les flags prend effet (`sys.usb=adb`, `noadbauth=1`).
- **adbd** (ARM ET_EXEC non strippé ; `.text` VA 0x8160 / fichier 0x160 ; fileoff = VA-0x8000) :
  - `amzn_is_root_allowed` @0x2d5b8 = `amzn_is_dev_unlocked() && (fos_flags & 0x2)`
  - `amzn_is_adb_auth_disable_allowed` @0x2d5e8 = `fos_flags & 0x20` (non conditionné)
  - `amzn_is_dev_unlocked` @0x2d5fc = `/proc/cmdline` contient `androidboot.prod=0` **ou**
    `androidboot.unlocked_kernel=true`
  - `fos_read_debug_flags` @0x2d724 lit `/proc/idme/<name>` et parse en **hex**
  - `restart_root_service` @0xcb74 / `restart_unroot_service` @0xcc64
  - chaînes : `amzn_fos: ADB: Auto-root succeeded`, `… eng_device=%d`, `… unlocked_kernel=%d`,
    `adbd cannot run as root in production builds`, `ro.debuggable`
  - `adb root` → "cannot run as root in production builds" (`ro.debuggable=0`) — donc même avec la
    condition d'auto-root satisfaite, le contrôle prod d'AOSP conditionne le chemin de la commande.

### Pourquoi le root de la Session-10 ne persiste pas
- SELinux permissif + détournement de cellule + root ne sont que runtime.
- `/data/metrics` est une **vpartition** : `/system/bin/vpartition.sh` monte `/data/vp/metrics.img`
  (ext4, non-nosuid/noexec) sur `/data/metrics` à chaque boot ; un `su` écrit là ne **survit pas**
  au redémarrage. (C'est aussi pourquoi le `su` setuid donnait uid 0 mais **zéro caps**.)

### Track A (ré-exploitation au boot) — bloqué
- Aucun déclencheur `.rc` d'init n'exécute de code contrôlable (imports tous vérifiés ; les déclencheurs `persist.*` ne font que
  `start` des services fixes ; scripts dans `/system`/`/vendor`).
- Les services root lisent des configs dans `/data` mais n'exécutent jamais depuis celles-ci (`perfmonitord`, `amazonfiled`,
  `vpartition.sh`, `kisd`, …).
- Seul exécuteur au boot = une **app**, mais l'empreinte en pause de l'exploit est **VmRSS 534 Mo**
  (spray `kbm`) → lmkd le tue ; de plus une récupération perdue provoque un panic (`PANIC_ON_OOPS`) → bootloop.
- **L'auto-root d'adbd** existe mais est conditionné par la cmdline construite par LK (`prod=0`/`unlocked_kernel=true`).

### Conclusion / cible suivante (choisie : RE Track B)
Tout repose sur le fait de faire rapporter `eng`/`unlocked` par LK. À portée :
`androidboot.prod=1|0` et `androidboot.unlocked_kernel=false` sont définis par LK. Reverse LK pour trouver :
1. où il lit `fos_flags`/`dev_flags`/`usr_flags` (les items `K*`) et la condition exacte ;
2. la détermination `prod`/`unlocked` (item IDME ? buildvariant ? résultat de `amzn_verify_unlock` ?) ;
3. `amzn_verify_unlock` (vérification RSA libtomcrypt) pour un bypass ou un chemin faible unlock_code/version ;
4. le stockage `seccfg`/`para`/`ENV_v1`(LK_ENV) (absent de toute partition dumpée — peut-être protégé par tee) ;
5. le preloader (`boot0`, `EMMC_BOOT`) pour un bug.
Si l'un de ces éléments nous permet de définir eng/unlocked (de façon persistante, via boot1 ou une écriture de partition brute), alors
`FOS_FLAGS_DM_VERITY_OFF` désactive la verity de system(root) et `/system` peut être patché de façon persistante.

### Artefacts (de cette session)
`/tmp/opencode/mustang-dumps/` (peut être effacé au redémarrage de l'hôte) : `lk.img`, `boot1.img` (pristine),
`boot.img`, `MISC.img`, `metadata*.img`, `pmt.img`, `mbr.img`, `kb.img`, `dkb.img`, `reserved.img`,
`cache.img`, `boot0.img`, `boot1.img`, `adbd.bin`, `perfmonitord.bin`, `amazonfiled.bin`.
Helpers : `tools/findinitnet.py`, `findgadget*.py`, `findstores.py`, `adbd_sym.py` (dans /tmp) ;
le repo contient `run.sh`, `poc/stage3.c` (`selroot`), `poc/su.c`, `rootcmd.sh`.

### Commandes utiles```
# IDME read
/data/metrics/su sh -p -c 'for f in fos_flags dev_flags usr_flags serial region device_type_id unlock_version; do echo -n "$f="; cat /proc/idme/$f; echo; done'
# write boot1 (root; su lives only until reboot -> re-run run.sh first)
/data/metrics/su sh -p -c 'echo 0 > /sys/block/mmcblk0boot1/force_ro; dd if=/data/local/tmp/boot1.img of=/dev/block/mmcblk0boot1 bs=4096 count=4; sync; echo 1 > /sys/block/mmcblk0boot1/force_ro'
# dump a partition to host
adb exec-out '/data/metrics/su dd if=/dev/block/by-name/lk bs=4096 2>/dev/null' > lk.img

SESSION 12 — LK RE : la porte eng/unlocked est réelle, et les stockages de drapeaux non signés n'existent pas

Objectif : amener LK à traiter l'appareil comme eng/unlocked, ou trouver un bug du preloader/LK, afin que verity/SELinux puissent être désactivés de manière persistante. Résultat : le chemin de code LK pertinent a été inversé de bout en bout ; le basculement n'est pas atteignable par les stockages disponibles. Aucun appareil n'a été brické ; la seule expérience sur boot1 a été annulée pour revenir à l'état d'origine.

LK est en Thumb-2 PIC, relocalisé à la base 0xFF400000

lk.img commence par un petit stub ARM (fichier 0x200). Le relocateur à 0x224 copie de 0x200 vers une destination littérale et branche vers un point d'entrée littéral :

Donc adresse d'exécution = 0xFF400000 + offset fichier pour les offsets >= 0x200. Tout ce qui suit le stub est du Thumb-2, indépendant de la position. Les chaînes sont construites avec ldr rT,[pc,#imm] (offset T1 = imm8*4 ; offset ldr.w = imm12) suivi de add rT, pc ; la cible est (add+4) + *pool. Un scanner robuste qui survit au stub ARM et aux pools littéraux a été ajouté sous tools/lk_xref.py (gère les formes 16 et 32 bits, scanne tous les 2 octets). Tous les offsets ci-dessous sont des offsets fichier ; ajoutez 0xFF400000 pour les adresses d'exécution.

Flux de contrôle décodé (offset -> signification)

Le code de déverrouillage est signé par Amazon-RSA — non falsifiable

0x20b4 lit l'élément IDME unlock_code (0x400 octets, tous à zéro sur cet appareil) et exécute amzn_verify_unlock (0x222c -> 0x20f0). Cette fonction pilote libtomcrypt (des dizaines de chemins /features/libtomcrypt/src/pk/asn1/der/... et la vérification RSA), et l'image embarque le matériel de certificat : Sunnyvale / Amazon Lab126 / "$Common Kernel Signing Engineering CA0" à 0x317d9+, plus les diagnostics Image FAILED AUTHENTICATION on PRODUCTION device (0x3166e), Authentication failed on engineering device with production certificate (0x316a0), Image FAILED AUTHENTICATION on ENGINEERING device (0x31703), Image AUTHENTICATED with PRODUCTION certificate (0x31736). Il n'y a pas de raccourci code vide / longueur / version : , donc (confirmé dans ). Basculer ou nécessite soit un valide signé par Amazon (clé privée indisponible), soit un bug d'exécution de code dans le vérificateur. Rien d'exploitable (bornes/taille) n'a été trouvé statiquement dans 0x20b4/0x222c/0x20f0. =>

Les drapeaux verity/SELinux ne proviennent pas d'un stockage qui existe

Les drapeaux de sécurité sont lus via le getter à 0x57c. Test empirique :```

boot1 IDME item fos_flags data (offset 0x22B4, 8 bytes) set to "00000080"

dd if=/dev/block/mmcblk0boot1 ... ; reboot /proc/idme/fos_flags -> 00000080 (persisted, Android sees it) ro.boot.veritymode -> eio (unchanged!) root=/dev/dm-0 dm="system none ro,0 1 android-verity PARTUUID=..." (unchanged) androidboot.prod=1 / secure_cpu=1 / buildvariant=user (unchanged)

root@kitploit:~
`fos_flags=0x80` est `FOS_FLAGS_DM_VERITY_OFF` ; la porte décodée aurait désactivé
verity **si** le getter l'avait renvoyée.  Ce ne fut pas le cas.  La porte est active, pas
du code mort : son sentinelle de cache à usage unique est `-1` dans l'image
(`*(u32*)0x50c74 == 0xffffffff`), donc la fonction a réellement exécuté le
chemin `check_flag("fos_flags",0x80)` et a obtenu 0.  Par conséquent, le getter (au moins
au moment de la protection verity) **ne** lit **pas** les éléments IDME de boot1.

L'autre magasin candidat est le **LK env**, chargé depuis une partition littéralement
nommée `"para"` (loader 0x12fd4, magie `ENV_v1`, checksum @0x3ffc).  La propre
table de partitions de LK (0x4fcc0..0x50340) liste preloader/proinfo/nvram/protect1/
protect2/persist/seccfg/secro/**para**/logo/custom/expdb/tee1/tee2/metadata/
system/cache/userdata — mais le GPT réel de la tablette n'a **que 16 entrées**, toutes
de type `af3dc60f838472478e793d69d8477de4` :```
#0 proinfo 0x400   #1 PMT 0x1c00    #2 kb 0x4000     #3 dkb 0x4800
#4 lk 0x5000       #5 tee1 0x5800   #6 tee2 0x8000   #7 metadata 0xa800
#8 MISC 0x1e400   #9 reserved 0x1e800  #10 boot 0x22800  #11 recovery 0x2a800
#12 system 0x34800 #13 vendor 0x644000 #14 cache 0x6b4800 #15 userdata 0x7ae800

Il n'y a aucune partition para, seccfg, nvram, protect ou persist sur ce produit (et les dumps PMT/pmt.img sont entièrement à zéro). Donc l'env LK est vide, les clés Kfos_flags/Kdev_flags n'existent jamais, et toutes les vérifications fos_flags/dev_flags se résolvent à 0 — indépendamment de ce que contiennent les éléments IDME. Les éléments IDME de boot1 sont consommés par Android (/init.fosflags.sh, adbd, /proc/idme/*) mais pas par les barrières de sécurité de LK.

Conclusion — pourquoi l'eng/unlock persistant est bloqué

  1. unlocked_kernel nécessite un unlock_code signé par Amazon (RSA/libtomcrypt, CA embarquée). Non falsifiable hors ligne ; aucun bug de vérificateur trouvé. Blocage strict.
  2. Les flags DM_VERITY_OFF / selinux=permissive sont consommés depuis l'env LK (para/ENV_v1), qui n'existe pas sur ce GPT. Le fos_flags d'IDME est empiriquement ignoré par LK (0x80 persisté, verity est resté eio). Blocage strict sauf si la table de partitions est modifiée.
  3. Même un fos_flags=0x80 réussi ne ferait que définir androidboot.veritymode= disabled et un root= non-dm-0 ; cela ne déverrouillerait pas, et SELinux aurait toujours besoin de dev_flags depuis le même env absent pour passer en permissive.

Voies restantes (futures, risque plus élevé ; non tentées)

  • Synthétiser un store para/ENV_v1 : ajouter une entrée GPT nommée para (GPT primaire
    • de secours doivent tous deux être mis à jour) dans l'espace libre après userdata (userdata se termine au LBA 0x3a3dfde ; disque = 30535680 secteurs), puis fabriquer un env avec fos_flags=0x80 et dev_flags=0x40 (checksum à +0x3ffc = somme des octets sur 0x3ffc). C'est la seule route restante vers verity-off. Risques : corrompre le GPT primaire/de secours peut bricker ; et il n'a pas été prouvé que la barrière verity lit réellement para (seulement qu'il ne s'agit pas d'IDME boot1).
  • Bug du Preloader (boot0/EMMC_BOOT) : non reversé cette session. L'écriture de boot0 est interdite tant qu'une copie pristine et un chemin de récupération n'existent pas.
  • Recherche sur le vérificateur : le chemin du certificat d'ingénierie (0x316a0/0x31703) n'est atteignable qu'avec une identité d'appareil acceptée comme « engineering » plus un code signé par la clé d'ingénierie ; aucune clé privée n'est disponible.

Artefacts / reproductibilité

  • Outil ajouté : tools/lk_xref.py — résolveur de xref de chaînes LK indépendant de la base.
  • Dumps utilisés : /tmp/opencode/mustang-dumps/lk.img, boot1.img (pristine), boot0.img, mbr.img (GPT), pmt.img (entièrement à zéro).
  • Image d'expérimentation boot1 (fos_flags=0x80) conservée à /tmp/opencode/s12/boot1_f80.img ; appareil restauré sur boot1 pristine (vérifié /proc/idme/fos_flags -> 0).

Commandes utiles (root requis ; réarmer avec ./run.sh)```

re-arm runtime root (~1/3 per boot)

./run.sh --no-build

confirm LK's decisions without a UART

/data/metrics/su /system/bin/sh -p -c 'cat /proc/cmdline'

watch: root=/dev/dm-0 dm="system ... android-verity ..." (verity on)

androidboot.veritymode=eio ; androidboot.selinux=enforce ; prod=1

IDME read (Android copy; NOT what LK's gates use)

for f in fos_flags dev_flags usr_flags unlock_version serial; do cat /proc/idme/$f; echo; done

root@kitploit:~
## SESSION 13 — le préchargeur est finalement accessible (`1949:20ff` = préchargeur MTK, transport HID)

Lorsqu'il est éteint et branché en USB, la tablette s'énumère en tant que **`1949:20ff`**
(`Lab126`) — *pas* Android et *pas* le bootrom `0e8d:0003`. Descripteur :```
bInterfaceClass 3 (HID), iConfiguration "HID", iInterface "HID Interface"
HID report descriptor = 05 01 09 00 a1 01 c0   (empty collection!)
EP 0x81 IN  interrupt  4 bytes, bInterval 4
EP 0x01 OUT interrupt  4 bytes, bInterval 4
iSerial = GCC0X90805310009 (the IDME serial)

Identification. 0x20FF est listé comme "MTK Preloader" dans le fichier config/usb_ids.py de mtkclient (sous le VID MediaTek 0x0e8d : 0xe8d:{0x0003 Brom, 0x2000/0x2001/0x20ff/0x3000 Preloader}). Amazon a conservé le PID du preloader et changé le VID en 0x1949, et le présente comme une paire de points de terminaison HID avec un descripteur de rapport factice. Il s'agit donc du mode preloader MediaTek / USBDL, une étape en dessous de LK — atteinte ici par extinction + branchement, et non par le court-circuit CMD.

Les chaînes de descripteur "HID"/"HID Interface" ne sont pas présentes dans lk.img, boot0.img, boot.img ou les autres dumps, c'est-à-dire que le mode est produit par un composant que nous n'avons pas dumpé (bootrom/TEE) ou est assemblé à l'exécution.

Pourquoi cela importe. Le preloader Amazon utilisé par aftv2-tools expose des commandes intégrées, sans Download-Agent, sur ce flux d'octets exact :``` handshake : host A0 0A 50 05 -> dev 5F F5 AF FA 0xD1 read32 (addr, n_words) : echo cmd/addr/n, 00 00, nu32, 00 00 0xD4 write32(addr, words[]) : echo cmd/addr/n, 00 00, nu32, 00 00

root@kitploit:~
`aftv2-tools/read_mmc.py` utilise `read32`/`write32` pour manipuler le contrôleur MSDC
(base `0x11230000` sur MT8173 ; à vérifier pour MT8163) et lire/écrire des **blocs eMMC
bruts** sans DA et donc sans AVB/verity pour interférer. Si le preloader de mustang
accepte 0xD1/0xD4, c'est un chemin direct vers un déverrouillage persistant (patch de `boot` /
`lk`), indépendant du code de déverrouillage RSA et de l'environnement LK absent.

### Outils ajoutés (root requis ; chmod le nœud USB d'abord)```
lsusb -d 1949:20ff                 # note Bus/Dev, e.g. Bus 001 Device 003
sudo chmod 666 /dev/bus/usb/001/003
# 1) does it answer the MTK handshake? (no DA, no flash access)
nix-shell -p python3Packages.pyusb --run \
    'python3 tools/mtk_preloader_hid.py handshake'
# 2) read-only arbitrary memory read
nix-shell -p python3Packages.pyusb --run \
    'python3 tools/mtk_preloader_hid.py read32 0x00100000 4'

tools/probe_preloader.py est la sonde minimale de handshake uniquement ; tools/mtk_preloader_hid.py est le transport complet (handshake/read32/ write32 ; write32 est protégé et ne doit pas être utilisé tant que la carte de registres eMMC n'est pas confirmée).

Statut / prochaines étapes

  • Non confirmé : si le preloader de mustang implémente réellement 0xD1/0xD4 (la sonde de handshake en décide). Si c'est le cas, le chemin de lecture/écriture eMMC d'aftv2 peut probablement être porté directement.
  • Ensuite : trouver la base MSDC du MT8163 (DT du noyau ou preloader), dumper une partition (read_mmc), puis patcher boot.img/lk depuis le preloader et redémarrer.
  • Il s'agit d'un stade de démarrage inférieur à tout ce qui figure dans SESSION 12, donc cela ne dépend pas de amzn_verify_unlock ni du verrou d'environnement LK.
  • Ne PAS exécuter d'écritures SP Flash Tool / mtkclient contre celui-ci avant que le protocole et la disposition eMMC soient confirmés.
Télécharger l’outil
pi_blocked_on
  • Le waiter proxy réside sur la pile du thread waiter lui-même (futex.c:1975 passe this->rt_waiter, déclaré dans futex_wait_requeue_pi à futex.c:2880) → le waiter marque sa propre frame libérée via les fd_sets de select (nr 142) arm32
  • Climat d'exploitation : pas de KASLR (base fixe 0xc0008000), pas de PAN, DEBUG_RT_MUTEXES désactivé → rt_mutex_waiter compact de 48 octets (tree_entry@0, pi_tree_entry@0xc, task@0x18, lock@0x1c, prio@0x20, deadline@0x28)
  • Chaîne minimale : 2 slots d'écriture → modprobe_path @ 0xc111488c (chaîne auto-localisée dans vmlinux ; KALLSYMS_ALL désactivé donc les symboles de données nécessitent cette astuce) → exec binfmt inconnu → script root (setenforce 0, désactiver OTA, su)
  • Références dans refs/ : NebuSec/CyberMeowfia (original), GhostLock-5.10 (port Fire OS 8, déclencheur ARM 32 bits complet dans src/exp32/), ghostlock-...-4.19-k40 (port Android Qualcomm 4.19)
  • Étape 3 : appel de fonction kernel arbitraire → ROOT (session 10)
    selroot
    selinux_state.enforcing
    commit_creds(&init_cred)
    uid=0
  • [_] Étape 4 : script root (su, permissive, OTA désactivé) + persistance — root obtenu ; persistance bloquée (voir SESSION 11) : LK conditionne verity-off/SELinux-permissive à eng/unlocked, la ré-exploitation au démarrage n'a pas d'exécuteur viable. Suivant : reverse LK/amzn_verify_unlock.
  • Étape 5 : chaîne de démarrage d'OS personnalisée
  • jc
    nr_extres
  • Le résultat de l'alloc JIT est écrit par le kernel via info->gpu_alloc_addr (un GPU VA que vous devez pré-allouer et passer)
  • objet évictablepressionrésultat
    aucun900 Mosurvit
    aucun1300 Mopanic (bug lowmem système — sans rapport)
    région normale + DONT_NEED700 Mosurvit
    région JIT + DONT_NEED700 Mopanic dans le chemin de reclaim
  • kernel/config-* — dump de /proc/config.gz depuis l'appareil en cours d'exécution
  • ksrc/ — source OSS Amazon (platform.tar + arbre midgard-r26p0 extrait)
  • OTA : /tmp/opencode/mustang_ota.bin (sha256 6068515a… correspond à fireos-archive) et l'archive tar de sources kernel de 2,2 Go conservée dans ~/Desktop/amazon-mustang/
  • Chemin de l'arbre source : ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/
  • sprayers rename concurrents pendant la tempêteles renames calent (journal/GFP_NOFS) → 128 au total → déréférencement poubelle
    pré-drain 12K événements + pression + petite traînecrash au déréférencement
    + kill-enfant-à-l'éviction (poll 10ms)crash au déréférencement
    + cycle de vie épinglé cpu0 (drain3)crash au déréférencement
    pression par paliers (drain4 v1)les enfants ont libéré la mémoire en sortant → pas d'éviction (chemin légal validé)
    runrésultat
    pmap G=c2a412a4 400MBcrash au deref
    pmap G=c2f4b2a4 480MBcrash ; +0 renommages post-kill (480MB étouffe le fs)
    iso2 (spray + regions + oracle)REGION HIT — le spray ne casse pas le reclaim
    mix v1 (événements+régions concurrents)crash ; confondu (threads d'événements en spin)
    pmap G=c2a7d2a4 350MB + retouchcrash ; +4452 renommages OK
    mix2 (séquentiel : 2s événements PUIS régions)crash ; +7126 renommages (28K allocs d'événements), 2715 régions
    fn
    fn=0
    à l'adresse de la cellule
    PM_PAGE+NF_CELL_OFF
    probe
  • La carte directe physmap est XN au-dessus de kernel_x_end : arch/arm/mm/mmu.c map_lowmem() mappe lowram sous le texte du noyau en MT_MEMORY_RWX, mais tout ce qui est au-dessus de kernel_x_end en MT_MEMORY_RW → PMD_SECT_XN (ligne 509). Le shellcode intégré à 0xc154b600 provoque un prefetch-abort. Le payload doit être un pointeur de fonction noyau réel, pas du code dans la physmap.
  • littéral (offset fichier)valeursignification
    0x2700xFF4002F8str r4,[r6] zone de travail
    0x2740xFF40027Cdestination (base+0x27C)
    0x2780xFF54A440fin de copie (incl. BSS)
    0x27C0xFF400484point d'entrée
    offsetfonction
    0xdf7cis_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ] ; g = global @0x52838. 1 sur cet appareil.
    0x20b4verify_stored_unlock() = memset(buf,0,0x100) ; lit IDME/env unlock_code (0x100) via 0x57c ; bl 0x222c ; retourne (verify==0).
    0x222c / 0x20f0amzn_verify_unlock(code,len) — vérification RSA/PKCS#1 libtomcrypt (voir ci-dessous).
    0xda3eis_unlocked() = is_secure_or_prod() && verify_stored_unlock().
    0x29a28is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock()) ; mis en cache dans le global @0x50c74.
    0x29974Constructeur de cmdline SELinux : dev_flags & 0x20 -> androidboot.selinux=enforce, dev_flags & 0x40 -> ...=permissive (chacun conditionné par byte[+0x162]).
    0x118xx/0x11bxxconstructeur de cmdline du noyau (unlocked_kernel, prod=1/0, verifiedbootstate, rpmb_state, secure_cpu, versions, root=).
    0x27af8Porte UART : fos_flags & 0x4 -> printk.disable_uart=0, sinon =1.
    0x12fd4Chargeur d'env LK : partition "para", 0x4000 octets, magie ENV_v1, checksum = somme des octets sur 0x3ffc comparée au mot @0x3ffc.
    0x1efd0recherche de partition par nom (utilisé pour "para", "boot", ...).
    0x57crépartiteur de getters via l'emplacement de callback @0x58218 ; les emplacements @0x58200..0x5821c sont enregistrés depuis une table à 0x5a8-0x734.
    0x2a19cfastboot oem unlock : bl 0x222c(code,len) ; en cas de succès écrit unlock_code (0x100) via 0x408.
    verify(zeros) != 0
    unlocked_kernel=false
    /proc/cmdline
    androidboot.unlocked_kernel
    androidboot.prod
    unlock_code
    le basculement eng/unlocked via le chemin documenté est cryptographiquement infaisable.
  • La piste A (ré-exploitation au démarrage) reste donc bloquée exactement comme dans la SESSION 11 : son seul chemin de déverrouillage est la même barrière LK.