How to Hook Win32 API With Kernel Patching


This post is about SSDT patching to perform API hooking within the kernel instead of the classic user mode hooking using remote threads and things like that.
SSDT hooking is as far as I know the lowest level technique to replace/hook/intercept/whatever API and for this reason has been used for years both by malwares writers and AV vendors.
I’m using the past tence due to the fact that on 2005 Microsoft introduced a Kernel Patching Protection ( also known as “PatchGuard” ) for 64 bit systems, making this technique uneffective in the worst case or quite harder to perform in the average case.

If you open the file ntoskrnl.exe ( located in the System32/SysWOW64 folder ) with your preferred disassembler, you will noticed a KeServiceDescriptorTable symbol being exported:

KeServiceDescriptorTable

It’s a pointer to a kernel structure defined as

1
2
3
4
5
6
7
typedef struct SystemServiceDescriptorTable
{
PULONG ServiceTableBase;
PULONG ServiceCounterTableBase;
ULONG NumberOfServices;
PUCHAR ParamTableBase;
}SSDT,*PSSDT;

It works as a big lookup table of Windows Native System Services, a list of kernel API such as NtTerminateProcess, NtLoadDriver, etc.

As you might correctly guess, if it’s exported it can be read and afterwards modified by a kernel driver with some hacks to temporary disable writing protection.

Starting from the address ServiceTableBase, each routine can be translated to its service index by the following formula:

1
(*(PULONG)((PUCHAR)Api+1))

For instance, if we wanted to obtain the service index for ZwTerminateProcess, we would do:

1
ULONG ulZwTerminateProcessNumber = (*(PULONG)((PUCHAR)ZwTerminateProcess+1))

( This would suggest that every kernel service routine has its own service number stored in 4 bytes after the first byte of its opcodes )

Once we have the correct service number for the API we want to hook, we can replace it in the descriptor table disabling write protection with the CR0 cpu register, setting the new routine address and then restoring CR0 protection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef NTSTATUS (*NtTerminateProcess_T)(HANDLE,NTSTATUS);

ULONG ulZwTerminateProcessNumber = (*(PULONG)((PUCHAR)ZwTerminateProcess+1))

// disable write protection
__asm
{
mov eax,cr0
and eax,not 0x10000
mov cr0,eax
}

// store the original address to restore it on driver unloading
NtTerminateProcess_T fnOriginalNtTerminateProcess = (NtTerminateProcess_T)*(PULONG)(ULONG)KeServiceDescriptorTable->ServiceTableBase + ulZwTerminateProcessNumber * 4;

// finally patch the table with our own function
*((PULONG)ulZwTerminateProcessNumber) = (ULONG)OurNtTerminateProcessHook;

// restore write protection
__asm
{
mov eax,cr0
or eax,0x10000
mov cr0,eax
}

As you can see we store the original function pointer, this is important since once the driver will start the unloading process, we need it to restore the original API address before closing. Forgetting about this step would lead to unpredictable behaviour ( almost surely a BSOD ) since the kernel would call an address that it’s not mapped anymore or that it’s being used by something else.

This is a short overview of this old but effective technique, which was used to protect processes from being killed hooking NtTerminateProcess for instance and filtering by PID.
Since PatchGuard has been introduced, SSDT hooking became hard to perform therefore Microsoft released a whole new set of kernel callbacks for newer systems, I will talk about this on another post.

The full source code for SSDT patching can be found here, tnx to zwclose7 from rohitab for the source code.

Become a Patron!