В версии 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», что приведет к падению системы.
Допустим, мы устанавливаем патч на Windows 7 x64. Попробуем реализовать патч какой — либо функции. Например, можно выбрать функцию FatCommonWrite подсистемы fastfat, которая вызывается при записи каких — либо данных на fat32 флешку. Для начала нужно написать драйвер, который будет содержать в себе заполненную секцию ".hotp1 " и новую функцию.
Теперь нужно написать приложение, которое вызовет процесс хотпатчинга. Для этого напишем обычное Win32 приложение.
Данное приложение запустит процесс хотпатчинга. Останется только вставить fat32 флешку в компьютер, записать на нее что — нибудь и лицезреть немногословный BSOD.
FatCommonWrite до патча:
![](https://habrastorage.org/r/w780q1/files/575/d71/802/575d71802e004fd08f96899c41ab5cbe.jpg)
FatCommonWrite после патча:
![](https://habrastorage.org/r/w780q1/files/f11/337/72a/f1133772a84643519c16f47daedb4075.jpg)
В заключение стоит отметить, что хотя данная технология и не является потенциальной уязвимостью, но все равно представляет собой достаточно интересный способ патчинга памяти, принадлежащей ядру Windows.
Стоит отметить, что такого рода патчи выпускались непродолжительное время и только под 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 ". Примерная структура, представляющая заголовок патча представлена ниже. Структура взята отсюда, в абсолютной ее точности уверенности нет, но разногласий с дизасмом не обнаружено.
В случае, если патч накладывается на 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, которая работает аналогично часто используемой в свое время функции NtQuerySystemInformation, то есть принимает на вход одним из аргументов SystemInformationClass, который определяет дальнейшее поведение функции. Если передать функции SystemInformationClass = 69, то, провалившись в kernel-mode посредством syscall'a, управление передается функции MmHotPatchRoutine.
Там происходит загрузка в память *.hp файла и дальнейшая передача управления на функцию MiPerformHotpatch.
В ней, кроме прочего, происходит поиск секции ".hotp1 " в загруженном модуле, вызов функции RtlFindRtlPatchHeader, а так же поиск целевого модуля в памяти посредством перебора всех сессий в системе. Далее происходит передача управления на функцию RtlInitializeHotpatch.
![](https://habrastorage.org/r/w780q1/files/65f/fd5/11f/65ffd511f9da4060910cfd8b6f16438a.jpg)
Не будем углубляться в функции RtlpApplyRelocationFixups и RtlpValidateTargetRanges, скажем только, что с помощью последней можно убедиться, что целевая функция является hotpatchable.
![](https://habrastorage.org/r/w780q1/files/86e/a92/99a/86ea9299a6e744419d0de957ca8b5f97.jpg)
В функции RtlReadHookInformation происходит, собственно, установка патчей.
Структура каждого патча представлена ниже.
Далее два раза происходит вызов функции RtlpReadSingleHookInformation, в которой первый раз происходит определение размера трамплина (формат и размер команды «jmp»), а второй раз непосредственно установка патча.
![](https://habrastorage.org/r/w780q1/files/d8a/318/cb5/d8a318cb5d284e29b732e827a0cbdc7e.jpg)
Так же в этой функции происходит проверка расстояния между загруженным и целевым модулем. Ели оно больше 2GB, то установка патча не происходит.
typedef struct _HOTPATCH_HEADER {
DWORD Signature;//"HOT1"
DWORD Version;//В нашем случае = 0x00010000
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, которая работает аналогично часто используемой в свое время функции NtQuerySystemInformation, то есть принимает на вход одним из аргументов SystemInformationClass, который определяет дальнейшее поведение функции. Если передать функции SystemInformationClass = 69, то, провалившись в kernel-mode посредством syscall'a, управление передается функции MmHotPatchRoutine.
Там происходит загрузка в память *.hp файла и дальнейшая передача управления на функцию MiPerformHotpatch.
В ней, кроме прочего, происходит поиск секции ".hotp1 " в загруженном модуле, вызов функции RtlFindRtlPatchHeader, а так же поиск целевого модуля в памяти посредством перебора всех сессий в системе. Далее происходит передача управления на функцию RtlInitializeHotpatch.
![](https://habrastorage.org/files/65f/fd5/11f/65ffd511f9da4060910cfd8b6f16438a.jpg)
Не будем углубляться в функции RtlpApplyRelocationFixups и RtlpValidateTargetRanges, скажем только, что с помощью последней можно убедиться, что целевая функция является hotpatchable.
![](https://habrastorage.org/files/86e/a92/99a/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»), а второй раз непосредственно установка патча.
![](https://habrastorage.org/files/d8a/318/cb5/d8a318cb5d284e29b732e827a0cbdc7e.jpg)
Так же в этой функции происходит проверка расстояния между загруженным и целевым модулем. Ели оно больше 2GB, то установка патча не происходит.
Допустим, мы устанавливаем патч на Windows 7 x64. Попробуем реализовать патч какой — либо функции. Например, можно выбрать функцию 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 = {
0x31544F48, // "1TOH"
0x00010000, // 1.0
0x00000000, // FixupRgn
0x00000000, // FixupRgn Rva
0x00000000, // Validations
0x00000000, // Validation Rva
0x00000001, // 1 Hook
0x00005060, // HookRva
0x0000000000010000, // HotpBase
0x0000000000010000, // TargetBase
0x00005050, // Targetname Rva
0x00000000, // ModuleID
0x0000000000000000, // Quad
"fastfat.sys",
{
0x0030, // hook type HOTP_Hook_AMD64_IND
0x8000, // hook option +- 2GB
0x0002B6F0, // hook rva
0x0004392A, // hotp rva
0x00000000 // 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 до патча:
![](https://habrastorage.org/files/575/d71/802/575d71802e004fd08f96899c41ab5cbe.jpg)
FatCommonWrite после патча:
![](https://habrastorage.org/files/f11/337/72a/f1133772a84643519c16f47daedb4075.jpg)
В заключение стоит отметить, что хотя данная технология и не является потенциальной уязвимостью, но все равно представляет собой достаточно интересный способ патчинга памяти, принадлежащей ядру Windows.