我们知道在ssdt中可以被hook,那么如何检测出来自己的ssdt被hook了呢?
在内核文件中保留了一份原始的ssdt表,我们只要通过检测内核文件即可获取到原始的ssdt表即可
首先先要确认自己的内核使用的文件是哪个?
是ntkrnlmp.exe还是ntkrnlpa.exe?
这个可以通过ZwQuerySystemInformation传入SystemModuleInformation(11)得到系统模块列表,第一个模块就是系统当前使用的内核模块了
NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)GetProcAddress(hNtDll, "NtQuerySystemInformation");
if (NtQuerySystemInformation == NULL)
{
printf("GetProcAddress for NtQuerySystemInformation Error: %d\n", GetLastError());
__leave;
}
lpSystemInfo = (LPVOID)malloc(dwNumberBytes);
Status = NtQuerySystemInformation(11,
lpSystemInfo,
dwNumberBytes,
&dwReturnLength);
PMODULES module = (PMODULES)lpSystemInfo;
DWORD imageBase = (DWORD)module->smi.Base;
char * pKernelName = module->smi.ModuleNameOffset + module->smi.ImageName;
if (Status == STATUS_INFO_LENGTH_MISMATCH)
{
printf("STATUS_INFO_LENGTH_MISMATCH\n");
__leave;
}
else if (Status != STATUS_SUCCESS)
{
printf("NtQuerySystemInformation Error: %d\n", GetLastError());
__leave;
}
strcpy_s(*name, strlen(pKernelName) + 1, pKernelName);
这样我们就获取了当前使用的内核文件了
通过手动加载这个内核文件找到KeServiceDescriptorTable的地址,使用windbg可以直接显示出KeServiceDescriptorTable,是因为内核代码初始化了这个东西,所以我们需要找到谁初始化了这个KeServiceDescriptorTable,初始化KeServiceDescriptorTable的是我们的KiInitSystem函数
VOID KiInitSystem (VOID)
{
ULONG Index;
// 初始化调度队列链表头,每一个优先级都有一个独立的进程链表
for (Index = 0; Index < MAXIMUM_PRIORITY; Index += 1) {
InitializeListHead(&KiDispatcherReadyListHead[Index]);
}
// 初始化BugCheck回调函数链表,及其旋转锁
InitializeListHead(&KeBugCheckCallbackListHead);
KeInitializeSpinLock(&KeBugCheckCallbackLock);
// 初始化定时器过期的DPC对象
KeInitializeDpc(&KiTimerExpireDpc,
(PKDEFERRED_ROUTINE)KiTimerExpiration, NIL);
// 初始化profile链表,及其旋转锁
KeInitializeSpinLock(&KiProfileLock);
InitializeListHead(&KiProfileListHead);
// 初始化当前活动的profile链表
InitializeListHead(&KiProfileSourceListHead);
// 初始化定时器链表
for (Index = 0; Index < TIMER_TABLE_SIZE; Index += 1) {
InitializeListHead(&KiTimerTableListHead[Index]);
}
// 初始化swap通知事件
KeInitializeEvent(&KiSwapEvent,SynchronizationEvent,FALSE);
InitializeListHead(&KiProcessInSwapListHead);
InitializeListHead(&KiProcessOutSwapListHead);
InitializeListHead(&KiStackInSwapListHead);
InitializeListHead(&KiWaitInListHead);
InitializeListHead(&KiWaitOutListHead);
// 初始化SSDT
**KeServiceDescriptorTable[0].Base = &KiServiceTable[0];**
KeServiceDescriptorTable[0].Count = NULL;
KeServiceDescriptorTable[0].Limit = KiServiceLimit;
#if defined(_IA64_)
KeServiceDescriptorTable[0].TableBaseGpOffset =
(LONG)(*(KiServiceTable-1) - (ULONG_PTR)KiServiceTable);
#endif
KeServiceDescriptorTable[0].Number = &KiArgumentTable[0];
for (Index = 1; Index < NUMBER_SERVICE_TABLES; Index += 1) {
KeServiceDescriptorTable[Index].Limit = 0;
}
// 拷贝SSDT到Shadow服务表
RtlCopyMemory(KeServiceDescriptorTableShadow,
KeServiceDescriptorTable,
sizeof(KeServiceDescriptorTable));
// ……
return;
}
注意到这里
KeServiceDescriptorTable[0].Base = &KiServiceTable[0]
我们反汇编一下
我们可以通过硬编码确定这个位置
如何找到呢?
通过我们的PE文件的重定位段 reloc解析,解析每个需要重定位的地址地址前两个值是否为0x05C7
这样就可以找到我们的KiServiceTable了
下面我们看一下KiServiceTable里面的内容是什么?
这里就是我们所有的函数了对比一下Kernel Detective获取到的
为啥会不一样呢?
因为我们获取的地址还要-我们的PE文件的ImageBase+ntkrnlpa.exe加载的基地址才是上图的地址了
现在我们获取了函数原始数据,那么如何将每个函数名跟函数对应的函数地址对应起来呢?
要解决这个问题需要找到我们的ntdll.dll
通过解析这个dll获取到函数名,在这个dll中我们需要的函数名都是以函数名导出的,我们需要找到所有以Nt开头的函数名,通过这个函数名查看具体的函数
可以看到我们第一个函数
注意这句
mov eax,0
就是说0对应的是我们第0个函数
像这种函数调用都是通过
mov eax,?
通过后面的值进行调用第几个函数所以我们通过这里可以查找出每个函数名和对应的函数编号了特征值就是
B8
后面的就是我们具体的函数了
最后一步就是从内核中获取我们的KeServiceDescriptorTable中的函数,并且将基地址返回给ring3的程序进行比较看是否不同,不同我们就认为他的函数地址被hook了
程序中显示的加载的文件是如何做到的呢?
原理是通过枚举所有的驱动,将所有的函数地址跟他们进行比较看是在哪个驱动中,将地址传输到ring3的程序中
ps 查找驱动没写,需要的可以自己写一下,这里只是提一个思路
如何修复这个表呢?
就是将我们得到的原始表数据写入到这个KeServiceDescriptorTable中即可
ps写入的代码没有写,需要的可以自己写一下。
后面是原始函数地址,内核函数地址还没有输出,改一下代码即可输出来了
这个就是如何检测SSDT是否被HOOK的原理了
代码地址
http://download.csdn.net/detail/qq_15069267/9674966
需要注意的是我写的是控制台的程序,mfc是真的不会写啊啊啊,写出来也很丑。