Treiberfiltervorgänge in der Registrierung. Übe

Hallo habr

Als ich vor der Aufgabe stand, einen eigenen Treiber zu schreiben, der die Vorgänge in der Registrierung überwacht, suchte ich natürlich im Internet nach Informationen darüber. Das einzige, was auf Anfrage von "Treiber-Filter der Registry" herauskam, war ein Strom von Artikeln zum Schreiben eines Treiber-Filters (Hurra), ABER all diese Artikel betrafen nur den Dateisystem- Filter (Traurigkeit).

Leider wurde nur der Artikel von 2003 gefunden, dessen Code Sie in Ihrem neuen VS19 niemals sammeln werden.

Glücklicherweise gibt es auf GitHub ein großartiges Beispiel von Microsoft (ich werde sofort einen Link setzen ), auf dem der Großteil dieser Analyse aufbauen wird.

Vielleicht reicht ein Link zu einem Beispiel für Superprogrammierer aus, um alles in 5 Minuten herauszufinden. Es gibt aber auch Anfänger, Studenten, wie ich, für die dieser Artikel höchstwahrscheinlich sein wird. Ich hoffe das hilft wirklich jemandem.

Ok Gejagt. Wir öffnen ein Beispiel. Achtung! Wir haben keine Angst vor einer großen Anzahl von Dateien, 80% brauchen wir nicht.

Wir sehen 2 Ordner im Projekt: exe und sys. Das erste enthält ein Programm, mit dem der Treiber gestartet, im System registriert und nach Abschluss der Arbeit mit dem Treiber entfernt wird. Wir fangen mit ihr an.

Öffnen Sie regctrl.c

Hier ist fast der gesamte Programmcode, den wir benötigen.

Gehen Sie sofort zur wmain-Funktion. Was sehen wir dort? Laden des Treibers mit der Funktion UtilLoadDriver (util.c) und anschließende Anweisungen für einige Einstellungen:

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

Ja, Sie müssen den Parameter in die Registrierung im angegebenen Ordner eingeben (Sie können cmd oder pens verwenden). Dies ist notwendig, damit wir mehr Nachrichten vom Treiber sehen können
Vergessen Sie nicht, eine Anwendung herunterzuladen, mit der Sie Debugging-Informationen anzeigen können. Ich habe DbgView verwendet.

Weiter sehen wir 2 interessante Funktionen: DoKernelModeSamples und DoUserModeSamples - sie werden benötigt, um die Funktionsweise des Treibers zu demonstrieren. Hier sendet der erste beispielsweise eine Anfrage an den IOCL-Treiber mit der DeviceIoControl-Funktion, der Treiber startet seinerseits die notwendigen Funktionen über den zweiten Parameter IOCTL_DO_KERNELMODE_SAMPLES.

Aus der Beschreibung der DeviceIoControl-Funktion geht hervor, dass sie einen Puffer an den Treiber übergeben und diesen auch akzeptieren kann. Wir werden das in Zukunft brauchen. In dieser Datei ist in der Zwischenzeit nichts Interessantes für uns.

Gehen wir zum Ordner sys, der Datei driver.c

Beginnen wir mit der DriverEntry-Funktion. Dort zeigt der Treiber eine Art Debugging-Information an, dann erstellt die IoCreateDeviceSecure-Funktion ein benanntes Geräteobjekt und wendet die angegebenen Sicherheitsparameter an, ein interessantes Bit erwartet uns weiter:

 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; 

In Klammern stehen die Hauptfunktionscodes für das IRP. Das heißt, dies sind die Arten von Paketen, die die Aufmerksamkeit unseres Fahrers erhalten. Nach dem "=" Zeichen wird die Funktion angezeigt, die das eingehende Paket verarbeitet. Andererseits wenig interessant. ABER. Hier müssen Sie eine interessante Funktion hinzufügen. Erinnern Sie sich an diesen Ort, wir kommen wieder hierher
Wenn also bei DeviceCreate, DeviceClose, DeviceCleanup und DeviceUnload alles klar ist, was passiert dann in DeviceControl? Und dort fliegt die Anfrage unseres Programms, die wir mit der DeviceIoControl-Funktion gesendet haben. Wir greifen die Anforderung vom Stapel ab und rufen (in diesem Beispiel) nur den zweiten Parameter ab, über den ich gesprochen habe:

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

Basierend auf IoControlCode führt der Treiber eine bestimmte Funktion aus. Ich rate Ihnen, zum Beispiel die Datei pre.c zu verstehen und herauszufinden, was dort passiert.

Und wir beenden die Betrachtung des Beispiels mit dem letzten interessanten Punkt - natürlich der Rückruffunktion .

Hier werden Benachrichtigungen über Vorgänge in der Registrierung angezeigt. Erinnerst du dich an den Ort, an den ich mich erinnern wollte? Es ist etwas höher. Hier würden wir CmRegisterCallbackEx verlassen. Sie deklarieren die Rückruffunktion als "Tasche", in die die IRP-Pakete zur Verarbeitung fliegen. CallbackCtx-> Altitude bestimmt die Stufe unseres Treibers (wir sind nicht die einzigen, die die Registrierung überwachen), dh in welcher Höhe unser Treiber Pakete abfängt und etwas mit ihnen unternimmt (Wiederum ist in pre.c ziemlich klar, was und wie es passiert) : Wir registrieren die Funktion, machen etwas mit der Registrierung, alles ist repariert, Informationen werden vom Treiber angezeigt, und dann machen wir die entgegengesetzte Aktion - CmUnRegisterCallback - damit uns nichts anderes einfällt.

Oh ja Keine Panik, wenn Sie in DbgView einen endlosen Strom von Nachrichten vom Treiber finden - es gibt immer einige Hangouts in der Registrierung.

Tatsächlich können Sie aus den Argumenten der CallBack-Funktion alle erforderlichen Informationen extrahieren - sowohl die Operation, die für einen Schlüssel ausgeführt wird (dies ist nur im Code - NotifyClass), als auch den Schlüsselnamen

Gehen wir nun von diesem Beispiel weg. Überlegen Sie, was interessanterweise getan werden kann.

Eine solche Aufgabe: Lassen Sie uns die Namen von Programmen und Registrierungsschlüsseln in einer Datei haben, wir geben auch die Programmzugriffsrechte für einen bestimmten Schlüssel an (wir beschränken uns auf einfach: er hat / hat keinen Zugriff).

Unser Programm (das im exe-Ordner) liest die Konfiguration und sendet sie per IOCL-Anfrage an den Treiber. Das heißt, in der DeviceIoControl-Funktion als drittes Argument übergeben wir den Puffer. Sie können die Konfiguration beliebig übertragen und anordnen.

Der Treiber erhält diese Rechte und speichert sich in einem globalen Puffer. Das Eingabearray kann folgendermaßen erhalten werden:

 in_buf = Irp->AssociatedIrp.SystemBuffer; 

Versuchen Sie nun, einigen Programmen den Zugriff auf den Schlüssel zu verweigern
Gehen Sie zur Rückruffunktion.

Bezeichnen wir den Namen unseres Programms und den Schlüssel, auf den es keinen Zugriff hat, MyProg und MyKey.

Wir müssen herausfinden, welches Programm gerade versucht, auf den Schlüssel zuzugreifen, und dessen Namen mit denen vergleichen, die in unserer Konfiguration registriert sind. Der Prozessname kann folgendermaßen ermittelt werden:

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

Die GetProcessImageName-Funktion ist keine Bibliothek (sondern Internet), ihre verschiedenen Variationen sind in vielen Foren zu finden. Ich werde sie hier lassen:

 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; } 

Wir haben festgestellt, dass MyProg gerade auf die Registrierung zugreift. Jetzt müssen Sie herausfinden, welcher Schlüssel.

Aus dem zweiten Argument extrahieren wir Informationen über den Schlüssel, auf den zugegriffen wird

 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; } } } 

Geben Sie einfach einen Wert zurück, der auf ein Verbot hinweist. Und alle.

Dieser Artikel soll nicht sicherstellen, dass jeder, der danach liest, Superfahrer sieht.

Dies ist sozusagen eine Einführung in den Ablauf der Dinge :) Da es normalerweise nicht genug ist, wenn Sie gerade erst anfangen zu verstehen.

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


All Articles