Uninformed: Informative Information for the Uninformed

Vol 3» 2006.Jan


System Images

The protection of certain key kernel images is one of the more critical aspects of PatchGuard's protection schemes. If a driver were still able to hook functions in nt, ndis, or any other key kernel components, then PatchGuard would be mostly irrelevant. In order to address this concern, PatchGuard performs a set of operations that are intended to ensure that system images cannot be tampered with. The table in figure [*] shows which kernel images are currently protected by this scheme.

Image Name
ntoskrnl.exe
hal.dll
ndis.sys

The approach taken to protect each of these images is the same. To kick things off, the address of a symbol that resides within the image is passed to a PatchGuard sub-routine that will be referred to as nt!PgCreateImageSubContext. This routine is prototyped as shown below:

NTSTATUS PgCreateImageSubContext(
    IN PPATCHGUARD_CONTEXT ParentContext,
    IN LPVOID SymbolAddress);

For ntoskrnl.exe, the address of nt!KiFilterFiberContext is passed in as the symbol address. For hal.dll, the address of HalInitializeProcessor is passed. Finally, the address passed for ndis.sys is its entry point address which is obtained through a call to nt!GetModuleEntryPoint.

Inside nt!PgCreateImageSubContext, the basic approach taken to protect the images is through the generation of a few distinct PatchGuard sub-contexts. The first sub-context is designed to hold the checksum of an individual image's sections, with a few exceptions. The second and third sub-contexts hold the checksum of an image's Import Address Table (IAT) and Import Directory, respectively. These routines all make use of a shared routine that is responsible for generating a protection sub-context that holds the checksum for a block of memory using the random XOR key and random rotate bits stored in the parent PatchGuard context structure. The prototype for this routine is shown below:

typedef struct BLOCK_CHECKSUM_STATE
{
    ULONG   Unknown;
    ULONG64 BaseAddress;
    ULONG   BlockSize;
    ULONG   Checksum;
} BLOCK_CHECKSUM_STATE, *PBLOCK_CHECKSUM_STATE;

PPATCHGUARD_SUB_CONTEXT PgCreateBlockChecksumSubContext(
    IN PPATCHGUARD_CONTEXT Context,
    IN ULONG Unknown,
    IN PVOID BlockAddress,
    IN ULONG BlockSize,
    IN ULONG SubContextSize,
    OUT PBLOCK_CHECKSUM_STATE ChecksumState OPTIONAL);

The block checksum sub-context stores the checksum state at the end of the PATCHGUARD_CONTEXT. The checksum state is stored in a BLOCK_CHECKSUM_STATE structure. The Unknown attribute of the structure is initialized to the Unknown parameter from nt!PgCreateBlockChecksumSubContext. The purpose of this field was not deduced, but the value was set to zero during debugging.

The checksum algorithm used by the routine is fairly simple. The pseudo-code below shows how it works conceptually:

ULONG64 Checksum = Context->RandomHashXorSeed;
ULONG   Checksum32;

// Checksum 64-bit blocks
while (BlockSize >= sizeof(ULONG64))
{
    Checksum    ^= *(PULONG64)BaseAddress;
    Checksum     = RotateLeft(Checksum, Context->RandomHashRotateBits);
    BlockSize   -= sizeof(ULONG64);
    BaseAddress += sizeof(ULONG64);
}

// Checksum aligned blocks
while (BlockSize-- > 0)
{
    Checksum    ^= *(PUCHAR)BaseAddress;
    Checksum     = RotateLeft(Checksum, Context->RandomHashRotateBits);
    BaseAddress++;
}

Checksum32 = (ULONG)Checksum;

Checksum >>= 31;

do
{
    Checksum32  ^= (ULONG)Checksum;
    Checksum   >>= 31;
} while (Checksum);

The end result is that Checksum32 holds the checksum of the block which is subsequently stored in the Checksum attribute of the checksum state structure along with the original block size and block base address that were passed to the function.

For the purpose of initializing the checksum of image sections, nt!PgCreateImageSubContext calls into nt!PgCreateImageSectionSubContext which is prototyped as:

PPATCHGUARD_SUB_CONTEXT PgCreateImageSectionSubContext(
    IN PPATCHGUARD_CONTEXT ParentContext,
    IN PVOID SymbolAddress,
    IN ULONG SubContextSize,
    IN PVOID ImageBase);

This routine first checks to see if nt!KiOpPrefetchPatchCount is zero. If it is not, a block checksum context is created that does not cover all of the sections in the image3.2. Otherwise, the function appears to enumerate the various sections included in the supplied image, calculating the checksum across each. It appears to exclude checksums of sections named INIT, PAGEVRFY, PAGESPEC, and PAGEKD.

To account for an image's Import Address Table and Import Directory, nt!PgCreateImageSubContext calls nt!PgCreateBlockChecksumSubContext on the directory entries for both, but only if the directory entries exist and are valid for the supplied image.