Простая маскировка модуля ядра Linux с применением DKOM

  • Tutorial
Как известно, задача сокрытия модуля ядра от вездесущих «глаз» пользователя может иметь множество приложений. В данной статье рассматривается применение 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_VADDRMODULES_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 @ %pK\n", pos);
        } else {
                debug("Can't find \"modules\" head, aborting\n");
        }

        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\" unlinked\n", THIS_MODULE->name);

        } else {
                /* 1 -> 0 : show */

                list_add(&THIS_MODULE->list, p_modules);

                debug("Module \"%s\" linked again\n", THIS_MODULE->name);
        }

        mutex_unlock(&module_mutex);
}


В первом случае, для исключения их списка используется list_del. Во втором — list_add. Обе операции защищаются захватом соответствующего мьютекса.

Практическая часть



В подготовленном примере содержится код модуля, который реализует функции маскировки. Для проверки следует собрать модуль и загрузить его стандартными средствами через make и insmod

Далее, непосредственно после загрузки, модуль будет доступен через lsmod или rmmod. Далее привожу последовательность действий по проверке функций маскировки:

# insmod 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
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама

Комментарии 12

    0
    Как же страшно жить!
      +1
      Было бы интересно увидеть патчик, позволяющий запретить подобные хаки.

      Хотя, чего это я, приеду домой, вчитаюсь повнимательнее и попробую сам сделать :)

      P.S: автору спасибо, на хабре так редко мелькают статьи по разработке в Kernelspace, и им уделяется так мало внимания…
        0
        Статья просто бомба! Действительно, крайне жаль, что на хабре не так много людей, которым интересная kernelspace-разработка, да еще и в Linux-ядре.
          +1
          Я бы не сказал что слишком мало) Проблема в том что писать про ядро не так просто (не повторить статью из lwn), да и понимает о чем конкретно идёт речь небольшой процент читающих.
          И по защите — в последних андроид ядрах в ядре выключены модули по умолчанию (метод указанный ниже).
          +2
          Собирайте ядро с выключенной опцией CONFIG_MODULES и живите спокойно :)
            0
            От рута всё равно не спасёт. Он откроет /dev/mem и запишет свой код в kernel space. Просто это сложнее, чем скомпилить модуль.
              +2
              /dev/mem давно уже ограничен и нельзя просто так взять и записать в произвольный адрес памяти:

              314 /*
              315  * devmem_is_allowed() checks to see if /dev/mem access to a certain address
              316  * is valid. The argument is a physical page number.
              317  *
              318  *
              319  * On x86, access has to be given to the first megabyte of ram because that area
              320  * contains bios code and data regions used by X and dosemu and similar apps.
              321  * Access has to be given to non-kernel-ram areas as well, these contain the PCI
              322  * mmio resources as well as potential bios/acpi data regions.
              323  */
              
                0
                Любопытно, linux движется в ту же сторону, что и windows.
                К примеру, на Server 2003 локальный админ через \Device\PhysicalMemory мог что-то поделать, а также открыть процесс чужой терминальной сессии и сделать туда WriteProcessMemory. В 2008R2 ничего из этого локальный админ сделать не может.
                  0
                  Ну логичный тренд, всё-таки. Зачем админу доступ к чужим процессам?
                    +1
                    Ну так он же царьибох на компе, как жеж ;)

                    Вообще тренд стал в линуксе очень явным, когда стал маячить Secure Boot. Вот тут они хорошо подсуетились, все лазейки позакрывали, так что у рута в этом случае реально нет шансов завести неподписанный код в режиме ядра.
                      0
                      SELinux всегда ограничивал рута и нет в этом ничего плохого :) Другое дело на сколько такая защита всё-таки эффективна, ведь в конечном счёте переполнению буфера в ядре не важно под кем оно происходит…
          0
          .

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое