
adb-driven KernelSU loader per Google Pixel stock: R/W temporaneo del kernel tramite CVE-2026-43499 (GhostLock), quindi caricamento tardivo di un kernelsu.ko con firma corrispondente per la KMI in esecuzione. Indipendente dal manager.
Uno strumento guidato da adb che trasforma un Google Pixel stock con bootloader bloccato in un dispositivo rooted con KernelSU senza sbloccare il bootloader né modificare l'immagine di boot. Dall'host esegue un exploit del kernel in userspace non privilegiato sul dispositivo per ottenere accesso temporaneo in lettura/scrittura al kernel, usa quella primitiva per applicare una credenziale di root, e poi carica tardivamente un modulo del kernel caricabile KernelSU (kernelsu.ko) nel kernel GKI in esecuzione e passa il controllo a qualunque gestore KernelSU (KernelSU, KernelSU-Next, SukiSU o un'altra variante) sia già installato. Prende di mira i Pixel Android 17 su kernel GKI 6.1 e 6.6 e guida l'intero flusso tramite adb shell per una sequenziazione deterministica e log con timestamp.
Il payload lato dispositivo è la LPE a catena completa fornita per CVE-2026-43499 ("GhostLock"), una use-after-free dello stack nell'ereditarietà di priorità futex/rtmutex in kernel/locking/rtmutex.c. Nel percorso di rollback requeue-PI, remove_waiter() azzera pi_blocked_on sul requeuer (current) anziché sul waiter effettivo, lasciando un puntatore pendente in uno slot dello stack del kernel liberato che conteneva un rt_mutex_waiter. Il bug è raggiungibile da un normale processo non privilegiato:
owner, waiter, consumer) costruiscono una catena PI; il waiter si ferma su FUTEX_WAIT_REQUEUE_PI, il thread principale attiva FUTEX_CMP_REQUEUE_PI, e una sched_setattr dal consumer guida il rollback.pselect()/select() controllata (o una rotta TCP_ZEROCOPY_RECEIVE su alcuni target 6.1) le cui parole fd_set finiscono sopra la struct del waiter, scrivendo un rt_mutex_waiter piatto contraffatto così che il puntatore pendente attraversi campi rb-tree e lock controllati dall'attaccante — una singola primitiva di scrittura di puntatore controllata.mm_struct// spruzzati.Lo stato di root e SELinux viene poi applicato tramite la primitiva della pipe: il cred del task figlio root viene azzerato a uid/gid 0 con set di capacità completi, il suo osid/sid SELinux impostato a SECINITSID_KERNEL, seccomp azzerato, e selinux_state.enforcing impostato a 0.
La catena dell'exploit, l'oracolo KASLR e il canale laterale KernelSnitch provengono dalla ricerca IonStack Part II — GhostLock di NebuSec (il PoC NebuSec/CyberMeowfia, Apache-2.0), adattata qui per Pixel/aarch64. Vedi Attribuzione e Licenza.
Esattamente una fase della catena può mandare in panico il kernel: la derivazione dello slide KASLR, che fa una gara su una pagina che spera di aver reclamato. Ogni altra fase è sicura per i retry, e la base del testo del kernel è fissa per la durata di un singolo avvio. Il flusso host si divide su questa proprietà:
KASLR_BASE nel suo ambiente. La scrittura del waiter contraffatto ripunta ctl_table.data del sysctl random_table a un puntatore noto del testo del kernel; leggere /proc/sys/kernel/random/boot_id lo fa trapelare tramite proc_do_uuid(), e sottrarre l'offset dell'immagine produce _stext/la base KASLR. restore_slide_boot_id() ripara il ctl_table.data corrotto. Poiché una gara persa riavvia il dispositivo, un'attesa-di-avvio precede ogni tentativo e un controllo di liveness classifica una sparizione come un panico. Al successo il log del dispositivo emette slide-kaslr-ok pid=<pid> base=<hex>, e la base è ancorata all'avvio corrente.KASLR_BASE=0x<base> esportato. Questo percorso non va mai in panico e viene ripetuto finché id non riporta tramite la su temporanea.Il dispositivo connesso viene risolto rispetto a data/targets.json a runtime; nulla è hardcoded per dispositivo. Avvengono due risoluzioni indipendenti:
uname -r), sia dalla voce target corrispondente sia derivato dalla stringa di release (es. android14-6.1).Il riuso di un payload su molti dispositivi deriva dalla struttura GKI/KMI. Ogni dispositivo sulla stessa build GKI esegue il vmlinux identico byte-per-byte, e gli offset dei campi delle struct (task_struct->cred, cred->uid, …) sono congelati per la vita di un ramo KMI dal contratto di tipo KMI e dall'applicazione dei CRC MODVERSIONS. Gli indirizzi assoluti dei simboli del kernel, al contrario, sono decisi dal linker per ogni build ab<NNN>, quindi gli offset fissi dell'exploit appartengono a uno specifico vmlinux; immagini del kernel distinte richiedono quindi payload distinti anche quando il loro KMI corrisponde. data/targets.json codifica esattamente questo: molti dispositivi si deduplicano su un payload chiave per immagine del kernel, mentre un'immagine del kernel diversa ottiene il proprio.
Il late-load LKM richiede un kernel GKI (5.10+) con supporto ai moduli caricabili e un .ko corrispondente al KMI. Il modulo del kernel KernelSU autentica il suo gestore verificando nel kernel il blocco di firma v2 dell'APK del gestore e confrontando lo SHA-256 del certificato di firma con una coppia KSU_EXPECTED_SIZE/KSU_EXPECTED_HASH compilata nel .ko. Il kernelsu.ko distribuito dentro una release del gestore e l'APK di quel gestore condividono quindi un'identità di firma; un ksud non corrispondente carica il driver ma non imposta mai il bit di autorizzazione del gestore, lasciando il dispositivo senza root utilizzabile.
Il flusso onora questo legame: risolve il percorso APK del gestore installato (pm path <pacchetto gestore>), scarica l'APK, estrae lib/arm64-v8a/libksud.so come binario ksud, e se nessun gestore è installato si interrompe. Con la root temporanea in mano, quel ksud viene messo in scena come eseguibile di proprietà di root e invocato come ksud late-load --kmi <kmi> --package-name <pacchetto gestore>. Il late-load rileva il KMI corrente, estrae "{kmi}_kernelsu.ko" dai suoi asset incorporati, esegue la rilocazione manuale dei simboli (risolvendo ogni simbolo SHN_UNDEF contro /proc/kallsyms, riscrivendo le voci a SHN_ABS), e chiama init_module(2) sul buffer applicato. Esegue poi la restante pipeline di boot che init farebbe (installare ksud, restorecon, caricare sepolicy.rule e i profili root, eseguire gli script post-fs-data/stage, montare l'overlay dei moduli).
Il late-load si demonizza e riapplica SELinux nel suo figlio forkato, che smantella il demone su temporanea dell'exploit; la verifica quindi non deve passare da su. Invece il driver caricato viene interrogato direttamente tramite la sua superficie syscall, raggiungibile da una shell semplice senza root: ksud debug version viene interrogato e la versione del kernel riportata viene analizzata. Una versione non vuota e non zero conferma che il driver è residente e risponde. Il percorso di installazione del driver è il meccanismo reboot(2) magico → fd di installazione → KSU_IOCTL_GET_INFO (reboot(0xDEADBEEF, 0xCAFEBABE, 0, &fd) installa un fd anonimo [ksu_driver]; GET_INFO restituisce {version, flags, features, uapi_version}), con il canale legacy prctl(0xDEADBEEF, …) sondato come fallback. La stessa sonda eseguita all'avvio cortocircuita l'intero flusso quando il modulo è già residente per l'avvio corrente.
L'exploit mette in scena la sua su temporanea in /apex/com.android.virt/bin/su, su un tmpfs montato sopra quella directory bin dell'apex nel namespace di mount di adbd, perché quella directory precede /system/bin nel PATH della shell — così una su nuda in adb shell raggiunge quella temporanea mentre il flusso è in esecuzione. Il late-load poi smantella il demone su temporanea (vedi (e)) senza rimuovere lo shadow, il che lascia una adb shell su nuda che esegue un client orfano che fallisce con su: connect daemon: Permission denied anche se la root funziona, e lascia i binari reali dell'apex (crosvm, virtmgr, vm, …) nascosti. Una volta che la verifica riporta un driver vivo, il flusso smonta il tmpfs di messa in scena — tramite la /system/bin/su di KernelSU stessa, poiché il demone dell'exploit è già sparito — rimuove il client su temporanea, il socket e il log, e riporta quale su una semplice ora risolve. È best-effort: al fallimento avvisa con il comando manuale invece di far fallire l'esecuzione, e un riavvio pulisce il mount comunque.
adb sull'host, con il dispositivo autorizzato (debug USB abilitato).kernelsu.ko incorporato.artifacts/exploits/ (vedi Compilazione dei payload).# Un dispositivo su adb; gestore installato; payload compilati.
bin/pixel-ksu-root
Il driver risolve il dispositivo rispetto a data/targets.json, esegue il flusso KASLR in due fasi, carica tardivamente il modulo tramite il ksud derivato dal gestore e verifica tramite la syscall del driver. Esce con codice non zero se nessun payload si risolve per il dispositivo, se nessun gestore è installato, o se la verifica non riporta mai un driver vivo.
KASLR_BASE=0x<hex> — passata al payload del dispositivo durante la Fase B per riprodurre contro una base fissa già derivata per avvio. Non impostata durante la Fase A così il payload deriva la base da sé.ANDROID_NDK_HOME — percorso all'Android NDK, richiesto solo quando si compilano i payload.API — livello API Android per la toolchain NDK quando si compilano i payload (default 35).pixel-ksu-root/
├── bin/ Punto di ingresso del driver host (flusso guidato da adb)
├── data/
│ └── targets.json Tabella di risoluzione dispositivo→payload e dispositivo→KMI
├── exploit/ Sorgente del payload CVE-2026-43499 incluso
│ ├── Makefile Build aarch64 NDK per target
│ ├── src/ Set sorgente baseline android15-6.6
│ │ ├── main.c slide.c fops.c pipe.c root.c preload.c util.c
│ │ ├── su_daemon.c Helper su temporanea generato dalla catena
│ │ ├── kernelsnitch/ Header del canale laterale di occupazione hash futex
│ │ └── targets/ target.h per dispositivo+build (offset del kernel)
│ └── src/61/ Set sorgente android14-6.1 (slide61.c, rotta TCP)
├── lib/ Funzioni shell/helper condivise lato host
├── scripts/
│ └── build-payloads.sh Compila e deduplica il set di payload
├── artifacts/
│ └── exploits/ File .so dei payload compilati e deduplicati
└── docs/ Note di progettazione e analisi
scripts/build-payloads.sh avvolge il exploit/Makefile per target ed emette il set di payload deduplicato nominato in data/targets.json in artifacts/exploits/. Compila un .so per gruppo di offset unico (dal target build_from di quel gruppo) piuttosto che uno per dispositivo.
export ANDROID_NDK_HOME=/percorso/android-ndk # deve contenere la toolchain NDK aarch64
scripts/build-payloads.sh # compila ogni payload in data/targets.json
Il Makefile seleziona la toolchain Clang NDK aarch64 da ANDROID_NDK_HOME e compila un singolo target alla volta; API (default 35) sceglie il driver aarch64-linux-android<API>-clang. Il set sorgente è scelto per famiglia di kernel — i target android15-6.6 compilano il baseline src/, i target android14-6.1 compilano src/61/ — e gli offset assoluti del kernel di ogni target provengono da src/targets/<codename>-<build>/target.h. Per compilare un singolo target direttamente:
make -C exploit TARGET=husky-CP2A.260705.006
data/targets.json elenca 19 voci dispositivo/build che coprono 18 modelli Pixel (bluejay appare su due build firmware), raggruppati in 5 payload di offset del kernel. La selezione è per immagine del kernel, quindi i dispositivi che condividono un vmlinux si deduplicano su un payload; un'immagine del kernel diversa ottiene il proprio.
I dispositivi sull'immagine del kernel condivisa android14-6.1-a (6.1.157-android14-11-gbd23337e42e7-ab14791245) coprono le famiglie Pixel 6/6 Pro/6a, 7/7 Pro/7a, 8/8 Pro e 9/9 Pro/9 Pro XL/9 Pro Fold; android14-6.1-b e android14-6.1-akita isolano i modelli sullo stesso KMI la cui build del kernel o i cui offset differiscono; android15-6.6 copre la famiglia Pixel 10.
NebuSec/CyberMeowfia sotto Apache-2.0; scoperta accreditata agli strumenti VEGA di NebuSec, divulgata il 2026-07-07.exploit/ incluso aggiunge gli offset target android14-6.1 e android15-6.6 e un demone di late-load KernelSU sopra l'exploit NebuSec; non porta una licenza separata ed eredita i termini Apache-2.0 a monte.ksud e il modello di autorizzazione del gestore che questo strumento carica tardivamente.Il sorgente incluso sotto exploit/ mantiene la sua licenza a monte (Apache-2.0 per l'exploit derivato da NebuSec). Questo progetto è agnostico rispetto al gestore: carica tardivamente qualunque variante KernelSU il cui gestore sia installato e non prende di mira né raggruppa alcun fork specifico.
ksudinit_module e rilevamento driverapk_sign.rssk_buffpipe_bufferashmem_miscs[0].fops con una file_operations contraffatta i cui slot puntano tutti a funzioni del kernel reali e compatibili per prototipo (configfs_bin_write_iter, configfs_read_iter, copy_splice_read, ashmem_ioctl, noop_llseek, …), così la CFI sul bordo anteriore è soddisfatta mentre read/write/splice su un fd ashmem producono un R/W del kernel vincolato.pipe_buffer sulla pagina slab trapelata (page puntata a qualunque target tramite la conversione vmemmap↔direct-map, ops = anon_pipe_buf_ops, PIPE_BUF_FLAG_CAN_MERGE), così semplici read()/write() sulla pipe spostano byte da e verso indirizzi arbitrari del kernel — un R/W arbitrario stabile del kernel.uid=0/proc/sys/kernel/random/boot_id live con l'avvio registrato al momento della cattura; qualsiasi modifica scarta la base e torna alla Fase A. Un ciclo esterno ripete deriva→replay attraverso i riavvii.adb shellumount| Payload | KMI | Compilato da | Dispositivi |
|---|
android14-6.1-a | android14-6.1 | bluejay-CP2A.260705.006 | oriole, raven, bluejay (CP2A), panther, cheetah, comet, shiba, husky, lynx, caiman |
android14-6.1-b | android14-6.1 | komodo-CP2A.260705.006 | komodo, tegu, tokay |
android14-6.1-akita | android14-6.1 | akita-CP2A.260805.005 | akita |
android14-6.1-cp1a | android14-6.1 | bluejay-CP1A.260405.005 | bluejay (CP1A) |
android15-6.6 | android15-6.6 | blazer-CP2A.260705.006 | frankel, blazer, mustang, rango |