
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 in esecuzione su Samsung A17
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) di 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 fittizi usato dalla route PI:
| Offset | Oggetto | Scopo |
|---|---|---|
0x0E80 | fake_lock | rt_mutex fittizio |
0x0F80 | fake_fops | Tabella file_operations fittizia |
0x1180 | fake_w0 | rt_mutex_waiter fittizio usato come albero di destinazione |
0x1240 | fake_right | Nodo destro fittizio dell'rb-tree — è da qui che proviene il valore scritto |
0x1260 | fake_left | Nodo sinistro fittizio dell'rb-tree |
0x1280 | fake_task | task_struct fittizia |
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.
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 a percorrere l'albero PI fittizio.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 fittizio.
pselect() / select() copia l'fd_set dell'utente nello stack del kernel e successivamente lo percorre. Disponiamo i bitmap dell'fd_set in modo che le word che il kernel interpreta come puntatori dell'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.
ret = 6 (invece del valore predefinito 9) conferma che la scrittura è andata a segno: il consumer ha raggiunto il target durante select(), svegliandolo in anticipo.
Questo è un fork di mobilehackinglab/ghostlock-a17 (MIT). Quanto segue è preso 1:1 dall'exploit upstream:
| Componente | File | Note |
|---|---|---|
| KernelSnitch | src/kernelsnitch/* | mm_struct leak tramite timing dell'hash dei futex |
| Heap spray | src/spray.c | layout di oggetti fittizi, 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 |
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.