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
BYOVD-DriverKiller — Driver Reverse & Exploitation | Kitploit
Herramientas/GitHubGitHub/alex3o/byovd-driverkiller
ExploitationReverse EngineeringPost-ExploitationBinary AnalysisLearning & EducationRed Teaming
GitHubalex3o/byovd-driverkiller

BYOVD-DriverKiller

Driver Reverse & Exploitation

Ver Repositorio
8314hace 11 mesesRevisado 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

BYOVD-DriverKiller

⚠️ Aviso: Este proyecto es estrictamente educativo y demostrativo. No está pensado para utilizarse en un contexto malintencionado. El objetivo es aprender la metodología de ingeniería inversa y los pasos de explotación de un driver de Windows.


Aquí explico el proceso que seguí para resolver el ejercicio propuesto por d1rk(SaadAhla) https://github.com/SaadAhla, que consiste en realizar ingeniería inversa y explotación sobre un driver legítimo, firmado y no presente en las listas de bloqueo (HVCI, LOLBIN...). Un programa en C que permite terminar cualquier proceso activo del sistema mediante este driver en modo kernel está disponible; detallo su funcionamiento un poco más abajo.

POC-BYOD

📃 Uso: DriverKiller.exe <nom_processus.exe> [-d]

Opción -d: Permite eliminar el servicio y el driver del sistema después de la explotación.

El modo testsigning debe estar activado en la máquina objetivo porque el certificado del driver ha caducado.


Parte 1 - Ingeniería inversa:

El ejercicio proporciona un archivo .sys, nombrado con su hash SHA-256. El primer paso es abrir este archivo con IDA.
IDA está disponible gratuitamente. Solo hay que ir al sitio de Hex-Rays para generar una licencia y descargar el software.

Empezamos enumerando la IAT (Import Address Table) del driver y buscando la llamada a la API que nos interesa: ZwTerminateProcess.

screen1-git

Al hacer doble clic en ZwTerminateProcess, IDA nos redirige al código compilado de esta función. Seleccionando la entrada y mostrando las referencias cruzadas, obtenemos la lista de funciones del driver que la llaman.

screen2-git

Observamos que es la función sub_12EF4, en el offset 1CE, la que utiliza ZwTerminateProcess. Después de un doble clic, IDA muestra su código compilado.

screen11-git

El código descompilado revela las llamadas a ZwOpenProcess (que abre un handle hacia el proceso objetivo) y a ZwTerminateProcess (que termina el proceso a través de ese handle).

Al consultar la documentación de ZwOpenProcess (https://learn.microsoft.com/fr-fr/windows-hardware/drivers/ddi/ntddk/nf-ntddk-zwopenprocess), vemos que el parámetro ClientID corresponde a un puntero que indica el PID del proceso objetivo.

En la línea anterior, ClientId.UniqueProcess se inicializa con la variable v22. Esta última se define justo encima:

root@kitploit:~
v22 = (void )((_QWORD *)i + 10);

Para entender esta asignación, hay que identificar la variable i y el campo +10.

screen3-git

Más arriba en esta función, observamos una llamada a ZwQuerySystemInformation con el parámetro SYSTEM_PROCESS_INFORMATION. También entendemos que i es el iterador sobre las entradas de esta estructura con la variable v6.

Según la documentación de ZwQuerySystemInformation: (https://learn.microsoft.com/en-us/windows/win32/sysinfo/zwquerysysteminformation), esta función devuelve una matriz con una entrada por cada proceso activo en el sistema.

La estructura SYSTEM_PROCESS_INFORMATION se describe aquí: https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation

root@kitploit:~
typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    BYTE Reserved1[48];
    UNICODE_STRING ImageName;
    KPRIORITY BasePriority;
    HANDLE UniqueProcessId;
    PVOID Reserved2;
    ULONG HandleCount;
    ULONG SessionId;
    PVOID Reserved3;
    SIZE_T PeakVirtualSize;
    SIZE_T VirtualSize;
    ULONG Reserved4;
    SIZE_T PeakWorkingSetSize;
    SIZE_T WorkingSetSize;
    PVOID Reserved5;
    SIZE_T QuotaPagedPoolUsage;
    PVOID Reserved6;
    SIZE_T QuotaNonPagedPoolUsage;
    SIZE_T PagefileUsage;
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;

Recordatorio: tamaños de algunos tipos en Windows x64

  • ULONG = 4 bytes
  • USHORT = 2 bytes
  • HANDLE = 8 bytes
  • PWSTR = 8 bytes
  • KPRIORITY (typedef de un LONG) = 4 bytes
  • UNICODE_STRING = 16 bytes, porque esta es su estructura:
root@kitploit:~
  typedef struct _UNICODE_STRING {
    USHORT Length;        -> 2      
    USHORT MaximumLength; -> + 2 = 4
    PWSTR  Buffer;        -> + 8 = 12 (12 n'est pas un multiple de 8 donc padding de 4 ajouté en amont de Buffer) = 16
} UNICODE_STRING;

Cálculo del offset de UniqueProcessId:

root@kitploit:~
    ULONG NextEntryOffset;        -> 4
    ULONG NumberOfThreads;        -> + 4 = 8
    BYTE Reserved1[48];           -> + 48 = 56
    UNICODE_STRING ImageName;     -> + 16 = 72
    KPRIORITY BasePriority;       -> + 4 = 76 (76 n'est pas un multiple de 8 donc padding de 4 ajouté) = 80
    HANDLE UniqueProcessId;       -> + 8 = 88

El miembro UniqueProcessId está, por tanto, en el offset 0x50 (80 decimal).

Al observar la asignación de nuestra variable v22, vemos que i se convierte (cast) a puntero QWORD (8 bytes)

root@kitploit:~
v22 = (void )((_QWORD *)i + 10);
Por lo tanto, v22 corresponde a la dirección de i + 10 * 8 = 80 bytes. Esta variable contiene, entonces, el PID obtenido de la estructura SYSTEM_PROCESS_INFORMATION.

Para saber qué PID se pasará a ZwTerminateProcess, hay que analizar la condición que rodea esta asignación.

screen4-git

Vemos que primero se recupera el nombre de la imagen del proceso:

root@kitploit:~
v9 = (wchar_t )((_QWORD *)i + 8);
Porque v9 = dirección de i + 8 × 8 = 64 bytes. Esto corresponde al Buffer del miembro ImageName, ya que dicho miembro se encuentra en el offset 56 + 2 (USHORT) + 2 (USHORT) + 4 (padding) = 64

Teniendo en cuenta las manipulaciones y los bucles que aparecen a continuación, se puede plantear la hipótesis de que se realiza una comparación entre el nombre del proceso pasado como argumento (a2) y los procesos activos del sistema v9/String.

root@kitploit:~
sub_1C078(String, v9, (int)v13);
v17 = strupr(a2);
v18 = strupr(String);

Por lo tanto, se supone que el parámetro a2 contiene el nombre del proceso que se terminará mediante ZwTerminateProcess. Observamos que a2 es un parámetro de la función sub_12EF4. Para profundizar, hay que examinar las referencias de esta función (la he renombrado ZwTerminateProcessCaller para una mejor legibilidad).

screen5-git

Vemos que ZwTerminateProcessCaller es llamada por la función sub_13624 en el offset 61A.

screen6-git

Antes de analizar este código descompilado, voy a buscar las referencias de la función sub_13624 (renombrada ZwTerminateProcessCallerCaller) para asegurarme de que este código se utiliza después de una llamada API a DeviceIoControl desde el modo de usuario (UserMode).

screen§-git

Vemos que ZwTerminateProcessCallerCaller es llamada por la función sub_14130 (renombrada ZwTerminateProcessCallerCallerCaller ...por suerte para nosotros, es la última antes del punto de entrada 😅).

screen7-git

Vemos que ZwTerminateProcessCallerCallerCaller es llamada por la función sub_1A4A8 en el offset 306.

screen8-git

Encontramos la asignación de la función ZwTerminateProcessCallerCallerCaller:

root@kitploit:~
memset64(DriverObject->MajorFunction, (unsigned __int64)ZwTerminateProcessCallerCallerCaller, 0x1Cu);
Lo que significa que esta función se asigna a todas las entradas de la tabla MajorFunction (0x1B = 27, y existen 28 IRP principales).

screen9-git

Antes de volver a la función sub_13624 (alias ZwTerminateProcessCallerCaller), se obtienen el Symbolic Name y el Device Name (idénticos aquí): Viragtlt.

screen12-git

Al volver a ZwTerminateProcessCallerCaller, observamos que su segundo parámetro (por tanto, a2) corresponde a MasterIrp->AssociatedIrp.SystemBuffer.

screen13-git

Justo encima de la llamada a ZwTerminateProcessCaller se encuentra el código IOCTL: -2106392528 (en hexadecimal: 0x82730030).

Gracias a esta información, se puede deducir que para explotar este driver hay que enviar una llamada API DeviceIoControl al driver con el nombre del proceso a terminar en el SystemBuffer.


🔷 Información obtenida mediante ingeniería inversa:

  • IOCTLCode : 0x82730030
  • Device Name : Viragtlt
  • Symbolic Name : Viragtlt
  • SystemBuffer debe contener el nombre del proceso objetivo

Parte 2 - Explotación

Para explotar este driver (si está instalado y activo en la máquina objetivo), es necesario abrir un handle hacia él y luego hacer una llamada API DeviceIoControl con un Buffer que contenga el nombre del proceso que se desea terminar.
Para este ejercicio, he desarrollado un proyecto en C que:

  • Comprueba si el driver está presente y activo en el sistema (con un nombre de servicio concreto):
    • Si es así, el programa explota el driver con una llamada API DeviceIoControl.
    • Si no, el programa extrae el driver de sus recursos, lo despliega en el escritorio del usuario, crea un servicio activo y luego explota el driver con una llamada API DeviceIoControl. (Requiere privilegios de administrador porque se realiza la creación de un servicio.)
  • Si el driver está presente en el sistema pero el servicio no está iniciado, el programa intenta iniciar el servicio y luego lo explota con una llamada API DeviceIoControl.

También he añadido una opción -d que permite eliminar el servicio y el driver del sistema después de la explotación.

Este es el comportamiento del programa C en su ciclo de ejecución completo:

git

Evasión de AV/EDR

En este caso, DriverKiller.exe no es detectado por Microsoft Defender, ni estática ni dinámicamente. La evasión no tiene mucho sentido aquí porque el driver explotado tiene un certificado caducado, por lo que su uso en condiciones reales es difícilmente concebible. Pero para una mayor furtividad, se podría haber implementado:

  • El enmascaramiento de ciertas llamadas API de la tabla IAT mediante implementaciones personalizadas de GetProcAddress y GetModuleHandle
  • Un acercamiento al Kernel para la ejecución de llamadas API (Direct/Indirect Syscalls)
  • Técnicas Anti-VM / Anti-Debug

Detección del driver al 29/08/2025 (resultado ya existente; no he enviado nada a VirusTotal por razones evidentes):

image

⚠️ Este proyecto se realiza en un contexto de aprendizaje. Puede contener imprecisiones o errores. ¡Toda sugerencia, corrección o debate es bienvenido! 😃 Gracias a d1rk(SaadAhla): https://github.com/SaadAhla !

Descargar herramienta