Взлом patch-guard

       

Обходи мента сзади, а Patch-Guard спереди


Для проверки целостности образом системных библиотек разработчики Patch-Guard'а не стали использовать тяжеловесные алгоритмы типа MD5, а взяли быстрый и легкий СRC. Очевидно, они не учили мат. часть, поскольку, CRC хоть и относится к _надежным_ алгоритмам, он ориентирован на _непреднамеренные_ искажения. Помехи на линии например. Дописав несколько "корректирующих" байт (для CRC8— один, для CRC16 – два, для CRC 32 — четыре и для CRC64 – восемь), мы добьемся того, что контрольная сумма модифицированного образа останется _неизменной_ и Patch-Guard ничего не заметит!

Поскольку, Patch-Guard контролирует блоки фиксированного размера, то наша задача слегка усложняется и вместо дописывания корректирующих байт, мы должны записать их поверх неиспользуемых нулевых байт, или ненулевых — какая разница?! Главное, чтобы их значение можно было безболезненно менять на любое другое. В каждом файле легко найти множество команд NOP (опкод 90h), расположенных между функциями и служащих для выравнивания.

Остается только расковырять саму функцию, используемую Patch-Guard'ом для подсчета контрольной суммы. Листинг, приведенный ниже, позаимствованный из статьи "Bypassing PatchGuard on Windows x64":

PPATCHGUARD_SUB_CONTEXT PgCreateBlockChecksumSubContext(

       IN PPATCHGUARD_CONTEXT Context,

       IN ULONG Unknown,

       IN PVOID BlockAddress,

       IN ULONG BlockSize,

       IN ULONG SubContextSize,

       OUT PBLOCK_CHECKSUM_STATE ChecksumState OPTIONAL)

{

       ULONG64       Checksum = Context->RandomHashXorSeed;

       ULONG  Checksum32;



      

       // Checksum 64-bit blocks

       while (BlockSize >= sizeof(ULONG64))

       {

       Checksum    ^= *(PULONG64)BaseAddress;

       Checksum     = RotateLeft(Checksum, Context->RandomHashRotateBits);

       BlockSize   -= sizeof(ULONG64);

       BaseAddress += sizeof(ULONG64);

       }

      

       // Checksum aligned blocks

       while (BlockSize-- > 0)


       {

       Checksum    ^= *(PUCHAR)BaseAddress;

       Checksum     = RotateLeft(Checksum, Context->RandomHashRotateBits);

       BaseAddress++;

       }

      

       Checksum32 = (ULONG)Checksum;

      

       Checksum >>= 31;

      

       do

       {

              Checksum32  ^= (ULONG)Checksum;

              Checksum   >>= 31;

       } while (Checksum);

}

Листинг 5  код функции PgCreateBlockChecksumSubContext, рассчитывающий контрольную сумму заданного блока

Как можно видеть, на выходе функции PgCreateBlockChecksumSubContext мы получаем 32-битный Checksum, записываемый в структуру BLOCK_CHECKSUM_STATE вместе с базовым адресом и размером контролируемого блока (кстати говоря, префикс "Pg" определенно означает Patch-Guard, позволяя нам легко и быстро отделять принадлежащие к Patch-Guard'у функции ото всех остальных функций ядра):

typedef struct BLOCK_CHECKSUM_STATE

{

       ULONG  Unknown;

       ULONG64       BaseAddress;

       ULONG  BlockSize;

       ULONG  Checksum;

} BLOCK_CHECKSUM_STATE, *PBLOCK_CHECKSUM_STATE;

Листинг 6 структура, хранящая 32-битный CRC вместе с другими данными

Информационная емкость 32-битной контрольной суммы составляет всего 4 байта, которые элементарно рассчитываются даже без всякого перебора. Подробнее об этом можно прочитать в моей статье: "как подделывают CRC16/32" (валяющейся на ftp://nezumi.org,ru), а так же в замечательном хакерском руководстве, ориентированном на астматиков и доходчиво рассказывающим как вычисляется и подделывается CRC32 путем дописывания 4х корректирующих байт в конец контролируемого блока: http://foff.astalavista.ms/tutorialz/Crc.htm, добротный перевод которой на русский лежит на: www.pilorama.r2.ru/library/pdf/crcrevrs.pdf. Учитывая, что Microsoft в любой момент может пересмотреть свой позиции, расширив контрольную сумму до 8-байт (что на 64-разрядных процессорах очень легко сделать), нелишне будет заблаговременно ознакомится с базовыми принципами алгоритма CRC64, описание которого припрятано на:  http://www.pdl.cmu.edu/mailinglists/ips/mail/msg02982.html.



Аналогичным образом осуществляется и модификация GDT/IDT – в них полно незадействованных полей и выкроить четыре байта не будет проблемой. А вот с SSDT дела обстоят похуже, поскольку Patch-Guard сверяет "рабочую" копию с ее "оригиналом", хранящимся внутри образа NTOSKRNL.EXE по адресу nt!KeServiceDescriptorTable. Кажется, что ситуация финиш, но нет! Ведь мы уже умеем безболезненно модифицировать образ ядра, следовательно, нам ничего не будет стоит синхронно произвести изменения в обоих таблицах и Patch-Guard снова ничего не заметит.



Рисунок 7 в IDT полно неиспользуемых нулей, которые можно использовать для коррекции



Рисунок 8 вместо подсчета контрольной суммы SSDT, Patch-Guard сверяет ее с оригиналом, поэтому обе копии приходится править синхронно

Весь вопрос в том — насколько надежен и "законен" такой хак? Ведь модификация образа ядра — далеко не атомарная операция! Сначала мы должны рассчитать CRC32 "исправленного" файла, затем модифицировать некое количество байт образа, после чего внедрить четыре "корректирующих" байта. А что если в промежутке между модификацией и "коррекцией" неожиданно проснется Patch-Guard и закричит: "измена!!! нас поимели!!!". Чтобы заставить его заткнуться, достаточно вспоминать, что "стражники" представляют собой обыкновенные отложенные процедуры — (Deferred Procedure Call) или, сокращенно, DPC, исполняющиеся на IRQL уровне равному двум. Если мы повысим IRQL (не забывая, что в многопроцессорных системах каждый процессор имеет свой IRQL), то никакие DPC исполняться не смогут пока уровень вновь не будет понижен. Правда, вместе с DPC не будет работать и подкачка с диска, так что можно очень легко нарваться на голубой экран смерти, но! Если сначала обратиться к той странице образа, которую мы собирались модифицировать, а затем — к странице, куда собрались внедрять корректирующие байты, обе страницы гарантированно окажутся в памяти и мы можем смело повышать IRQL на время модификации.


Содержание раздела