
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.
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.
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.
nix-shell -p android-tools --run './run.sh' # add -p zig too if no zig
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.
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.
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)WAIT_REQUEUE_PI/CMP_REQUEUE_PI), nessun device node,
nulla controllato da SELinux — gli ostacoli fatali del percorso kbase qui non esistonosched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) a
sched/core.c:4706 — dereferenzia obsoleto ✓exp32/main.crt_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 parametriFailed 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, compilato Sat May 3 01:25:15 UTC 2025 (Linaro GCC 6.3-2017.05)/dev/kb, /dev/dkb (partizioni di kernel-backup Amazon) root:drmrpc 0660 — bloccatimali_kbase r26p0-01rel0 (Midgard, Mali-T720), all'interno dell'intervallo affetto NVD r4p0–r31p0mali_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]] obsoletomali_kbase_mem.c:3138 kbase_jit_backing_lost → percorso di destroy (si attiva durante il reclaim)0xc0008000 / PA 0x40080000)ARM_SW_DOMAIN_PAN → ret2usr praticabile; CONFIG_PANIC_ON_OOPS=y (tentativi falliti = riavvio)SLAB_FREELIST_RANDOM/HARDENED, nessun CONFIG_USER_NS/USERFAULTFD/NF_TABLESCONFIG_MODULES=y, nessun STATIC_USERMODEHELPER → sovrascrittura di modprobe_path = root_IOC_TYPE 0x80)MEM_ALLOC è di 32 byte (in ha 4 × u64 incl. extent)BASE_MEM_PROT_GPU_RD|WR (bit 2|3), non i legacy R|Wmmap(fd, offset=3<<12, PROT_NONE)JOB_SUBMIT deve essere uguale a sizeof(base_jd_atom_v2) = 48 (base_jd_prio/base_jd_dep_type sono typedef u8)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)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.
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)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
## 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):
/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).
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:
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.
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.
Testati e morti dal dominio shell:
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.
(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.
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.
kbase_jit_free(kctx, reg) @ 0xc058495c con finto reg completamente controllato:
reg->cpu_alloc NULL → backed size 0 → blocco trim saltato (0xc0584978)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)list_add(gpu_alloc->evict_node, &kctx->evict_list): testa evict_list
@ kctx+0x1427c; scrive in gpu_alloc+0x18/0x1c (deve essere scrivibile)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+0x148e89 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).
add_key (CONFIG_KEYS=y): negato da SELinux per la shell. Morto.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)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.
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:
*(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)poc/stage3.c):
kern_table[pid_max].proc_handler @ 0xc1113f40 (scrivibile
.data, verificato via scansione string-pointer + handler == proc_dointvec_minmax)b +8; W2 scrive N a P = campo handler)read /proc/sys/kernel/pid_max
(leggibile dalla shell); gira nel contesto del proprio task → i creds si applicano a noiinotify_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)| variante | risultato |
|---|
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.
iso): timing identico, trailing con
REGION commit-0 + oracle di riuso pool stage-2 → ORACLE HIT
→ timing/raggiungibilità sono OK; gli eventi sono il problemacurrent->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.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).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.
/tmp/opencode/ksrc2/kernel/mediatek/mt8163/4.9/ (arch/arm incl.
mustang.dtsi, mm/, fs/eventpoll.c, fs/notify) da ksrc/platform.tar.
Riscontri:
physmap_retouch() dopo il kill (fa fault-in di tutte le pagine dello spray
prima del trailing/deref)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).
vmalloc=496M slub_max_order=0 slub_debug=O loglevel=8 initcall_debug
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.
[+] uname.nodename="X?lhost" (was "localhost") <<< ORACLE HIT
**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.)
pmap 200) è il sanity check dell'ambiente (4/4 hit quando
caldo; crash-miss quando freddo)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:
ls -la; lasciare i file markerpoc/stage3.c: pin/root/drain{,2,3,4,5}/iso — arma completa +
oracle + harness di isolamento, forensics su crash-point con O_SYNCinit_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.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).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:
selroot: entry = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}.*(enforcing)=0 → SELinux Permissive.kbm_cpu[reg]+off a {fn=commit_creds, priv=&init_cred}.commit_creds(&init_cred) nel task del mittente → uid 0 con
un SELinux permissivo → root utilizzabile, tutto in un solo reclaim, nessuna catena necessaria.[+] 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**; 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
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.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.
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 di sicurezza vengono letti tramite il getter a 0x57c. Test empirico:```
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` è `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.
unlocked_kernel richiede un unlock_code firmato da Amazon (RSA/libtomcrypt,
CA incorporata). Non falsificabile offline; nessun bug del verificatore trovato. Blocco rigido.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.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.para/ENV_v1: aggiungere una voce GPT denominata para (primaria
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).boot0/EMMC_BOOT): non analizzato in questa sessione. Scrivere
boot0 è vietato finché non esistono una copia integra e un percorso di ripristino.tools/lk_xref.py — risolutore di xref di stringhe LK indipendente dalla base./tmp/opencode/mustang-dumps/lk.img, boot1.img (integro),
boot0.img, mbr.img (GPT), pmt.img (tutto a zero)./tmp/opencode/s12/boot1_f80.img; dispositivo ripristinato a boot1 integro
(verificato /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
## 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
`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).
read_mmc), quindi patchare boot.img/lk dal preloader e riavviare.amzn_verify_unlock o dal gate dell'env LK.pi_blocked_onfutex.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 arm32DEBUG_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)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)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)selrootselinux_state.enforcingcommit_creds(&init_cred)uid=0jcnr_extresinfo->gpu_alloc_addr (un GPU VA che bisogna
pre-allocare e passare)| oggetto evacuabile | pressione | risultato |
|---|
| nessuno | 900 MB | sopravvissuto |
| nessuno | 1300 MB | panic (bug lowmem di sistema — non correlato) |
| regione normale + DONT_NEED | 700 MB | sopravvissuto |
| regione JIT + DONT_NEED | 700 MB | panic nel percorso di reclaim |
kernel/config-* — dump di /proc/config.gz dal dispositivo in esecuzioneksrc/ — sorgente OSS Amazon (platform.tar + albero midgard-r26p0 estratto)/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/ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/| sprayer rename concorrenti durante la tempesta | i rename si bloccano (journal/GFP_NOFS) → 128 totali → deref spazzatura |
| pre-drain 12K eventi + pressione + piccolo trailing | crash 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) |
| run | risultato |
|---|
| pmap G=c2a412a4 400MB | crash al deref |
| pmap G=c2f4b2a4 480MB | crash; +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 + retouch | crash; +4452 rinomine OK |
| mix2 (sequenziale: 2s eventi POI regions) | crash; +7126 rinomine (28K alloc eventi), 2715 regions |
fnfn=0PM_PAGE+NF_CELL_OFFprobekernel_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) | valore | significato |
|---|
| 0x270 | 0xFF4002F8 | str r4,[r6] scratch |
| 0x274 | 0xFF40027C | destinazione (base+0x27C) |
| 0x278 | 0xFF54A440 | fine copia (incl. BSS) |
| 0x27C | 0xFF400484 | entry point |
| offset | funzione |
|---|
0xdf7c | is_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ]; g = globale @0x52838. 1 su questa unità. |
0x20b4 | verify_stored_unlock() = memset(buf,0,0x100); legge IDME/env unlock_code (0x100) tramite 0x57c; bl 0x222c; restituisce (verify==0). |
0x222c / 0x20f0 | amzn_verify_unlock(code,len) — verifica RSA/PKCS#1 di libtomcrypt (vedi sotto). |
0xda3e | is_unlocked() = is_secure_or_prod() && verify_stored_unlock(). |
0x29a28 | is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock()); memorizzato in cache nella globale @0x50c74. |
0x29974 | Costruttore cmdline SELinux: dev_flags & 0x20 -> androidboot.selinux=enforce, dev_flags & 0x40 -> ...=permissive (ciascuno condizionato da byte[+0x162]). |
0x118xx/0x11bxx | costruttore cmdline del kernel (unlocked_kernel, prod=1/0, verifiedbootstate, rpmb_state, secure_cpu, versioni, root=). |
0x27af8 | Gate UART: fos_flags & 0x4 -> printk.disable_uart=0, altrimenti =1. |
0x12fd4 | Loader env di LK: partizione "para", 0x4000 byte, magic ENV_v1, checksum = somma dei byte su 0x3ffc confrontata con la word @0x3ffc. |
0x1efd0 | lookup della partizione per nome (usato per "para", "boot", ...). |
0x57c | dispatcher getter tramite lo slot di callback @0x58218; gli slot @0x58200..0x5821c sono registrati da una tabella a 0x5a8-0x734. |
0x2a19c | fastboot oem unlock: bl 0x222c(code,len); in caso di successo scrive unlock_code (0x100) tramite 0x408. |
verify(zeros) != 0unlocked_kernel=false/proc/cmdlineandroidboot.unlocked_kernelandroidboot.produnlock_code