Vous êtes ici : Accueil » Articles » Programmation
>>Conception d’un driver NT
Sommaire - Présentation PrésentationIl faut tout d’abord se souvenir que Windows NT5 a été conçu pour pouvoir exécuter divers types d’applications : les « traditionnels » Win32, Win16 et DOS, certes, mais également OS/2 et POSIX6. Pour permettre cela, une structure bien particulière a été adoptée : les modes fondamentaux. Modes fondamentaux
C’est la seconde étape : la « segmentation » des différents composants du système d’exploitation en différentes couches, ce qui, entre autre, permet de faciliter la portabilité vers différentes plates-formes matérielles. Service NT Les applications ne peuvent pas appeler directement les services de NT. Elles « passent » par un sous-système d’environnement (SSE), dont le rôle principal est de mettre à leur disposition une API9 pour accéder, de manière indirecte, aux fonctions natives du système d’exploitation. Windows NT est livré avec trois SSEs : Win32, celui dit « natif10 », OS/2 et POSIX. Un SSE actif est associé à un processus système. Par exemple, celui de Win32 est le fameux csrss.exe qui apparaît dans la liste des processus du gestionnaire des tâches (appelé par CTRL+ALT+DEL). Cette ultime étape permet de disposer, via les SSEs, de plusieurs « OS ». La mémoireLa mémoire gérée par Windows se divise en deux segments principaux.
L’espace total est de 4 Go et est divisé en 2. L’espace d’adressage est de 32 bits. Cliquez sur l’image pour l’agrandir Le développementLe développement d’un driver. Tout d’abord vous devez disposez d’un environnement de développement (DDK — Driver Developpment Kit) répondant à votre système actuel. Visitez le site de Microsoft. Il existe différent type de drivers :
Tout ces objets sont contrôlés par des Entrées/Sorties : les IRP (Input Output Request Packet). Il existe 28 types différents, mais 8 sont usuellement utilisées :
Les routines du driver
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { PDEVICE_OBJECT deviceObject = NULL; NTSTATUS status; UNICODE_STRING uniNtNameString; UNICODE_STRING uniWin32NameString; PDEVICE_EXTENSION extension; DbgPrint( ("Entered to the driver!\n") ); // // Crée une version caractere unicode // du driver // RtlInitUnicodeString(&uniNtNameString, NT_DEVICE_NAME ); // // Creation de l'objet Device // status = IoCreateDevice( DriverObject, // Device extension sizeof (DEVICE_EXTENSION), // Nom du driver &uniNtNameString, // Type du Driver FILE_DEVICE_UNKNOWN, // Device non standart 0, // Device exclusive FALSE, &deviceObject ); if ( NT_SUCCESS(status) ) { // // Creation des points d'entree // des fonctions supporté par le driver // DriverObject->MajorFunction[IRP_MJ_CREATE]= DriverOpen; DriverObject->MajorFunction[IRP_MJ_CLOSE]= DriverClose; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]= DriverDeviceControl; DriverObject->DriverUnload = DriverUnload; // Initialisation de l'extension extension = (PDEVICE_EXTENSION)deviceObject->DeviceExtension; extension->DeviceObject = deviceObject; extension->DriverObject = DriverObject; DbgPrint( ("Driver prêt!\n") ); // // Definition de la methode // employé pour utiliser les buffers // deviceObject->Flags |= DO_DIRECT_IO; // // Crée le nom de la device // pour le systeme ou // DOS_DEVICE_NAME est un define // RtlInitUnicodeString(&uniWin32NameString,DOS_DEVICE_NAME ); // // Création d'un lien symbolique entre // le nom réel du drivers et // le nom utilisé par le systeme. // status = IoCreateSymbolicLink(&uniWin32NameString,&uniNtNameString ); if (!NT_SUCCESS(status)) { DbgPrint( ("Impossible de créer le lien symboliques\n") ); IoDeleteDevice(DriverObject->DeviceObject ); } else { DbgPrint( ("Tout est initialisé!\n") ); } } else { DbgPrint( ("Impossible de créer la device\n") ); } return status; }
VOID DriverUnload( IN PDRIVER_OBJECT DriverObject) { UNICODE_STRING uniWin32NameString; PDEVICE_EXTENSION extension; extension =DriverObject->DeviceObject->DeviceExtension; DbgPrint( ("Driver déchargé!!\n") ); RtlInitUnicodeString(&uniWin32NameString,DOS_DEVICE_NAME ); // // Suppression du lien // IoDeleteSymbolicLink(&uniWin32NameString ); // // Suppression de la device // IoDeleteDevice(DriverObject->DeviceObject ); }
NTSTATUS halioDeviceControl( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp ) { NTSTATUS ntStatus; // Structure representant // le stack de l'Irp (requete) PIO_STACK_LOCATION irpStack; PDEVICE_EXTENSION extension; // Pointeur du Buffer de l'Irp PULONG ioBuffer; // Code de l'Irp ULONG ioControlCode; Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; // Initialisation du stack irpStack = IoGetCurrentIrpStackLocation(Irp); extension =DeviceObject->DeviceExtension; // Initalisation de notre buffer ioBuffer = Irp->AssociatedIrp.SystemBuffer; // initialisation du code de la requete ioControlCode = irpStack->Parameters.DeviceIoControl.IoControlCode; // Suivant le Code switch (ioControlCode) { case xxxxxx: { break; } case xxxxxx: { break; } default:{ Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; break; } } // Mise a jour du statut de la requete ntStatus = Irp->IoStatus.Status; // Irp complete IoCompleteRequest(Irp, IO_NO_INCREMENT); return ntStatus; } Je ne vais pas toutes les décrirent ici, d’une part car cela serait trop long, et d’autre part car je ne les connais pas toutes :) Vous pouvez définir vos propres codes IRP en utilisant la macro CTL_CODE. Attention toutefois à ne pas définir une code existant. #define IOCTL_Device_Function CTL_CODE( DeviceType, FunctionCode, TransferType, RequiredAccess) Les buffersPour dialoguer avec le driver, vous avez le choix entre différent type de buffer. Le dialoque sera réaliser via le client par l’api DeviceIoControl : BOOL DeviceIoControl( // handle to device of interest HANDLE hDevice, // control code of operation to perform DWORD dwIoControlCode, // pointer to buffer to supply input data LPVOID lpInBuffer, // size of input buffer DWORD nInBufferSize, // pointer to buffer to receive output data LPVOID lpOutBuffer, // size of output buffer DWORD nOutBufferSize, // pointer to variable to receive output byte LPDWORD lpBytesReturned, // pointer to overlapped structure // for asynchronous operation LPOVERLAPPED lpOverlapped );
Donc, cette méthode n’est pas souvent utilisée pour les codes de contrôles passés aux pilotes de bas niveau. Les pilotes doivent déterminer s’ils doivent donner un accès avec tampon ou direct aux données lorsqu’ils reçoivent cette requête. Il devra possiblement mettre sous verrou les tampons lorsqu’il transfèrera de l’information à ceux-ci. Tout ceci dans le but de prévenir que le logiciel appelant accède aux tampons lorsqu’un transfert de données est effectué. Exemple d’utilisation d’un driver en utilsant la méthode METHOD_BUFFERED, voir les documents liés. la Table des appels systèmes — SSDTLorsqu’un processus sous Windows NT fait une requête pour écrire sur un fichier, ouvrir un fichier, lire la base de registre, etc. le programme doit appeler une des fonctions fournies par l’API de Windows, comme WriteFile(). Ces fonctions API sont normalement composées de fonctions plus primitives se trouvant dans NTDLL.DLL, comme NtWriteFile(). Ces fonctions sont elles-mêmes de simples interfaces aux services que peut fournir le noyau du système d’exploitation (kernel). Ces fonctions noyaux ont le même nom que leur homonyme dans NTDLL.DLL, sauf qu’elles commencent par Zw à la place de Nt, comme par exemple, ZwWriteFile(). WriteFile -> NtWriteFile -> ZwWriteFile C’est la base même du fonctionnement des antivirus et rootkits. (Voir l’article sur les rootkits) Ils placent des crochets d’interceptions(hook) directement dans le noyau windows afin d’analyser/modifier les requêtes. Concrètement ils modifient l’adresse des fonctions dans la SDT en spécifiant leurs propres adresses. Ensuite suivant le but de la routine la véritable fonction est appellée ou pas. Installer un driverPour que le driver puisse etre utilisé par une application cliente, il faut que celui ci soit enregistré auprés du système. Il faut donc crée un service qui représentera le driver. Plusieurs solutions s’offrent à vous. Vous pouvez utiliser un logiciel qui permet d’installer un driver (en créant le service) ou le coder en utilisant les routines d’accès à la base de registre. Vous pouvez également générer le fichier .inf à l’aide de geninf (prévu dans la pack ddk) et faire un regini file.ini en mode console. Utilisation Pour dialoguer avec le driver il faut créer un espace mémoire, il sera crée comme si le client créé un fichier via CreateFile : HANDLE hDevice; hDevice = CreateFile ("\\\\.\\NomDriver", GENERIC_READ | GENERIC_WRITE, 0,NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL); Il est possible d’utiliser les fonctions ReadFile et WriteFile pour lire/écrire(dialoguer) avec le driver. Néanmoins, la plupart des accès aux drivers ce font via la routine DeviceIoControl : BOOL DeviceIoControl( // handle to device of interest HANDLE hDevice, // control code of operation to perform DWORD dwIoControlCode, // pointer to buffer to supply input data LPVOID lpInBuffer, // size of input buffer DWORD nInBufferSize, // pointer to buffer to receive output data LPVOID lpOutBuffer, // size of output buffer DWORD nOutBufferSize, // pointer to variable to receive output byte LPDWORD lpBytesReturned, // pointer to overlapped structure // for asynchronous operation LPOVERLAPPED lpOverlapped ); Cet article est une première approche et fait parfois référence au très bon document “API Native” de Arnold McDonald, il est donc conseillé de lire les documents liés. Références |