Mastodon

Friday, January 21, 2011

Обход детектирования модификаций кода в ядре

Репост из блога Esage Lab

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

Как несложно догадаться, детектирование перехватов, установленных как легитимным ПО так и всевозможными зловредами, на ряду с детектированием скрытого исполняемого кода является одной из возможностей, благодаря которым универсальные  антируткиты все ещё остаются актуальными в качестве детекторов аномалий.

В "джентльменский набор" антируткита, обычно, входят возможности по детектированию следующих типов перехватов:
  • Модификация кода исполняемых модулей в памяти (сплайсинг - одна из возможных вариаций).
  • Модификация таблиц импорта или экспорта.
  • Модификация таблицы системных вызовов.
  • Модификация указателей на функции, которые содержатся в каких-либо динамических структурах ядра (DRIVER_OBJECT, OBJECT_TYPE_INITIALIZER и другие).

Детектирование модификаций кода в Rootkit Unhooker

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

Идея подобной атаки - отнюдь не нова: например, Skywing упоминал подобный метод как теоретически возможное решение для обхода PatchGuard на 64-х разрядных версиях Windows. Однако, в связи с тем, что в публичных источниках какую-либо реализацию подобного нам найти не удалось - было решено написать PoC, который бы в качестве демонстрации работоспособности идеи мог скрывать от антируткитов модификации кода, характерные для стандартных руткитов режима ядра.

Для понимания принципа работы PoC-а кратко рассмотрим реализацию механизма трансляции виртуальных адресов памяти в физические, которая применяется в современных операционных системах семейства Windows NT. Раздел "Protected Mode Memory Management" тома 3A руководства разработчика для архитектуры Intel 64 и IA-32 рассказывает нам о том, что в зависимости от значений различных управляющих флагов в контрольных регистрах процессора существует несколько режимов адресации, которые определяют как размер физической страницы, так и разрядность физического адреса (максимальное количество памяти, к которой способен обращаться процессор):


По умолчанию в Windows, начиная с XP Service Pack 2, используется режим PAE (Physical Address Extension) поскольку он является необходимым условием для поддержки NX/XD-битов, на которых, в свою очередь, базируется технология DEP. Как видно из таблицы, разрядность физического адреса в данном режиме составляет 36 бит, а размер физической страницы может составлять 2 мегабайта или 4 килобайта. Windows может использовать 2MB и 4KB физические страницы одновременно, по усмотрению диспетчера памяти ядра операционной системы. Так же стоит помнить, что размер виртуальной страницы памяти при этом всегда будет составлять 4KB (1000h байт).

Получение физического адреса из виртуального заключается в "складывании" адреса физической страницы и смещения на ней. Адрес физической страницы отыскивается процессором путём просмотра таблиц PDPTE, PDE и PTE, индексами для которых являются различные группы битов виртуального (линейного) адреса. Смещение на физической странице определяется младшими 12-ю битами виртуального адреса:

Трансляция виртуального адреса в физический на системах с PAE для 4KB страниц

Физический адрес самой первой таблицы в иерархии, PDPTE, процессор считывает из регистра CR3, в который его помещает операционная система во время перехода в защищенный режим или при переключении задач.

Таким образом, для адресации 4GB виртуального адресного пространства Windows инициализирует:
  • 4 записи в таблице PDPTE
  • 4 таблицы PDE с 512-ю записями в каждой
  • 512 таблиц PTE c 512-ю записями в каждой
В обзорных статьях, описывающих управление памятью в Windows, почему-то не упоминается один важный момент: для каждого процесса ядро аллоцирует свои собственные таблицы PDPTE и PDE а так же ту часть PTE таблиц, которые описывают пространство виртуальной памяти пользовательского режима. Общие для всех процессов только PTE-таблицы, описывающие пространство виртуальной памяти режима ядра.

Как уже было упомянуто, Windows может использовать и 2MB физические страницы, в случае с которыми схема вычисления физического адреса несколько упрощается за счёт того, что в ней не задействуются таблицы PTE:


Исходя из вышеизложенных принципов можно сделать вывод, что обращения к виртуальным адресам для какого-либо конкретного процесса в Windows можно "перенаправить" на ложную физическую страницу, путём манипуляции значениями физического адреса, которые хранятся в его PDE и/или PTE таблицах. Данная идя и легла в основу реализованного PoC-а, работа которого заключается в осуществлении следующих манипуляций:
  1. Во время инициализации драйвер режима ядра PoC-а выделяет память, в которую копирует оригинальное содержимое страницы кода ядра, на который, в последствии, будет установлен перехват методом сплайсинга.
  2. Устанавливает демонстрационный перехват функции ядра IoCallDriver() а так же нотификатор на создание/завершение процессов, который используется для отслеживания запуска процессов антируткитов.
  3. По факту запуска процесса антируткита (которые, для упрощения примера, идентифицируются по списку известных имен исполняемых файлов) PoC модифицирует таблицы трансляции виртуальных адресов для этого процесса таким образом, что бы при попытке чтения модифицированной страницы кода ядра обращение происходило к "ложной" физической странице, которая содержит оригинальную копию кода.
Модификация таблиц трансляции виртуальных адресов в физические осуществляется следующим образом:
  1. Выполняется поиск PDE записи, которая соответствует нужному виртуальному адресу.
  2. Если PDE запись указывает на 2MB страницу - PoC производит логическое разбиение этой страницы на 512 страниц по 4KB, путём аллокации для них новой таблицы PTE записей.
  3. Так как PTE записи, описывающие адреса пространства ядра, общие для всех процессов - PoC производит копирование таблицы PTE в новый регион памяти, физический адрес которого записывается в ранее найденную PDE запись.
  4. И наконец - в PTE запись, соответствующую нужному виртуальному адресу, производится установка физического адреса "ложной" страницы памяти.
В графическом представлении модифицированные таблицы выглядят так:


Данная техника сокрытия имеет и некоторые недостатки:
  • Текущая демонстрационная реализация PoC-а умеет скрывать модификации кода только от тех процессов, имена исполняемых файлов которых заранее известны. Однако, этот недостаток можно устранить в "боевой" реализации путём идентификации процесса антируткита по факту попытки считывания ним модифицированной страницы памяти. Саму попытку считывания можно перехватывать используя или отладочные регистры процессора, или продемонстрированную в рутките Shadow Walker технику принудительной инвалидации страницы с перехватом исключения #PF при обращении к ней.
  • Так как в контексте процесса антируткита все обращения будут перенаправляться на "ложную" страницу с оригинальным исполняемым кодом - установленные руткитом перехваты для данного процесса срабатывать не будут. Для рассмотренной техники сокрытия данное ограничение нельзя обойти по определению, однако, для решения стоящих перед руткитом задач оно может являться не существенным.
Видео, демонстрирующее работу PoC-а:


PTBypass PoC from eSage Lab on Vimeo.

PoC работоспособен на 32-х разрядных версиях Windows XP, Vista и 7. Установленный перехват не детектируется такими антируткитами как RootkitUnhooker, GMER, Kernel Detective и CMC Anti Rootkit. Исходные тексты и исполняемые файлы PoC-а доступны в репозитории на GitHub а так же в виде архива.