Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
amazon-mustang-hack — Ricerca di exploit del kernel che ottiene root temporaneo su Amazon Fire 7 (Fire OS 7.3.3.1) tramite la use-after-free JIT di Mali kbase CVE-2022-38181, con una catena di overwrite di modprobe_path. | Kitploit
Strumenti/GitHubGitHub/artur9010/amazon-mustang-hack
Sicurezza AndroidEscalation di PrivilegiAnalisi delle VulnerabilitàExploitReverse EngineeringSicurezza MobilePaper e RicercaSviluppo PayloadBinary Exploitation

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi
GitHubartur9010/amazon-mustang-hack

amazon-mustang-hack

Ricerca di exploit del kernel che ottiene root temporaneo su Amazon Fire 7 (Fire OS 7.3.3.1) tramite la use-after-free JIT di Mali kbase CVE-2022-38181, con una catena di overwrite di modprobe_path.

Vedi RepositorySito web
13h 58m faNon ancora revisionato

Progetto assistito dall'IA. Questa ricerca, lo sviluppo dell'exploit e la documentazione sono stati prodotti con l'assistenza dell'IA utilizzando i modelli GLM-5.3 e DeepSeek V4.1 Flash.

amazon-mustang-hack

Ricerca di exploit root per l'Amazon Fire 7 9ª gen (mustang, MT8163, Mali-T720) sull'ultimo firmware — Fire OS 7.3.3.1, PS7331.4463N, kernel 4.9.117 (compilato il 2025-05-03, SPL 2024-08-01).

Obiettivo: LineageOS. Il percorso del bootloader è morto su questa unità (bootrom patchata — solo preloader tramite cortocircuito CMD), quindi l'unica via rimasta è un exploit software del kernel.

Avvio rapido```

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:~
In caso di successo:```
/data/metrics/su id     # run a command as root
/data/metrics/su        # interactive root shell

Il reclaim riesce all'incirca 1 avvio su 3 e una perdita manda in panic/riavvia il tablet; run.sh si limita ad attendere il riavvio e riprovare. SELinux viene forzato in Permissive come parte dell'exploit, quindi root è solo a runtime — un riavvio ripristina lo stock e si riesegue run.sh.

st3 e su precompilati (armv7 static) sono inclusi nel repository, quindi non serve alcun toolchain per eseguire. ./run.sh --build li ricompila da poc/*.c se si dispone di zig.

Tutto ciò che segue è solo un log del lavoro svolto dal modello, nessun input umano sotto.

TARGET PRIMARIO (dalla sessione 5): kbase CVE-2022-38181 — stage 2 DIMOSTRATO

GhostLock (sotto) è parcheggiato: la variante rtmutex BUG_ON di MTK + nessuna divulgazione di indirizzi kernel dalla shell = vicolo cieco architetturale su questa build (sessioni 2-4). La UAF del JIT di kbase è stata ridiagnosticata (il "panic incondizionato" del destroy-worker era il deref di JIT_FREE, log perso per la morte di adbd a metà panic) e lo stage 2 è ora dimostrato dall'oracolo — vedi la sezione SESSIONE 5.

PARCHEGGIATO: GhostLock, CVE-2026-43499

UAF sullo stack futex-PI di remove_waiter() in rtmutex (divulgazione NebuSec 2026-07, fix 3bfdc63936dd integrato 2026-04). Intervallo vulnerabile 2.6.39–7.1 → il nostro 4.9.117 (maggio 2025) è affetto.

Verificato sulla nostra build esatta:

  • CONFIG_FUTEX=y, rtmutex compilato, bug presente alla lettera: rtmutex.c:1108-1111 usa current->pi_lock/current->pi_blocked_on (dovrebbe essere waiter->task); sito di chiamata difettoso rtmutex.c:1723 (percorso d'errore di rt_mutex_start_proxy_lock)
  • Superficie di trigger = pure syscall futex (WAIT_REQUEUE_PI/CMP_REQUEUE_PI), nessun device node, nulla controllato da SELinux — gli ostacoli fatali del percorso kbase qui non esistono
  • Consumer: sched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) a sched/core.c:4706 — dereferenzia obsoleto ✓

TODO (piano di porting)

  1. Scrivere il trigger (deadlock requeue-PI a 3 thread, core 0-3) — port di exp32/main.c
  2. Geometria dello stamp: offset del frame rt_waiter rispetto all'area fd_set di do_sys_select — disassemblare il nostro vmlinux (do_sys_select stack_fds vs frame di futex_wait_requeue_pi), esporre STAMP_NFDS/STAMP_WAITER_OFF come parametri
  3. Codifica fake-writer per waiter arm32 da 48 byte → slot "scrivi V in ADDR"
  4. 2 slot → modprobe_path, fire, script root
  5. Fallback se lo stamp via select non riesce a raggiungere: stamp via setsockopt(MCAST_JOIN_SOURCE_GROUP)

Stato

  • Bootrom (metodo hardware amonet) — patchato su questa unità, vicolo cieco
  • mtk-su (CVE-2020-0069) — patchato, Failed critical init step 3
  • Ricognizione della superficie d'attacco — /dev/mali0 world-RW + SELinux gpu_device, kbase r26p0-01rel0
  • CVE-2022-38181 confermato nel sorgente della build esatta; il trigger dello stage 1 funziona
  • CVE-2026-43499 (GhostLock) verificato ma bloccato: variante rtmutex BUG_ON di MTK + nessuna divulgazione di indirizzi kernel dalla shell (sessioni 2-4)
  • CVE-2022-38181 stage 2 DIMOSTRATO (sessione 5): il panic del destroy-worker era una diagnosi errata; redirect della UAF sulla regione spruzzata, verificato dall'oracolo
  • Stage 1: trigger + stamp + consumer (crash = catena attiva)
  • Stage 2 (percorso kbase): redirect della UAF sulla regione spruzzata — DIMOSTRATO sessione 5
  • Stage 2b: controllo degli slot byte-grezzi (churn dello stamp xattr) → scrittura unlink
  • — hijack dell'hook nf LOCAL_OUT, catena a 2 pacchetti: azzera , riscrive la fake entry in . , SELinux Permissive.

Risultati chiave

Dispositivo / firmware

  • Modello KFMUWI, device mustang, Fire OS 7.3.3.1 PS7331.4463N/0031575863040
  • Kernel 4.9.117-g08fe75b-dirty, compilato Sat May 3 01:25:15 UTC 2025 (Linaro GCC 6.3-2017.05)
  • Amazon ha silenziosamente riemesso la 7.3.3.1 a maggio 2025 (nuovo incrementale, stessa stringa di versione)
  • Revisione del Bootrom post-2020: corto verso GND su eMMC CMD dà solo il preloader (patchato)
  • /dev/kb, /dev/dkb (partizioni di kernel-backup Amazon) root:drmrpc 0660 — bloccati

Perché CVE-2022-38181 si applica

  • Driver: mali_kbase r26p0-01rel0 (Midgard, Mali-T720), all'interno dell'intervallo affetto NVD r4p0–r31p0
  • La ricompilazione di maggio 2025 di Amazon ha incluso il bug del 2018 alla lettera — nessun backport
  • Codice vulnerabile esatto, verificato dal sorgente:
    • mali_kbase_mem.c:2721 kbase_jit_destroy_worker libera la regione, non azzera mai kctx->jit_alloc[id]
    • mali_kbase_softjobs.c:1270 kbase_jit_free_finish dereferenzia il jit_alloc[ids[j]] obsoleto
    • mali_kbase_mem.c:3138 kbase_jit_backing_lost → percorso di destroy (si attiva durante il reclaim)

Clima di sfruttamento (tutto verificato dal dump della config live + vmlinux OTA)

  • armv7 32-bit, non-LPAE → nessun KASLR (kernel a VA fisso 0xc0008000 / PA 0x40080000)
  • Nessun ARM_SW_DOMAIN_PAN → ret2usr praticabile; CONFIG_PANIC_ON_OOPS=y (tentativi falliti = riavvio)
  • Nessun SLAB_FREELIST_RANDOM/HARDENED, nessun CONFIG_USER_NS/USERFAULTFD/NF_TABLES
  • CONFIG_MODULES=y, nessun STATIC_USERMODEHELPER → sovrascrittura di modprobe_path = root
  • 1 GB di RAM → direct reclaim (necessario per l'eviction) facilmente raggiungibile; pressione >~1 GB manda il kernel in panic da sola (bug lowmem/OOM non correlato) — mantenere lo spray ≤ 900 MB, usare ~700 MB

Stranezze UAPI incontrate durante lo sviluppo del PoC (r26p0, _IOC_TYPE 0x80)

  • L'union di MEM_ALLOC è di 32 byte (in ha 4 × u64 incl. extent)
  • i flag devono includere BASE_MEM_PROT_GPU_RD|WR (bit 2|3), non i legacy R|W
  • mmap della tracking-page richiesto prima di qualsiasi alloc: mmap(fd, offset=3<<12, PROT_NONE)
  • lo stride di JOB_SUBMIT deve essere uguale a sizeof(base_jd_atom_v2) = 48 (base_jd_prio/base_jd_dep_type sono typedef u8)
  • JIT: MEM_JIT_INIT (nr 14, struct v2), alloc/free sono soft job via JOB_SUBMIT (BASE_JD_REQ_SOFT_JIT_ALLOC=0x209, ...FREE=0x20a; =ptr utente, =conteggio)

Differenziale dello stage 1 (prova che il bug si attiva)

Il panic avviene durante l'eviction stessa (evictable_reclaim_scan_objects → backing_lost → destroy worker) — i riferimenti pendenti (jit_alloc[], lista di eviction) vengono percorsi prima che si sottometta mai JIT_FREE. Lo stage 2 deve vincere la corsa: riallocare il kbase_va_region liberato con il nostro spray di MEM_ALLOC mentre la pressione è ancora in corso.

Artefatti

  • poc/stage2.c — exploit dello stage 2 (modalità: step/uaf/spstep/spfree/spray/keys) — spray 700 = esecuzione completa dell'oracolo; sopravvive e si mette in pausa (kill per pulire)
  • poc/mustang_jit_uaf.c — PoC dello stage 1 (modalità: jit N / control N / pressure N)
  • poc/build.sh — cross-build con zig (static musl armv7)
  • kernel/vmlinux — simboli recuperati dalla build OTA esatta (vmlinux-to-elf)

Build & run```

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:~
## Riferimenti

- Avviso GHSL-2022-054: https://securitylab.github.com/advisories/GHSL-2022-054_Arm_Mali/
- Writeup dell'exploit di Mo sul Pixel 6: https://github.blog/2023-01-23-pwning-the-all-google-phone-with-a-non-google-bug/
- Precedente del Fire HD 10 (trona), stessa famiglia di bug: ericpardee.github.io/fire-hd-ownership
- Portale OSS di Amazon: amazon.com gp/help/customer/display.html nodeId=200203720
- Thread di sblocco su XDA (morto per questa rev hw): xdaforums.com/t/fire-7-2019-mustang-unbrick-downgrade-unlock-root.3944365/


## Addendum Sessione 3 (indagine approfondita sui syscall-stamp)

Profondità copy-source misurate (assolute vs syscall-entry sp0; il waiter copre -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 (MIGLIORE — 0x1c sotto waiter+0x00)**
- poll entries @ -0x3e0 (interamente sotto; lato sbagliato)

Esclusi in questa sessione:
- catena io_submit troppo superficiale (~-0x130) | semtimedop: CONFIG_SYSVIPC=n (stub)
- configfs montato ma ZERO sottosistemi registrati (nessuna destinazione mkdir)
- /sys/kernel/debug, /config: SELinux nega per la shell
- /proc/sys/kernel: getdents funziona (29 voci elencate), solo pid_max APRIBILE;
  letture di kptr_restrict/hotplug/hostname/domainname tutte negate
- Il valore scritto è SEMPRE waiter+0 (indirizzo kernel stack, eseguibile, shellcode a
  +0x1c): rb_link_node *link = node, insert_color scrive parent-color — nessuna
  variante a valore controllato possibile senza stamping dei campi dell'albero (gap -0x1d8..-0x1bc)
- I campi di dispatch a doppio deref leggono *(waiter+0)=1, *(waiter+4/+8)=0 — BLX 1/0
  (nf_hooks, net_families, inet[6]_protos, seq_file->op tutti morti)
- timer_list.function@+0xc e work_struct.func@+0xc leggerebbero *(waiter+0xc) =
  pi_tree self-ptr = waiter+0xc ESEGUIBILE — ma nessun percorso accoda waiter+0 come
  timer/work (corruzione di link / nessuna sorgente di accodamento indiretta)
- Tree-root-nonzero (slot handler sysctl) = pointer-chase deterministico attraverso
  .text come rb-tree; termina su una parola zero — simulabile offline, ma atterrare
  in uno slot scrivibile utile è implausibile

Piste rimanenti per la sessione 4:
1. percorsi ioctl profondi: copia dev_ioctl ifreq (40B dati utente) — misurare
   la profondità della catena SyS_ioctl→sock_ioctl→dev_ioctl vs -0x1d8
2. Qualsiasi altra copia 0x1c più profonda di sendmmsg (nulla trovato finora)
3. Se la caccia alla superficie di stamp fallisce: riconsiderare costrutti
   walk-chained o cercare classi di slot scrivibili-zero-chiamati non ancora enumerate


## SESSIONE 4 — LE DUE SVOLTE

### 1. rtmutex_common.h di MTK è l'intero mistero
MTK ha sostituito la rt_mutex_top_waiter NULL-safe di upstream con:```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

NO NULL CHECK + BUG_ON. Ogni anchor tutto-a-zero muore a *(NULL+0x1c); gli anchor spazzatura muoiono all'udf. LA WALK RICHIEDE: lock->waiters_leftmost (lock+8) deve puntare a un finto waiter W (scrivibile) con W->lock (+0x1c) == lock.

Flusso della walk completamente mappato (rt_mutex_adjust_prio_chain @ 0xc0189a58):

  • 9b44-9b54: retry head; pi_blocked_on==NULL → uscita pulita ret 0
  • 9abc-9adc: orig_waiter==NULL → salta i controlli pi_waiters (adjust_pi passa sempre NULL)
  • 9b10-9b28: controllo prio (prio==task->prio + MIN → esci 9b58)
  • 9b2c-9b38: trylock(lock+0) — ticket; fallisce → retry loop con bailout del contatore (9a90-9aa8, limite @ *(0xc11189c8))
  • 9ba4-9bc0: controlli deadlock
  • 9bcc-9bd8: IL BUG_ON (leftmost→W→W->lock==lock o muori)
  • 9bdc-9c04: dequeue (albero residuo RB_CLEAR_NODE'd = VUOTO → skip sicuro), scrittura prio/deadline
  • 9c04 bl: rt_mutex_enqueue → *link = waiter+0 a lock+4 ← LA SCRITTURA
  • 9c3c+: owner==NULL → percorso di uscita pulita

2. Il leak dell'indirizzo kernel-stack (uccide il requisito address-free)

/proc/self/task//stat campo 28 (kstkesp) restituisce il VERO SP kernel per thread bloccati in syscall da contesto SHELL (verificato: osservati valori non nulli).

  • waiter si blocca in read(blocking_pipe) → stat → kstkesp
  • base dello stack = kstkesp & ~0x1fff (arm32 THREAD_SIZE=8192)
  • indirizzo assoluto rt_waiter = base + delta fisso (calcolabile: sp0 = base+0x2000-0x48 pt_regs; waiter = sp0-0x1d8)
  • TUTTI i valori di stamp auto-referenziali diventano calcolabili!

Stamp completo auto-consistente (dopo il leak):

  • L = waiter+0x24 (finto lock NELLA WINDOW — tutte e 4 le word controllabili)
  • iov[0].base (waiter+0x1c lock) = L
  • iov[0].len (waiter+0x20 prio) = 1 (≠139)
  • W = waiter+0x1c; stamp *(W+0x1c) = *(waiter+0x38) = L (BUG_ON passa)
  • lock+0 (waiter+0x24) = 0; lock+4 (waiter+0x28) = 0 (la scrittura atterra qui); lock+8 (waiter+0x2c) = W; lock+0xc (waiter+0x30) = 0 (owner NULL)

Stato dell'oracle

  • crash durante la walk = walk eseguita (sonda dead-lock: crash deterministico, codice pulito)
  • walk pulita + nessuna scrittura = trylock-fail retry-bailout (anchor kptr: word runtime non nulla)
  • Tutto è ora deterministico post-pollution-fix.

TODO prossima sessione

  1. Implementare il leak: waiter si blocca sulla pipe, main legge stat, calcola base
  2. Stampare la window auto-consistente, far partire la walk → completamento senza crash = scrittura provata
  3. Weaponize: la scrittura atterra sempre a lock+4 (rb_link_node) — lock deve vivere nella window (unica memoria completamente controllata), quindi ricerca sulla selezione del target: o trovare il trucco called-slot-in-window, o costruzione a due stadi.

SESSION 4 STATO FINALE — IL MURO (caratterizzato con precisione)

Il quadro completo

La walk parte in modo deterministico (sonda dead-lock: crash ogni volta, codice pulito). La scrittura non può atterrare a causa di una coincidenza tripla di kernel-hardening:

  1. Variante MTK rtmutex BUG_ON: lock+8 (leftmost) DEVE puntare a W con *(W+0x1c)==lock. Anchor tutti-a-zero/spazzatura muoiono. Nessun pattern statico auto-referenziale esiste (6571 candidati scansionati, 0 hit). Puntatori runtime sconosciuti.
  2. Nessuna disclosure di indirizzo kernel dalla shell:
    • kstkesp su arm32 = USER SP (task_pt_regs->ARM_sp) — non kernel stack. MORTO.
    • dmesg/pstore/pagetypeinfo/kallsyms/stack — tutti negati.
    • kptr_restrict=1 a runtime (anche le word anchor fops sono runtime-nonzero — la run anchor kptr è uscita via trylock-fail retry-bailout, non trylock success)
  3. Tabelle fops in rodata: trylock strex abortisce (crash dello sweep della sessione-3).

La stamp window (waiter+0x1c..0x5b) è l'unica memoria controllata+a-contenuto-noto, ma il suo INDIRIZZO è l'incognita di cui abbiamo bisogno. Le costruzioni auto-referenziali richiedono tutte di stampare un indirizzo kernel come costante — circolare senza un leak.

Confronto con gitchw (perché la loro scrittura ARM32 ha funzionato, la nostra non ancora)

Il loro kernel 5.4 ha rtmutex_top_waiter UPSTREAM (NULL-safe: if (!leftmost) return NULL) — gli anchor ad albero vuoto sopravvivono, la loro scrittura è atterrata su null_fops (scrivibile sul loro kernel). Persino LORO sono bloccati al dispatch ("ioctl reboot"). L'albero MTK 4.9.117 di Mustang ha la variante BUG_ON — i tablet Fire OS 8 (GhostLock-5.10) hanno avuto successo perché i loro kernel 5.10 sono in stile upstream.

Fatti verificati della sessione-4

  • Il retry loop della walk ha un bailout del contatore (limite @ *(0xc11189c8)); trylock-fail su word anchor runtime-nonzero → uscita pulita retry-bailout (run anchor kptr)
  • Anche il percorso no-requeue (9ce4, walk COMPLETA) dereferenzia leftmost a 9d64 — nessuna via di fuga
  • Esistono auto-puntatori RB_CLEAR_NODE come residui nella window (waiter+0 e +0xc contengono i propri indirizzi) ma nessun confronto di check li usa in modo da evitare di stampare indirizzi noti
  • Candidati real-mutex (il chain mutex ha un waiter vivo = BUG_ON passerebbe) — ma &chain_mutex è un indirizzo heap, irraggiungibile senza leak

OPZIONI PROSSIMA SESSIONE (in ordine di priorità)

  1. Caccia ai kernel-pointer in logcat: gli HAL/daemon Amazon sono chiacchieroni; qualsiasi kernel pointer loggato (anche stale) sblocca la costruzione. Economico da testare.
  2. Comportamento di /proc/net %pK su QUESTA build: alcuni alberi 4.9 stampano puntatori non hashati in /proc/net/tcp,udp,unix per lettori non privilegiati. Testare dal vivo.
  3. Percorsi di thread-exit su pi_blocked_on pendente (one-shot, deref diversi).
  4. Rivedere il bug kbase JIT accantonato con la conoscenza 4.9 accumulata.

ADDENDUM SESSIONE 4 — CACCIA AL LEAK: ESAURITA (definitivo)

Testati e morti dal dominio shell:

  • /proc/net/{tcp,unix,packet,netlink,ptype}: %pK-hashati a 00000000 (kptr_restrict=1)
  • /proc/timer_list: LEGGIBILE ma puntatori %pK-azzerati (simboli visibili, nessun indirizzo)
  • logcat: nessun kernel pointer nel chatter Amazon/wpa
  • kstkesp (stat f28): USER SP su arm32 (task_pt_regs->ARM_sp)
  • Nodi MTK (/proc/ged, mtk_cmdq_debug, mtktz, ptp, chip, aed, driver/*): tutti negati da SELinux
  • /proc/{iomem,vmstat,kmsg,keys,crypto,slabs...}: negati
  • /sys/kernel/notes: negato
  • CONFIG_VECTORS_BASE=0xffff0000 (high vectors — NULL+0x1c faulta)
  • CONFIG_KUSER_HELPERS=y (kuser a 0xffff0000, non page 0)

CONCLUSIONE: GhostLock su mustang richiede una disclosure di indirizzo kernel che questo kernel non espone al dominio shell. Il finto lock auto-referenziale non può essere costruito senza di essa.

PUNTO DECISIONALE

(a) Grind boot-deterministico: reboot → calibrare l'indirizzo dello stack via crash oracle (~20-30 reboot), verificare la riproducibilità. Long shot — l'allocazione dello stack dei thread a late-boot è improbabile che sia stabile. (b) PIVOT di nuovo su kbase CVE-2022-38181 con gli asset accumulati: vmlinux exact-build + sorgente completo + toolchain + disciplina di trace O_SYNC + profonda conoscenza 4.9. Il blocco originale (panic del destroy-worker durante l'eviction JIT) è un problema di spray-timing, ora meglio compreso. (c) Fermarsi all'onesto ~45%: trigger provato, walk mappata all'istruzione, scrittura bloccata da MTK BUG_ON + no-leak.

Raccomandato: (b) — il bug kbase è verificato-presente in questo sorgente esatto, aveva un trigger funzionante, e il suo blocco è meccanico, non architetturale.

SESSIONE 5 — STAGE 2 PROVATO (opzione b eseguita)

Ri-diagnosi: il "panic destroy incondizionato" non è mai esistito

La modalità step (alloc id=1 → DONT_NEED → pressione 700MB → MEM_QUERY, NESSUN free) sopravvive: query=-1 (regione liberata dal destroy worker, rbtree-pulito). Il percorso del worker è byte-identico al flusso legale JIT_FREE-sotto-pressione. Il crash della sessione-1 era sempre il deref pendente di JIT_FREE; la sua riga di log era persa perché il panic uccide adbd a metà flush. Verificato altre due volte con la modalità uaf (free nudo → panic, stesso cutoff del log). Il flusso GHSL-2022-054 è completamente live su questa build.

Inventario completo delle primitive (disassembly vmlinux esatto)

kbase_jit_free(kctx, reg) @ 0xc058495c con finto reg completamente controllato:

  • reg->cpu_alloc NULL → backed size 0 → blocco trim saltato (0xc0584978)
  • decremento bin: kctx+0x147dd (byte) + kctx+0x147de+bin_id (byte)
  • mark_reclaim(reg->gpu_alloc) @ 0xc059b158: catena K=*(gpu_alloc+0x38) → *(K+0x1429c)==0 salta mm-atomics → atomic_sub nents@K+0x141c8, D=*(K+4) → atomic_sub nents@D+0x538. Con nents=0 tutte le scritture sono store no-op (strex dello stesso valore).
  • reg->flags |= 0x100000 (scrittura nel finto, benigna)
  • shrink_cpu_mapping esce presto quando new==old (nents=0 → return)
  • list_add(gpu_alloc->evict_node, &kctx->evict_list): testa evict_list @ kctx+0x1427c; scrive in gpu_alloc+0x18/0x1c (deve essere scrivibile)
  • Il percorso WARN (0xc0584bd4) è non fatale (nessun panic_on_warn) e CONTINUA
  • UNLINK @ 0xc0584b08/b0c: r3=*(reg+0x3c) prev, r2=*(reg+0x38) next → *(next+4)=prev; *(prev+0)=next — due write-what-where arbitrarie, poi relink di reg+0x38 in jit_pool_head @ kctx+0x148e8

Catena statica fake-gpu_alloc (scansione offline vmlinux, /tmp/opencode/scan_s.py)

9 candidati; S=0xc118b7ec (dati xfrm, dormienti su questo dispositivo): *(S+8)=0 (nents), *(S+0x18)=S+0x18 (evict_node vuoto → nessun WARN), K=*(S+0x38)=0xc118b820 → *(K+0x1429c)=0, K/D+0x141c8/+0x538 tutti in dati scrivibili. Target oracle preparati: init_uts_ns.name.nodename=0xc110d561 ("(none)", leggibile via uname), scratch P=0xc118bd58 (zeri xfrm). Evitare S=0xc111cba4 (adiacente a tracepoint). CONFIG_DEBUG_RODATA=y → tutti i target di scrittura devono essere in .data/.bss (bss 0xc11d9000-0xc12d9000).

Ingegnerizzazione dello spray (cosa ha funzionato, cosa no)

  • add_key (CONFIG_KEYS=y): negato da SELinux per la shell. Morto.
  • buffer valore setxattr: kvmalloc(96)+copy_from_user avviene PRIMA del check SELinux → la dance di alloc è SELinux-proof anche quando la chiamata fallisce; transitorio (liberato a fine syscall), i byte persistono a +4..95 (il ptr freelist clobbera +0..3 = rblink, non usato da kbase_jit_free)
  • kbase_va_region stesso: kzalloc(72) → kmalloc-96! MEM_ALLOC(va=0x40, commit=0x10) mette SOLO la region in kmalloc-96 (phy alloc → 384) → tipo di reclaim deterministico. Una vittima real-region fa completare kbase_jit_free attraverso stato completamente legale (jit_node vuoto → self-unlink).
  • Spray sequenziale post-pressione: MANCA SEMPRE — il worker libera lo slot a metà pressione in slab parziali (SLUB: free a slab non attivo ≠ cpu freelist); sotto pressione le nostre alloc falliscono → volume netto zero → nessuna rotazione
  • Sprayer pinnati + cap: ancora miss (cap 512 esaurito prima dell'eviction; il worker può girare su qualsiasi cpu)
  • VINCITORE: spray commit_pages=0 — nessuna pagina fisica → i MEM_ALLOC riescono attraverso l'intera tempesta di pressione → ~6000 allocazioni nette → rotazione della partial-list garantita. 8 thread (2/cpu, cpu 0-3 hardcoded — /proc/cpuinfo è filtrato per la shell a 1 core, usare Cpus_allowed_list) + figlio di pressione pinnato a cpu0 + batch di retention da 16 alloc dopo il join.
  • trappola query(jit_va): post-reclaim, le region dello spray riusano la VA liberata nella zona custom → query=0 è ambiguo (live-original vs spray-covering-VA)

ORACLE HIT — redirect verificato dalla macchina

run spray 700 2026-09-11: 5895 region spruzzate durante la pressione, JIT_FREE su id=1 pendente completato su una region reclamata, poi JIT_ALLOC(0x40, bin 0) ha percorso jit_pool_head e restituito la VA della region spruzzata #4251 (0x142701000) — esattamente la region che il puntatore pendente ha consumato. Kill del processo dopo: teardown kctx pulito, nessun crash. Stage 2 completo: redirect UAF deterministico con tipo di oggetto + contenuti controllati.

Piano Stage 3 (unlink raw-byte)

Il reclaim di tipo region dà sopravvivenza alla dance legale ma jit_node è INIT'd self → nessuna primitiva di unlink. Servono byte grezzi a +0x38/+0x3c:

  1. stamping xattr: alternare burst di alloc region (volume netto → rotazione slab) con tempeste xattr (stampare ogni slot head, i byte persistono post-free) → finestra quieta → deref
  2. oppure sendmsg cmsgs pinnati (optmem_max=10240 → ~106 × 96B tenuti)
  3. poi: W1 *(N+4)=P con P=pagina shellcode userland (nessun PAN!) — candidati: fops const sono .rodata (DEBUG_RODATA) → targetare fn ptr non-const in .data, o la testa della lista formats di binfmt, o proc_handler sysctl (verificare la scrivibilità della tabella). fallback: modprobe_path via scritture concatenate a byte (i valori devono essere indirizzi scrivibili — usare target a forma di puntatore)
  4. no-KASLR + vmlinux esatto: prepare_kernel_cred 0xc0149e3c, commit_creds 0xc014993c

SESSIONE 5B — STAGE 3: arma costruita, race di reclaim non ancora vinta

Fatto

  • Arma Stage-3 completa e preparata (poc/stage3.c):
    • Target: kern_table[pid_max].proc_handler @ 0xc1113f40 (scrivibile .data, verificato via scansione string-pointer + handler == proc_dointvec_minmax)
    • N = entry dello shellcode 0x11111112 (mmap 0x11111000; W1 clobbera entry+4, saltato da b +8; W2 scrive N a P = campo handler)
    • shellcode ring0 arm32 codificato a mano: prepare_kernel_cred(0) + commit_creds + ret 0; trigger = read /proc/sys/kernel/pid_max (leggibile dalla shell); gira nel contesto del proprio task → i creds si applicano a noi
    • modalità oracle benigna scrive uts nodename (0xc110d561, unaligned ok)
  • Selezione primitiva di spray:
    • pin sendmsg NETLINK_USERSOCK (copia msg_control in kmalloc-96, tenuta mentre bloccato, mai parsata): negato da SELinux (socket create EACCES)
    • sendmsg unix/UDP: il parsing dei cmsg avvelena i byte del payload ✗
    • eventi inotify: inotify_handle_event fa kmalloc di name_len+0x1d, byte del nome (completamente controllati, vincolo NUL/slash-free) a event+0x1c; name_len=60 → kmalloc-96; accodati → tenuti; 4 istanze → 4 eventi per rename; SELinux-OK dalla shell. Finto ridisegnato NUL-free: cpu_alloc punta a S (nents@S+8 = 0 → stessa semantica di NULL)
  • Catena meccanica validata end-to-end (drain4, run senza eviction): 20K eventi drenati + 13.5K rename trailing multi-cpu + JIT_FREE + oracle + pausa, tutto pulito. Il log O_SYNC (/data/local/tmp/s3.log) sopravvive ai panic — forense esatta del punto di crash.

Tentativi di reclaim sullo slot liberato (tutti mancati finora)

varianterisultato

Ipotesi di lavoro: race storm-junk — tra il destroy worker che libera lo slot (a metà tempesta) e il child-kill/quiet, l'attività di reclaim residua prende l'unico-slot-libero dello slab vittima con byte non-payload. Lo spray di region (stage 2) vince perché alloca continuamente DURANTE la tempesta; i rename non possono.

Insidie incontrate

  • bug del toggle spray: la sorgente del rename deve essere il nome del payload (era il nome temp → ENOENT dopo 2 ondate → solo 512 eventi in tutto)
  • l'hostname del dispositivo è "localhost"/varia — l'oracle confronta before/after
  • st3 in pausa + pkill → dispositivo WEDGE (teardown con 33K eventi?!) — uccidere i processi in pausa solo via reboot; secondo hard-wedge della sessione
  • /proc/cpuinfo mostra 1 cpu alla shell; usare Cpus_allowed_list

Prossime mosse (in ordine di priorità)

  1. drain4 @ 500MB (soglia di eviction confermata lì), poll 1ms, kill istantaneo, trailing 4-cpu × 3200 — restringere la finestra della tempesta
  2. churn di xattr-stamp (l'alloc-copy di setxattr avviene PRIMA del check SELinux — SELinux-proof) concorrente con la tempesta + rotazione region, terminare con stamp
  3. accettare il region-reclaim (provato) + trovare una primitiva di secondo stadio sullo stato region-vittima (analisi double jit_free negativa finora)

SESSIONE 5C — il blocco, caratterizzato con precisione

Risultati empirici di questa sessione

  • drain4@500 (poll 1ms, kill istantaneo, +4.5K rename): ancora crash al deref
  • overlap trailing con kill (drain5): crash PRIMA (i rename durante la tempesta di kill-recovery colpiscono un fault a livello di sistema) — overlap abbandonato
  • ESPERIMENTO DI ISOLAMENTO (modalità iso): timing identico, trailing con REGION commit-0 + oracle di riuso pool stage-2 → ORACLE HIT → timing/raggiungibilità sono OK; gli eventi sono il problema
  • eventi con mask-ciclate (rotazione MOVED_TO/CREATE/DELETE per sconfiggere inotify_merge): ancora crash al deref
  • CONFIG_MEMCG=n → eventi e region condividono UN SOLO kmalloc-96 (teoria memcg morta); inotify_merge confronta anche i nomi (teoria merge morta — i nostri nomi toggle non si sono mai fusi; gli eventi erano accodati e tenuti per tutto il tempo)
  • /proc/slabinfo assente; /proc/self/pagemap leggibile ma PFN-azzerato (masking post-4.0, nessun CAP_SYS_ADMIN)

IL VERO BLOCCO (due parti, entrambe provate)

  1. Il soft-job finish gira in contesto WORKER del job-scheduler kbase (jd_run_atom ← dispatch js, mali_kbase_jd.c:81-112/677), non inline nella submit ioctl → current->mm è quello di un kernel thread → l'elegante design "puntare i puntatori del finto nel nostro mmap userspace" (nessun PAN!) FAULT in modo non deterministico. 5/5 crash con un finto altrimenti perfetto.
  2. Quindi gpu_alloc deve puntare a memoria KERNEL con una catena runtime sopravvivibile: K=*(S+0x38) leggibile, *(K+0x1429c)==0 a runtime (salta mm-atomics), K+0x141c8 scrivibile, D=*(K+4) → D+0x538 scrivibile, nents *(S+8) preferibilmente 0. I 9 candidati S offline erano validati contro i byte del FILE — il drift runtime (init xfrm/tracepoint) li rende non verificati. Una catena sbagliata = crash = reboot (~3 min di ciclo).

Piani sessione-6 (entrambi completamente specificati)

A. Finto physmap-spray (ret2dir, classico arm32 no-PAN): spruzzare ~450MB di pagine utente ognuna contenente il pattern finto cotto per UN indirizzo indovinato G (G&0xfff = 0x141 per byte nome NUL-free; S=G; K=G-0x141b4 così K+4 atterra in-page; K+0x141c8/+0x1429c → G+0x10/+0xd4 in-page; D=G+0x300; gli store sub-0 spuri colpiscono RAM mappata casuale - innocui con nents=0). Lo spray funge anche da pressione di eviction (anon sporco = unevictable → servono solo ~100-200MB extra). Probabilità ≈ 45% (page hit) × ~50% (la word K+0x1429c estranea è zero... se K+0x1429c è tenuta in-page secondo il layout sopra, probabilità = solo page-hit). Miss = crash = reboot, riprova. B. Brute-force dei 9 candidati S statici (xfrm 0xc118b7ec per primo, adiacente a tracepoint 0xc111cba4 secondo...): 1 reboot per candidato, payload oracle benigno prima, arma al hit. C. G esatto basato su pagemap (morto: PFN mascherati) — non rivisitare.

SESSIONE 5D — finto physmap costruito; G-sweep 0/3; confondenti eliminati

Stabilito in questa sessione (tutto verificato su binario/dispositivo)

  • Identità della cache CONFERMATA uguale: region = kmem_cache_alloc_trace( kmalloc_caches[7], GFP|0x8000, 0x48) [disasm kbase_alloc_free_region]; event = __kmalloc(89, GFP) → kmalloc-96. Eventi e region POSSONO condividere la cache della vittima. (MEMCG off; singolo set di cache.)
  • coda inotify: ≥5000 eventi tenuti, nessun overflow, nessun collasso da merge (run qmeas 1000 & 5000) — lo spray di eventi persiste
  • iso2 (spray physmap + trailing region + oracle): HIT — lo spray physmap NON rompe il reclaim delle region; macchinario sano
  • eventi vs reclaim region: region 3/3 (iso, iso2, stage-2), eventi 0/10 MA le 3 run pmap sono spiegate da G-miss a probabilità 0.3-0.45 ciascuna (P(3 miss|eventi-funzionano) ≈ 0.2-0.3 — non conclusivo)
  • modalità mix confondata: spray 480MB soffoca i rename post-kill (+0); 350MB OK (+4452); thread rename falliti in spin disturbano anche il reclaim delle region (mix crash, iso2 pulito)
  • query_commit restituisce -1 anche su EINVAL — strumentato; EINVAL osservato = miss di lookup rbtree = genuinamente liberato ✓ (non un falso positivo)

Design pmap attuale (in stage3.c, modalità pmap/mix/iso2)

  • G cotto in ogni pagina spruzzata (offset 0x2a4): nents@+0x2ac=0, evict self@+0x2bc/0x2c0, K=G+0x100@+0x2dc, D=G+0x200@+0x3a8; K+0x141c8/-0x1429c atterrano ~20 pagine sopra (store sub-0 innocuo / la lettura deve essere 0-o-valida). PM_SPRAY_MB 350, sweep G provati: c2a412a4, c2f4b2a4, c2a7d2a4 — tutti crash al deref
  • sanity del range physmap: RAM 1GB → physmap ~0xc0000000-0xc3fffffff; i carveout MTK (GPU/M4U/secure) possono occupare chunk — G landmine

TODO sessione-6 (in ordine di priorità)

  1. Estrarre il sorgente kernel completo (tarball 2.2GB in ~/Desktop/amazon-mustang/ — platform.tar): ottenere arch/arm + mm/ + drivers/of + mapping riservati MTK → calcolare la mappa dei carveout → targettare G in subrange physmap RAM-verificate; verificare anche la geometria della cache kmalloc (ARCH_KMALLOC_MINALIGN!) e il bit GFP 0x8000
  2. Sweep G con ipotesi informate dal placement (reboot multipli, variare la dimensione dello spray per decorrelare)
  3. Se lo sweep G si esaurisce: riconsiderare payload multi-G o candidati S da statici runtime-plausibili (i campi puntatore adiacenti a uts hanno fallito: NULL-K)

SESSIONE 6 — scoperta zram, estrazione sorgente, questione eventi ancora aperta### Sorgente kernel completa ora estratta

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

  • bit GFP 0x8000 = ___GFP_ZERO (solo kzalloc; nessuna suddivisione cache)
  • kmalloc-96 è una vera cache da 96 byte (nessun HWCACHE_ALIGN sulle cache kmalloc)
  • mustang.dtsi: nodo memoria 0x40000000/512MB — esteso dal preloader (il dispositivo mostra MemTotal 977MB); CONFIG_VMSPLIT_3G, HIGHMEM=y
  • zram0 ATTIVO (SwapCached > 0) → "dirty anon = unevictable" era SBAGLIATO: lo spray physmap viene swappato sotto pressione → l'alias G diventa stale → aggiunto physmap_retouch() dopo il kill (fa fault-in di tutte le pagine dello spray prima del trailing/deref)

Esecuzioni di questa sessione (tutte O_SYNC registrate, ~6 riavvii)

Verdetto sugli eventi (Bayesiano, onesto)

Le regions reclaim: 3/3. Eventi: 0/~12 tentativi inclusi 28K allocazioni esclusive con vantaggio iniziale e fake corretto per costruzione. Se gli eventi reclaim con p_hit(G)≈0.35, cinque miss pmap ≈ 11.6% — possibile ma ora improbabile (~10-15%). O gli eventi strutturalmente non possono occupare questo slot (motivo ignoto — stessa cache, stesso contesto, stesso timing) o le nostre ipotesi su G mancano sistematicamente (skew al confine highmem, posizionamento dell'allocatore).

Albero decisionale della sessione 7

  1. Risolvere prima G (economico, nessun exploit): strumentare temporaneamente il flusso ISO2 — region trailing + oracle — ma rendere l'evento PAYLOAD un fake physmap e verificare se QUALSIASI G in un intervallo spazzato produce un cambio di nodename senza regions in race (puro pmap, sweep di G su ~0xc1500000-0xc2a00000 centro lowmem, 1 G per riavvio, 4-5 riavvii)
  2. Se lo sweep di G si esaurisce → eventi dichiarati morti → caccia ad allocatori kmalloc-96 raw-byte alternativi raggiungibili dalla shell (audit: seq_file, tty ldisc, fdtable, sk_filter (bloccato: code-field a +0x38), netlink nlmsg (skb ✗), keys (negato)) — o rivisitare primitive region a due stadi (analisi finora negativa)
  3. Considerare in seguito lo sblocco UART/ramoops via root; non inseguire

SESSIONE 7 — bombe da cmdline; mistero degli eventi ora precisamente delimitato

CONFIG_CMDLINE di mustang_defconfig (verità di base per il posizionamento):

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

  1. vmalloc=496M → physmap = 0xc0008000..~0xc2080000 SOLO (i 520MB bassi della RAM) — TUTTE le precedenti ipotesi su G (0xc2a4xxxx+) erano nello SPAZIO VMALLOC. Ogni conclusione di "G-miss" dalle sessioni 5D/6 è invalidata; l'interpretazione del crash regge ma lo sweep era puntato sulla mappa sbagliata.
  2. slub_max_order=0: tutte le pagine slab order-0
  3. KMALLOC_MIN_SIZE = ARCH_KMALLOC_MINALIGN = 64 (L1_CACHE_SHIFT 6) → i casi speciali di kmalloc_index() per 96/192 sono DISABILITATI → region kzalloc(0x48=72) → caches[7]; event __kmalloc(89) → caches[7] (disasm + include/linux/slab.h:287 verificati) — ENTRAMBI nella cache unificata da 128 byte "kmalloc-128/96". Identità della cache: RI-CONFERMATA uguale.
  4. zram attivo → sostituito lo spray anon physmap con kbm_spray: 160 × 2MB regioni kbase MEM_ALLOC (GFP_KERNEL → ZONE_NORMAL → solo lowmem, pinned → immune a zram), pattern scritto via mmap CPU — intervallo corretto, ~65% di copertura lowmem

Esecuzioni (O_SYNC registrate)

  • kbm + G=0xc16412a4: crash al deref (+4831 rinomine)
  • kbm + G=0xc1c4b2a4 @200MB: NESSUNA eviction (query=16 — la calibrazione della pressione varia con lo spray pinned; free legale, sopravvissuto)
  • kbm + G=0xc1c4b2a4 @400MB: eviction ✓, +4053 rinomine, crash al deref
  • Record G in intervallo valido: 0/2. Se gli eventi funzionano con ~65% di copertura: P(2 miss) ≈ 12%. Domanda ancora aperta ma più ristretta che mai.

La domanda, forma finale

Le regions prendono lo slot vittima 3/3; gli eventi 0/13. Stessa cache (provato a livello di sorgente + disasm), stesso contesto di processo, stesse cpu pinned, stesso timing post-kill, migliaia di allocazioni con vantaggio esclusivo iniziale. Meccanismo ignoto. Sospetti rimanenti: correlazione rate/frequenza di allocazione con la rotazione della partial-list (ioctl delle region ~1ms di distanza vs rinomine degli eventi ~100µs di distanza — direzioni opposte?), o dettagli dell'ordinamento della freelist SLUB sotto slub_max_order=0 che favoriscono... poco chiaro.

TODO della sessione 8

  1. Esperimento di livello strumentale: DUE varianti di payload evento con valori N/P DIVERSI alternati (due set di nomi) — se il nodename cambia mai, l'ULTIMO vincitore è identificato; sweep di G in [0xc1200000..0xc2000000] con kbm spray, budget di 3-4 riavvii
  2. Se ancora 0/N: abbandonare gli eventi. Alternative in ordine di rango: a. array pipe_buf via F_SETPIPE_SZ(4096 → 1 buf? no — 16 buf = kcalloc(16, 28)=448→512 ✗) — morto b. audit fs/notify + fs per altri oggetti kmalloc-128 che trasportano nome/dati (eventi fanotify? mq off; fanotify richiede gruppi...) c. buffer seq_file (kmalloc(PAGE_SIZE) ✗) d. sock filter (collisione code-field a +0x38 ✗) e. accettare il reclaim di tipo region + concatenare un SECONDO bug/tecnica
  3. Riesaminare PERCHÉ le regions vincono — magari strumentare via più jit id: N slot dangling, race region-vs-evento per slot, l'oracle rileva quale spray ha preso quale slot → impronta statistica del meccanismo

SESSIONE 8 — SCRITTURA ARBITRARIA RAGGIUNTA SUL DISPOSITIVO; resta il mistero del dispatch

IL TRAGUARDO```

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

root@kitploit:~
**L'intera catena raw-byte funziona sul dispositivo live**: l'evento recupera lo
slot della regione liberata → kbase_jit_free dereferenzia il nostro fake (S=pagina physmap dallo
spray kbm, G=0xc154b2a4) → l'unlink esegue le nostre due scritture.
Verificato ripetutamente con il payload benigno (scrittura nodename).

### Catena di scoperte della sessione
1. le pagine kbm erano GFP_HIGHUSER → HIGHMEM, invisibili al physmap
   (mali_kbase_mem_pool.c:164!) — risolto con lo spill della zonelist: 260 × 2MB
   di spray > highmem-free → l'eccesso finisce in ZONE_NORMAL (physmap)
2. I primi tentativi di weapon sono andati in crash: il target W1 N+4 era una pagina USER —
   l'unlink gira nel contesto kworker (nessun mm) → fault. Risolto incorporando lo
   shellcode ring-0 DENTRO il pattern physmap a page+0x600 (direct map RWX su
   arm32 non-LPAE) — codice residente nel kernel, nessun ret2usr necessario
3. Bug dell'offset ctl_table: proc_handler è a entry+0x14, non +0x18 (la
   scansione originale lo aveva corretto; la mia define era sbagliata) — stava scrivendo extra1
4. Regressione da over-pressure trovata e annullata: budget kid 20×100MB +
   trailing 10s rompeva il reclaim; la config funzionante è 2 kid/200MB +
   5s/+3200 trailing (hit benigno 1/1 dopo il revert)
5. **diag2: W2 → &pid_max globale (0xc1114d7c) → la lettura restituisce il nostro valore
   (-1055861411 = 0xc110d55d come int32) — scrittura + rilettura DIMOSTRATE**

### Il mistero rimanente (a un esperimento dalla chiusura)
diag1 con l'indirizzo CORRETTO dell'handler (0xc1113f3c): W1 scatta (nodename
cambia), W2 deve essere stato eseguito (istruzione successiva) — eppure le letture di pid_max
restituiscono ancora valori puliti → il campo handler che scriviamo non è quello
attraverso cui l'inode fa dispatch. diag3 (in coda; richiede un boot con hit): W2 →
campo entry->data (0xc1113f2c) che punta a nodename — se la lettura allora
mostra nodename-bytes-as-int, la nostra entry È viva e solo l'offset dell'handler è in qualche modo
sbagliato; se non cambia, l'inode usa una copia shadow della tabella e dobbiamo cacciare quella viva.

### Realtà dell'hit-rate
Lancio di moneta per boot (~25-40%), a cluster; diversi boot safe-miss/crash
di fila sono normali. Circa 1 boot su 3-4 è un hit. Mantieni la config funzionante
ESATTAMENTE (2 kid, 5s trailing, kbm a 260 regioni, G=0xc154b2a4).

### TODO Sessione-9
1. Completare diag3 su un boot con hit (rilancia finché "W1 FIRED")
2. Se l'entry è viva: ricontrollare empiricamente l'offset dell'handler (scrivere
   l'indirizzo di proc_dostring come handler via... N deve essere uguale a un valore
   utile — usa l'unlink per scrivere invece entry->data e fai pivot: ad es.,
   data=selinux_enforcing-adiacente...)
3. Se tabella shadow: individuare quella viva — kallsyms non ha simboli dati;
   candidati: scansiona il comportamento di /proc/sys, o trova una seconda regione ctl_table
   tramite il pattern della lista header in .data (entry a passo 0x20
   con handler=proc_dointvec_minmax e maxlen=4 — enumerale TUTTE e
   fai diag-write su ciascuna)
4. Classe di target alternativa che evita del tutto il dispatch: puntatori a funzione
   in .data chiamati da percorsi raggiungibili dalla shell (serve audit)
5. La primitiva di scrittura in sé è FATTA — qualsiasi target affidabile a indirizzo
   kernel ora basta per il root

## SESSIONE 9 — nf-WEAPON: reclaim+unlink+hit-G DIMOSTRATI IN-WEAPON; resta solo la walk dell'hook

### Il nuovo design del trigger (sostituisce interamente il percorso sysctl-handler)
L'idea dell'utente tradotta in memoria kernel: nessun file SUID (il sistema è
dm-verity RO; la primitiva scrive in RAM kernel). Invece: **fake netfilter
hook**. Questo kernel ha il backport Android-common della NUOVA
API nf_hook_entries, ma implementata come LISTA COLLEGATA (verificato dal
disasm di nf_hook_slow + helper 0xc09897d4):
- `__ip_local_out(net, sk, skb)` carica la CELLA entries da
  **[net+0x58c]**, la salva in state+0x1c, chiama nf_hook_slow
- walk: `entry = *cell`; while(entry){ if (state->[4] <= entry->[0x20])
  call entry->[0xc](entry->[0x14], skb, state); entry = entry->[0]; }
  — cioè **fn@entry+0x0c, priv@entry+0x14, priority@entry+0x20,
  next@entry+0x00**; state+4 = soglia INT_MIN (passa sempre)
- init_net = **0xc1104548** (CONFIG_NET_NS=n → sock_net() inlina la
  costante; 6678 riferimenti movw/movt, campione dell'istogramma; confermato incrociato dal
  letterale di nf_hook_slow stesso). TARGET: **[init_net+0x58c] = 0xc1104ad4**
- L'hook LOCAL_OUT gira nel contesto del processo MITTENTE → il commit_creds(prepare_kernel_cred(0))
  del nostro hookfn dà root al processo che ha inviato
  il pacchetto. Trigger = sendto(127.0.0.1:9) UDP.

### Layout modalità nf (poc/stage3.c, modalità `nf 200`)
- pagine pattern kbm (relative alla pagina, unica fonte di verità — la vecchia
  weapon aveva TRE bug ora corretti: proc_handler@+0x14 non +0x18; il target W1
  deve essere memoria KERNEL (contesto kworker, nessun mm); codice incorporato a
  page+0x600 vs mismatch entry G+0x600=page+0x8a4):
  - +0x2ac/+0x2bc/+0x2c0/+0x2dc/+0x3a8: catena fake phy-alloc (invariata)
  - +0x600: hookfn nf_code (store del marker + prepare_kernel_cred +
    commit_creds + return NF_ACCEPT(1))
  - +0x700: fake entry {next=0, fn=PM_PAGE+0x600, priv=0, prio=0x100}
  - +0x740: cell → PM_PAGE+0x700
- payload: N = PM_PAGE+0x740 (0xc154b740), P = 0xc1104ad4
  → W1: *(cell+4)=P (finisce nella nostra pagina), W2: *(init_net+0x58c)=cell

### Strumentazione AUTO-DIAGNOSTICANTE (kbm_scan_for)
Le mappature CPU kbm sono mantenute; dopo il free scansioniamo ogni pagina
sprayata alla ricerca di una parola nota:
- scan(HOOKS_PTR_ADDR) a page+0x744 → prova reclaim + unlink + rivela
  quale pagina fisica supporta la guess G
- scan(0x600d600d) a page+0x7f0 → prova che l'hookfn È STATO ESEGUITO
  (nf_code scrive questo marker come sua 2ª azione)

### LA RUN CHE CONTA (2026-09-11, tarda sessione 9)```
[+] W1 CONFIRMED: region 135 page+0x185000 <- 0xc1104ad4 (reclaim + G hit!)
[+] nf trigger done, uid=2000 euid=2000

L'intera catena di attacco è stata eseguita su un boot reale: l'evento ha reclamato lo slot, il fake è stato eseguito, l'unlink è stato eseguito, la pagina G-guess (0xc154b000) era genuinamente nostra (regione 135 pagina 389). W2 = *(0xc1104ad4)=cell è l'istruzione adiacente — deve essere stata eseguita. Eppure UDP sendto non ci ha dato root → il fallimento è DENTRO il percorso dell'hook: semantica del walk, plumbing di [state+0x1c], confronto di priorità, o i campi dell'entry. (L'esperimento con marker per discriminare hookfn-eseguito-vs-no è stato aggiunto; solo crash-boot prima della fine della sessione — nessun dato pulito ancora.)

Apprendimenti su hit-rate / stato del boot (conquistati duramente)

  • CONFIGURAZIONE FUNZIONANTE (non toccare): 2 kid × 100MB pressione a step, 100×50ms/+3200 rename in coda, 260×2MB spray kbm, G=0xc154b2a4, pre-drain ~5000 rename
  • Over-pressure (20 kid / 10s in coda) ROMPE il reclaim — ripristinato
  • Il settle del boot conta: le esecuzioni lanciate immediatamente dopo boot_completed competono con le allocazioni di avvio del sistema → serie fredde; attendere 60-90s dopo il boot prima di eseguire
  • "W1 non è scattato" (controllo nodename) è PRIVO DI SIGNIFICATO per il payload nf — W1 scrive nella nostra pagina; usare invece kbm_scan_for
  • Boot con crash-at-free ≈ slot spazzatura o conversioni G-miss; il controllo benigno (pmap 200) è il sanity check dell'ambiente (4/4 hit quando caldo; crash-miss quando freddo)
  • Le directory /data/local/tmp/.w* vengono pulite all'avvio dell'esecuzione (le directory accumulate degradano il reclaim)

TODO Sessione-10 (albero decisionale, in ordine)

  1. Eseguire nf 200 su boot con settle ritardato finché non appare la riga W1-CONFIRMED, poi leggere la riga MARKER: a. marker PRESENTE, uid!=0 → le creds dello shellcode hanno fallito (controllare gli indirizzi prepare_kernel_cred/commit_creds; le codifiche blx) b. marker ASSENTE → il walk non ci ha mai chiamato: verificare con un SECONDO marker scritto da... prossime diagnostiche: hookfn che scrive SOLO il marker e ritorna 1 (nessuna cred) — se ancora assente:
    • dumpare i nostri byte effettivi dell'entry tramite il mapping CPU subito prima del trigger (sono nostri da leggere!)
    • controllare se [init_net+0x58c] viene anche solo consultato: usare la scrittura per corrompere invece qualcosa di osservabile (es. puntarlo a una cella il cui *cell = entry con fn = una funzione kernel come kfree → crash immediato al trigger = il campo VIENE consultato)
    • ri-verificare l'offset 0x58c: forse hooks_ipv4[NF_INET_LOCAL_OUT] vive a un indice diverso (NF_INET_POST_ROUTING=4?)
  2. Se il walk ci chiama: sistemare le creds → root → poi il piano dell'utente: setenforce 0; cp /system/bin/sh /data/local/tmp/su; chown root; chmod 6755; verificare ls -la; lasciare i file marker
  3. Persistenza (post-root): patch dell'immagine di boot via /dev/block/by-name/boot
    • disabilitare dm-verity, o in stile Magisk; su su /data da solo è uid0-in- shell-domain dopo il reboot (SELinux di nuovo enforcing) — setenforce 0 è solo runtime
  4. Note di pulizia: i processi st3 in pausa hanno stato kctx corrotto — uccidere solo via reboot; l'hijack nf rompe gli hook LOCAL_OUT per tutto il traffico — reboot dopo il root per ripristinare

Aggiunte di asset di stasera

  • modalità di poc/stage3.c: pin/root/drain{,2,3,4,5}/iso — arma completa + oracle + harness di isolamento, forensics su crash-point con O_SYNC
  • infrastruttura userland-fake (ufake_prep) — MANTENERE ma utilizzabile solo se viene mai trovato un percorso di deref context-inline
  • tools/: analisi offline vmlinux kdis/scan_s/resolve/dumpb/findsysctl

SESSIONE 10 — ROOT OTTENUTO (2026-09-11)

I tre bug che bloccavano l'arma-nf, tutti risolti

  1. init_net sbagliato: 0xc1104548 è __stack_chk_guard (l'istogramma movw/movt era inquinato dai load dello stack-canary — il 2025 ha costruito l'intero piano nf su di esso). Il vero init_net = 0xc1185040 (confermato: ip_send_skb(net,...) chiamato con questo letterale; ~994 riferimenti tutti nello stack net). Cella IPv4 LOCAL_OUT = init_net+0x58c = 0xc11855cc.
  2. Bug del doppio-deref: nf_iterate tratta [init_net+0x58c] come il puntatore nf_hook_ops stesso — legge fn@+0xc, priv@+0x14, prio@+0x20 direttamente da quel valore. L'entry fake della sessione-9 viveva a PM_PAGE+0x700 con la cella che puntava ad essa (campi next/ inutilizzati nella cella → → crash). L'entry fake DEVE vivere (0xc154b740). Con questo risolto, la chiamata all'hook è stata dimostrata (modalità = SAFE_FN processa tutti i 200 sendto senza problemi).

Il muro SELinux e il bypass a 2 pacchetti

commit_creds(prepare_kernel_cred(0)) / override_creds(&init_cred) dà uid 0 ma atterra nel SID SELinux kernel, che questa policy Fire OS non consente di scrivere su /data o /sys/fs/selinux/enforce (verificato: EACCES). Anche il vero SID init (7) è negato (test con struct cred fake). enforcing_setup è __init (liberato → crash). L'atomic_sub di mark_reclaim richiede nents=1 che rompe l'early-exit di shrink_cpu_mapping.

La vittoria: il processo di exploit mantiene i mapping CPU kbm, quindi l'entry nf fake può essere riscritta sul posto tra i pacchetti:

  • modalità selroot: entry = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}.
  • Pacchetto 1: *(enforcing)=0 → SELinux Permissive.
  • Riscrivere l'entry via kbm_cpu[reg]+off a {fn=commit_creds, priv=&init_cred}.
  • Pacchetto 2: commit_creds(&init_cred) nel task del mittente → uid 0 con un SELinux permissivo → root utilizzabile, tutto in un solo reclaim, nessuna catena necessaria.

Verificato sul dispositivo (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**; st3 in pausa è `Uid: 0 0 0 0`,
  `CapEff: 3fffffffff`.
- `/data` è montato **nosuid** quindi un `su` setuid non può funzionare. Un piccolo
  `rootshell` (invia UDP → `commit_creds` su se stesso → `execl sh`) fornisce una
  shell root interattiva: `uid=0(root) context=u:r:kernel:s0`.
- Root può leggere/scrivere `/dev/block/by-name/*` (`dd if=boot ...` OK).

### Stato del codice Stage-3 (`poc/stage3.c`)
- modalità: `nf <mb> [probe|uprobe|oc|chain|fc <sid>|notrig|selroot]`
- `selroot` è l'arma funzionante. Statiche chiave: `init_net=0xc1185040`,
  `HOOKS_PTR_ADDR=0xc11855cc`, `ENFORCING_ADDR=0xc1213ea8`,
  `ZERO_GADGET=0xc01d503c`, `commit_creds=0xc014993c`,
  `init_cred=0xc1114f54`.
- Il post-exploit usa syscall dirette (niente `system()`); mantieni il kctx vivo
  (`pause()`) per evitare crash in fase di teardown.

### Rimanente (Stage 4/5)
- Persistenza tra riavvii (verity / boot image / recovery), poiché il dirottamento
  della cella + SELinux permissivo sono solo a runtime e rieseguire l'exploit richiede
  il lancio della moneta di reclaim ~1/3.
- `su` avrà bisogno di una home non-nosuid (`/system`) o di un launcher che lo riattivi.

## SESSIONE 11 — RICOGNIZIONE PERSISTENZA (Track B + Track A) e il passaggio al RE

L'obiettivo era il root persistente. Sono stati definiti due percorsi:
- **Track B**: disabilitare il verified boot (dm-verity / SELinux) così che `/system` possa essere modificato.
- **Track A**: rieseguire l'exploit all'avvio.

Entrambi si riducono allo stesso blocco: **far sì che LK tratti il dispositivo come `eng`/`unlocked`.**

### Fatti sul verified-boot (build esatta)
- Bootloader bloccato, AVB `green`, `ro.boot.unlocked_kernel=false`, `ro.boot.secure_cpu=1`,
  `rpmb_state=1`. Bootrom patchato (nessun BROM); preloader solo via CMD short.
- `/system` è montato da **Android dm-verity dalla cmdline del kernel costruita da 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` = dispositivo verity chiamato `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 MB).
- GPT completa (16 partizioni, nessuna `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 MB) = preloader (magic `EMMC_BOOT`);
  boot1 (4 MB) = archivio IDME.

### Riscontri statici su LK (UFBL)
Header di `lk.img`: `88 16 88 58 | 00052974 | "LK"`; tabella vettori ARM a 0x200, il resto Thumb-2,
position-independent/rilocato (i literal pool usano `ldr+add pc`, quindi un disasm ingenuo relativo alla base fallisce).
Stringhe rilevanti (offset nel file): `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`.
**Conclusione: LK subordina gli effetti di sicurezza di `fos_flags`/`dev_flags` a eng/unlocked.**

### Archivio IDME (eMMC **boot1**) — scrivibile, persistente, letto da LK e Android
- Magic `beefdeed` + `"2.1\0"` + count(0x19=25) a 0x0; elementi da 0x10.
- Formato elemento: `char name[16]; u32 size; u32 type(=1); u32 magic(=0x124); u8 data[size] (pad4)`.
- Offset elementi (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`. I valori sono ASCII (i flag sono **stringhe esadecimali**).
- Lettura a runtime: `/proc/idme/<name>` (sola lettura). I valori dell'ultimo boot sono in cache; una scrittura su boot1
  ha effetto al boot successivo. Il percorso di scrittura richiede la cancellazione di `/sys/block/mmcblk0boot1/force_ro` (root).
- **Confermato che LK legge boot1**: cambiare `serial` ha cambiato `ro.boot.serialno` al boot successivo.
  Ma LK **tronca serial a 16 byte** e ha ignorato `fos_flags=0x80`, `dev_flags=0xff`,
  tutti-uno, ecc. — verity/selinux/`prod` invariati. Quindi l'iniezione via cmdline tramite serial fallisce.

### Consumatori lato Android dei flag IDME
- `/init.fosflags.sh` (servizio `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`. Verificato: impostare i flag ha effetto (`sys.usb=adb`, `noadbauth=1`).
- **adbd** (ARM ET_EXEC non strippato; `.text` VA 0x8160 / file 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 subordinato)
  - `amzn_is_dev_unlocked` @0x2d5fc = `/proc/cmdline` contiene `androidboot.prod=0` **oppure**
    `androidboot.unlocked_kernel=true`
  - `fos_read_debug_flags` @0x2d724 legge `/proc/idme/<name>` e interpreta come **hex**
  - `restart_root_service` @0xcb74 / `restart_unroot_service` @0xcc64
  - stringhe: `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`) — quindi anche con il
    gate di auto-root soddisfatto, il controllo prod di AOSP blocca il percorso del comando.

### Perché il root della Sessione-10 non persiste
- SELinux permissivo + dirottamento della cella + root sono solo a runtime.
- `/data/metrics` è una **vpartition**: `/system/bin/vpartition.sh` monta `/data/vp/metrics.img`
  (ext4, non-nosuid/noexec) su `/data/metrics` a ogni boot; un `su` scritto lì **non**
  sopravvive al riavvio. (Ecco anche perché il `su` setuid dava uid 0 ma **zero cap**.)

### Track A (re-exploit all'avvio) — bloccato
- Nessun trigger `.rc` di init esegue codice controllabile (import tutti verificati; i trigger `persist.*` avviano solo
  servizi fissi; script in `/system`/`/vendor`).
- I servizi root leggono configurazioni da `/data` ma non eseguono mai da lì (`perfmonitord`, `amazonfiled`,
  `vpartition.sh`, `kisd`, …).
- L'unico esecutore all'avvio = un'**app**, ma l'impronta in pausa dell'exploit è **VmRSS 534 MB**
  (spray `kbm`) → lmkd la uccide; inoltre un reclaim perso causa un panic (`PANIC_ON_OOPS`) → bootloop.
- **adbd auto-root** esiste ma è subordinato alla cmdline costruita da LK (`prod=0`/`unlocked_kernel=true`).

### Conclusione / prossimo obiettivo (scelto: RE Track B)
Tutto dipende dal far sì che LK riporti `eng`/`unlocked`. Alla portata:
`androidboot.prod=1|0` e `androidboot.unlocked_kernel=false` sono impostati da LK. Fare reverse di LK per trovare:
1. dove legge `fos_flags`/`dev_flags`/`usr_flags` (gli elementi `K*`) e il gate esatto;
2. la determinazione di `prod`/`unlocked` (elemento IDME? buildvariant? risultato di `amzn_verify_unlock`?);
3. `amzn_verify_unlock` (verifica RSA libtomcrypt) per un bypass o un percorso debole unlock_code/version;
4. l'archivio `seccfg`/`para`/`ENV_v1`(LK_ENV) (non presente in nessuna partizione dumpata — forse protetto da tee);
5. il preloader (`boot0`, `EMMC_BOOT`) per un bug.
Se uno di questi ci permette di impostare eng/unlocked (in modo persistente, tramite boot1 o una scrittura raw su partizione), allora
`FOS_FLAGS_DM_VERITY_OFF` disabilita la verity di system(root) e `/system` può essere modificato in modo persistente.

### Artefatti (da questa sessione)
`/tmp/opencode/mustang-dumps/` (potrebbe essere cancellata al riavvio dell'host): `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`.
Helper: `tools/findinitnet.py`, `findgadget*.py`, `findstores.py`, `adbd_sym.py` (in /tmp);
il repo ha `run.sh`, `poc/stage3.c` (`selroot`), `poc/su.c`, `rootcmd.sh`.

### Comandi utili```
# 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

SESSIONE 12 — LK RE: il gate eng/unlocked è reale, e gli store di flag non firmati non esistono

Obiettivo: far sì che LK tratti il dispositivo come eng/unlocked, o trovare un bug nel preloader/LK, così che verity/SELinux possano essere disabilitati in modo persistente. Risultato: percorso di codice LK rilevante invertito end-to-end; il flip non è raggiungibile tramite gli store disponibili. Nessun dispositivo è stato brickato; il singolo esperimento su boot1 è stato ripristinato allo stato originale.

LK è Thumb-2 PIC, rilocato alla base 0xFF400000

lk.img inizia con un piccolo stub ARM (file 0x200). Il rilocatore a 0x224 copia da 0x200 a una destinazione letterale e salta a un entry point letterale:

Quindi indirizzo runtime = 0xFF400000 + offset file per offset >= 0x200. Tutto ciò che segue lo stub è Thumb-2, position-independent. Le stringhe sono costruite con ldr rT,[pc,#imm] (offset T1 = imm8*4; offset ldr.w = imm12) seguito da add rT, pc; il target è (add+4) + *pool. Uno scanner robusto che sopravvive allo stub ARM e ai literal pool è stato aggiunto come tools/lk_xref.py (gestisce le forme a 16 e 32 bit, scansiona ogni 2 byte). Tutti gli offset seguenti sono offset file; aggiungere 0xFF400000 per gli indirizzi runtime.

Flusso di controllo decodificato (offset -> significato)

Il codice di sblocco è firmato con RSA Amazon — non falsificabile

0x20b4 legge l'elemento IDME unlock_code (0x400 byte, tutti zero su questa unità) ed esegue amzn_verify_unlock (0x222c -> 0x20f0). Quella funzione pilota libtomcrypt (decine di percorsi /features/libtomcrypt/src/pk/asn1/der/... e verifica RSA), e l'immagine incorpora il materiale del certificato: Sunnyvale / Amazon Lab126 / "$Common Kernel Signing Engineering CA0" a 0x317d9+, più le diagnostiche 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). Non esiste una scorciatoia per codice vuoto / lunghezza / versione: , quindi (confermato in ). Flippare o richiede o un valido firmato da Amazon (chiave privata non disponibile) o un bug di esecuzione di codice nel verificatore. Nulla di sfruttabile (bounds/size) è stato trovato staticamente in 0x20b4/0x222c/0x20f0. =>

I flag verity/SELinux non provengono da uno store che esiste

I flag di sicurezza vengono letti tramite il getter a 0x57c. Test empirico:```

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` è `FOS_FLAGS_DM_VERITY_OFF`; il gate decodificato avrebbe disattivato
verity **se** il getter lo avesse restituito. Non lo ha fatto. Il gate è attivo, non
codice morto: il suo sentinella di cache one-time è `-1` nell'immagine
(`*(u32*)0x50c74 == 0xffffffff`), quindi la funzione ha effettivamente eseguito il
percorso `check_flag("fos_flags",0x80)` e ha ottenuto 0. Pertanto il getter (almeno
al momento della protezione verity) **non** sta leggendo gli elementi IDME di boot1.

L'altro store candidato è l'**LK env**, caricato da una partizione letteralmente
chiamata `"para"` (loader 0x12fd4, magic `ENV_v1`, checksum @0x3ffc). La tabella
delle partizioni di LK stessa (0x4fcc0..0x50340) elenca preloader/proinfo/nvram/protect1/
protect2/persist/seccfg/secro/**para**/logo/custom/expdb/tee1/tee2/metadata/
system/cache/userdata — ma il GPT effettivo del tablet ha **solo 16 voci**, tutte
di tipo `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

Non esiste nessuna partizione para, seccfg, nvram, protect o persist su questo prodotto (e i dump PMT/pmt.img sono tutti a zero). Quindi l'env di LK è vuoto, le chiavi Kfos_flags/Kdev_flags non esistono mai, e tutti i controlli fos_flags/dev_flags restituiscono 0 — indipendentemente da ciò che contengono gli elementi IDME. Gli elementi IDME di boot1 sono consumati da Android (/init.fosflags.sh, adbd, /proc/idme/*) ma non dai gate di sicurezza di LK.

Conclusione — perché eng/unlock persistente è bloccato

  1. unlocked_kernel richiede un unlock_code firmato da Amazon (RSA/libtomcrypt, CA incorporata). Non falsificabile offline; nessun bug del verificatore trovato. Blocco rigido.
  2. I flag DM_VERITY_OFF / selinux=permissive sono consumati dall'env di LK (para/ENV_v1), che non esiste su questo GPT. L'IDME fos_flags è empiricamente ignorato da LK (0x80 persistito, verity rimasto eio). Blocco rigido a meno che la tabella delle partizioni non venga modificata.
  3. Anche un fos_flags=0x80 riuscito imposterebbe solo androidboot.veritymode= disabled e un root= non-dm-0; non sbloccherebbe, e SELinux avrebbe comunque bisogno di dev_flags dallo stesso env assente per passare a permissive.

Vie rimanenti (future, a rischio più elevato; non tentate)

  • Sintetizzare uno store para/ENV_v1: aggiungere una voce GPT denominata para (primaria
    • backup GPT devono essere entrambe aggiornate) nello spazio libero dopo userdata (userdata termina all'LBA 0x3a3dfde; disco = 30535680 settori), poi creare un env con fos_flags=0x80 e dev_flags=0x40 (checksum a +0x3ffc = somma dei byte su 0x3ffc). Questa è l'unica via rimanente per disattivare verity. Rischi: corrompere il GPT primario/di backup può brickare; e non è stato dimostrato che il gate verity legga effettivamente para (solo che non è boot1 IDME).
  • Bug del Preloader (boot0/EMMC_BOOT): non analizzato in questa sessione. Scrivere boot0 è vietato finché non esistono una copia integra e un percorso di ripristino.
  • Ricerca sul verificatore: il percorso del certificato di engineering (0x316a0/0x31703) è raggiungibile solo con un'identità del dispositivo accettata come "engineering" più un codice firmato con la chiave di engineering; nessuna chiave privata è disponibile.

Artefatti / riproducibilità

  • Strumento aggiunto: tools/lk_xref.py — risolutore di xref di stringhe LK indipendente dalla base.
  • Dump usati: /tmp/opencode/mustang-dumps/lk.img, boot1.img (integro), boot0.img, mbr.img (GPT), pmt.img (tutto a zero).
  • Immagine sperimentale boot1 (fos_flags=0x80) conservata in /tmp/opencode/s12/boot1_f80.img; dispositivo ripristinato a boot1 integro (verificato /proc/idme/fos_flags -> 0).

Comandi utili (richiesto root; riattivare con ./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:~
## SESSIONE 13 — il preloader è raggiungibile dopotutto (`1949:20ff` = preloader MTK, trasporto HID)

Mentre è spento e collegato via USB, il tablet si enumera come **`1949:20ff`**
(`Lab126`) — *non* Android e *non* il bootrom `0e8d:0003`.  Descrittore:```
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)

Identificazione. 0x20FF è elencato come "MTK Preloader" in config/usb_ids.py di mtkclient (sotto il VID MediaTek 0x0e8d: 0xe8d:{0x0003 Brom, 0x2000/0x2001/0x20ff/0x3000 Preloader}). Amazon ha mantenuto il PID del preloader e cambiato il VID in 0x1949, e lo presenta come una coppia di endpoint HID con un report descriptor fittizio. Quindi questa è la modalità MediaTek preloader / USBDL, uno stadio sotto LK — raggiunta qui tramite spegnimento + collegamento, non tramite il corto CMD.

Le stringhe del descriptor "HID"/"HID Interface" non sono presenti in lk.img, boot0.img, boot.img o negli altri dump, cioè la modalità è prodotta da un componente che non abbiamo dumpato (bootrom/TEE) o è assemblata a runtime.

Perché questo è importante. Il preloader Amazon usato da aftv2-tools espone comandi integrati, senza Download-Agent, su questo esatto flusso di byte:``` 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` usa `read32`/`write32` per accedere al controller MSDC
(base `0x11230000` su MT8173; verificare per MT8163) e leggere/scrivere **blocchi eMMC
grezzi** senza DA e quindi senza AVB/verity di mezzo. Se il preloader di mustang
accetta 0xD1/0xD4, quello è un percorso diretto allo sblocco persistente (patch di `boot` /
`lk`), indipendente dal codice di sblocco RSA e dall'env LK assente.

### Strumenti aggiunti (root necessario; chmod del nodo USB prima)```
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 è la sonda minimale di solo handshake; tools/mtk_preloader_hid.py è il transport completo (handshake/read32/ write32; write32 è protetto e non dovrebbe essere usato finché la mappa dei registri eMMC non è confermata).

Stato / prossimi passi

  • Non confermato: se il preloader di mustang implementa effettivamente 0xD1/0xD4 (la sonda di handshake lo determina). Se lo fa, il percorso di lettura/scrittura eMMC di aftv2 può probabilmente essere portato direttamente.
  • Poi: trovare la base MSDC dell'MT8163 (kernel DT o preloader), dumpare una partizione (read_mmc), quindi patchare boot.img/lk dal preloader e riavviare.
  • Questo è uno stadio di boot inferiore rispetto a tutto ciò che è in SESSION 12, quindi non dipende da amzn_verify_unlock o dal gate dell'env LK.
  • NON eseguire scritture con SP Flash Tool / mtkclient su di esso prima che il protocollo e il layout eMMC siano confermati.
Scarica lo strumento
pi_blocked_on
  • Il proxy waiter risiede sullo stack del thread waiter stesso (futex.c:1975 passa this->rt_waiter, dichiarato in futex_wait_requeue_pi a futex.c:2880) → il waiter marca il proprio frame liberato tramite i fd_set di select (nr 142) su arm32
  • Clima di sfruttamento: nessun KASLR (base fissa 0xc0008000), nessun PAN, DEBUG_RT_MUTEXES disattivato → rt_mutex_waiter compatto da 48 byte (tree_entry@0, pi_tree_entry@0xc, task@0x18, lock@0x1c, prio@0x20, deadline@0x28)
  • Catena minimale: 2 write-slot → modprobe_path @ 0xc111488c (stringa auto-localizzata in vmlinux; KALLSYMS_ALL disattivato quindi i simboli dati richiedono questo trucco) → exec di binfmt sconosciuto → script root (setenforce 0, disabilita OTA, su)
  • Riferimenti in refs/: NebuSec/CyberMeowfia (originale), GhostLock-5.10 (port per Fire OS 8, trigger ARM 32-bit completo in src/exp32/), ghostlock-...-4.19-k40 (port Android Qualcomm 4.19)
  • Stage 3: chiamata arbitraria a funzione kernel → ROOT (sessione 10)
    selroot
    selinux_state.enforcing
    commit_creds(&init_cred)
    uid=0
  • [_] Stage 4: script root (su, permissive, OTA off) + persistenza — root ottenuto; persistenza bloccata (vedi SESSIONE 11): LK vincola verity-off/SELinux-permissive a eng/unlocked, il re-exploit all'avvio non ha un esecutore praticabile. Prossimo: reverse di LK/amzn_verify_unlock.
  • Stage 5: catena di avvio di un OS personalizzato
  • jc
    nr_extres
  • il risultato dell'alloc JIT è scritto dal kernel tramite info->gpu_alloc_addr (un GPU VA che bisogna pre-allocare e passare)
  • oggetto evacuabilepressionerisultato
    nessuno900 MBsopravvissuto
    nessuno1300 MBpanic (bug lowmem di sistema — non correlato)
    regione normale + DONT_NEED700 MBsopravvissuto
    regione JIT + DONT_NEED700 MBpanic nel percorso di reclaim
  • kernel/config-* — dump di /proc/config.gz dal dispositivo in esecuzione
  • ksrc/ — sorgente OSS Amazon (platform.tar + albero midgard-r26p0 estratto)
  • OTA: /tmp/opencode/mustang_ota.bin (sha256 6068515a… corrisponde a fireos-archive) e tarball del sorgente kernel da 2.2 GB conservato in ~/Desktop/amazon-mustang/
  • Percorso dell'albero sorgente: ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/
  • sprayer rename concorrenti durante la tempestai rename si bloccano (journal/GFP_NOFS) → 128 totali → deref spazzatura
    pre-drain 12K eventi + pressione + piccolo trailingcrash al deref
    + kill-child-all'eviction (poll 10ms)crash al deref
    + lifecycle pinnato a cpu0 (drain3)crash al deref
    pressione a step (drain4 v1)i figli hanno liberato memoria all'uscita → nessuna eviction (percorso legale validato)
    runrisultato
    pmap G=c2a412a4 400MBcrash al deref
    pmap G=c2f4b2a4 480MBcrash; +0 rinomine post-kill (480MB soffoca il fs)
    iso2 (spray + regions + oracle)REGION HIT — lo spray non rompe il reclaim
    mix v1 (eventi+regions concorrenti)crash; confuso (thread di eventi in spinning)
    pmap G=c2a7d2a4 350MB + retouchcrash; +4452 rinomine OK
    mix2 (sequenziale: 2s eventi POI regions)crash; +7126 rinomine (28K alloc eventi), 2715 regions
    fn
    fn=0
    all'indirizzo della cella
    PM_PAGE+NF_CELL_OFF
    probe
  • La mappa diretta physmap è XN sopra kernel_x_end: arch/arm/mm/mmu.c map_lowmem() mappa lowram sotto il testo del kernel MT_MEMORY_RWX, ma tutto sopra kernel_x_end MT_MEMORY_RW → PMD_SECT_XN (riga 509). Lo shellcode cablato a 0xc154b600 va in prefetch-abort. Il payload deve essere un puntatore a funzione kernel reale, non codice nel physmap.
  • letterale (off file)valoresignificato
    0x2700xFF4002F8str r4,[r6] scratch
    0x2740xFF40027Cdestinazione (base+0x27C)
    0x2780xFF54A440fine copia (incl. BSS)
    0x27C0xFF400484entry point
    offsetfunzione
    0xdf7cis_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ]; g = globale @0x52838. 1 su questa unità.
    0x20b4verify_stored_unlock() = memset(buf,0,0x100); legge IDME/env unlock_code (0x100) tramite 0x57c; bl 0x222c; restituisce (verify==0).
    0x222c / 0x20f0amzn_verify_unlock(code,len) — verifica RSA/PKCS#1 di libtomcrypt (vedi sotto).
    0xda3eis_unlocked() = is_secure_or_prod() && verify_stored_unlock().
    0x29a28is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock()); memorizzato in cache nella globale @0x50c74.
    0x29974Costruttore cmdline SELinux: dev_flags & 0x20 -> androidboot.selinux=enforce, dev_flags & 0x40 -> ...=permissive (ciascuno condizionato da byte[+0x162]).
    0x118xx/0x11bxxcostruttore cmdline del kernel (unlocked_kernel, prod=1/0, verifiedbootstate, rpmb_state, secure_cpu, versioni, root=).
    0x27af8Gate UART: fos_flags & 0x4 -> printk.disable_uart=0, altrimenti =1.
    0x12fd4Loader env di LK: partizione "para", 0x4000 byte, magic ENV_v1, checksum = somma dei byte su 0x3ffc confrontata con la word @0x3ffc.
    0x1efd0lookup della partizione per nome (usato per "para", "boot", ...).
    0x57cdispatcher getter tramite lo slot di callback @0x58218; gli slot @0x58200..0x5821c sono registrati da una tabella a 0x5a8-0x734.
    0x2a19cfastboot oem unlock: bl 0x222c(code,len); in caso di successo scrive unlock_code (0x100) tramite 0x408.
    verify(zeros) != 0
    unlocked_kernel=false
    /proc/cmdline
    androidboot.unlocked_kernel
    androidboot.prod
    unlock_code
    il flip eng/unlocked tramite il percorso documentato è crittograficamente infattibile.
  • La Track A (re-exploit all'avvio) rimane quindi bloccata esattamente come nella SESSIONE 11: il suo unico percorso di sblocco è lo stesso gate di LK.