Hotpatch. Патчим память ядра Windows

в 12:57, , рубрики: hotpatch, vulnerability research, 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.

Hotpatch. Патчим память ядра Windows - 1

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

Hotpatch. Патчим память ядра Windows - 2

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

Hotpatch. Патчим память ядра Windows - 3

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

Hotpatch. Патчим память ядра Windows - 4

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

Hotpatch. Патчим память ядра Windows - 5

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

Автор: Vallid

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js