Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2020-15368 — CVE-2020-15368, también conocido como "Cómo explotar un controlador vulnerable" | Kitploit
Herramientas/GitHubGitHub/stong/cve-2020-15368
Escalada de PrivilegiosAnálisis de VulnerabilidadesExplotaciónShellcodeAprendizaje y EducaciónDesarrollo de PayloadsExplotación de Binarios
GitHubstong/cve-2020-15368

CVE-2020-15368

CVE-2020-15368, también conocido como "Cómo explotar un controlador vulnerable"

Ver Repositorio
51549hace 4 añosRevisado por Kitploit

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

Cómo explotar un driver vulnerable de Windows

Exploit y Prueba de Concepto (PoC) para CVE-2020-15368. Asrock reempaquetó el driver de rweverything para su herramienta de configuración del controlador RGB y lo firmó. Lo "protegen" cifrando sus ioctls...lol. Encontramos este CVE por accidente el verano pasado, y que yo sepa, el driver todavía no está parcheado. El impacto es, por supuesto, ejecución de código arbitrario en el kernel, etc. Así que disfruta de este "0day" lol.

Si quieres discutir conmigo sobre si es un CVE REAL Y LEGÍTIMO, no dudes en contactarme en Twitter, podemos tener una gran pelea en las redes sociales públicas, ¡y será muy emocionante para todos los involucrados! ¡Incluso compraré un dominio para este bug si así lo deseas. ¡Es todo marketing!!!!

De todos modos, este bug es bastante mierda, así que lo usaré como tutorial sobre cómo pwnear tu típico driver vulnerable. Así que esta publicación está dirigida a principiantes. Aprenderás a explotar un driver vulnerable. Hay muchos otros drivers de mierda como este. El mundo es tuyo. Diviértete

DESCARGO DE RESPONSABILIDAD: Esta publicación se proporciona únicamente con fines educativos. Es responsabilidad del lector cumplir con todas las leyes locales, estatales y federales aplicables. El/los autor(es) de esta publicación no asumen ninguna responsabilidad y no son responsables de ningún uso indebido o daño causado por el software contenido en esta publicación.

Historia

Atrapados en cuarentena, mis compañeros de piso (Pear0, Codetector) y yo estábamos jugando con la nueva placa base Asrock de Pear0. Los LED rojos brillantes eran extremadamente molestos y no era posible configurarlos en Linux. Así que nuestro plan era hacer ingeniería inversa al driver de Windows que lo controlaba y replicar las operaciones de E/S en Linux.

En resumen, no tardamos mucho en darnos cuenta de que el driver es literalmente solo un driver genérico que concede acceso arbitrario de lectura/escritura a cualquier cosa. Esto incluye registros de control como CR3, CR4, memoria física, etc. Estos drivers están destinados a usarse como herramienta de depuración y el sitio web del proveedor lo indica claramente.

docs/lol.png

Pensamos que era extremadamente gracioso. Es bastante emocionante la primera vez que haces que una computadora sufra un triple fallo y se reinicie en seco desde el espacio de usuario. (Quizás menos emocionante la vigésima vez.) De todos modos, reportamos el bug y luego lo olvidamos durante un año.

Configuración

Como novato en kernel, me preguntaba cómo cargar e interactuar realmente con el driver. Resulta que es extremadamente fácil.

Puedes simplemente crear un servicio para el driver en Process Hacker (obviamente, se requieren privilegios de administrador para cargar drivers). Luego puedes hacer clic derecho e iniciarlo. Sí, es así de simple.

docs/processhacker.png

Podemos ver nuestro objeto Device en WinObjEx64.

docs/processhacker.png

Incluso podemos jugar con el dispositivo en FileTest.

docs/filetest.png

docs/filetest2.png

Estas 3 herramientas son increíbles, especialmente PH y FileTest. Son como una navaja suiza y deberían estar en la caja de herramientas de todo reverser de Windows. Por ejemplo, según tengo entendido, Jonas L ha encontrado innumerables vulnerabilidades de LPE en Windows solo jodiendo con FileTest. Así que Windows realmente tiene grandes herramientas para joder. Ojalá hubiera esta mierda en Linux.

Bypass de "seguridad"

Rweverything tiene un ioctl que recibe otro ioctl como parámetro, que controla qué operación realizar (leer memoria, escribir memoria, leer msr, etc.), y una unión de algunos parámetros específicos de la operación como dirección de origen, dirección de destino, etc. Cuando comparamos el código de los dos drivers:

docs/comparison.png

🤔🤔🤔🤔🤔🤔🤔🤔🤔

No obstante, el driver hace un intento chapucero de seguridad por oscuridad al exigir que todas las llamadas ioctl estén cifradas apropiadamente con una clave AES fija. El código (después de una pequeña limpieza) se ve así:

root@kitploit:~
if ( IoControlCode == 0x22EC00 )
{

  char enc_key[32];
  memset(enc_key, 0, sizeof(enc_key));
  memmove(enc_key, "C110DD4FE9434147B92A5A1E3FDBF29A", 32ui64);
  memcpy(enc_key + 13, ioctl_args->key, 16);
  
  size_t cb_decrypted = 0;
  my_decrypted_cmd* decryptedCmd = NULL;
  DWORD iv_size = ioctl_args->iv_size;
  DWORD input_size = *(DWORD*)(ioctl_args + bufferLen - 6);

  // really just calls BCrypt API to get an AES implementation
  if ( (unsigned int)decrypt_ioctl_params(enc_key, 32, ioctl_args->iv, iv_size, ioctl_args + bufferLen - input_size - 6, input_size, &decryptedCmd, &cb_decrypted) )
  {
    // Decryption failed
    if ( decryptedCmd )
      ExFreePoolWithTag(decryptedCmd, 0);
    irp->IoStatus.Status = 0xC000000D;
    goto Fail_Out;
  }
  IoControlCode = decryptedCmd->opcode;
  Rweverything_Args = &decryptedCmd->args;
}
else // not 0x22EC00
{
  if ( IoControlCode != 0x22E858 &&
       IoControlCode != 0x22E860 &&
       IoControlCode != 0x22E800 &&
       IoControlCode != 0x22E804 )// whitelisted control codes
    IoControlCode = 0; // block everything else
}

El driver permite algunas operaciones aburridas que hacen algo de PMIO, pero todos los códigos de control "divertidos" están protegidos por esta rutina de descifrado. A pesar de tener esta lista blanca explícita, todavía incluye toda la funcionalidad peligrosa de Rweverything. En lugar de ocultar estas características peligrosas, probablemente deberían haberlas eliminado por completo.

También, curiosamente, permite al usuario especificar parte de la clave (???), por alguna razón que no tengo idea. El código está muy mal escrito.

En fin, es relativamente fácil escribir el código del cliente para usar esta extraña API cifrada y pasarle las llamadas ioctl arbitrarias que queramos. No te aburriré con los detalles de eso.

Hablando con el driver

Abrimos un manejador hacia el driver y usamos DeviceIoControl para llamar al ioctl, algo muy estándar.

root@kitploit:~
HANDLE hDevice = CreateFileA("\\\\.\\GlobalRoot\\Device\\AsrDrv104", GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

// ... set up the encrypted ioctl data

BOOL result = DeviceIoControl(hDevice, 0x22EC00, ioctl_data, in_buf_size, out_buf, sizeof(out_buf), &bytes_returned, NULL);

Ahora que podemos hablar con la parte oculta de Rweverything del driver, lo primero que quería hacer era provocar un crash para saber que mi cliente del driver funciona.

La forma más directa de lograrlo es sobrescribir CR3 con basura. Sé que algunos de los que leen esto son novatos y está bien, así que lo explicaré en detalle. Yo también soy tonto, así que tal vez esto te ayude a aprender. Si sabes lo que estás haciendo, puedes saltarte esta parte.

En x86, cuando la paginación está habilitada (casi todo el tiempo en cualquier sistema operativo moderno), CR3 apunta a la dirección base física del directorio de tabla de páginas de nivel superior. Si no sabes qué significa eso, ve a leer el artículo de Wikipedia sobre Memoria virtual.

Cuando sobrescribimos CR3 con basura, digamos 0x0000000000000000, el TLB se vacía, y al intentar ejecutar la siguiente instrucción, el procesador (específicamente la MMU) intentará traducir el puntero de instrucción a una dirección física. La traducción de direcciones puede considerarse esencialmente una serie de recorridos de tablas de páginas que comienzan en CR3. CR3 ahora apunta a memoria física en 0, que sí existe y es accesible; sin embargo, es extremadamente improbable que sea una tabla de páginas válida. (Las entradas de la tabla de páginas, o PTE para abreviar, deben seguir una estructura específica).

Cuando esto sucede, obtendremos un fallo de página en la traducción de direcciones. Ahora, normalmente la CPU nos llevaría a la dirección del manejador de fallos de página. Pero, ¿cómo sabe dónde está la función del manejador de fallos de página? Esto se almacena en una estructura de datos en memoria conocida como Tabla de Descriptores de Interrupción (IDT). El procesador tiene un registro (leído/escrito por las instrucciones sidt y lidt) que contiene la dirección virtual de la IDT. ¿Ves el problema ahora? Para manejar el fallo de página necesitamos primero hacer otro acceso a memoria virtual y, por lo tanto, otra traducción de direcciones.

Por supuesto, nuestra segunda traducción de direcciones también fallará. Ahora tenemos un Doble Fallo: un fallo que ocurre mientras se maneja el primer fallo de página. Esto es bastante serio pero aún recuperable---el procesador nos dará una última oportunidad de recuperarnos. Por supuesto, este intento también se ve truncado bruscamente con un tercer y último fallo de página, un Triple Fallo. En este punto, la CPU simplemente se rinde y reinicia la máquina en seco. Si realizaste este procedimiento en una máquina física, probablemente verías la pantalla de presentación del BIOS en este momento.

Ahora, si tienes alguna pregunta, te daré la misma respuesta que siempre me dieron a mí: ve a leer el Manual de Intel Volumen 3A. (también conocido como la Biblia).

Explotando el driver

OK, ahora, ¿cómo explotamos realmente el driver? Mirando alrededor, vemos una primitiva libre de lectura/escritura de memoria física arbitraria. Básicamente mapea la dirección física que quieras usando MmMapIoSpace, copia tu buffer a ella (o viceversa), y desmapea la dirección.

Nota pequeña: cuando intentas llamar a MmMapIoSpace con argumentos estúpidos como nosotros mientras el depurador de kernel está adjunto, obtendrás un bugcheck. Puedes evitarlo escribiendo un byte mágico en WinDbg. Busca el comentario que hace referencia a MiShowBadMapper en exploit.cpp para ver más. Realmente no sé de qué se trata esa mierda y no me importa averiguarlo de verdad.

¿Cómo podemos aprovechar esta primitiva para obtener ejecución de código en el kernel? El principal problema con esta primitiva es que opera sobre memoria física. Como programa en modo usuario, no tenemos idea de cómo se ve la disposición de la memoria física---el sistema operativo maneja todo eso por nosotros. Incluso si podemos obtener direcciones virtuales de algunas estructuras de datos del kernel o punteros a funciones del kernel, no tenemos idea de dónde están en el espacio de direcciones físico.

Una idea es leer CR3, leer las tablas de páginas, y realizar nosotros mismos la traducción de direcciones virtuales. Es una gran idea. No funciona. Esto se debe a que Windows ya no permite mapear tablas de páginas con MmMapIoSpace. Así que tenemos que ser más inteligentes.

Usé la técnica de xeroxz de VDM. Es bastante simple, pero la técnica es bastante inteligente. Aunque no conocemos la disposición de la memoria física, aún podemos escanear toda la memoria física hasta encontrar lo que buscamos. Una cosa que podemos aprovechar es que los contenidos de las páginas son siempre los mismos tanto física como virtualmente: cualquier offset relativo a los límites de página siempre se conserva. Por ejemplo, si tengo la página 0x7fff000000000XXX mapeada al marco físico 0x0000000123456XXX, el XXX de todas las direcciones es el mismo tanto en la dirección física como en la virtual. Toda la estructura dentro de la página se conserva; así, podemos escanear alguna página interesante que querramos sobrescribir.

Lo más fácil que podemos sobrescribir es probablemente algún manejador de syscall o ioctl de fácil acceso. En Windows, hay una función estándar Beep() que hace que tu computadora emita un pitido. Lo creas o no, esto está implementado en un driver, Beep.sys, que proporciona el dispositivo Beep. (De hecho, puedes verlo en la captura de pantalla de WinObjEx64 de antes.) Cualquiera puede usar el dispositivo Beep, y rara vez se llama. Así que sobrescribamos el manejador de ioctl de Beep.

Podemos cargar Beep.sys en IDA y revisar el manejador de DeviceIoControl.

docs/beep.png

En el offset de página 0x270, tenemos este código con los bytes 40 53 48 .... Ninguno de estos bytes está reubicado, por lo que escanear esta función es muy fácil. Si hubiera bytes reubicados, tendríamos que comodinarles. Es la misma idea que el escaneo de firmas cuando escribes algún hack de juego.

Así que después de escanear la memoria física para localizar este código, podemos simplemente sobrescribirlo con nuestro propio shellcode. También debes tener cuidado, ya que puede haber múltiples copias de esta página dando vueltas en la memoria física (!) así que ve y encuentra todas las copias.

En este punto, podemos escalar privilegios con bastante facilidad intercambiando el token de seguridad de nuestro proceso con el de un proceso del sistema para obtener permisos de nt authority\system. Desafortunadamente, el driver de asrock requiere permisos de administrador para abrirse de todos modos, así que esto no es muy interesante.

Para nosotros, escribimos un shellcode básico que asigna y copia un payload de etapa 2, y luego crea un nuevo hilo de kernel. No podemos hacer todo en nuestro manejador de Beep sobrescrito porque 1) estamos limitados a 1 página y 2) el sistema se bloqueará cuando intentemos cerrar nuestro manejador hacia el dispositivo Beep, ya que también destrozamos el resto del código del dispositivo beep. En cuanto a obtener punteros del kernel, esto es fácil porque NtQuerySystemInformation nos los dará gratis si lo pedimos amablemente.

root@kitploit:~
__int64 __declspec(dllexport) __fastcall MyIRPHandler(struct _DEVICE_OBJECT* a1, IRP* irp)
{
    MyIrpStruct* user_data = (MyIrpStruct*)irp->AssociatedIrp.SystemBuffer;

    void* my_rwx = user_data->nt_ExAllocatePoolWithTag(NonPagedPoolExecute, user_data->payload_size, 'lmao');

    user_data->nt_memcpy(my_rwx, user_data->payload, user_data->payload_size);

    HANDLE hThread;
    user_data->nt_PsCreateSystemThread(&hThread, THREAD_ALL_ACCESS, NULL, NULL, NULL, (PKSTART_ROUTINE)my_rwx, NULL);

    user_data->nt_IofCompleteRequest(irp, 0);

    return 0;
}

Así que parcheamos rápidamente Beep, llamamos al manejador de ioctl sobrescrito, y desparcheamos Beep. Ahora hemos creado de forma segura un hilo de kernel que ejecuta nuestro código sin destrozar nada más en el sistema. En este punto podemos mapear nuestros propios drivers o lo que sea.

Conclusión

Soy un mal investigador de seguridad y solo encuentro bugs inútiles, por accidente. Gracias a todos por leer. Por favor suscríbanse a mi OnlyFans

Descargar herramienta