
GhostLock (CVE-2026-43499) adattatore per kernel Android 4.19.152-perf+
Exploit del kernel per dispositivi OnePlus/OPPO/realme con bootloader bloccato. Ottiene la root + installazione di KernelSU senza sbloccare il bootloader o modificare l'immagine di boot. Rilevamento automatico a runtime della versione del kernel con tabella di offset multi-dispositivo.
CVE-2026-43499 — Futex PI (Priority Inheritance) Use-After-Free
Colpisce il kernel Linux 2.6.39 ~ 7.1. Corretto nel mainline 7.1 (commit 3bfdc63936dd). Android GKI 6.12.x rimane vulnerabile.
La syscall pselect6 copia i dati fd_set sullo stack del kernel. Quando combinata con il meccanismo futex PI waiter, un frame di stack liberato può essere riutilizzato come struttura rt_mutex_waiter. Il ribilanciamento dell'albero rb durante la scansione della catena PI scrive quindi valori controllati a indirizzi arbitrari del kernel.
| Dispositivo | SoC | Kernel | Note |
|---|---|---|---|
| OnePlus 15T (PLZ110) | SM8845 | 6.12.38-...-ab14552068 | Stesso kernel di Ace 6T. QEMU ha verificato SP diff=-64. |
L'overlay dello stack pselect funziona solo quando il rt_mutex_waiter liberato ricade nella regione controllabile dall'utente del buffer stack_fds. La posizione del waiter è determinata dall'output del compilatore (PGO + LTO), non dalla versione del kernel. Vedere Layout dello Stack per i dettagli.
Due percorsi di root, selezionati automaticamente in base alle capacità del dispositivo:
Richiede off_ashmem_misc_fops != 0 (ashmem C con miscdevice statico in 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
Vantaggi rispetto al Percorso B:
selinux_state.policycap (risolve i problemi di rete su OnePlus 13)Attualmente disponibile su: OnePlus 13 (kernel 6.6, ashmem C). Non disponibile su dispositivi con ashmem Rust (6.12 GKI) — il miscdevice è allocato sullo heap, l'indirizzo non è prevedibile in fase di compilazione.
Usato quando gli offset UMH o i misc_fops dell'ashmem C non sono disponibili.
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, core_sys_select del kernel alloca un buffer stack_fds di 256 byte:
stack_fds: 0 5 10 14 | 15 20 25 29
├─in─┤─out─┤─ex──┤ ├res_in┤res_out┤res_ex┤
◄── USER CONTROLLED ──►│◄── KERNEL ZEROED ──►
L'exploit scrive campi fittizi del waiter (task, lock) nelle bitmap di input fd_set. Perché funzioni, i campi task e lock del waiter devono ricadere nella zona controllabile (word 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
Regola di fattibilità: waiter word + 11 (offset di lock in rt_waiter_node) deve essere ≤ 14. La word massima di waiter fattibile è 3.
La posizione del waiter è determinata dal layout dei frame di stack del compilatore (profili di ottimizzazione PGO + LTO + BOLT), che varia in base al ramo del SoC. La stessa versione del kernel può avere layout diversi su SoC diversi.
Tutte le scritture sul kernel passano attraverso l'alias linear-map dell'immagine:
data_addr(x) = PAGE_OFFSET + (kernel_phys_load - PHYS_OFFSET) + (x - KIMAGE_TEXT_BASE)
Il bootloader sceglie kernel_phys_load, quindi varia in base al SoC e non è presente in boot.img né nel DT. Campo per-dispositivo in struct kernel_offsets; 0 = usa il valore predefinito di 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 valore errato fallisce silenziosamente — la scrittura finisce comunque nella RAM mappata, quindi non c'è crash e nessun effetto. Non confonderlo con un problema di PSELECT_SHIFT. Leggilo su un dispositivo rooted dello stesso modello (Kernel code inizia a _stext; _text è più in basso di 0x10000):
su -c 'grep -i "Kernel code" /proc/iomem' # c7810000-... -> 0xc7800000
Kernel diversi posizionano il waiter in posizioni diverse all'interno della zona controllabile. Usa PSELECT_SHIFT per regolare:
# 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
Il waiter word di check_feasibility.py è inaffidabile: l'aritmetica sui frame è corretta, ma gli offset delle struct che deduce dagli zero-store non lo sono (su OnePlus 15 restituisce word 3; il valore misurato è word 2). Uno shift errato costa un kernel panic per tentativo, quindi misuralo su un dispositivo rooted:
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 fornisce solo la root. L'installazione di KernelSU dipende da ksud — un binario che contiene moduli kernelsu.ko integrati per ogni versione KMI. Lo script di root trova ksud sul dispositivo e chiama ksud late-load --kmi android16-6.12.
Senza ksud, l'exploit ottiene la root (uid=0) ma KSU non verrà installato e
sunon persisterà.
# 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"
Dopo il primo jailbreak riuscito, persist.adb.tcp.port viene impostato tramite resetprop — i riavvii successivi sono completamente automatici.
/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: esegui entro 30 secondi dall'avvio per la migliore affidabilità di temporizzazione di KernelSnitch.
È necessario solo boot.img — nessuna root, nessun accesso al dispositivo richiesto.
# 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
Il nucleo dell'exploit è indipendente dal dispositivo. L'adattamento può richiedere:
VA_BITS diverso (48 vs 39) → aggiornare il layout di memoria in target.hkernel_phys_load diverso → leggere da /proc/iomem o usare la variabile d'ambiente KPHYS=common.hPSELECT_SHIFT diverso → determinarlo tramite test kprobe in QEMUSTRUCT_OFFSETS_6_6 o STRUCT_OFFSETS_6_12 nella voce del dispositivoIl percorso di root UMH (call_usermodehelper) richiede:
off_system_unbound_wq e off_call_usermodehelper_exec_work da kallsymsoff_ashmem_misc_fops = ashmem_misc + 0x10 (solo ashmem C, miscdevice.fops in BSS)Solo per ricerca sulla sicurezza autorizzata e scopi educativi.
| Dispositivo | SoC | Kernel | Stato |
|---|
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14275539 | Funzionante |
| OnePlus Ace 6T (PLR110) | SM8845 | 6.12.38-...-ab14552068 | Funzionante |
| OnePlus 15 (CPH2749) | SM8850 | 6.12.23-...-ab14541642 | Funzionante |
| Xiaomi 17 (pudding) | SM8850 | 6.12.23-...-abogki463945075 | Funzionante |
| Xiaomi 17 (pudding) | SM8850 | 6.12.69-...-abogki514973465 | Funzionante (aggiornamento di agosto 2026) |
| OnePlus 13 (IN2060) | SM8750 | 6.6.89-...-abogki446052083 | Funzionante (PSELECT_SHIFT=-2) |
| OPPO Pad 4 Pro | SM8750 | 6.6.89-...-ab14358676 | Funzionante (PSELECT_SHIFT=-2) |
| Dispositivo | SoC | Kernel | Motivo |
|---|
| OPPO Find X9 Ultra | SM8750 | 6.12.58-android16-6 | PGO elimina il frame do_futex → SP diff=+32, waiter word=14. Non esiste uno shift sicuro. |
| OPPO Find X7 | — | 6.1.157 | 6.1 GKI: waiter a word 13 (tutti i dispositivi 6.1 OPLUS/GKI) |
| realme RMX5070 | SM6650 | 6.1.141 | 6.1 GKI: waiter a word 13 |
| realme RMX3852 | SM8635 | 6.1.141 | Stesso ramo 6.1 di RMX5070 |
| OnePlus 13R / Ace 5 | SM8650 | 6.1.x | Stesso ramo 6.1 |
| OnePlus 12 | SM8650 | 6.1.141 | 6.1 GKI: do_futex inline con PGO, waiter word=13/19 |
| OPPO Pad 5 (OPD2502) | MT6878 | 6.1.134 | Stesso ramo 6.1 |
| OPPO PKW110 | — | 5.15.180 | Frame do_futex 0x140 (4,5x normale) → waiter word=-29, irraggiungibile |
| Motorola Edge 60 Fusion | MT6878 | 6.1.145 | 6.1 GKI: waiter a word 13 (non OPLUS, stesso risultato) |
| iQOO Z9 5G | — | 5.15.178 | Frame do_futex troppo grande, waiter irraggiungibile. Non è un dispositivo OPLUS (vivo). |
| Metodo |
|---|
| Passaggi |
|---|
| APK ReSukiSU (consigliato) | Installa ReSukiSU o questo fork. La release ufficiale include libksud.so. |
| Release CI | Scarica ksud-aarch64-linux-android.zip da ReSukiSU CI |
| Fonte | Conteggio | Metodo |
|---|
| kallsyms (simboli globali) | 28 | extract_target.py |
| BTF (campi delle struct) | 57 | extract_btf.py |
| Derivati (stessa struct, uso diverso) | 9 | Automatico |
| Costanti (valori fissi) | 12 | Nessuna estrazione necessaria |
| File | Descrizione |
|---|
src/core/main.c | Punto di ingresso dell'exploit, Write 1/2, percorso UMH, bootstrap, script di root |
src/core/fops.c | Percorso pselect, meccanismo di scrittura PI, stadio CFI |
src/core/util.c | Heap spray, kernelsnitch, slab drain, preparazione del payload |
src/core/pipe_physrw.c | Lettura/scrittura della memoria fisica basata su pipe buffer (potenzia la r/w di configfs) |
src/core/umh_root.c | Root UMH tramite iniezione su workqueue + handler --umh |
src/core/miniadb.c | Client ADB mini (TCP + autenticazione RSA) |
src/core/common.h | Parametri di temporizzazione, macro |
src/core/target.h | Layout di memoria, valori predefiniti dei campi delle struct (6.12) |
src/core/runtime_struct_offsets.h | Override per-dispositivo dei campi delle struct (6.6 vs 6.12) |
src/devices/offsets.h | Aggrega tutte le tabelle di offset dei dispositivi + macro STRUCT_OFFSETS_* |
src/devices/<device>/offsets.h | Voci di offset del kernel per-dispositivo |
src/core/slide.c | Leak dell'indirizzo del kernel SLIDE |
src/core/root.c | Configurazione della shell di root (patch diretta delle credenziali tramite pipe physrw) |
tools/extract_target.py | Estrazione degli offset da kallsyms |
tools/extract_btf.py | Estrazione degli offset delle struct da BTF |
tools/check_feasibility.py | Verifica della fattibilità del layout dello stack |