[Из песочницы] Hotpatch. Патчим память ядра Windows

В версии Windows Server 2003 SP1 была представлена технология, называемая «хотпатчингом». То есть обновление системы «на лету», без необходимости ее перезагрузки. Технология позволяет устанавливать патчи на отдельные функции (как пользовательские, так и режима ядра). В версии 8.1 возможность установки хотпатчей была ликвидирована. Примечательно, что использовать данную возможность можно из user-mode’a даже в случае kernel-mode патчей.Стоит отметить, что такого рода патчи выпускались непродолжительное время и только под Windows Server 2003 SP1.Рассмотрим конкретный пример патча: Security Update KB914389. Данный апдейт содержит несколько патчей функций из драйверов mrxsmb.sys и rdbss.sys.

В составе патча для каждого драйвера находятся два файла: драйвер, которым будет заменен патчируемый после перезагрузки, и загадочный файл с расширением *.hp.sys, который является обыкновенным драйвером. В нем должна присутствовать секция с названием ».hotp1 » (два пробела в конце обязательны). Рассмотрим подробнее сам процесс патчинга.

Для начала, нужно ввести понятие hotpatchable функции. Это такая функция, первая инструкция которой является двухбайтовой инструкцией «mov edi, edi», а перед началом функции находятся пять nop’ов.Так же различают semi-hotpatchable функции — те, у которых первая инструкция двухбайтовая, но не mov edi, edi.

Инструкция «mov edi, edi» введена в hotpatchable функции для того, чтобы обезопасить хотпатч на мультипроцессорных системах. Например, если первая инструкция была бы однобайтовая, то мог получиться следующий результат: один из потоков входит в патчируемую функцию и выполняет первую команду. В то же время другой поток устанавливает патч на данную функцию, в результате чего первый оказывается посреди двухбайтовой инструкции «jmps -5», что приведет к падению системы.

Ниже будет представлен достаточно глубокий анализ механизма хотпатчинха, который может быть неинтересен многим читателям Суть технологии такова: сначала происходит загрузка драйвера *.hp.sys в память с помощью функции MmLoadSystemImage. Далее считываются все характеристики патча, которые находятся в секции ».hotp1 ». Примерная структура, представляющая заголовок патча представлена ниже. Структура взята отсюда, в абсолютной ее точности уверенности нет, но разногласий с дизасмом не обнаружено. typedef struct _HOTPATCH_HEADER { DWORD Signature;//«HOT1» DWORD Version;//В нашем случае = 0×00010000 DWORD FixupRgnCount;//Используется на x86 системах, в качестве таблицы релокаций в функции RtlpApplyRelocationFixups DWORD FixupRgnRva;//RVA массива релокаций DWORD ValidationCount;//Используется в функции RtlpValidateTargetRanges DWORD ValidationArrayRva;//RVA массива валидаций DWORD HookCount;//Собственно, суть патча. Количество функций, которые будут изменены DWORD HookArrayRva;//Указатель на массив хуков, используется в функции RtlReadHookInformation ULONGLONG OrigHotpBaseAddress;//Для х86 систем. Если патч и патчируемый модуль загружены ULONGLONG OrigTargetBaseAddress;//по этим адресам, то релокации не применяются DWORD TargetNameRva;//Смещение, по которому находится имя модуля, который будет патчится DWORD ModuleIdMethod;//не используется union { ULONGLONG Quad; GUID Guid; struct { GUID Guid; DWORD Age; } PdbSig; BYTE Hash128[16]; BYTE Hash160[20]; } TargetModuleIdValue; } HOTPATCH_HEADER, *PHOTPATCH_HEADER; В случае, если патч накладывается на hotpatchable функцию x86 системах первая инструкция патчируемой функции заменяется на короткий jmp — jmps -5 (оппкод ebf9). Он переводит поток управления на пять байт назад, где помещается пятибайтовая инструкция jmp m32, то есть дальний jmp на адрес, указанный в хотпатче.В остальных случаях, независимо от типа патчируемой функции, проверяется разница в адресах target — модуля и загруженного *.hp.sys. Патч устанавливается только в случае, если модуль загрузился в пределах ±2GB от target-модуля (ограничивается размером операнда «ff 25» jmp’а). Первая инструкция заменяется на шестибайтовую инструкцию «jmp m32», на rip-relative адрес до target функции.А теперь рассмотрим как же можно запустить процесс хотпатчинга.

Из ntdll.dll экспортируется функция NtSetSystemInformation, которая работает аналогично часто используемой в свое время функции NtQuerySystemInfotmation, то есть принимает на вход одним из аргументов SystemInformationClass, который определяет дальнейшее поведение функции. Если передать функции SystemInformationClass = 69, то, провалившись в kernel-mode посредством syscall’a, управление передается функции MmHotPatchRoutine.

Там происходит загрузка в память *.hp файла и дальнейшая передача управления на функцию MiPerformHotpatch.В ней, кроме прочего, происходит поиск секции ».hotp1 » в загруженном модуле, вызов функции RtlFindRtlPatchHeader, а так же поиск целевого модуля в памяти посредством перебора всех сессий в системе. Далее происходит передача управления на функцию RtlInitializeHotpatch.

65ffd511f9da4060910cfd8b6f16438a.jpg

Не будем углубляться в функции RtlpApplyRelocationFixups и RtlpValidateTargetRanges, скажем только, что с помощью последней можно убедиться, что целевая функция является hotpatchable.

86ea9299a6e744419d0de957ca8b5f97.jpg

В функции RtlReadHookInformation происходит, собственно, установка патчей.

Структура каждого патча представлена ниже.

typedef struct _HOTPATCH_HOOK { WORD HookType;//Один из HOTPATCH_HOOK_TIPE WORD HookOptions; DWORD HookRva; DWORD HotpRva; DWORD ValidationRva; } HOTPATCH_HOOK, *PHOTPATCH_HOOK;

typedef enum _HOTPATCH_HOOK_TYPE { HOTP_Hook_None = 0, HOTP_Hook_VA32 = 1, HOTP_Hook_X86_JMP = 2, HOTP_Hook_PCREL32 = 3, //not yet implemented HOTP_Hook_X86_JMP2B = 4, HOTP_Hook_VA64 = 16, HOTP_Hook_IA64_BRL = 32, HOTP_Hook_IA64_BR = 33, //not yet implemented HOTP_Hook_AMD64_IND = 48, HOTP_Hook_AMD64_CNT = 49 } HOTPATCH_HOOK_TYPE; Далее два раза происходит вызов функции RtlpReadSingleHookInformation, в которой первый раз происходит определение размера трамплина (формат и размер команды «jmp»), а второй раз непосредственно установка патча.d8a318cb5d284e29b732e827a0cbdc7e.jpg

Так же в этой функции происходит проверка расстояния между загруженным и целевым модулем. Ели оно больше 2GB, то установка патча не происходит.

Допустим, мы устанавливаем патч на Windows 7×64. Попробуем реализовать патч какой — либо функции. Например, можно выбрать функцию FatCommonWrite подсистемы fastfat, которая вызывается при записи каких — либо данных на fat32 флешку. Для начала нужно написать драйвер, который будет содержать в себе заполненную секцию ».hotp1 » и новую функцию.

#pragma section (».hotp1 »)

__declspec (allocate (».hotp1 »)) struct Hotp_Header { ULONG Signature; ULONG Version; ULONG FixupRgnCount; ULONG FixupRgnRva; ULONG ValidationCount; ULONG ValidationArrayRva; ULONG HookCount; ULONG HookArrayRva; ULONGLONG OrigHotpBaseAddress; ULONGLONG OrigTargetBaseAddress; ULONG TargetNameRva; ULONG ModuleIdMethod; union { ULONGLONG Quad; GUID Guid;

struct { GUID guid; ULONG Age; } PdbSig;

UCHAR Hash128[16]; UCHAR Hash160[20]; } TargetModuleIdValue; CHAR TagretName[13]; struct { USHORT HookType; USHORT HookOptions; ULONG HookRva; ULONG HotpRva; ULONG ValidationRva; } Hook; } hpHeader = { 0×31544F48, // »1TOH» 0×00010000, // 1.0 0×00000000, // FixupRgn 0×00000000, // FixupRgn Rva 0×00000000, // Validations 0×00000000, // Validation Rva 0×00000001, // 1 Hook 0×00005060, // HookRva 0×0000000000010000, // HotpBase 0×0000000000010000, // TargetBase 0×00005050, // Targetname Rva 0×00000000, // ModuleID 0×0000000000000000, // Quad «fastfat.sys», { 0×0030, // hook type HOTP_Hook_AMD64_IND 0×8000, // hook option ± 2GB 0×0002B6F0, // hook rva 0×0004392A, // hotp rva 0×00000000 // valid rva } };

NTSTATUS FatCommonWrite () { PINT32 p = 0; INT32 a = *p;//Сразу же узнаем, что наша функция вызвалась (:

return a; } Теперь нужно написать приложение, которое вызовет процесс хотпатчинга. Для этого напишем обычное Win32 приложение. typedef struct _SYSTEM_HOTPATCH_CODE_INFORMATION { ULONG Flags; ULONG InfoSize; USHORT NameOffset; USHORT NameLength; } SYSTEM_HOTPATCH_CODE_INFORMATION;

// //… //Заполняем структуру PatchInfo необходимыми данными, которые можно найти, если отреверсить KB914389 // //

//Устанавливаем необходимые привилегии. OpenProcessToken (GetCurrentProcess (), TOKEN_ADJUST_PRIVILEGES, &hToken); SetPrivilege (hToken, SE_DEBUG_NAME, TRUE); SetPrivilege (hToken, SE_LOAD_DRIVER_NAME, TRUE);

ZwSetSystemInformation (69, PatchInfo, PatchInfo→InfoSize); Данное приложение запустит процесс хотпатчинга. Останется только вставить fat32 флешку в компьютер, записать на нее что — нибудь и лицезреть немногословный BSOD.FatCommonWrite до патча:

575d71802e004fd08f96899c41ab5cbe.jpg

FatCommonWrite после патча:

f1133772a84643519c16f47daedb4075.jpg

В заключение стоит отметить, что хотя данная технология и не является потенциальной уязвимостью, но все равно представляет собой достаточно интересный способ патчинга памяти, принадлежащей ядру Windows.

© Habrahabr.ru