Opérations de filtrage des pilotes dans le registre. Pratique

Bonjour, Habr!

Lorsque j'ai été confronté à la tâche d'écrire mon propre pilote, qui surveille les opérations dans le registre, j'ai bien sûr commencé à rechercher sur Internet au moins quelques informations à ce sujet. Mais la seule chose qui est sortie à la demande de «Driver-filter of the Registry» était un flux d'articles sur l'écriture d'un driver-filter (acclamations), MAIS tous ces articles ne concernaient que le filtre du système de fichiers (tristesse).

Malheureusement, la seule chose qui a été trouvée était l'article de 2003, le code à partir duquel vous ne récupérerez jamais dans votre nouveau VS19.

Heureusement, il existe un excellent exemple de Microsoft sur GitHub (je vais immédiatement lancer un lien ), sur lequel la plupart de cette analyse sera construite.

Peut-être qu'un lien vers un exemple suffit aux superprogrammeurs pour tout comprendre en 5 minutes. Mais il y a aussi des débutants, des étudiants, comme moi, pour qui, très probablement, cet article sera. J'espère que cela aide vraiment quelqu'un.

Ok Pourchassé. Nous ouvrons un exemple. Attention! Nous n'avons pas peur d'un grand nombre de fichiers, 80% dont nous n'avons pas besoin.

Nous voyons 2 dossiers dans le projet: exe et sys. Le premier contient un programme qui démarre le pilote, l'enregistre dans le système et, à la fin du travail avec le pilote, le supprime. Nous allons commencer avec elle.

Ouvrez regctrl.c

Voici presque tout le code de programme dont nous avons besoin.

Passez immédiatement à la fonction wmain. Que voyons-nous là-bas? Chargement du pilote avec la fonction UtilLoadDriver (util.c), puis instructions pour certains paramètres:

printf("\treg add \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Debug Print Filter\" /v IHVDRIVER /t REG_DWORD /d 0x8\n\n"); 

Oui, vous devez entrer le paramètre dans le registre dans le dossier spécifié (vous pouvez utiliser cmd ou utiliser des stylos). Cela est nécessaire pour que nous puissions voir plus de messages du pilote
Soit dit en passant, n'oubliez pas de télécharger une application qui vous permet de visualiser les informations de débogage, j'ai utilisé DbgView.

De plus, nous voyons 2 fonctions intéressantes: DoKernelModeSamples et DoUserModeSamples - elles sont nécessaires pour démontrer le fonctionnement du pilote. Voici le premier, par exemple, envoie une requête au pilote IOCL avec la fonction DeviceIoControl, le pilote, à son tour, lancera les fonctions nécessaires en utilisant le deuxième paramètre IOCTL_DO_KERNELMODE_SAMPLES.

D'après la description de la fonction DeviceIoControl, nous voyons qu'elle peut passer un tampon au pilote et également l'accepter. Nous en aurons besoin à l'avenir. En attendant, il n'y a rien d'intéressant pour nous dans ce dossier.

Allons dans le dossier sys, fichier driver.c

Commençons par la fonction DriverEntry. Là, le pilote affiche une sorte d'informations de débogage, puis la fonction IoCreateDeviceSecure crée un objet de périphérique nommé et applique les paramètres de sécurité spécifiés, un bit intéressant nous attend plus loin:

 DriverObject->MajorFunction[IRP_MJ_CREATE] = DeviceCreate; DriverObject->MajorFunction[IRP_MJ_CLOSE] = DeviceClose; DriverObject->MajorFunction[IRP_MJ_CLEANUP] = DeviceCleanup; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControl; DriverObject->DriverUnload = DeviceUnload; 

Entre parenthèses sont les principaux codes de fonction pour l'IRP. Autrement dit, ce sont les types de colis qui recevront l'attention de notre chauffeur. Après le signe "=", la fonction qui traitera le paquet entrant est indiquée. Là encore, peu intéressant. MAIS. Ici, vous devrez ajouter une fonctionnalité intéressante. Rappelez-vous cet endroit, nous reviendrons ici
Donc, si tout est évident avec DeviceCreate, DeviceClose, DeviceCleanup et DeviceUnload, que se passe-t-il dans DeviceControl? Et là, la demande de notre programme volera, que nous avons envoyée avec la fonction DeviceIoControl. Nous récupérons la demande de la pile et récupérons (dans cet exemple) juste le deuxième paramètre dont j'ai parlé:

 IrpStack = IoGetCurrentIrpStackLocation(Irp); Ioctl = IrpStack->Parameters.DeviceIoControl.IoControlCode; 

Basé sur IoControlCode, le pilote ira pour exécuter une fonction particulière. Je vous conseille de comprendre, par exemple, de considérer le fichier pre.c et de comprendre ce qui s'y passe.

Et nous terminerons l'examen de l'exemple par le dernier point intéressant - bien sûr, la fonction de rappel .

C'est là que les notifications d'opérations survenant dans le registre vont voler. Tu te souviens de l'endroit que j'ai demandé de retenir? C'est un peu plus haut. Ici, nous laisserions CmRegisterCallbackEx. Ils déclareront la fonction de rappel comme un «sac» dans lequel les paquets IRP voleront pour le traitement. CallbackCtx-> Altitude déterminera le niveau de notre pilote (nous ne sommes pas les seuls à surveiller le registre), c'est-à-dire à quelle hauteur notre pilote interceptera les paquets et fera quelque chose avec eux (Encore une fois, en pré.c, il est assez clair de savoir quoi et comment cela se passe : Nous enregistrons la fonction, faisons quelque chose avec le registre, tout est corrigé, les informations sont affichées par le pilote, puis nous faisons l'action inverse - CmUnRegisterCallback - pour que rien d'autre ne nous arrive).

Ah oui. Ne paniquez pas lorsque dans DbgView vous trouvez un flux infini de messages du pilote - il y a toujours des hangouts dans le registre.

En fait, à partir des arguments de la fonction CallBack, vous pouvez extraire toutes les informations nécessaires - à la fois l'opération effectuée sur une clé (c'est juste dans le code - NotifyClass) et le nom de la clé

Maintenant, éloignons-nous de cet exemple. Considérez ce qui peut être fait de façon intéressante.

Une telle tâche: laissez-nous les noms des programmes et des clés de registre dans un fichier, nous spécifions également les droits d'accès du programme à une certaine clé (nous nous limitons à simple: il a / n'a pas accès).

Notre programme (celui du dossier exe) va lire la configuration et l'envoyer au pilote en utilisant une requête IOCL. Autrement dit, dans la fonction DeviceIoControl comme troisième argument, nous passerons le tampon. Vous pouvez transférer et organiser la configuration comme vous le souhaitez.

Le pilote obtient ces droits et se sauvegarde dans un tampon global. Le tableau d'entrée peut être obtenu de cette manière:

 in_buf = Irp->AssociatedIrp.SystemBuffer; 

Essayez maintenant de refuser un accès au programme à la clé
Accédez à la fonction de rappel.

Notons le nom de notre programme et la clé à laquelle il n'a pas accès, respectivement MyProg et MyKey.

Nous devons savoir quel programme essaie actuellement d'accéder à la clé et comparer son nom avec ceux qui sont enregistrés dans notre configuration. Le nom du processus peut être obtenu de cette manière:

 PUNICODE_STRING processName = NULL; GetProcessImageName(PsGetCurrentProcess(), &processName); if (wcsstr(processName->Buffer, MyProg) != NULL) { <>} 

La fonction GetProcessImageName n'est pas une bibliothèque (mais Internet), ses différentes variantes peuvent être trouvées sur de nombreux forums. Je vais la laisser ici:

 typedef NTSTATUS(*QUERY_INFO_PROCESS) ( __in HANDLE ProcessHandle, __in PROCESSINFOCLASS ProcessInformationClass, __out_bcount(ProcessInformationLength) PVOID ProcessInformation, __in ULONG ProcessInformationLength, __out_opt PULONG ReturnLength ); QUERY_INFO_PROCESS ZwQueryInformationProcess; NTSTATUS GetProcessImageName( PEPROCESS eProcess, PUNICODE_STRING* ProcessImageName ) { NTSTATUS status = STATUS_UNSUCCESSFUL; ULONG returnedLength; HANDLE hProcess = NULL; PAGED_CODE(); // this eliminates the possibility of the IDLE Thread/Process if (eProcess == NULL) { return STATUS_INVALID_PARAMETER_1; } status = ObOpenObjectByPointer(eProcess, 0, NULL, 0, 0, KernelMode, &hProcess); if (!NT_SUCCESS(status)) { DbgPrint("ObOpenObjectByPointer Failed: %08x\n", status); return status; } if (ZwQueryInformationProcess == NULL) { UNICODE_STRING routineName = RTL_CONSTANT_STRING(L"ZwQueryInformationProcess"); ZwQueryInformationProcess = (QUERY_INFO_PROCESS)MmGetSystemRoutineAddress(&routineName); if (ZwQueryInformationProcess == NULL) { DbgPrint("Cannot resolve ZwQueryInformationProcess\n"); status = STATUS_UNSUCCESSFUL; goto cleanUp; } } /* Query the actual size of the process path */ status = ZwQueryInformationProcess(hProcess, ProcessImageFileName, NULL, // buffer 0, // buffer size &returnedLength); if (STATUS_INFO_LENGTH_MISMATCH != status) { DbgPrint("ZwQueryInformationProcess status = %x\n", status); goto cleanUp; } *ProcessImageName = ExAllocatePoolWithTag(NonPagedPoolNx, returnedLength, '2gat'); if (ProcessImageName == NULL) { status = STATUS_INSUFFICIENT_RESOURCES; goto cleanUp; } /* Retrieve the process path from the handle to the process */ status = ZwQueryInformationProcess(hProcess, ProcessImageFileName, *ProcessImageName, returnedLength, &returnedLength); if (!NT_SUCCESS(status)) ExFreePoolWithTag(*ProcessImageName, '2gat'); cleanUp: ZwClose(hProcess); return status; } 

Nous avons constaté que MyProg accède actuellement au registre. Vous devez maintenant découvrir quelle clé.

À partir du deuxième argument, nous extrayons des informations sur la clé à laquelle on accède

 REG_PRE_OPEN_KEY_INFORMATION* pRegPreCreateKey = (REG_PRE_OPEN_KEY_INFORMATION*)Argument2; if (pRegPreCreateKey != NULL) { if (wcscmp(pRegPreCreateKey->CompleteName->Buffer, MyKey) == 0) { if (){// return STATUS_SUCCESS; } else {// return STATUS_ACCESS_DENIED; } } } 

Renvoyez simplement une valeur indiquant une interdiction. Et c'est tout.

Cet article n'est pas destiné à garantir que tous ceux qui liront ensuite iront voir des super pilotes.

Ceci, pour ainsi dire, est une introduction au cours des affaires :) Étant donné que ce n'est généralement pas suffisant quand vous commencez à comprendre.

Source: https://habr.com/ru/post/fr485606/


All Articles