
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.
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.
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.
nix-shell -p android-tools --run './run.sh' # add -p zig too if no zig
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.
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.
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)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 icisched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) à
sched/core.c:4706 — déréférence un obsolète ✓exp32/main.crt_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 ajustablesFailed critical init step 3/dev/mali0 world-RW + SELinux gpu_device, kbase r26p0-01rel0mustang, Fire OS 7.3.3.1 PS7331.4463N/00315758630404.9.117-g08fe75b-dirty, compilé le sam. 3 mai 01:25:15 UTC 2025 (Linaro GCC 6.3-2017.05)/dev/kb, /dev/dkb (partitions de sauvegarde kernel Amazon) root:drmrpc 0660 — verrouillésmali_kbase r26p0-01rel0 (Midgard, Mali-T720), dans la plage affectée NVD r4p0–r31p0mali_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ètemali_kbase_mem.c:3138 kbase_jit_backing_lost → chemin de destruction (se déclenche pendant le reclaim)0xc0008000 / PA 0x40080000)ARM_SW_DOMAIN_PAN → ret2usr viable ; CONFIG_PANIC_ON_OOPS=y (tentatives échouées = redémarrage)SLAB_FREELIST_RANDOM/HARDENED, pas de CONFIG_USER_NS/USERFAULTFD/NF_TABLESCONFIG_MODULES=y, pas de STATIC_USERMODEHELPER → écrasement de modprobe_path = root_IOC_TYPE 0x80)MEM_ALLOC fait 32 octets (in a 4 × u64 dont extent)BASE_MEM_PROT_GPU_RD|WR (bits 2|3), pas le R|W héritémmap(fd, offset=3<<12, PROT_NONE)JOB_SUBMIT doit être égal à sizeof(base_jd_atom_v2) = 48 (base_jd_prio/base_jd_dep_type sont des typedefs u8)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)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.
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)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
## 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) :
/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).
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 :
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.
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.
Testés et morts depuis le domaine shell :
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.
(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.
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.
kbase_jit_free(kctx, reg) @ 0xc058495c avec faux reg entièrement contrôlé :
reg->cpu_alloc NULL → taille backed 0 → bloc trim sauté (0xc0584978)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)list_add(gpu_alloc->evict_node, &kctx->evict_list) : tête evict_list
@ kctx+0x1427c ; écrit dans gpu_alloc+0x18/0x1c (doit être inscriptible)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+0x148e89 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).
add_key (CONFIG_KEYS=y) : refusé par SELinux pour shell. Mort.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)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.
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 :
*(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)poc/stage3.c) :
kern_table[pid_max].proc_handler @ 0xc1113f40 (inscriptible
.data, vérifié via scan de pointeur-chaîne + handler == proc_dointvec_minmax)b +8 ; W2 écrit N à P = champ handler)read /proc/sys/kernel/pid_max
(lisible depuis shell) ; s'exécute dans son propre contexte de tâche → les creds s'appliquent à nousinotify_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)| variante | ré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.
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èmecurrent->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.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).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.
/tmp/opencode/ksrc2/kernel/mediatek/mt8163/4.9/ (arch/arm incl.
mustang.dtsi, mm/, fs/eventpoll.c, fs/notify) depuis ksrc/platform.tar.
Constats :
physmap_retouch() après le kill (recharge toutes les pages du spray
avant le trailing/deref)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).
vmalloc=496M slub_max_order=0 slub_debug=O loglevel=8 initcall_debug
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.
[+] uname.nodename="X?lhost" (was "localhost") <<< ORACLE HIT
**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.)
pmap 200) est la vérification de santé de l'environnement (4/4 réussites à chaud ;
échec-crash à froid)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 :
ls -la ; laisser les fichiers marqueurspoc/stage3.c : pin/root/drain{,2,3,4,5}/iso — arme complète +
oracle + harnais d'isolation, forensique de point de crash O_SYNCinit_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.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).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 :
selroot : entrée = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}.*(enforcing)=0 → SELinux Permissive.kbm_cpu[reg]+off vers {fn=commit_creds, priv=&init_cred}.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.[+] 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
- `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
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.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.
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 de sécurité sont lus via le getter à 0x57c. Test empirique :```
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)
`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.
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.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.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.para/ENV_v1 : ajouter une entrée GPT nommée para (GPT primaire
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).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.tools/lk_xref.py — résolveur de xref de chaînes LK indépendant de la base./tmp/opencode/mustang-dumps/lk.img, boot1.img (pristine),
boot0.img, mbr.img (GPT), pmt.img (entièrement à zéro)./tmp/opencode/s12/boot1_f80.img ; appareil restauré sur boot1 pristine
(vérifié /proc/idme/fos_flags -> 0)../run.sh)```./run.sh --no-build
/data/metrics/su /system/bin/sh -p -c 'cat /proc/cmdline'
for f in fos_flags dev_flags usr_flags unlock_version serial; do cat /proc/idme/$f; echo; done
## 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
`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).
read_mmc), puis patcher boot.img/lk depuis le preloader et redémarrer.amzn_verify_unlock ni du verrou d'environnement LK.pi_blocked_onfutex.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) arm32DEBUG_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)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)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)selrootselinux_state.enforcingcommit_creds(&init_cred)uid=0jcnr_extresinfo->gpu_alloc_addr (un GPU VA que vous devez
pré-allouer et passer)| objet évictable | pression | résultat |
|---|
| aucun | 900 Mo | survit |
| aucun | 1300 Mo | panic (bug lowmem système — sans rapport) |
| région normale + DONT_NEED | 700 Mo | survit |
| région JIT + DONT_NEED | 700 Mo | panic dans le chemin de reclaim |
kernel/config-* — dump de /proc/config.gz depuis l'appareil en cours d'exécutionksrc/ — source OSS Amazon (platform.tar + arbre midgard-r26p0 extrait)/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/ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/| sprayers rename concurrents pendant la tempête | les renames calent (journal/GFP_NOFS) → 128 au total → déréférencement poubelle |
| pré-drain 12K événements + pression + petite traîne | crash 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é) |
| run | résultat |
|---|
| pmap G=c2a412a4 400MB | crash au deref |
| pmap G=c2f4b2a4 480MB | crash ; +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 + retouch | crash ; +4452 renommages OK |
| mix2 (séquentiel : 2s événements PUIS régions) | crash ; +7126 renommages (28K allocs d'événements), 2715 régions |
fnfn=0PM_PAGE+NF_CELL_OFFprobekernel_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) | valeur | signification |
|---|
| 0x270 | 0xFF4002F8 | str r4,[r6] zone de travail |
| 0x274 | 0xFF40027C | destination (base+0x27C) |
| 0x278 | 0xFF54A440 | fin de copie (incl. BSS) |
| 0x27C | 0xFF400484 | point d'entrée |
| offset | fonction |
|---|
0xdf7c | is_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ] ; g = global @0x52838. 1 sur cet appareil. |
0x20b4 | verify_stored_unlock() = memset(buf,0,0x100) ; lit IDME/env unlock_code (0x100) via 0x57c ; bl 0x222c ; retourne (verify==0). |
0x222c / 0x20f0 | amzn_verify_unlock(code,len) — vérification RSA/PKCS#1 libtomcrypt (voir ci-dessous). |
0xda3e | is_unlocked() = is_secure_or_prod() && verify_stored_unlock(). |
0x29a28 | is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock()) ; mis en cache dans le global @0x50c74. |
0x29974 | Constructeur de cmdline SELinux : dev_flags & 0x20 -> androidboot.selinux=enforce, dev_flags & 0x40 -> ...=permissive (chacun conditionné par byte[+0x162]). |
0x118xx/0x11bxx | constructeur de cmdline du noyau (unlocked_kernel, prod=1/0, verifiedbootstate, rpmb_state, secure_cpu, versions, root=). |
0x27af8 | Porte UART : fos_flags & 0x4 -> printk.disable_uart=0, sinon =1. |
0x12fd4 | Chargeur d'env LK : partition "para", 0x4000 octets, magie ENV_v1, checksum = somme des octets sur 0x3ffc comparée au mot @0x3ffc. |
0x1efd0 | recherche de partition par nom (utilisé pour "para", "boot", ...). |
0x57c | répartiteur de getters via l'emplacement de callback @0x58218 ; les emplacements @0x58200..0x5821c sont enregistrés depuis une table à 0x5a8-0x734. |
0x2a19c | fastboot oem unlock : bl 0x222c(code,len) ; en cas de succès écrit unlock_code (0x100) via 0x408. |
verify(zeros) != 0unlocked_kernel=false/proc/cmdlineandroidboot.unlocked_kernelandroidboot.produnlock_code