Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
amazon-mustang-hack — Investigación de exploit del kernel que logra root temporal en Amazon Fire 7 (Fire OS 7.3.3.1) mediante el use-after-free del JIT de Mali kbase CVE-2022-38181, con una cadena de sobrescritura de modprobe_path. | Kitploit
Herramientas/GitHubGitHub/artur9010/amazon-mustang-hack
Seguridad AndroidEscalada de PrivilegiosAnálisis de VulnerabilidadesExplotaciónIngeniería InversaSeguridad MóvilPapers e InvestigaciónDesarrollo de PayloadsExplotación de Binarios

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir
GitHubartur9010/amazon-mustang-hack

amazon-mustang-hack

Investigación de exploit del kernel que logra root temporal en Amazon Fire 7 (Fire OS 7.3.3.1) mediante el use-after-free del JIT de Mali kbase CVE-2022-38181, con una cadena de sobrescritura de modprobe_path.

Ver RepositorioSitio web
hace 13h 56mAún no revisado

Proyecto asistido por IA. Esta investigación, el desarrollo del exploit y la documentación fueron producidos con asistencia de IA utilizando los modelos GLM-5.3 y DeepSeek V4.1 Flash.

amazon-mustang-hack

Investigación de exploit de root para el Amazon Fire 7 9.ª gen (mustang, MT8163, Mali-T720) en el firmware final — Fire OS 7.3.3.1, PS7331.4463N, kernel 4.9.117 (compilado 2025-05-03, SPL 2024-08-01).

Objetivo: LineageOS. La ruta del bootloader está muerta en esta unidad (bootrom parcheada — solo preloader vía cortocircuito CMD), por lo que la única ruta restante es un exploit de kernel por software.

Inicio rápido```

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:~
Si tiene éxito:```
/data/metrics/su id     # run a command as root
/data/metrics/su        # interactive root shell

El reclaim gana aproximadamente 1 arranque de cada 3 y una pérdida provoca un panic/reinicio de la tablet; run.sh simplemente espera al reinicio y reintenta. SELinux se fuerza a Permissive como parte del exploit, por lo que root es solo en tiempo de ejecución — un reinicio restaura el estado original y vuelves a ejecutar run.sh.

Los binarios precompilados st3 y su (armv7 estáticos) están incluidos, así que no se necesita toolchain para ejecutar. ./run.sh --build los recompila desde poc/*.c si tienes zig.

Todo lo que hay debajo es solo un registro del trabajo realizado por el modelo, no hay intervención humana a continuación.

OBJETIVO PRINCIPAL (desde la sesión 5): kbase CVE-2022-38181 — etapa 2 PROBADA

GhostLock (abajo) está aparcado: la variante BUG_ON rtmutex de MTK + la ausencia de divulgación de direcciones del kernel desde el shell = callejón sin salida arquitectónico en esta compilación (sesiones 2-4). El UAF del JIT de kbase fue re-diagnosticado (el "panic incondicional" del destroy-worker era la desreferencia de JIT_FREE, registro perdido por la muerte de adbd a mitad del panic) y la etapa 2 ahora está probada por oráculo — ver la sección SESIÓN 5.

APARCADO: GhostLock, CVE-2026-43499

UAF de pila futex-PI en remove_waiter() de rtmutex (divulgación de NebuSec 2026-07, corrección 3bfdc63936dd aplicada 2026-04). Rango vulnerable 2.6.39–7.1 → nuestro 4.9.117 (mayo 2025) está afectado.

Verificado en nuestra compilación exacta:

  • CONFIG_FUTEX=y, rtmutex compilado, bug presente textualmente: rtmutex.c:1108-1111 usa current->pi_lock/current->pi_blocked_on (debería ser waiter->task); sitio de llamada con bug rtmutex.c:1723 (ruta de error de rt_mutex_start_proxy_lock)
  • Superficie de activación = syscalls futex puras (WAIT_REQUEUE_PI/CMP_REQUEUE_PI), sin nodo de dispositivo, nada restringido por SELinux — los obstáculos fatales de la ruta kbase no existen aquí
  • Consumidor: sched_setattr → __sched_setscheduler → rt_mutex_adjust_pi(p) en sched/core.c:4706 — desreferencia obsoleto ✓

TODO (plan de port)

  1. Escribir el trigger (interbloqueo requeue-PI de 3 hilos, núcleos 0-3) — port de exp32/main.c
  2. Geometría del stamp: offset del frame rt_waiter vs área fd_set de do_sys_select — desensamblar nuestro vmlinux (do_sys_select stack_fds vs frame de futex_wait_requeue_pi), exponer STAMP_NFDS/STAMP_WAITER_OFF como parámetros ajustables
  3. Codificación del fake-writer para waiter de 48 bytes en arm32 → slots "escribir V en ADDR"
  4. 2 slots → modprobe_path, disparar, script root
  5. Alternativas si el select-stamp no alcanza: stamp con setsockopt(MCAST_JOIN_SOURCE_GROUP)

Estado

  • Bootrom (método hardware amonet) — parcheado en esta unidad, callejón sin salida
  • mtk-su (CVE-2020-0069) — parcheado, Failed critical init step 3
  • Reconocimiento de superficie de ataque — /dev/mali0 world-RW + SELinux gpu_device, kbase r26p0-01rel0
  • CVE-2022-38181 confirmado en el código fuente de la compilación exacta; el trigger de etapa 1 funciona
  • CVE-2026-43499 (GhostLock) verificado pero bloqueado: variante BUG_ON rtmutex de MTK + sin divulgación de direcciones del kernel desde el shell (sesiones 2-4)
  • CVE-2022-38181 etapa 2 PROBADA (sesión 5): el panic del destroy-worker fue un diagnóstico erróneo; redirección del UAF sobre la región rociada, verificada por oráculo
  • Etapa 1: trigger + stamp + consumidor (crash = cadena activa)
  • Etapa 2 (ruta kbase): redirección del UAF sobre la región rociada — PROBADA sesión 5
  • Etapa 2b: control de slot byte a byte (rotación de stamp por xattr) → escritura unlink
  • — secuestro del hook nf LOCAL_OUT, cadena de 2 paquetes : poner a cero , reescribir la entrada falsa a . , SELinux Permissive.

Hallazgos clave

Dispositivo / firmware

  • Modelo KFMUWI, dispositivo mustang, Fire OS 7.3.3.1 PS7331.4463N/0031575863040
  • Kernel 4.9.117-g08fe75b-dirty, compilado Sat May 3 01:25:15 UTC 2025 (Linaro GCC 6.3-2017.05)
  • Amazon reeditó silenciosamente 7.3.3.1 en mayo de 2025 (nuevo incremental, misma cadena de versión)
  • Revisión de Bootrom posterior a 2020: cortocircuito a GND en eMMC CMD da solo preloader (parcheado)
  • /dev/kb, /dev/dkb (particiones de respaldo del kernel de Amazon) root:drmrpc 0660 — bloqueadas

Por qué aplica CVE-2022-38181

  • Driver: mali_kbase r26p0-01rel0 (Midgard, Mali-T720), dentro del rango afectado de NVD r4p0–r31p0
  • La recompilación de Amazon de mayo de 2025 incluyó el bug de 2018 textualmente — sin backport
  • Código vulnerable exacto, verificado en el código fuente:
    • mali_kbase_mem.c:2721 kbase_jit_destroy_worker libera la región, nunca limpia kctx->jit_alloc[id]
    • mali_kbase_softjobs.c:1270 kbase_jit_free_finish desreferencia el jit_alloc[ids[j]] obsoleto
    • mali_kbase_mem.c:3138 kbase_jit_backing_lost → ruta de destrucción (se dispara durante el reclaim)

Clima de explotación (todo verificado del volcado de configuración en vivo + vmlinux de la OTA)

  • armv7 32 bits, no-LPAE → sin KASLR (kernel en VA fija 0xc0008000 / PA 0x40080000)
  • Sin ARM_SW_DOMAIN_PAN → ret2usr viable; CONFIG_PANIC_ON_OOPS=y (intentos fallidos = reinicio)
  • Sin SLAB_FREELIST_RANDOM/HARDENED, sin CONFIG_USER_NS/USERFAULTFD/NF_TABLES
  • CONFIG_MODULES=y, sin STATIC_USERMODEHELPER → sobrescritura de modprobe_path = root
  • 1 GB de RAM → reclaim directo (necesario para la expulsión) trivialmente alcanzable; presión >~1 GB provoca panic del kernel por sí sola (bug de lowmem/OOM no relacionado) — mantener el spray ≤ 900 MB, usar ~700 MB

Rarezas de la UAPI encontradas durante el desarrollo del PoC (r26p0, _IOC_TYPE 0x80)

  • La unión MEM_ALLOC es de 32 bytes (in tiene 4 × u64 incl. extent)
  • los flags deben incluir BASE_MEM_PROT_GPU_RD|WR (bits 2|3), no los legacy R|W
  • mmap de la tracking-page requerido antes de cualquier alloc: mmap(fd, offset=3<<12, PROT_NONE)
  • El stride de JOB_SUBMIT debe ser igual a sizeof(base_jd_atom_v2) = 48 (base_jd_prio/base_jd_dep_type son typedefs u8)
  • JIT: MEM_JIT_INIT (nr 14, struct v2), alloc/free son soft jobs vía JOB_SUBMIT (BASE_JD_REQ_SOFT_JIT_ALLOC=0x209, ...FREE=0x20a; =ptr de usuario, =count)

Diferencial de etapa 1 (prueba de que el bug se dispara)

El panic ocurre durante la propia expulsión (evictable_reclaim_scan_objects → backing_lost → destroy worker) — las referencias colgantes (jit_alloc[], lista de evict) se recorren antes de que enviemos JIT_FREE. La etapa 2 debe ganar la carrera: reasignar el kbase_va_region liberado con nuestro propio spray de MEM_ALLOC mientras la presión sigue en curso.

Artefactos

  • poc/stage2.c — exploit de etapa 2 (modos: step/uaf/spstep/spfree/spray/keys) — spray 700 = ejecución completa del oráculo; sobrevive y se pausa (matar para limpiar)
  • poc/mustang_jit_uaf.c — PoC de etapa 1 (modos: jit N / control N / pressure N)
  • poc/build.sh — compilación cruzada con zig (musl estático armv7)
  • kernel/vmlinux — símbolos recuperados de la compilación exacta de la OTA (vmlinux-to-elf)

Compilación y ejecución```

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

- Aviso GHSL-2022-054: https://securitylab.github.com/advisories/GHSL-2022-054_Arm_Mali/
- Análisis del exploit de Mo para Pixel 6: https://github.blog/2023-01-23-pwning-the-all-google-phone-with-a-non-google-bug/
- Precedente del Fire HD 10 (trona), misma familia de bugs: ericpardee.github.io/fire-hd-ownership
- Portal OSS de Amazon: amazon.com gp/help/customer/display.html nodeId=200203720
- Hilo de desbloqueo en XDA (muerto para esta rev de hw): xdaforums.com/t/fire-7-2019-mustang-unbrick-downgrade-unlock-root.3944365/


## Apéndice de la sesión 3 (sondeo profundo de sellos de syscall)

Profundidades de copia-origen medidas (absolutas vs sp0 en entrada de syscall; el waiter abarca -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 (MEJOR — 0x1c antes de waiter+0x00)**
- poll entries @ -0x3e0 (completamente por debajo; lado equivocado)

Descartado en esta sesión:
- cadena io_submit demasiado superficial (~-0x130) | semtimedop: CONFIG_SYSVIPC=n (stub)
- configfs montado pero CERO subsistemas registrados (sin objetivos mkdir)
- /sys/kernel/debug, /config: denegado por SELinux para shell
- /proc/sys/kernel: getdents funciona (29 entradas listadas), solo pid_max es ABRIBLE;
  lecturas de kptr_restrict/hotplug/hostname/domainname todas denegadas
- El valor de escritura es SIEMPRE waiter+0 (dirección de pila del kernel, ejecutable, shellcode en
  +0x1c): rb_link_node *link = node, insert_color escribe parent-color — no
  es posible una variante de valor controlado sin estampado de campos del árbol (hueco -0x1d8..-0x1bc)
- Campos de despacho de doble desreferencia leen *(waiter+0)=1, *(waiter+4/+8)=0 — BLX 1/0
  (nf_hooks, net_families, inet[6]_protos, seq_file->op todos muertos)
- timer_list.function@+0xc y work_struct.func@+0xc leerían *(waiter+0xc) =
  pi_tree self-ptr = waiter+0xc EJECUTABLE — pero ninguna ruta encola waiter+0 como
  timer/work (corrupción de enlace / sin fuentes de encolado indirecto)
- Raíz-del-árbol-no-cero (slot de handler sysctl) = pointer-chase determinista a través de
  .text como rb-tree; termina en una palabra cero — simulable offline, pero aterrizar
  en un slot escribible útil es implausible

Pistas restantes para la sesión 4:
1. rutas profundas de ioctl: copia de ifreq en dev_ioctl (40B de datos de usuario) — medir
   profundidad de la cadena SyS_ioctl→sock_ioctl→dev_ioctl vs -0x1d8
2. Cualquier otra copia 0x1c más profunda que sendmmsg (nada encontrado aún)
3. Si la caza de superficie-de-estampado falla: reconsiderar construcciones encadenadas por recorrido o
   cazar clases de slots escribibles-llamados-con-cero aún no enumeradas


## SESIÓN 4 — LOS DOS AVANCES

### 1. El rtmutex_common.h de MTK es todo el misterio
MTK reemplazó el rt_mutex_top_waiter seguro frente a NULL de upstream por:```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

SIN COMPROBACIÓN DE NULL + BUG_ON. Cada ancla todo-ceros muere en *(NULL+0x1c); las anclas basura mueren en el udf. EL RECORRIDO REQUIERE: lock->waiters_leftmost (lock+8) debe apuntar a un waiter falso W (escribible) con W->lock (+0x1c) == lock.

Flujo del recorrido completamente mapeado (rt_mutex_adjust_prio_chain @ 0xc0189a58):

  • 9b44-9b54: reintento de cabecera; pi_blocked_on==NULL → salida limpia ret 0
  • 9abc-9adc: orig_waiter==NULL → omite comprobaciones de pi_waiters (adjust_pi siempre pasa NULL)
  • 9b10-9b28: comprobación de prio (prio==task->prio + MIN → salida 9b58)
  • 9b2c-9b38: trylock(lock+0) — ticket; falla → bucle de reintento con contador de escape (9a90-9aa8, límite @ *(0xc11189c8))
  • 9ba4-9bc0: comprobaciones de deadlock
  • 9bcc-9bd8: EL BUG_ON (leftmost→W→W->lock==lock o muere)
  • 9bdc-9c04: dequeue (árbol sobrante RB_CLEAR_NODE'd = VACÍO → omisión segura), escritura de prio/deadline
  • 9c04 bl: rt_mutex_enqueue → *link = waiter+0 en lock+4 ← LA ESCRITURA
  • 9c3c+: owner==NULL → ruta de salida limpia

2. La fuga de dirección de pila del kernel (mata el requisito de ausencia de direcciones)

/proc/self/task//stat campo 28 (kstkesp) devuelve el SP REAL del kernel para hilos bloqueados en syscall desde contexto de SHELL (verificado: valores distintos de cero observados).

  • waiter se bloquea en read(blocking_pipe) → stat → kstkesp
  • base de pila = kstkesp & ~0x1fff (arm32 THREAD_SIZE=8192)
  • dirección abs de rt_waiter = base + delta fijo (computable: sp0 = base+0x2000-0x48 pt_regs; waiter = sp0-0x1d8)
  • ¡TODOS los valores de sello autorreferenciales se vuelven computables!

Sello completo autoconsistente (tras la fuga):

  • L = waiter+0x24 (lock falso EN LA VENTANA — las 4 palabras controlables)
  • iov[0].base (waiter+0x1c lock) = L
  • iov[0].len (waiter+0x20 prio) = 1 (≠139)
  • W = waiter+0x1c; sello *(W+0x1c) = *(waiter+0x38) = L (BUG_ON pasa)
  • lock+0 (waiter+0x24) = 0; lock+4 (waiter+0x28) = 0 (la escritura aterriza aquí); lock+8 (waiter+0x2c) = W; lock+0xc (waiter+0x30) = 0 (owner NULL)

Estado del oráculo

  • crash durante el recorrido = el recorrido se ejecutó (sonda de dead-lock: crash determinista, código limpio)
  • recorrido limpio + sin escritura = trylock-fail retry-bailout (ancla kptr: palabra en runtime distinta de cero)
  • Todo es ahora determinista tras el arreglo de contaminación.

TODO de la próxima sesión

  1. Implementar la fuga: waiter se bloquea en pipe, main lee stat, computa base
  2. Sellar ventana autoconsistente, disparar recorrido → finalización sin crash = escritura probada
  3. Weaponizar: la escritura siempre aterriza en lock+4 (rb_link_node) — lock debe vivir en la ventana (única memoria totalmente controlada), así que investigación de selección de objetivo: ya sea encontrar el truco de slot-llamado-en-ventana, o construcción en dos etapas.

ESTADO FINAL DE LA SESIÓN 4 — EL MURO (caracterizado con precisión)

El cuadro completo

El recorrido se dispara de forma determinista (sonda de dead-lock: crash cada vez, código limpio). La escritura no puede aterrizar debido a una coincidencia triple de endurecimiento del kernel:

  1. Variante BUG_ON de rtmutex de MTK: lock+8 (leftmost) DEBE apuntar a W con *(W+0x1c)==lock. Las anclas todo-ceros/basura mueren. No existe patrón autorreferencial estático (6571 candidatos escaneados, 0 aciertos). Punteros en runtime desconocidos.
  2. Sin divulgación de direcciones del kernel desde shell:
    • kstkesp en arm32 = SP de USUARIO (task_pt_regs->ARM_sp) — no pila del kernel. MUERTO.
    • dmesg/pstore/pagetypeinfo/kallsyms/stack — todos denegados.
    • kptr_restrict=1 en runtime (las palabras ancla de fops también son distintas de cero en runtime — la ejecución del ancla kptr salió vía trylock-fail retry-bailout, no por éxito de trylock)
  3. Tablas fops en rodata: trylock strex aborta (crashes del barrido de la sesión 3).

La ventana de sello (waiter+0x1c..0x5b) es la única memoria con contenido controlado+conocido, pero su DIRECCIÓN es la incógnita que necesitamos. Las construcciones autorreferenciales todas requieren sellar una dirección del kernel como constante — circular sin una fuga.

Comparación con gitchw (por qué su escritura ARM32 funcionó, la nuestra aún no)

Su kernel 5.4 tiene rtmutex_top_waiter UPSTREAM (seguro ante NULL: if (!leftmost) return NULL) — las anclas de árbol vacío sobreviven, su escritura aterrizó en null_fops (escribible en su kernel). Incluso ELLOS están atascados en el dispatch ("ioctl reboot"). El árbol MTK 4.9.117 de Mustang tiene la variante BUG_ON — las tablets Fire OS 8 (GhostLock-5.10) tuvieron éxito porque sus kernels 5.10 son estilo upstream.

Hechos verificados de la sesión 4

  • El bucle de reintento del recorrido tiene un escape por contador (límite @ *(0xc11189c8)); trylock-fail sobre palabras ancla distintas de cero en runtime → salida limpia por retry-bailout (ejecuciones del ancla kptr)
  • La ruta sin-requeue (9ce4, recorrido COMPLETO) también desreferencia leftmost en 9d64 — sin escape
  • Los autopunteros RB_CLEAR_NODE existen como sobrantes en la ventana (waiter+0 y +0xc contienen sus propias direcciones) pero ninguna comparación de comprobación los usa de forma que evite sellar direcciones conocidas
  • Candidatos de mutex real (el mutex de cadena tiene waiter vivo = BUG_ON pasaría) — pero &chain_mutex es una dirección de heap, inalcanzable sin fuga

OPCIONES DE LA PRÓXIMA SESIÓN (ordenadas)

  1. Caza de punteros del kernel en logcat: los HALs/daemons de Amazon son charlatanes; cualquier puntero del kernel registrado (incluso obsoleto) desbloquea la construcción. Barato de probar.
  2. Comportamiento de %pK en /proc/net en ESTA build: algunos árboles 4.9 imprimen punteros sin hashear en /proc/net/tcp,udp,unix para lectores sin privilegios. Probar en vivo.
  3. Rutas de salida de hilo sobre pi_blocked_on colgante (one-shot, desreferencias diferentes).
  4. Revisar el bug de JIT de kbase archivado con el conocimiento acumulado de 4.9.

ADDENDUM DE LA SESIÓN 4 — CAZA DE FUGAS: AGOTADA (definitivo)

Probado y muerto desde el dominio shell:

  • /proc/net/{tcp,unix,packet,netlink,ptype}: hasheado con %pK a 00000000 (kptr_restrict=1)
  • /proc/timer_list: LEGIBLE pero punteros puestos a cero con %pK (símbolos visibles, sin direcciones)
  • logcat: sin punteros del kernel en la cháchara de Amazon/wpa
  • kstkesp (stat f28): SP de USUARIO en arm32 (task_pt_regs->ARM_sp)
  • Nodos MTK (/proc/ged, mtk_cmdq_debug, mtktz, ptp, chip, aed, driver/*): todos denegados por SELinux
  • /proc/{iomem,vmstat,kmsg,keys,crypto,slabs...}: denegados
  • /sys/kernel/notes: denegado
  • CONFIG_VECTORS_BASE=0xffff0000 (vectores altos — NULL+0x1c falla)
  • CONFIG_KUSER_HELPERS=y (kuser en 0xffff0000, no página 0)

CONCLUSIÓN: GhostLock en mustang requiere una divulgación de dirección del kernel que este kernel no expone al dominio shell. El lock falso autorreferencial no puede construirse sin ella.

PUNTO DE DECISIÓN

(a) Molienda determinista por arranque: reiniciar → calibrar dirección de pila vía oráculo de crash (~20-30 reinicios), verificar reproducibilidad. Apuesta arriesgada — la asignación de pila de hilo en arranque tardío probablemente no es estable. (b) PIVOTAR de vuelta a kbase CVE-2022-38181 con los activos acumulados: vmlinux de build exacta + código fuente completo + toolchain + disciplina de trazas O_SYNC + conocimiento profundo de 4.9. El bloqueador original (panic del destroy-worker durante la expulsión JIT) es un problema de timing de spray, ahora mejor comprendido. (c) Parar en un honesto ~45%: trigger probado, recorrido mapeado hasta la instrucción, escritura bloqueada por BUG_ON de MTK + sin fuga.

Recomendado: (b) — el bug de kbase está verificado-presente en este código fuente exacto, tenía un trigger funcional, y su bloqueador es mecánico, no arquitectónico.

SESIÓN 5 — ETAPA 2 PROBADA (opción b ejecutada)

Re-diagnóstico: el "panic incondicional del destroy" nunca existió

El modo step (alloc id=1 → DONT_NEED → presión de 700MB → MEM_QUERY, SIN free) sobrevive: query=-1 (región liberada por el destroy worker, rbtree-limpio). La ruta del worker es byte-idéntica al flujo legal de JIT_FREE-bajo-presión. El crash de la sesión 1 era siempre la desreferencia colgante de JIT_FREE; su línea de log se perdió porque el panic mata adbd a mitad del flush. Verificado dos veces más con el modo uaf (free desnudo → panic, mismo corte de log). El flujo GHSL-2022-054 está totalmente vivo en esta build.

Inventario completo de primitivas (desensamblado del vmlinux exacto)

kbase_jit_free(kctx, reg) @ 0xc058495c con reg falso totalmente controlado:

  • reg->cpu_alloc NULL → tamaño respaldado 0 → bloque de recorte omitido (0xc0584978)
  • decremento de bin: kctx+0x147dd (byte) + kctx+0x147de+bin_id (byte)
  • mark_reclaim(reg->gpu_alloc) @ 0xc059b158: cadena K=*(gpu_alloc+0x38) → *(K+0x1429c)==0 omite mm-atomics → atomic_sub nents@K+0x141c8, D=*(K+4) → atomic_sub nents@D+0x538. Con nents=0 todas las escrituras son stores no-op (strex del mismo valor).
  • reg->flags |= 0x100000 (escritura en el falso, benigna)
  • shrink_cpu_mapping sale temprano cuando new==old (nents=0 → return)
  • list_add(gpu_alloc->evict_node, &kctx->evict_list): cabeza de evict_list @ kctx+0x1427c; escribe en gpu_alloc+0x18/0x1c (debe ser escribible)
  • La ruta WARN (0xc0584bd4) es no fatal (sin panic_on_warn) y CONTINÚA
  • UNLINK @ 0xc0584b08/b0c: r3=*(reg+0x3c) prev, r2=*(reg+0x38) next → *(next+4)=prev; *(prev+0)=next — dos escrituras-arbitrarias-qué-dónde, luego relink de reg+0x38 en jit_pool_head @ kctx+0x148e8

Cadena estática de gpu_alloc falso (escaneo offline del vmlinux, /tmp/opencode/scan_s.py)

9 candidatos; S=0xc118b7ec (datos xfrm, inactivo en este dispositivo): *(S+8)=0 (nents), *(S+0x18)=S+0x18 (evict_node vacío → sin WARN), K=*(S+0x38)=0xc118b820 → *(K+0x1429c)=0, K/D+0x141c8/+0x538 todos en datos escribibles. Objetivos del oráculo preparados: init_uts_ns.name.nodename=0xc110d561 ("(none)", legible vía uname), scratch P=0xc118bd58 (ceros de xfrm). Evitar S=0xc111cba4 (adyacente a tracepoint). CONFIG_DEBUG_RODATA=y → todos los objetivos de escritura deben estar en .data/.bss (bss 0xc11d9000-0xc12d9000).

Ingeniería de spray (lo que funcionó, lo que no)

  • add_key (CONFIG_KEYS=y): denegado por SELinux para shell. Muerto.
  • buffer de valor de setxattr: kvmalloc(96)+copy_from_user ocurre ANTES de la comprobación de SELinux → la danza de alloc es a prueba de SELinux incluso cuando la llamada falla; transitorio (liberado al final de la syscall), los bytes persisten en +4..95 (el ptr de freelist sobrescribe +0..3 = rblink, no usado por kbase_jit_free)
  • kbase_va_region en sí: kzalloc(72) → kmalloc-96! MEM_ALLOC(va=0x40, commit=0x10) pone SOLO la región en kmalloc-96 (alloc phy → 384) → tipo de reclamación determinista. Una víctima de región real hace que kbase_jit_free complete a través de estado totalmente legal (jit_node vacío → auto-unlink).
  • Spray secuencial post-presión: SIEMPRE falla — el worker libera el slot a mitad de presión en slabs parciales (SLUB: free a slab no activo ≠ freelist de cpu); bajo presión nuestros allocs fallan → volumen neto cero → sin rotación
  • Sprayers fijados + caps: aún fallan (cap de 512 agotado antes de la expulsión; el worker puede correr en cualquier cpu)
  • GANADOR: spray con commit_pages=0 — sin páginas físicas → los MEM_ALLOC tienen éxito durante toda la tormenta de presión → ~6000 asignaciones netas → rotación de lista parcial garantizada. 8 hilos (2/cpu, cpus 0-3 hardcodeadas — /proc/cpuinfo está filtrado a 1 core para shell, usar Cpus_allowed_list) + hijo de presión fijado a cpu0 + lote de retención de 16 allocs tras el join.
  • Trampa de query(jit_va): post-reclamación, las regiones del spray reutilizan la VA liberada en la zona custom → query=0 es ambiguo (original-viva vs spray-cubriendo-VA)

ACIERTO DEL ORÁCULO — redirección verificada por máquina

Ejecución spray 700 2026-09-11: 5895 regiones rociadas durante la presión, JIT_FREE sobre id=1 colgante completado sobre una región reclamada, luego JIT_ALLOC(0x40, bin 0) recorrió jit_pool_head y devolvió la VA de la región rociada #4251 (0x142701000) — la región exacta que consumió el puntero colgante. Kill del proceso después: teardown de kctx limpio, sin crash. Etapa 2 completa: redirección UAF determinista con tipo de objeto + contenido controlados.

Plan de la etapa 3 (unlink de bytes crudos)

La reclamación de tipo región da supervivencia a la danza legal pero jit_node está INIT'd a sí mismo → sin primitiva de unlink. Se necesitan bytes crudos en +0x38/+0x3c:

  1. sellado con xattr: alternar ráfagas de alloc de región (volumen neto → rotación de slab) con tormentas de xattr (sellar cada slot de cabeza, los bytes persisten post-free) → ventana silenciosa → desreferencia
  2. o cmsgs de sendmsg fijados (optmem_max=10240 → ~106 × 96B retenidos)
  3. luego: W1 *(N+4)=P con P=página de shellcode de userspace (¡sin PAN!) — candidatos: fops const son .rodata (DEBUG_RODATA) → apuntar a fn ptr no-const en .data, o cabeza de lista formats de binfmt, o proc_handler de sysctl (verificar escribibilidad de la tabla). fallback: modprobe_path vía escrituras encadenadas por bytes (los valores deben ser direcciones escribibles — usar objetivos con forma de puntero)
  4. sin-KASLR + vmlinux exacto: prepare_kernel_cred 0xc0149e3c, commit_creds 0xc014993c

SESIÓN 5B — ETAPA 3: arma construida, carrera de reclamación aún no ganada

Hecho

  • Arma de etapa 3 completa y preparada (poc/stage3.c):
    • Objetivo: kern_table[pid_max].proc_handler @ 0xc1113f40 (escribible .data, verificado vía escaneo de punteros a strings + handler == proc_dointvec_minmax)
    • N = entrada de shellcode 0x11111112 (mmap 0x11111000; W1 sobrescribe entry+4, omitido por b +8; W2 escribe N en P = campo handler)
    • shellcode ring0 de arm32 codificado a mano: prepare_kernel_cred(0) + commit_creds + ret 0; trigger = read /proc/sys/kernel/pid_max (legible desde shell); corre en contexto de su propia tarea → los creds se aplican a nosotros
    • modo oráculo benigno escribe uts nodename (0xc110d561, desalineado ok)
  • Selección de primitiva de spray:
    • pines de sendmsg NETLINK_USERSOCK (copia de msg_control a kmalloc-96, retenida mientras bloqueado, nunca parseada): denegado por SELinux (socket create EACCES)
    • sendmsg unix/UDP: el parseo de cmsg envenena los bytes del payload ✗
    • eventos de inotify: inotify_handle_event hace kmalloc de name_len+0x1d, bytes del nombre (totalmente controlados, restricción sin NUL/slash) en event+0x1c; name_len=60 → kmalloc-96; encolado → retenido; 4 instancias → 4 eventos por rename; SELinux-OK desde shell. Falso rediseñado sin NUL: cpu_alloc apunta a S (nents@S+8 = 0 → misma semántica que NULL)
  • Cadena mecánica validada de extremo a extremo (drain4, ejecución sin expulsión): 20K eventos drenados + 13.5K renames finales multi-cpu + JIT_FREE + oráculo + pausa, todo limpio. El log O_SYNC (/data/local/tmp/s3.log) sobrevive a los panics — forense exacta del punto de crash.

Intentos de reclamación sobre el slot liberado (todos fallados hasta ahora)

varianteresultado

Hipótesis de trabajo: carrera de basura de tormenta — entre el destroy worker liberando el slot (a mitad de tormenta) y el kill-child/silencio, la actividad de reclamación residual toma el único-slot-libre del slab víctima con bytes no-payload. El spray de regiones (etapa 2) gana porque asigna continuamente DURANTE la tormenta; los renames no pueden.

Trampas encontradas

  • bug del toggle de spray: la fuente del rename debe ser el nombre del payload (era el nombre temporal → ENOENT tras 2 oleadas → solo 512 eventos en total)
  • el hostname del dispositivo es "localhost"/varía — el oráculo compara antes/después
  • st3 pausado + pkill → dispositivo BLOQUEADO (¿teardown con 33K eventos?!) — matar procesos pausados solo vía reinicio; segundo bloqueo duro de la sesión
  • /proc/cpuinfo muestra 1 cpu a shell; usar Cpus_allowed_list

Próximos movimientos (ordenados)

  1. drain4 @ 500MB (umbral de expulsión confirmado ahí), poll de 1ms, kill instantáneo, final de 4-cpu × 3200 — encoger la ventana de tormenta
  2. churn de sellado con xattr (el alloc-copy de setxattr ocurre ANTES de la comprobación de SELinux — a prueba de SELinux) concurrente con tormenta + rotación de regiones, terminar-con-sello
  3. aceptar la reclamación de región (probada) + encontrar una primitiva de segunda etapa sobre el estado de víctima-región (análisis de doble jit_free negativo hasta ahora)

SESIÓN 5C — el bloqueador, caracterizado con precisión

Resultados empíricos de esta sesión

  • drain4@500 (poll de 1ms, kill instantáneo, +4.5K renames): aún crash en la desreferencia
  • solapar el final con el kill (drain5): crashea ANTES (los renames durante la tormenta de recuperación del kill golpean un fallo a nivel de sistema) — solapamiento abandonado
  • EXPERIMENTO AISLANTE (modo iso): timing idéntico, final con REGIONES commit-0
    • oráculo de reutilización de pool de etapa 2 → ACIERTO DEL ORÁCULO → timing/alcanzabilidad están BIEN; los eventos son el problema
  • eventos con ciclo de máscaras (rotación MOVED_TO/CREATE/DELETE para vencer inotify_merge): aún crash en la desreferencia
  • CONFIG_MEMCG=n → eventos y regiones comparten UN kmalloc-96 (teoría memcg muerta); inotify_merge también compara nombres (teoría de merge muerta — nuestros nombres alternantes nunca se fusionaron; los eventos estuvieron encolados y retenidos todo el tiempo)
  • /proc/slabinfo ausente; /proc/self/pagemap legible pero PFN puestos a cero (enmascarado post-4.0, sin CAP_SYS_ADMIN)

EL BLOQUEADOR REAL (dos partes, ambas probadas)

  1. El soft-job finish corre en contexto de WORKER del job-scheduler de kbase (jd_run_atom ← dispatch de js, mali_kbase_jd.c:81-112/677), no inline en el ioctl de submit → current->mm es el de un hilo del kernel → el elegante diseño de "apuntar los punteros del falso a nuestro propio mmap de userspace" (¡sin PAN!) FALLA de forma no determinista. 5/5 crashes con un falso por lo demás perfecto.
  2. Por lo tanto gpu_alloc debe apuntar a memoria del KERNEL con una cadena en runtime sobrevivible: K=*(S+0x38) legible, *(K+0x1429c)==0 en runtime (omite mm-atomics), K+0x141c8 escribible, D=*(K+4) → D+0x538 escribible, nents *(S+8) preferiblemente 0. Los 9 candidatos S offline fueron validados contra bytes de ARCHIVO — la deriva en runtime (init de xfrm/tracepoint) los deja sin verificar. Una cadena incorrecta = crash = reinicio (~3 min de ciclo).

Planes de la sesión 6 (ambos totalmente especificados)

A. Falso con spray de physmap (ret2dir, clásico arm32 sin-PAN): rociar ~450MB de páginas de usuario conteniendo cada una el patrón falso preparado para UNA dirección adivinada G (G&0xfff = 0x141 para bytes de nombre sin NUL; S=G; K=G-0x141b4 para que K+4 caiga en página; K+0x141c8/+0x1429c → G+0x10/+0xd4 en página; D=G+0x300; los stores sub-0 extraviados golpean RAM mapeada aleatoria - inofensivo con nents=0). El spray sirve también como presión de expulsión (anon sucio = no expulsable → solo se necesitan ~100-200MB extra). Probabilidades ≈ 45% (acierto de página) × ~50% (palabra K+0x1429c ajena es cero... si K+0x1429c se mantiene en página según el layout anterior, probabilidades = solo acierto de página). Fallo = crash = reinicio, reintentar. B. Fuerza bruta sobre los 9 candidatos S estáticos (xfrm 0xc118b7ec primero, adyacente a tracepoint 0xc111cba4 segundo...): 1 reinicio por candidato, payload de oráculo benigno primero, arma al acierto. C. G exacta basada en pagemap (muerta: PFNs enmascarados) — no revisitar.

SESIÓN 5D — falso de physmap construido; barrido de G 0/3; confusores eliminados

Establecido esta sesión (todo verificado en binario/dispositivo)

  • Identidad de caché CONFIRMADA igual: región = kmem_cache_alloc_trace( kmalloc_caches[7], GFP|0x8000, 0x48) [desensamblado de kbase_alloc_free_region]; evento = __kmalloc(89, GFP) → kmalloc-96. Eventos y regiones PUEDEN compartir la caché de la víctima. (MEMCG off; conjunto de caché único.)
  • cola de inotify: ≥5000 eventos retenidos, sin desbordamiento, sin colapso por merge (ejecuciones qmeas 1000 y 5000) — el spray de eventos persiste
  • iso2 (spray de physmap + final de regiones + oráculo): ACIERTO — el spray de physmap NO rompe la reclamación de regiones; maquinaria sólida
  • eventos vs reclamación de regiones: regiones 3/3 (iso, iso2, etapa-2), eventos 0/10 PERO las 3 ejecuciones pmap se explican por fallos de G a 0.3-0.45 de probabilidad cada una (P(3 fallos|eventos-funcionan) ≈ 0.2-0.3 — no concluyente)
  • modo mix confundido: spray de 480MB asfixia los renames post-kill (+0); 350MB OK (+4452); hilos giratorios de rename fallido también perturban la reclamación de regiones (mix crasheó, iso2 limpio)
  • query_commit devuelve -1 también en EINVAL — instrumentado; EINVAL observado = fallo de búsqueda en rbtree = genuinamente liberado ✓ (no un falso positivo)

Diseño actual de pmap (en stage3.c, modo pmap/mix/iso2)

  • G horneada en cada página rociada (offset 0x2a4): nents@+0x2ac=0, evict self@+0x2bc/0x2c0, K=G+0x100@+0x2dc, D=G+0x200@+0x3a8; K+0x141c8/-0x1429c caen ~20 páginas arriba (store sub-0 inofensivo / la lectura debe ser 0-o-válida). PM_SPRAY_MB 350, barrido de G probado: c2a412a4, c2f4b2a4, c2a7d2a4 — todos crashean en la desreferencia
  • cordura del rango de physmap: RAM 1GB → physmap ~0xc0000000-0xc3fffffff; los carveouts de MTK (GPU/M4U/secure) pueden ocupar trozos — minas terrestres de G

TODO de la sesión 6 (ordenado)

  1. Extraer el código fuente completo del kernel (tarball de 2.2GB en ~/Desktop/amazon-mustang/ — platform.tar): obtener arch/arm + mm/ + drivers/of + mapeos reservados de MTK → computar el mapa de carveouts → apuntar G a subrangos de physmap verificados como RAM; también verificar la geometría de la caché kmalloc (¡ARCH_KMALLOC_MINALIGN!) y el bit GFP 0x8000
  2. Barrido de G con conjeturas informadas por la colocación (múltiples reinicios, variar el tamaño del spray para descorrelacionar)
  3. Si el barrido de G se agota: reconsiderar payload multi-G o candidatos S de estáticos plausibles en runtime (campos de puntero adyacentes a uts fallaron: NULL-K)

SESIÓN 6 — descubrimiento de zram, extracción de fuente, pregunta de eventos aún abierta### Código fuente completo del kernel ahora extraído

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

  • bit GFP 0x8000 = ___GFP_ZERO (solo kzalloc; sin división de caché)
  • kmalloc-96 es una caché real de 96 bytes (sin HWCACHE_ALIGN en cachés kmalloc)
  • mustang.dtsi: nodo de memoria 0x40000000/512MB — extendido por el preloader (el dispositivo muestra MemTotal 977MB); CONFIG_VMSPLIT_3G, HIGHMEM=y
  • zram0 ACTIVO (SwapCached > 0) → "anon sucio = no desalojable" era INCORRECTO: el spray de physmap se intercambia bajo presión → el alias G queda obsoleto → se añadió physmap_retouch() tras el kill (falla todas las páginas del spray de vuelta antes del trailing/deref)

Ejecuciones de esta sesión (todas con O_SYNC registrado, ~6 reinicios)

Veredicto sobre eventos (bayesiano, honesto)

Las regiones reclaman: 3/3. Eventos: 0/~12 intentos incluyendo 28K asignaciones exclusivas con ventaja inicial y fake correcto por construcción. Si los eventos reclaman con p_hit(G)≈0.35, cinco fallos de pmap ≈ 11.6% — posible pero ahora improbable (~10-15%). O los eventos estructuralmente no pueden tomar este slot (razón desconocida — misma caché, mismo contexto, mismo timing) o nuestras conjeturas de G fallan sistemáticamente (sesgo en el límite de highmem, colocación del asignador).

Árbol de decisión de la sesión 7

  1. Resolver G primero (barato, sin exploit): instrumentar temporalmente el flujo ISO2 — trailing de región + oráculo — pero hacer que el EVENTO de PAYLOAD sea un fake de physmap y comprobar si ALGÚN G en un rango barrido produce un cambio de nodename sin que las regiones compitan (pmap puro, barrido de G sobre ~0xc1500000-0xc2a00000 centro de lowmem, 1 G por reinicio, 4-5 reinicios)
  2. Si el barrido de G se agota → eventos declarados muertos → buscar asignadores alternativos de kmalloc-96 de bytes crudos alcanzables desde shell (auditoría: seq_file, tty ldisc, fdtable, sk_filter (bloqueado: campo code en +0x38), netlink nlmsg (skb ✗), keys (denegado)) — o revisar primitivas de región de dos etapas (análisis negativo hasta ahora)
  3. Considerar desbloqueo de UART/ramoops vía root más tarde; no perseguir

SESIÓN 7 — bombas en cmdline; el misterio de los eventos ahora precisamente acotado

CONFIG_CMDLINE de mustang_defconfig (verdad fundamental para la colocación):

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

  1. vmalloc=496M → physmap = 0xc0008000..~0xc2080000 SOLAMENTE (los 520MB bajos de RAM) — TODAS las conjeturas previas de G (0xc2a4xxxx+) estaban en ESPACIO VMALLOC. Cada conclusión de "fallo de G" de las sesiones 5D/6 queda invalidada; la interpretación del crash se mantiene pero el barrido apuntaba al mapa equivocado.
  2. slub_max_order=0: todas las páginas de slab son order-0
  3. KMALLOC_MIN_SIZE = ARCH_KMALLOC_MINALIGN = 64 (L1_CACHE_SHIFT 6) → los casos especiales de kmalloc_index() para 96/192 están DESHABILITADOS → región kzalloc(0x48=72) → caches[7]; evento __kmalloc(89) → caches[7] (disasm + include/linux/slab.h:287 verificado) — AMBOS en la caché fusionada de 128 bytes "kmalloc-128/96". Identidad de caché: RE-CONFIRMADA igual.
  4. zram activo → se reemplazó el spray de physmap anon por kbm_spray: 160 × 2MB regiones MEM_ALLOC de kbase (GFP_KERNEL → ZONE_NORMAL → solo lowmem, fijadas → inmunes a zram), patrón escrito vía mmap de CPU — rango correcto, ~65% de cobertura de lowmem

Ejecuciones (O_SYNC registrado)

  • kbm + G=0xc16412a4: crash en deref (+4831 renombrados)
  • kbm + G=0xc1c4b2a4 @200MB: SIN desalojo (query=16 — la calibración de presión varía con el spray fijado; libre legal, sobrevivió)
  • kbm + G=0xc1c4b2a4 @400MB: desalojo ✓, +4053 renombrados, crash en deref
  • Registro de G en rango válido: 0/2. Si los eventos funcionan con ~65% de cobertura: P(2 fallos) ≈ 12%. La pregunta sigue abierta pero más acotada que nunca.

La pregunta, forma final

Las regiones toman el slot de víctima 3/3; los eventos 0/13. Misma caché (probado a nivel de código fuente + disasm), mismo contexto de proceso, mismas cpus fijadas, mismo timing post-kill, miles de asignaciones con ventaja inicial exclusiva. Mecanismo desconocido. Sospechosos restantes: correlación de tasa/frecuencia de asignación con la rotación de lista parcial (ioctls de región ~1ms de diferencia vs renombrados de eventos ~100µs de diferencia — ¿direcciones opuestas?), o detalles del orden de la freelist de SLUB bajo slub_max_order=0 que favorecen... poco claro.

TODO de la sesión 8

  1. Experimento de grado instrumental: DOS variantes de payload de evento con valores N/P DIFERENTES alternándose (dos conjuntos de nombres) — si el nodename cambia alguna vez, se identifica el ÚLTIMO ganador; barrer G en [0xc1200000..0xc2000000] con spray kbm, presupuesto de 3-4 reinicios
  2. Si sigue 0/N: abandonar los eventos. Alternativas clasificadas: a. arrays pipe_buf vía F_SETPIPE_SZ(4096 → 1 buf? no — 16 bufs = kcalloc(16, 28)=448→512 ✗) — muerto b. auditar fs/notify + fs en busca de otros objetos kmalloc-128 que lleven nombre/datos (¿eventos fanotify? mq off; fanotify necesita grupos...) c. buffers seq_file (kmalloc(PAGE_SIZE) ✗) d. filtros sock (colisión de campo code en +0x38 ✗) e. aceptar la reclamación de tipo región + encadenar un SEGUNDO bug/técnica
  3. Reexaminar POR QUÉ ganan las regiones — quizás instrumentar vía múltiples jit ids: N slots colgantes, carrera región-vs-evento por slot, el oráculo detecta qué spray tomó qué slot → huella estadística del mecanismo

SESIÓN 8 — ESCRITURA ARBITRARIA LOGRADA EN EL DISPOSITIVO; queda el misterio del dispatch

EL HITO```

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

root@kitploit:~
**La cadena completa de bytes en crudo funciona en el dispositivo real**: el evento reclama el
slot de región liberado → kbase_jit_free desreferencia nuestro falso (S=página physmap del
spray kbm, G=0xc154b2a4) → el unlink ejecuta nuestras dos escrituras.
Verificado repetidamente con el payload benigno (escritura de nodename).

### Cadena de descubrimientos de la sesión
1. las páginas kbm eran GFP_HIGHUSER → HIGHMEM, invisibles para physmap
   (mali_kbase_mem_pool.c:164!) — corregido mediante el desbordamiento de zonelist: 260 × 2MB
   de spray > highmem-free → el excedente aterriza en ZONE_NORMAL (physmap)
2. Los primeros intentos de arma fallaron: el objetivo W1 N+4 era una página USER —
   el unlink se ejecuta en ctx de kworker (sin mm) → fallo. Corregido incrustando el
   shellcode de ring-0 DENTRO del patrón physmap en page+0x600 (direct map RWX en
   arm32 non-LPAE) — código residente en kernel, sin necesidad de ret2usr
3. Bug de offset en ctl_table: proc_handler está en entry+0x14, no en +0x18 (el
   escaneo original lo tenía bien; mi define estaba mal) — estaba escribiendo extra1
4. Regresión por sobrepresión encontrada y revertida: el presupuesto de kid 20×100MB +
   trailing de 10s rompía el reclaim; la configuración que funciona es 2 kids/200MB +
   5s/+3200 trailing (el benigno acertó 1/1 tras revertir)
5. **diag2: W2 → &pid_max global (0xc1114d7c) → la lectura devuelve nuestro valor
   (-1055861411 = 0xc110d55d como int32) — escritura + lectura de vuelta PROBADA**

### El misterio restante (a un experimento de cerrarse)
diag1 con la dirección CORRECTA del handler (0xc1113f3c): W1 se dispara (nodename
cambia), W2 debió ejecutarse (siguiente instrucción) — sin embargo las lecturas de pid_max
siguen devolviendo valores limpios → el campo handler que escribimos no es el que
el inode despacha. diag3 (en cola; necesita un boot con acierto): W2 →
campo entry->data (0xc1113f2c) apuntando a nodename — si la lectura entonces
muestra los bytes de nodename como int, nuestra entry ESTÁ viva y solo el offset del handler
está de algún modo mal; si no se ve afectada, el inode usa una copia shadow de la tabla
y cazamos la viva.

### Realidad de la tasa de aciertos
Moneda al aire por boot (~25-40%), agrupados; varios boots con fallo seguro/crash
seguidos es normal. Aproximadamente 1 de cada 3-4 boots es un acierto. Mantén la configuración
que funciona EXACTAMENTE (2 kids, 5s trailing, kbm de 260 regiones, G=0xc154b2a4).

### TODO de la sesión 9
1. Completar diag3 en un boot con acierto (reintentar hasta "W1 FIRED")
2. Si la entry está viva: re-verificar empíricamente el offset del handler (escribir
   la dirección de proc_dostring como handler vía... N debe ser igual a un valor
   útil — usa el unlink para escribir entry->data en su lugar y pivotar: p. ej.,
   data=selinux_enforcing-adyacente...)
3. Si es tabla shadow: localizar la viva — kallsyms no tiene símbolos de datos;
   candidatos: escanear el comportamiento de /proc/sys, o encontrar una segunda región
   ctl_table mediante el patrón de lista de headers en .data (entries con stride de 0x20
   con handler=proc_dointvec_minmax y maxlen=4 — enumerar TODAS y
   hacer diag-write en cada una)
4. Clase de objetivo alternativa que evite el dispatch por completo: punteros a función
   en .data llamados desde rutas alcanzables por shell (requiere auditoría)
5. La primitiva de escritura en sí está HECHA — cualquier objetivo fiable de dirección
   de kernel ahora basta para root

## SESIÓN 9 — nf-WEAPON: reclaim+unlink+G-hit PROBADO EN EL ARMA; solo queda el recorrido del hook

### El nuevo diseño del trigger (reemplaza por completo la ruta del handler sysctl)
La idea del usuario traducida a memoria de kernel: sin archivo SUID (el sistema es
dm-verity RO; la primitiva escribe RAM de kernel). En su lugar: **hook netfilter falso**.
Este kernel tiene el backport común de Android de la NUEVA
API nf_hook_entries, pero implementada como LISTA ENLAZADA (verificado por
disasm de nf_hook_slow + helper 0xc09897d4):
- `__ip_local_out(net, sk, skb)` carga la CELDA de entries desde
  **[net+0x58c]**, la almacena en state+0x1c, llama a nf_hook_slow
- recorrido: `entry = *cell`; while(entry){ if (state->[4] <= entry->[0x20])
  call entry->[0xc](entry->[0x14], skb, state); entry = entry->[0]; }
  — es decir, **fn@entry+0x0c, priv@entry+0x14, priority@entry+0x20,
  next@entry+0x00**; state+4 = umbral INT_MIN (siempre pasa)
- init_net = **0xc1104548** (CONFIG_NET_NS=n → sock_net() inlinea la
  constante; 6678 refs movw/movt, campeón del histograma; confirmado de forma cruzada por
  el propio literal de nf_hook_slow). OBJETIVO: **[init_net+0x58c] = 0xc1104ad4**
- El hook LOCAL_OUT se ejecuta en el contexto del proceso EMISOR → el commit_creds(prepare_kernel_cred(0)) de nuestra hookfn da root al proceso que envió
  el paquete. Trigger = sendto(127.0.0.1:9) UDP.

### Diseño del modo nf (poc/stage3.c, modo `nf 200`)
- páginas de patrón kbm (relativas a página, única fuente de verdad — el viejo
  arma tenía TRES bugs ahora corregidos: proc_handler@+0x14 no +0x18; el objetivo W1
  debe ser memoria de KERNEL (ctx de kworker, sin mm); código incrustado en
  page+0x600 vs el desajuste entry G+0x600=page+0x8a4):
  - +0x2ac/+0x2bc/+0x2c0/+0x2dc/+0x3a8: cadena falsa de phy-alloc (sin cambios)
  - +0x600: hookfn nf_code (almacenamiento de marcador + prepare_kernel_cred +
    commit_creds + return NF_ACCEPT(1))
  - +0x700: entry falsa {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 (aterriza en nuestra página), W2: *(init_net+0x58c)=cell

### Instrumentación AUTO-DIAGNOSTICABLE (kbm_scan_for)
Se conservan los mapeos de CPU de kbm; tras el free escaneamos cada página
rociada en busca de una palabra conocida:
- scan(HOOKS_PTR_ADDR) en page+0x744 → prueba reclaim + unlink + revela
  qué página física respalda la conjetura de G
- scan(0x600d600d) en page+0x7f0 → prueba que la hookfn SE EJECUTÓ
  (nf_code escribe este marcador como su 2ª acción)

### LA EJECUCIÓN QUE IMPORTA (2026-09-11, final de la sesión 9)```
[+] W1 CONFIRMED: region 135 page+0x185000 <- 0xc1104ad4 (reclaim + G hit!)
[+] nf trigger done, uid=2000 euid=2000

La cadena de armas completa se disparó en un arranque real: el evento reclamó el slot, el fake se ejecutó, el unlink se ejecutó, la página G-guess (0xc154b000) era genuinamente nuestra (región 135 página 389). W2 = *(0xc1104ad4)=cell es la instrucción adyacente — debe haberse ejecutado. Sin embargo, el sendto UDP no nos dio root → el fallo está DENTRO de la ruta del hook: semántica del walk, el cableado de [state+0x1c], la comparación de prioridad, o los campos de entrada. (Se añadió el experimento del marcador para discriminar hookfn-ejecutado-vs-no; solo arranques con crash antes del final de la sesión — aún sin datos limpios.)

Aprendizajes sobre tasa de acierto / estado de arranque (ganados con esfuerzo)

  • CONFIGURACIÓN FUNCIONAL (no tocar): 2 hijos × 100MB de presión escalonada, 100×50ms/+3200 renames finales, spray kbm de 260×2MB, G=0xc154b2a4, pre-drenaje ~5000 renames
  • Sobre-presión (20 hijos / 10s finales) ROMPE la reclamación — revertido
  • El asentamiento del arranque importa: las ejecuciones lanzadas inmediatamente tras boot_completed compiten con las asignaciones de inicio del sistema → rachas en frío; asentar 60-90s tras el arranque antes de ejecutar
  • "W1 no se disparó" (comprobación de nodename) NO TIENE SENTIDO para el payload nf — W1 escribe en nuestra página; usar kbm_scan_for en su lugar
  • Arranques con crash-en-free ≈ slot basura o conversiones de fallo de G; el control benigno (pmap 200) es la comprobación de cordura del entorno (4/4 aciertos en caliente; fallo con crash en frío)
  • Los directorios /data/local/tmp/.w* se limpian al inicio de la ejecución (los directorios acumulados degradan la reclamación)

TODO de la sesión 10 (árbol de decisión, en orden)

  1. Ejecutar nf 200 en arranques con retardo de asentamiento hasta que aparezca la línea W1-CONFIRMED, luego leer la línea MARKER: a. marcador PRESENTE, uid!=0 → fallaron las creds del shellcode (comprobar las direcciones de prepare_kernel_cred/commit_creds; codificaciones blx) b. marcador AUSENTE → el walk nunca nos llamó: verificar con un SEGUNDO marcador escrito por... siguientes diagnósticos: hookfn que SOLO escribe el marcador y devuelve 1 (sin creds) — si sigue ausente:
    • volcar nuestros bytes de entrada reales mediante el mapeo de CPU justo antes del trigger (¡son nuestros para leer!)
    • comprobar si [init_net+0x58c] siquiera se consulta: usar la escritura para en su lugar corromper algo observable (p. ej. apuntarlo a una celda cuyo *cell = entrada con fn = una función del kernel como kfree → crash inmediato al disparar = el campo SÍ se consulta)
    • reverificar el offset 0x58c: quizá hooks_ipv4[NF_INET_LOCAL_OUT] vive en un índice distinto (¿NF_INET_POST_ROUTING=4?)
  2. Si el walk nos llama: arreglar las creds → root → luego el plan del usuario: setenforce 0; cp /system/bin/sh /data/local/tmp/su; chown root; chmod 6755; verificar ls -la; dejar los archivos marcador
  3. Persistencia (post-root): parche de la imagen de arranque vía /dev/block/by-name/boot + deshabilitar dm-verity, o estilo Magisk; su en /data por sí solo es uid0-en-dominio-shell tras reiniciar (SELinux vuelve a enforcing) — setenforce 0 es solo en tiempo de ejecución
  4. Notas de limpieza: los procesos st3 pausados tienen estado kctx corrupto — matar solo mediante reinicio; el hijack de nf rompe los hooks LOCAL_OUT para todo el tráfico — reiniciar tras root para restaurar

Adiciones de recursos de esta noche

  • Modos de poc/stage3.c: pin/root/drain{,2,3,4,5}/iso — arma completa + oráculo + arnés de aislamiento, forense de puntos de crash con O_SYNC
  • Infraestructura de fake en espacio de usuario (ufake_prep) — CONSERVAR pero solo usable si alguna vez se encuentra una ruta de deref inline de contexto
  • tools/: análisis offline de vmlinux kdis/scan_s/resolve/dumpb/findsysctl

SESIÓN 10 — ROOT CONSEGUIDO (2026-09-11)

Los tres bugs que bloqueaban el arma-nf, todos corregidos

  1. init_net incorrecto: 0xc1104548 es __stack_chk_guard (el histograma movw/movt estaba contaminado por cargas del canario de pila — 2025 construyó todo el plan nf sobre él). El init_net real = 0xc1185040 (confirmado: ip_send_skb(net,...) llamado con este literal; ~994 referencias todas en la pila de red). Celda IPv4 LOCAL_OUT = init_net+0x58c = 0xc11855cc.
  2. Bug de doble deref: nf_iterate trata [init_net+0x58c] como el propio puntero nf_hook_ops — lee fn@+0xc, priv@+0x14, prio@+0x20 directamente de ese valor. La entrada fake de la sesión 9 vivía en PM_PAGE+0x700 con la celda apuntando a ella (campos / sin usar en la celda → → crash). La entrada fake DEBE vivir (0xc154b740). Con esto corregido, la llamada al hook quedó probada (modo = SAFE_FN procesa los 200 sendtos limpiamente).

El muro de SELinux y el bypass de 2 paquetes

commit_creds(prepare_kernel_cred(0)) / override_creds(&init_cred) da uid 0 pero aterriza en el SID de SELinux kernel, al que esta política de Fire OS no permite escribir /data ni /sys/fs/selinux/enforce (verificado: EACCES). El SID real de init (7) también denegado (prueba con struct cred falso). enforcing_setup es __init (liberado → crash). El atomic_sub de mark_reclaim necesita nents=1, lo que rompe la salida temprana de shrink_cpu_mapping.

La victoria: el proceso del exploit conserva los mapeos de CPU de kbm, así que la entrada nf falsa puede reescribirse in situ entre paquetes:

  • modo selroot: entrada = {fn=0xc01d503c (mov r3,#0;str r3,[r0];bx lr), priv=0xc1213ea8 (selinux_state.enforcing)}.
  • Paquete 1: *(enforcing)=0 → SELinux Permissive.
  • Reescribir la entrada vía kbm_cpu[reg]+off a {fn=commit_creds, priv=&init_cred}.
  • Paquete 2: commit_creds(&init_cred) en la tarea del emisor → uid 0 con un SELinux permisivo → root usable, todo en una sola reclamación, sin necesidad de cadena.

Verificado en 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 en pausa es `Uid: 0 0 0 0`,
  `CapEff: 3fffffffff`.
- `/data` está montado **nosuid**, por lo que un `su` setuid no puede funcionar. Un pequeño
  `rootshell` (envía UDP → `commit_creds` sobre sí mismo → `execl sh`) proporciona una
  shell root interactiva: `uid=0(root) context=u:r:kernel:s0`.
- Root puede leer/escribir `/dev/block/by-name/*` (`dd if=boot ...` OK).

### Estado del código de la Etapa 3 (`poc/stage3.c`)
- modos: `nf <mb> [probe|uprobe|oc|chain|fc <sid>|notrig|selroot]`
- `selroot` es el arma funcional. Estáticos clave: `init_net=0xc1185040`,
  `HOOKS_PTR_ADDR=0xc11855cc`, `ENFORCING_ADDR=0xc1213ea8`,
  `ZERO_GADGET=0xc01d503c`, `commit_creds=0xc014993c`,
  `init_cred=0xc1114f54`.
- La post-explotación usa syscalls directas (sin `system()`); mantén vivo el kctx
  (`pause()`) para evitar un crash durante el desmontaje.

### Pendiente (Etapa 4/5)
- Persistencia tras reinicio (verity / imagen de boot / recovery), ya que el secuestro
  de celda + SELinux permisivo son solo en tiempo de ejecución y volver a ejecutar el exploit
  requiere el lanzamiento de moneda de ~1/3 de recuperación.
- `su` necesitará un home no-nosuid (`/system`) o un lanzador que lo reactive.

## SESIÓN 11 — RECONOCIMIENTO DE PERSISTENCIA (Track B + Track A) y el traspaso de RE

El objetivo era root persistente. Se delimitaron dos vías:
- **Track B**: deshabilitar el arranque verificado (dm-verity / SELinux) para poder parchear `/system`.
- **Track A**: volver a ejecutar el exploit en el arranque.

Ambas se reducen al mismo bloqueo: **hacer que LK trate el dispositivo como `eng`/`unlocked`.**

### Hechos del arranque verificado (build exacta)
- Bootloader bloqueado, AVB `green`, `ro.boot.unlocked_kernel=false`, `ro.boot.secure_cpu=1`,
  `rpmb_state=1`. Bootrom parcheado (sin BROM); preloader solo vía CMD short.
- `/system` es montado por **Android dm-verity desde la cmdline del kernel construida por 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 llamado `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 particiones, sin `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) = almacén IDME.

### Hallazgos estáticos de LK (UFBL)
Cabecera de `lk.img`: `88 16 88 58 | 00052974 | "LK"`; tabla de vectores ARM en 0x200, el resto Thumb-2,
independiente de posición/reubicado (los literal pools usan `ldr+add pc`, por lo que un desensamblado ingenuo relativo a la base falla).
Cadenas relevantes (offsets de archivo): `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`.
**Conclusión: LK condiciona los efectos de seguridad de `fos_flags`/`dev_flags` a eng/unlocked.**

### Almacén IDME (eMMC **boot1**) — escribible, persistente, leído por LK y Android
- Magic `beefdeed` + `"2.1\0"` + count(0x19=25) en 0x0; elementos desde 0x10.
- Formato de elemento: `char name[16]; u32 size; u32 type(=1); u32 magic(=0x124); u8 data[size] (pad4)`.
- Offsets de elementos (prístinos): `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`. Los valores son ASCII (los flags son **cadenas hex**).
- Lectura en tiempo de ejecución: `/proc/idme/<name>` (solo lectura). Los valores del último arranque se cachean; una escritura en boot1 surte
  efecto en el siguiente arranque. La ruta de escritura requiere limpiar `/sys/block/mmcblk0boot1/force_ro` (root).
- **Confirmado que LK lee boot1**: cambiar `serial` cambió `ro.boot.serialno` en el siguiente arranque.
  Pero LK **trunca serial a 16 bytes** e ignoró `fos_flags=0x80`, `dev_flags=0xff`,
  all-ones, etc. — verity/selinux/`prod` sin cambios. Así que la inyección en cmdline vía serial falla.

### Consumidores en el lado Android de los flags IDME
- `/init.fosflags.sh` (servicio `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`. Verificado: establecer flags surte efecto (`sys.usb=adb`, `noadbauth=1`).
- **adbd** (ARM ET_EXEC sin strip; `.text` VA 0x8160 / archivo 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` (sin condicionar)
  - `amzn_is_dev_unlocked` @0x2d5fc = `/proc/cmdline` contiene `androidboot.prod=0` **o**
    `androidboot.unlocked_kernel=true`
  - `fos_read_debug_flags` @0x2d724 lee `/proc/idme/<name>` y parsea **hex**
  - `restart_root_service` @0xcb74 / `restart_unroot_service` @0xcc64
  - cadenas: `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`) — así que incluso con la
    puerta de auto-root satisfecha, la comprobación de prod de AOSP condiciona la ruta del comando.

### Por qué el root de la Sesión 10 no persiste
- SELinux permisivo + secuestro de celda + root son solo en tiempo de ejecución.
- `/data/metrics` es una **vpartition**: `/system/bin/vpartition.sh` monta `/data/vp/metrics.img`
  (ext4, non-nosuid/noexec) en `/data/metrics` en cada arranque; un `su` escrito ahí **no**
  sobrevive al reinicio. (También es la razón por la que un `su` setuid daba uid 0 pero **cero caps**.)

### Track A (re-explotación en el arranque) — bloqueado
- Ningún trigger `.rc` de init ejecuta código controlable (imports todos verificados; los triggers `persist.*` solo
  `start` servicios fijos; scripts en `/system`/`/vendor`).
- Los servicios root leen configuraciones de `/data` pero nunca ejecutan desde ellas (`perfmonitord`, `amazonfiled`,
  `vpartition.sh`, `kisd`, …).
- El único ejecutor en el arranque = una **app**, pero la huella en pausa del exploit es **VmRSS 534 MB**
  (spray de `kbm`) → lmkd la mata; además una recuperación perdida provoca un pánico (`PANIC_ON_OOPS`) → bootloop.
- **adbd auto-root** existe pero está condicionado por la cmdline construida por LK (`prod=0`/`unlocked_kernel=true`).

### Conclusión / siguiente objetivo (elegido: RE de Track B)
Todo depende de hacer que LK reporte `eng`/`unlocked`. A nuestro alcance:
`androidboot.prod=1|0` y `androidboot.unlocked_kernel=false` son establecidos por LK. Reversear LK para encontrar:
1. dónde lee `fos_flags`/`dev_flags`/`usr_flags` (los elementos `K*`) y la puerta exacta;
2. la determinación de `prod`/`unlocked` (¿elemento IDME? ¿buildvariant? ¿resultado de `amzn_verify_unlock`?);
3. `amzn_verify_unlock` (verificación RSA de libtomcrypt) para un bypass o una ruta débil de unlock_code/version;
4. el almacenamiento `seccfg`/`para`/`ENV_v1`(LK_ENV) (no está en ninguna partición volcada — quizá protegido por tee);
5. el preloader (`boot0`, `EMMC_BOOT`) en busca de un bug.
Si cualquiera de estos nos permite establecer eng/unlocked (de forma persistente, vía boot1 o una escritura directa en partición), entonces
`FOS_FLAGS_DM_VERITY_OFF` deshabilita la verity de system(root) y `/system` puede parchearse de forma persistente.

### Artefactos (de esta sesión)
`/tmp/opencode/mustang-dumps/` (puede borrarse al reiniciar el host): `lk.img`, `boot1.img` (prístino),
`boot.img`, `MISC.img`, `metadata*.img`, `pmt.img`, `mbr.img`, `kb.img`, `dkb.img`, `reserved.img`,
`cache.img`, `boot0.img`, `boot1.img`, `adbd.bin`, `perfmonitord.bin`, `amazonfiled.bin`.
Helpers: `tools/findinitnet.py`, `findgadget*.py`, `findstores.py`, `adbd_sym.py` (en /tmp);
el repo tiene `run.sh`, `poc/stage3.c` (`selroot`), `poc/su.c`, `rootcmd.sh`.

### Comandos útiles```
# 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

SESIÓN 12 — LK RE: la puerta eng/unlocked es real, y los almacenes de flags sin firmar no existen

Objetivo: conseguir que LK trate el dispositivo como eng/unlocked, o encontrar un bug en el preloader/LK, para que verity/SELinux puedan desactivarse de forma persistente. Resultado: se invirtió la ruta de código relevante de LK de extremo a extremo; el cambio no es alcanzable mediante los almacenes disponibles. Ningún dispositivo fue brickeado; el único experimento con boot1 fue revertido a su estado original.

LK es Thumb-2 PIC, reubicado en la base 0xFF400000

lk.img comienza con un pequeño stub ARM (archivo 0x200). El reubicador en 0x224 copia desde 0x200 a un destino literal y salta a una entrada literal:

Así que dirección en tiempo de ejecución = 0xFF400000 + offset de archivo para offsets >= 0x200. Todo lo que sigue al stub es Thumb-2, independiente de posición. Las cadenas se construyen con ldr rT,[pc,#imm] (offset T1 = imm8*4; offset de ldr.w = imm12) seguido de add rT, pc; el objetivo es (add+4) + *pool. Se añadió un escáner robusto que sobrevive al stub ARM y a los pools de literales como tools/lk_xref.py (maneja las formas de 16 y 32 bits, escanea cada 2 bytes). Todos los offsets a continuación son offsets de archivo; súmese 0xFF400000 para las direcciones en tiempo de ejecución.

Flujo de control decodificado (offset -> significado)

El código de desbloqueo está firmado con RSA de Amazon — no es falsificable

0x20b4 lee el ítem IDME unlock_code (0x400 bytes, todo ceros en esta unidad) y ejecuta amzn_verify_unlock (0x222c -> 0x20f0). Esa función acciona libtomcrypt (docenas de rutas /features/libtomcrypt/src/pk/asn1/der/... y verificación RSA), y la imagen incorpora el material del certificado: Sunnyvale / Amazon Lab126 / "$Common Kernel Signing Engineering CA0" en 0x317d9+, además de los diagnósticos 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). No hay atajo de código vacío / longitud / versión: verify(zeros) != 0, por lo tanto (confirmado en ). Cambiar o requiere o bien un válido firmado por Amazon (clave privada no disponible) o un bug de ejecución de código en el verificador. No se encontró nada explotable (bounds/size) estáticamente en 0x20b4/0x222c/0x20f0. =>

Los flags de verity/SELinux no provienen de un almacén que exista

Los flags de seguridad se leen a través del getter en 0x57c. Prueba empírica:```

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` es `FOS_FLAGS_DM_VERITY_OFF`; la puerta decodificada habría desactivado
verity **si** el getter lo hubiera devuelto. No lo hizo. La puerta está activa, no es
código muerto: su centinela de caché de una sola vez es `-1` en la imagen
(`*(u32*)0x50c74 == 0xffffffff`), por lo que la función realmente ejecutó la
ruta `check_flag("fos_flags",0x80)` y obtuvo 0. Por lo tanto, el getter (al menos
en el momento de la protección de verity) **no** está leyendo los elementos IDME de boot1.

El otro almacén candidato es el **LK env**, cargado desde una partición literalmente
llamada `"para"` (loader 0x12fd4, magic `ENV_v1`, checksum @0x3ffc). La propia tabla
de particiones de LK (0x4fcc0..0x50340) lista preloader/proinfo/nvram/protect1/
protect2/persist/seccfg/secro/**para**/logo/custom/expdb/tee1/tee2/metadata/
system/cache/userdata — pero el GPT real de la tablet tiene **solo 16 entradas**, todas
de 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

No hay partición para, seccfg, nvram, protect ni persist en este producto (y los volcados de PMT/pmt.img son todo ceros). Así que el entorno de LK está vacío, las claves Kfos_flags/Kdev_flags nunca existen, y todas las comprobaciones de fos_flags/dev_flags se resuelven a 0 — independientemente de lo que contengan los elementos IDME. Los elementos IDME de boot1 son consumidos por Android (/init.fosflags.sh, adbd, /proc/idme/*) pero no por las puertas de seguridad de LK.

Conclusión — por qué el eng/unlock persistente está bloqueado

  1. unlocked_kernel requiere un unlock_code firmado por Amazon (RSA/libtomcrypt, CA embebida). No es falsificable offline; no se encontró ningún bug en el verificador. Bloqueo duro.
  2. Los flags DM_VERITY_OFF / selinux=permissive se consumen del entorno de LK (para/ENV_v1), que no existe en este GPT. IDME fos_flags es empíricamente ignorado por LK (0x80 persistió, verity se mantuvo en eio). Bloqueo duro a menos que se modifique la tabla de particiones.
  3. Incluso un fos_flags=0x80 exitoso solo establecería androidboot.veritymode= disabled y un root= distinto de dm-0; no desbloquearía, y SELinux seguiría necesitando dev_flags del mismo entorno ausente para pasar a permissive.

Vías restantes (futuras, de mayor riesgo; no intentadas)

  • Sintetizar un almacén para/ENV_v1: añadir una entrada GPT llamada para (GPT primario
    • de respaldo deben actualizarse ambos) en el espacio libre después de userdata (userdata termina en LBA 0x3a3dfde; disco = 30535680 sectores), luego fabricar un entorno con fos_flags=0x80 y dev_flags=0x40 (checksum en +0x3ffc = suma de bytes sobre 0x3ffc). Esta es la única ruta restante para desactivar verity. Riesgos: corromper el GPT primario/de respaldo puede brickear; y no se demostró que la puerta de verity realmente lea para (solo que no es boot1 IDME).
  • Bug del Preloader (boot0/EMMC_BOOT): no revertido en esta sesión. Escribir boot0 está prohibido hasta que exista una copia prístina y una ruta de recuperación.
  • Investigación del verificador: la ruta del certificado de ingeniería (0x316a0/0x31703) es solo alcanzable con una identidad de dispositivo aceptada como "engineering" más un código firmado por la clave de ingeniería; no hay clave privada disponible.

Artefactos / reproducibilidad

  • Herramienta añadida: tools/lk_xref.py — resolvedor de xref de cadenas de LK independiente de la base.
  • Volcados usados: /tmp/opencode/mustang-dumps/lk.img, boot1.img (prístino), boot0.img, mbr.img (GPT), pmt.img (todo ceros).
  • Imagen de experimento de boot1 (fos_flags=0x80) conservada en /tmp/opencode/s12/boot1_f80.img; dispositivo restaurado a boot1 prístino (verificado /proc/idme/fos_flags -> 0).

Comandos útiles (se requiere root; rearmar 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:~
## SESIÓN 13 — el preloader es accesible después de todo (`1949:20ff` = preloader MTK, transporte HID)

Mientras está apagada y conectada por USB, la tablet se enumera como **`1949:20ff`**
(`Lab126`) — *no* Android y *no* el bootrom `0e8d:0003`.  Descriptor:```
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)

Identificación. 0x20FF aparece como "MTK Preloader" en el archivo config/usb_ids.py de mtkclient (bajo el VID de MediaTek 0x0e8d: 0xe8d:{0x0003 Brom, 0x2000/0x2001/0x20ff/0x3000 Preloader}). Amazon mantuvo el PID del preloader y cambió el VID a 0x1949, y lo presenta como un par de endpoints HID con un descriptor de reporte ficticio. Por lo tanto, este es el modo preloader / USBDL de MediaTek, una etapa por debajo de LK — alcanzada aquí mediante apagado + conexión, no mediante el cortocircuito de CMD.

Las cadenas del descriptor "HID"/"HID Interface" no están presentes en lk.img, boot0.img, boot.img ni en los demás volcados, es decir, el modo es producido por un componente que no hemos volcado (bootrom/TEE) o se ensambla en tiempo de ejecución.

Por qué esto importa. El preloader de Amazon utilizado por aftv2-tools expone comandos integrados, sin Download-Agent, a través de este flujo de bytes exacto:``` 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` para manipular el controlador MSDC
(base `0x11230000` en MT8173; verificar para MT8163) y leer/escribir **bloques eMMC
en bruto** sin DA y por lo tanto sin AVB/verity de por medio. Si el preloader de mustang
acepta 0xD1/0xD4, esa es una vía directa al desbloqueo persistente (parchear `boot` /
`lk`), independiente del código de desbloqueo RSA y del ausente entorno LK.

### Herramientas añadidas (se requiere root; hacer chmod al nodo USB primero)```
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 es la sonda mínima de solo handshake; tools/mtk_preloader_hid.py es el transporte completo (handshake/read32/ write32; write32 está protegido y no debe usarse hasta que el mapa de registros de la eMMC esté confirmado).

Estado / próximos pasos

  • Sin confirmar: si el preloader de mustang realmente implementa 0xD1/0xD4 (la sonda de handshake lo determina). Si lo hace, la ruta de lectura/escritura de la eMMC de aftv2 probablemente pueda portarse directamente.
  • Luego: encontrar la base MSDC del MT8163 (DT del kernel o preloader), volcar una partición (read_mmc), luego parchear boot.img/lk desde el preloader y reiniciar.
  • Esta es una etapa de arranque inferior a todo lo de la SESIÓN 12, por lo que no depende de amzn_verify_unlock ni de la puerta de entorno de LK.
  • NO ejecutar escrituras de SP Flash Tool / mtkclient contra él antes de que el protocolo y el diseño de la eMMC estén confirmados.
Descargar herramienta
pi_blocked_on
  • El proxy waiter vive en la propia pila del hilo waiter (futex.c:1975 pasa this->rt_waiter, declarado en futex_wait_requeue_pi en futex.c:2880) → el waiter marca su propio frame liberado mediante select de arm32 (nr 142) fd_sets
  • Clima de explotación: sin KASLR (base fija 0xc0008000), sin PAN, DEBUG_RT_MUTEXES desactivado → rt_mutex_waiter compacto de 48 bytes (tree_entry@0, pi_tree_entry@0xc, task@0x18, lock@0x1c, prio@0x20, deadline@0x28)
  • Cadena mínima: 2 write-slots → modprobe_path @ 0xc111488c (cadena auto-localizada en vmlinux; KALLSYMS_ALL desactivado, así que los símbolos de datos necesitan este truco) → exec de binfmt desconocido → script root (setenforce 0, desactivar OTA, su)
  • Referencias en refs/: NebuSec/CyberMeowfia (original), GhostLock-5.10 (port a Fire OS 8, trigger completo de ARM de 32 bits en src/exp32/), ghostlock-...-4.19-k40 (port a Android Qualcomm 4.19)
  • Etapa 3: llamada arbitraria a función del kernel → ROOT (sesión 10)
    selroot
    selinux_state.enforcing
    commit_creds(&init_cred)
    uid=0
  • [_] Etapa 4: script root (su, permissive, OTA off) + persistencia — root obtenido; persistencia bloqueada (ver SESIÓN 11): LK condiciona verity-off/SELinux-permissive a eng/unlocked, el re-exploit en tiempo de arranque no tiene ejecutor viable. Siguiente: revertir LK/amzn_verify_unlock.
  • Etapa 5: cadena de arranque de SO personalizada
  • jc
    nr_extres
  • El resultado del alloc de JIT lo escribe el kernel a través de info->gpu_alloc_addr (una GPU VA que debes pre-asignar y pasar)
  • objeto expulsablepresiónresultado
    ninguno900 MBsobrevivió
    ninguno1300 MBpanic (bug de lowmem del sistema — no relacionado)
    región normal + DONT_NEED700 MBsobrevivió
    región JIT + DONT_NEED700 MBpanic en la ruta de reclaim
  • kernel/config-* — volcado de /proc/config.gz del dispositivo en ejecución
  • ksrc/ — código fuente OSS de Amazon (platform.tar + árbol midgard-r26p0 extraído)
  • OTA: /tmp/opencode/mustang_ota.bin (sha256 6068515a… coincide con fireos-archive) y tarball de código fuente del kernel de 2.2 GB conservado en ~/Desktop/amazon-mustang/
  • Ruta del árbol de código fuente: ksrc/kernel/mediatek/mt8163/4.9/drivers/misc/mediatek/gpu/gpu_mali/mali_midgard/midgard-r26p0/
  • sprayers de rename concurrentes durante la tormentalos renames se estancan (journal/GFP_NOFS) → 128 total → desreferencia basura
    pre-drenar 12K eventos + presión + pequeño finalcrash en la desreferencia
    + kill-child-en-expulsión (poll de 10ms)crash en la desreferencia
    + ciclo de vida fijado a cpu0 (drain3)crash en la desreferencia
    presión escalonada (drain4 v1)los hijos liberaron memoria al salir → sin expulsión (ruta legal validada)
    ejecuciónresultado
    pmap G=c2a412a4 400MBcrash en deref
    pmap G=c2f4b2a4 480MBcrash; +0 renombrados post-kill (480MB asfixia el fs)
    iso2 (spray + regiones + oráculo)ACIERTO DE REGIÓN — el spray no rompe la reclamación
    mix v1 (eventos+regiones concurrentes)crash; confundido (hilos de eventos en spin)
    pmap G=c2a7d2a4 350MB + retouchcrash; +4452 renombrados OK
    mix2 (secuencial: 2s eventos LUEGO regiones)crash; +7126 renombrados (28K allocs de eventos), 2715 regiones
    next
    fn
    fn=0
    en la dirección de la celda
    PM_PAGE+NF_CELL_OFF
    probe
  • El mapa directo de physmap es XN por encima de kernel_x_end: arch/arm/mm/mmu.c map_lowmem() mapea lowram por debajo del texto del kernel como MT_MEMORY_RWX, pero todo lo que está por encima de kernel_x_end como MT_MEMORY_RW → PMD_SECT_XN (línea 509). El shellcode incrustado en 0xc154b600 provoca prefetch-abort. El payload debe ser un puntero a función real del kernel, no código en el physmap.
  • literal (offset de archivo)valorsignificado
    0x2700xFF4002F8scratch de str r4,[r6]
    0x2740xFF40027Cdestino (base+0x27C)
    0x2780xFF54A440fin de copia (incl. BSS)
    0x27C0xFF400484punto de entrada
    offsetfunción
    0xdf7cis_secure_or_prod() -> byte[ [[g]+0 ] + 0x163 ]; g = global @0x52838. 1 en esta unidad.
    0x20b4verify_stored_unlock() = memset(buf,0,0x100); lee IDME/env unlock_code (0x100) vía 0x57c; bl 0x222c; devuelve (verify==0).
    0x222c / 0x20f0amzn_verify_unlock(code,len) — verificación RSA/PKCS#1 de libtomcrypt (ver abajo).
    0xda3eis_unlocked() = is_secure_or_prod() && verify_stored_unlock().
    0x29a28is_verity_disabled() = (fos_flags & 0x80) && !(is_secure_or_prod() && verify_stored_unlock()); cacheado en el global @0x50c74.
    0x29974Constructor de cmdline de SELinux: dev_flags & 0x20 -> androidboot.selinux=enforce, dev_flags & 0x40 -> ...=permissive (cada uno condicionado por byte[+0x162]).
    0x118xx/0x11bxxConstructor de cmdline del kernel (unlocked_kernel, prod=1/0, verifiedbootstate, rpmb_state, secure_cpu, versiones, root=).
    0x27af8Puerta UART: fos_flags & 0x4 -> printk.disable_uart=0, si no =1.
    0x12fd4Cargador de env de LK: partición "para", 0x4000 bytes, magia ENV_v1, checksum = suma de bytes sobre 0x3ffc comparada con la palabra @0x3ffc.
    0x1efd0búsqueda de partición por nombre (usado para "para", "boot", ...).
    0x57cdespachador getter a través del slot de callback @0x58218; los slots @0x58200..0x5821c se registran desde una tabla en 0x5a8-0x734.
    0x2a19cfastboot oem unlock: bl 0x222c(code,len); en caso de éxito escribe unlock_code (0x100) vía 0x408.
    unlocked_kernel=false
    /proc/cmdline
    androidboot.unlocked_kernel
    androidboot.prod
    unlock_code
    el cambio a eng/unlocked mediante la ruta documentada es criptográficamente inviable.
  • La Vía A (re-explotación en tiempo de arranque) por lo tanto permanece bloqueada exactamente como en la SESIÓN 11: su única ruta de desbloqueo es la misma puerta de LK.