Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
В этой статье я расскажу, как нашел бинарный баг в драйвере AdGuard. Уязвимость получила номер CVE-2022-45770. Я покажу, как изучал блокировщик рекламы и раскрутил уязвимость до локального повышения привилегий. По дороге поизучаем низкоуровневое устройство Windows.
info
За консультацию в процессе исследования спасибо @Denis_Skvortcov. В его блоге крутые статьи на тему эксплуатации уязвимостей в антивирусах для Windows. Сейчас взгляд Дениса пал на Avast.
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Я мало что понимал в виндовых драйверах до того, как прочитал книгу Павла Йосифовича Windows Kernel Programming. В книге все начинается с простого драйвера в духе Hello World и заканчивается сложным драйвером‑фильтром. Также рассказывается про отладку драйверов в виртуальной машине с WinDbg на хосте и про типичные ошибки программирования драйверов. После прочтения, конечно же, хочется применить знания на практике и разобрать какой‑нибудь драйвер. Может, нам повезет и мы найдем уязвимость?
info
Статья рассчитана на тех, кто немного разбирается в реверс‑инжиниринге сишного кода. В ней не будет подробного разбора процесса реверса. За более детальным описанием реверса обратись к моей первой статье «Разборки на куче. Эксплуатируем хип уязвимого SOAP-сервера на Linux».
AdGuard — классный блокировщик рекламы, поддерживающий шифрованный DNS (DoH, DoT, DoQ). Чтобы блокировать рекламные запросы всех приложений, а не только браузера, используется WDM-драйвер. Давай установим AdGuard на Windows 10 в виртуальной машине и начнем его изучать.
Так получилось, что я установил сборку для x86, поэтому исследовать мы будем 32-битный драйвер.
Первым делом нужно убедиться, что драйвер находится на поверхности атаки. То есть непривилегированное приложение может открыть драйвер для взаимодействия — чтения, записи и отправки IOCTL. В этом нам поможет пара строк на PowerShell с библиотекой NtObjectManager за авторством Джеймса Форшоу.
Для определения артефактов (файлов, ключей реестра) исследуемого продукта прекрасно подходит утилита от Microsoft Attack Surface Analyzer. С ее помощью нужно собрать два снапшота ОС: до установки исследуемой программы и после, а также создать дифф, который покажет установленные артефакты. Таким образом можно определить путь девайса в Object-Manager:
DeviceCtrlSM_Protected2adgnetworkwfpdrv
Ошибка при открытии девайса драйвера
Драйвер открыть не получилось. Ошибка 0xC000010 STATUS_INVALID_DEVICE_REQUEST
, и это не 0xC0000022 ACCESS_DENIED
! Значит, доступ к девайсу драйвера у нас есть, но драйверу что‑то не понравилось в нашем запросе. Такое странное поведение — отличный повод приступить к реверсу. Давай откроем драйвер в IDA и посмотрим на несколько важных мест.
Первое место — инициализирующий код драйвера в функции DriverEntry
.
Функция создания девайса драйвера
Функция IoCreateDevice()
потенциально небезопасна, так как не позволяет явно указать DACL. Таким образом, DACL берется либо из .INF-файла, либо из DACL-треда или процесса, который его создает. Также отметим, что девайс создается с неэксклюзивным доступом (EXCLUSIVE_FALSE
).
info
Рекомендуется использовать IoCreateDeviceSecure()
, куда можно явно передать DACL.
Аргумент FILE_DEVICE_SECURE_OPEN
присутствует. Если бы его не было, то было бы можно обойти строгий DACL, открыв произвольный файл на этом девайсе. Смотрим дальше.
Флаг DO_DIRECT_IO
говорит о том, что usermode-буферы для вызовов WriteFile()
и ReadFile()
будут мапиться в пространство ядра и у нас есть возможности для атаки TOCTOU в случае double fetch в коде драйвера. Если бы на месте этого флага был METHOD_NEITHER
, было бы еще интереснее.
Здесь тоже все нормально, двигаемся дальше.
Второе место — функция — обработчик открытия девайса драйвера. Найти ее просто. В коде инициализации драйвера необходимо явно назначить обработчики функций OpenFile()
, WriteFile()
и ReadFile()
.
Обработчики usermode-запросов в коде драйвера
На скриншотах IDA ты видишь названия переменных и функций, придуманных мной во время реверса. Конечно же, символа от бинаря нам никто не даст.
OSR Online IOCTL Decoder
Флаг DO_DIRECT_IO
влияет на метод передачи данных из юзермода в ядро только для FileRead()
и FileWrite()
. Для DeviceIoControl()
метод зашит в код IOCTL. Для быстрого просмотра метода можешь использовать ресурс osronline.com.
Без труда находим обработчик открытия девайса.
Обработчик IRP_MJ_CREATE
Здесь реализован кастомный эксклюзивный доступ к драйверу — PID открывшего его процесса сохраняется в глобальную переменную hasOwner
. Следующая попытка открыть драйвер возвращает ошибку STATUS_INVALID_REQUEST
.
И что это за PID? Кто открыл драйвер раньше всех? Это сервисный процесс AdguardSvc.exe
. Можем ли мы на него воздействовать? На удивление — да. Убить его через Terminate()
нам не хватит прав, но у UI-процесса AdguardUI.exe
есть кнопка «Выключить защиту».
Диалоговое окно отключения AdGuard
Когда процесс AdguardSvc.exe
закроется, снова попробуем открыть девайс драйвера.
Get-NtFile() с теми же аргументами возвращает другой результат
Получаем права на чтение, запись и отправку IOCTL от непривилегированного пользователя. Отлично! Поверхность атаки определена.
На данном этапе исследования можно отметить две ошибки.
IoCreateDevice(EXCLUSIVE_TRUE)
. Некритично.
Исследование можно было заканчивать после неудачной попытки открыть девайс драйвера, но мы внимательно отнеслись к коду ошибки и получили первую зацепку.
Кстати, проверить DACL девайса ты можешь и с помощью такой команды:
icacls.exe \.Device<name>
Либо:
accesschk.exe -l \.GLOBALROOTDevice<name>
В дизассемблерном листинге мы заметили большое количество обработчиков IOCTL. Что можно сделать вместо того, чтобы реверсить каждый?
Фаззинг драйверов несколько сложнее фаззинга юзермодных приложений, потому что работа происходит не с виртуальным пространством единственного процесса, а со всей ОС целиком. Отсюда усложнение инфраструктуры — установка агента в виртуальную машину и запуск ее в QEMU/KVM, как, например, в фаззере kAFL.
Источник: xakep.ru