ACTUALIZACIÓN (2 de diciembre de 2024): El bootkit descrito en este informe parece formar parte de un proyecto creado por estudiantes de ciberseguridad que participan en el programa de formación coreano Best of the Best (BoB). Según nos informaron: "El objetivo principal de este proyecto es concienciar a la comunidad de seguridad sobre los riesgos potenciales y fomentar medidas proactivas para prevenir amenazas similares. Desafortunadamente, pocas muestras de bootkits fueron reveladas antes de la presentación prevista en la conferencia".Esto apoya nuestra creencia de que se trataba de una prueba inicial de concepto y no de malware listo para la producción utilizado por verdaderos actores de amenazas. No obstante, la entrada del blog sigue siendo correcta: se trata de un bootkit funcional con soporte limitado y representa la primera prueba de concepto de bootkit UEFI para el sistema operativo Linux.

En los últimos años, el panorama de las amenazas UEFI (Unified Extensible Firmware Interface), 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 del despliegue de bootkits en sistemas Windows modernos basados en UEFI, y a la que siguieron muchas otras PoC (EfiGuard, Boot Backdoor, UEFI-bootkit). Pasaron varios años hasta que se descubrieron los dos primeros bootkits UEFI reales in the wild (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 UEFI Secure Boot en sistemas actualizados (2023, ESET).

Un punto en común entre estos bootkits era que estaban dirigidos exclusivamente a sistemas Windows. Hoy desvelamos nuestro último descubrimiento: el primer bootkit UEFI diseñado para sistemas Linux al que sus creadores llamaron Bootkitty. Creemos que este bootkit no es más que una prueba inicial de concepto y, según nuestra telemetría, no se ha desplegado en la naturaleza. 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 deshabilitar la función de verificación de firma del kernel y precargar dos binarios ELF aún desconocidos a través del proceso init de Linux (que es el primer proceso ejecutado por el kernel de Linux durante el arranque del sistema). Durante nuestro análisis, descubrimos un módulo del kernel posiblemente relacionado y sin firma -con indicios que sugieren que podría haber sido desarrollado por el mismo autor o autores que el bootkit- que despliega un binario ELF responsable de cargar otro módulo del kernel desconocido durante nuestro análisis.

Puntos claves de este blogpost:
  • En noviembre 2024, una aplicación UEFI desIconocida, llamada bootkit.efi, fue subida a VirusTotal.
  • Nuestro análisis inicial confirmó que es un bootkit UEFI, llamado Bootkitty por sus creadores y sorprendentemente es el primer bootkit UEFI apuntando a Linux, en particular, a versiones Ubuntu.
  • Bootkitty está firmado por un certificado self-signed, por lo que no es capaz de ejecutarse en sistemas con UEFI Secure Boot habilitado, a menos que se hayan instalado los certificados de los atacantes.
  • Está diseñado para arrancar el kernel de Linux sin problemas, este o no haiblitado el UEFI Secure Boot, ya que parchea, en la memoria, las funciones necesarias para la verificación de integridad antes de que se ejecute GRUB.
  • bootkit.efi contiene muchos artefactos que sugieren que se trata más de una prueba de concepto que del trabajo de un actor activo de amenazas.
  • Descubrimos un módulo del kernel posiblemente relacionado, al que denominamos BCDropper, que despliega un programa ELF responsable de cargar otro módulo del kernel.

Visió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 un malware utilizado activamente. En esta sección, examinaremos 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, cuya output se muestra en la Figura 1, puede imprimir arte ASCII que creemos que representa el nombre del bootkit: Bootkitty.

Figure 1. ASCII art embedded in the bootkit
Figura 1. Arte ASCII insertado en el bootkit

La segunda función, puede imprimir un texto, mostrado en la Figura 2, que contiene la lista de posibles autores del bootkit y otras personas que quizás 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.

Figure 2. List of names embedded in the bootkit (redacted)
Figura 2. Lista de nombres insertados en el bootkit

En cada arranque, Bootkitty imprime en la pantalla las cadenes mostradas en la figura 3.

Figure 3. Bootkitty’s welcome message
Figura 3. Mensaje de bienvenida de Bootkitty

Nótese que el nombre BlackCat se menciona también en el módulo cargable del kernel descrito más adelante. A pesar del nombre, creemos que no hay conexión con el grupo de ransomware ALPHV/BlackCat. Esto se debe a que BlackCat es un nombre utilizado por los investigadores y Bootkitty fue desarrollado en C, mientras que el grupo se hace llamar ALPHV y desarrolla su malware exclusivamente en Rust.

Como se mencionó anteriormente, Bootkitty actualmente sólo es compatible con un número limitado de sistemas. La razón es que, para encontrar las funciones que quiere modificar en la memoria, utiliza patrones de bytes codificados. Mientras que 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 sólo para un número limitado de configuraciones. La forma en que parchea el kernel de Linux descomprimido limita aún más el uso del bootkit es: 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.

Figure 4. Bootkitty’s code responsible for patching the decompressed kernel before it is executed
Figura 4. Código de BootKitty responsable de parchear el kernel descomprimido antes de ejecutarlo

Explicaremos cómo el bootkit llega al parcheado real del kernel más adelante en la sección Hook de descompresión de imágenes del Kernel de Linux; por ahora, sólo ten 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 de parchear 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 apoyan la prueba de concepto. Por otro lado, podría tratarse de una versión inicial no lista para la producción de un malware creado por actores de amenazas.

Por último, pero no menos importante, el binario del bootkit está firmado por el certificado autofirmado que se muestra en la Figura 5.

Figure 5. Self-signed certificate used to sign the bootkit
Figura 5. Certificado self-signed

Análisis técnico

Comenzamos con una visión general de la ejecución de Bootkitty, como se muestra en la Figura 6. En primer lugar, describimos brevemente la funcionalidad principal y, en secciones posteriores, entramos en más detalles.

Nos centramos en tres partes principales:

  • Ejecución del bootkit y el parcheo del bootloader legítimo GRUB (puntos 4 y 5 en la figura 6).
  • Parcheo del stub loader EFI del kernel Linux (puntos 6 y 7 de la Figura 6).
  • Parcheo y descompresión de las imágenes del kernel de Linux (puntos 8 y 9 de la figura 6).
Figure 6. Bootkitty bootkit execution overview
Figura 6. Descripción general de la ejecución de Bootkitty

Initialización y GRUB hooking

Después de que Bootkitty es ejecutado por el shim, comprueba si UEFI Secure Boot está habilitado examinando el valor de la variable UEFI  SecureBoot, y procede a hookear dos funciones de los protocolos de autenticación UEFI (este proceso se muestra en la Figura 7):

  • EFI_SECURITY2_ARCH_PROTOCOL.FileAuthentication: esta función es utilizada por el firmware para medir y verificar la integridad de las imágenes UEFI PE. La función hook de Bootkitty modifica la salida de esta función para que siempre devuelva EFI_SUCCESS, lo que significa que la verificación tuvo éxito.
  • EFI_SECURITY_ARCH_PROTOCOL.FileAuthenticationState: esta función es utilizada por el firmware para ejecutar una política específica de la plataforma en respuesta a diferentes valores de estado de autenticación. De nuevo, el hook del bootkit la modifica de forma que siempre devuelve EFI_SUCCESS, lo que significa que el firmware puede utilizar el archivo independientemente de su estado de autenticación real.
Figure 7. Hooking of the UEFI security authentication protocols
Figura 7. Hooking de los protocolos de autenticación de seguridad UEFI

Después de comprobar el estado de UEFI Secure Boot, Bootkitty procede a cargar el GRUB legítimo desde la ruta codificada en la partición EFI del sistema: /EFI/ubuntu/grubx64-real.efi.> Este archivo podría ser una copia de seguridad, creada por el atacante, de un GRUB legítimo. Una vez cargado GRUB (aún no ejecutado), el bootkit comienza a parchear y hookear el siguiente código en la memoria de GRUB:

  • La función start_image en el módulo peimage GRUB (a embebido dentro de GRUB). Esta función es responsable de iniciar una imagen PE ya cargada, y es invocada por GRUB para iniciar el binario stub EFI del kernel Linux (conocido en general como vmlinuz.efi o vmlinuz). La función hook aprovecha el hecho de que en el momento en que se ejecuta el hook, vmlinuz ya está cargado en memoria (pero aún no se ha ejecutado), y parchea la función responsable de descomprimir la imagen real del kernel Linux dentro de vmlinuz (ten en cuenta que en algunos casos, debido a la forma en que se compila el kernel Linux, puede ser bastante complicado encontrar el nombre exacto de la función que se parchea; sin embargo, creemos que en esta ocasión debería ser la función zzstd_decompress_dctx). Encontrará más detalles sobre hook de descompresión en la sección Hook de descompresión de imágenes de Kernel Linux
  • La función shim_lock_verifier_init, que es parte del mecanismo de verificación shim_lock  dentro de GRUB  – debería activarse automáticamente si el UEFI Secure Boot está habilitado. Es responsable de decidir si los ficheros proporcionados (por ejemplo, módulos de GRUB, kernel de Linux, configuraciones...) deben ser verificados o no durante el arranque. El hook instalado, sin embargo, es algo confuso y las intenciones del autor no están claras porque modifica el output de shim_lock_verifier_init en una forma que establece el flag de salida a GRUB_VERIFY_FLAGS_SINGLE_CHUNK (valor 2) para cualquier tipo de fichero proporcionado, lo que debería, según el manual de GRUB, reforzar la seguridad aún más. Curiosamente, debido al hook descrito en el siguiente punto, esta función shim_lock_verifier_init ni siquiera es llamada durante el arranque, volviéndose así irrelevante.
  • La función grub_verifiers_open. Esta función es invocada por GRUB cada vez que abre un archivo, y es responsable de comprobar si los verificadores GRUB instalados (esto incluye el verificador shim_lock descrito anteriormente) requieren una verificación de integridad para el archivo que se está cargando. La función es hookeada por el bootkit de forma que retorna inmediatamente sin proceder a ninguna comprobación de firma (tenen cuenta que esto significa que ni siquiera ejecuta la función previamente hookeada shim_lock_verifier_init).

Hook de descompresión de imágenes de kernel de Linux

Este hook es responsable de parchear la imagen descomprimida del kernel de Linux. El hook es llamado justo antes de que la imagen del kernel sea descomprimida, 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 kernel antes de proceder al parcheado del kernel.

Ahora, como el kernel está descomprimido y se encuentra en la memoria intacto (aún no se ha ejecutado), el código del gancho lo parchea en los desplazamientos codificados (sólo en memoria). Específicamente, como se muestra en la Figura 8:

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.

Parchea el puntero/dirección a la primera variable de entorno del proceso init

  • 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).

  • Hookea la función  module_sig_check.

  • Parchea el pointer/address de la primera variable de entorno del proceso init.
Figure 8. Bootkitty’s kernel-decompression hook inside vmlinuz
Figura 8. Hook de descompresión del kernel de Bootkitty dentro de vmlinuzy 

La función module_sig_check se ha parcheado para que devuelva siempre 0. Esta función es responsable de comprobar si el módulo está firmado. Al parchear la función para que devuelva 0, el kernel cargará cualquier módulo sin verificar la firma. En sistemas Linux con UEFI Secure Boot habilitado, los módulos del kernel necesitan ser firmados si están destinados a ser cargados. Este también es el caso cuando el kernel se construye con CONFIG_MODULE_SIG_FORCE habilitado o cuando module.sig_enforce=1 se pasa como un argumento de línea de comandos del kernel, como se describe en la documentación del kernel de Linux. El escenario probable es que al menos un módulo malicioso del kernel se cargue en una fase posterior, como el dropper analizado a continuación.

El primer proceso que ejecuta el kernel de Linux es desde la primera ruta codificada que funcione (empezando por /init desde initramfs), junto con argumentos de línea de comandos y variables de entorno. El código hook reemplaza la primera variable de entorno con LD_PRELOAD=/opt/injector.so /init.LD_PRELOADes una variable de entorno que se utiliza para cargar objetos compartidos ELF antes que otros y puede utilizarse 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/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 este blogpost estaba siendo finalizado para su publicación, se ha publicado un escrito que describe los componentes desaparecidos mencionados en nuestro informe. Ahora está claro que se utilizan sólo para cargar otra etapa.

Impacto y remedación

Aparte de cargar objetos compartidos ELF desconocidos, Bootkitty deja huellas en el sistema. La primera es la modificación intencionada, aunque no necesaria, de las cadenas de la versión del kernel y del banner de Linux. La primera puede verse ejecutando uname - v (figura 9) y la segunda ejecutandodmesg (figura 10).

Figure 9. BoB13 string in uname output
Figura 9. Cadena BoB13 en el output unname
Figure 10. BoB13 string in dmesg output
Figura 10. Cadena BoB13  en output in 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 fue sustituida porLD_PRELOAD=/opt/injector.so /init por el bootkit).

Figure 11. init process arguments and environment variables in dmesg output
Figura 11. Argumentos del proceso init y variables de entorno en la salida dmesg

Observe en la Figura 11 que la palabra /init en la primera línea corresponde al programa legítimo en initramfs que eventualmente pasa el control a systemd en instalaciones Ubuntu por default. La presencia de la variable de entorno LD_PRELOAD también puede verificarse inspeccionando el archivo/proc/1/environ.

Después de arrancar un sistema con Bootkitty en nuestro entorno de pruebas, nos dimos cuenta de que el kernel estaba marcado como tainted (el comando de la Figura 12 se puede utilizar para comprobar el valor tainted), que no era el caso cuando el bootkit estaba ausente. Otra forma de saber si el bootkit está presente en el sistema con UEFI Secure Boot activado es intentando cargar un módulo de kernel ficticio sin firmar durante el tiempo de ejecución. Si está presente, el módulo se cargará; si no - el kernel se niega a cargarlo.

Figure 12. Tainted state right after the system has started with Bootkitty
Figura 12. Estado tainted justo después de arrancar el sistema con Bootkitty

A simple remedy tip to get rid of the bootkit is to move the legitimate /EFI/ubuntu/grubx64-real.efi file back to its original location, which is /EFI/ubuntu/grubx64.efi. This will make shim execute the legitimate GRUB and thus the system will boot up without the bootkit (note that this covers only the scenario when the bootkit is deployed as /EFI/ubuntu/grubx64.efi).

BCDropper y BCObserver

Además del bootkit, descubrimos un módulo de kernel sin firma posiblemente relacionado que denominamos BCDropper, subido a VirusTotal más o menos 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 por ejemplo:

  • una cadena BlackCat en el output del comando modinfo, mostrada en la Figura 13.
  • otra presencia de la cadena blackcat en las rutas de depuración en el binario del módulo, mostrada en la Figura 14, y
  • que contiene una función de ocultación de archivos 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 codificado utilizado para filtrar estas entradas es injector (ten en cuenta que Bootkitty intenta precargar una biblioteca compartida desde la ruta /opt/injector.so).

Sin embargo, incluso con las pruebas presentadas, no podemos afirmar con seguridad si el módulo del kernel está relacionado o no 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 está soportada por el bootkit.

Figure 13. Dropper module information
Figura 13. Información del modulo dropper
Figure 14. Dropper debug symbols referencing blackcat
Figura 14. Símbolos referenciando a blackcat
Figure 15. List of files, in the dropper, to hide
Figura 15. Lista de archivos en el dropper, para ocultar

Como su nombre indica, el módulo del kernel suelta un archivo ELF incrustado que hemos llamado BCObserver, concretamente en /opt/observer, y lo ejecuta a través de /bin/bash (Figura 17). Además, el módulo se oculta a sí mismo eliminando su entrada de la lista de módulos. El módulo del 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.

Figure 16. Hex-Rays decompiled dropper code
Figura 16. Hex-Rays decompiled dropper code

BCObserver es una aplicación bastante simple que espera hasta que el gestor de pantalla gdm3 se está ejecutando, y luego carga un módulo del kernel desconocido desde /opt/rootkit_loader.ko a través de la llamada al sistema finit_module. Al esperar a que se inicie el gestor de pantalla, el código se asegura de que el módulo del kernel se cargue después de que el sistema haya arrancado por completo.

Figure 17. Hex-Rays decompiled observer code
Figure 17. Hex-Rays decompiled observer code

Aunque no podemos confirmar si el dropper está relacionado de alguna manera con el bootkit, y si es así, cómo se supone que se ejecuta, estamos bastante seguros de que el bootkit parchea la función module_sig_check por una razón, y cargar un módulo del kernel sin firmar (como el dropper descrito aquí) definitivamente tendría sentido

Conclusión

Sea o no una prueba de concepto, Bootkitty marca un interesante avance en el panorama de las amenazas UEFI, rompiendo la creencia de que los modernos bootkits UEFI 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, subraya 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á activado, el firmware y el sistema operativo están actualizados, y también lo está la lista de revocaciones UEFI.

Por cualquier consulta sobre nuestras investigaciones publicadas en WeLiveSecurity, por favor contactanos en threatintel@eset.com
ESET Research ofrece informes privados de inteligencia APT y fuentes de datos. Para cualquier consulta sobre este servicio, visita la página ESET Threat Intelligence.

IoCs

En nuestro repositorio de GitHub encuentras una lista completa de indicadores de compromiso (IoC) y muestras.

Files

SHA-1 Filename Detection Description
35ADF3AED60440DA7B80F3C452047079E54364C1 bootkit.efi EFI/Agent.A Bootkitty UEFI bootkit.
BDDF2A7B3152942D3A829E63C03C7427F038B86D dropper.ko Linux/Rootkit.Agent.FM BCDropper.
E8AF4ED17F293665136E17612D856FA62F96702D observer Linux/Rootkit.Agent.FM BCObserver.

MITRE ATT&CK techniques

Esta tabla se construyó utilizando la versión 16 de MITRE ATT&CK framework.

Tactic ID Name Description
Resource Development T1587.001 Develop Capabilities: Malware Bootkitty is a brand-new UEFI bootkit developed by an unknown author.
T1587.002 Develop Capabilities: Code Signing Certificates Bootkitty sample is signed with a self-signed certificate.
Execution T1106 Native API BCObserver uses the finit_module system call to load a kernel module.
T1129 Shared Modules Bootkitty uses LD_PRELOAD to preload shared modules from a hardcoded path into the init process during system start.
Persistence T1574.006 Hijack Execution Flow: Dynamic Linker Hijacking Bootkitty patches init’s environment variable with LD_PRELOAD so it loads a next stage when executed.
T1542.003 Pre-OS Boot: Bootkit Bootkitty is a UEFI bootkit meant to be deployed on the EFI System Partition.
Defense Evasion T1014 Rootkit BCDropper serves as a rootkit implemented as a loadable kernel module for Linux systems.
T1562 Impair Defenses Bootkitty disables signature verification features in the GRUB bootloader and Linux kernel.
T1564 Hide Artifacts BCDropper hides itself by removing its module’s entry from the kernel’s modules list.