Faire fonctionner n'importe quel processus avec NTFS transactionnel: ma première étape pour créer un sandbox pour Windows

TransactionMaster Il existe un module dans le noyau Windows chargé de prendre en charge le regroupement des opérations sur les fichiers dans une entité appelée transaction . Les actions sur cette entité sont isolées et atomiques: elle peut être appliquée en la rendant permanente ou annulée . Très pratique lors de l'installation de programmes, d'accord? Nous passons toujours d'un état convenu à un autre, et en cas de problème, toutes les modifications sont annulées.


Depuis que j'ai appris le support d'une telle fonctionnalité, j'ai toujours voulu regarder le monde de l'intérieur de ces transactions. Et vous savez quoi: j'ai trouvé une méthode simple et vraiment merveilleuse pour faire fonctionner n'importe quel processus dans une transaction de fichier, mais les marges du livre sont trop étroites pour lui . Dans la plupart des cas, cela ne nécessite même pas de privilèges administratifs.


Voyons comment cela fonctionne, expérimentons mon programme et comprenons ce que sont les bacs à sable.


Dépôt


Pour ceux qui sont impatients d'essayer: TransactionMaster sur GitHub .


Théorie


La prise en charge de NTFS transactionnel, ou TxF , est apparue dans Windows Vista, et a permis de simplifier considérablement le code responsable de la récupération des erreurs dans le processus de mise à jour du logiciel et du système d'exploitation lui-même. En fait, la tâche de restauration a été transférée au noyau du système d'exploitation, qui a commencé à appliquer la sémantique ACID complète aux opérations de fichiers - il suffit de demander.


Pour prendre en charge cette technologie, de nouvelles fonctions API ont été ajoutées qui dupliquaient les fonctionnalités existantes, ajoutant un nouveau paramètre - une transaction. La transaction elle-même est devenue l'un des nombreux objets du noyau dans le système d'exploitation, avec les fichiers, les processus et les objets de synchronisation. Dans le cas le plus simple, la séquence d'actions lorsque vous travaillez avec des transactions consiste à créer un objet de transaction en appelant CreateTransaction , à travailler avec des fichiers (à l'aide de fonctions telles que CreateFileTransacted , MoveFileTransacted , DeleteFileTransacted et similaires), et à appliquer / DeleteFileTransacted la transaction à l'aide de CommitTransaction / RollbackTransaction .


Voyons maintenant l'architecture de ces fonctionnalités. Nous savons que la couche d'API documentée, provenant de bibliothèques telles que kernel32.dll , ne transfère pas directement le contrôle au noyau du système d'exploitation, mais fait référence à la couche d'abstraction sous-jacente en mode utilisateur - ntdll.dll , qui effectue déjà un appel système. Et là, une surprise nous attend: il n'y a tout simplement pas de duplication de fonctions pour travailler avec des fichiers dans le cadre de transactions dans ntdll , comme dans le noyau .


Couches API

Néanmoins, les prototypes de ces fonctions de l'API native n'ont pas changé depuis des temps immémoriaux, ce qui signifie qu'ils apprendront ailleurs dans le contexte de quelle transaction effectuer l'opération. Mais d’où? La réponse est que chaque thread a un champ spécial dans lequel le handle de la transaction en cours est stocké. La zone de mémoire où elle se trouve est appelée TEB , le bloc d'environnement de flux. Parmi les choses connues, le dernier code d'erreur et l' identifiant de flux y sont également stockés.


Ainsi, les fonctions avec le suffixe *Transacted définissent le champ de la transaction en cours, appellent une fonction similaire sans suffixe, puis restaurent la valeur précédente. Ils le font en utilisant une paire de fonctions RtlGetCurrentTransaction / RtlSetCurrentTransaction de ntdll . Le code des fonctions elles-mêmes est très simple, à l'exception du cas avec WoW64 , qui sera discuté ci - dessous.


Qu'est-ce que tout cela signifie pour nous? En modifiant une variable dans la mémoire de processus, nous pouvons contrôler dans le contexte de quelle transaction elle travaille avec le système de fichiers. Vous n'avez pas besoin de définir d'interruptions ni d'appels de fonction d'interception, remettez simplement le descripteur de transaction au processus cible et corrigez quelques octets dans sa mémoire pour chacun des threads. Cela semble élémentaire, faisons-le!


Pièges


Les toutes premières expériences ont montré que l'idée est réalisable: Far Manager , que j'utilise à la place de Windows Explorer, survit parfaitement à la substitution des transactions à la volée, et permet de regarder le monde dans son contexte. Mais il y avait aussi des programmes qui créent constamment de nouveaux threads pour les opérations de fichiers. Et dans le scénario d'origine, c'est une lacune, car il n'est pas très pratique de suivre la création de threads dans un autre processus (sans parler du fait que le «retard» est critique ici). Un exemple d'application de la deuxième classe est le WinFile récemment porté.


DLL de suivi


Heureusement, le suivi synchrone de la création de threads et de la configuration ultérieure des transactions pour eux est complètement élémentaire depuis le processus cible. Il suffit d'y incorporer une DLL, et le chargeur de module appellera son point d'entrée avec le paramètre DLL_THREAD_ATTACH chaque * fois lors de la création d'un nouveau thread. En implémentant cette fonctionnalité, j'ai corrigé la compatibilité avec une douzaine de programmes supplémentaires.


* Techniquement, un appel ne fonctionne pas toujours, et ce comportement peut parfois être observé dans l'interface de mon programme. Pour la plupart, les exceptions sont les threads du pool de travail du chargeur de module lui-même. Le fait est que les bibliothèques DLL sont notifiées sous le verrou du chargeur de démarrage, et cela signifie: vous ne pouvez pas charger de nouveaux modules pour le moment. Et les threads du chargeur, comme vous le savez, font exactement cela, parallélisant l'accès au système de fichiers. Une exception est prévue pour de tels cas: si vous spécifiez THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH comme indicateur lors de l'appel de NtCreateThreadEx , vous pouvez éviter d'attacher un nouveau thread aux DLL existantes et, respectivement, des blocages. C'est ce qui se passe ici.


Lancer l'explorateur


Il reste la troisième, dernière catégorie de programmes qui se bloquent toujours en essayant de les faire fonctionner dans une transaction. L'un de ces programmes est l'Explorateur Windows. Je ne peux pas diagnostiquer avec précision le problème, mais l'application est compliquée et la commutation à chaud à l'intérieur de la transaction ne l'affecte pas beaucoup. La raison en est peut-être qu'il contient de nombreux descripteurs de fichiers ouverts, dont certains cessent d'être valides dans le nouveau contexte. Ou peut-être que c'est autre chose. Dans de telles situations, le redémarrage du processus est utile pour qu'il fonctionne dès le début de la transaction. Ensuite, aucune incohérence ne devrait survenir.


Et par conséquent, j'ai ajouté la possibilité de démarrer de nouveaux processus au programme, pour lesquels la transaction et le suivi des nouveaux flux sont configurés avant d'atteindre le point d'entrée, tandis que le processus est suspendu. Et vous savez quoi, ça a marché! Certes, étant donné qu'Explorer utilise activement des objets COM en dehors du processus, l'aperçu se rompt lors du déplacement de fichiers. Mais sinon, tout est stable.


Quoi de neuf avec WoW64?


Ce sous-système de lancement de programmes 32 bits sur des systèmes 64 bits est un outil extrêmement pratique, mais la nécessité de prendre en compte ses fonctionnalités complique souvent la programmation du système. J'ai mentionné ci-dessus que le comportement de Rtl[Get/Set]CurrentTransaction nettement différent dans le cas de processus similaires. La raison en est que les threads des processus WoW64 ont jusqu'à deux blocs d'environnement. Ils ont différentes tailles de pointeur, et il est souhaitable de les maintenir dans un état cohérent, bien que, dans le cas des transactions, le TEB 64 bits soit prioritaire. Lorsque nous établissons des transactions à distance, nous devons reproduire le comportement de ces fonctions. Ce n'est pas difficile, mais vous ne devez pas l'oublier, et les détails peuvent être trouvés ici . Enfin, les processus WoW64 ont besoin d'une copie supplémentaire de 32 bits de notre DLL de suivi.


Problèmes non résolus


Forcé de pleurer - le tout premier scénario qui me vient à l'esprit, à savoir le lancement des programmes d'installation dans ce mode - n'est pas encore opérationnel. Premièrement, il n'est pas configuré pour capturer les processus enfants dans la même transaction. Il y a plusieurs solutions ici, j'y travaille. Mais si l'application crée plusieurs processus, il est encore trop tôt pour l'utiliser en combinaison avec des transactions.


Deuxièmement, le cas des fichiers exécutables qui n'existent pas en dehors de la transaction mérite une attention particulière. Je me souviens qu'il y avait une sorte de virus qui trompait les antivirus naïfs de cette façon: il a été décompressé dans une transaction, s'est lancé lui-même, puis a annulé la transaction. Il y a un processus, mais il n'y a pas de fichier exécutable. L'antivirus pourrait décider qu'il n'y a rien à analyser et ignorer la menace. Nous devons également travailler sur des solutions créatives, car, pour une raison quelconque, NtCreateUserProcess (et, par conséquent, CreateProcess ) ignore la transaction en cours. Bien sûr, NtCreateProcessEx reste toujours, mais on s'attend à beaucoup de problèmes pour résoudre les problèmes de compatibilité. Je penserai à quelque chose.


Et où sont les bacs à sable?


Jetez un oeil à l'image. Ici, trois programmes différents affichent le contenu du même dossier à partir de trois transactions différentes. Cool, non?


Un regard de l'intérieur de la transaction

Et pourtant, mon programme n'est en aucun cas un bac à sable, il manque un détail important - la frontière de sécurité . Bien sûr, cela n'empêche pas certaines entreprises de vendre des objets similaires sous le couvert de bacs à sable à part entière, dommage pour eux, que puis-je dire. Et, malgré le fait que cela semble complètement impossible, comment pouvons-nous empêcher un programme de changer une variable dans notre mémoire même si nous sommes même un débogueur? - J'ai une délicieuse astuce en magasin qui me permettra de terminer ce que j'ai commencé et de créer le premier bac à sable que je connais, qui ne nécessitera pas de pilote, mais virtualisera le système de fichiers. D'ici là, attendez les mises à jour, utilisez Sandboxie et testez la technologie AppContainer . Merci de votre attention.


Référentiel de projet sur GitHub: TransactionMaster .
Le même article en anglais .

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


All Articles