Hook SSDT(Shadow)

[TOC]

Basic struct

struct SSDTStruct
{
    LONG* pServiceTable;
    PVOID pCounterTable;
#ifdef _WIN64
    ULONGLONG NumberOfServices;
#else
    ULONG NumberOfServices;
#endif
    PCHAR pArgumentTable;
};

Find SSDT(Shadow) base address

x86

SSDT:

SSDT Shadow:

Before dump SSDT Shadow, you have to attach to any user-mode GUI process (like winlogon.exe)

Function Index to real function address:

Find table address in a driver

Because nt!KeServiceDescriptorTable is exported by kernel, we can get its address in the program

Then we can get the address of nt!KeServiceDescriptorTableShadow by add the offset (like previously windbg shows)

Then Get the addresses of two tables

x64

SSDT:

SSDT Shadow:

Before dump SSDT Shadow, you have to attach to any user-mode GUI process (like winlogon.exe)

Function Index to real function address:

Find table address in a driver

Find nt!KeServiceDescriptorTable and nt!KeServiceDescriptorTableShadow in kernel function nt!KiSystemServiceStart

As shown above, we can search the whole kernel address from the kernel base for KiSystemServiceStart 's pattern

After find the target function, we can retrieve the address of nt!KeServiceDescriptorTableShadow in the instruction:

Then we can get the addresses of nt!KiServiceTable(SSDT) and win32k!W32pServiceTable(SSDT Shadow)

Find the base address of ntoskrnl.exe and win32k.sys

First, we use the undocumented function ZwQuerySystemInformation with parameter of SystemModuleInformation to get information of system modules

while the struct of SYSTEM_MODULE_INFORMATION is

Usually the module ntoskrnl.exe is always the first module in the buffer, which isModule[0], so:

But the position of win32k.sys in the buffer is not sure, so we have to traverse the buffer to find it by compare the FullPathName field of each SYSTEM_MODULE_ENTRY with "win32k.sys"

Find SSDT function index by function name

Index in ntdll.dll

Almost all SSDT function have a stub function which has the same name and is exported by NTDLL.DLL

eg. the first few lines of user-mode function NtCreateFile exported by NTDLL.DLL (You have to attach to user-mode process to view symbols in ntdll)

As above shows, mov eax, 52h transfers the system call number to EAX, and call syscall to trap into the kernel, then kernel will use the number 52h to find corresponding kernel function in SSDT

SO, we can get any SSDT function's index from its name by looking into its user-mode stub function exported by NTDLL.DLL!

Get SSDT index from ntdll.dll

First we must read ntdll.dll into memory

Then get the real function address of any exported function .

To do this, we have to get its Export Address Table from its PE header

and get the file offset of the function address from its name

Then get the real address of exported function in memory ( in ntdll.dll )

finally search the whole function for pattern mov eax, xx, retrieve the SSDT index

Combine index with SSDT(Shadow)

x64

x86

Shadow is similar

Hook SSDT

!!! Yon have to first disable PatchGuard or enable DebugMode on your system !!! 😀

In order to unhook SSDT in future, define some hook structs as below (HOOKOPCODES is our shellcode for x64 hook)

Disable Write Protection

Because SSDT are read-only kernel memory, so you have to disable write protection before modifying it , we use MDL to do this. Below is the helper function to copy data to any read-only kernel memory

Also, we can modify the "WP" flags of the cr0register to enable or disable kernel memory write protection

To disable WP:

there we can modify the kernel memory without access violation. 🧐

To enable WP after writing:

x86 Hook

Just replace the SSDT index with your own function

x64 Hook

In x64 platform, because realFunction = ntTable[FunctionIndex] >> 4 + ntTable, the only thing you can modify is the value of ntTable[FunctionIndex], and it's long type(32 bit), which means you can only jump within the range -2GB + ntTable ~ 2GB + ntTable, Obviously the address is inside the ntoskrnl.exe module address space. So to jump to the function located in our own module, we have to make a "indirect jump".

Jump from SSDT to our stub function, then jump to our real function. Below is the stub shellcode

As you can see, the shellcode is only 12 bytes, so we only need to find a executable memory block which is larger than 12 bytes. Well in every code page(4KB size) there are many nop instruction where we can insert our shellcode, So we try to insert the shellcode into the code page where the real kernel function to be hooked is.

Get the ntoskrnl.exe's section header first

find out in which section the real function is

then try to find a at least 12 bytes large nop block in the code page

Next we need to write the shellcode to the cave address we just got, and insert our own function address

initialize the hook struct

write to the cave address

After that, we can change the corresponding SSDT offset to point to shellcode cave.

You have to convert cave address to the relative offset to SSDT first !!

update SSDT

DONE!

Hook Shadow SSDT

The steps of hooking Shadow is similar with SSDT hook, except that you have to attach to a user-mode GUI process before getting the base address of the Shadow.

Last updated