Hay un módulo en el núcleo de Windows que es responsable de admitir la agrupación de operaciones de archivos en alguna entidad llamada transacción . Las acciones en esta entidad son aisladas y atómicas: puede aplicarse haciéndola permanente o revertida . Muy conveniente al instalar programas, ¿de acuerdo? Siempre nos movemos de un estado acordado a otro, y si algo sale mal, todos los cambios se revierten.
Como aprendí sobre el soporte de dicha funcionalidad, siempre quise mirar el mundo desde el interior de estas transacciones. Y sabes qué: encontré un método simple y realmente maravilloso para hacer que cualquier proceso funcione dentro de una transacción de archivo, pero los márgenes del libro son demasiado estrechos para él . En la mayoría de los casos, esto ni siquiera requiere privilegios administrativos.
Veamos cómo funciona, experimente con mi programa y comprenda de qué se tratan los sandboxes.
Repositorio
Para aquellos que están ansiosos por probar: TransactionMaster en GitHub .
Teoría
El soporte para NTFS transaccional, o TxF , apareció en Windows Vista e hizo posible simplificar significativamente el código responsable de la recuperación de errores en el proceso de actualización del software y el sistema operativo. De hecho, la tarea de restauración se transfirió al núcleo del sistema operativo, que comenzó a aplicar la semántica de ACID completa a las operaciones de archivo, solo pregunte.
Para admitir esta tecnología, se agregaron nuevas funciones API que duplicaban la funcionalidad existente, agregando un nuevo parámetro: una transacción. La transacción en sí se ha convertido en uno de los muchos objetos del núcleo en el sistema operativo, junto con archivos, procesos y objetos de sincronización. En el caso más simple, la secuencia de acciones cuando se trabaja con transacciones consiste en crear un objeto de transacción llamando a CreateTransaction
, trabajando con archivos (usando funciones como CreateFileTransacted
, MoveFileTransacted
, DeleteFileTransacted
y similares), y aplicando / CommitTransaction
la transacción usando CommitTransaction
/ RollbackTransaction
.
Ahora echemos un vistazo a la arquitectura de estas características. Sabemos que la capa API documentada, de bibliotecas como kernel32.dll
, no transfiere directamente el control al núcleo del sistema operativo, sino que se refiere a la capa de abstracción subyacente en modo de usuario: ntdll.dll
, que ya realiza una llamada al sistema. Y aquí nos espera una sorpresa: simplemente no hay duplicación de funciones para trabajar con archivos en el contexto de transacciones en ntdll , como en el núcleo .

Sin embargo, los prototipos de estas funciones de Native API no han cambiado desde tiempos inmemoriales, lo que significa que aprenderán de otro lugar en el contexto de qué transacción realizar la operación. ¿Pero de dónde? La respuesta es que cada subproceso tiene un campo especial en el que se almacena el identificador de la transacción actual. El área de memoria donde se encuentra se llama TEB , el bloque de entorno de flujo. De las cosas conocidas, el último código de error y el identificador de flujo también se almacenan allí.
Por lo tanto, las funciones con el sufijo *Transacted
establecen el campo de la transacción actual, llaman a una función similar sin sufijo y luego restauran el valor anterior. Lo hacen usando un par de RtlSetCurrentTransaction
RtlGetCurrentTransaction
/ RtlSetCurrentTransaction
de ntdll
. El código de las funciones en sí es muy sencillo, con la excepción del caso de WoW64 , que se analizará a continuación.
¿Qué significa todo esto para nosotros? Al cambiar una variable en la memoria del proceso, podemos controlar en el contexto de qué transacción está trabajando con el sistema de archivos. No es necesario establecer trampas ni interceptar llamadas de función, solo entregue el descriptor de transacción al proceso de destino y corrija algunos bytes en su memoria para cada uno de los hilos. Eso suena elemental, ¡hagámoslo!
Trampas
Los primeros experimentos mostraron que la idea es viable: Far Manager , que utilizo en lugar del Explorador de Windows, sobrevive perfectamente a la sustitución de transacciones sobre la marcha y le permite mirar el mundo en su contexto. Pero también hubo programas que constantemente crean nuevos hilos para las operaciones de archivos. Y en el escenario original, esta es una brecha, ya que no es muy conveniente rastrear la creación de hilos en otro proceso (sin mencionar el hecho de que la "tardanza" es crítica aquí). Un ejemplo de una aplicación de la segunda clase es el WinFile recientemente portado.
DLL de seguimiento
Afortunadamente, el seguimiento sincrónico de la creación de subprocesos y la posterior configuración de transacciones para ellos es completamente elemental desde el proceso de destino. Es suficiente para incrustar una DLL en él, y el cargador de módulos llamará a su punto de entrada con el parámetro DLL_THREAD_ATTACH
cada * vez al crear un nuevo hilo. Al implementar esta funcionalidad, arreglé la compatibilidad con una docena de programas más.
* Técnicamente, una llamada no siempre funciona, y este comportamiento a veces se puede observar en la interfaz de mi programa. En su mayor parte, las excepciones son subprocesos del grupo de trabajo del cargador de módulos. La cuestión es que las bibliotecas DLL se notifican bajo el bloqueo del cargador de arranque, y esto significa: no puede cargar nuevos módulos en este momento. Y los subprocesos del cargador, como saben, hacen exactamente eso, paralelizando el acceso al sistema de archivos. Se proporciona una excepción para tales casos: si especifica THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH
como un indicador al llamar a NtCreateThreadEx
, puede evitar agregar un nuevo subproceso a las DLL existentes y, respectivamente, puntos muertos. Sobre esto es lo que sucede aquí.
Lanzar explorador
Queda la tercera y última categoría de programas que aún se bloquean cuando se intenta que funcionen dentro de una transacción. Uno de estos programas es el Explorador de Windows. No puedo diagnosticar con precisión el problema, pero la aplicación es complicada y el cambio en caliente dentro de la transacción no lo afecta mucho. Quizás la razón es que tiene muchos descriptores de archivos abiertos, algunos de los cuales dejan de ser válidos en el nuevo contexto. O tal vez es otra cosa. En tales situaciones, reiniciar el proceso ayuda, de modo que funcione desde el principio de la transacción. Entonces no deben surgir inconsistencias.
Y por lo tanto, agregué la capacidad de iniciar nuevos procesos al programa, para lo cual la transacción y el seguimiento de nuevos flujos se configuran incluso antes de llegar al punto de entrada, mientras el proceso está en pausa. Y sabes qué, funcionó! Es cierto que, dado que Explorer utiliza activamente objetos COM fuera del proceso, la vista previa se rompe al mover archivos. Pero por lo demás, todo es estable.
¿Qué pasa con WoW64?
Este subsistema para iniciar programas de 32 bits en sistemas de 64 bits es una herramienta extremadamente conveniente, pero la necesidad de tener en cuenta sus características a menudo complica la programación del sistema. Mencioné anteriormente que el comportamiento de Rtl[Get/Set]CurrentTransaction
marcadamente diferente en el caso de procesos similares. La razón de esto radica en el hecho de que los subprocesos en los procesos WoW64 tienen hasta dos bloques de entorno. Tienen diferentes tamaños de puntero y es deseable mantenerlos en un estado coherente, aunque, en el caso de transacciones, el TEB de 64 bits tiene prioridad. Cuando establecemos transacciones de forma remota, debemos reproducir el comportamiento de estas funciones. No es difícil, pero no debe olvidarse, y los detalles se pueden encontrar aquí . Finalmente, los procesos de WoW64 necesitan una copia adicional de 32 bits de nuestra DLL de seguimiento.
Problemas sin resolver
Obligado a llorar, el primer escenario que viene a la mente, es decir, el lanzamiento de instaladores de programas en este modo, aún no está operativo. En primer lugar, no está configurado para capturar procesos secundarios en la misma transacción. Aquí hay varias soluciones, estoy trabajando en esto. Pero si la aplicación crea varios procesos, aún es demasiado pronto para usarla en combinación con transacciones.
En segundo lugar, el caso con archivos ejecutables que no existen fuera de la transacción merece especial atención. Recuerdo que había algún tipo de virus que engañaba a los antivirus ingenuos de esta manera: se descomprimió en una transacción, se lanzó y luego retiró la transacción. Hay un proceso, pero no hay un archivo ejecutable. El antivirus podría decidir que no hay nada para analizar e ignorar la amenaza. También necesitamos trabajar en soluciones creativas, porque, por alguna razón, NtCreateUserProcess
(y, en consecuencia, CreateProcess
) ignora la transacción actual. Por supuesto, NtCreateProcessEx
siempre permanece, pero se espera mucho alboroto para solucionar problemas de compatibilidad. Pensaré en algo.
¿Y dónde están los areneros?
Mira la foto. Aquí, tres programas diferentes muestran el contenido de la misma carpeta de tres transacciones diferentes. Genial, verdad?
Y, sin embargo, mi programa no es en absoluto un entorno limitado, carece de un detalle importante: la frontera de seguridad . Por supuesto, esto no impide que algunas compañías vendan artesanías similares bajo el disfraz de cajas de arena completas, una pena, qué puedo decir. Y, a pesar del hecho de que esto parece completamente imposible, ¿cómo podemos evitar que un programa cambie una variable en nuestra memoria, incluso si somos un depurador? - Tengo un truco delicioso en la tienda que me permitirá completar lo que comencé y crear el primer sandbox que conozco, que no requerirá un controlador, pero virtualizará el sistema de archivos. Hasta entonces, espere actualizaciones, use Sandboxie y experimente con la tecnología AppContainer . Gracias por su atencion
Repositorio de proyectos en GitHub: TransactionMaster .
El mismo artículo en inglés .