Investigadores de ESET analizan el primer bootkit UEFI diseñado para sistemas Linux.
En los últimos años, el panorama de amenazas de UEFI, en particular el de los bootkits UEFI, ha evolucionado significativamente. Todo comenzó con la primera prueba de concepto (PoC) de bootkit UEFI descrita por Andrea Allievi en 2012, que sirvió como demostración de la implementación de bootkits en sistemas Windows modernos basados en UEFI, y fue seguida por muchas otras PoC ( EfiGuard , Boot Backdoor , UEFI-bootkit ). Pasaron varios años hasta que se descubrieron los dos primeros bootkits UEFI reales ( ESPEcter , 2021 ESET; FinSpy bootkit , 2021 Kaspersky), y pasaron dos años más hasta que apareció el infame BlackLotus , el primer bootkit UEFI capaz de eludir el arranque seguro UEFI en sistemas actualizados (2023, ESET).
Un denominador común entre estos bootkits conocidos públicamente era que estaban destinados exclusivamente a sistemas Windows. Hoy, presentamos nuestro último descubrimiento: el primer bootkit UEFI diseñado para sistemas Linux, llamado Bootkitty por sus creadores. Creemos que este bootkit es simplemente una prueba de concepto inicial y, según nuestra telemetría, no se ha implementado en la práctica. Dicho esto, su existencia subraya un mensaje importante: los bootkits UEFI ya no se limitan únicamente a los sistemas Windows.
El objetivo principal del bootkit es desactivar la función de verificación de firma del núcleo y precargar dos binarios ELF aún desconocidos a través del proceso init de Linux (que es el primer proceso que ejecuta el núcleo de Linux durante el arranque del sistema). Durante nuestro análisis, descubrimos un módulo de núcleo sin firmar posiblemente relacionado (con indicios que sugieren que podría haber sido desarrollado por el mismo autor o autores que el bootkit) que implementa un binario ELF responsable de cargar otro módulo de núcleo desconocido durante nuestro análisis.
Descripción general de Bootkitty
Como se mencionó en la introducción, Bootkitty contiene muchos artefactos que sugieren que podríamos estar tratando con una prueba de concepto en lugar de con malware en uso activo. En esta sección, analizamos más de cerca estos artefactos, además de otra información básica sobre el bootkit.
Bootkitty contiene dos funciones no utilizadas, capaces de imprimir cadenas especiales en la pantalla durante su ejecución. La primera función, cuyo resultado se muestra en la Figura 1, puede imprimir un arte ASCII que creemos que representa un posible nombre del bootkit: Bootkitty.
|
Figura 1. Arte ASCII incrustado en el bootkit |
La segunda función, puede imprimir texto, como se muestra en la Figura 2, que contiene la lista de posibles autores del bootkit y otras personas que tal vez participaron de alguna manera en su desarrollo. Uno de los nombres mencionados en la imagen se puede encontrar en GitHub, pero el perfil no tiene ningún repositorio público que contenga o mencione un proyecto de bootkit UEFI; por lo tanto, no podemos confirmar ni negar la autenticidad de los nombres mencionados en el bootkit.
|
Figura 2. Lista de nombres incluidos en el bootkit (redactado) |
Durante cada arranque, Bootkitty imprime en pantalla las cadenas que se muestran en la Figura 3.
|
Figura 3. Mensaje de bienvenida de Bootkitty |
Tenga en cuenta que el nombre BlackCat también se menciona en el módulo de kernel cargable que se describe más adelante. A pesar del nombre, creemos que no existe conexión con el grupo de ransomware ALPHV/BlackCat. Esto se debe a que BlackCat es un nombre utilizado por los investigadores y Bootkitty se desarrolló en C, mientras que el grupo se autodenomina ALPHV y desarrolla su malware exclusivamente en Rust.
Como se mencionó anteriormente, Bootkitty actualmente solo es compatible con un número limitado de sistemas. La razón es que para encontrar las funciones que desea modificar en la memoria, utiliza patrones de bytes codificados. Si bien la coincidencia de patrones de bytes es una técnica común cuando se trata de bootkits, los autores no utilizaron los mejores patrones para cubrir múltiples versiones del kernel o GRUB; por lo tanto, el bootkit es completamente funcional solo para un número limitado de configuraciones. Lo que limita aún más el uso del bootkit es la forma en que aplica parches al kernel Linux descomprimido: como se muestra en la Figura 4, una vez que se descomprime la imagen del kernel, Bootkitty simplemente copia los parches maliciosos en los desplazamientos codificados dentro de la imagen del kernel.
|
Figura 4. Código de Bootkitty responsable de parchar el kernel descomprimido antes de su ejecución |
Más adelante, en la sección sobre el gancho de descompresión de la imagen del kernel de Linux , explicamos cómo llega el bootkit a la aplicación de parches del kernel real . Por ahora, solo tenga en cuenta que, debido a la falta de comprobaciones de la versión del kernel en la función que se muestra en la Figura 4, Bootkitty puede llegar al punto en el que aplica parches a código o datos completamente aleatorios en estos desplazamientos codificados, lo que hace que el sistema se bloquee en lugar de comprometerlo. Este es uno de los hechos que respalda la prueba de concepto. Por otro lado, podría ser una versión inicial no lista para producción de malware creado por actores de amenazas malintencionados.
Por último, pero no menos importante, el binario bootkit está firmado por el certificado autofirmado que se muestra en la Figura 5.
|
Figura 5. Certificado autofirmado utilizado para firmar el bootkit |
Análisis técnico
Comenzamos con una descripción general de la ejecución de Bootkitty, como se muestra en la Figura 6. Primero, describimos brevemente la funcionalidad principal y luego, en las secciones posteriores, profundizamos en más detalles.
Nos centramos en tres partes principales:
Ejecución del bootkit y parcheo del gestor de arranque GRUB legítimo (puntos 4 y 5 en la Figura 6).
Aplicación de parches al cargador de stubs EFI del kernel de Linux (puntos 6 y 7 en la Figura 6).
Aplicación de parches a la imagen del kernel Linux descomprimido (puntos 8 y 9 en la Figura 6).
|
Figura 6. Descripción general de la ejecución de Bootkitty |
Inicialización y enganche de GRUB
Después de que el shim ejecuta Bootkitty, verifica si el Arranque Seguro UEFI está habilitado examinando el valor de la variable SecureBoot UEFI y procede a conectar dos funciones de los protocolos de autenticación UEFI si es así (este proceso se muestra en la Figura 7):
EFI_SECURITY2_ARCH_PROTOCOL.FileAuthentication: el firmware utiliza esta función para medir y verificar la integridad de las imágenes UEFI PE. La función de enlace de Bootkitty modifica la salida de esta función para que siempre devuelva EFI_SUCCESS, lo que significa que la verificación se realizó correctamente.
EFI_SECURITY_ARCH_PROTOCOL.FileAuthenticationState: el firmware utiliza esta función para ejecutar una política específica de la plataforma en respuesta a diferentes valores de estado de autenticación. Nuevamente, el gancho del bootkit la modifica de manera que siempre devuelva EFI_SUCCESS, lo que significa que el firmware puede usar el archivo independientemente de su estado de autenticación real.
|
Figura 7. Conexión de los protocolos de autenticación de seguridad UEFI |
Después de comprobar el estado del arranque seguro UEFI, Bootkitty procede a cargar el GRUB legítimo desde la ruta codificada en la partición del sistema EFI: /EFI/ubuntu/grubx64-real.efi. Este archivo debería ser una copia de seguridad, creada por el atacante, de un GRUB legítimo. Una vez que GRUB está cargado (aún no se ha ejecutado), el bootkit comienza a aplicar parches y a enganchar el siguiente código en la memoria de GRUB:
La función start_image dentro del módulo de GRUB peimage (un módulo integrado dentro de GRUB). Esta función es responsable de iniciar una imagen PE ya cargada, y GRUB la invoca para iniciar el binario stub EFI del kernel de Linux (conocido en general como vmlinuz.efi o vmlinuz). La función de gancho aprovecha el hecho de que en el momento en que se ejecuta el gancho, vmlinuz ya está cargado en la memoria (pero aún no se ha ejecutado) y parchea la función responsable de descomprimir la imagen real del kernel de Linux dentro de vmlinuz (tenga en cuenta que en algunos casos, debido a la forma en que se compila el kernel de Linux, puede ser bastante difícil encontrar el nombre exacto de la función que se está parcheando; sin embargo, creemos que esta vez debería ser la función zstd_decompress_dctx ). Hay más detalles sobre el gancho de descompresión en la sección Gancho de descompresión de imágenes del kernel de Linux.
La función shim_lock_verifier_init , que forma parte del mecanismo de verificación shim_lock dentro de GRUB – debería activarse automáticamente si está habilitado el arranque seguro UEFI. Es responsable de decidir si los archivos proporcionados (por ejemplo, módulos GRUB, kernel Linux, configuraciones...) deben verificarse o no durante el arranque. Sin embargo, el gancho instalado es algo confuso y las intenciones del autor no están claras porque modifica la salida de shim_lock_verifier_init de manera que establece el indicador de salida en GRUB_VERIFY_FLAGS_SINGLE_CHUNK (valor 2) para cualquier tipo de archivo proporcionado, lo que debería, según el manual de GRUB , fortalecer aún más la seguridad. Curiosamente, debido al gancho descrito en el siguiente punto, esta función shim_lock_verifier_init ni siquiera se llama durante el arranque, por lo que se vuelve irrelevante.
La función grub_verifiers_open . GRUB invoca esta función cada vez que abre un archivo y es responsable de verificar si los verificadores de archivos de GRUB instalados (esto incluye el verificador shim_lock descrito anteriormente) requieren una verificación de integridad para el archivo que se está cargando. El bootkit conecta la función de manera que regresa inmediatamente sin proceder a ninguna verificación de firma (tenga en cuenta que esto significa que ni siquiera ejecuta la función shim_lock_verifier_init conectada previamente).
Gancho de descompresión de imágenes del kernel de Linux
Este gancho es responsable de aplicar el parche a la imagen del núcleo de Linux descomprimida. El gancho se llama justo antes de que se descomprima la imagen del núcleo, por lo que restaura los bytes de la función de descompresión original y ejecuta la función original para descomprimir la imagen del núcleo antes de proceder a aplicar el parche al núcleo.
Ahora, como el núcleo está descomprimido y permanece intacto en la memoria (aún no se ha ejecutado), el código de enlace lo parchea en desplazamientos codificados (solo en la memoria). Específicamente, como se muestra en la Figura 8, esto es lo siguiente:
Reescribe la versión del kernel y las cadenas de banner de Linux con el texto BoB13 (esto no tiene un impacto significativo en el sistema).
Engancha la función module_sig_check.
Parches el puntero/dirección a la primera variable de entorno del proceso de inicio .
|
Figura 8. Gancho de descompresión del núcleo de Bootkitty dentro de vmlinuz |
La función module_sig_check está parcheada para que siempre devuelva 0. Esta función es responsable de verificar si el módulo está firmado de forma válida. Al parchear la función para que devuelva 0 , el núcleo cargará cualquier módulo sin verificar la firma. En los sistemas Linux con arranque seguro UEFI habilitado, los módulos del núcleo deben estar firmados si se pretende que se carguen. Este también es el caso cuando el núcleo se crea con CONFIG_MODULE_SIG_FORCE habilitado o cuando module.sig_enforce=1 se pasa como un argumento de línea de comandos del núcleo, como se describe en la documentación del núcleo de Linux. El escenario probable es que al menos un módulo de núcleo malicioso se cargue en una fase posterior, como el dropper analizado a continuación.
El primer proceso que ejecuta el kernel de Linux es init desde la primera ruta codificada que funciona (comenzando con /init desde initramfs), junto con los argumentos de la línea de comandos y las variables de entorno. El código de gancho reemplaza la primera variable de entorno con LD_PRELOAD=/opt/injector.so /init. LD_PRELOAD es una variable de entorno que se utiliza para cargar objetos compartidos ELF antes que otros y se puede utilizar para anular funciones. Es una técnica común utilizada por los atacantes para cargar binarios maliciosos. En este caso, los objetos compartidos ELF /opt/injector.so y /init se cargan cuando se inicia el proceso init . Aquí es donde la intención se vuelve menos clara, principalmente por qué la segunda cadena /init es parte de LD_PRELOAD.
No hemos descubierto ninguno de estos objetos compartidos ELF posiblemente maliciosos, aunque justo cuando se estaba terminando de publicar esta entrada del blog, se publicó un artículo que describe los componentes faltantes mencionados en nuestro informe . Ahora está claro que se utilizan solo para cargar otra etapa.
Impacto y remediación
Además de cargar objetos compartidos ELF desconocidos, Bootkitty deja huellas en el sistema. La primera es la modificación intencionada, aunque no necesaria, de la versión del kernel y de las cadenas de banner de Linux. La primera se puede ver ejecutando uname -v (Figura 9) y la segunda ejecutando dmesg (Figura 10).
Figura 9. Cadena BoB13 en la salida uname
Figura 10. Cadena BoB13 en la salida de dmesg
Durante nuestro análisis, la salida del comando dmesg también incluía detalles sobre cómo se ejecutaba el proceso init. Como se muestra en la Figura 11, el proceso se ejecutó con la variable de entorno LD_PRELOAD (originalmente era HOME=/ y el bootkit la reemplazó con LD_PRELOAD=/opt/injector.so /init).
|
Figura 11. Argumentos del proceso init y variables de entorno en la salida de dmesg |
Observe en la Figura 11 que la palabra /init en la primera línea corresponde al programa legítimo en initramfs que finalmente pasa el control a systemd en las instalaciones predeterminadas de Ubuntu. La presencia de la variable de entorno LD_PRELOAD también se puede verificar inspeccionando el archivo /proc/1/environ .
Después de iniciar un sistema con Bootkitty en nuestro entorno de prueba, notamos que el kernel estaba marcado como tainted (el comando de la Figura 12 se puede usar para verificar el valor tainted), lo que no era el caso cuando el bootkit estaba ausente. Otra forma de saber si el bootkit está presente en el sistema con el arranque seguro UEFI habilitado es intentar cargar un módulo de kernel ficticio sin firmar durante el tiempo de ejecución. Si está presente, se cargará el módulo; si no, el kernel se niega a cargarlo.
|
Figura 12. Estado contaminado justo después de que el sistema se haya iniciado con Bootkitty |
Un consejo sencillo para deshacerse del bootkit es mover el archivo legítimo /EFI/ubuntu/grubx64-real.efi a su ubicación original, que es /EFI/ubuntu/grubx64.efi . Esto hará que shim ejecute el GRUB legítimo y, por lo tanto, el sistema se iniciará sin el bootkit (tenga en cuenta que esto cubre solo el escenario en el que el bootkit se implementa como /EFI/ubuntu/grubx64.efi ).
BCDropper y BCObserver
Además del bootkit, descubrimos un módulo de kernel no firmado posiblemente relacionado que llamamos BCDropper, cargado en VirusTotal aproximadamente al mismo tiempo y con el mismo ID de remitente que el bootkit, que contenía indicios de que podría haber sido desarrollado por el mismo autor que el bootkit, como una cadena BlackCat en la salida del comando modinfo, que se muestra en la Figura 13,
Otra presencia de la cadena blackcat en las rutas de depuración en el binario del módulo, que se muestra en la Figura 14, y
Contiene una función de ocultación de archivos no utilizada que oculta entradas específicas de los listados de directorios. Como se muestra en la Figura 15, uno de los prefijos de cadena de nombre de archivo codificados de forma rígida que se utilizan para filtrar estas entradas es injector (tenga en cuenta que Bootkitty intenta precargar una biblioteca compartida desde la ruta /opt/injector.so).
Sin embargo, incluso con la evidencia presentada, no podemos decir con certeza si el módulo del kernel está relacionado con Bootkitty (o si fue creado por el mismo desarrollador). Además, la versión del kernel mencionada en la Figura 13 ( 6.8.0-48-generic ) no es compatible con Bootkit.
|
Figura 13. Información del módulo cuentagotas |
Figura 14. Símbolos de depuración del cuentagotas que hacen referencia a blackcat
Figura 15. Lista de archivos, en el cuentagotas, para ocultar
Como sugiere su nombre, el módulo kernel suelta un archivo ELF incrustado que llamamos BCObserver, específicamente en /opt/observer , y lo ejecuta a través de /bin/bash (Figura 17). Además de eso, el módulo se oculta a sí mismo eliminando su entrada de la lista de módulos . El módulo kernel también implementa otras funcionalidades relacionadas con el rootkit, como ocultar archivos (los de la Figura 15), procesos y puertos abiertos, pero el dropper no las utiliza directamente.
Figura 16. Código descompilado del cuentagotas Hex-Rays
BCObserver es una aplicación bastante simple que espera hasta que se esté ejecutando el administrador de pantalla gdm3 y luego carga un módulo de kernel desconocido desde /opt/rootkit_loader.ko a través de la llamada al sistema finit_module . Al esperar a que se inicie el administrador de pantalla, el código garantiza que el módulo de kernel se cargue después de que el sistema se haya iniciado por completo.
Figura 17. Código de observador descompilado de Hex-Rays
Si bien no podemos confirmar si el cuentagotas está relacionado de alguna manera con el bootkit y, de ser así, cómo debe ejecutarse, estamos bastante seguros de que el bootkit parchea la función module_sig_check por alguna razón, y cargar un módulo de kernel sin firmar (como el cuentagotas descrito aquí) definitivamente tendría sentido.
Conclusión
Ya sea una prueba de concepto o no, Bootkitty marca un avance interesante en el panorama de amenazas de UEFI, rompiendo con la creencia de que los bootkits de UEFI modernos son amenazas exclusivas de Windows. Aunque la versión actual de VirusTotal no representa, por el momento, una amenaza real para la mayoría de los sistemas Linux, enfatiza la necesidad de estar preparados para posibles amenazas futuras.
Para mantener sus sistemas Linux a salvo de tales amenazas, asegúrese de que el Arranque seguro UEFI esté habilitado, que el firmware y el sistema operativo de su sistema estén actualizados, y también lo esté su lista de revocaciones UEFI.
(Mal)Traducido de:
https://www.welivesecurity.com/en/eset-research/bootkitty-analyzing-first-uefi-bootkit-linux/
Que te diviertas!