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;//В нашем случае = 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, которая работает аналогично часто используемой в свое время функции NtQuerySystemInfotmation, то есть принимает на вход одним из аргументов SystemInformationClass, который определяет дальнейшее поведение функции. Если передать функции SystemInformationClass = 69, то, провалившись в kernel-mode посредством syscall’a, управление передается функции MmHotPatchRoutine.

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

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

В функции 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»), а второй раз непосредственно установка патча.

Так же в этой функции происходит проверка расстояния между загруженным и целевым модулем. Ели оно больше 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 до патча:

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

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

ссылка на оригинал статьи http://habrahabr.ru/post/252275/