0x00 Hook技术
hook技术分为两块:
- Ring3层的Hook,俗称应用层hook技术
- Ring0层的Hook,俗称内核层Hook技术
Ring3层的hook又分为两种类型
- Windows消息的hook
- WindowsAPI的hook
如下图所示:
hook1 hook2 hook1关于Windows消息的Hook,相信很多朋友都有接触过的,因为一个SetWindowsHookEx即可以完成消息 Hook,在这里简要介绍一下消息 Hook,消息 Hook 是通过SetWindowsHookEx可以实现将自己的钩子插入到钩子链的最前端,而对于发送给被 Hook 的窗口(也有可能是所有的窗口,即全局 Hook)的消息都会被我们的钩子处理函数所捕获到,也就是我们可以优先于窗体先捕获到这些消息,Windows 消息 Hook 可以实现为进程内消息 Hook 和全局消息 Hook,对于进程内消息 Hook,则可以简单的将 Hook 处理函数直接写在这个进程内,即是自己 Hook 自己,而对于用途更为广泛的全局消息 Hook,则需要将 Hook 处理函数写在一个 DLL 中,这样才可以让你的处理函数被所有的进程所加载(进程自动加载包含 Hook 消息处理函数的 DLL)。
对于 Windows 消息 Hook 呢,可以有个简单的邪恶应用,就是记录键盘按键消息,从而达到监视用户输入的键值信息的目的,这样,对于一些简单的用户通过键盘输入的密码就可以被 Hook 获取到,因为没当用户按下一个键时,Windows 都会产生一个按键消息(当然有按下,弹起等消息的区分),然后我们可以 Hook 到这个按键消息,这样就可以在 Hook 的消息处理函数中获取到用户按下的是什么键了。
不过消息的hook不是本文的重点。
本文要讲的SSDT hook呢其实是属于内核Hook,常见于病毒以及杀软中。
下图展示了内核hook的几个基本类型。
Kernel hook0x01 SSDT简介
SSDT全称System Service Descriptor Table(系统描述符表),这个表用于将Ring3的Win32API和内核的API联系起来。
SSDT并不仅仅只包含一个庞大的地址索引表,它还包含一些有用的信息,如地址索引的基地址,服务函数的个数等。通过修改此表可以达到对一些关心的系统动作进行过滤以及监控的目的。
在NT4.0的windows操作系统中,默认存在两个系统服务描述符表,这两个描述符表对应了两类不同的系统服务,这两个表为:KeServiceDescriptorTable(SSDT)和KeServiceDescriptorTable(SSDT Shadow)。其中SSDT负责处理来自Ring3层的Kernel32.dll的系统调用。而SSDT Shadow则主要处理来自User32.dll和GDI32.dll的系统调用。同时SSDT在ntoskrnl.exe中是导出的,而SSDT Shadow如其名是未被Windows所导出的,而关于SSDT的全部内容都是通KeServiceDescriptorTable来完成的。
以下截图说明,KeServiceDescriptorTable是在ntoskrnl.exe中被导出的:
Export1随后我们看看看看Windows操作系统的源码中如何定义KeServiceDescriptorTable的,通过观察WRK可知,
KeServiceDescriptorTable in WRK这么看还是有点蛋疼啊。改写以下变量的名称吧。
typedef struct _KSYSTEM_SERVICE_TABLE{
PULONG ServiceTableBase; //SSDT的基地址指针
PULONG ServiceCounterTableBase; //SSDT中每个服务被调用次数表的基地址指针
ULONG NumberOfService; //服务函数个数,NumberOfService*4就是整个地址表的大小
ULONG ParamTableBase; //SSPT的基地址
}KSYSTEM_SERVICE_TABLE,*PKSYSTEM_SERVICE_TABLE;
typedef struct _KSERVICE_TABLE_DESCRIPTOR{
KSYSTEM_SERVICE_TABLE ntoskrnl; //ntoskrnl.exe的服务函数
KSYSTEM_SERVICE_TABLE win32k; //win32k.sys的服务函数(GDI32/User32)
KSYSTEM_SERVICE_TABLE notUsed1;
KSYSTEM_SERVICE_TABLE notUsed2;
}KSERVICE_TABLE_DESCRIPTOR,*PKSERVICE_TABLE_DESCRIPTOR;
//导出由ntoskrnl.exe所导出的SSDT
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
从上述介绍可知,KeServiceDescriptorTable可以看做是一个数组,是4个KSYSTEM_SERVICE_TABLE结构形成的数组,而每个KSYSTEM_SERVICE_TABLE对应一个PE文件导出的服务描述符表,根据这些服务描述符表我们可以获得更加详细的服务信息。在应用层ntdll.dll中的API在这个系统服务描述表中都存在一个与之对应的服务,当我们的应用程序调用ntdll.dll里的API时最终可以调用对应的系统服务函数,通知给内核一个索引,内核通过该索引在SSDT中查找对应的服务,内核调用服务完成应用的API调用请求。
应用层调用Win32API流程
有了以上的SSDT基础后,我们再来看看在应用层调用Win32API(主要指ntdll.dll中的API)的流程,我们主要针对ntdll.dll中的NtQuerySystemInformation这个API的调用流程来进行阐述。
这里存在四个类似的API。
4APIs再给出这些API的调用流程。
calling way这里我们可以看到ntdll.dll中的nt和zw都会进入内核层去调用ntoskrnl的zw函数,而zw最终会调用nt函数,这个函数作为内核API最终去请求系统服务的执行。
用exescope工具可以打开ntdll.dll,看到NtQuerySystemInformation以及ZwQuerySystemInformation。
ntdll2
而实质上zw和nt都是同一函数,指向同一区域,入口地址相同。
因此Ntdll.dll中的API都是对内核API的封装,当Kernel32.dll中的API通过Ntdll.dll去调用系统API时,会进行参数检查,并调用中断(int 2Eh或SysEnter),从而从Ring3进入Ring0,并将所要调用的服务号,即SSDT数组的索引值,存放进寄存器EAX中,并且将参数地址放到指定的寄存器EDX中,再复制参数到内核地址空间,根据存放在EAX中的索引值来在SSDT数组中调用指定的服务。
经过上面步骤我们来到Ring0层。使用exescope看看ntoskrnl.exe中的ZwQuerySystemInformation以及NtQuerySystemInformation。
ntoskrnl1 ntoskrnl2再反汇编ntoskrnl这个文件可以看到Zw函数中调用KiSystemService系统服务分发函数时往EAX中存放了索引号ADh。如图:
ZwQSystemInfor随后根据该索引值检索SSDT项,最后根据该SSDT项中所存放的系统服务地址来调用这个系统服务。在这里就是调用KeServiceDescriptorTable[ADh]处保存的地址对应的系统服务。那就是Ring0下的NtQuerySystemInformation。
0x02 详解SSDT
这节内我们用WinDbg来调试XP系统,借此说明SSDT是个什么鬼。
据我们上文的结构定义可知,KeServiceDescriptorTable是一个指向4个KSYSTEM_SERVICE_TABLE结构首地址的指针,以下图为例吧。
这里的0x80563520这一行就是ntoskrnl对应的服务描述符表结构KSYSTEM_SERVICE_TABLE。那么第一个32位的0x804e58a0则是对应ntoskrnl对应的KSYSTEM_SERVICE_TABLE中的SSDT Base,即服务描述符表的首地址。通过对该首地址的dump,我们可以看到许多以128位为一组排列的服务描述符,每个描述符中的第一个32位(0x80591bfb)对应着系统服务的入口地址。通过对该入口地址的反汇编,可以看到这是SSDT第一个系统服务NtAcceptConnectPort函数。如图:
NtAcceptConnectPort那么知道了SSDT首地址,同时知道了索引,那么我们就可以通过索引来找到对应的系统服务入口地址了。通过计算“SSDT中系统服务地址所在的地址 = SSDT首地址 + 4 * 索引值”,可以推算出NtQuerySystemInformation的起始地址位0x80586ff1,对该地址进行反汇编,可得下图:
NtQuerySystemInformation由此可知,SSDT就是个保存Windows系统服务地址的数组。
0x03 SSDT hook原理
从上面的分析中我们可以看到SSDT数组中保存了系统服务的地址,如Ring0下的NtQuerySystemInformation系统服务地址,就保存在KeServiceDescriptorTable[ADh]中,既然hook就是取出这个地址后替换上我们的hook函数,在hook函数中执行原函数即可。
参考文献:
http://www.cnblogs.com/boyxiao/archive/2011/09/03/2164574.html
网友评论