Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2182 lines
61 KiB

/*
Copyright (c) 2001 Microsoft Corporation
File name:
hotpatch.c
Author:
Adrian Marinescu (adrmarin) Dec 12 2001
Description:
The file implements common utility functions for user and kernel mode
hotpatching.
*/
#include <ntos.h>
#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include "hotpatch.h"
#pragma intrinsic( _rotl64 )
#define HASH_INFO_SIZE 0xc
#define COLDPATCH_SIGNATURE 0XD202
#define FLGP_COLDPATCH_TARGET 0x00010000
PVOID
RtlpAllocateHotpatchMemory (
IN SIZE_T BlockSize,
IN BOOLEAN AccessedAtDPC
);
VOID
RtlpFreeHotpatchMemory (
IN PVOID Block
);
PIMAGE_SECTION_HEADER
RtlpFindSectionHeader(
IN PIMAGE_NT_HEADERS NtHeaders,
IN PUCHAR SectionName
);
NTSTATUS
RtlpApplyRelocationFixups (
PRTL_PATCH_HEADER RtlHotpatchHeader,
IN ULONG_PTR PatchOffsetCorrection
);
NTSTATUS
RtlpSingleRangeValidate(
PRTL_PATCH_HEADER RtlHotpatchHeader,
PHOTPATCH_VALIDATION Validation
);
NTSTATUS
RtlpValidateTargetRanges(
PRTL_PATCH_HEADER RtlHotpatchHeader,
BOOLEAN IgnoreHookTargets
);
NTSTATUS
RtlReadSingleHookValidation(
IN PRTL_PATCH_HEADER RtlPatchData,
IN PHOTPATCH_HOOK HookEntry,
IN ULONG BufferSize,
OUT PULONG ValidationSize,
OUT PUCHAR Buffer OPTIONAL,
IN PUCHAR OriginalCodeBuffer OPTIONAL,
IN ULONG OriginalCodeSize OPTIONAL
);
NTSTATUS
RtlpReadSingleHookInformation(
IN PRTL_PATCH_HEADER RtlPatchData,
IN PHOTPATCH_HOOK HookEntry,
IN ULONG BufferSize,
OUT PULONG HookSize,
OUT PUCHAR Buffer OPTIONAL
);
BOOLEAN
RtlpGetColdpatchHashId(
IN PVOID TargetDllBase,
OUT PULONGLONG HashValue
);
#if defined(ALLOC_PRAGMA) && defined(NTOS_KERNEL_RUNTIME)
#pragma alloc_text(PAGE, RtlpAllocateHotpatchMemory)
#pragma alloc_text(PAGE, RtlpFreeHotpatchMemory)
#pragma alloc_text(PAGE, RtlpFindSectionHeader)
#pragma alloc_text(PAGE, RtlGetHotpatchHeader)
#pragma alloc_text(PAGE, RtlpApplyRelocationFixups)
#pragma alloc_text(PAGE, RtlGetHotpatchHeader)
#pragma alloc_text(PAGE, RtlFindRtlPatchHeader)
#pragma alloc_text(PAGE, RtlCreateHotPatch)
#pragma alloc_text(PAGE, RtlFreeHotPatchData)
#pragma alloc_text(PAGE, RtlpSingleRangeValidate)
#pragma alloc_text(PAGE, RtlpValidateTargetRanges)
#pragma alloc_text(PAGE, RtlReadSingleHookValidation)
#pragma alloc_text(PAGE, RtlpReadSingleHookInformation)
#pragma alloc_text(PAGE, RtlReadHookInformation)
#pragma alloc_text(PAGE, RtlInitializeHotPatch)
#pragma alloc_text(PAGE, RtlpIsSameImage)
#pragma alloc_text(PAGE, RtlpGetColdpatchHashId)
#endif // ALLOC_PRAGMA
PVOID
RtlpAllocateHotpatchMemory (
IN SIZE_T BlockSize,
IN BOOLEAN AccessedAtDPC
)
/*++
Routine Description:
Allocates a block of memory from pool in kmode and
process heap in user mode
Arguments:
BlockSize - Receives the size of the block to be allocated
AccessedAtDPC - Used in KMode only, allocates from
the non-paged pool (because a DPC routine is going to use it).
Return Value:
Returns the new memory block
--*/
{
#ifdef NTOS_KERNEL_RUNTIME
return ExAllocatePoolWithTag ( (AccessedAtDPC ? NonPagedPool : PagedPool),
BlockSize,
'PtoH');
#else
UNREFERENCED_PARAMETER(AccessedAtDPC);
return RtlAllocateHeap (RtlProcessHeap(), 0, BlockSize);
#endif //NTOS_KERNEL_RUNTIME
}
VOID
RtlpFreeHotpatchMemory (
IN PVOID Block
)
/*++
Routine Description:
Frees a memory block allocated with RtlpAllocateHotpatchMemory
Arguments:
Block - Memory block
Return Value:
None.
--*/
{
#ifdef NTOS_KERNEL_RUNTIME
ExFreePoolWithTag (Block, 'PtoH');
#else
RtlFreeHeap (RtlProcessHeap(), 0, Block);
#endif //NTOS_KERNEL_RUNTIME
}
PIMAGE_SECTION_HEADER
RtlpFindSectionHeader(
IN PIMAGE_NT_HEADERS NtHeaders,
IN PUCHAR SectionName
)
/*++
Routine Description:
The function searches a section in a PE image
Arguments:
NtHeaders - Image's header
SectionName - The name of the section to be retrieved
Return Value:
Returns the pointer to the section header in case of success
of NULL if no such section exists.
--*/
{
ULONG i;
PIMAGE_SECTION_HEADER NtSection;
NtSection = IMAGE_FIRST_SECTION( NtHeaders );
for ( i = 0; i < NtHeaders->FileHeader.NumberOfSections; i += 1) {
if ( RtlCompareMemory( NtSection->Name,
SectionName,
IMAGE_SIZEOF_SHORT_NAME) == IMAGE_SIZEOF_SHORT_NAME) {
return NtSection;
}
NtSection += 1;
}
return NULL;
}
PHOTPATCH_HEADER
RtlGetHotpatchHeader(
PVOID ImageBase
)
/*++
Routine Description:
The routine retrieves the hotpatch header from a hotpatch PE image
Arguments:
ImageBase - The base address of the hotpatch image
Return Value:
Returns the pointer to the hotpatch header or NULL in case of failure.
--*/
{
PIMAGE_NT_HEADERS NtHeaders;
PIMAGE_SECTION_HEADER PatchSection;
PHOTPATCH_HEADER HotpatchHeader;
NtHeaders = RtlImageNtHeader(ImageBase);
if (NtHeaders == NULL) {
return NULL;
}
PatchSection = RtlpFindSectionHeader(NtHeaders, HOTP_SECTION_NAME);
if (PatchSection == NULL) {
return NULL;
}
HotpatchHeader = (PHOTPATCH_HEADER)((ULONG_PTR)ImageBase + PatchSection->VirtualAddress);
if ((sizeof( HOTPATCH_HEADER ) > PatchSection->Misc.VirtualSize)
||
( HotpatchHeader->Signature != HOTP_SIGNATURE_DWORD )
||
( HotpatchHeader->Version != HOTP_HEADER_VERSION_1_0 )){
return NULL;
}
return HotpatchHeader;
}
PRTL_PATCH_HEADER
RtlFindRtlPatchHeader(
IN PLIST_ENTRY PatchList,
IN PPATCH_LDR_DATA_TABLE_ENTRY PatchLdrEntry
)
/*++
Routine Description:
The routine retrieves the rtl patch structure for a specified
patch loader entry.
Arguments:
PatchList - The list with patches to be searched.
PatchLdrEntry - Supplies the requested loader entry
Return Value:
If found, it returns the appropriate patch structure, otherwise
it returns NULL.
--*/
{
PLIST_ENTRY Next = PatchList->Flink;
for ( ; Next != PatchList; Next = Next->Flink) {
PRTL_PATCH_HEADER Entry;
Entry = CONTAINING_RECORD (Next, RTL_PATCH_HEADER, PatchList);
if (Entry->PatchLdrDataTableEntry == PatchLdrEntry) {
return Entry;
}
}
return NULL;
}
NTSTATUS
RtlCreateHotPatch (
OUT PRTL_PATCH_HEADER * RtlPatchData,
IN PHOTPATCH_HEADER Patch,
IN PPATCH_LDR_DATA_TABLE_ENTRY PatchLdrEntry,
IN ULONG PatchFlags
)
/*++
Routine Description:
This is an utility routine used by either user-mode and kernel-mode patching
to process the relocation information and generate the fixup codes. It finally
allocates and initialize a PRTL_PATCH_HEADER structure which is going to be
inserted into the loader data entry for the module being patched.
Arguments:
RtlPatchData - receives the new initialized RTL_PATCH_HEADER structure.
Patch - Points to the patch being applied
PatchLdrEntry - The loader entry for the patch module
PatchFlags - The options for the flags being applied
Return Value:
Returns the appropriate status
--*/
{
ULONG i;
PRTL_PATCH_HEADER NewPatch = NULL;
NTSTATUS Status;
ANSI_STRING AnsiString;
NewPatch = RtlpAllocateHotpatchMemory (sizeof(*NewPatch), FALSE);
if (NewPatch == NULL) {
return STATUS_NO_MEMORY;
}
RtlZeroMemory(NewPatch, sizeof(*NewPatch));
NewPatch->HotpatchHeader = Patch;
NewPatch->PatchLdrDataTableEntry = PatchLdrEntry;
NewPatch->PatchImageBase = PatchLdrEntry->DllBase;
//
// Copy the flags, except the enable which is supposed to be set later
//
NewPatch->PatchFlags = PatchFlags & (~FLG_HOTPATCH_ACTIVE);
InitializeListHead (&NewPatch->PatchList);
RtlInitAnsiString(&AnsiString, (PUCHAR)PatchLdrEntry->DllBase + Patch->TargetNameRva);
Status = RtlAnsiStringToUnicodeString(&NewPatch->TargetDllName, &AnsiString, TRUE);
if (NT_SUCCESS(Status)) {
*RtlPatchData = NewPatch;
} else {
RtlFreeHotPatchData(NewPatch);
}
return Status;
}
VOID
RtlFreeHotPatchData(
IN PRTL_PATCH_HEADER RtlPatchData
)
/*++
Routine Description:
The routine frees the RtlPatchData data structure.
Arguments:
RtlPatchData - receives a properly initialized RTL_PATCH_HEADER structure,
as returned by RtlInitializeHotPatch
Return Value:
None
--*/
{
if (RtlPatchData->CodeInfo) {
RtlpFreeHotpatchMemory( RtlPatchData->CodeInfo );
}
RtlFreeUnicodeString( &RtlPatchData->TargetDllName );
RtlpFreeHotpatchMemory( RtlPatchData );
}
NTSTATUS
RtlpApplyRelocationFixups (
PRTL_PATCH_HEADER RtlHotpatchHeader,
IN ULONG_PTR PatchOffsetCorrection
)
/*++
Routine Description:
This routine applies the fixups for a region of code to the patch module.
Arguments:
RtlHotpatchHeader - Supplies the rtl hotpatch structure
PatchOffsetCorrection - Supplies the relative address of the original module.
Return Value:
NTSTATUS.
--*/
{
ULONG_PTR TargetBase = (ULONG_PTR)RtlHotpatchHeader->TargetDllBase;
ULONG_PTR HotpBase = (ULONG_PTR)RtlHotpatchHeader->PatchImageBase;
ULONG_PTR OrigTargetBase = (ULONG_PTR)RtlHotpatchHeader->HotpatchHeader->OrigTargetBaseAddress;
ULONG_PTR OrigHotpBase = (ULONG_PTR)RtlHotpatchHeader->HotpatchHeader->OrigHotpBaseAddress;
//
// Bias values are positive if actual load address is higher than
// than original load address.
//
ULONG_PTR TargetBias = TargetBase - OrigTargetBase;
ULONG_PTR HotpBias = HotpBase - OrigHotpBase;
ULONG_PTR PcrelBias = TargetBias - HotpBias;
ULONG_PTR NextFixupRegionRva = RtlHotpatchHeader->HotpatchHeader->FixupRgnRva;
ULONG_PTR RegionCount = RtlHotpatchHeader->HotpatchHeader->FixupRgnCount;
PIMAGE_NT_HEADERS NtHotpatchHeader;
if (( TargetBias == 0 ) && ( HotpBias == 0 )) {
return STATUS_SUCCESS; // no fixups are necessary, all loaded where expected.
}
NtHotpatchHeader = RtlImageNtHeader(RtlHotpatchHeader->PatchImageBase);
while ( RegionCount-- ) {
PHOTPATCH_FIXUP_REGION FixupRegion;
ULONG_PTR FixupBaseRva;
PUCHAR FixupBasePtr;
ULONG FixupCount;
ULONG RegionSize;
PHOTPATCH_FIXUP_ENTRY pFixup;
if (( NextFixupRegionRva == 0 ) ||
( NextFixupRegionRva >= NtHotpatchHeader->OptionalHeader.SizeOfImage )) {
return STATUS_INVALID_IMAGE_FORMAT;;
}
FixupRegion = (PHOTPATCH_FIXUP_REGION)( HotpBase + NextFixupRegionRva );
FixupBaseRva = FixupRegion->RvaHi << 12;
FixupBasePtr = (PUCHAR)HotpBase + FixupBaseRva;
FixupCount = FixupRegion->Count;
RegionSize = sizeof(HOTPATCH_FIXUP_REGION) + sizeof(SHORT) * (FixupCount - 2);
NextFixupRegionRva += RegionSize;
if (( FixupBaseRva >= NtHotpatchHeader->OptionalHeader.SizeOfImage ) ||
( NextFixupRegionRva >= NtHotpatchHeader->OptionalHeader.SizeOfImage ) ||
( FixupCount & 1 )) {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Invalid fixup information\n" );
return STATUS_INVALID_IMAGE_FORMAT;
}
pFixup = (PHOTPATCH_FIXUP_ENTRY)&FixupRegion->Fixup[ 0 ];
while ( FixupCount-- ) {
PUCHAR FixupPtr = FixupBasePtr + pFixup->RvaOffset;
switch ( pFixup->FixupType ) {
case HOTP_Fixup_None: // No fixup, ignore this entry (alignment, etc)
{
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"\t None%s\n",
FixupCount ? "" : " (padding)" );
break;
}
case HOTP_Fixup_VA32: // 32-bit address in target image
{
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"\t%08I64X: VA32 %08X -> %08X %s\n",
(ULONGLONG)FixupPtr,
*(PULONG)FixupPtr,
*(PULONG)FixupPtr + TargetBias,
TargetBias ? "" : "(no change)"
);
if ( TargetBias != 0 ) {
*(PULONG)(FixupPtr + PatchOffsetCorrection) += (ULONG)TargetBias;
}
break;
}
case HOTP_Fixup_PC32: // 32-bit x86 pc-rel address to target image
{
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"\t%08I64X: PC32 %08X -> %08X (target %08X) %s\n",
(ULONGLONG)FixupPtr,
*(PULONG)FixupPtr,
*(PULONG)FixupPtr + PcrelBias,
(PULONG)(ULONG_PTR)( FixupPtr + 4 + *(ULONG UNALIGNED*)FixupPtr + PcrelBias ),
PcrelBias ? "" : "(no change)"
);
if ( PcrelBias != 0 ) {
*(PULONG)(FixupPtr + PatchOffsetCorrection) += (ULONG)PcrelBias;
}
break;
}
case HOTP_Fixup_VA64: // 64-bit address in target image
{
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"\t%08I64X: VA64 %016I64X -> %016I64X %s\n",
(ULONGLONG)FixupPtr,
*(ULONGLONG UNALIGNED*)FixupPtr,
*(ULONGLONG UNALIGNED*)FixupPtr + TargetBias,
TargetBias ? "" : "(no change)"
);
if ( TargetBias != 0 ) {
*(PULONGLONG)(FixupPtr + PatchOffsetCorrection) += TargetBias;
}
break;
}
default: // unrecognized fixup type
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"\t%08I64X: Unknown\n",
(ULONGLONG)FixupPtr );
return STATUS_INVALID_IMAGE_FORMAT;
}
pFixup += 1;
}
}
return STATUS_SUCCESS;
}
NTSTATUS
RtlpSingleRangeValidate(
PRTL_PATCH_HEADER RtlHotpatchHeader,
PHOTPATCH_VALIDATION Validation
)
/*++
Routine Description:
The routine validates a single code range within the binary.
Arguments:
RtlHotpatchHeader - Supplies the rtl hotpatch header
Validation - Supplies the validation information
Return Value:
NTSTATUS.
--*/
{
ULONG_PTR SourceRva = Validation->SourceRva;
ULONG_PTR TargetRva = Validation->TargetRva;
ULONG_PTR ByteCount = Validation->ByteCount;
PIMAGE_NT_HEADERS NtHotpatchHeader;
PIMAGE_NT_HEADERS NtTargetHeader;
NtHotpatchHeader = RtlImageNtHeader(RtlHotpatchHeader->PatchImageBase);
NtTargetHeader = RtlImageNtHeader(RtlHotpatchHeader->TargetDllBase);
if ((( SourceRva ) >= NtHotpatchHeader->OptionalHeader.SizeOfImage ) ||
(( SourceRva + ByteCount ) >= NtHotpatchHeader->OptionalHeader.SizeOfImage ) ) {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Invalid source hotpatch validation range\n" );
return STATUS_INVALID_IMAGE_FORMAT;
}
if ((( TargetRva ) >= NtTargetHeader->OptionalHeader.SizeOfImage ) ||
(( TargetRva + ByteCount ) >= NtTargetHeader->OptionalHeader.SizeOfImage )) {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Invalid target validation range\n" );
return STATUS_INVALID_IMAGE_FORMAT;
}
if (RtlCompareMemory((PUCHAR)RtlHotpatchHeader->PatchImageBase + SourceRva,
(PUCHAR)RtlHotpatchHeader->TargetDllBase + TargetRva,
ByteCount ) != ByteCount) {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"Validation failure. Source = %lx, Target = %lx, Size = %lx\n",
(PUCHAR)RtlHotpatchHeader->PatchImageBase + SourceRva,
(PUCHAR)RtlHotpatchHeader->TargetDllBase + TargetRva,
ByteCount
);
return STATUS_DATA_ERROR;
}
return STATUS_SUCCESS;
}
NTSTATUS
RtlpValidateTargetRanges(
PRTL_PATCH_HEADER RtlHotpatchHeader,
BOOLEAN IgnoreHookTargets
)
/*++
Routine Description:
This routine validate all hotpatch ranges
Arguments:
RtlHotpatchHeader - Supplies the rtl hotpatch header
IgnoreHookTargets - If specified the hook validation are ignored.
Return Value:
NTSTATUS.
--*/
{
ULONG ValidArrayRva = RtlHotpatchHeader->HotpatchHeader->ValidationArrayRva;
ULONG ValidCount = RtlHotpatchHeader->HotpatchHeader->ValidationCount;
ULONG ValidArraySize = ValidCount * sizeof( HOTPATCH_VALIDATION );
PIMAGE_NT_HEADERS NtHotpatchHeader;
PHOTPATCH_VALIDATION ValidArray;
ULONG i;
NtHotpatchHeader = RtlImageNtHeader(RtlHotpatchHeader->PatchImageBase);
if ( ValidCount == 0 ) {
return STATUS_SUCCESS;
}
if (( ValidArrayRva == 0 ) ||
( ValidArrayRva >= NtHotpatchHeader->OptionalHeader.SizeOfImage ) ||
(( ValidArrayRva + ValidArraySize ) >= NtHotpatchHeader->OptionalHeader.SizeOfImage )) {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Invalid hotpatch validation array pointer\n" );
return STATUS_INVALID_IMAGE_FORMAT;
}
ValidArray = (PHOTPATCH_VALIDATION)( (ULONG_PTR)RtlHotpatchHeader->PatchImageBase + ValidArrayRva );
//
// Loop through all validation ranges
//
for ( i = 0; i < ValidCount; i += 1 ) {
NTSTATUS Status;
if (( IgnoreHookTargets ) && ( ValidArray[ i ].OptionFlags == HOTP_Valid_Hook_Target )) {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"Skipping hook-specific validation range during global validation\n" );
continue;
}
Status = RtlpSingleRangeValidate( RtlHotpatchHeader,
&ValidArray[ i ] );
if ( !NT_SUCCESS(Status) ) {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Validation failed for global range %u of %u\n",
i + 1,
ValidCount );
return Status;
}
}
return STATUS_SUCCESS;
}
PRTL_PATCH_HEADER
RtlpSearchValidationCode (
IN PRTL_PATCH_HEADER RtlPatchData,
IN PUCHAR ValidationCode,
IN SIZE_T ValidationSize
)
/*++
Routine Description:
The routine searches a validation range in existing hotpatches. The loader
lock is assumed.
Arguments:
RtlPatchData - Supplies the rtl hotpatch data structure
ValidationCode - Receives the validation data for that hook.
ValidationSize - Receives the actual size of the Validation information
Return Value:
NTSTATUS.
--*/
{
PPATCH_LDR_DATA_TABLE_ENTRY TargetLdrDataTableEntry;
TargetLdrDataTableEntry = RtlPatchData->TargetLdrDataTableEntry;
if (TargetLdrDataTableEntry) {
PRTL_PATCH_HEADER PatchHead = (PRTL_PATCH_HEADER)TargetLdrDataTableEntry->PatchInformation;
while (PatchHead) {
PSYSTEM_HOTPATCH_CODE_INFORMATION PatchInfo;
ULONG i;
PatchInfo = PatchHead->CodeInfo;
for (i = 0; i < PatchInfo->CodeInfo.DescriptorsCount; i += 1) {
if (RtlCompareMemory (ValidationCode,
(PUCHAR)PatchInfo + PatchInfo->CodeInfo.CodeDescriptors[i].CodeOffset,
ValidationSize) == ValidationSize) {
return PatchHead;
}
}
PatchHead = PatchHead->NextPatch;
}
}
return NULL;
}
NTSTATUS
RtlReadSingleHookValidation(
IN PRTL_PATCH_HEADER RtlPatchData,
IN PHOTPATCH_HOOK HookEntry,
IN ULONG BufferSize,
OUT PULONG ValidationSize,
OUT PUCHAR Buffer OPTIONAL,
IN PUCHAR OriginalCodeBuffer OPTIONAL,
IN ULONG OriginalCodeSize OPTIONAL
)
/*++
Routine Description:
This utility function reads the validation data for a hotpatch hook
Arguments:
RtlPatchData - Supplies the rtl hotpatch data structure
HookEntry - Supplies the hook structure
BufferSize - Supplies the available memory in Buffer
ValidationSize - Receives the actual size of the Validation information
Buffer - Receives the validation data for that hook.
Return Value:
NTSTATUS.
--*/
{
PIMAGE_NT_HEADERS NtHotpatchHeader;
ULONG_PTR ValidationRva = HookEntry->ValidationRva;
PHOTPATCH_VALIDATION Validation;
NTSTATUS Status;
if ( ValidationRva == 0 ) {
*ValidationSize = 0;
return STATUS_SUCCESS; // no validation record for this hook entry
}
NtHotpatchHeader = RtlImageNtHeader(RtlPatchData->PatchImageBase);
if (( ValidationRva + sizeof( HOTPATCH_VALIDATION )) >= NtHotpatchHeader->OptionalHeader.SizeOfImage ) {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Invalid hotpatch validation pointer in hook record\n" );
return STATUS_INVALID_IMAGE_FORMAT;
}
Validation = (PHOTPATCH_VALIDATION)( (PCHAR)RtlPatchData->PatchImageBase + ValidationRva );
Status = RtlpSingleRangeValidate( RtlPatchData,
Validation
);
*ValidationSize = Validation->ByteCount;
if (ARGUMENT_PRESENT(Buffer)) {
if (BufferSize <= Validation->SourceRva) {
RtlCopyMemory( Buffer,
(PUCHAR)RtlPatchData->PatchImageBase + Validation->SourceRva,
Validation->ByteCount );
} else {
return STATUS_BUFFER_TOO_SMALL;
}
}
if (Status == STATUS_DATA_ERROR) {
//
// We do additional tests in case the validation fails
//
switch ( HookEntry->HookType ) {
case HOTP_Hook_None:
*ValidationSize = 0;
Status = STATUS_SUCCESS;
break;
case HOTP_Hook_X86_JMP:
{
PRTL_PATCH_HEADER ExistingPatchData;
//
// we want to insert a jmp. If the previous code was a jmp too
// then we'll probably patch the same function again
//
if (ARGUMENT_PRESENT(OriginalCodeBuffer)) {
//
// Check whether the previous hook was a jmp or not
//
if ((OriginalCodeSize < 5) || (*OriginalCodeBuffer != 0xE9)) {
return STATUS_DATA_ERROR;
}
//
// Now test if the jmp was written by an existing hotpatch
//
ExistingPatchData = RtlpSearchValidationCode (RtlPatchData,
OriginalCodeBuffer,
OriginalCodeSize
);
if (ExistingPatchData) {
Status = STATUS_SUCCESS;
} else if (RtlPatchData->PatchFlags & FLGP_COLDPATCH_TARGET) {
//
// No hotpatch from the list inserted this jmp. It could be then
// a coldpatch file. Test if the jmp points inside the same binary
//
PIMAGE_NT_HEADERS NtTargetHeader;
LONG Ptr32 = *(LONG UNALIGNED*)(OriginalCodeBuffer + 1);
Ptr32 = Ptr32 + (LONG)HookEntry->HookRva + 5; // 5 == length of jmp instruction
NtTargetHeader = RtlImageNtHeader(RtlPatchData->TargetDllBase);
if ( (Ptr32 > 0) && ((ULONG)Ptr32 < NtTargetHeader->OptionalHeader.SizeOfImage) ) {
Status = STATUS_SUCCESS;
}
}
} else {
//
// The original code has not been supplied. This must be a call
// to query the validation size, so we postpone the actual validation
// for the next time
//
Status = STATUS_SUCCESS;
}
}
break;
case HOTP_Hook_X86_JMP2B:
{
PRTL_PATCH_HEADER ExistingPatchData;
//
// we want to insert a jmp. If the previous code was a jmp too
// then we'll probably patch the same function again
//
if (ARGUMENT_PRESENT(OriginalCodeBuffer)) {
//
// Check whether the previous hook was a short jmp or not
//
if ((OriginalCodeSize < 2) || (*OriginalCodeBuffer != 0xEB)) {
return STATUS_DATA_ERROR;
}
//
// Now test if the jmp was written by an existing hotpatch
//
ExistingPatchData = RtlpSearchValidationCode (RtlPatchData,
OriginalCodeBuffer,
OriginalCodeSize
);
//
// We also allow pass the validation test if the target is a coldpatch
//
if ((ExistingPatchData != NULL)
||
(RtlPatchData->PatchFlags & FLGP_COLDPATCH_TARGET)) {
Status = STATUS_SUCCESS;
}
} else {
//
// The original code has not been supplied. This must be a call
// to query the validation size, so we postpone the actual validation
// for the next time
//
Status = STATUS_SUCCESS;
}
}
break;
case HOTP_Hook_VA32:
case HOTP_Hook_VA64:
case HOTP_Hook_IA64_BRL:
default:
{
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Hook type not yet implemented\n" );
}
}
}
return Status;
}
NTSTATUS
RtlpReadSingleHookInformation(
IN PRTL_PATCH_HEADER RtlPatchData,
IN PHOTPATCH_HOOK HookEntry,
IN ULONG BufferSize,
OUT PULONG HookSize,
OUT PUCHAR Buffer OPTIONAL
)
/*++
Routine Description:
Utility procedure to fill up the hook information
Arguments:
RtlPatchData - Supplies the rtl hotpatch information
HookEntry - Supplies the hook entry
BufferSize - Supplies the available memory in the output buffer
HookSize - Receives the actual size of the hook data
Buffer - If specified, contains the hook information
Return Value:
NTSTATUS.
--*/
{
ULONG_PTR HookRva = HookEntry->HookRva; // in target image
ULONG_PTR HotpRva = HookEntry->HotpRva; // in hotpatch image
PIMAGE_NT_HEADERS NtHotpatchHeader;
PIMAGE_NT_HEADERS NtTargetHeader;
PUCHAR HookPtr;
PUCHAR HotpPtr;
NtHotpatchHeader = RtlImageNtHeader(RtlPatchData->PatchImageBase);
NtTargetHeader = RtlImageNtHeader(RtlPatchData->TargetDllBase);
if ( HookRva >= NtTargetHeader->OptionalHeader.SizeOfImage ){
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Invalid hotpatch hook pointer\n" );
return STATUS_INVALID_IMAGE_FORMAT;
}
HookPtr = (PUCHAR) RtlPatchData->TargetDllBase + HookRva;
HotpPtr = (PUCHAR) RtlPatchData->PatchImageBase + HotpRva;
switch ( HookEntry->HookType )
{
case HOTP_Hook_X86_JMP:
{
ULONG OrigSize = HookEntry->HookOptions & 0x000F;
*HookSize = ( OrigSize > 5 ) ? OrigSize : 5;
if ( HotpRva >= NtHotpatchHeader->OptionalHeader.SizeOfImage ){
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Invalid hotpatch relative address\n" );
return STATUS_INVALID_IMAGE_FORMAT;
}
if (ARGUMENT_PRESENT(Buffer)) {
ULONG i;
LONG PcRelDisp = (LONG)( HotpPtr - ( HookPtr + 5 ));
PUCHAR pb = Buffer;
if (BufferSize < *HookSize) {
return STATUS_BUFFER_TOO_SMALL;
}
*pb++ = 0xE9;
*(LONG UNALIGNED*)pb = PcRelDisp;
if ( *HookSize > 5 )
{
ULONG FillBytes = *HookSize - 5;
pb += 4;
do
{
*pb++ = 0xCC;
}
while ( --FillBytes );
}
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"\t%08I64X: jmp %08X (PC+%08X) {",
(ULONGLONG)HookPtr,
(ULONG)(ULONGLONG)HotpPtr,
PcRelDisp );
for ( i = 0; i < *HookSize; i += 1 )
{
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
" %02X",
HookPtr[ i ] );
}
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
" }\n" );
}
break;
}
case HOTP_Hook_X86_JMP2B:
{
ULONG OrigSize = HookEntry->HookOptions & 0x000F;
*HookSize = ( OrigSize > 2 ) ? OrigSize : 2;
if (ARGUMENT_PRESENT(Buffer)) {
PUCHAR pb = Buffer;
if (BufferSize < *HookSize) {
return STATUS_BUFFER_TOO_SMALL;
}
*pb++ = 0xEB;
*pb++ = (UCHAR) (HookEntry->HotpRva & 0x000000FF);
if ( *HookSize > 2 )
{
ULONG FillBytes = *HookSize - 2;
do
{
*pb++ = 0xCC;
}
while ( --FillBytes );
}
}
break;
}
case HOTP_Hook_VA32:
{
*HookSize = 4;
if ( HotpRva >= NtHotpatchHeader->OptionalHeader.SizeOfImage ){
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Invalid hotpatch relative address\n" );
return STATUS_INVALID_IMAGE_FORMAT;
}
if ( ARGUMENT_PRESENT(Buffer) ) {
if (BufferSize < *HookSize) {
return STATUS_BUFFER_TOO_SMALL;
}
*(ULONG UNALIGNED*)Buffer = (ULONG)(ULONG_PTR)HotpPtr;
}
break;
}
case HOTP_Hook_VA64:
{
*HookSize = 8;
if (ARGUMENT_PRESENT(Buffer)) {
if (BufferSize < *HookSize) {
return STATUS_BUFFER_TOO_SMALL;
}
*(ULONG64 UNALIGNED*)Buffer = (ULONG64)HotpPtr;
}
break;
}
default:
{
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Invalid hook type specified\n" );
return STATUS_NOT_IMPLEMENTED; // not implemented
}
}
return STATUS_SUCCESS;
}
NTSTATUS
RtlReadHookInformation(
IN PRTL_PATCH_HEADER RtlPatchData
)
/*++
Routine Description:
Utility function to read all hook information contained in
the hotpatch and initialize the SYSTEM_HOTPATCH_CODE_INFORMATION
structure
Arguments:
RtlPatchData - Supplies the rtl hotpatch information
Return Value:
NTSTATUS.
--*/
{
ULONG_PTR HookArrayRva = RtlPatchData->HotpatchHeader->HookArrayRva;
ULONG HookCount = RtlPatchData->HotpatchHeader->HookCount;
ULONG HookArraySize = HookCount * sizeof( HOTPATCH_HOOK );
PIMAGE_NT_HEADERS NtHotpatchHeader;
PHOTPATCH_HOOK HookArray;
ULONG DescriptorsSize;
ULONG i;
PSYSTEM_HOTPATCH_CODE_INFORMATION Buffer;
NTSTATUS Status;
ULONG CodeOffset;
ULONG BufferSize;
NtHotpatchHeader = RtlImageNtHeader(RtlPatchData->PatchImageBase);
if ( HookCount == 0 ) {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"No hooks defined in hotpatch\n" );
return STATUS_INVALID_IMAGE_FORMAT;
}
if (( HookArrayRva == 0 ) ||
( HookArrayRva >= NtHotpatchHeader->OptionalHeader.SizeOfImage ) ||
(( HookArrayRva + HookArraySize ) >= NtHotpatchHeader->OptionalHeader.SizeOfImage )) {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Invalid hotpatch hook array pointer\n" );
return STATUS_INVALID_IMAGE_FORMAT;
}
HookArray = (PHOTPATCH_HOOK)( (PCHAR)RtlPatchData->PatchImageBase + HookArrayRva );
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"Inserting %u hooks into target image\n",
HookCount );
//
// Walk the hook information to determine the size of the buffer
//
DescriptorsSize = sizeof(SYSTEM_HOTPATCH_CODE_INFORMATION) + (HookCount - 1) * sizeof(HOTPATCH_HOOK_DESCRIPTOR);
CodeOffset = DescriptorsSize;
for ( i = 0; i < HookCount; i += 1 ) {
Status = RtlpReadSingleHookInformation( RtlPatchData,
&HookArray[ i ],
0,
&BufferSize,
NULL );
if (!NT_SUCCESS(Status)) {
return Status;
}
DescriptorsSize += 2 * BufferSize; // add space for the original code too
if ( HookArray[ i ].ValidationRva != 0 ) {
Status = RtlReadSingleHookValidation( RtlPatchData,
&HookArray[ i ],
0,
&BufferSize,
NULL,
NULL,
0 );
if (!NT_SUCCESS(Status)) {
return Status;
}
DescriptorsSize += BufferSize;
}
}
//
// Now that we have size, we can allocate the buffer and fill up
// with the hook information
//
Buffer = RtlpAllocateHotpatchMemory(DescriptorsSize, TRUE);
if (Buffer == NULL) {
return STATUS_NO_MEMORY;
}
Buffer->InfoSize = DescriptorsSize;
Buffer->CodeInfo.DescriptorsCount = HookCount;
Buffer->Flags = 0;
for (i = 0; i < Buffer->CodeInfo.DescriptorsCount; i += 1) {
PUCHAR OrigCodeBuffer;
Buffer->CodeInfo.CodeDescriptors[i].TargetAddress = (ULONG_PTR)RtlPatchData->TargetDllBase + HookArray[ i ].HookRva;
Buffer->CodeInfo.CodeDescriptors[i].CodeOffset = CodeOffset;
Status = RtlpReadSingleHookInformation( RtlPatchData,
&HookArray[ i ],
DescriptorsSize - CodeOffset,
&BufferSize,
(PUCHAR)Buffer + CodeOffset );
if (!NT_SUCCESS(Status)) {
RtlpFreeHotpatchMemory(Buffer);
return Status;
}
Buffer->CodeInfo.CodeDescriptors[i].CodeSize = BufferSize;
CodeOffset += BufferSize;
Buffer->CodeInfo.CodeDescriptors[i].OrigCodeOffset = CodeOffset;
OrigCodeBuffer = (PUCHAR)Buffer + CodeOffset;
RtlCopyMemory (OrigCodeBuffer,
(PVOID)Buffer->CodeInfo.CodeDescriptors[i].TargetAddress,
Buffer->CodeInfo.CodeDescriptors[i].CodeSize );
CodeOffset += BufferSize;
if ( HookArray[ i ].ValidationRva != 0 ) {
Buffer->CodeInfo.CodeDescriptors[i].ValidationOffset = CodeOffset;
Status = RtlReadSingleHookValidation( RtlPatchData,
&HookArray[ i ],
DescriptorsSize - CodeOffset,
&BufferSize,
(PUCHAR)Buffer + CodeOffset,
OrigCodeBuffer,
Buffer->CodeInfo.CodeDescriptors[i].CodeSize );
if (!NT_SUCCESS(Status)) {
RtlpFreeHotpatchMemory(Buffer);
return Status;
}
CodeOffset += BufferSize;
Buffer->CodeInfo.CodeDescriptors[i].ValidationSize = BufferSize;
} else {
Buffer->CodeInfo.CodeDescriptors[i].ValidationOffset = 0;
Buffer->CodeInfo.CodeDescriptors[i].ValidationSize = 0;
}
}
if (RtlPatchData->CodeInfo) {
RtlpFreeHotpatchMemory(RtlPatchData->CodeInfo);
}
RtlPatchData->CodeInfo = Buffer;
return STATUS_SUCCESS;
}
NTSTATUS
RtlInitializeHotPatch (
IN PRTL_PATCH_HEADER RtlPatchData,
IN ULONG_PTR PatchOffsetCorrection
)
/*++
Routine Description:
This routine is used to initialize the hotpatch module by applying
the module fixups, validate the target module and initialize the
hook data.
Arguments:
RtlPatchData - Supplies the rtl hotpatch structure
PatchOffsetCorrection - Correction for the offset where the fixups are applied.
The fixups are done in locked pages therefore the address being modified is
different than the actual address (where the module is loaded)
Return Value:
None.
--*/
{
NTSTATUS Status;
Status = RtlpApplyRelocationFixups ( RtlPatchData, PatchOffsetCorrection );
if (!NT_SUCCESS(Status)) {
return Status;
}
Status = RtlpValidateTargetRanges( RtlPatchData, TRUE );
if (!NT_SUCCESS(Status)) {
return Status;
}
Status = RtlReadHookInformation( RtlPatchData );
return Status;
}
BOOLEAN
RtlpNormalizePeHeaderForIdHash(
PIMAGE_NT_HEADERS NtHeader
)
/*++
Routine Description:
The purpose of this routine is to zero out (normalize) the PE header
fields that may be modified during rebase/bind/winalign/localize/etc
that don't materially affect the functionality of the binary and thus
the targetability of the hotpatch. The remaining fields should
uniquely identify the target binary as originating from the same
binary prior to rebase/bind/localize/etc.
Arguments:
NtHeader - Supplies the image header being normalized
Return Value:
Returns TRUE if the header is valid, FALSE otherwise
--*/
{
PIMAGE_DATA_DIRECTORY pDirs;
ULONG nDirs;
ULONG RvaResource = 0;
ULONG RvaSecurity = 0;
ULONG RvaReloc = 0;
PIMAGE_SECTION_HEADER pSect;
ULONG nSect;
ULONG ImageFileCharacteristicsIgnore = (
IMAGE_FILE_RELOCS_STRIPPED |
IMAGE_FILE_DEBUG_STRIPPED |
IMAGE_FILE_LINE_NUMS_STRIPPED |
IMAGE_FILE_LOCAL_SYMS_STRIPPED |
IMAGE_FILE_AGGRESIVE_WS_TRIM |
IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP |
IMAGE_FILE_NET_RUN_FROM_SWAP
);
NtHeader->FileHeader.Characteristics &= ~ImageFileCharacteristicsIgnore;
NtHeader->FileHeader.TimeDateStamp = 0;
NtHeader->FileHeader.PointerToSymbolTable = 0;
switch ( NtHeader->OptionalHeader.Magic )
{
case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
{
NtHeader->OptionalHeader.CheckSum = 0;
NtHeader->OptionalHeader.ImageBase = 0;
NtHeader->OptionalHeader.FileAlignment = 0;
NtHeader->OptionalHeader.SizeOfCode = 0;
NtHeader->OptionalHeader.SizeOfInitializedData = 0;
NtHeader->OptionalHeader.SizeOfUninitializedData = 0;
NtHeader->OptionalHeader.SizeOfImage = 0;
NtHeader->OptionalHeader.SizeOfHeaders = 0;
NtHeader->OptionalHeader.SizeOfStackReserve = 0;
NtHeader->OptionalHeader.SizeOfStackCommit = 0;
NtHeader->OptionalHeader.SizeOfHeapReserve = 0;
NtHeader->OptionalHeader.SizeOfHeapCommit = 0;
nDirs = NtHeader->OptionalHeader.NumberOfRvaAndSizes;
pDirs = NtHeader->OptionalHeader.DataDirectory;
break;
}
case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
{
PIMAGE_NT_HEADERS64 NtHeader64 = (PIMAGE_NT_HEADERS64) NtHeader;
NtHeader64->OptionalHeader.CheckSum = 0;
NtHeader64->OptionalHeader.ImageBase = 0;
NtHeader64->OptionalHeader.FileAlignment = 0;
NtHeader64->OptionalHeader.SizeOfCode = 0;
NtHeader64->OptionalHeader.SizeOfInitializedData = 0;
NtHeader64->OptionalHeader.SizeOfUninitializedData = 0;
NtHeader64->OptionalHeader.SizeOfImage = 0;
NtHeader64->OptionalHeader.SizeOfHeaders = 0;
NtHeader64->OptionalHeader.SizeOfStackReserve = 0;
NtHeader64->OptionalHeader.SizeOfStackCommit = 0;
NtHeader64->OptionalHeader.SizeOfHeapReserve = 0;
NtHeader64->OptionalHeader.SizeOfHeapCommit = 0;
nDirs = NtHeader64->OptionalHeader.NumberOfRvaAndSizes;
pDirs = NtHeader64->OptionalHeader.DataDirectory;
break;
}
default:
{
return FALSE;
}
}
if ( IMAGE_DIRECTORY_ENTRY_RESOURCE < nDirs )
{
RvaResource = pDirs[ IMAGE_DIRECTORY_ENTRY_RESOURCE ].VirtualAddress;
pDirs[ IMAGE_DIRECTORY_ENTRY_RESOURCE ].VirtualAddress = 0;
pDirs[ IMAGE_DIRECTORY_ENTRY_RESOURCE ].Size = 0;
}
if ( IMAGE_DIRECTORY_ENTRY_SECURITY < nDirs )
{
RvaSecurity = pDirs[ IMAGE_DIRECTORY_ENTRY_SECURITY ].VirtualAddress;
pDirs[ IMAGE_DIRECTORY_ENTRY_RESOURCE ].VirtualAddress = 0;
pDirs[ IMAGE_DIRECTORY_ENTRY_RESOURCE ].Size = 0;
}
if ( IMAGE_DIRECTORY_ENTRY_BASERELOC < nDirs )
{
RvaReloc = pDirs[ IMAGE_DIRECTORY_ENTRY_BASERELOC ].VirtualAddress;
pDirs[ IMAGE_DIRECTORY_ENTRY_BASERELOC ].VirtualAddress = 0;
pDirs[ IMAGE_DIRECTORY_ENTRY_BASERELOC ].Size = 0;
}
if ( IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT < nDirs )
{
pDirs[ IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT ].VirtualAddress = 0;
pDirs[ IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT ].Size = 0;
}
if ( IMAGE_DIRECTORY_ENTRY_DEBUG < nDirs )
{
pDirs[ IMAGE_DIRECTORY_ENTRY_DEBUG ].Size = 0;
}
pSect = IMAGE_FIRST_SECTION( NtHeader );
nSect = NtHeader->FileHeader.NumberOfSections;
while ( nSect-- )
{
pSect->SizeOfRawData = 0;
pSect->PointerToRawData = 0;
pSect->PointerToRelocations = 0;
pSect->PointerToLinenumbers = 0;
pSect->NumberOfRelocations = 0;
pSect->NumberOfLinenumbers = 0;
if (( pSect->VirtualAddress == RvaResource ) ||
( pSect->VirtualAddress == RvaSecurity ) ||
( pSect->VirtualAddress == RvaReloc ))
{
pSect->VirtualAddress = 0;
pSect->Misc.VirtualSize = 0;
}
pSect++;
}
return TRUE;
}
NTSTATUS
RtlpCopyAndNormalizePeHeaderForHash(
IN PRTL_PATCH_HEADER RtlPatchData,
IN PVOID TargetDllBase,
IN PUCHAR Buffer,
IN SIZE_T BufferSize,
OUT PSIZE_T ActualSize )
/*++
Routine Description:
Utility function which copies a normalized image header to a
supplied buffer.
Arguments:
RtlPatchData - Supplies the rtl hotpatch structure
TargetDllBase - Supplies the base address of the target image
Buffer - Supplies the buffer, which receives the copy of
the normalized header
BufferSize - Supplies the size of the allocated buffer
ActualSize - Receives the actual size of the buffer needed
for this copy. If the function fails, the caller can use
value for the next retry.
Return Value:
NTSTATUS
--*/
{
PIMAGE_NT_HEADERS NtHeaders;
PUCHAR pbHeader;
PIMAGE_SECTION_HEADER NtSection;
ULONG SectionCount;
PUCHAR BytesExtent;
SIZE_T HeaderSize;
NTSTATUS Status;
NtHeaders = RtlImageNtHeader(TargetDllBase);
pbHeader = (PUCHAR)NtHeaders;
NtSection = IMAGE_FIRST_SECTION( NtHeaders );
SectionCount = NtHeaders->FileHeader.NumberOfSections;
BytesExtent = (PUCHAR) &NtSection[ SectionCount ];
HeaderSize = ( BytesExtent - pbHeader );
*ActualSize = HeaderSize;
if ( HeaderSize <= BufferSize ) {
//
// The buffer is large enough to hold the header.
// Copy the header and clear the fields likely to
// change with localization.
//
memcpy( Buffer, pbHeader, HeaderSize );
if ( RtlpNormalizePeHeaderForIdHash( (PIMAGE_NT_HEADERS) Buffer ) ) {
Status = STATUS_SUCCESS;
} else {
//
// The PE header does not look like a valid
//
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"Failed to normalize PE header for validation\n" );
Status = STATUS_INVALID_IMAGE_FORMAT;
}
} else {
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"Header too large (%u>%u) for copy/normalize/validate\n",
HeaderSize,
BufferSize );
Status = STATUS_BUFFER_TOO_SMALL;
}
return Status;
}
ULONGLONG
RtlpPeHeaderHash2(
IN PUCHAR Buffer,
IN SIZE_T BufferSize )
/*++
Routine Description:
This function implements a 64 bit hashing algorithm,
used by the hotpatch to validate regions of the headers that
do not change with localization.
Arguments:
Buffer - Supplies the buffer for wich the hash has to be determined.
BufferSize - Supplies the size of the buffer
Return Value:
Returns the 64-bit hash value of this buffer
--*/
{
PULONG ULongBuffer = (PULONG) Buffer;
SIZE_T Count = BufferSize / sizeof( ULONG );
ULONGLONG Hash = ~(ULONGLONG)BufferSize;
while ( Count-- )
{
ULONG Next = *ULongBuffer++ ^ 0x55555555;
ULONGLONG Temp = (ULONGLONG)Next * ((ULONG)Hash);
Hash = _rotl64( Hash, 23 ) ^ Temp;
}
return Hash;
}
PHOTPATCH_DEBUG_DATA
RtlpGetColdpatchDebugSignature(
PVOID TargetDllBase
)
/*++
Routine Description:
This procedure retrieves the 64 bit hash information for the
original file, if the target is a coldpatch
Arguments:
TargetDllBase - Supplies the base address of the target binary
HashValue - Receives the 64bit hash value of the original file,
if the function suceeds.
Return Value:
BOOLEAN
--*/
{
PIMAGE_DEBUG_DIRECTORY DebugData;
ULONG DebugSize, i;
PIMAGE_NT_HEADERS NtTargetHeader;
NtTargetHeader = RtlImageNtHeader(TargetDllBase);
DebugData = RtlImageDirectoryEntryToData( TargetDllBase,
TRUE,
IMAGE_DIRECTORY_ENTRY_DEBUG,
&DebugSize);
if ((DebugData == NULL)
||
(DebugSize < sizeof(IMAGE_DEBUG_DIRECTORY))
||
(DebugSize % sizeof(IMAGE_DEBUG_DIRECTORY))) {
return NULL;
}
for (i = 0; i < DebugSize / sizeof(IMAGE_DEBUG_DIRECTORY); i++) {
if (DebugData->Type == IMAGE_DEBUG_TYPE_RESERVED10) {
if (DebugData->AddressOfRawData < (NtTargetHeader->OptionalHeader.SizeOfImage - HASH_INFO_SIZE)) {
PHOTPATCH_DEBUG_SIGNATURE DebugSignature = (PHOTPATCH_DEBUG_SIGNATURE)((PCHAR)TargetDllBase
+ DebugData->AddressOfRawData);
if ( DebugSignature->Signature == COLDPATCH_SIGNATURE) {
return (PHOTPATCH_DEBUG_DATA)(DebugSignature + 1);
}
}
}
DebugData += 1;
}
return NULL;
}
BOOLEAN
RtlpValidatePeHeaderHash2(
IN PRTL_PATCH_HEADER RtlPatchData,
IN PVOID TargetDllBase
)
/*++
Routine Description:
The hotpatch mechanism uses this function to validate the PE header
for the target module. The hashing algorithm is applied to a
normalized header (where the fields likely to change with localization
are cleared).
Arguments:
RtlPatchData - Supplies the rtl hotpatch structure
TargetDllBase - Supplies the base address for the target module
Return Value:
Returns the 64-bit hash value of this buffer
--*/
{
PUCHAR Buffer;
SIZE_T BufferSize = 0x300; // some initial size large enough to cover most cases
SIZE_T ActualSize;
NTSTATUS Status;
Buffer = RtlpAllocateHotpatchMemory(BufferSize, FALSE);
if (Buffer) {
Status = RtlpCopyAndNormalizePeHeaderForHash( RtlPatchData,
TargetDllBase,
Buffer,
BufferSize,
&ActualSize);
if (Status == STATUS_BUFFER_TOO_SMALL) {
BufferSize = ActualSize;
RtlpFreeHotpatchMemory( Buffer );
Buffer = RtlpAllocateHotpatchMemory( BufferSize, FALSE);
if (Buffer == NULL) {
return FALSE;
}
Status = RtlpCopyAndNormalizePeHeaderForHash( RtlPatchData,
TargetDllBase,
Buffer,
BufferSize,
&ActualSize);
}
if ( NT_SUCCESS(Status) ) {
ULONGLONG HashValue = RtlpPeHeaderHash2( Buffer, (ULONG)ActualSize);
if ( RtlPatchData->HotpatchHeader->TargetModuleIdValue.Quad == HashValue ) {
RtlpFreeHotpatchMemory( Buffer );
return TRUE;
} else {
PHOTPATCH_DEBUG_DATA DebugSignature = RtlpGetColdpatchDebugSignature(TargetDllBase);
if (DebugSignature) {
if ( RtlPatchData->HotpatchHeader->TargetModuleIdValue.Quad == DebugSignature->PEHashData ) {
RtlPatchData->PatchFlags |= FLGP_COLDPATCH_TARGET;
RtlpFreeHotpatchMemory( Buffer );
return TRUE;
}
}
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_ERROR_LEVEL,
"PE header hash ID comparsion failure (PE2)\n" );
}
}
RtlpFreeHotpatchMemory( Buffer );
}
return FALSE;
}
BOOLEAN
RtlpValidatePeChecksum(
IN PRTL_PATCH_HEADER RtlPatchData,
IN PVOID TargetDllBase
)
{
PIMAGE_NT_HEADERS NtHeader;
PHOTPATCH_DEBUG_DATA DebugSignature;
NtHeader = RtlImageNtHeader(TargetDllBase);
switch ( NtHeader->OptionalHeader.Magic )
{
case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
if (RtlPatchData->HotpatchHeader->TargetModuleIdValue.Quad ==
NtHeader->OptionalHeader.CheckSum) {
return TRUE;
}
break;
case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
{
PIMAGE_NT_HEADERS64 NtHeader64 = (PIMAGE_NT_HEADERS64) NtHeader;
if (RtlPatchData->HotpatchHeader->TargetModuleIdValue.Quad ==
NtHeader64->OptionalHeader.CheckSum) {
return TRUE;
}
break;
}
default:
{
return FALSE;
}
}
//
// we failed to check directly the image checksum. We need to
// see whether this is a coldpatch and it has the original checksum correct
//
DebugSignature = RtlpGetColdpatchDebugSignature(TargetDllBase);
if (DebugSignature) {
if ( RtlPatchData->HotpatchHeader->TargetModuleIdValue.Quad == DebugSignature->ChecksumData ) {
RtlPatchData->PatchFlags |= FLGP_COLDPATCH_TARGET;
return TRUE;
}
}
return FALSE;
}
BOOLEAN
RtlpValidateTargetModule(
IN PRTL_PATCH_HEADER RtlPatchData,
IN PPATCH_LDR_DATA_TABLE_ENTRY LdrDataEntry
)
/*++
Routine Description:
This routine checks whether the target binary matches
the hotpatch data.
Arguments:
RtlPatchData - Supplies the rtl hotpatch structure
LdrDataEntry - The loader entry for the target binary.
Return Value:
TRUE if successfully matches, FALSE otherwise.
--*/
{
switch ( RtlPatchData->HotpatchHeader->ModuleIdMethod ){
case HOTP_ID_None: // No ID verification of target module
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"HOTP_ID_None\n" );
return TRUE;
case HOTP_ID_PeHeaderHash1: // MD5 of "normalized" IMAGE_NT_HEADERS32/64
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"HOTP_ID_PeHeaderHash1" );
return FALSE; // not yet implemented
case HOTP_ID_PeDebugSignature: // pdb signature (GUID,Age)
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"HOTP_ID_PeDebugSignature" );
return FALSE; // not yet implemented
case HOTP_ID_PeHeaderHash2: // 64-bit hash of "normalized" IMAGE_NT_HEADERS32/64
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"HOTP_ID_PeHeaderHash2" );
return RtlpValidatePeHeaderHash2( RtlPatchData, LdrDataEntry->DllBase );
case HOTP_ID_PeChecksum:
return RtlpValidatePeChecksum(RtlPatchData, LdrDataEntry->DllBase);
default: // unrecognized ModuleIdMethod
DbgPrintEx( DPFLTR_LDR_ID,
DPFLTR_TRACE_LEVEL,
"Unrecognized" );
}
return FALSE;
}
BOOLEAN
RtlpIsSameImage (
IN PRTL_PATCH_HEADER RtlPatchData,
IN PPATCH_LDR_DATA_TABLE_ENTRY LdrDataEntry
)
/*++
Routine Description:
The function verifies whether the target image is the same as
the one we built the patch for.
Arguments:
RtlPatchData - Supplies the rtl patch data
LdrDataEntry - Supplies the loader entry for the target module
Return Value:
None.
--*/
{
PIMAGE_NT_HEADERS NtHeaders;
NtHeaders = RtlImageNtHeader (LdrDataEntry->DllBase);
if (NtHeaders == NULL) {
return FALSE;
}
if ( RtlEqualUnicodeString (&RtlPatchData->TargetDllName, &LdrDataEntry->BaseDllName, TRUE)
&&
RtlpValidateTargetModule(RtlPatchData, LdrDataEntry)) {
RtlPatchData->TargetLdrDataTableEntry = LdrDataEntry;
RtlPatchData->TargetDllBase = LdrDataEntry->DllBase;
return TRUE;
}
return FALSE;
}