И последним этапом в цепочке проверки целостности кода стадии загрузки 32-битной операционной системы Windows 7 является библиотека ci.dll (именуемая разработчиками Модулем Целостности Кода, Code Integrity Module). Данный компонент впервые появился в операционной системе Windows 7 и в своем составе имеет функции, обеспечивающие проверку целостности бинарных образов на этапе отображения/загрузки в адресное пространство ядра. Таким образом, можно сделать вывод, что фактически проверке по задумке разработчиков, подлежат только ключевые образы, являющиеся для системы критичными в плане безопасности. Область действия функционала ci.dll ограничивается только локальной системой, то есть той, на которой непосредственно происходит выполнение кода функций, иначе говоря библиотека является ни чем иным как изолированным идентификатором безопасности локальной системы.
Теперь, собственно, сделаем небольшое отступление для того, что бы дать краткое описание необходимых для понимания происходящего механизмов. С определенного времени в Windows появились так называемые сертификаты подписи кода.
Получается, что введение подобного типа сертификатов призвано обеспечить:
- проверка подлинности кода;
- проверка целостности кода;
Существует несколько видов сертификатов подписи кода, но в контексте данного материала нас интересуют лишь следующие:
- Цифровая подпись Authenticode (Authenticode Signing) - сертификат, предназначающийся для удостоверения кода (программ/скриптов) пользовательского режима;
- Цифровая подпись модулей режима ядра (Kernel Mode Signing) - сертификат, позволяющий удостоверять модули, предназначенные для работы в режиме ядра (нативные библиотеки/драйвера);
Цифровая подпись Authenticode используется для подписи 32- и 64-битных исполняемых файлов (.exe, .dll, .cab .msi, и некоторые другие). Помимо этого, позволяет подписывать код для Microsoft Office, Microsoft VBA, Silverlight 4. Цифровая подпись (сертификат разработчика) модулей режима ядра (Kernel Mode) позволяет подписывать приложения, предназначенные для работы в режиме ядра. Для 64-битных версий операционных это имеет особое значение, поскольку для систем данной разрядности (начиная с Windows Vista) необходимо, чтобы все kernel-mode-приложения были подписаны сертификатом, выпущенным доверенным центром сертификации. Как вы уже поняли, перечисленный перечень удостоверяющих процедур призваны осуществить функции, предоставляемые (экспортируемые) библиотекой ci.dll.
Справедливы следующие утверждения:
- Библиотека ci.dll присутствует в операционных системах Windows 7 и более поздних.
- Библиотека ci.dll функционирует в только в ситуации, когда Windows загружается в штатном (нормальном) режиме загрузки, подразумевающем отключенный режим отладки (Debug mode) и включенную проверку целостности драйверов (ENABLE_DRIVER_INTEGRITY).
- основное назначение библиотеки ci.dll - проверка целостности драйверов и компонентов (подписанных цифровой подписью). Поэтому, логично что функции библиотеки ci.dll могут возвращать запрашивающему коду некий статус, сигнализирующий об успешном/неудачном прохождении вышеуказанных проверок целостности.
- Библиотека ci.dll обеспечивает поддержку следующих алгоритмов: проверка цифровой подписи RSA PKCS#1 (v1.5), хэш SHA-1, хэш SHA-256, хэш SHA-384, хэш SHA-512.
- Библиотека ci.dll обеспечивает на этапе инициализации тесты собственной целостности с использованием следующих алгоритмов: SHS (SHA-1) проверка известного ответа, SHS (SHA-256) проверка известного ответа, SHS (SHA-512) проверка известного ответа, RSA проверка на основе теста с использованием известной подписи формата PKCS#1 v1.5.
Теперь для лучшего понимания следующего далее материла дадим пару определений.
Экспортируемые и внутренние функции библиотеки:
Функция | Описание |
---|---|
CiCheckSignedFile | Для выборки и подписи Authenticode, проверяет можно ли получить выборку из подписи и что сама подпись является подлинной. (Опционально) может возвращать информацию о подписи. |
CiFindPageHashesInCatalog | Для выборки и подписи Authenticode первой страницы PE-образа, подтверждает что выборку можно получить из .cat-файла. (Опционально) может возвращать информацию по каталогу. |
CiFindPageHashesInSignedFile | Для выборки и подписи Authenticode первой страницы PE-образа, проверяет можно ли получить выборку из подписи и что сама подпись является подлинной. (Опционально) может возвращать информацию о подписи. |
CiFreePolicyInfo | Освобождает память, выделенную ранее функциями CiVerifyHashInCatalog, CiCheckSignedFile, CiFindPageHashesInCatalog и CiFindPageHashesInSignedFile. Исключительно внутренняя сервисная функция. |
CiGetPEInformation | Функция возвращает системные конфигурационные данные, связанные с подписанным (защищенным) образом. |
CiInitialize | Функция проверки кода ядра и начальной (инициализации) настройки адресов функций обратного вызова для последующего обращения. |
CiVerifyHashInCatalog | Для Authenticode-выборки файла, проверяет что выборку можно получить из .cat-файла. (Опционально) может возвращать информацию по каталогу. |
Функция | Описание |
CiValidateImageHeader | Проверяет заголовки исполняемого образа. |
CiValidateImageData | Проверяет данные секций исполняемого образа. |
Из ядра Windows 7, например, на этапе начальной загрузки, вызываются функции CiInitialize, CiValidateImageHeader, CiValidateImageData. Если проверка собственной целостности не проходит, то функция CiInitialize возвращает статус STATUS_UNSUCCESSFUL, в этом случае загрузка операционной системы прекращается с ошибкой. В случае успешного прохождения тестов, функция CiInitialize возвращает (инициализированную) структуру с именем _g_CiCallbacks, через которую впоследствии будет осуществляться обратный вызов экспортируемых и некоторых внутренних функций (были описаны выше). Вызывающий код в дальнейшем может использовать эти функции для проведения проверки любого исполняемого образа на целостность.
Отключение проверки образов
Отдельным довеском публикую алгоритмы изменения исходного кода библиотеки. В одном из экспериментов с 32-битной реализацией Windows 7 потребовалось отключение проверки целостности модулей начальной загрузки. Конечно же, подобная возможность официально имеется для 32-битных операционных систем Windows, тем не менее были выявлен ряд причин по которым требовалось ручное, принудительное изменение кода. Интерес для нас представляет код функции CiGetPEInformation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
PAGE:17896430 ; =============== S U B R O U T I N E ======================================= PAGE:17896430 PAGE:17896430 ; Attributes: bp-based frame PAGE:17896430 PAGE:17896430 ; __stdcall CiGetPEInformation(x, x, x) PAGE:17896430 public _CiGetPEInformation@12 PAGE:17896430 _CiGetPEInformation@12 proc near PAGE:17896430 8B FF mov edi, edi PAGE:17896432 55 push ebp PAGE:17896433 8B EC mov ebp, esp PAGE:17896435 5D pop ebp PAGE:17896436 E9 1B 98 00 00 jmp _PEProcessDispatch@12 PAGE:17896436 _CiGetPEInformation@12 endp PAGE:17896436 PAGE:17896436 ; --------------------------------------------------------------------------- |
Можно было непосредственно изменить его, тем не менее, более корректным вариантом будет изменить код вызываемой "вложенной" функции PEProcessDispatch:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
PAGE:1789FC56 ; =============== S U B R O U T I N E ======================================= PAGE:1789FC56 PAGE:1789FC56 ; Attributes: bp-based frame PAGE:1789FC56 PAGE:1789FC56 ; __stdcall PEProcessDispatch(x, x, x) PAGE:1789FC56 _PEProcessDispatch@12 proc near PAGE:1789FC56 PAGE:1789FC56 var_14 = dword ptr -14h PAGE:1789FC56 var_10 = dword ptr -10h PAGE:1789FC56 var_C = dword ptr -0Ch PAGE:1789FC56 var_8 = dword ptr -8 PAGE:1789FC56 var_1 = byte ptr -1 PAGE:1789FC56 arg_0 = dword ptr 8 PAGE:1789FC56 arg_4 = dword ptr 0Ch PAGE:1789FC56 arg_8 = dword ptr 10h PAGE:1789FC56 PAGE:1789FC56 8B FF mov edi, edi PAGE:1789FC58 55 push ebp PAGE:1789FC59 8B EC mov ebp, esp PAGE:1789FC5B 83 EC 14 sub esp, 14h PAGE:1789FC5E 56 push esi PAGE:1789FC5F 8B 75 08 mov esi, [ebp+arg_0] PAGE:1789FC62 57 push edi PAGE:1789FC63 33 FF xor edi, edi PAGE:1789FC65 89 7D F8 mov [ebp+var_8], edi PAGE:1789FC68 C6 45 FF 00 mov [ebp+var_1], 0 PAGE:1789FC6C 3B F7 cmp esi, edi PAGE:1789FC6E 75 0A jnz short loc_1789FC7A PAGE:1789FC70 B8 0D 00 00 C0 mov eax, 0C000000Dh PAGE:1789FC75 E9 A2 01 00 00 jmp loc_1789FE1C |
Требуется заменить начальные байты функции так, что бы значение регистра EAX
(в котором возвращается результат выполнения) равнялось 0 и сразу после этого осуществлялся возврат из функции.