
GhostLock reducido a una sola primitiva: SELinux desactivado en Galaxy A17 (BZA5) mediante futex PI UAF (CVE-2026-43499). Sin root, sin parche de credenciales, sin rwforge.
Un fork mínimo de GhostLock que conserva solo una primitiva: desactivar SELinux mediante CVE-2026-43499 (futex PI UAF).
ghost-hoock ejecutándose en un Samsung A17
ghost-hoock es un fork reducido del exploit GhostLock de Mobile Hacking Lab, reducido a una única primitiva:
Una escritura restringida mediante futex PI UAF ->
selinux_enforcing = 0.
Sin root, sin sobrescritura de cred, sin canal rwforge, sin UMH, sin configfs. Solo lo mínimo necesario para poner SELinux en modo permisivo en el kernel vulnerable.
Salida de ejemplo en un Samsung Galaxy A17 (SM-A175F, BZA5):
[] kernel: 6.12.23-android16-5-abA175FXXS5BZD2-4k
[+] offsets matched: 6.12.23-android16-5-abA175FXXS5BZD2-4k
[] init_cred image=ffffffc082512b08 alias=ffffff8002512b08
[+] startup context pid=10331 uid=2000 euid=2000 gid=2000 egid=2000 attr=u:r:shell:s0 enforce=1
[+] startup limits pid=10331 NoNewPrivs=0 Seccomp=0 Seccomp_filters=0
[+] build config pid=10331 label=ghost-hoock
[] p0 kernel_phys_load=0000000040000000 delta=0000000000000000 core=0
[] target selinux_enforcing=ffffff800277e560
[] W1 attempt 1/20
[] === W1: SELinux === target=0xffffff800277e560 mode=1
[] prepare_kernel_page ok attempt=1
[] pselect route setup simple=0 shift=0 page=ffffff806c4f0000 fake_lock=ffffff806c4f0000 ...
[] pselect returned ret=6 errno=0 calls=1 success=1 delay=0
[] pselect route done calls=1 success=1 step=0 errno=0
[+] SELinux DISABLED (attempt 1)
Luego:
$ getenforce
Permissive
getenforce devuelve Permissive
El exploit ataca CVE-2026-43499 — un use-after-free en la cadena rt_mutex de PI (Priority Inheritance) de futex del kernel de Linux. La cadena en ghost-hoock consta de cuatro pasos:
Canal lateral de temporización contra la tabla hash de futex del kernel. Martilleamos FUTEX_WAKE_PRIVATE sobre un conjunto de futexes en espacio de usuario, medimos los deltas de rdtsc y correlacionamos las colisiones de buckets del hash. Esto recupera la dirección de nuestro propio mm_struct — la base de la página de spray que necesitaremos después.
Esta es la técnica KernelSnitch, tomada tal cual del exploit original.
Asignamos una página slab grande de order-3 y luego la disponemos con el diseño de objetos falsos usado por la ruta PI:
| Offset | Objeto | Propósito |
|---|---|---|
0x0E80 | fake_lock | rt_mutex falso |
0x0F80 | fake_fops | Tabla file_operations falsa |
0x1180 | fake_w0 | rt_mutex_waiter falso usado como árbol objetivo |
0x1240 | fake_right | Nodo derecho falso del rb-tree — de aquí proviene el valor de la escritura |
0x1260 | fake_left | Nodo izquierdo falso del rb-tree |
0x1280 | fake_task | task_struct falso |
La página completa se envía a través de un socket AF_UNIX como SKB_SEND_SIZE = 2 * ORDER3_SIZE de sendmsg, de modo que los datos del skb aterrizan en nuestra página filtrada. Luego la liberamos en un orden controlado para que nuestra página acabe en un slab parcial por CPU que podamos reclamar.
Tres hilos:
FUTEX_WAIT_REQUEUE_PI sobre f_wait, apuntando a f_pi_target.FUTEX_LOCK_PI sobre f_pi_target y luego sobre f_pi_chain.sched_setattr(tid, SCHED_BATCH, nice=19) sobre el TID del waiter, lo que dispara rt_mutex_setprio() y fuerza al kernel a recorrer el árbol PI falso.Una cuarta llamada desde el hilo principal — FUTEX_CMP_REQUEUE_PI(1, f_pi_target) — inicia el requeue. Dentro del kernel, rb_erase() se ejecuta contra nuestro árbol falso.
pselect() / select() copia el fd_set del usuario en la pila del kernel y luego lo recorre. Disponemos los bitmaps del fd_set de modo que las palabras que el kernel trata como punteros del rb-tree aterricen en fake_right y su padre — y el rb_set_parent(child, parent) resultante se convierte en:
*(uint64_t *)target = value | color
Para mode = 1 (Write 1), target = selinux_enforcing y value = base + 0x100, que se codifica como byte0 = 0, byte1 = 1. El kernel escribe 0 en selinux_enforcing[0] — SELinux ahora es permisivo.
ret = 6 (en lugar del 9 por defecto) confirma que la escritura aterrizó: el consumer alcanzó el objetivo durante select(), despertándolo antes de tiempo.
Este es un fork de mobilehackinglab/ghostlock-a17 (MIT). Lo siguiente se toma 1:1 del exploit upstream:
| Componente | Archivo | Notas |
|---|---|---|
| KernelSnitch | src/kernelsnitch/* | Fuga de mm_struct mediante temporización del hash de futex |
| Heap spray | src/spray.c | Diseño de objetos falsos, prepare_skb_payload, prepare_kernel_page |
| Ruta PI + pselect | src/route.c | prepare_pselect_fdsets, do_pselect_fake_lock_route, consumer_thread, waiter_thread, owner_thread |
| Offsets de BZA5 | include/offsets_bza5.h | Tabla de símbolos extraída de 6.12.23-android16-5-abA175FXXS5BZD2-4k |
| Cabecera de objetivo BZA5 | include/target.h | Diseño de direcciones, offsets del payload (solo subconjunto W1) |
| Offsets de estructuras en tiempo de ejecución | include/runtime_struct_offsets.h | Macros _RSO() para campos de task_struct |
El código auxiliar (macros pr_*, SYSCHK, pin_to_core, set_limit, set_unbuffer) también se conserva tal cual del original.
El GhostLock original logra root completo en el A17: instala un canal físico de lectura/escritura rwforge, parchea cred / real_cred, ejecuta un helper UMH con credenciales de init, captura logs y más. En ghost-hoock, todo lo que va más allá de la primera escritura restringida ha desaparecido.