El 28 de marzo de 2024 salió la nueva sobre la puerta trasera XZ Utils. Desde entonces, he estado pensando en cómo podríamos identificar estas puertas traseras antiguamente de que se lancen los paquetes o, al menos, cómo identificarlas luego de las actualizaciones. Luego de aproximadamente una semana, decidí intentar escribir un escáner esencial para al menos identificar ganchos en la memoria, lo que rápidamente se convirtió en un plan mucho más ancho de lo que esperaba. En esta publicación, analizaremos cuál fue la idea auténtico, qué era necesario construir y con qué terminamos.
La saco
Si miras mis publicaciones de blog anteriores (ELFLoader y COFFLoader), notarás que escribo muchos cargadores en memoria. Para probar mi idea, comencé con uno de mis antiguos cargadores en memoria porque pensé que podría reutilizarlo y modificarlo fácilmente para adaptarlo a mis deposición. Resultó que este no era el caso y explicaremos por qué en breve.
El objetivo completo de este plan era hacer algunas cosas. La primera parte fue identificar qué bibliotecas se necesitaban para un binario específico. Sin incautación, eso requiere analizar el binario en el disco, identificar las bibliotecas y luego identificar todas las bibliotecas que cargan esas bibliotecas. Luego tuve que relacionar esos principios entre sí y comparar lo que había en la memoria con lo que había en el disco. La longevo parte del trabajo es prácticamente el mismo que se necesitaría para construir un cargador en memoria, por lo que si desea obtener más detalles al respecto, consulte mis publicaciones de blog ELFLoader o COFFLoader. Todas las diferencias entre un cargador habitual y lo que tuve que hacer se cubrirán a continuación.
Si solo desea ver el código de un cargador en memoria ELF, le recomiendo el plan en https://github.com/infosecguerrilla/ReflectiveSOInjection ya que es suficiente simple y maneja la mayoría de las reubicaciones necesarias.
Enganche de función habitual
Lo primero que intenté hacer fue identificar un gracia de función básica. La razón principal por la que elegí hacer esto primero fue porque tengo una prueba de concepto de puerta trasera de éxito simple que ya usaba enlace. Esto me permitió realizar pruebas sin ejecutar una puerta trasera existente en mi sistema.
Analizar el ELF
Lo primero que tuve que hacer fue analizar el archivo binario existente, que es lo que haría con un cargador en memoria, pero con una ligera diferencia. En punto de simplemente analizar para cargar, tuve que identificar todos los desplazamientos de las secciones, analizar las reubicaciones/desplazamientos, encontrar una forma de obtener la memoria de los procesos remotos y luego compararlos. Para hacer la comparación, decidí rehacer las reubicaciones del binario para cada sección que quería comparar y anular esas secciones antiguamente de la comparación. Luego de algunas idas y venidas con el equipo, finalmente encontramos una posibilidad, analizamos las reubicaciones una vez, almacenamos todas las reubicaciones en una directorio vinculada y luego aplicamos las reubicaciones a esa sección solo si estaba interiormente de ese rango de memoria.
Validar todas las secciones
Una vez que se analizaron todos los detalles de todo el binario y de cada biblioteca importada, nos concentramos en compararlos. Para hacer esto, necesitábamos obtener los bytes de la sección del proceso remoto, y para esto elegí usar “ptrace” y requerir ejecutar como root. Si podemos adjuntar, extraeremos todas las secciones de cada biblioteca importada directamente en uso. Si no podemos adjuntarnos al proceso remoto, lo omitimos.
Una vez que esos bytes de sección se copian en nuestro proceso, parcheamos las reubicaciones en ese rango adjunto con la sección analizada y luego las comparamos. Donado que esto es solo una prueba de concepto, inventé un cálculo hash accidental y, si la sección difería entre procesos, tendríamos un valía diferente y simplemente podríamos imprimir las diferencias. Una vez que se identifica una diferencia, podemos comparar byte por byte y encontrar el desplazamiento en el que difiere e imprimir el resultado si queremos.
Conveniente a la forma en que estoy haciendo esta comparación, que anula el rango de bytes en la reubicación en ambas secciones y luego compara esas secciones modificadas, nos topamos con el problema donde la Tabla de Compensación Entero (GOT) mostraba que era libre. Una forma de solucionarlo era analizar cada binario en el proceso remoto y cargarlos manualmente, pero en ese momento ya tenía la capacidad de identificar enlaces de funciones en la memoria y pincharse objetos compartidos, así que decidí emplazar a ese escáner. bueno y abordarlo de otra forma como un segundo escáner.
Problemas notados
Una cosa en la que no pensé al escribir esto fueron las llamadas “dlopen” / “dlsym”. Si estas funciones se utilizan para cargar complementos, módulos o para ampliar funcionalidad adicional cuando sea necesario, terminará con bibliotecas que se cargan en el proceso remoto que no son requeridas por el proceso pero que siquiera son identificadores claros de comportamiento pillo. Incluso existe una buena posibilidad de que si ve bibliotecas específicas cargadas en un proceso y no tiene ninguna biblioteca que requiera “libdl.so”, entonces eso podría ser un identificador de comportamiento pillo. Esto cambia con GLIBC 2.34, donde “libpthread.so” y “libdl.so” se eliminan y ahora son parte de “libc.so”, por lo que ahora necesitamos identificar si alguna biblioteca resuelve “dlopen” como un identificador.
Tengo ganchos
Para los ganchos GOT, decidí acotar el escáner a solo un PID específico, descifrar y analizar ese ejecutable saco, resolver todos los símbolos que importa, aplicarle todas las reubicaciones y comparar solo el GOT. Esto introdujo muchos problemas porque ahora teníamos que resolver los símbolos en la dirección exacta que usaba el proceso remoto. En el otro, ya resolvimos la dirección saco de todas las bibliotecas, por lo que podríamos reutilizarla y luego cascar los archivos exactos. Esto resulta relativamente difícil con los sistemas más nuevos que utilizan paquetes snap y varios contenedores. Por ahora, simplemente omití el escaneo de archivos binarios con “/snap/” en la ruta. Para los procesos que se ejecutan interiormente de contenedores, hasta que pueda encontrar una forma de asignar los puntos de montaje de los procesos remotos a nuestro proceso y resolverlos a partir de esas rutas, los dejaré como falsos positivos.
La desventaja de restringir solo a ese proceso es que si el proceso usa una biblioteca que luego pasión a la función que cualquiera enganchó, se perdería. Por suerte para nosotros, la puerta trasera de XZ Utils enganchó “RSA_public_decrypt”, que se pasión directamente desde el proceso. Para enfrentarse esta demarcación, además necesitaríamos cargar y comparar recursivamente todos los GOT de cada biblioteca cargada en el proceso remoto, lo que aumentaría drásticamente el tiempo de ejecución. Decidí que esta es una compensación segura porque si se instala una puerta trasera y apunta a un proceso específico, es probable que los atacantes quieran secuestrar una función que se pasión directamente.
¿Funcionan?
Ahora la gran pregunta es, con todo este código escrito, ¿identifica algún comportamiento pillo y encuentra esas puertas traseras? Configuré un sistema de prueba Debian usando una ISO que descargué una semana luego de que se encontró la puerta trasera, pensando que sería obvio retornar a abrirla con una copia del objeto compartido. Pero no, ya tenía una puerta trasera con el XZ Utils.
Como no tengo el código fuente para esto, escribí una función genérica de instalación de gracia GOT para poder secuestrar la función de éxito para instalar mi puerta trasera de éxito de prueba de concepto para validar que funcionaron de la forma que esperaba, y hizo.
Para el escáner “validar todas las secciones”, me conecto con la misma puerta trasera de éxito y ejecuto el escáner en la memoria sobre todos los procesos. Resulta que algunos procesos de Linux tienen secciones “wtext”, que supongo significa “texto grabable”, y se modifican con cosas que parecen estar relacionadas con Nvidia y OpenGL. Por eso, estoy comprobando si hay una sección “wtext” y, si la hay, imprimiré un identificador de “WTEXT” para etiquetarlo como un posible aparente positivo.
Otro hallazgo global es cuando hay varias copias de una biblioteca instaladas y un binario usa RUNPATH/RPATH para especificar la ruta de la biblioteca de esa copia. Otro posible aparente positivo es cuando hay múltiples versiones de un símbolo definido por una biblioteca y “dlsym” termina resolviendo la lectura más nueva, pero el binario usa la lectura preparatorio. Estoy menos seguro de cómo controvertir con eso.
Para interpretarlo, debemos mirar “FileOffset” 127410 en libc.so.6 con nm -D /usr/lib/x86_64-linux-gnu/libc.so.6 | grep -I 127410 y los resultados serán el nombre de la función que se enganchó como se muestra a continuación.
Para cualquier cosa que no tenga diferencias, obtendrá un resultado como este o el resultado aparente positivo a continuación:
Los falsos positivos aquí resultan ser de una segunda lectura de libnghttp2 que “NetworkManager” se está cargando en mi sistema. Puede ayudar a determinar si se comercio de un real positivo o de un aparente positivo mirando el ‘volcado de objetos -x’ salida o ‘nm -D’ salida e identificar si es una función compensada o en los encabezados. Si el valía “FileOffset” parece estar en el encabezado, entonces es muy probable que esté analizando el archivo incorrecto y lo más probable es que sea un aparente positivo. Otro ejemplo de falsos positivos incluye lo subsiguiente, que son las modificaciones de “wtext” que mencioné anteriormente.
Para alterar el código, recepción: https://github.com/trustedsec/VerifyELF
El repositorio tendrá un tutorial completo sobre cómo construirlo, ejecutarlo y notas sobre cómo interpretar los resultados.
Trabajo futuro
Conveniente a que esto tiene beneficios principalmente para la defensa y la respuesta a incidentes, me gustaría que este tipo de subsistencia se realice de forma proactiva en los servicios que se consideran “críticos”. Otro ejemplo que me gustaría ver se utiliza para identificar indicadores de compromiso (IOC) para cosas como “ArcaneDoor” y su función de autenticación, autorización y contabilidad (AAA), las modificaciones reales y dónde están. . Aún mejor es ampliar esto e identificar las bibliotecas que usan los procesos normales, las bibliotecas que usan memoria “RWX” / “R-X” y las secciones interiormente de los procesos con esos permisos que no se sabe que los tienen.
Referencias
https://blog.talosintelligence.com/arcanedoor-new-espionage-focused-campaign-found-targeting-perimeter-network-devices/
https://www.akamai.com/blog/security-research/critical-linux-backdoor-xz-utils-discovered-what-to-know