Wie man einen Prozess mit Transactional NTFS zum Laufen bringt: Mein erster Schritt zum Schreiben einer Sandbox für Windows

TransactionMaster Eines der Module im Windows-Kernel bietet Unterstützung für das Kombinieren einer Reihe von Dateioperationen zu einer als Transaktion bezeichneten Entität. Genau wie in Datenbanken sind diese Entitäten isoliert und atomar . Sie können einige Änderungen am Dateisystem vornehmen, die außerhalb des Dateisystems erst sichtbar werden, wenn Sie sie festschreiben . Alternativ können Sie immer alles zurücksetzen . In jedem Fall handeln Sie auf die Gruppe von Operationen als Ganzes. Genau das, was zur Wahrung der Konsistenz beim Installieren von Software oder Aktualisieren unserer Systeme erforderlich ist, oder? Wenn etwas schief geht - das Installationsprogramm oder sogar das gesamte System stürzt ab - wird die Transaktion automatisch zurückgesetzt.


Als ich zum ersten Mal einen Artikel über diesen unglaublichen Mechanismus sah, fragte ich mich immer, wie die Welt von innen aussehen würde. Und weißt du was? Ich habe gerade einen wirklich wunderbaren Ansatz gefunden, um einen Prozess dazu zu zwingen, innerhalb einer vordefinierten Transaktion zu arbeiten. Wofür dieser Rand zu eng ist . Darüber hinaus sind in den meisten Fällen nicht einmal Administratorrechte erforderlich.


Lassen Sie uns dann über Windows-Interna sprechen, ein neues Tool ausprobieren und eine Frage beantworten: Was hat es mit Sandboxen zu tun?


Repository


Wer sofort experimentieren möchte, ist auf der Projektseite von GitHub: TransactionMaster willkommen.


Theorie


Die Einführung von Transactional NTFS, auch als TxF bekannt , in Windows Vista war ein revolutionärer Schritt zur Aufrechterhaltung der Systemkonsistenz und damit der Stabilität. Durch die direkte Offenlegung dieser Funktionalität für die Entwickler konnte Microsoft die Fehlerbehandlung in allen Komponenten, die für die Installation und Aktualisierung der Software verantwortlich sind, erheblich vereinfachen. Die Aufgabe, einen Sicherungsplan für alle möglichen Dateisystemfehler zu erstellen, wurde zur Aufgabe des Betriebssystems selbst, das bei Bedarf eine umfassende ACID- Semantik bereitstellte.


Um dieses neue Instrument zur Verfügung zu stellen, hat Microsoft eine Reihe von API-Funktionen eingeführt, die vorhandene Funktionen duplizierten, jedoch innerhalb eines Transaktionskontexts. Die Transaktion selbst wurde neben vorhandenen Objekten wie Dateien, Prozessen und Synchronisationsprimitiven zu einem neuen Kernelobjekt. Im einfachsten CreateTransaction erstellt die Anwendung mit CreateTransaction eine neue Transaktion, führt die erforderlichen Vorgänge aus ( CreateFileTransacted , MoveFileTransacted , DeleteFileTransacted usw.) und CommitTransaction sie dann mit CommitTransaction / RollbackTransaction oder setzt sie zurück.


Werfen wir einen Blick auf die Architektur dieser neuen Funktionen. Wir wissen, dass die offizielle API-Schicht aus Bibliotheken wie kernel32.dll den Kernel nicht direkt aufruft, sondern die Parameter konvertiert und den Aufruf stattdessen an ntdll.dll . Was dann einen Syscall ausgibt. Überraschenderweise gibt es keine Anzeichen für zusätzliche -Transacted- Funktionen auf der Ntdll- und der Kernel-Seite des Aufrufs.


API-Schichten

Die Definitionen dieser Native API-Funktionen haben sich seit Jahrzehnten nicht geändert, sodass es keinen zusätzlichen Parameter gibt, um eine Transaktion anzugeben. Woher weiß der Kernel, welchen er dann verwenden soll? Die Antwort ist einfach, aber vielversprechend: Jeder Thread verfügt über ein bestimmtes Feld, in dem ein Handle für die aktuelle Transaktion gespeichert wird. Diese Variable befindet sich in einem bestimmten Speicherbereich namens TEB - Thread Environmental Block. Wie bei anderen bekannten Feldern, die sich auch hier befinden, kann ich den letzten Fehlercode und die Thread-ID benennen.


Aus diesem Grund setzen alle Funktionen mit dem Suffix -Transacted das aktuelle Transaktionsfeld in TEB, rufen die entsprechende nicht transaktionierte API auf und stellen den vorherigen Wert wieder her. Um dieses Ziel zu erreichen, verwenden sie zwei recht einfache Routinen mit dem Namen RtlGetCurrentTransaction / RtlSetCurrentTransaction von ntdll . Sie bieten eine ausreichende Abstraktionsebene, was im Fall von WoW64 nützlich ist, dazu später mehr.


Was bedeutet das für uns? Durch Ändern einer Variablen im Speicher können wir steuern, in welchem ​​Kontext der Prozess auf das Dateisystem zugreift. Es müssen keine Hooks oder Kernel-Modus-Callbacks installiert werden. Wir müssen lediglich das Handle an den Zielprozess übergeben und ein paar Bytes Speicher pro Thread ändern. Klingt überraschend einfach, aber das Ergebnis muss erstaunlich sein!


Fallstricke


Das erste Arbeitskonzept enthüllte viele eigentümliche Details. Zu meiner großen Freude eignet sich Far Manager , das ich als Ersatz für Windows Explorer verwende, hervorragend für das Transaktions-Hot-Switching. Ich habe aber auch ein paar Programme entdeckt, bei denen meine Methode keine Wirkung erwartet hat, da sie neue Threads für Dateioperationen erstellen. Ein Beispiel für den Vertreter der zweiten Klasse ist WinFile . Nur zur Erinnerung, die aktuelle Transaktion ist eine Pro-Thread-Funktion. Anfänglich war es eine Lücke in der Darstellung, da das Verfolgen der Erstellung von Threads außerhalb des Prozesses angesichts der Zeitempfindlichkeit dieses Vorgangs ziemlich schwierig ist.


Thread-Tracking-DLL


Glücklicherweise ist es im Kontext des Zielprozesses äußerst einfach, synchrone Benachrichtigungen zur Thread-Erstellung zu erhalten. Alles, was wir brauchen, ist eine DLL zu ntdll , die die aktuelle Transaktion an neue Threads ntdll Den Rest ntdll der Modul-Loader von ntdll . Jedes Mal, wenn * ein neuer Thread in den Prozess eintritt, wird unser Einstiegspunkt mit dem Parameter DLL_THREAD_ATTACH . Durch die Implementierung dieser Funktionalität habe ich die Kompatibilität mit einer ganzen Reihe verschiedener Programme behoben.


* Genau genommen tritt dieser Rückruf nicht unter allen möglichen Bedingungen auf. Hin und wieder sehen Sie ein oder zwei Hilfsthreads, die ohne Transaktion herumhängen. Meistens sind dies die Threads aus dem Arbeitspool des Modulladers. Der Grund dafür ist, dass DLL-Benachrichtigungen unter der Loader-Sperre erfolgen , was eine Reihe von Einschränkungen mit sich bringt, einschließlich der Möglichkeit, mehr Module zu laden. Und genau das müssen solche Threads leisten, die in der Zwischenzeit den Dateizugriff parallelisieren. Daher gibt es eine Ausnahme, um Deadlocks zu verhindern: Wenn der Aufrufer beim Erstellen eines Threads mit Hilfe von NtCreateThreadEx das Flag THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH NtCreateThreadEx , werden die DLL-Benachrichtigungs-Callbacks nicht ausgelöst.


Starten Sie den Windows Explorer


Leider gibt es immer noch einige Programme, die Transaktions-Hot-Switching nicht gut verarbeiten können, und Windows Explorer ist eines davon. Ich kann das Problem nicht zuverlässig diagnostizieren. Es ist eine komplexe Anwendung, bei der normalerweise viele Handles geöffnet sind. Wenn der Kontext einer Transaktion einige davon ungültig macht, kann dies zu einem Absturz führen. Auf jeden Fall besteht die universelle Lösung für solche Probleme darin, sicherzustellen, dass der Prozess vom ersten Befehl an in einem konsistenten Kontext ausgeführt wird.


Daher habe ich eine Option implementiert, mit der beim Erstellen eines neuen Prozesses sofort eine DLL-Injektion durchgeführt werden kann. Und es stellte sich heraus, dass es genug war, um Abstürze zu beheben. Da der Explorer jedoch intensiv Out-of-Process-COM verwendet, funktionieren die Vorschau und einige andere Funktionen bei geänderten Dateien immer noch nicht.


Was ist mit WoW64?


Die Kompatibilitätsvorteile, die das 64-Bit-Windows- Subsystem bietet, sind aufrichtig bemerkenswert. Die Berücksichtigung seiner Besonderheiten wird jedoch bei der Systemprogrammierung oftmals mühsam. Zuvor habe ich erwähnt, dass das Verhalten von Rtl[Get/Set]CurrentTransaction in diesem Fall etwas komplizierter wird. Da solche Prozesse mit einer anderen Zeigergröße als der Rest des Systems arbeiten, verwaltet jeder WoW64-Thread zwei ihm zugeordnete TEBs: Das Betriebssystem selbst erwartet eine 64-Bit-Version, und die Anwendung benötigt eine 32-Bit-Version gut, um richtig zu arbeiten. Auch wenn aus Sicht des Kernels der native TEB Vorrang hat, enthalten diese Funktionen zusätzlichen Code, um sicherzustellen, dass die entsprechenden Werte immer übereinstimmen. Auf jeden Fall ist es wichtig, all diese Besonderheiten zu berücksichtigen, wenn neue Funktionen implementiert werden .


Ungelöste Probleme


So traurig es auch ist, das erste Anwendungsszenario, das uns in den Sinn kommt - die Installation von Anwendungen in diesem Modus - funktioniert derzeit nicht gut. Erstens erstellen Installer häufig zusätzliche Prozesse, und ich habe noch nicht implementiert, dass untergeordnete Prozesse in derselben Transaktion erfasst werden. Ich sehe verschiedene Möglichkeiten, aber es kann eine Weile dauern. Ein weiteres großes Problem tritt auf, wenn wir versuchen, Binärdateien auszuführen, die während der Installation entpackt werden und daher nirgendwo anders existieren. Wenn man NtCreateUserProcess , dass NtCreateUserProcess und damit CreateProcess die aktuelle Transaktion aus irgendeinem Grund ignorieren, CreateProcess das Lösen dieses Problems wahrscheinlich etwas Kreativität, kombiniert mit einer Reihe ausgefeilter Tricks. Natürlich können wir NtCreateProcessEx in letzter Instanz immer auf NtCreateProcessEx verlassen, aber in diesem Fall kann das NtCreateProcessEx Kompatibilitätsproblemen zum Albtraum werden.


Übrigens erinnert es mich an eine Geschichte, die ich über eine Malware gelesen habe, die es geschafft hat, ein paar naive Virenschutzprogramme zu täuschen, die einen ähnlichen Ansatz verfolgen. Es wurde verwendet, um eine Nutzlast in eine Transaktion abzulegen, diese zu starten und die Änderungen rückgängig zu machen, um die Spuren abzudecken, sodass der Prozess ausgeführt werden konnte, ohne dass sich ein Image auf der Festplatte befand. Auch wenn die Logik „keine Datei - keine Bedrohung“ albern klingt, war dies vor einigen Jahren bei einigen AV-Geräten möglicherweise nicht der Fall.


Was ist mit Sandboxing?


Schauen Sie sich diesen Screenshot unten an. Sie können drei Programme sehen, die hinsichtlich des Inhalts desselben Ordners überhaupt nicht übereinstimmen. Sie arbeiten alle in drei verschiedenen Transaktionen. Das ist die Macht der Isolation in der ACID-Semantik.


Transaktionsisolation

Mein Programm ist überhaupt keine Sandbox. Es fehlt ein entscheidendes Stück - eine Sicherheitsgrenze . Ich weiß, dass es einigen Unternehmen immer noch gelingt, ähnliche Produkte zu verkaufen und sie als echte Sandkästen zu präsentieren. Schande über sie, was soll ich sagen? Und Sie könnten denken: Wie können Sie es jemals zu einer Sandbox machen, obwohl Sie als Debugger nicht zuverlässig verhindern können, dass ein Prozess eine Variable ändert, die die Transaktion steuert, sie befindet sich schließlich in seinem Speicher. Fair genug, deshalb muss ich einen weiteren wunderbaren Trick in meinem Ärmel haben, der mir irgendwann helfen wird, dieses Projekt zu beenden, und den ich vorerst nicht verrate. Ja, ich plane, eine vollständig benutzerorientierte Sandbox mit Dateisystemvirtualisierung zu erstellen. Verwenden Sie in der Zwischenzeit Sandboxie und experimentieren Sie weiter mit AppContainern . Bleib dran.


Projekt-Repository auf GitHub: TransactionMaster .

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


All Articles