Содержание статьи
Хочешь стать уверенным пользователем своего компьютера? Легко находить кнопку «Пуск» и запускать игру «Сапер»? Хочешь, чтобы твои друзья завистливо смотрели на тебя, потому что ты начал пользоваться заветными «горячими клавишами» Alt+TAB? Если да, то скажи мне, зачем ты взял журнал старшего брата? Разве он для тебя его покупал? Отдай журнал немедленно!
Отдал? Хорошо. В этой статье мы будем распиливать циркулярной пилой такие вещи, как Driver Signature Enforcement, немало озаботившую в свое время драйверописателей, а также TLS – Thread Local Storage, этакого неотъемлемого и скрытого соседа любого PE-файла.
Кое-что о Driver Signature Enforcement
Как известно, начиная с Windows Vista, парни из Microsoft серьезно озаботились загрузкой неподписанных (читай – чужих) драйверов в систему (для Vista речь идет о 64-битной версии). В первую очередь это было сделано, наверное, для защиты системы от руткитов, основная боевая часть которых зависит от драйвера ядра. В результате это привело к всевозможным проблемам, доставшимся системным разработчикам, занятым в разработке драйверов. Теперь для успешной загрузки драйвера в Windows Vista/7 нужна была цифровая подпись, а ее нужно было покупать за немаленькие деньги у таких компаний, как Verisign или Thawte.
Разработчики руткитов из-за этих новостей несильно огорчились. Вместо того, чтобы огорчаться, они начали искать «левые» пути и лазейки, позволяющие грузить неподписанные драйвера в систему. У них это, традиционно, получилось, и первой среди них была наша любимая красавица – Рутковска.
Я не буду рассказывать тебе об этих способах, о них ты вполне сможешь прочесть в интернете. Вместо того, чтобы есть чужую рыбу, мы лучше возьмем свою удочку и посмотрим, что можно выудить из этого забавного механизма – Driver Signature Enforcement.
Его «сердцем» является библиотека сi.dll, которая, как всегда, располагается в папке /%systemroot%/system32. Экспортирует она следующие функции:
- CiCheckSignedFile
- CiFindPageHashesInCatalog
- CiFindPageHashesInSignedFile
- CiFreePolicyInfo
- CiGetPEInformation
- CiInitialize
- CiVerifyHashInCatalog
Самая интересная здесь функция – CiInitialize, она импортируется ядром во время процесса инициализации системы, ее псевдокод выглядит примерно таким образом:
VOID SepInitializeCodeIntegrity()
{
ULONG CiOptions;
{spipped...}
memset( g_CiCallbacks, 0, 3*sizeof ( SIZE_T ));
CiOptions = 4|2;
if(KeLoaderBlock)
{
if(*(ULONG*)(KeLoaderBlock+84))
{
if(SepIsOptionPresent((KeLoaderBlock+84),
L"DISABLE_INTEGRITY_CHECKS"))
CiOptions = 0;
if(SepIsOptionPresent((KeLoaderBlock+84),
L"TESTSIGNING"))
CiOptions |= 8;
}
CiInitialize(CiOptions,(KeLoaderBlock+32), &g_CiCallbacks);
}
}
Самое интересное здесь то, что CiInitialize возвращает обратно в ядро три указателя на функции:
g_CiCallbacks[0] = CI!CiValidateImageHeader,
g_CiCallbacks[1] = CI!CiValidateImageData,
g_CiCallbacks[2] = CI!CiQueryInformation.
Запомним это и посмотрим на законченный стек вызовов функций на самом раннем этапе инициализации системы:
nt!SepInitializeCodeIntegrity
nt!SepInitializationPhase1 + 0x1a1
nt!SeInitSystem + 0x29
nt!Phase1InitializationDiscard + 0x7ce
nt!Phase1Initialization + 0xd
nt!PspSystemThreadStartup + 0x9e
nt!KiThreadStartup + 0x19
Как здесь можно увидеть, SepInitializeCodeIntegrity (а вернее, CiInitialize) создает некие необходимые в дальнейшем условия для успешной загрузки системы. Если мы полезем вглубь CiInitialize, то увидим, что эта функция проверяет валидность драйверов, находящихся в Boot Driver List (то есть, грузящихся при старте). Если во время этого процесса будут обнаружены ошибки, то процесс загрузки будет остановлен.
Продолжаем рассматривать процесс загрузки драйверов в систему. Стек вызовов системных функций в этом случае в Vista/7 будет выглядеть следующим образом:
nt!MmLoadSystemImage
nt!MiObtainSectionForDriver
nt!MiCreateSectionForDriver
nt!MmCheckSystemImage
nt!NtCreateSection
nt!MmCreateSection
nt!MiValidateImageHeader
nt!SeValidateImageHeader
nt!_g_CiCallbacks[0] т.е. CI!CiValidateImageData
Интерес для нас представляет SeValidateImageHeader – она проверяет, есть ли цифровая подпись у драйвера.
Делает она это следующим образом. Сначала идет проверка переменной nt!g_CiEnabled (ее смысл, думаю, расшифровывать нет необходимости :)) и, если она установлена в TRUE, проверит значение указателя nt!g_CiCallbacks[0]. Если последний не равен NULL, то вызовет эту функцию и вернет управление. Если же nt!g_CiCallbacks[0] будет пустым, то она вернет статус 0xc0000428, что в переводе на общечеловеческий язык соответствует «Windows cannot verify the digital signature of this file».
Если же переменная nt!g_CiEnabled равна FALSE, то функция выделит в памяти один байт, скопирует туда указатель на свой первый аргумент, после чего с чистой совестью вернет STATUS_SUCCESS. Все! Вот таким вот нехитрым способом WIndows Vista / 7 проверяет наличие и валидность цифровой подписи, чтобы загрузить драйвер.
Вывод: проверка того, будет ли загружен драйвер в систему, зависит всего лишь от одной переменной. И если кто-либо захочет выключить эту проверку – все, что нужно будет сделать – это переписать в памяти один байт. Правда, сделать это будет довольно сложно, потому что ни nt!g_CiEnabled, ни nt!g_CiCallbacks не экспортируются ядром и найти их будет проблематично.
RTFM
Что ты знаешь о TLS? Thread Local Storage, или локальная память потока – наверное, самая документированная из самых недокуметированных возможностей Windows.
Что мы знаем о TLS? Оно обычно используется программистами в многопоточных приложениях. Рихтер в своей библии системного программирования приводит такой пример – с каждым потоком в TLS связывается дата и время, когда он был создан. В момент уничтожения потока можно посчитать время, в течение которого поток существовал. Сценарии, где есть данные, которые связаны одновременно и с программой в целом, и с отдельным потоком в частности, вынуждают использовать TLS. Например, пусть процесс владеет некоторым массивом. Каждый элемент массива вместе с его содержимым соответствует отдельному потоку. Откуда поток узнает, какой индекс в глобальном массиве – его? Да, можно передать функции потока ThreadProc параметр в виде индекса… но это все известные стороны медали. Что не лежит на поверхности TLS? Что мы о нем не знаем?
Реализация механизма TLS в Windows предусматривает два варианта – явный, который можно задействовать набором функций, импортируемых kernel32 (TlsGetValue, TlsSetValue, TlsAlloc и TlsFree), и второй, неявный, который позволяет создавать и использовать так называемые локальные переменные потока, для чего при их объявлении используется __declspec(thread) . Ты можешь спросить: «В чем же разница между ними?». Отвечу: при использовании TLS в своих грязных целях следует помнить об одной неочевидной вещи – в операционках до Windows Vista использование переменных, объявленных как __declspec(thread) в библиотеке, которая грузится явно через вызов LoadLibrary(Ex), закончится ошибкой Access Violation. Почему? Все дело в том, что ключевое слово __declspec(thread) имеет некоторые ограничения. Если библиотека DLL объявляет любые нелокальные данные или объекты как __declspec( thread ), то это может привести к сбою защиты при динамической загрузке. После загрузки библиотеки DLL с помощью функции LoadLibrary возможен сбой в системе всякий раз, когда код ссылается на нелокальные данные __declspec( thread ). Пространство глобальных переменных для потока выделяется во время выполнения, поэтому размер данного пространства определяется, исходя из расчетов требований приложения, а также требований всех статически компонуемых библиотек DLL. При использовании функции LoadLibrary не существует способа расширения этого пространства, чтобы разрешить объявление локальных переменных потока с ключевым словом __declspec (thread). Поэтому в таких случаях в библиотеках DLL следует использовать API-функции TLS, такие как TlsAlloc, чтобы разместить TLS, если библиотека DLL загружается с помощью функции LoadLibrary.
Уф, надеюсь, доступно разъяснил. Но что с того? Как TLS можно использовать в контексте компьютерной безопасности? А вот так!
PE-формат поддерживает функции обратного вызова (TLS-callback), автоматически вызываемые системой до передачи управления на точку входа. В частности, это позволяет определить наличие отладчика или скрытно выполнить некоторые действия. Если PE-файл имеет свои калбэки, они могут изменять таблицу TLS во время исполнения. Это значит, что, если у тебя установлен один калбэк, он легко сможет добавить другие калбэки во время исполнения.
TLS используется в большом количестве протекторов, защит, вирусов, crackme и прочих программ, находящихся в сфере наших с тобой общих интересов.
Blacklight используют некоторые антиотладочные приемы, начинающиеся с создания callback таблицы TLS (Thread Local Storage). Blacklight's TLS callback пытается обмануть отладчик, создавая копию главного процесса (fork) до того, как объект процесса полностью создан.
Некоторые вирусы внедряются исключительно путем модифицирования всего четырех байт – указателя на TLS-таблицу, расположенную в памяти (в одной из системных DLL), где находится указатель на команду передачи управления на shell-код.
Конечно, подобная техника внедрения работает только на той версии операционной системы, под которую она заточена, но антивирусы таких вирусов не обнаруживают. Или вообще не обращают внимания на изменение directory table.
Кстати, если тебя интересует использование TLS в качестве антиотладочного приема – подробности читай в отличной статье К. Касперски «Исчерпывающее руководство по приготовлению и взлому TLS» (http://www.xakep.ru/magazine/xa/118/080/1.asp).
Заключение
Будем надеяться, что прочтение этой статьи смогло сделать тебя продвинутым пользователем компьютера. А что делает продвинутый пользователь? Берет отладчик и ковыряет все подряд. От этого он становится еще более продвинутым пользователем :). В общем, двигайся вперед, читай ][, и да пребудет с тобой Сила!
CD
На диске ты сможешь найти фундаментальный, аж на целых 186 Кб, труд Matthew Conover'a из Symantec, посвященный изучению механизма безопасности в ядре Vista – «Windows Vista Kernel Mode Security», а также несколько сорцов, посвященных теме статьи.
WWW
Если тебя заинтересовал вопрос безопасности Windows Vista и выше – милости просим сюда: http://www.codeproject.com/KB/vista-security.