Аннотация:
В данной статье автор достаточно подробно рассмотрит темы поиска и эксплуатации уязвимостей в драйверах режима ядра для Windows, а также автоматизацию их выявления. Существенная часть материала посвящена вопросам проектирования и написания свободного от уязвимостей кода: на примере исходных текстов будут показаны типичные ошибки, допускаемые разработчиками, и рассказано о том, как их нужно избегать. Кроме этого, в статье представлена методология тестирования, которая способна выявить большую часть освещенных уязвимостей.
1. Я думаю, что стоит проверять не только принадлежность указателя к диапазону адресов режима пользователя, но и то, что весь буфер находится в этом диапазоне адресов:
UCHAR Data[BUFFER_SIZE];
// проверяем размер и принадлежность указателя
// к диапазону адресов режима пользователя
if (Buff->Size <= sizeof(Data) &&
Buff->Data < (MM_HIGEST_USER_ADDRESS) - sizeof(Data))
{
2. "Такой тип уязвимостей называется double fetch ... Для предотвращения подобных ситуаций разработчику достаточно всего-навсего обращаться к полям структуры всего один раз, сохраняя их значения в локальных переменных." Думаю, что предложение делать так не решает проблемы. На мой взгляд, здесь важно обеспечить атомарность чтения всех нужных полей структуры.
С уважением,
Геннадий Майко.
Re[2]: Уязвимости в драйверах режима ядра для Windows
Правила форума нарушены.
— оверквотинг
Правила можно найти в разделе FAQ данного форума и\или ресурса.
Нарушение правил может повлечь за собой санкции, описанные там же — модератор
Существенные замечания, спасибо.
Для обеспечения атомарности, впринципе, будет достаточно задрать IRQL с последующим меппингом нужных страниц памяти через IoAllocateMdl/MmProbeAndLockPages/MmMapLockedPagesSpecifyCache.
case IOCTL_PROCESS_DATA:
{
if (Size == sizeof(REQUEST_BUFFER))
{
UCHAR Data[BUFFER_SIZE];
// проверяем размер и принадлежность указателя
// к диапазону адресов режима пользователяif (Buff->Size <= sizeof(Data) &&
Buff->Data < MM_HIGEST_USER_ADDRESS)
{
BOOLEAN bOk = FALSE;
__try
{
ProbeForRead(Buff->Data, Buff->Size, 1);
bOk = TRUE;
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
// ProbeForRead вызвала исключение
}
if (bOk)
{
// выполняем копирование данных в локальный буфер
RtlCopyMemory(Data, Buff->Data, Buff->Size);
// обработка полученных данных
// ...
}
}
}
break;
}
Как насчет того что Buff->Data будет валиден на момент исполнения ProbeForRead, но будет освобожден другим потоком приложения непосредственно перед или во время RtlCopyMemory? Вылетим в BSOD.
Как много веселых ребят, и все делают велосипед...
Re: Уязвимости в драйверах режима ядра для Windows
+ помимо этих 2х проблем в коде что я нашел (пока не прочитал дальше) похоже в статье очень нехватает проверки на валидность хэндлов ядра, которые передает приложение в драйвер, причем не валидность его как такового (ZwDuplicateHandle глупая и лишняя операция, ибо настолько же эффективные проверки валидности имеются во всех остальных функциях принимающих хэндл. Не рассказано про разделение на хэндлы ядреные и юзермодные, и что с помощью передачи хэндла в драйвер (в случае такой возможности в интерфейсе драйвера) приложение может получить доступ к чьему нить открытому ядреному хэндлу.
+ очень не хватает обзора подсистемы безопасности винды, возможность и необходимость установки Security dezscriptor'а на объект драйвера и проверки необходимых привилегии в токене caller'а при исполнении security-critical штук.
Как много веселых ребят, и все делают велосипед...
Re[3]: Уязвимости в драйверах режима ядра для Windows
_>Существенные замечания, спасибо. _>Для обеспечения атомарности, впринципе, будет достаточно задрать IRQL с последующим меппингом нужных страниц памяти через IoAllocateMdl/MmProbeAndLockPages/MmMapLockedPagesSpecifyCache.
Одного лишь задирания IRQL не достаточно для атомарности ввиду возможности наличия других процессоров. Так что надо хотябы задрать IRQL на всех остальных процессорах, исполнив на них свою DPC например на время работы своего кода.
Это не говоря о том что MmProbeAndLockPages/MmMapLockedPagesSpecifyCache не очень то поюзаешь с задранным IRQL.
Как много веселых ребят, и все делают велосипед...
Re[2]: Уязвимости в драйверах режима ядра для Windows
O>...Buff->Data будет валиден на момент исполнения ProbeForRead, но будет освобожден другим потоком приложения непосредственно перед или во время RtlCopyMemory?
Это ты как себе такое представляешь? Руки из жопы, разве что...
Re[3]: Уязвимости в драйверах режима ядра для Windows
O>>...Buff->Data будет валиден на момент исполнения ProbeForRead, но будет освобожден другим потоком приложения непосредственно перед или во время RtlCopyMemory? x64>Это ты как себе такое представляешь? Руки из жопы, разве что...
Нет. Не руки из жопы, а приложение, написанное хакером, который запустил его на сервере под ограниченными правами и цель которого — положить сервер. Кернелмодный код надо писать именно так.
А если на возможность таких хакеров положить болт — мона ваще обойтись без ProbeForRead
Как много веселых ребят, и все делают велосипед...
Re[4]: Уязвимости в драйверах режима ядра для Windows
O>Нет. Не руки из жопы, а приложение, написанное хакером...
Ну просто ты написал "приложение" вместо "злонамереный процесс", я и подумал, что ты имеешь в виду всего лишь криворукость разработчика. Аккуратнее надо с терминологией всё таки. А по поводу этой уязвимости в статье жа написано:
Такой тип уязвимостей называется double fetch, и пример показанный мной возник не на пустом месте. Такие уязвимости тяжело выявлять и ещё сложнее эксплуатировать, однако в реальных программах они встречаются. Примером этого может служить уязвимость MS08-061, которая была найдена осенью 2008 года в драйвере графической подсистемы Windows (win32k.sys). Для предотвращения подобных ситуаций разработчику достаточно всего-навсего обращаться к полям структуры всего один раз, сохраняя их значения в локальных переменных.
Техника эксплуатации double fetch-уязвимостей заключается в создании двух потоков, первый из которых будет в цикле отправлять IRP-запрос драйверу, а второй, с более высоким приоритетом, вызывать функцию Sleep, подбирая интервал задержки таким образом, чтобы исполнение первого потока прервалось в нужном месте. В процессе этих манипуляций другие потоки системы должны быть приостановлены, если такая возможность присутствует. Разумеется, никаких гарантий успешной эксплуатации нет даже близко, и в условиях, отличных от лабораторных, её вероятность будет определяться исключительно волей случая.
Re[5]: Уязвимости в драйверах режима ядра для Windows
O>>Нет. Не руки из жопы, а приложение, написанное хакером... x64>Ну просто ты написал "приложение" вместо "злонамереный процесс", я и подумал, что ты имеешь в виду всего лишь криворукость разработчика.
Для драйвера по умолчанию надо считать что приложение которое им пользуется — злонамеренное. Даже если оно хорошее (проверили сертификат исполняемого файла etc) — в него мог быть внедрен другим процессом злонамеренный код. Тем более что статья про _уязвимости_, а не просто про баги.
x64> Аккуратнее надо с терминологией всё таки. А по поводу этой уязвимости в статье жа написано:
А я еще не дочитал тогда до туда Хорошо, что написано. Плохо то что приведенный в качестве идеального кода пример имеет в себе написанную уязвимость. Еще хуже то что предлагаемые методы борьбы с этой уязвимостью очень весьма ректальны. Вообще техника должна быть такая: вначале надо просто capture'ить все буфера переданные приложением в intermediate буфер в ядре (естественно накрыв все это дело в __try..__except, затем валидировать intermediate буфер и работать из основной логики драйвера исключительно с ним.
Как много веселых ребят, и все делают велосипед...
Re[2]: Уязвимости в драйверах режима ядра для Windows
От:
Аноним
Дата:
06.09.09 06:11
Оценка:
Здравствуйте, ononim, Вы писали:
O>+ помимо этих 2х проблем в коде что я нашел (пока не прочитал дальше) похоже в статье очень нехватает проверки на валидность хэндлов ядра, которые передает приложение в драйвер, причем не валидность его как такового (ZwDuplicateHandle глупая и лишняя операция, ибо настолько же эффективные проверки валидности имеются во всех остальных функциях принимающих хэндл. Не рассказано про разделение на хэндлы ядреные и юзермодные, и что с помощью передачи хэндла в драйвер (в случае такой возможности в интерфейсе драйвера) приложение может получить доступ к чьему нить открытому ядреному хэндлу. O>+ очень не хватает обзора подсистемы безопасности винды, возможность и необходимость установки Security dezscriptor'а на объект драйвера и проверки необходимых привилегии в токене caller'а при исполнении security-critical штук.
Спасибо за ценный фидбек.
Думаю, что работа над ошибками и рассмотрение незатронутых вопросов (та же подсистемы безопасности) будет отличной темой для следующей редакции данной статьи)
Здравствуйте, Геннадий Майко, Вы писали:
ГМ>2. "Такой тип уязвимостей называется double fetch ... Для предотвращения подобных ситуаций разработчику достаточно всего-навсего обращаться к полям структуры всего один раз, сохраняя их значения в локальных переменных." Думаю, что предложение делать так не решает проблемы. На мой взгляд, здесь важно обеспечить атомарность чтения всех нужных полей структуры. Why Your User Mode Pointer Captures are Probably Broken
... << RSDN@Home 1.2.0 alpha 4 rev. 1237>>
Valery A. Boronin, RSDN Team, linkedin.com\in\boronin
R&D Mgmt & Security. AppSec & SDL. Data Protection and Systems Programming. FDE, DLP, Incident Management. Windows Filesystems and Drivers.
Re: Уязвимости в драйверах режима ядра для Windows
Подытоживая тему работы с адресами пользовательского режима в драйвере, хочу привести правильную последовательность:
1.1. Проверяем переданную длину буфера, если есть
1.2. Проверяем соответствие MM_HIGHEST_USER_ADDRESS, т.е. pBuffer < MM_HIGHEST_USER_ADDRESS && pBuffer + Size < MM_HIGHEST_USER_ADDRESS, если могут приходить только пользовательские адреса. Иначе учитываем PeviousMode.
1.3. Внутри блока __try ... __except копируем данные в локальные переменные либо в буфер в пуле. Исходные типы должны быть объявлены с ключевым словом volatile.
При этом вызов функции ProbeForRead становится ненужным, т.к. ничего полезного в дополнение к описанному она не делает.
Если размер буфера таков, что копирование отнимает много времени, можем работать прямо с юзеровским адресом при условии что:
2.1. Работа с юзеровским адресом находится внутри блока __try ... __except
2.2. Все функции, в которые передаётся буфер, работают с ним внутри блока __try ... __finally и корректно выполняют cleanup в блоке __finally.
Это геморрой, поэтому лучше один раз скопировать и работать с копией.
Если в юзеровском буфере содержатся указатели на другие юзеровские буферы, для них справедливо всё то же самое, т.е. обязательно выполнять условия 1.1.-1.3.
Ещё в статье есть небольшая неточность в фразе "Такая ситуация сложилась в первую очередь из-за того, что изначально графическая подсистема работала в режиме пользователя (по Windows NT 4.0 включительно)".
Дело в том, что в NT 4 графическая подсистема уже была в ядре. Последняя версия с графикой в пользовательском режиме — NT 3.51.
А вообще хочу сказать, что статья интересная и полезная, даже если в некоторых местах не совсем правильная. На эту тему, к сожалению, очень мало статей, а дырявых драйверов напротив — много.
Re[2]: Уязвимости в драйверах режима ядра для Windows
Здравствуйте, axxie,
A>1.2. Проверяем соответствие MM_HIGHEST_USER_ADDRESS, т.е. pBuffer < MM_HIGHEST_USER_ADDRESS && pBuffer + Size < MM_HIGHEST_USER_ADDRESS, если могут приходить только пользовательские адреса. Иначе учитываем PeviousMode.
--
Так как Size >= 0, то первое условие (pBuffer < MM_HIGHEST_USER_ADDRESS) можно опустить.
C уважением,
Геннадий Майко.
Re[3]: Уязвимости в драйверах режима ядра для Windows
Здравствуйте, Геннадий Майко, Вы писали:
A>>1.2. Проверяем соответствие MM_HIGHEST_USER_ADDRESS, т.е. pBuffer < MM_HIGHEST_USER_ADDRESS && pBuffer + Size < MM_HIGHEST_USER_ADDRESS, если могут приходить только пользовательские адреса. Иначе учитываем PeviousMode. ГМ>-- ГМ>Так как Size >= 0, то первое условие (pBuffer < MM_HIGHEST_USER_ADDRESS) можно опустить.
Не совсем. Например, если pBuffer=0xffffff00, Size=0x1000, условие pBuffer + Size < MM_HIGHEST_USER_ADDRESS будет выполняться из-за переполнения. Кстати, ещё один тип уязвимостей.
Re[2]: Уязвимости в драйверах режима ядра для Windows
Здравствуйте, axxie, Вы писали:
A>Подытоживая тему работы с адресами пользовательского режима в драйвере, хочу привести правильную последовательность:
Здесь вполне можно положиться на рекомендации MSDN в части использования пользовательских буферов, передаваемых в драйвер с помощью METHOD_NEITHER — Using Neither Buffered Nor Direct I/O.
Практический пример с комментариями можно найти в c:\WinDDK\7100.0.0\src\general\ioctl\wdm\sys\sioctl.c (см. IOCTL_SIOCTL_METHOD_NEITHER).