Как найти скрытый процесс Linux: расследование через сравнение /proc и ps

На сервере с Debian при разборе инцидента всё выглядело чисто: ps aux показывал стандартный набор — systemd, sshd, nginx, пара кронов. CPU в норме. Но сетевой мониторинг фиксировал два established-соединения на внешние адреса, а ss -tupna показывала эти соединения без имени процесса — пустые поля вместо PID. Три команды в /proc — и нашёлся PID, который ps упорно не показывал. Ещё три — и стала видна полная командная строка атакующего с путём к бинарнику, скопированному в /var/lib под случайным именем. Десять минут работы. Ниже — пошаговый разбор метода, чтобы вы могли повторить его на своём сервере.
Зачем атакующие скрывают процессы на Linux-сервере
Прежде чем разбирать технику обнаружения, разберёмся с мотивацией. Скрытый процесс — не самоцель, а инструмент. Три основных сценария:
Криптомайнинг. Майнер жрёт CPU, и если его видно в top или ps, администратор заметит аномалию за минуты. Скрытый процесс работает месяцами, генерируя доход атакующему.
C2-канал (Command & Control — канал управления заражённой машиной). Backdoor, через который атакующий возвращается на сервер. Пока процесс скрыт, доступ сохраняется.
Эксфильтрация данных. Медленная выгрузка баз, конфигов, ключей. Скрытый процесс не попадает в мониторинг и не вызывает алертов.
Во всех случаях атакующему нужно, чтобы стандартные утилиты администратора — ps, top, htop — не показывали вредоносный процесс. Для этого используются руткиты.
Почему ps показывает не всё: механика rootkit-скрытия
Руткит (rootkit) — вредоносное ПО, главная задача которого — скрыть присутствие атакующего в системе. В классификации MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1014 — её идентификаторы) это техника T1014 — Rootkit, тактика Defense Evasion (уклонение от обнаружения).
Есть два принципиально разных уровня скрытия, и понимание разницы критично для выбора метода обнаружения.
Userland-руткиты: подмена на уровне библиотек
Userland-руткит работает в пользовательском пространстве — там же, где запускаются обычные программы. Самый распространённый метод — перехват через LD_PRELOAD. Это переменная окружения Linux, которая заставляет динамический загрузчик подгружать указанную библиотеку (файл .so) раньше стандартных, включая libc. Если атакующий создаёт .so-файл, в котором переопределена функция readdir() (её используют ps, ls, top для получения списка файлов и каталогов), эти утилиты просто «не видят» нужный процесс.
Именно так работает открытый инструмент libprocesshider: он перехватывает readdir() и пропускает записи с заданным именем. По публичным данным, группировка TeamTNT использовала libprocesshider в реальных кампаниях для сокрытия криптомайнера XMRig.
Активация происходит двумя способами. Первый — через переменную окружения LD_PRELOAD=/path/to/malicious.so. Действует на один процесс, не требует root. Второй — через файл /etc/ld.so.preload. Действует на все динамически скомпонованные программы в системе, требует root.
На практике вредоносные .so-файлы часто размещаются во временных каталогах (/tmp, /dev/shm) или в скрытых подкаталогах домашних директорий, а затем перемещаются в системные пути вроде /usr/local/lib, чтобы мимикрировать под легитимные библиотеки.
Вот что здесь принципиально: userland-руткит подменяет вывод утилит, но не затрагивает саму файловую систему /proc. Прямое обращение к /proc через встроенные средства оболочки обходит перехват. На этом и строится весь метод обнаружения.
Kernel-руткиты: скрытие средствами ядра
Kernel-руткит работает внутри ядра операционной системы. Два основных механизма:
LKM-руткиты (Loadable Kernel Module — загружаемый модуль ядра) встраиваются в ядро как модуль и перехватывают системные вызовы напрямую. Они способны скрыть запись о процессе из /proc, делая его невидимым и для ps, и для прямого ls /proc. Это серьёзнее, чем userland-перехват.
eBPF-руткиты — более современный подход. eBPF (extended Berkeley Packet Filter) — механизм ядра для безопасного выполнения программ внутри ядра. Известны proof-of-concept eBPF-руткиты (ebpfkit, TripleCross), демонстрирующие сокрытие процессов и сетевых соединений средствами eBPF. Преимущество для атакующего — загрузка не зависит от конкретной версии ядра и не рискует вызвать kernel panic, в отличие от LKM.
Даже kernel-руткиты оставляют следы. Исследователи отмечают, что некоторые eBPF-руткиты скрывают каталог процесса от ls /proc, но stat /proc/PID может вернуть информацию, если PID известен. Запомните этот нюанс — он пригодится дальше.
Анализ /proc: почему этот каталог надёжнее ps
/proc — виртуальная файловая система (procfs), которую ядро Linux генерирует в оперативной памяти. Для каждого запущенного процесса ядро создаёт каталог /proc/[PID], где PID — числовой идентификатор процесса. Содержимое каталога даёт полную картину:
| Файл/каталог | Что содержит | Зачем при расследовании |
|---|---|---|
/proc/[PID]/cmdline |
Командная строка запуска (аргументы через нулевой байт) | Восстановить, как именно запущен процесс |
/proc/[PID]/exe |
Символическая ссылка на исполняемый файл | Найти и скопировать бинарник |
/proc/[PID]/maps |
Карта памяти (загруженные библиотеки) | Обнаружить подгруженные вредоносные .so |
/proc/[PID]/fd/ |
Открытые файловые дескрипторы | Увидеть сокеты, файлы, пайпы процесса |
/proc/[PID]/status |
UID владельца, потребление памяти, состояние | Определить, от чьего имени работает процесс |
/proc/[PID]/environ |
Переменные окружения процесса | Найти LD_PRELOAD и другие подозрительные переменные |
Когда вы запускаете ps aux, утилита сама обращается к /proc, читает эти файлы и форматирует вывод. Но если между ps и /proc стоит перехватчик (LD_PRELOAD-библиотека, подменяющая readdir()), ps покажет отфильтрованный список. Прямое обращение к /proc через встроенные команды оболочки использует другой путь выполнения — и если перехватчик нацелен только на конкретные утилиты, скрытый процесс станет виден.
Два источника одних данных с разной уязвимостью к подмене. Разница между ними — индикатор компрометации.
Сравнение /proc и ps: пошаговый поиск скрытых процессов на сервере
Переходим к практике. Что нужно: SSH-доступ к серверу с правами root (или sudo), стандартные утилиты ps, ls, cat.
Шаг 1. Собираем PID из /proc. Перечисляем все числовые каталоги — каждый соответствует активному процессу:
ls -d /proc/[0-9]* | sed 's|/proc/||' | sort -n > /tmp/pids_proc.txt
ls -d выводит только имена каталогов. Шаблон [0-9]* фильтрует по числовым именам. sed убирает префикс /proc/, sort -n сортирует числа по возрастанию. Результат — текстовый файл со списком PID, которые знает ядро.
Шаг 2. Собираем PID из ps. Получаем список PID, которые показывает утилита ps:
ps -eo pid --no-headers | tr -d ' ' | sort -n > /tmp/pids_ps.txt
Флаг -eo pid выводит только столбец PID для всех процессов. tr -d ' ' убирает пробелы форматирования.
Шаг 3. Сравниваем и ищем расхождения:
# PID из /proc, которых нет в ps — кандидаты на скрытые процессы
comm -23 /tmp/pids_proc.txt /tmp/pids_ps.txt
comm -23 выводит строки, которые есть только в первом файле. Если результат непустой — эти PID видит ядро, но ps их скрывает.
На чистой системе вывод будет пустым. Иногда проскочит 1-2 PID короткоживущих процессов ядра — они появляются между выполнением двух команд (гонка состояний, а не заражение). Повторите команду 2-3 раза: если один и тот же PID стабильно присутствует — это сигнал к расследованию.
При eBPF-руткитах метод с ls /proc может не сработать, потому что руткит скрывает каталог и от ls. Тогда используйте перебор с stat: пройдитесь по диапазону PID командой for pid in $(seq 1 65535); do stat /proc/$pid > /dev/null 2>&1 && echo $pid; done и сравните результат с ps. В ряде исследований показано, что stat обходит eBPF-скрытие процессов.
Восстановление командной строки процесса: что делал атакующий
Допустим, сравнение выявило скрытый PID — для примера возьмём 764. Сначала убеждаемся, что каталог существует: stat /proc/764. Если команда вернула информацию — процесс реален.
Командная строка. Файл /proc/764/cmdline содержит аргументы запуска, разделённые нулевым байтом (\0). Читаем и делаем вывод читаемым: cat /proc/764/cmdline | tr '\0' ' '. Результат может выглядеть как /var/lib/randomname --config /tmp/.X12-unix/cfg — или как имитация системного процесса: [kworker/0:3] с пробелами в конце. Атакующие часто маскируют имя процесса под системный (Masquerading, T1036 — техника применима в том числе на Linux).
Исполняемый файл. Ссылка /proc/764/exe ведёт к бинарнику: ls -la /proc/764/exe. Если файл удалён с диска, путь покажет суффикс (deleted), но бинарник всё ещё можно скопировать: cp /proc/764/exe /tmp/recovered_malware. Получите хеш для проверки: sha256sum /tmp/recovered_malware.
Открытые соединения и файлы. Каталог /proc/764/fd/ содержит символические ссылки на все ресурсы: ls -la /proc/764/fd/. Сетевые сокеты отобразятся как socket:[inode]. Дополнительно — lsof -p 764 (если lsof не скомпрометирован).
Карта памяти. Файл /proc/764/maps перечисляет загруженные библиотеки. Если в списке есть .so-файлы из /tmp, /dev/shm или скрытых каталогов (имя начинается с точки) — это может быть компонент LD_PRELOAD-руткита.
Переменные окружения. Файл /proc/764/environ (разделитель — нулевой байт): cat /proc/764/environ | tr '\0' '\n' | grep LD_PRELOAD. Если LD_PRELOAD указывает на подозрительный .so — вы нашли механизм скрытия.
Автоматизация: unhide и обнаружение rootkit Linux
Ручной метод хорош для понимания принципа и для одноразового расследования. Для регулярных проверок есть инструменты, которые делают то же самое автоматически.
unhide сравнивает /proc, системные вызовы и вывод ps. Установка на Debian/Ubuntu: sudo apt install unhide. На RHEL/CentOS: sudo dnf install unhide (может потребоваться EPEL). Три режима:
unhide proc — сравнивает /proc с ps (автоматизация нашего ручного метода). unhide sys — сравнивает ps с результатами системных вызовов ядра. unhide brute — перебирает все возможные PID и проверяет существование каждого.
Отдельная утилита unhide-tcp принудительно проверяет все TCP/UDP-порты и сравнивает с тем, что показывает ss / netstat. Полезна для обнаружения скрытых сетевых сервисов.
chkrootkit проверяет систему на известные признаки руткитов: модифицированные бинарники, подозрительные модули ядра, скрытые процессы. Установка: sudo apt install chkrootkit, запуск: sudo chkrootkit.
rkhunter — аналогичный инструмент с базой сигнатур, проверяющий целостность системных файлов: sudo rkhunter --check.
Нюанс, о котором часто забывают: если сервер скомпрометирован kernel-руткитом, инструменты, запущенные на этой же системе, тоже могут быть обмануты. Надёжнее загрузиться с внешнего носителя (Live USB) и анализировать файловую систему оттуда. Но на продакшн-сервере, который нельзя останавливать, комбинация ручной проверки /proc + unhide — разумный первый шаг.
DFIR Linux сервер: первые действия после обнаружения скрытого процесса
DFIR (Digital Forensics and Incident Response — цифровая криминалистика и реагирование на инциденты) начинается с момента обнаружения скрытого PID. Вот минимальный чеклист — порядок действий здесь принципиален:
-
Не убивайте процесс сразу. Сначала соберите артефакты: cmdline, exe, maps, fd, environ, сетевые соединения. После
killчасть данных потеряна безвозвратно —/proc/PIDисчезает вместе с процессом. Я видел, как команды в панике делалиkill -9и потом неделю пытались восстановить, что вообще произошло. -
Зафиксируйте сетевую активность:
ss -tupnaпокажет все TCP/UDP-соединения. Соединения без PID и имени процесса при активном eBPF-рутките — дополнительный индикатор компрометации. -
Сохраните бинарник:
cp /proc/PID/exe /tmp/evidence_binaryиsha256sum /tmp/evidence_binaryдля проверки на VirusTotal. -
Проверьте механизм закрепления (persistence — способ, которым малварь обеспечивает себе автозапуск после перезагрузки). Где искать: — systemd: подозрительные юниты в
/etc/systemd/system/и~/.config/systemd/user/— cron:crontab -l, содержимое/etc/cron.d/,/var/spool/cron/— LD_PRELOAD: файл/etc/ld.so.preloadсуществует и содержит путь к .so — userland-руткит (скрытие через файлы и каталоги, T1564.001) — Модули ядра:lsmodи сравнение с известной базовой линией модулей -
Снимите дамп памяти, если есть возможность. Модуль LiME (
insmod lime.ko "path=/tmp/memdump.lime format=lime") позволяет получить полный образ RAM для анализа в Volatility. -
Изолируйте сервер — только после сбора сетевых артефактов. Ротация SSH-ключей, смена паролей, ревью всех authorized_keys — обязательны.
Статические сигнатуры антивирусов ненадёжны против руткитов: даже минимальная модификация бинарника (добавление нулевого байта) может существенно снизить уровень детекции на VirusTotal у части движков. Поведенческий анализ — сверка /proc с ps, мониторинг LD_PRELOAD, проверка загруженных модулей ядра — остаётся основным методом обнаружения.
На разборах инцидентов я вижу одну и ту же ошибку: команда фиксирует подозрительную сетевую активность, запускает ps aux | grep, не находит процесс — и закрывает тикет как false positive. Сверка с /proc занимает три минуты и не требует ничего, кроме shell-доступа и прав root. Но этот шаг пропускают, потому что привыкли доверять выводу ps. Руткиты существуют именно для эксплуатации этого доверия. Если запомните одну вещь из этой статьи — пусть это будет правило: ps показывает то, что ему разрешено показать, а /proc хранит то, что знает ядро. Разница между ними — индикатор компрометации. Если хочешь не просто статьи, а пройти Linux-форензику системно — на codeby.school есть трек IB Basics, где эти навыки собраны в курс без академического тона.
Эту тему и смежные навыки разбирают на практике в курсе «Цифровая криминалистика и реагирование на инциденты (DFIR)» Codeby Academy.