系统服务挂钩

来源:互联网 发布:网站app生成软件 编辑:程序博客网 时间:2024/04/28 18:50

系统服务就是由操作系统提供的一组函数,类似上层开发的Win32 API

       不仅Win32 API可以挂钩,系统服务也可以挂钩。开发者为了捕获各种事件,可以挂钩文件创建函数CreateFile,注册表访问函数RegCreateKey。使用挂钩可以改变操作系统的行为,只要适当地改变挂钩的数据结构和上下文,足够引起新的行为。例如,通过挂钩NtCreateFile系统服务,可以保护敏感文件不被打开,尽管NTFS为文件提供了用户级的安全,但这些安全措施却不能用在FAT分区上。

Kernel-Level Hooking

系统级挂钩要用驱动程序实现,此方法的缺点就是,需要确定传递给内核函数的参数,但多数情况下这些服务都是未公开的。所以,这类挂钩更难实现,但可以产生更好的效果。

User-Level Hooking

用户级的挂钩的优点是这些函数通常都是公开的,可以知道所需的参数。这类挂钩只能局限于用户模式而不能扩展到内核模式。

<!--[if !supportEmptyParas]--> <!--[endif]-->

下面主要要讨论Kernel-Level Hooking

       这方面的知识可以阅读《Undocmented Windows 2000 Secrets》,在我的BLOG上可以下载由Kendiv大牛翻译的中文版,这是我看过关于挂钩系统服务最详细的书了。另外还有一本《Undocmented Windows NT》,其中第6章专门讲述这方面知识,后者可以作为补充教材,本书同样在我的BLOG上提供下载。除了这两本书以外,网上还有一些零星的文章介绍这个,比如《剖析Windows服务调用机制》等,很容易在网上搜索得到 。如果你要彻底掌握这些知识,建议把上面提到的书籍文章通通看一看。

      我这里主要把重点提一下,以及自已学习过程中理解的东东,和理解后写出来的源码。不少文章有代码片段,但很多像我一样,刚刚接解内核开发,连DDK都没安装,看过那些代码片段总觉得太空洞。《Undocmented Windows 2000 Secrets》虽然提供代码(w2k_spy),但过于复杂,不利于初学者。 

       关于驱动程序如果编译安装,请参考有关资料。如果想要速学,建议看Undocmented Windows 2000 Secrets》第三章。接下来,假设你已经懂得如何编译安装驱动,以及挂钩系统服务的基本知识了。

<!--[if !supportEmptyParas]--> <!--[endif]-->

系统服务是如何被调用的?

系统服务的用户接口是以包装函数的形式提供的,这些包装函数都在NTDLL.dll中。这些包装函数使用INT 2E指令来切换到内核模式并执行所需的系统服务:每一种系统服务都用一个service ID的标识,包装函数将所需的系统服务的service ID送入EAX寄存器,将指向堆栈帧的指针送入EDX寄存器,然后发出INT 2E指令。INT 2E的处理程序将参数从用户模式的堆栈拷贝到内核模式的堆栈,这个由ntosknrl.exe提供的INT 2E处理程序内部被称为KiSystemService()。其中有一个系统服务分派表,表中的每一个表项都包含service ID和其对应的函数地址。每个函数的代码都位于内核之中。

挂钩系统服务最简单的方法就是修改系统服务分派表中的函数指针,使之指向由开发者插入的某个函数。要做到这点只能通过内核驱动,因为这个系统服务分派表是受操作系统保护的,这表所在的页属性被设置成只有内核程序才能读写,用户级的应用程序不能读写这些内存单元。


如何访问系统服务分派表?

2000/XP中默认存在两个系统服务分派表(SDT),它们对应了两类不同的系统服务。这两个分别是:KeServiceDescriptorTableKeServiceDescriptorTableShadowNT下只有前面那个。KeServiceDescriptorTable定义了在ntoskrln.exe中实现的系统服务,通常在kernel32.dlladvapi32.dll中提供的函数接口均是使用KeServiceDescriptorTable。同时存在的还有win32k.sys中实现的winuserGDI函数,它们是属于另一类系统服务调用,与之相对应的分派表是KeServiceDescriptorTableShadow,它提供了内核模式的USERGDI的服务。由服务KeAddSystEmServiceTable添加的服务会被同时复制到上面两个分派表中。

系统服务分派表的数据结构(SDT

typedef struct _SERVICE_DESCRIPTOR_TABLE       // SDT

{

    SYSTEM_SERVICE_TABLE         ntoskrnl ;           // ntoskrnl.exe (Nativate API)

         SYSTEM_SERVICE_TABLE         win32k ;             // win32k.sys (gdi/user support)

         SYSTEM_SERVICE_TABLE         table3 ;              // not used

         SYSTEM_SERVICE_TABLE         table4 ;              // not used

} SERVICE_DESCRIPTOR_TABLE ;

<!--[if !supportEmptyParas]--> <!--[endif]-->

typedef NTSTATUS (NTAPI *NTPROC)() ;

typedef NTPROC *PNTPROC ;

typedef struct _SYSTEM_SERVICE_TABLE // SSD

{

         PNTPROC ServiceTable ;            // array of entry points

         PULONG         CounterTable ;              // array of usage counters . be NULL

         ULONG         ServiceLimit ;               // number of table entries

         UCHAR*         ArgumentTable ;  // array of bytes counts

<!--[if !supportEmptyParas]--> <!--[endif]-->

} SYSTEM_SERVICE_TABLE

<!--[if !supportEmptyParas]--> <!--[endif]-->

访问KeServiceDescriptorTable很简单,因为它由ntoskrnl.exe公开导出。

加上 extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable; 这行就可以直接访问这个数据结构。但KeServiceDescriptorTableShadow没有导出(NT下没有此表),而且2000XP下,它的位置不一样。以后我们要挂钩的都是ntoskrnl.exe导出的函数,所以不用访问KeServiceDescriptorTableShadow 

       KeServiceDescriptorTabe只有第1个成员 ntoskrnl 有用到,其它3个成员全部为NULL KeServiceDescriptorTabeShadow前面2个成员都用到,它们的ntoskrnl成员都是相同的,差别在后者多了一个有效的win32k成员,也就是win32k.sys的系统服务分派表。在2000 KeServiceDescriptorTableShadow=KeServiceDescriptorTable+0xE0;

XP下,KeServiceDescriptorTableShadow=KeServiceDescriptorTable-0x40

      

如何得到要挂钩服务的ID

       Ntosknrl.exe的服务都保存在SYSTEM_SERVICE_TABLE.ServiceTable中,该成员是个线性数组,每个项保存一个服务的地址指针,只要把这个地址换成我们提供的函数地址指针就OK了。我们怎么知道要挂钩那个服务在数组中的ID(索引)呢?

ULONG ServiceIdFromFn (PVOID pfnHandler)

{

         PUCHAR         pbHandler = (PUCHAR) pfnHandler ;     

         ULONG ulService = * ((PULONG)(pbHandler + 1));              

         return ulService;

}

由这个服务函数的地址可以直接转化成ID ,如 ServiceIdFromFn( ZwCreateFile )就是返回 ZwCreateFile ID。而且,从这个ID我们就可以判断这个服务是由ntosknrl.exe,还是win32k.sys提供的。看ID二进制的 13,14,如果全为0表示由ntosknrl.exe提供,如果13位为114位为0表示由win32k.sys提供。

<!--[if !supportEmptyParas]--> <!--[endif]-->

例子:

理论看得再多,还不如读一下代码!有的时候我更喜欢直接看代码。

我提供的代码再简洁不过,没有多余的代码,一切都只为了实现 ZwCreateFile 的挂钩。我的ZwCreateFile只是把文件名称打印出来,然后直接调用真正的ZwCreateFile 。如果你不熟悉驱动安装,又想检验一下实践的成果,可以:

<!--[if !supportLists]-->1. <!--[endif]-->hooksys.sys文件复制到 c:/windows/system32/drivers

<!--[if !supportLists]-->2. <!--[endif]-->在命令提示符下,执行w2k_load c:/windows/system32/drivers/hooksys.sys安装

<!--[if !supportLists]-->3. <!--[endif]-->运行dbgview.exe ,我的BLOG上有提供下载。

<!--[if !supportLists]-->4. <!--[endif]-->在命令提示符下,执行net start hooksys 启动驱动

<!--[if !supportLists]-->5. <!--[endif]-->通过dbgview.exe 观察文件的创建情况

<!--[if !supportLists]-->6. <!--[endif]-->在命令提示符下,执行net stop hooksys 停止驱动

<!--[if !supportLists]-->7. <!--[endif]-->删除 hooksys.sys

<!--[if !supportEmptyParas]--> <!--[endif]-->

本代码在 XP sp2 下执行通过。例子下载

 
原创粉丝点击