Обходи мента сзади, а 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 на время модификации.