![]() |
![]() |
Retour menu KMD / Retour menu assembleur
A la différence du mode utilisateur, où nous pouvons appeler des fonctions directement depuis des dll, simplement en utilisant leurs adresses, en mode kernel un tel scénario serait extrêmement dangereux du point de vue de la stabilité du système. Ainsi, le système fournit un moyen intermédiaire pour communiquer avec le mode kernel. Un tel moyen est un Gestionnaire d'E/S (Entrée/Sortie) qui est un des composants du sous-système d'E/S. Ce gestionnaire d'E/S relit les applications et les composants système avec les périphériques, et défnii l'infrastructure qui supporte les modules de gestion de périphérique.
Un schéma simplifié de la façon dont le Gestionnaire d'E/S interagit avec les applications en mode utilisateur et les modules de gestion de périphérique est donné en Figure 4-1.

De la figure ci-dessus découle le fait qu'absolument tous les appels des applications en mode utilisateur vers les périphériques et, par la même, vers les modules de gestions de périphériques sont sous le contrôle du Gestionnaire d'E/S.
Le code en mode utilisateur est obligé de demander les opérations d'E/S au périphérique. Seulement et uniquement le périphérique. Le driver doit créer un périphériques (ou plusieurs) à contrôler. Dans notre cas, ce périphérique est un périphérique virtuel. Bien sûr créer un périphérique ne signifie pas la création d'un nouveau périphérique réel. Cela veut simplement dire qu'un nouvel objet va être créer en mémoire (nommément un objet de périphérique) représentant un périphérique physique ou virtuel sur le système et décrivant ses caractéristiques.
Lors de la création du périphérique, le driver dit au Gestionnaire d'E/S :"Voilà le périphérique que je dois contrôler. Si tu reçois des requêtes d'E/S vers ce périphérique, envoie-les moi et je m'occuperais du reste." Le driver sait seulement comment gérer les requêtes d'E/S pour son (ou ses) périphérique(s). La seule responsabilité du Gestionnaire d'E/S est de créer et diriger les requêtes d'E/S vers le périphérique approprié. Le code en mode utilisateur ne sait (et ne devrait) pas du tout savoir quel driver prend en charge tel ou tel périphérique(s) particulier(s).
A strictement parler ce code combine le programme de contrôle du service, responsable de l'enregistrement et du démarrage du driver, et le programme client pour communiquer avec le périphérique.
;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; ; VirtToPhys.asm - Programme de contrôle du driver VirtToPhys ; ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: .386 .model flat, stdcall option casemap:none ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; I N C L U D E F I L E S ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: include \masm32\include\windows.inc include \masm32\include\kernel32.inc include \masm32\include\user32.inc include \masm32\include\advapi32.inc includelib \masm32\lib\kernel32.lib includelib \masm32\lib\user32.lib includelib \masm32\lib\advapi32.lib include \masm32\include\winioctl.inc include \masm32\Macros\Strings.mac include common.inc ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; C O D E ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: .code ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; BigNumToString ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: BigNumToString proc uNum:UINT, pszBuf:LPSTR ; Cette fonction accèpte un nombre et le convertit en ; un chaîne de caractères, insérant les guillemets à la bonne place. local acNum[32]:CHAR local nf:NUMBERFMT invoke wsprintf, addr acNum, $CTA0("%u"), uNum and nf.NumDigits, 0 and nf.LeadingZero, FALSE mov nf.Grouping, 3 mov nf.lpDecimalSep, $CTA0(".") mov nf.lpThousandSep, $CTA0(" ") and nf.NegativeOrder, 0 invoke GetNumberFormat, LOCALE_USER_DEFAULT, 0, addr acNum, addr nf, pszBuf, 32 ret BigNumToString endp ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; start ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: start proc uses esi edi local hSCManager:HANDLE local hService:HANDLE local acModulePath[MAX_PATH]:CHAR local _ss:SERVICE_STATUS local hDevice:HANDLE local adwInBuffer[NUM_DATA_ENTRY]:DWORD local adwOutBuffer[NUM_DATA_ENTRY]:DWORD local dwBytesReturned:DWORD local acBuffer[256+64]:CHAR local acThis[64]:CHAR local acKernel[64]:CHAR local acUser[64]:CHAR local acAdvapi[64]:CHAR local acNumber[32]:CHAR invoke OpenSCManager, NULL, NULL, SC_MANAGER_ALL_ACCESS .if eax != NULL mov hSCManager, eax push eax invoke GetFullPathName, $CTA0("VirtToPhys.sys"), \ sizeof acModulePath, addr acModulePath, esp pop eax invoke CreateService, hSCManager, $CTA0("VirtToPhys"), \ $CTA0("Virtual To Physical Address Converter"), \ SERVICE_START + SERVICE_STOP + DELETE, SERVICE_KERNEL_DRIVER, \ SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, addr acModulePath, \ NULL, NULL, NULL, NULL, NULL .if eax != NULL mov hService, eax ; La procedure DriverEntry du driver va être appelée invoke StartService, hService, 0, NULL .if eax != 0 ; Le driver va recevoir de packet de requête d'E/S [I/O request packet (IRP)]de type IRP_MJ_CREATE invoke CreateFile, $CTA0("\\\\.\\slVirtToPhys"), GENERIC_READ + GENERIC_WRITE, \ 0, NULL, OPEN_EXISTING, 0, NULL .if eax != INVALID_HANDLE_VALUE mov hDevice, eax lea esi, adwInBuffer assume esi:ptr DWORD invoke GetModuleHandle, NULL mov [esi][0*(sizeof DWORD)], eax invoke GetModuleHandle, $CTA0("kernel32.dll", szKernel32) mov [esi][1*(sizeof DWORD)], eax invoke GetModuleHandle, $CTA0("user32.dll", szUser32) mov [esi][2*(sizeof DWORD)], eax invoke GetModuleHandle, $CTA0("advapi32.dll", szAdvapi32) mov [esi][3*(sizeof DWORD)], eax lea edi, adwOutBuffer assume edi:ptr DWORD ; Le driver va recevoir des IRP de type IRP_MJ_DEVICE_CONTROL invoke DeviceIoControl, hDevice, IOCTL_GET_PHYS_ADDRESS, \ esi, sizeof adwInBuffer, \ edi, sizeof adwOutBuffer, \ addr dwBytesReturned, NULL .if ( eax != 0 ) && ( dwBytesReturned != 0 ) invoke GetModuleFileName, [esi][0*(sizeof DWORD)], \ addr acModulePath, sizeof acModulePath lea ecx, acModulePath[eax-5] .repeat dec ecx mov al, [ecx] .until al == '\' inc ecx push ecx CTA0 "%s \t%08Xh\t%08Xh ( %s )\n", szFmtMod invoke BigNumToString, [edi][0*(sizeof DWORD)], addr acNumber pop ecx invoke wsprintf, addr acThis, addr szFmtMod, ecx, \ [esi][0*(sizeof DWORD)], \ [edi][0*(sizeof DWORD)], addr acNumber invoke BigNumToString, [edi][1*(sizeof DWORD)], addr acNumber invoke wsprintf, addr acKernel, addr szFmtMod, addr szKernel32, \ [esi][1*(sizeof DWORD)], \ [edi][1*(sizeof DWORD)], addr acNumber invoke BigNumToString, [edi][2*(sizeof DWORD)], addr acNumber invoke wsprintf, addr acUser, addr szFmtMod, addr szUser32, \ [esi][2*(sizeof DWORD)], \ [edi][2*(sizeof DWORD)], addr acNumber invoke BigNumToString, [edi][3*(sizeof DWORD)], addr acNumber invoke wsprintf, addr acAdvapi, addr szFmtMod, addr szAdvapi32, \ [esi][3*(sizeof DWORD)], \ [edi][3*(sizeof DWORD)], addr acNumber invoke wsprintf, addr acBuffer, \ $CTA0("Module:\t\tVirtual:\t\tPhysical:\n\n%s\n%s%s%s"), \ addr acThis, addr acKernel, addr acUser, addr acAdvapi assume esi:nothing assume edi:nothing invoke MessageBox, NULL, addr acBuffer, $CTA0("Modules Base Address"), \ MB_OK + MB_ICONINFORMATION .else invoke MessageBox, NULL, $CTA0("Can't send control code to device."), NULL, \ MB_OK + MB_ICONSTOP .endif ; Le driver va recevoir des IRP de type IRP_MJ_CLOSE invoke CloseHandle, hDevice .else invoke MessageBox, NULL, $CTA0("Device is not present."), NULL, MB_ICONSTOP .endif ; La procédure de déchargement (Unload) de notre driver va être appeléé. invoke ControlService, hService, SERVICE_CONTROL_STOP, addr _ss .else invoke MessageBox, NULL, $CTA0("Can't start driver."), NULL, MB_OK + MB_ICONSTOP .endif invoke DeleteService, hService invoke CloseServiceHandle, hService .else invoke MessageBox, NULL, $CTA0("Can't register driver."), NULL, MB_OK + MB_ICONSTOP .endif invoke CloseServiceHandle, hSCManager .else invoke MessageBox, NULL, $CTA0("Can't connect to Service Control Manager."), NULL, \ MB_OK + MB_ICONSTOP .endif invoke ExitProcess, 0 start endp ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: ; ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: end start
Sans considérer le code qui prépare les données à être envoyées vers le périphérique, et le code responsable de formatage et de l'affichage de la sortie du périphérique, il y une chose nouvelle ici - seulement trois appels : CreateFile, DeviceIoControl et CloseHandle. Toutes ces fonctions acceptent le handle du périphérique (Je répète, pas le driver) comme argument.
Après son chargement, le driver VirToPhys créé un périphérique nommé "devVirtToPhys" (Le préfixe "dev" n'est pas nécessaire, mais je l'ai ajouté avec raison - je vous dirais pourquoi plus tard).
Le nom du périphérique est placé dans l'espace de nom du Gestionnaire d'objet (Object Manager namespace). Le Gestionnaire d'objet est un composant système protégeant et surveillant les objets. Par convention, les objets de périphérique sont placés dans le répertoire \Device, non accessible par les applications utilisant l'API Win32.
Pour avoir une vision de l'espace de nom maintenu par le Gestionnaire d'objet, vous pouvez utiliser mon Windows Object Explorer (WinObjEx) (disponible sur ce site) ou Object Viewer par Mark Russinovich ( http://www.sysinternals.com/ ).
Pour voir les objets créés par VirToPhys sur votre ordinateur, faites simplement fonctionner VirtToPhys.exe, mais ne fermez pas la fenêtre de dialogue.


L'objet de driver VirtToPhys (Je n'utilise pas de préfixe pour le nom) est situé dans le répertoire \Driver.
Les noms de périphériques internes ne peuvent être utilisés par les applications Win32 (tous les répertoires exceptés "\BaseNamedObjects" et "\??", sont invisible aux programmes utilisateurs) - au lieu de cela le nom du périphérique apparaîtra dans un répertoire spécial situé dans l'espace de nom du Gestionnaire d'objet, "\??'. Ce répertoire contient des liens symboliques au nom du périphérique interne réel. Les modules de gestion de périphérique sont responsables de la création de ces liens dans le répertoire, ainsi leurs périphériques seront accessibles aux applications Win32.
Donc notre driver doit rendre possible, à un code en mode utilisateur, l'ouverture vers un objet de périphérique, pour cela il doit créer un lien symbolique dans le répertoire "\??" qui pointe sur l'objet de périphérique situé dans le répertoire "\Device". Par la suite, quand le programme appelant veut un handle de périphérique, le Gestionnaire d'E/S peut retrouver l'objet de périphérique directement.
Vous pouvez examiner ou même changer ces liens depuis le mode utilisateur avec les fonction Win32 QueryDosDevice et DefineDosDevice
Une fois ouvert le répertoire "\??", vous verrez qu'il comporte de nombreux liens symboliques. Avant Windows NT 4, ce répertoire s'appelait \DosDevices : il fut renommé en "\??" pour des raisons de performances - ce nom vient en première place dans l'ordre alphabétique.
Pour des raisons de compatibilité ascendante vous trouverez, à la racine du répertoire de l'espace de nom du Gestionnaire d'objet, un lien "\DosDevice". Celui -ci est un lien vers le répertoire "\??".
Le driver VirtToPhys créé un lien symbolique "slVirtToPhys" vers le périphérique "devVirtToPhys" dans le répertoire "\??" , dont la valeur est la chaîne de caractère "\Device\devVirtToPhys". Ici j'ai utilisé le préfixe "dev".
J'ai ajouté les préfixes uniquement pour distinguer les différents types d'objet. Ceci pour montrer qu'il n'est pas nécessaire pour le nom de périphérique et pour le lien symbolique (bien que soit le cas d'habitude) de coïncider avec le nom du driver. La chose importante ici est que le nom du lien symbolique doit spécifier un nom de périphérique valide. Encore un autre point important - Il ne peut y avoir deux objets avec les mêmes nom dans un répertoire d'objet unique, comme il est impossible d'avoir deux noms de fichiers identiques dans un seul et même répertoire.
Ainsi, à la sortie de la fonction StartService nous avons trois nouveaux objets:
Si vous vous rappelez, dans la seconde partie de ce document, je vous ais promis de vous dire ce qu'était "\??" comme dans "\??\C:\masm32\...". Donc "\??\C:" est un lien symbolique a un périphérique interne nommé "\Device\HarddiskVolume1", qui est le premier disque dur sur le système.
Revenons à notre code source. Une fois que le driver est démarré nous voulons bien sûr l'appeler. Pour ce faire, nous devons simplement ouvrir un handle de fichier vers le driver en appelant la fonction CreateFile:
La description de CreateFile prend une place importante au niveau de la documentation. Seulement une petite partie nous concernant à propos des drivers sera présentée.
CreateFile proto stdcall lpFileName:LPCSTR, dwDesiredAccess:DWORD, \ dwShareMode:DWORD, lpSecurityAttributes:LPVOID, \ dwCreationDistribution:DWORD, dwFlagsAndAttributes:DWORD, \ hTemplateFile:HANDLE
Malgré son nom la fonction peut créer ou ouvrir (beaucoup de fonctions Create* fonctionne de cette façon) des fichiers, mais aussi des objets. Microsoft aurait du nommer cette fonction CreateObject. Le périphérique peut en effet apparaître comme un objet.
| Paramètre | Description | |
|---|---|---|
lpFileName |
Pointe vers une chaîne de caractère terminée par NULL qui spécifie le nom du périphérique à ouvrir. Le lien symbolique pointant vers l'objet de périphérique pour être plus exact. |
|
dwDesiredAccess |
Spécifie le type d'accès au périphérique
Nous aurons besoin de deux valeurs: |
|
GENERIC_READ |
Spécifie un accès en lecture. Des données peuvent être lues depuis le périphérique. |
|
GENERIC_WRITE |
Spécifie un accès en écriture. Des données peuvent être écrites sur le périphérique. |
|
|
Ces drapeaux (flags) peuvent être combinés ensemble. |
||
dwShareMode |
Un jeu de drapeaux de bits spécifiant la façon dont le périphérique peut être partagé. Trois valeurs peuvent nous être utiles : |
|
0 |
Le driver ne peut être partagé. Toute opération sur le driver échouera, jusqu'a ce que le handle soit fermé. Bien que la documentation dise cela, je n'ai pu le réaliser en utilisant 0. |
|
|
Si vous devez partage le périphérique, utilisez les valeurs suivantes : |
||
FILE_SHARE_READ |
Toute opération en ouverture sur le périphérique réussira seulement si un accès en lecture est demandé. |
|
FILE_SHARE_WRITE |
Toute opération en écriture sur le périphérique réussira seulement si un accès en écriture est demandé. |
|
lpSecurityAttributes |
Pointeur vers une structure SECURITY_ATTRIBUTES. Comme aucune protection spéciale n'est nécessaire dans notre cas, et comme nous ne voulons pas que le handle retourné soit hérité par un processus enfant, nous spécifions NULL. |
|
dwCreationDistribution |
Spécifie l'action à prendre pour les fichiers qui existent et quelle action prendre pour les fichiers qui n'existent pas. Pour les périphériques, ce paramètre doit toujours être OPEN_EXISTING. |
|
dwFlagsAndAttributes |
Spécifie les attributs et les drapeaux. Ce paramètre doit toujours être égale à 0. |
|
hTemplateFile |
Spécifie un handle vers un fichier template . Pour les périphériques ce paramètre doit toujours être NULL. |
|
Si CreateFile crée ou ouvre avec succès le périphérique spécifié, un handle vers ce périphérique est retourné ; Autrement, INVALID_HANDLE_VALUE est retourné.
La plupart des fonctions de Windows qui retourne un handle retourne NULL quand elles n'ont pas fonctionnées correctement. A la différence, CreateFile retourne INVALID_HANDLE_VALUE défini comme étant -1.
Nous appelons CreateFile comme suit:
invoke CreateFile, $CTA0("\\\\.\\slVirtToPhys"), GENERIC_READ + GENERIC_WRITE, \ 0, NULL, OPEN_EXISTING, 0, NULL
J'espère que tout est clair pour les cinq derniers paramètres. Le second paramètre est une combinaison des drapeaux GENERIC_READ + GENERIC_WRITE, car nous voulons à la fois envoyer des données au périphérique mais aussi recevoir le résultat de son travail.
Examinons à présent le premier paramètre. C'est un pointeur vers le nom du lien symbolique, de la forme "\\.\slVirtToPhys". Le "\\.\" est un alias Win32 défini pour l'ordinateur local. La fonction CreateFile est une enveloppe autour de l'autre fonction NtCreateFile (qui ce situe dans \%SystemRoot%\System32\ntdll.dll), qui à son tour accède au service système correspondant (ne confondez pas avec les processus de services Win32).
Un service système est un point d'entrée dans le kernel depuis un sous-système d'environnement. Un "dispatch" de service système est déclenché lors de l'exécution d'une int 2Eh (Windows NT/2000) ou de l'instruction sysenter (Windows XP/2003) sur les processeurs X86. Exécuter cette instruction résulte en une "trap" (interruption) provoquant la transition du thread s'exécutant dans le mode kernel et l'entrée dans le dispatcher de service système.
NtCreateFile substitue un alias pour l'ordinateur local "\\.\" avec "\??" (Ainsi "\\.\slVirtToPhys" devient "\?? \slVirtToPhys") et appel la fonction du kernel ObOpenObjectByName. Au trvaers du lien symbolique ObOpenObjectByName trouve l'objet "\Device\devVirtToPhys" et retourne un pointeur vers celui-ci (ainsi le lien symbolique visible depuis le code en mode utilisateur est utilisé par le Gestionnaire d'objet pour une conversion en un nom interne). En utilisant ce pointeur NtCreateFile crée le nouveau fichier objet représentant le périphérique et retourne son handle.
Le système d'exploitation abstrait toutes les requêtes d'E/S comme des opérations sur des fichiers virtuels, cachant le fait que la cible d'une opération d'E/S n'est peut être pas un périphérique avec une structure de fichier. Le driver convertit les requêtes depuis un fichier virtuel en requêtes matérielles spécifiques. Toute donnée lu ou écrite est vue comme un simple flux d'octets dirigé vers ces fichiers virtuels.
Avant que CreateFile ne retourne, le gestionnaire d'E/S crée une IRP de type IRP_MJ_CREATE et l'envoi vers le driver pour traitement. La routine définie pour procéder à ce genre d'opération sur les IRP exécutera le même contexte de thread que l'initiateur des requêtes d'E/S (celui qui appel CreateFile) à IRQL = PASSIVE_LEVEL. Si cette routine de driver retourne avec succès, le Gestionnaire d'objet crée un handle pour l'objet de fichier dans la table des handles du processus, et le handle se propage à rebours de la chaîne (logique) appelante, pour finalement atteindre l'application comme une valeur de retour pour CreateFile.
Le nouvel objet de fichier est l'objet exécutif et n'entre pas de l'espace de nom du Gestionnaire d'objet. Vous pouvez utiliser Process Explorer par Mark Russinovich ( http://www.sysinternals.com ) pour explorer de tels objets.


Résumons nous. Donc "\\.\slVirtToPhys" devient un lien symbolique "\??\slVirtToPhys" et est finalement utilisé pour trouver le périphérique approprié "\Device\devVirtToPhys". Depuis l'objet de périphérique DEVICE_OBJECT on extrait l'information pour savoir quel driver est responsable de la gestion de ce périphérique. Alors le Gestionnaire d'E/S envoi des requêtes IRP_MJ_CREATE directement au driver. De cette façon le driver sait qu'un morceau de code essai d'accéder à son périphérique. Si le driver veut autoriser l'accès il retourne "succès". Maintenant le Gestionnaire d'objet crée un handle pour l'objet "fichier virtuel" représentant le périphérique et le retourne au code en mode utilisateur.
Les Handles et les liens symboliques servent de pointeurs indirects pour les ressources systèmes; cette indirection empêche les programmes applicatifs de jouer directement avec les structures de donnée du système.
.if eax != INVALID_HANDLE_VALUE
mov hDevice, eax
Si CreateFile retourne un handle de périphérique valide, nous sauvons ce dernier dans la variable hDevice. A présent nous pouvons communiquer avec les périphériques en appelant ReadFile, WriteFile, et DeviceIoControl. DeviceIoControl est une fonction universelle pour communiquer avec les périphériques. En voici le prototype :
DeviceIoControl proto stdcall hDevice:HANDLE, dwIoControlCode:DWORD, \ lpInBuffer:LPVOID, nInBufferSize:DWORD, \ lpOutBuffer:LPVOID, nOutBufferSize:DWORD, \ lpBytesReturned:LPVOID, lpOverlapped:LPVOID
Cette fonction accepte même plus de paramètre que CreateFile, mais elle est somme toute assez simple.
| Paramètre | Description |
|---|---|
hDevice |
Handle vers le périphérique ; |
dwIoControlCode |
-Code de contrôle indiquant quelle opération de contrôle faire ; Nous discuterons de ceci un peu plus tard. |
lpInBuffer |
Pointeur vers un buffer contenant les données requises pour faire l'opération. Ce paramètre peut être NULL si le paramètre dwIoControlCode spécifie une opération que ne requière aucune donnée en entrée ; |
nInBufferSize |
Spécifie la taille, en octets, du buffer pointé par lpInBuffer ; |
lpOutBuffer |
Pointeur vers un buffer recevant les données de sortie de l'opération. Ce paramètre peut être NULL si le paramètre dwIoControlCode spécifie une opération qui ne produit aucune donnée en sortie. |
nOutBufferSize |
Spécifie la taille, en octets, du buffer pointé par lpOutBuffer; |
lpBytesReturned |
Pointeur vers une variable qui reçoit la taille, en octets, des données stockées dans le buffer pointé par lpOutBuffer; |
lpOverlapped |
Pointeur vers une structure OVERLAPPED. Cette structure est requise pour aider à contrôler une opération asynchrone. Comme nous voulons seulement appeler notre driver de manière synchrone (DeviceIoControl ne retournera pas tant que la routine du driver appropriée ne soit complétée), nous passons NULL. |
Un module de gestion de périphérique peut être considéré un ensemble de fonction en mode kernel. Un code de contrôle d'E/S défini quel fonction sera appelée. L'argument dwIoControlCode de la fonction DeviceIoControl est utilisé à cette fin. Il indique l'opération de contrôle que nous voulons réalisée et de quelle façon elle doit l'être.
Le code de contrôle est une contant numérique sur 32 bits pouvant être définie en utilisant la macro CTL_CODE, partie intégrante des fichiers include winioctl.inc et ntddk.inc.

| Champ de bit | Description | |
|---|---|---|
DeviceType |
Le champ device type (16 bits) [type de périphérique] indique le type de périphérique qui implémente cette opération de contrôle. Les valeurs dans la fourchette 0 - 7FFFh sont réservées par Microsoft. Les valeurs allant de 8000h - 0FFFFh sont utilisables pour les développeurs de nouveau type de driver en mode kernel. Dans \include\w2k\ntddk.inc vous pourrez trouver un jeu de constantes symboliques FILE_DEVICE_XXX dont les valeurs sont dans la fourchette réservée par Microsoft. Nous utiliserons FILE_DEVICE_UNKNOWN. Cependant vous pouvez définir une autre FILE_DEVICE_XXX. |
|
Access |
Le code d'accès (2 bits) indique les droits d'accès qu'une application requière sur le handle de périphérique pour effectuer l'opération de contrôle. Comme ce champ est de seulement d'une taille de deux bits, nous avons 4 possibilités : |
|
FILE_ANY_ACCESS (0) |
Droits d'accès maximum. Le driver procédera à l'opération demandée pour tout programme appelant qui posséderait un handle pour son périphérique. |
|
FILE_READ_ACCESS (1) |
Droits d'accès en lecture. Avec ce droit d'accès, le driver de périphérique transfère des données depuis le périphérique vers le buffer mémoire. |
|
FILE_WRITE_ACCESS (2) |
Droits d'accès en écriture. Avec ce droit d'accès, le driver de périphérique transfère des données depuis le buffer mémoire vers le périphérique. |
|
FILE_READ_ACCESS or FILE_WRITE_ACCESS (3) |
Droits d'accès à la fois en lecture et en écriture. Avec ce droit d'accès, le driver de périphérique transfère des données entre le buffer mémoire et le périphérique (dans les deux sens). |
|
Function |
Le code de fonction (12 bits) indique précisément quelle opération de contrôle ce code décrit. Il peut prendre des valeurs entre 800h - 0FFFh pour les codes de contrôle privés d'E/S. Les valeurs dans la fourchette 0 - 7FFh sont réservés par Microsoft pour les codes de contrôles publics d'E/S. |
|
Method |
La méthode de "buffering" (2 bits) indique comment le Gestionnaire d'E/S va s'occuper des buffers d'entrée et de sortie fournis par l'application. Ce champ est de seulement d'une taille de deux bits, nous avons donc 4 possibilités (définies en tant que constantes) : |
|
METHOD_BUFFERED (0) |
buffered E/S; |
|
METHOD_IN_DIRECT (1) |
E/S directe ; |
|
METHOD_OUT_DIRECT (2) |
||
METHOD_NEITHER (3) |
neither E/S. => E/S sans gestion |
|
Nous aborderons un peu plus tard, et plus en détail, la gestion des buffers. A présent la chose importante est de savoir que la méthode de "buffered E/S" est la plus sûre car c'est le système qui supervise la gestion des buffers, celui ci gérant les opérations de copie en mémoire. Les drivers utilisent communément les E/S par buffers quand le programme appelant requière de petit transfères de mémoire (quelques pages), pour des raisons complexes dues à la gestion mémoire (à ce sujet voir MDL "Memory descriptor list"). Nous utiliserons la méthode de "buffered E/S" dans le driver VirtToPhys.
Vous pouvez former des codes de contrôle d'E/S manuellement, mais il est plus pratique d'utiliser la macro CTL_CODE, qui offre un mécanisme pour générer des valeurs IOCTL. La voici :
CTL_CODE MACRO DeviceType:=<0>, Function:=<0>, Method:=<0>, Access:=<0> EXITM %(((DeviceType) SHL 16) OR ((Access) SHL 14) OR ((Function) SHL 2) OR (Method)) ENDM
Comme je m'ai déjà dit, la macro CTL_CODE est définie à la fois dans winioctl.inc, qui est inclue dans le code source du programme de contrôle de service, et dans ntddk.inc, inclus dans le code source du driver.
Comme nous utilisons les constantes NUM_DATA_ENTRY et DATA_SIZE, ainsi que code de contrôle d'E/S IOCTL_GET_PHYS_ADDRESS à la fois dans le programme de contrôle de service et dans le driver, ils sont placés dans un fichier include nommé common.inc. Ainsi, tout changement dans ce fichier sera répercuté dans les deux codes source.
NUM_DATA_ENTRY equ 4 DATA_SIZE equ (sizeof DWORD) * NUM_DATA_ENTRY IOCTL_GET_PHYS_ADDRESS equ CTL_CODE(FILE_DEVICE_UNKNOWN, 800h, METHOD_BUFFERED, FILE_READ_ACCESS + FILE_WRITE_ACCESS)
Retournons à présent au code source du driver.
lea esi, adwInBuffer
assume esi:ptr DWORD
invoke GetModuleHandle, NULL
mov [esi][0*(sizeof DWORD)], eax
invoke GetModuleHandle, $CTA0("kernel32.dll", szKernel32)
mov [esi][1*(sizeof DWORD)], eax
invoke GetModuleHandle, $CTA0("user32.dll", szUser32)
mov [esi][2*(sizeof DWORD)], eax
invoke GetModuleHandle, $CTA0("advapi32.dll", szAdvapi32)
mov [esi][3*(sizeof DWORD)], eax
Ici nous remplissons le buffer adwInBuffer avec les adresses virtuelles à convertir.
lea edi, adwOutBuffer
assume edi:ptr DWORD
invoke DeviceIoControl, hDevice, IOCTL_GET_PHYS_ADDRESS, \
esi, sizeof adwInBuffer, \
edi, sizeof adwOutBuffer, \
addr dwBytesReturned, NULL
En appelant DeviceIoControl nous passons un buffer au driver qui convertira chaque adresse virtuelle en adresse physique.
.if ( eax != 0 ) && ( dwBytesReturned != 0 ) invoke GetModuleFileName, [esi][0*(sizeof DWORD)], \ addr acModulePath, sizeof acModulePath lea ecx, acModulePath[eax-5] .repeat dec ecx mov al, [ecx] .until al == '\' inc ecx push ecx CTA0 "%s \t%08Xh\t%08Xh ( %s )\n", szFmtMod invoke BigNumToString, [edi][0*(sizeof DWORD)], addr acNumber pop ecx invoke wsprintf, addr acThis, addr szFmtMod, ecx, \ [esi][0*(sizeof DWORD)], \ [edi][0*(sizeof DWORD)], addr acNumber invoke BigNumToString, [edi][1*(sizeof DWORD)], addr acNumber invoke wsprintf, addr acKernel, addr szFmtMod, addr szKernel32, \ [esi][1*(sizeof DWORD)], \ [edi][1*(sizeof DWORD)], addr acNumber invoke BigNumToString, [edi][2*(sizeof DWORD)], addr acNumber invoke wsprintf, addr acUser, addr szFmtMod, addr szUser32, \ [esi][2*(sizeof DWORD)], \ [edi][2*(sizeof DWORD)], addr acNumber invoke BigNumToString, [edi][3*(sizeof DWORD)], addr acNumber invoke wsprintf, addr acAdvapi, addr szFmtMod, addr szAdvapi32, \ [esi][3*(sizeof DWORD)], \ [edi][3*(sizeof DWORD)], addr acNumber invoke wsprintf, addr acBuffer, \ $CTA0("Module:\t\tVirtual:\t\tPhysical:\n\n%s\n%s%s%s"), \ addr acThis, addr acKernel, addr acUser, addr acAdvapi assume esi:nothing assume edi:nothing invoke MessageBox, NULL, addr acBuffer, $CTA0("Modules Base Address"), \ MB_OK + MB_ICONINFORMATION .else invoke MessageBox, NULL, $CTA0("Can't send control code to device."), NULL, \ MB_OK + MB_ICONSTOP .endif
Si DeviceIoControl retourne avec succès dwBytesReturned est égale au nombre d'octets dont le buffer adwOutBuffer aura été rempli par le driver. Ensuite il nous faut prendre les données et les afficher à l'utilisateur. Je suis sûr que vous êtes assez futé pour comprendre ce qui se passe ici. Les séquences d'échappement utilisées dans $CTA0 sont des séquences communes (voir \Macros\Strings.mac pour les détails)

invoke CloseHandle, hDevice
A présent nous avons juste à fermer le handle ouvert sur le périphérique. A ce point, le Gestionnaire d'E/S envoi deux IRP au driver de périphérique. Dans un premier temps il s'agit de IRP_MJ_CLEANUP pour dire au driver que le handle de périphérique va être fermé. Ensuite vient IRP_MJ_CLOSE, pour dire au driver que le handle de périphérique a bien été fermé. Vous pouvez aussi évitez la fermeture du handle de périphérique en retournant un code d'erreur depuis la routine responsable de la gestion de la requête IRP_MJ_CLEANUP. La routine du driver définie à cet effet (gestion de ce type d'IRP) exécutera le même contexte de thread que l'initiateur des requêtes d'E/S (celui qui appelle CloseHandle) à IRQL = PASSIVE_LEVEL.
Nous parlerons dans la prochaine partie de la façon dont le driver gère les IRP.
Pour faire fonctionner le driver sur des environnements précédent les dernières versions de Windows NT (donc NT 4) vous devrez changer "\??" en "\DosDevices" et recompiler le driver, car comme il à été mentionné, sur Windows NT4 le répertoire "\??" s'appelait "\DosDevices".
Copyright © 2002-2004 Four-F, four-f@mail.ru
Traduction par Neitsa , tzcorporation@hotmail.com