Драйверы открывают доступ в ядро ОС, вот только начиная с Win7-x64 Microsoft ввела принудительную их подпись по EV-сертификатам (Extended Validation). А всё потому, что с выходом платформы KMDF (Kernel Mode Driver Frameworks) программирование драйверов стало напоминать игру «Тетрис», в результате чего огромная армия пионеров дружненько заполнила рынок кривыми дровишками. Теперь-же, Windows примет на свой борт только те драйвера, которые прошли жёсткий фейс-контроль в штабе Microsoft, и имеют удостоверяющую личность цифровую печать.
Таким образом, хоть мы и напишем драйвер для своего устройства (интерес представляют исключительно шпионы), загрузить его в систему будет проблематично, а отдавать за подпись шекели как-то не вдохновляет, хотя цены вполне приемлемые от 300 до 700 бакинских коммисаров в год. Поэтому исследователи всех мастей не пишут сейчас свои драйвера, а ищут уязвимости и дыры в уже подписанных модулях. Человеку свойственно ошибаться, а потому сколько приоткрытых люков имеется на данный момент в драйверах, остаётся только гадать.
На программном уровне, драйвер олицетворяет структура DRIVER_OBJECT, и она всегда привязывается к какому либо устройству DEVICE_OBJECT. Это устройство может быть как физическим (например порт или диск), так и логическим типа файловой системы FAT/NTFS. Один драйвер способен обслуживать запросы сразу от нескольких устройств одного типа, тогда будем иметь одну структуру DRIVER_OBJECT, и несколько связанных с ней DEVICE_OBJECT. Вот представленная отладчиком WinDbg топология драйвера шины PCI.SYS где видно, что в его команде аж 20 подчинённых устройств, и нужно сказать это не предел (у acpi.sys их 43):
Один драйвер не может решать сразу все задачи, поэтому драйвера собираются в цепочку образуя «Стек». Выполнив свою работу, драйверы в стеке передают запрос следующему и так, пока запрос не дойдёт до драйвера самого низкого уровня. Обычно на самом низком уровне находятся драйверы физ.устройств PDO (Physical Device Object), а на более высоком уровне функциональные FDO. Ответ на запрос распространяется по стеку в обратном порядке.
Вот пример стека для одного из устройств драйвера PCI.SYS.
Коммуникация двух и нескольких устройств в стеке позволяет находить комфортные условия для работы – каждый решает свою задачу, которая превращается в одну глобальную.
1. Модель драйверов фильтра
Не возможно атаковать драйвера не зная принципы их функционирования. Чтобы построить абъюзивную схему отношений, здесь нужен вектор нападения. Драйверы основных устройств (таких шины, мосты и прочее) пишутся в закрытых офисах Microsoft, и следовательно они тщательно отшлифованы – найти в них дыры практически нереально. Другое дело фильтр-драйверы, которые инкапсулируются в стек-драйверов устройства (выше или ниже основного драйвера), расширяя тем-самым его базовый функционал. На этом фоне выделяются фильтры файловой системы NTFS. Перед тем-как записать данные на диск, этот фильтр может например зашифровать поток, а при чтении обратно расшифровать. Так работает модуль «BitLocker» в Windows.
Когда мы запрашиваем доступ к файлу вызовом
Теперь, когда придёт IRP зарегистрированного фильтром типа, управление получит его обратный вызов, который может выполнить перечисленные ниже действия:
Это основы работы драйвера фильтра. Важно отметить, что пакет запроса не распространяется сам по всему стеку – цепочка драйверов должна его явно передавать друг-другу, а потому любой из драйверов может остановить продвижение пакета дальше, сразу вернув ответ диспетчеру ввода-вывода. Фильтр файловой системы обычно располагается в стеке сверху драйвера NTFS.SYS, однако это не табу и его можно вставить ниже, что позволит фильтровать не только запросы к FS, но и изменять ответы, модифицируя данные секторов диска.
2. Менеджер фильтров и мини-фильтры
Реализовать драйвер фильтра с нуля довольно сложно – нужно будет обрабатывать каждый из 28-ми типов запросов на ввод-вывод IRP_MJ_xx, даже если они нам не нужны. Другая проблема – это позиция фильтра в стеке. Легко прикрепить его первым к вершине, но попытка загнать в середину может привести к катастрофе, т.к. порядок следования фильтров может отличаться от порядка загрузки их в память. Ядро это не юзер-спейс – оно не понимает таких шуток, и сразу падает в бсод.
Чтобы упростить написание фильтров, Win поставляется с их диспетчером FLTMGR.SYS, который отвечает за обработку запросов на ввод-вывод, и передачу пакетов IRP по стеку. Так появились драйверы «Мини-фильтра», вместо уже устаревших драйверов фильтра. На следующей схеме показано, как меняется архитектура ввода-вывода, когда в штате появился новый сотрудник в лице «диспетчера фильтров» (выделен синим). Здесь видно, что мини-фильтры не добавляют в стек свои собственные объекты устройств – вместо этого они регистрируются у диспетчера/менеджера, который вызывает мини-фильтры для обработки IRP. Если запрос не поддерживается ни одним из мини-фильтров, диспетчер сам передаёт пакет IRP сл.драйверу в стеке, сводя на нет всевозможного рода ошибки. Лог отладчика подтверждает, что менеджер в стеке располагается выше драйвера NTFS.SYS:
Более того, диспетчер реализует механизм упорядочивания мини-фильтров по значению высоты «Altitudes» – чем оно выше, тем выше приоритет. Например при поступлении запроса IRP, мини-фильтр на высоте 420.000 будет вызываться перед фильтром на высоте 280.000, и т.д. Значения высоты решают ещё проблему того, что теперь порядок следования фильтров в стеке не повлияет на порядок загрузки их в память. Приоритеты раздаёт Microsoft в момент подписи драйверов – со списком зареганых на данный момент высот можно ознакомиться
3. Сбор информации о мини-фильтрах
Мини-фильтр сначала должен зарегистрировать своё присутствие функцией
На своём узле я обнаружил всего 2 мини-фильтра, и оба они хукают обращения к низлежащему драйверу NTFS. Первый LuaFV.sys это фильтр модуля UAC, который закрывает смертным юзерам доступ к системным файлам – в дословном переводе означает «Low User Account File Virtualization». Второй FileInfo.sys представляет драйвер, который отвечает за проверку файлов Win на целостность (см.утилиты sfc и dism.exe). Поскольку UAC имеет смысл только в контексте системного диска, фильтр luafv.sys контролирует обращения исключительно к разделу C:\, о чём свидетельствует число экземпляров(1). Просмотреть лист активных томов накопителя можно ключём «Volumes», и по желанию вручную прикреплять/отсоединять мини-фильтры к этим томам ключами «Attach/Detach» соответственно (см.хелп):
Узнать, к каким именно разделам диска приатачен тот или иной фильтр можно ключём «Instances» – как видим теория выше верна, и под надзором luafv.sys находится раздел(C:\) с установленной системой. Его собрат FileInfo.sys не знает точно, где могут лежать системные файлы для проверки, а потому ведёт перекрёстный огонь сразу по всем томам, в т.ч. и сетевым дискам «Mup». Итого шесть лог.разделов на моих хардах (включая неактивную флешку G:\ под ником Vol#3), которые контролируют шесть указанных на первом скрине экземпляров фильтра:
4. Природа уязвимостей в мини-фильтрах
Чтобы найти баг, злоумышленник должен послать драйверу данные, на которые дров не рассчитывал. Что делает мини-фильтры привлекательными, так это множество каналов связи с ними. Например драйверу мини-фильтра не нужно создавать подчинённый DEVICE_OBJECT для своей жизнедеятельности – этим занимается диспетчер FltMgr.sys. Однако фильтр может создать девайс для своих собственных нужд. Типичный вектор атаки – это когда мы открываем дескриптор «объекта устройства», и отправляем ему управляющие коды FSCTL для анализа уязвимого поведения. Ясно, что это не единственно возможный вариант, а потому рассмотрим поверхностно остальные.
4.1. Порты коммуникации
Один из уникальных механизмов связи диспетчера с фильтром – это порт, который мини-фильтр создаёт функцией диспетчера
После того-как порт связи с мини-фильтром создан, мы можем открыть его по имени, или вызовом из библиотеки FltLib.dll функции
Диспетчер предоставляет сл.функции из своей тушки FltMgr.sys для фильтров, чтобы они могли взаимодействовать с приложениями юзер-моды:
Перечень функций для обратной коммуникации из юзера к мини-фильтрам выглядит так (см. FltLib.dll в папке System32):
За доставку сообщений отвечает вспомогательный драйвер диспетчера FltMgrMsg.sys, который мы видим выше в окне программы «Win Object Explorer» из пакета SysinternalsSuite.
4.2. Ntfs Reparse Point, или точки повторной обработки
«Reparse Point» представляет собой тип атрибута в файловых потоках NTFS. Если открыть спеку, то он числится в метаданных под номером
На данный момент предусмотрено 2 типа точек RP – это пользовательские, и определённые в Microsoft. Последние используются диспетчером объектов для создания символических ссылок на файлы, а драйвером Mountvol.sys для создания точек монтирования томов жёсткого диска. Далее нас будут интересовать исключительно пользовательские «Third-Party Reparse Point».
Пользовательскую точку определяет структура REPARSE_GUID_DATA_BUFFER с таким прототипом, которую нужно будет аргументом передать в функцию
Интерес здесь представляет буфер с данными, которые скопируются в поток «Reparse Point» файла. В качестве данных можно использовать что-угодно (например шелл-код), лишь-бы они не превышали макс.размера в 16 КБ. В спеке на NTFS имеется такая табличка со-значением тегов, хотя для пользовательских точек старшие 2-байта можно сбросить в нуль, или выставить значение
Если мы хотим просканировать диск на наличие файлов с потоками RP, можно вызвать
Со своей стороны диспетчер фильтров тоже имеет функции для работы с точками RP, которые может вызывать драйвер мини-фильтра:
Если фильтр импортирует эти API в свою тушку, значит он реализует формат пользовательских точек RP – это может стать потенциальной уязвимостью, т.к. в буфере может притаиться хоть сам чёрт.
Для эксперимента создадим пустой файл с любым расширением (у меня TestFile.txt), и ещё один с произвольными данными, которые поместим в буфер потока RP (я записал строку HelloHackerLab). Теперь возьмём софтину «NTFS Stream Explorer», где можно быстро создать точку повторной обработки – по нашим данным она сама заполнит структуру REPARSE_GUID_DATA_BUFFER, и вызовет все нужные API. Вот скрин и галки, которые нужно в ней взвести:
Что примечательно, система не может теперь получить доступ к новоиспечённому TestFile.txt – его нельзя ни открыть, ни удалить, ни скопировать. Единственно кому доступен файл, это утилита fsutil.exe из штатной поставки Win, которой необходимо передать ключ «reparsepoint» (принимает 2 аргумента: query и delete). Как видим, она исправно распарсила файл, а чтобы обратно получить доступ к нему, нужно просто удалить точку аргументом «delete»:
Ещё одна уязвимость состоит в том, что начиная с Win10 в составе ОС появился новый управляющий код FSCTL_SET_REPARSE_POINT_EX =0x0009040C, который старые мини-фильтры не обрабатывают, ожидая только кода без суффикса(EX). В некоторых случаях это позволит приложению добавлять/удалять теги с флагом Microsoft 0x8000, что приведёт к получению файла с системной меткой, для обхода таких сторожей как WinDefender.
4.3. Уязвимость в значении высоты мини-фильтра
Это класс ошибок, которые вызваны порядком следования мини-фильтров в стеке, на основе заданных им высот «Altitudes». Например, несоответствие высоты фильтра является причиной пропуска файловых событий шпионом «Process Monitor» из пакета SysinternalsSuite. Если запустить его и следом уже знакомую нам утилиту fltmc, то можно обнаружить, что драйвер мини-фильтра шпиона называется «ProcMon24.sys», и ему назначена высота 382.500, в то время как сторож LUAFV находится на более низком уровне 135.000. Как результат, фильтр LUAFV перехватывает все обращения шпиона ProcMon.exe к системным файлам, искажая логи последнего. Баг в том, что мы можем отсоединить от тома фильтр ProcMon24 командой «Detach», и снова присоединить его «Attach» на гораздо меньшей высоте чем LUAFV, например 20.000. Так шпион обойдёт сторожа, и в его логах мы получим больше интересной информации. Для справки по ключам введите fltmc attach.
5. Эпилог
В узких кругах уязвимые драйвера называют LOL-драйверами от «Living Off The Land». Огромная их коллекция собрана здесь:
Ссылки по теме:
Руководство по поиску багов
Примеры мини-фильтров
Windows-driver-samples/filesys/miniFilter at main · microsoft/Windows-driver-samples
Разбор портов коммуникации
Справочник по кодам FSCTL
Дом.страница софта «NTFS Stream Explorer»
Таким образом, хоть мы и напишем драйвер для своего устройства (интерес представляют исключительно шпионы), загрузить его в систему будет проблематично, а отдавать за подпись шекели как-то не вдохновляет, хотя цены вполне приемлемые от 300 до 700 бакинских коммисаров в год. Поэтому исследователи всех мастей не пишут сейчас свои драйвера, а ищут уязвимости и дыры в уже подписанных модулях. Человеку свойственно ошибаться, а потому сколько приоткрытых люков имеется на данный момент в драйверах, остаётся только гадать.
На программном уровне, драйвер олицетворяет структура DRIVER_OBJECT, и она всегда привязывается к какому либо устройству DEVICE_OBJECT. Это устройство может быть как физическим (например порт или диск), так и логическим типа файловой системы FAT/NTFS. Один драйвер способен обслуживать запросы сразу от нескольких устройств одного типа, тогда будем иметь одну структуру DRIVER_OBJECT, и несколько связанных с ней DEVICE_OBJECT. Вот представленная отладчиком WinDbg топология драйвера шины PCI.SYS где видно, что в его команде аж 20 подчинённых устройств, и нужно сказать это не предел (у acpi.sys их 43):
Код:
0: kd> !drvobj pci
Driver Object (fffffa800443a980) is for: \Driver\pci
Device Object list:
fffffa80043d5060 fffffa80043cc040 fffffa80043cb660
fffffa80043c7a10 fffffa80043c7060 fffffa80043c6a10
fffffa80043c5a10 fffffa80043c5060 fffffa80043c4a10
fffffa80043c3a10 fffffa80043c3060 fffffa80043c2a10
fffffa80043c1a10 fffffa80043c1060 fffffa80043c0a10
fffffa80043cbcb0 fffffa80043c4060 fffffa800443a5d0
fffffa80043c6060 fffffa80043c2060
Один драйвер не может решать сразу все задачи, поэтому драйвера собираются в цепочку образуя «Стек». Выполнив свою работу, драйверы в стеке передают запрос следующему и так, пока запрос не дойдёт до драйвера самого низкого уровня. Обычно на самом низком уровне находятся драйверы физ.устройств PDO (Physical Device Object), а на более высоком уровне функциональные FDO. Ответ на запрос распространяется по стеку в обратном порядке.
Вот пример стека для одного из устройств драйвера PCI.SYS.
Коммуникация двух и нескольких устройств в стеке позволяет находить комфортные условия для работы – каждый решает свою задачу, которая превращается в одну глобальную.
Код:
0: kd> !devstack fffffa80043c6a10
!DevObj !DrvObj !DevExt ObjectName
---------------- ---------------- ---------------- -------------
fffffa80043e92f0 \Driver\intelide fffffa80043e9440 PciIde0
fffffa80043c37f0 \Driver\ACPI fffffa80039a82d0
> fffffa80043c6a10 \Driver\pci fffffa80043c6b60 NTPNP_PCI0012
ServiceName is "intelide"
1. Модель драйверов фильтра
Не возможно атаковать драйвера не зная принципы их функционирования. Чтобы построить абъюзивную схему отношений, здесь нужен вектор нападения. Драйверы основных устройств (таких шины, мосты и прочее) пишутся в закрытых офисах Microsoft, и следовательно они тщательно отшлифованы – найти в них дыры практически нереально. Другое дело фильтр-драйверы, которые инкапсулируются в стек-драйверов устройства (выше или ниже основного драйвера), расширяя тем-самым его базовый функционал. На этом фоне выделяются фильтры файловой системы NTFS. Перед тем-как записать данные на диск, этот фильтр может например зашифровать поток, а при чтении обратно расшифровать. Так работает модуль «BitLocker» в Windows.
Когда мы запрашиваем доступ к файлу вызовом
NtCreateFile(), системный диспетчер ввода-вывода создаёт пакет запроса IRP (Interrupt Request Packet, содержит тип операции и её аргументы). Далее IRP попадает в стек, где может располагаться драйвер фильтра. Архитектурой WDM всего поддерживается 28 типа запросов с кодами IRP_MJ_xx, а на те, которые интересуют, драйвер-фильтра вешает свои функции обратного вызова «Callback», или как их называют в доках «Dispatch Routines» (процедуры обслуживания).
Код:
Dispatch routines:
[00] IRP_MJ_CREATE
[01] IRP_MJ_CREATE_NAMED_PIPE
[02] IRP_MJ_CLOSE
[03] IRP_MJ_READ
[04] IRP_MJ_WRITE
[05] IRP_MJ_QUERY_INFORMATION
[06] IRP_MJ_SET_INFORMATION
[07] IRP_MJ_QUERY_EA
[08] IRP_MJ_SET_EA
[09] IRP_MJ_FLUSH_BUFFERS
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION
[0b] IRP_MJ_SET_VOLUME_INFORMATION
[0c] IRP_MJ_DIRECTORY_CONTROL
[0d] IRP_MJ_FILE_SYSTEM_CONTROL
[0e] IRP_MJ_DEVICE_CONTROL
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL
[10] IRP_MJ_SHUTDOWN
[11] IRP_MJ_LOCK_CONTROL
[12] IRP_MJ_CLEANUP
[13] IRP_MJ_CREATE_MAILSLOT
[14] IRP_MJ_QUERY_SECURITY
[15] IRP_MJ_SET_SECURITY
[16] IRP_MJ_POWER
[17] IRP_MJ_SYSTEM_CONTROL
[18] IRP_MJ_DEVICE_CHANGE
[19] IRP_MJ_QUERY_QUOTA
[1a] IRP_MJ_SET_QUOTA
[1b] IRP_MJ_PNP
Теперь, когда придёт IRP зарегистрированного фильтром типа, управление получит его обратный вызов, который может выполнить перечисленные ниже действия:
1. Не трогая, передать пакет следущему драйверу в стеке.
2. Изменить пакет IRP, и отдать его сл.драйверу.
3. Завершить указанную в IRP операцию с успешным результатом.
4. Завершить операцию IRP с ошибкой.
5. Передав без изменения запрос сл.драйверу, изменить ответ на обратном пути.
6. Передать пакет IRP в стек другого устройства.
Это основы работы драйвера фильтра. Важно отметить, что пакет запроса не распространяется сам по всему стеку – цепочка драйверов должна его явно передавать друг-другу, а потому любой из драйверов может остановить продвижение пакета дальше, сразу вернув ответ диспетчеру ввода-вывода. Фильтр файловой системы обычно располагается в стеке сверху драйвера NTFS.SYS, однако это не табу и его можно вставить ниже, что позволит фильтровать не только запросы к FS, но и изменять ответы, модифицируя данные секторов диска.
2. Менеджер фильтров и мини-фильтры
Реализовать драйвер фильтра с нуля довольно сложно – нужно будет обрабатывать каждый из 28-ми типов запросов на ввод-вывод IRP_MJ_xx, даже если они нам не нужны. Другая проблема – это позиция фильтра в стеке. Легко прикрепить его первым к вершине, но попытка загнать в середину может привести к катастрофе, т.к. порядок следования фильтров может отличаться от порядка загрузки их в память. Ядро это не юзер-спейс – оно не понимает таких шуток, и сразу падает в бсод.
Чтобы упростить написание фильтров, Win поставляется с их диспетчером FLTMGR.SYS, который отвечает за обработку запросов на ввод-вывод, и передачу пакетов IRP по стеку. Так появились драйверы «Мини-фильтра», вместо уже устаревших драйверов фильтра. На следующей схеме показано, как меняется архитектура ввода-вывода, когда в штате появился новый сотрудник в лице «диспетчера фильтров» (выделен синим). Здесь видно, что мини-фильтры не добавляют в стек свои собственные объекты устройств – вместо этого они регистрируются у диспетчера/менеджера, который вызывает мини-фильтры для обработки IRP. Если запрос не поддерживается ни одним из мини-фильтров, диспетчер сам передаёт пакет IRP сл.драйверу в стеке, сводя на нет всевозможного рода ошибки. Лог отладчика подтверждает, что менеджер в стеке располагается выше драйвера NTFS.SYS:
Код:
0: kd> !drvobj ntfs
Driver object (fffffa800445f9c0) is for: \FileSystem\Ntfs
Device object list:
fffffa80047e6030 fffffa8004bd8030 fffffa8004ba8030
fffffa8004b75030 fffffa80049ba030 fffffa8004bc3030
0: kd> !devstack fffffa80047e6030
!DevObj !DrvObj !DevExt
---------------- ------------------ ----------------
fffffa8003bfb060 \FileSystem\FltMgr fffffa8003bfb1b0
> fffffa80047e6030 \FileSystem\Ntfs fffffa80047e6180
Более того, диспетчер реализует механизм упорядочивания мини-фильтров по значению высоты «Altitudes» – чем оно выше, тем выше приоритет. Например при поступлении запроса IRP, мини-фильтр на высоте 420.000 будет вызываться перед фильтром на высоте 280.000, и т.д. Значения высоты решают ещё проблему того, что теперь порядок следования фильтров в стеке не повлияет на порядок загрузки их в память. Приоритеты раздаёт Microsoft в момент подписи драйверов – со списком зареганых на данный момент высот можно ознакомиться
Ссылка скрыта от гостей
.
Код:
420.000 – 430.000: Filter
400.000 – 410.000: FS_Filter Top
392.000 – 395.000: FS_Filter Security Monitor
360.000 – 390.000: FS_Filter Activity Monitor
340.000 – 350.000: FS_Filter Undelete
320.000 – 330.000: FS_Filter Anti-Virus
300.000 – 310.000: FS_Filter Replication
280.000 – 290.000: FS_Filter Backup
272.000 – 275.000: FS_Filter Security Content
260.000 – 270.000: FS_Filter Content Screener
240.000 – 250.000: FS_Filter Quota Management
220.000 – 230.000: FS_Filter System Recovery
200.000 – 210.000: FS_Filter Cluster File System
180.000 – 190.000: FS_Filter HSM
170.000 – 175.000: FS_Filter Imaging ZIP
160.000 – 170.000: FS_Filter Compression
140.000 – 150.000: FS_Filter Encryption
130.000 – 140.000: FS_Filter Virtualization
120.000 – 130.000: FS_Filter Physical Quota
100.000 – 110.000: FS_Filter Open File
80.000 - 90.000: FS_Filter Security Enhancer
60.000 - 70.000: FS_Filter Copy Protection
52.000 - 55.000: FS_Filter Security Bottom
40.000 - 50.000: FS_Filter Bottom
20.000 - 30.000: FS_Filter System
3. Сбор информации о мини-фильтрах
Мини-фильтр сначала должен зарегистрировать своё присутствие функцией
FltRegisterFilter() из драйвера FltMgr.sys. При этом в структуре «FLT_REGISTRATION» он перечисляет все колбеки IRP_MJ_xx, которые планирует перехватить. Теперь драйвер помещает свою тушку в стек мини-фильтров функцией FltStartFiltering(), а позже может отсоедениться вызовом FltUnregisterFilter(). Штатная утилита ком.строки fltmc.exe выводит паспорт всех зареганых в диспетчере фильтров – здесь видим высоту (приоритет в стеке) и кол-во его экземпляров:На своём узле я обнаружил всего 2 мини-фильтра, и оба они хукают обращения к низлежащему драйверу NTFS. Первый LuaFV.sys это фильтр модуля UAC, который закрывает смертным юзерам доступ к системным файлам – в дословном переводе означает «Low User Account File Virtualization». Второй FileInfo.sys представляет драйвер, который отвечает за проверку файлов Win на целостность (см.утилиты sfc и dism.exe). Поскольку UAC имеет смысл только в контексте системного диска, фильтр luafv.sys контролирует обращения исключительно к разделу C:\, о чём свидетельствует число экземпляров(1). Просмотреть лист активных томов накопителя можно ключём «Volumes», и по желанию вручную прикреплять/отсоединять мини-фильтры к этим томам ключами «Attach/Detach» соответственно (см.хелп):
Узнать, к каким именно разделам диска приатачен тот или иной фильтр можно ключём «Instances» – как видим теория выше верна, и под надзором luafv.sys находится раздел(C:\) с установленной системой. Его собрат FileInfo.sys не знает точно, где могут лежать системные файлы для проверки, а потому ведёт перекрёстный огонь сразу по всем томам, в т.ч. и сетевым дискам «Mup». Итого шесть лог.разделов на моих хардах (включая неактивную флешку G:\ под ником Vol#3), которые контролируют шесть указанных на первом скрине экземпляров фильтра:
4. Природа уязвимостей в мини-фильтрах
Чтобы найти баг, злоумышленник должен послать драйверу данные, на которые дров не рассчитывал. Что делает мини-фильтры привлекательными, так это множество каналов связи с ними. Например драйверу мини-фильтра не нужно создавать подчинённый DEVICE_OBJECT для своей жизнедеятельности – этим занимается диспетчер FltMgr.sys. Однако фильтр может создать девайс для своих собственных нужд. Типичный вектор атаки – это когда мы открываем дескриптор «объекта устройства», и отправляем ему управляющие коды FSCTL для анализа уязвимого поведения. Ясно, что это не единственно возможный вариант, а потому рассмотрим поверхностно остальные.
4.1. Порты коммуникации
Один из уникальных механизмов связи диспетчера с фильтром – это порт, который мини-фильтр создаёт функцией диспетчера
FltCreateCommunicationPort(). Имя порта указывается в структуры OBJECT_ATTRIBUTES, и далее сохраняется в системном пространстве имён OMNS (Object Manager Name Space) или в корне каталога, или по пути \\FileSystem\Filters. При этом в той-же структуре фильтр должен указать адрес своей функи обратного вызова для обслуживания запросов, плюс дескриптор безопасности SD, чтобы доступ к порту имел только админ, хотя можно расшарить порт и для юзера.После того-как порт связи с мини-фильтром создан, мы можем открыть его по имени, или вызовом из библиотеки FltLib.dll функции
FilterConnectCommunicationPort() прямо из пользовательского режима. Если диспетчер даст нам добро на доступ к порту, то при подключении сработает обратный вызов «ConnectNotifyCallback» с уведомлением, что всё гуд. Основная проблема здесь в том, как программно найти имя порта, а потому придётся использовать набор сторонних инструментов, например расширение !fltkd.help отладчика WinDbg, или софт «SystemInformer» (в младенчестве ProcessHacker).Диспетчер предоставляет сл.функции из своей тушки FltMgr.sys для фильтров, чтобы они могли взаимодействовать с приложениями юзер-моды:
Код:
FltCreateCommunicationPort()
FltCloseCommunicationPort()
FltSendMessage()
FltReceiveMessage()
Перечень функций для обратной коммуникации из юзера к мини-фильтрам выглядит так (см. FltLib.dll в папке System32):
Код:
FilterConnectCommunicationPort()
FilterGetMessage()
FilterSendMessage()
FilterReplyMessage()
За доставку сообщений отвечает вспомогательный драйвер диспетчера FltMgrMsg.sys, который мы видим выше в окне программы «Win Object Explorer» из пакета SysinternalsSuite.
4.2. Ntfs Reparse Point, или точки повторной обработки
«Reparse Point» представляет собой тип атрибута в файловых потоках NTFS. Если открыть спеку, то он числится в метаданных под номером
0xC0, и позволяет нашему приложению связать с произвольным файлом блок дополнительных своих данных. Когда диспетчер «Object Manager» обнаруживает данный атрибут в файле, он выполяет повторный поиск имени, чем собственно и обусловлено название «Reparse Point». По сути фишка бесполезна, но благодаря ей прогеры могут расширять функционал NTFS. На данный момент предусмотрено 2 типа точек RP – это пользовательские, и определённые в Microsoft. Последние используются диспетчером объектов для создания символических ссылок на файлы, а драйвером Mountvol.sys для создания точек монтирования томов жёсткого диска. Далее нас будут интересовать исключительно пользовательские «Third-Party Reparse Point».
Пользовательскую точку определяет структура REPARSE_GUID_DATA_BUFFER с таким прототипом, которую нужно будет аргументом передать в функцию
DeviceIoControl():
Код:
struct REPARSE_GUID_DATA_BUFFER
ReparseTag dd 0
ReparseDataLength dw 0
Reserved dw 0
ReparseGuid rb 16
DataBuffer db ?
ends
----------------------------------
1. Обязательный тег (см.табл.ниже)
2. Размер данных в буфере
3. Резерв (выравнивание)
4. Уникальный GUID точки RP
5. Буфер с данными (макс. 16 КБ)
Интерес здесь представляет буфер с данными, которые скопируются в поток «Reparse Point» файла. В качестве данных можно использовать что-угодно (например шелл-код), лишь-бы они не превышали макс.размера в 16 КБ. В спеке на NTFS имеется такая табличка со-значением тегов, хотя для пользовательских точек старшие 2-байта можно сбросить в нуль, или выставить значение
0x4000 «Высокая задержка», что означает низкую скорость отклика. Для точек мелкомягких обязательным является значение 0х8000 (взведён ст.бит), и нужно передавать структуру REPARSE_DATA_BUFFER с немного иным содержимым, и без 16-байтного GUID.Если мы хотим просканировать диск на наличие файлов с потоками RP, можно вызвать
FindNextFile() и проверить в выхлопе флаг FILE_ATTRIBUTE_REPARSE_POINT. Юзер-приложение может устанавливать, запрашивать и удалять точки повторной обратотки в файлах функцией DeviceIoControl() со-следующими кодами файловой системы.
Код:
FSCTL_SET_REPARSE_POINT = 0x000900a4
FSCTL_GET_REPARSE_POINT = 0x000900a8
FSCTL_DELETE_REPARSE_POINT = 0x000900ac
Со своей стороны диспетчер фильтров тоже имеет функции для работы с точками RP, которые может вызывать драйвер мини-фильтра:
Код:
FltAddOpenReparseEntry()
FltRemoveOpenReparseEntry()
FltTagFileEx()
FltUntagFile()
Если фильтр импортирует эти API в свою тушку, значит он реализует формат пользовательских точек RP – это может стать потенциальной уязвимостью, т.к. в буфере может притаиться хоть сам чёрт.
Для эксперимента создадим пустой файл с любым расширением (у меня TestFile.txt), и ещё один с произвольными данными, которые поместим в буфер потока RP (я записал строку HelloHackerLab). Теперь возьмём софтину «NTFS Stream Explorer», где можно быстро создать точку повторной обработки – по нашим данным она сама заполнит структуру REPARSE_GUID_DATA_BUFFER, и вызовет все нужные API. Вот скрин и галки, которые нужно в ней взвести:
Что примечательно, система не может теперь получить доступ к новоиспечённому TestFile.txt – его нельзя ни открыть, ни удалить, ни скопировать. Единственно кому доступен файл, это утилита fsutil.exe из штатной поставки Win, которой необходимо передать ключ «reparsepoint» (принимает 2 аргумента: query и delete). Как видим, она исправно распарсила файл, а чтобы обратно получить доступ к нему, нужно просто удалить точку аргументом «delete»:
Ещё одна уязвимость состоит в том, что начиная с Win10 в составе ОС появился новый управляющий код FSCTL_SET_REPARSE_POINT_EX =0x0009040C, который старые мини-фильтры не обрабатывают, ожидая только кода без суффикса(EX). В некоторых случаях это позволит приложению добавлять/удалять теги с флагом Microsoft 0x8000, что приведёт к получению файла с системной меткой, для обхода таких сторожей как WinDefender.
4.3. Уязвимость в значении высоты мини-фильтра
Это класс ошибок, которые вызваны порядком следования мини-фильтров в стеке, на основе заданных им высот «Altitudes». Например, несоответствие высоты фильтра является причиной пропуска файловых событий шпионом «Process Monitor» из пакета SysinternalsSuite. Если запустить его и следом уже знакомую нам утилиту fltmc, то можно обнаружить, что драйвер мини-фильтра шпиона называется «ProcMon24.sys», и ему назначена высота 382.500, в то время как сторож LUAFV находится на более низком уровне 135.000. Как результат, фильтр LUAFV перехватывает все обращения шпиона ProcMon.exe к системным файлам, искажая логи последнего. Баг в том, что мы можем отсоединить от тома фильтр ProcMon24 командой «Detach», и снова присоединить его «Attach» на гораздо меньшей высоте чем LUAFV, например 20.000. Так шпион обойдёт сторожа, и в его логах мы получим больше интересной информации. Для справки по ключам введите fltmc attach.
5. Эпилог
В узких кругах уязвимые драйвера называют LOL-драйверами от «Living Off The Land». Огромная их коллекция собрана здесь:
Ссылка скрыта от гостей
. Поиск багов в них это целая наука, на постижение которой можно потратить всю оставшуюся жизнь. Нужно знать не только общую архитектуру драйверов, но и пятой точкой уметь предсказывать все последующие шаги драйвера, после того-как пошлём ему пакет IRP для обработки. Отличным полигоном на начальном этапе может послужить 32-битная WinXP, которая не требует подписи драйверов, а потому любезно принимает на свой борт даже заведомо кривые субстанции для практической реализации атак. Здесь главное руку набить, после чего можно переселиться и на драйвера более актуальных систем.Ссылки по теме:
Руководство по поиску багов
Ссылка скрыта от гостей
Примеры мини-фильтров
Windows-driver-samples/filesys/miniFilter at main · microsoft/Windows-driver-samples
Разбор портов коммуникации
Ссылка скрыта от гостей
Справочник по кодам FSCTL
Ссылка скрыта от гостей
Дом.страница софта «NTFS Stream Explorer»
Ссылка скрыта от гостей