Then read the first bytes of the SSDT in ntoskrnl.exe file buffer. ( For simplicity, I read them in a binary file editor which is the same as in a program)
and the first 8 bytes in SSDT in ntoskrnl.exe file buffer showed above are
ULONGLONG originalBytes = 0x000000014048ECA0
Here comes the climax of the play.
which is
So we can conclude that the originalBytes in nt file is actually the default address of its corresponding kernel function .
Now we can calculate the original SSDT value of any SSDT function with its index even if SSDT has been modified in runtime.
// Get default function address with index
defaultAddress = (PULONGULONG)ssdtFileBase + SSDTIndex;
originalOffset = defaultAddress - (ntDefaultBase + tableRva);
originalValue = (LONG)(originalOffset << 4);
// Remember original value is LONG type
Combine together
So the steps to get the original value of any SSDT function is:
Calculate the RVA between them: tableRva
Read ntoskrnl.exe in memory and get its default image base address ntDefaultBase and original SSDT buffer ssdtFileBase
For any function we can calculate its default SSDT value with its SSDT index ,which is
LONG originalValue =
((PULONG_PTR)ssdtFileBase + Index - ntDefaultBase - tableRva) << 4
x86
In X86 platform the thing is very similar.
First we need to get the kernel base and SSDT base , as shown in Windbg:
0: kd> lmDmnt
Browse full module list
start end module name
83e4b000 8425d000 nt (pdb symbols) C:\Program Files (x86)\Windows Kits\10\Debuggers\x86\sym\ntkrpamp.pdb\C820DD65C4BC4499A56D7610BE16FD082\ntkrpamp.pdb
0: kd> dps nt!KeServiceDescriptorTable
83fb4b00 83ec9d9c nt!KiServiceTable
83fb4b04 00000000
83fb4b08 00000191
83fb4b0c 83eca3e4 nt!KiArgumentTable
we have
ntBase = 0x83e4b000 ;
ntTableBase = 0x83ec9d9c ;
// So the offset is
tableRva = ntTableBase - ntBase; // --> 0x7ed9c
// table offset to file base in ntoskrnl.exe file
tableOffset = RvaToOffset(tableRva); // --> 0x7e59c
Then read the nt file into memory( note that we need to get the right file path from SystemModuleInformation by ZwQuerySystemInformation)
// Get pSystemInfoBuffer by ZwQuerySystemInformation routine
PUCHAR ntFilename = pSystemInfoBuffer->Module[0].FullPathName;
PVOID pNtFileBase = ReadFile(ntFilename);
ULONG_PTR ssdtFileBase = pNtFileBase + tableOffset;
and analyze its PE header to get its ImageBase field.
Then we can calculate the original SSDT value of any SSDT function with its index.
// Get default function address with index
defaultAddress = (PULONG)ssdtFileBase + SSDTIndex;
originalValue = defaultAddress - (ntDefaultBase + tableRva);
Unhook Shadow
Unhooking Shadow SSDT is very similar to unhooking SSDT. The steps are almost the same.
x64
Calculate the RVA between them: w32kTableRva
Read win32k.sys into memory and get its default image base w32kDefaultBase and original Shadow SSDT w32kTableFileBase
For any function in Shadow SSDT, we can calculate its original value by its index, which is
LONG originalValue =
((PULONG_PTR)w32kTableFileBase + Index - w32kDefaultBase - w32kTableRva) << 4
x86
...
...
...
The original value in Shadow SSDT is
LONG originalValue =
(PULONG_PTR)w32kTableFileBase + Index - w32kDefaultBase - w32kTableRva
Then read the kernel executable to memory (just like we read ntdll.dll in ) to get original SSDT base in file ( the default path is "c:\windows\system32\ntoskrnl.exe", but to double-check, we get its image path from kernel module information )
as mentioned in , the real kernel function address equals value >> 4 plus table base address.