Hotpatch Patch Windows kernel memory

In Windows Server 2003 SP1, a technology called hot patching was introduced. That is, updating the system on the fly, without the need to reboot it. The technology allows you to install patches on individual functions (both user and kernel mode). In version 8.1, the ability to install hot patches was eliminated. It is noteworthy that you can use this feature from user-mode'a even in the case of kernel-mode patches.

It is worth noting that this kind of patches were released for a short time and only under Windows Server 2003 SP1.

Consider a specific patch example: Security Update KB914389. This update contains several feature patches from the mrxsmb.sys and rdbss.sys drivers.

The patch for each driver contains two files: the driver, which will be replaced by the patch after the reboot, and a mysterious file with the extension * .hp.sys, which is an ordinary driver. It should contain a section with the name ".hotp1" (two spaces at the end are required). Let's take a closer look at the patch process itself.

To get started, you need to introduce the concept of a hotpatchable function. This is such a function, the first instruction of which is a two-byte instruction "mov edi, edi", and before the start of the function there are five nops.
Semi-hotpatchable functions are also distinguished - those with the first instruction double-byte, but not mov edi, edi.

The instruction "mov edi, edi" is introduced in the hotpatchable function in order to secure the hot patch on multiprocessor systems. For example, if the first instruction were single-byte, then the following result could be obtained: one of the threads enters the patched function and executes the first command. At the same time, another thread installs a patch for this function, as a result of which the first one appears in the middle of the two-byte instruction “jmps -5”, which will lead to the system crash.

A rather in-depth analysis of the hot patch mechanism, which may be uninteresting to many readers, will be presented below.
The essence of the technology is this: first, the * .hp.sys driver is loaded into memory using the MmLoadSystemImage function. Next, all the characteristics of the patch, which are in the ".hotp1" section, are read. An exemplary structure representing the title of the patch is presented below. The structure is taken from here , there is no certainty in its absolute accuracy, but no disagreement with dysasm was found.

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;

In case the patch is superimposed on the hotpatchable function of x86 systems, the first instruction of the patched function is replaced with a short jmp - jmps -5 (opposit code ebf9). It transfers the control flow five bytes back, where the five-byte instruction jmp m32 is placed, that is, the far jmp to the address specified in the hot patch.
In other cases, regardless of the type of function being patched, the difference in the addresses of the target module and the loaded * .hp.sys is checked. The patch is installed only if the module is loaded within + -2GB of the target module (limited by the size of the operand “ff 25” jmp). The first instruction is replaced with a six-byte instruction “jmp m32”, with a rip-relative address to the target function.

Now let's see how you can start the hot patch process.

From ntdll.dll, the NtSetSystemInformation function is exported, which works similarly to the NtQuerySystemInformation function often used at one time, that is, it takes one of the arguments SystemInformationClass, which determines the further behavior of the function. If you pass the SystemInformationClass = 69 function, then, having failed in kernel-mode using syscall, the control is transferred to the MmHotPatchRoutine function.

There, a * .hp file is loaded into memory and further control is transferred to the MiPerformHotpatch function.
It, among other things, searches for the ".hotp1" section in the loaded module, calls the RtlFindRtlPatchHeader function, and also searches for the target module in memory by enumerating all the sessions in the system. Next, control is transferred to the RtlInitializeHotpatch function.



We will not delve into the functions of RtlpApplyRelocationFixups and RtlpValidateTargetRanges, we only say that with the help of the latter we can make sure that the target function is hotpatchable.



In the function RtlReadHookInformation, the installation of patches occurs.

The structure of each patch is presented below.

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;

Next, the RtlpReadSingleHookInformation function is called two times, in which the first time is the size of the springboard (the format and size of the jmp command), and the second time the patch is installed directly.



Also in this function, the distance between the loaded and the target module is checked. If it is larger than 2GB, the patch will not be installed.


Suppose we install a patch on Windows 7 x64. Let's try to implement a patch of some function. For example, you can select the FatCommonWrite function of the fastfat subsystem, which is called when any data is written to a fat32 flash drive. First you need to write a driver that will contain the filled ".hotp1" section and a new function.

#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;
}

Now you need to write an application that will invoke the hot patch process. To do this, we will write a regular Win32 application.

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);

This application will start the hot patch process. All that remains is to insert the fat32 flash drive into the computer, write something on it and see the laconic BSOD.

FatCommonWrite before the patch:



FatCommonWrite after the patch:



In conclusion, it is worth noting that although this technology is not a potential vulnerability, it is still an interesting way to patch memory belonging to the Windows kernel.

Also popular now: