Professional Documents
Culture Documents
Encontrar información sobre el desarrollo de drivers no siempre es tarea fácil, sin importar
el sistema operativo del cual estemos hablando.
Normalmente, lo ideal para entender este tipo de temas correctamente es leer libros
especializados en el tema u ir directamente a las fuentes. Suele ser lo correcto ya que el
tema es complejo, largo y puede volverse pesado para quien se apure en entender todos los
conceptos implicados.
Por ahora, esto no sera mas que una introducción, por lo tanto tratare que sea breve. Es solo
un intento de dar una perspectiva general sobre este tema y con esto lograr que mas gente
decida a interiorizarse en el, con suerte mas adelante podremos ver técnicas y/o conceptos
mas avanzados y que la mayoría los entiendan.
Es ideal conocer a fondo las herramientas que estamos utilizando, y con esto no solo me
refiero al lenguaje, si no tambien al entorno de trabajo que utilicemos.
Por ejemplo, las personas que utilizan Visual C++, habrán hecho ya alguna aplicacion en
consola u alguna aplicacion para Windows. Al crear el proyecto, el subsystem viene
predefinido, tal como /SUBSYSTEM:CONSOLE o /SUBSYSTEM:WINDOWS.
La novedad en todo esto, es que un driver es linkeado con otro tipo de subsystem, llamado
NATIVE.
MSDN /SUBSYSTEM
Citar
NATIVE
Device drivers for Windows NT. If /DRIVER:WDM is specified, NATIVE is the
default.
Podemos utilizar cualquier nombre como Entry Point, pero por convención en Windows se
utiliza DriverEntry.
Si estas utilizando el DDK, al seleccionar que vas a construir un driver se utilizan una serie
de opciones predefinidas. Esta es la razón por la cual DriverEntry se convirtió en algo
similar al Entry Point oficial.
Use the /DRIVER linker option to build a Windows NT kernel mode driver.
Citar
The UPONLY keyword causes the linker to add the IMAGE_FILE_UP_SYSTEM_ONLY
bit to the characteristics in the output header to specify that it is a uniprocessor (UP) driver.
The operating system will refuse to load a UP driver on a multiprocessor (MP) system.
Código:
/SUBSYSTEM:NATIVE /DRIVER:WDM -entry:DriverEntry
Antes de comenzar, hay que cambiar la mentalidad de "Compilar y probar" que todos
solemos adquirir mientras aprendemos a programar en modo usuario.
En el mundo de los drivers la situación cambia y lo hace en forma drástica.
Como mínimo podrías ocasionar un BSOD, y si estamos ante un driver que iniciara siempre
con el sistema, tenemos un problema.
Igualmente, nada que no puedas revertir entrando en modo seguro u volviendo a
configuraciones previas, pero esto solo cabe en las practicas y no en casos reales.
Cabe recordar, que esta es solo una introducción de los conceptos básicos, por lo tanto
quien quiera interiorizarse a fondo no le queda mas alternativa que revisar la MSDN u
libros como "Programming the Windows Driver Model".
Citar
The priority ranking of an interrupt. A processor has an IRQL setting that threads can raise
or lower. Interrupts that occur at or below the processor's IRQL setting are masked and will
not interfere with the current operation. Interrupts that occur above the processor's IRQL
setting take precedence over the current operation.
The particular IRQL at which a piece of kernel-mode code executes determines its
hardware priority. Kernel-mode code is always interruptible: an interrupt with a higher
IRQL value can occur at any time, thereby causing another piece of kernel-mode code with
the system-assigned higher IRQL to be run immediately on that processor. In other words,
when a piece of code runs at a given IRQL, the Kernel masks off all interrupt vectors with a
lesser or equal IRQL value on the microprocessor.
Si, puede ser difícil de comprender a la primer lectura, por eso vamos a intentar explicarlo
en términos mas sencillos.
El IRQL de un procesador ayuda a especificar como un determinado thread puede ser
interrumpido. Dicho thread solo puede ser interrumpido mediante código por un nivel mas
alto de IRQL en el mismo procesador.
En estas épocas, es normal ver equipos con varios procesadores. En esos casos cada
procesador corre en su propio IRQL
Estos 4 niveles serán con los que tendrás que trabajar normalmente.
Normalmente las APIs llevan una pequeña nota aclarando en que nivel de IRQL necesitas
estar para poder utilizar determinada API. En reglas generales, a mas alto sea el nivel,
menos APIs podes utilizar.
Passive
APC (Asynchronous Procedure Calls)
Dispatch
DIRQL
Citar
1) PASSIVE_LEVEL
El nivel mas bajo. En este nivel corre un thread que se ejecute en modo usuario.
Citar
2) APC_LEVEL
Este es el nivel donde ocurren las "Asynchronous Procedure Calls". Cuando ocurre una
APC, el nivel del procesador se aumenta a APC_LEVEL.
Citar
3) DISPATCH_LEVEL
La memoria paginada no es accesible en este nivel, por lo tanto toda la memoria accesible
debe ser no-paginada. Esto ocasiona que disminuya en gran parte la cantidad de APIs que
podes utilizar.
Citar
4) DIRQL
Interrupts Masked Off — All interrupts at IRQL<= DIRQL of driver's interrupt object.
Device interrupts with a higher DIRQL value can occur, along with clock and power failure
interrupts.
Driver Routines Called at DIRQL — InterruptService, SynchCritSection routines.
En este nivel generalmente un driver no lidia con interrupciones. Es mas que nada una
forma de conocer que dispositivos tienen prioridad sobre otros.
Abreviado IRP, basicamente es una estructura que permite a diferentes drivers comunicarse
entre si y requerir tareas a finalizar.
El manejo de IRPs puede ser muy simple u demasiado complejo dependiendo de cual sea la
estructura de los drivers.
El IRP también contendrá "peticiones secundarias", conocido como "IRP Stack Location".
Cada driver contendrá sus propias peticiones secundarias respecto de como interpretar el
IRP, denominada IO_STACK_LOCATION.
Creando el DriverEntry
Código
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING
pRegistryPath);
Registrypath es una cadena que apunta a una ubicacion en el registro donde la informacion
para el driver fue guardada. El driver puede utilizar luego esta ubicacion para guardar
informacion especifica.
Código
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING
pRegistryPath)
{
NTSTATUS NtStatus = STATUS_SUCCESS;
UINT uiIndex = 0;
PDEVICE_OBJECT pDeviceObject = NULL;
UNICODE_STRING DriverName, DosDeviceName;
DbgPrint("DriverEntry!\n");
RtlInitUnicodeString(&DriverName, L"\\Device\\ehn");
RtlInitUnicodeString(&DosDeviceName, L"\\DosDevices\\ehn");
NtStatus = IoCreateDevice(pDriverObject, 0,
&DriverName,
FILE_DEVICE_UNKNOWN,//No asociado a ningun
dispositivo en particular
FILE_DEVICE_SECURE_OPEN,
FALSE, &pDeviceObject);
Lo primero que se nota es la llamada a DbgPrint, esta funciona como el printf de toda la
vida, pero en este caso el output va a parar al kernel debugger. Estos mensajes se pueden
ver perfectamente con una herramienta como DbgView de Sysinternals
Código
typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING,
*PUNICODE_STRING;
Luego de crear el dispositivo, ahora queda configurar el Driver Object para que llame a
nuestro driver cuando ciertas peticiones se realicen. Estas peticiones se denominan IRP
Major requests.
Existen tambien las denominadas Minor requests a las cuales nos referimos con
anterioridad como peticiones secundarias, se encuentran en el stack location del IRP.
Código
for(uiIndex = 0; uiIndex < IRP_MJ_MAXIMUM_FUNCTION; uiIndex++)
pDriverObject->MajorFunction[uiIndex] =
ehn_UnSupportedFunction;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] =
ehn_Close;
pDriverObject->MajorFunction[IRP_MJ_CREATE] =
ehn_Create;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
ehn_DeviceControl;
pDriverObject->MajorFunction[IRP_MJ_READ] =
ehn_Read;
pDriverObject->MajorFunction[IRP_MJ_WRITE] =
ehn_Write;
Es decir, cuando una aplicación en modo usuario llame a algunas de estas funciones:
CreateFile
CloseHandle
WriteFile
ReadFile
DeviceIoControl
se llamara a tu driver.
Rutina Unload
Código
pDriverObject->DriverUnload = ehn_Unload;
Código
void Example_Unload(PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING DosDeviceName;
DbgPrint("Unload!!\n");
RtlInitUnicodeString(&DosDeviceName, L"\\DosDevices\\ehn");
IoDeleteSymbolicLink(&DosDeviceName);
IoDeleteDevice(DriverObject->DeviceObject);
}
Definir las otras funciones es tarea para el hogar ya que creo es la parte mas divertida de
todo esto y la que supongo motivara mas para los que quieran interiorizarse en el tema.
Igualmente este ejemplo es de lo mas básico y es el que se utiliza para comprender estos
conceptos por lo tanto probablemente al buscar los conceptos que faltan para definir lo
necesario se encuentren con ejemplos conceptualmente similares, por lo tanto la dificultad
es casi nula pero si sera bastante divertido de seguro.
Código
int _cdecl main()
{
HANDLE HSmng,HServ;
SERVICE_STATUS Sstatus;
HSmng = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
printf("Cargando el driver!\n");
if(HSmng)
{
HServ = CreateService(HSmng, "ehn",
"ehn driver",
SERVICE_START | DELETE | SERVICE_STOP,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_IGNORE,
"C:\\ehn.sys",
NULL, NULL, NULL, NULL, NULL);
if(!HServ)
HServ = OpenService(HSmng, "ehn", SERVICE_START | DELETE |
SERVICE_STOP);
if(HServ)
{
printf("Iniciando servicio\n");
StartService(HServ, 0, NULL);
printf("Presione una tecla para cerrar el servicio\n");
getchar();
ControlService(HServ, SERVICE_CONTROL_STOP, &Sstatus);
DeleteService(HServ);
CloseServiceHandle(HServ);
}
CloseServiceHandle(HSmng);
}
return EXIT_SUCESS;
}
Para cualquier detalle respecto de las APIs utilizadas, referirse a la documentación del
DDK y de la MSDN.
return EXIT_SUCESS
Luego de definir las funciones correspondientes en el driver, al utilizar las APIs desde
modo usuario podrán comprobar que secciones de código se utilizan de su driver, incluso al
definir las funciones que restan les basta con poner algunos dbgprint para luego ver con
dbgview que es exactamente lo que se esta ejecutando.
Toda área tiene una lista de cosas que NO hay que hacerse, el desarrollo de drivers no es la
excepción y Microsoft se encargo de resaltar este tipo de errores.
Citar
Never return STATUS_PENDING from a dispatch routine without marking the I/O request
packet (IRP) pending (IoMarkIrpPending).
Never call KeSynchronizeExecution from an interrupt service routine (ISR). It will
deadlock your system.
Never set DeviceObject->Flags to both DO_BUFFERED_IO and DO_DIRECT_IO. It can
confuse the system and eventually lead to fatal error. Also, never set
METHOD_BUFFERED, METHOD_NEITHER, METHOD_IN_DIRECT or
METHOD_OUT_DIRECT in DeviceObject->Flags, because these values are only used in
defining IOCTLs.
Never allocate dispatcher objects from a paged pool. If you do, it will cause occasional
system bugchecks.
Never allocate memory from paged pool, or access memory in paged pool, while running at
IRQL >= DISPATCH_LEVEL. It is a fatal error.
Never wait on a kernel dispatcher object for a nonzero interval at IRQL >=
DISPATCH_LEVEL. It is a fatal error.
Never call any function that causes the calling thread to wait directly or indirectly while
executing at IRQL >= DISPATCH_LEVEL. It is a fatal error.
Never lower the interrupt request level (IRQL) below the level at which your top-level
routine has been invoked.
Never call KeLowerIrql() if you haven't called KeRaiseIrql().
Never stall a processor (KeStallExecutionProcessor) longer than 50 microseconds.
Referencias
Lectura recomendada para los que quieran interiorizarse y/o iniciarse en este tema.
WDK
Peering Inside the PE: A Tour of the Win32 Portable Executable File Format
Key driver concepts
Getting started: Writing Windows drivers
Libros:
"Programming the windows driver model"
Saludos!
http://foro.elhacker.net/programacion_cc/principios_basicos_de_desarrollo_de_drivers_en_
windows_lenguaje_c-t307017.0.html#ixzz18DKk12M3