XPAJ. Investigación de un bootkit para Windows x64

Prólogo

La cantidad de bootkits crece sin cesar. Todo el tiempo a parecen nuevos y diferentes bootkits, complejos y simples, con diferentes objetivos (rootkits, troyanos extorsionadores, etc.). Los escritores de virus no desdeñan la oportunidad de dedicarse al análisis del código de la competencia.

Hoy en día son pocos los expertos que se sorprenderían al ver otro bootkit, ya que la inoculación del sector de arranque ha sido estudiada con gran profundidad, y en Internet hay bastante información sobre el tema. Pero esta vez tenemos en nuestro poder un ejemplar bastante interesante: el infector de ficheros Xpaj, que cuenta con funciones adicionales de bootkit y funciona tanto en Windows x86, como en Windows x64. Xpaj se caracteriza porque en los sistemas Windows x64 que tienen activo el sistema de defensa Patch Guard, funciona con éxito con el slicing en el núcleo, protegiendo el sector de arranque infectado contra lectura y modificación.

En este artículo analizaré el funcionamiento del rootkit sólo bajo Windows 7 x64. No tiene sentido analizar su funcionamiento bajo Windows x86, ya que el algoritmo de funcionamiento del rootkit en ambos sistemas operativos es más o menos el mismo.

¿Carga de prueba?

Según los resultados del análisis del mismo ejemplar de Xpaj llevado a cabo por la compañía Symantec, podemos afirmar lo siguiente:

  • el infector de ficheros Xpaj no infecta los módulos ejecutables de 64 bits, ni los drivers del modo de núcleo, ya que el virus infecta sólo los ficheros ejecutables de 32 bits (.exe y .dll);
  • los ficheros infectados no contienen mecanismos de autorreplicación;
  • el código inyectado desde el modo de núcleo en las aplicaciones de 64 bits sólo muestra un mensaje de depuración y no contiene nada más.

Basándonos en los hechos mencionados y en la estadística de infección (ver abajo) podemos suponer que este ejemplar del virus es sólo una versión de prueba. Es posible que la siguiente modificación del programa malicioso sea completa, y quizá los autores incluyan un mecanismo de infección de ficheros ejecutables de 64 bits, incluyendo los drivers del modo de núcleo (por supuesto, desactivando el mecanismo de verificación de firmas digitales).

Carga “útil”

Como de costumbre, todo empieza con la infección del MBR. La principal tarea del MBR infectado, como en casi todos los casos anteriores, es leer sectores adicionales y entregarles el mando.

En los sectores adicionales del final del disco hay módulos que el bootkit va cargando a medida de sus necesidades. Todos los módulos, excepto el primero, están comprimidos con APLib

El algoritmo de funcionamiento del primer módulo es el siguiente:

  • leer el MBR original antes de la infección y grabarlo en la memoria en lugar del infectado;
  • interceptar la interrupción 13h, responsable de la lectura y escritura de los sectores del disco;
  • entregar el mando al MBR original.

Como resultado de transferir el mando al MBR original, se inicia el sistema. Durante el inicio del sistema operativo tiene lugar la lectura del núcleo y de los componentes necesarios desde el disco. El interceptor de interrupciones del bootkit espera que se lea el fichero del núcleo, calculando las sumas de verificación desde el principio del fichero y revisando algunos campos del encabezado.

Para continuar el inicio del bootkit se escogió un método parecido al que se usó en TDL4, con la única diferencia de que esta vez la víctima fue el fichero del núcleo y no KDCOM.DLL. Al detectar la lectura del fichero del núcleo el bootkit guarda los primeros 0x120 desde el inicio del fichero y regraba el encabezado con su código.


Figura 1. Encabezado modificado del fichero del núcleo

Después, el bootkit encuentra la función exportada MmMapIoSpace y la intercepta mediante slicing. El interceptor apunta al código ubicado en el encabezado del fichero del núcleo.


Figura 2. Intercepción de la función MmMapIoSpace

El prototipo de la función tiene esta apariencia:

PVOID MmMapIoSpace(
IN PHYSICAL_ADDRESS PhysicalAddress,
IN SIZE_T NumberOfBytes,
IN MEMORY_CACHING_TYPE CacheType
);

Esta función permite visualizar la dirección física en la memoria virtual. No hay que olvidar que el primer módulo del bootkit todavía está en la memoria física en el momento de la primera llamada de esta función.

La posterior inicialización del bootkit ocurre después de la llamada de la función interceptada.


Figura 3. Pila de llamadas de MMmMapIoSpace

Durante la primera llamada, el código ubicado en el encabezado del fichero del núcleo restituye los bytes robados de la función MmMapIoSpace y llama la función original, que muestra la dirección física del primer módulo del bootkit (ver figura 1 y figura 5) en la memoria virtual. Y entrega el mando al código mostrado.


Figura 4. Encabezamiento del fichero del núcleo

Figura 16. Llamada del MMmMapIoSpace original y contenido de la memoria física

La inicialización del bootkit continúa con la entrega del mando a la memoria física mostrada.


Figura 6. Código mostrado

El algoritmo de funcionamiento subsiguiente es:

  • restituir los bytes 0x120 robados al principio del fichero del núcleo,
  • interceptar la interrupción INT 0x01 (KiDebugTrapOrFault) para cada procesador,
  • encontrar la función exportada ZwLoadDriver en el cache,
  • interceptar ZwLoadDriver mediante slicing,
  • encontrar la función exportada NtReadFile en el cache,
  • interceptar NtReadFile mediante slicing,
  • encontrar la función exportada NtWriteFile en el cache,
  • interceptar NtWriteFile mediante slicing,
  • dejar de interceptar la interrupción INT 0x01 (KiDebugTrapOrFault) para cada procesador,
  • llamar MmMapIoSpace con los parámetros originales, es decir, entregar el mando al núcleo para la consiguiente inicialización del sistema operativo.


Figura 7. Intercepción de KiDebugTrapOrFault


Figura 8. Primer estadio de las intercepciones de ZwLoadDriver, NtReadFile и NtWriteFile

La subsiguiente inicialización del bootkit se encomienda al interceptor de la función ZwLoadDriver, ya que en el primer estadio las intercepciones de NtReadFile/NtWriteFile tienen el único objetivo de entregar el mando a las funciones originales y no ejecutan ningún otro trabajo.


Figura 9. “Tapón” de la función NtReadFile

Es evidente que aquí se ha preparado un lugar especial, donde después se escribirá el código de trabajo para la intercepción de NtReadFile/NtWriteFile.

Durante la primera llamada de la función ZwLoadDriver ocurrirá la subsiguiente inicialización del bootkit.

El algoritmo de funcionamiento del interceptor ZwLoadDriver es el siguiente:

  • abrir “??physicaldrive0»”,
  • leer los sectores del tercer módulo del disco,
  • descomprimir APLib,
  • entregar el mando al punto de entrada del tercer módulo,
  • llamar el ZwLoadDriver original.
Hay algo curioso: durante la llamada del interceptor ZwLoadDriver se pone un indicador de que la función ya ha sido llamada. El indicador se pone antes de intentar abrir “??physicaldrive0”. En caso de repetición de la llamada al interceptor, el mando se entrega de inmediato a la función original. Lo que pasa es que este vínculo no aparece de inmediato en el sistema, sino en determinado momento de la inicialización del sistema operativo. De esta manera, si se fuerza la llamada ZwLoadDriver desde un driver al azar en una etapa temprana de la inicialización del sistema operativo, cuando todavía no se ha creado el vínculo en el sistema, se puede evitar la inicialización del bootkit.

Con esto termina el funcionamiento del primer módulo y se entrega el mando al tercer módulo, que concluye la inicialización del bootkit en el sistema.


Figura 10. Trabajo principal del tercer módulo

El tercer módulo ejecuta las siguientes acciones:

  • carga diferentes configuraciones,
  • instala la función de retrollamada (callback) de creación de proceso,
  • instala la función de retrollamada (callback) de carga del módulo en la memoria,
  • reemplaza los “tapones” de los interceptores NtReadFile/NtWriteFile por sus versiones completas.


Figura 11. Intercepción completa de NtReadFile

Compare el interceptor de la función NtReadFile en la figura 11 con el interceptor en la figura 9.

Es evidente que las intercepciones de NtReadFile/NtWriteFile son necesarias para proteger los lugares críticos del bootkit contra lectura y modificación.

Funciones de retrollamada (callback)

Durante la inicialización el bootkit instala dos funciones de retrollamada (callback).

La primera función desactiva los procesos de los productos antivirus. Al llamar la función durante la creación de cualquier proceso en el sistema, el bootkit calcula la suma de verificación en nombre del proceso y la compara con su lista interna de sumas de verificación.


Figura 12. Sumas de verificación de los nombres de procesos

Si la suma de verificación del nombre del proceso coincide con alguna suma de verificación de la lista del bootkit, éste inyecta la instrucción RET en el punto de entrada del proceso y lo cierra.


Figura 13. Función de clausura del proceso

El bootkit instala la segunda función de la retrollamada en la carga del módulo. La usa para introducir el código en diferentes procesos, entre ellos los de populares navegadores de Internet. De forma similar a la función de clausura del proceso, en esta función se usa un algoritmo de cálculo de la suma de verificación en nombre del proceso y se compara con la lista interna de la suma de verificación.


Figura 14. Función de inyección de código

¿Pero que pasa con Patch Guard?

Ya he mencionado el mecanismo de defensa incorporado en el núcleo de Windows x64. El propósito de Patch Guard es impedir que se introduzcan cambios en el núcleo del sistema operativo y sus estructuras críticas, como por ejemplo las tablas de servicio (SSDT, IDT, GDT), los objetos del núcleo, etc. El mecanismo de protección se activa en los primeros estadios de la inicialización del núcleo y verifica a intervalos regulares que no se hayan introducido modificaciones. Si detecta modificaciones, se bloquea el sistema. Este mecanismo se creó sobre todo para protegerse contra los rootkits que funcionan en modo de núcleo. Pero la otra cara de la moneda es que muchos antivirus y productos de defensa usaban las interrupciones en el núcleo para diferentes objetivos, entre ellos para sus módulos de defensa proactiva.

Las disputas causadas por este motivo entre las compañías antivirus y Microsoft no cesan hasta ahora. Hay quien considera que en el núcleo no deben existir intercepciones antivirus no documentadas y que se deben usar otros métodos para evitar que el código malicioso se introduzca en el núcleo. Otros consideran que la compañía Microsoft no ofrece un nivel aceptable de protección y las intercepciones son necesarias para reforzar la seguridad del sistema operativo. También hay quien piensa que si el código malicioso ha logrado penetrar el núcleo del sistema, ya no se puede confiar en él y que es inútil tratar de curar estos sistemas. A su manera, cada uno tiene razón y no quisiera prestar mucha atención a estas diferencias de opinión.

Cualquier defensa se puede vencer o evadir de una forma u otra. Patch Guard también ha corrido este destino. Este mecanismo ha sido bastante bien investigado tanto por investigadores independientes, como por los delincuentes, que han inventado varios métodos de evadirlo. Por ejemplo, TDL-4 usa un método conceptual que consiste en que el mecanismo de defensa simplemente no verifica el punto de intercepción de este rootkit. También se conocen mecanismos basados en la modificación del cargador y el fichero del núcleo del sistema operativo, destinados a desactivar la inicialización de Patch Guard. Existe incluso, un método basado en la modificación del núcleo ya inicializado, que desactiva el mecanismo de verificación. Además, Patch Guard no se inicia cuando se conecta un depurador del núcleo en el momento en que se carga el sistema operativo (para que los desarrolladores puedan usar el punto de pausa para depurar y verificar sus drivers).

Xpaj representa interés justo porque usa un método conceptual para evadir Patch Guard. Lo que pasa es que la inicialización de Patch Guard no se realiza en la etapa más temprana de carga del sistema operativo. Como Xpaj es un rootkit, puede controlar cualquier etapa de carga del sistema operativo y modificar el núcleo aún antes de que se inicialice el mecanismo de protección. En el momento de la inicialización, el núcleo ya contiene todas las modificaciones e intercepciones de X y en vez de detectarlas, Patch Guard ¡empieza a protegerlas!

Yo hice un pequeño experimento para convencerme de que Patch Guard de verdad protege las intercepciones ZwLoadDriver/NtReadFile/NtWriteFile. Inicié un sistema infectado en modo de depuración, esperé a que se cargara, conecté el depurador del núcleo y restablecí los bytes modificados de una de las funciones interceptadas. Después de unos instantes el sistema se vino abajo. Es decir, Patch Guard detectó la modificación del núcleo en la memoria y provocó un BSOD.


Como dicen en mi pueblo…esto es algo que no necesita comentarios.

Estadística

Con la ayuda de nuestro servicio “en la nube” KSN, recopilé las estadísticas de los sectores de arranque infectados por Xpaj y de la cantidad de instaladores detectados de este malware. Entre los datos estadísticos, los más interesantes son los de la distribución geográfica del bootkit y, por supuesto, los de las versiones de sistemas operativos infectados.


Figura 16. Geografía de los instaladores


Figura 17. Geografía de los MBR infectados


Figura 18. Estadística de sistemas operativos infectados

Conclusión

En 2008 registramos el renacimiento de una vieja tecnología: la infección del MBR. Desde entonces ha pasado bastante tiempo. Hoy, los bootkits son una tendencia moderna en la creación de rootkits, y en el futuro próximo este mecanismo seguirá presente en el arsenal de los delincuentes.

La infección del sector de inicio es un modo muy cómodo de iniciar el código malicioso lo más temprano posible, lo que además brinda enormes posibilidades de control del arranque del sistema operativo.

Los sistemas operativos de la familia Windows x64 han ocupado un lugar estable en la vida de los usuarios y, por supuesto, los escritores de virus tratan de hacer que sus criaturas sean universales, para mantener la cantidad de infecciones en un nivel alto.

En su lucha contra los rootkits la compañía Microsoft ha introducido una serie de limitaciones y nuevas tecnologías para Windows x64. Sin embargo, la práctica ha demostrado que ni exigir firmas válidas a los drivers del modo de núcleo, ni la presencia del mecanismo Patch Guard ha cambiado la situación en general. Los rootkits para sistemas operativos x64 existían, existen y existirán. Y su cantidad seguirá creciendo.

Publicaciones relacionadas

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *