进程隐藏与进程保护(SSDT Hook 实现)(一)
文章目录:
1. 引子 – Hook 技术:
2. SSDT 简介:
3. 应用层调用 Win32 API 的完整执行流程:
4. 详解 SSDT:
5. SSDT Hook 原理:
6. 小结:
1. 引子 – Hook 技术:
前面一篇博文呢介绍了代码的注入技术(远程线程实现),博文地址如下:
http://www.cnblogs.com/BoyXiao/archive/2011/08/11/2134367.html
虽然代码注入是很老的技术了,但是这种技术也还是比较常见,
当然也比较好用的,比如在 Spy++ 中就使用了远程线程注入技术,
同时,如果有兴趣的阅读过 Spy++ 的源码的朋友,当然也可以在其源码中阅读到关于远程线程注入技术了。
(这篇博文虽然我会截断分为两篇博文撰写,但是博文仍然会比较长,内容其实是比较多的,覆盖面也比较广,
需要有一定耐心和基础方可阅读完,有兴趣者请自备茶水以及零食,然后慢慢阅读全文,
PS:这话引用自园子里某位园友)
(然后的话就是漫漫长夜,心情不佳,于是写了篇博文,刚好又喝了点,所以估计会有些许疏漏之处,还请见谅 ~)
在这一篇博文中呢,介绍的是一种 Hook 技术,对于 Hook 技术,可以分为两块,
第一块是在 Ring3 层的 Hook,俗称应用层 Hook 技术,
而另外一块自然是在 Ring0 层得 Hook,俗称为内核层 Hook 技术,
而在 Ring3 层的 Hook 基本上可以分为两种大的类型,
第一类即是 Windows 消息的 Hook,第二类则是 Windows API 的 Hook。
关于 Hook 的几种类型呢,下面给出几个简洁的图示:
关于 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 技术的一种,
这种技术呢,也是比较老的技术了,貌似是当年 Rootkit 起火的时候出来的,
但是 SSDT Hook 现在也还比较流行,比如在很多的杀毒软件或者安全软件里面也都会使用到 SSDT Hook 技术。
关于内核 Hook 也有几种类型,下面也给出一副图示:
上面的几种内核级 Hook 技术,在看雪啊,debugman,xfocus 上都有很多的介绍,
而我只不过是落后这些技术很多年的小辈后生,在这里也只是将自己的学习以及一些总结的经验给列出来而已,
如果有兴趣想深入了解这些内容的话,完全可以在看雪上找到资料 ~
2. SSDT 简介:
以下介绍来自百度(PS:被百度文库弄去了很多博文,这里也抄它一下):
SSDT 的全称是 System Services Descriptor Table,系统服务描述符表。
这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来。
SSDT 并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。
通过修改此表的函数地址可以对常用 Windows 函数及 API 进行 Hook,从而实现对一些关心的系统动作进行过滤、监控的目的。
一些 HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块。
在 NT 4.0 以上的 Windows 操作系统中,默认就存在两个系统服务描述表,这两个调度表对应了两类不同的系统服务,
这两个调度表为:KeServiceDescriptorTable 和 KeServiceDescriptorTableShadow,
其中 KeServiceDescriptorTable 主要是处理来自 Ring3 层得 Kernel32.dll 中的系统调用,
而 KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,
并且 KeServiceDescriptorTable 在 ntoskrnl.exe(Windows 操作系统内核文件,包括内核和执行体层)是导出的,
而 KeServiceDescriptorTableShadow 则是没有被 Windows 操作系统所导出,
而关于 SSDT 的全部内容则都是通过 KeServiceDescriptorTable 来完成的 ~
从下面的截图可以看出 KeServiceDescriptorTable 在 ntoskrnl.exe 中被导出:
然后我们再来看看在 Windows 操作系统的源码 WRK 中,KeServiceDescriptorTable 是怎么被定义的 ~
首先来看 KeServiceDescriptorTable 是如何被 Windows 操作系统源码给导出的:
从下面的截图可以看出,这个系统服务描述表是在 WRK 源码中的某一个模块划分文件(.def)中所导出的。
关于 WRK 是什么东西 ? 则可以参阅我的另一篇博文《Windows 内核(WRK)简介》,博文地址如下:
http://www.cnblogs.com/BoyXiao/archive/2011/01/08/1930904.html
而在 Windows 源码 WRK 中对于系统服务描述符表的代码定义如下(KeServiceDecriptorTable 即由该结构定义):
上面的这个结构定义在成员变量的名称上还看不出什么名堂,下面给出我们将在自己代码中所使用的结构体:
1: typedef struct _KSYSTEM_SERVICE_TABLE
2: {
3: PULONG ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址
4: PULONG ServiceCounterTableBase; // 用于 checked builds, 包含 SSDT 中每个服务被调用的次数
5: ULONG NumberOfService; // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
6: ULONG ParamTableBase; // SSPT(System Service Parameter Table)的基地址
7:
8: } KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
9:
10: typedef struct _KSERVICE_TABLE_DESCRIPTOR
11: {
12: KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe 的服务函数
13: KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 的服务函数(GDI32.dll/User32.dll 的内核支持)
14: KSYSTEM_SERVICE_TABLE notUsed1;
15: KSYSTEM_SERVICE_TABLE notUsed2;
16:
17: } KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
18:
19: //导出由 ntoskrnl.exe 所导出的 SSDT
20: extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
有了上面的介绍后,我们可以简单的将 KeServiceDescriptor 看做是一个数组了(其实质也就是个数组),
在应用层 ntdll.dll 中的 API 在这个系统服务描述表(SSDT)中都存在一个与之相对应的服务,
当我们的应用程序调用 ntdll.dll 中的 API 时,最终会调用内核中与之相对应的系统服务,
由于有了 SSDT,所以我们只需要告诉内核需要调用的服务所在 SSDT 中的索引就 OK 了,
然后内核根据这个索引值就可以在 SSDT 中找到相对应的服务了,然后再由内核调用服务完成应用程序 API 的调用请求即可。
基本结构可以参考下图:
3. 应用层调用 Win32 API 的完整执行流程:
有了上面的 SSDT 基础后,我们再来看一下在应用层调用 Win32 API(这里主要指的是 ntdll.dll 中的 API)的完整流程,
这里我们主要是分析 ntdll.dll 中的 NtQuerySystemInformation 这个 API 的调用流程,
(PS:Windows 任务管理器即是通过这个 API 来获取到系统的进程等等信息的)。
先给出一副图示(先记住这里有四个类似的 API,但是必须得注意区分开来,弄混淆了就麻烦大了):
再给出这些个 API 的基本的调用流程(让大伙有个印象,至少不会迷失):
首先,使用 PE 工具来打开 ntdll.dll 文件,可以看到 NtQuerySystemInformation,
除了 NtQuerySystemInformation 外,同时还可以看到 ZwQuerySystemInformation,
而实质上,在 Windows 操作系统中,
Ntdll.dll 中的ZwQuerySystemInformation 和 NtQuerySystemInformation 是同一函数,
可以通过下面的截图看出,这两个函数的入口地址指向同一区域,他们的函数入口地址都是一样的 ~
很奇怪吧 ~ 其实我也觉得奇怪 ~ 何必多此一举呢 ~
众所周知 Ntdll.dll 中的 API 都只不过是一个简单的包装函数而已,
当 Kernel32.dll 中的 API 通过 Ntdll.dll 时,会完成参数的检查,
再调用一个中断(int 2Eh 或者 SysEnter 指令),从而实现从 Ring3 进入 Ring0 层,
并且将所要调用的服务号(也就是在 SSDT 数组中的索引值)存放到寄存器 EAX 中,
并且将参数地址放到指定的寄存器(EDX)中,再将参数复制到内核地址空间中,
再根据存放在 EAX 中的索引值来在 SSDT 数组中调用指定的服务 ~
经过上面的步骤后,便由 Ring3 层进入了 Ring0 层,
我们再通过 PE 工具来查看 ntoskrnl.exe 中的 ZwQuerySystemInformation 和 NtQuerySystemInformation
先来看 ntoskrnl.exe 中的 ZwQuerySystemInformation:
在上面的这幅截图中,可以看到在 Ring0 下的 ZwQuerySystemInformation 将 0ADh 放入了寄存器 eax 中,
然后调用了系统服务分发函数 KiSystemService,而这个 KiSystemService 函数则是根据 eax 寄存器中的索引值,
然后再 SSDT 数组中找到索引值为 eax 寄存器中存放的值得那个 SSDT 项,
最后就是根据这个 SSDT 项中所存放的系统服务的地址来调用这个系统服务了 ~
比如在这里就是调用 KeServiceDescriptorTable[0ADh] 处所保存的地址所对应的系统服务了 ~
也就是调用 Ring0 下的 NtQuerySystemInformation 了 ~
至此,在应用层中调用 NtQuerySystemInformation 的全部流程也就结束了 ~
最后,贴出一点在 Ring0 下的 NtQuerySystemInformation 的反汇编代码:
4. 详解 SSDT:
在这一节里面,我们将来看看 SSDT 到底是个什么东西 ~ 这里使用 WinDbg 来调试 XP SP2 系统 ~
首先来看看 KeServiceDescriptorTable 是何物 ?
从下面的截图中可以看到 KeServiceDesciptorTable 的首地址为 804e58a0,
然后查看分析这个地址,可以查看到第一个系统服务的入口地址为 80591bfb !
我们再来看看 80591bfb 这个地址对应的究竟是何系统服务 ?
从下面的截图中,可以看到 SSDT 中第一个系统服务就是 NtAcceptConnectPort !!!
由于我们知道了 SSDT 的首地址,又知道了 Ring0 下 NtQuerySystemInformation 服务的索引号,
所以可以根据 “SSDT 中系统服务地址所在的 Address = SSDT 首地址 + 4 * 索引号”,
推算出 NtQuerySystemInformation 服务的地址,
因此有 Address = 804e58a0 + 4 * 0adh = 804E5B54;
然后我们再来看 804E5B54 这个地址的信息,信息如下截图:
从截图中,我们可以看到 NtQuerySystemInformation 的起始地址为 80586ff1,
下面就来验证一下地址 80586ff1 到底是不是 NtQuerySystemInformation 的首地址 ~
从下面的截图中可以肯定 80586ff1 确实就是 NtQuerySystemInformation 的首地址,
这和我们上面对 SSDT 中指定索引号的服务的地址的计算公式计算出来的结果是统一的 !!!
从上面的介绍,可以看出,其实 SSDT 就是一个用来保存 Windows 系统服务地址的数组而已 !!!
5. SSDT Hook 原理:
有了上面的这部分基础后,就可以来看 SSDT HOOK 的原理了,
其实 SSDT Hook 的原理是很简单的,从上面的分析中,
我们可以知道在 SSDT 这个数组中呢,保存了系统服务的地址,
比如对于 Ring0 下的 NtQuerySystemInformation 这个系统服务的地址,
就保存在 KeServiceDescriptorTable[0ADh] 中,
既然是 Hook 的话,我们就可以将这个 KeServiceDescriptorTable[0ADh] 下保存的服务地址替换掉,
将我们自己的 Hook 处理函数的地址来替换掉原来的地址,
这样当每次调用 KeServiceDescriptorTable[0ADh]时就会调用我们自己的这个 Hook 处理函数了。
下面用几幅截图来表示:
下面的截图则是 SSDT Hook 之后了,可以看到将 SSDT 中的服务地址修改为 MyHookNtQuerySystemInformation 了,
这样的话,每次系统调用 NtQuerySystemInformation 这个系统服务时,
实质上调用的就是 MyHookNtQuerySystemInformation 了,而我们为了保证系统的稳定性(至少不让其崩溃),
一般会在 MyHookNtQuerySystemInformation 中调用系统中原来的服务,也就是 NtQuerySystemInformation。
6. 小结:
本篇博文呢尚还只是介绍了 SSDT 到底是个什么东西,而还没有给出具体的 SSDT Hook 的实现,
对于 SSDT Hook 的实现以及 Demo 我都放到(二)中完成,也就是本篇博文未完 , 待续 ……
关于 SSDT 的话,在看雪上有很多的文章,由于我也是前阵子对这东西突然感兴趣了,
所以我也算是初次了解,自然也看过了很多的文章,SSDT 在 Google 一搜索可以出来一大堆,
但是要说介绍 SSDT 最详细的话,我想还是我的这篇文章介绍的比较详细,
因为网上很多介绍 SSDT 的都只是将 SSDT 原理做了简单的介绍,然后在网上 down 一个 Demo,
把代码贴出来就完事了,甚至是代码都还无法完整编译通过的,
所以如果读者想对 SSDT 有所了解的话,可以好好看一看这篇文章的 ~
顺便这里还带出一个问题,是我这阵子脑子里突然冒出来的一个疑问,
但是由于时间或者说是个人状态问题,一直没有去研究 ~ 不晓得园子里有木有对这个有研究的 ~
众所周知,在 Windows 操作系统中,System 进程的进程 PID 为 4,
我想问的就是:System 进程的 PID 为何是 4 ?
欢迎大家对这个问题讨论啊 ~ 在这里先给点思路,
那就是可以通过 Windows 操作系统的启动过程,然后结合 WRK 源码进行研究 ~
进程隐藏与进程保护(SSDT Hook 实现)(二)
文章目录:
1. 引子 – Demo 实现效果:
2. 进程隐藏与进程保护概念:
3. SSDT Hook 框架搭建:
4. Ring0 实现进程隐藏:
5. Ring0 实现进程保护:
6. 隐藏进程列表和保护进程列表的维护:
7. 小结:
1. 引子 – Demo 实现效果:
上一篇《进程隐藏与进程保护(SSDT Hook 实现)(一)》呢把 SSDT 说得差不多了,
博文地址:
http://www.cnblogs.com/BoyXiao/archive/2011/09/03/2164574.html
不过呢,那也只是些理论的东西,看不到什么实物,估计说来说去把人说晕了后,也没什么感觉,
而这一篇博文的话,给出点新意的,让人头脑清醒点的 ~ 所以先给个 Demo 出来吧 ~
(不好意思,本着不喝倒怎出得了好文章这个理由,所以今天又喝多了点,文章有疏忽之处还请见谅 ~
顺便在这里跟朋友们分享一下哈,晚上于这个时间点,比如 2 点的时候啊,喝点小酒,
听点曲子,你会精神振奋,头脑更加清醒,思路也会很清晰,尤其是写起程序来那是唰唰的来の ~ )
进程隐藏效果:
应用程序主界面:
隐藏进程 taskmgr.exe:
取消进程隐藏 taskmgr.exe:
进程保护效果:
进程保护(保护自身进程 SSDTProcess.exe):
取消进程保护(这里还是以 SSDTProcess.exe 为例):
下面的截图表示 SSDTProcess.exe 已经被取消了保护,
此时再到任务管理器中结束 SSDTProcess.exe 时,你可以发现是可以正常结束这个进程的 ~
2. 进程隐藏与进程保护概念:
在 Ring3 下获取到当前 Windows 操作系统下的所有的进程无外乎以下的几种方法:
第一种:使用 ToolHelp 遍历获取到所有进程,关于这种方式的话,笔者以前写过一篇博文的,
《列举 Windows 所有进程(ToolHelp)》博文地址如下:
http://www.cnblogs.com/BoyXiao/archive/2011/02/27/1966383.html
第二种:使用 PSAPI 下的 EnumProcesses 获取到所有进程的 PID,然后提升进程权限为 SE_DEBUG 权限,
再调用 OpenProcess 即可打开进程,从而获取到进程的基本信息(可以查看 MSDN 的 PSAPI 专题)。
第三种:使用未公开的本地 API 即位于 Ntdll.dll 中的未文档化的 API – NtQuerySystemInformation,
而 Windows 任务管理器就是通过这种方式来获取到所有的进程信息的 ~
而事实上的是,ToolHelp 和 PSAPI 只不过是对 Ntdll.dll 中 NtQuerySystemInformation API 的一个封装,
所以在 Ring3 下获取系统中所有进程信息最终都会回到 Ndll.dll 中 NtQuerySystemInformation API 的调用上。
如果要实现在 Ring3 中对进程进行隐藏的话,只需要 Hook 掉 NtQuerySystemInformation API 即可。
而至于进程保护的话,我们需要考虑到两种情况,第一种则是该进程自行终止,第二种情况则是该进程被其他进程给杀掉,
第一种情况基本上对于窗口应用程序来说,一般都是用户点击了右上角的 x 按钮,然后产生 WM_CLOSE 消息,
最后由窗口过程退出进程,这种情况下,我们应该是需要允许退出的,也就是进程是可以正常退出的。
而第二种情况的话,就是进程被别的进程杀掉,比如在任务管理器中就可以杀掉绝大部分的应用程序进程,
而这里的进程保护就是要实现进程不能够被任务管理器或者其他的进程管理工具杀掉。
在 Ring3 中,由一个进程结束其他进程,调用的 API 为 Kernel32.dll 中的 TerminateProcess,
如果追溯这个 TerminateProcess,可以发现,其调用了 Ntdll.dll 中的 NtTerminateProcess API,
然后再追溯下去就可以到 ntoskrnl.exe 中的 ZwTerminateProcess 和系统服务 NtTerminateProcess 了。
而这和我的上一篇博文中介绍 NtQuerySystemInformation 就是一致的了,
所以如果我们要实现进程保护,需要 Hook 的系统服务就是 NtTerminateProcess ~
3. SSDT Hook 框架搭建:
从上面的介绍中,我们可以知道,要想实现进程隐藏和进程保护,我们需要在 SSDT 中 Hook 两个系统服务,
即 Ring0 下的 NtQuerySystemInformation 和 Ring0 下的 NtTerminateProcess,
既然要实现两个 Hook 的话,我干嘛不将 SSDT Hook 写成一个框架呢,
这样的话,以后我无论是需要 Hook 哪个 SSDT 中的系统服务,我直接调用这个 SSDT 框架不就 OK 了,
免得再去重复造轮子不,所以这里就来简单介绍一下这个 SSDT 框架 ~
当然这里谈得 SSDT Hook 框架可不是大伙眼里的什么 .Net 框架啊,MVC 框架啊之类的,
没那么复杂,算到底也就是一个 .cpp 和一个 .h 的文件而已,然后再在其中对外公开几个 API 即 OK 了 ~
这里既是使用了 SSDT 的话,而在前一篇博文中也谈到了在 ntoskrnl.exe 中导出了 KeServiceDescriptorTable,
但是内核中导出归导出,它导出有个屁用啊,别个类型啊什么的都没给你,看你怎么在你代码中使用它 ~
所以我们首先要做的就是如何使用这个 ntoskrnl.exe 中导出的 KeServiceDescriptorTable 了,
不过好在还有 WRK(当然在反汇编,逆向工程里面那些牛的作用也是相当的给力)的帮助,
我们可以定义下面的代码来完成在自己的代码中使用 KeServiceDescriptorTable 这个任务:
1: //=====================================================================================//
2: //Name: KSYSTEM_SERVICE_TABLE 和 KSERVICE_TABLE_DESCRIPTOR //
4: //Descripion: 用来定义 SSDT 结构 //
6: //=====================================================================================//
7: typedef struct _KSYSTEM_SERVICE_TABLE
8: {
9: PULONG ServiceTableBase; // SSDT (System Service Dispatch Table)的基地址
10: PULONG ServiceCounterTableBase; // 包含 SSDT 中每个服务被调用的次数
11: ULONG NumberOfService; // 服务函数的个数, NumberOfService * 4 就是整个地址表的大小
12: ULONG ParamTableBase; // SSPT(System Service Parameter Table)的基地址
13:
14: } KSYSTEM_SERVICE_TABLE, *PKSYSTEM_SERVICE_TABLE;
15:
16:
17: typedef struct _KSERVICE_TABLE_DESCRIPTOR
18: {
19: KSYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl.exe 的服务函数
20: KSYSTEM_SERVICE_TABLE win32k; // win32k.sys 的服务函数(GDI32.dll/User32.dll 的内核支持)
21: KSYSTEM_SERVICE_TABLE notUsed1;
22: KSYSTEM_SERVICE_TABLE notUsed2;
23:
24: } KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
25:
26:
27: //导出由 ntoskrnl.exe 所导出的 SSDT
28: extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
既然是个 SSDT Hook 框架的话,自然需要能够安装 Hook,当然也需要能够解除 Hook,
而我们拿什么来解除 Hook 呢,经过前面的介绍,我们可以知道的是,
SSDT Hook 其实就是拿我们自己的 Hook 函数的地址去替换掉原来 SSDT 中保存的系统服务的地址,
如果 Hook 了某个 API,那就意味着在 SSDT 中指定索引处所保存的系统服务的地址被修改为了 Hook 函数的地址,
而如果要解除这个 API 的 Hook,自然就需要将原来系统中原有的系统服务的地址写回 SSDT 指定索引处,
但是我们拿什么来保存 SSDT Hook 之前的系统服务的地址呢 ?
由于在 32 位机器上,一个入口地址可以用 32 位来表示,也就可以使用一个 ULONG 类型来保存,
而由于我们这个是 SSDT 框架,也就是能够随意的 Hook SSDT 中的任意系统服务,
自然为了能成功实现 Hook 的解除,就需要将 SSDT 中在 Hook 之前的每一个系统服务的地址保存下来,
根据上面的总结,这可以通过一个 ULONG 数组来保存就可以了 ~
然后再在 Hook 任意的系统服务之前,将 SSDT 中的所有系统服务的地址保存或者说是备份到 ULONG 数组中即可 ~
而后在解除 Hook 时,我们就可以从这个 ULONG 数组中取出原有系统服务的地址,
然后将地址写入到 SSDT 中即可实现 Hook 解除 ~
1: //定义 SSDT(系统服务描述表) 中服务个数的最大数目
2: //这里定义为 1024 个,实际上在 XP SP3 是 0x0128 个
3: #define MAX_SYSTEM_SERVICE_NUMBER 1024
4:
5: //用来保存 SSDT 中所有的旧的服务函数的地址
6: ULONG oldSysServiceAddr[MAX_SYSTEM_SERVICE_NUMBER];
同时由于要实现安装 Hook,解除 Hook,所以自然也要公开两个 API,一个用来安装 Hook,一个用来解除 Hook,
根据上面的这些呢,我们大致可以确定至少需要三个 API:
1: //备份 SSDT 中所有系统服务的地址
2: VOID BackupSysServicesTable();
3:
4: //安装 Hook
5: NTSTATUS InstallSysServiceHook(ULONG oldService, ULONG newService);
6:
7: //解除 Hook
8: NTSTATUS UnInstallSysServiceHook(ULONG oldService);
然后还需要注意的是,SSDT 中保存的地址不是说你想写就可以写的,
SSDT 在内存中是具有只读属性保护的,如果你想修改 SSDT 中的内容,你必须先要解除只读属性,
也就是要赋予 SSDT 所在的这块内存具有可写属性才行,不然回馈你的将是一个无情的蓝屏(内存写入错误) ~
你给了这块内存可写属性后,你他妈的写完后总的把可写属性去掉,把别个恢复到只读属性吧 ~
不然也太不厚道了,用完就不管了 ~ 所以还需要一个恢复只读属性的 API,
综述,在 SSDT Hook 框架中又有了两个 API:
1: //禁止写入保护,也就是恢复到只读
2: VOID DisableWriteProtect(ULONG oldAttr);
3:
4: //允许写入保护,也就是设置为可写
5: VOID EnableWriteProtect(PULONG pOldAttr);
然后呢下面就将上面的这些个 API 的实现代码给贴出来,个人觉得自己的代码风格还算比较好的,
应该还是看得下去吧(当然这只是我现在的观点,说不准再过段时间回头来看这些代码就会感慨这代码是给人看的嘛) ~
1: #include "SSDTHook.h"
2:
4: //=====================================================================================//
5: //Name: VOID DisableWriteProtect() //
6: // //
7: //Descripion: 用来去掉内存的可写属性,从而实现内存只读 //
8: // //
9: //=====================================================================================//
10: VOID DisableWriteProtect(ULONG oldAttr)
11: {
12: _asm
13: {
14: mov eax, oldAttr
15: mov cr0, eax
16: sti;
17: }
18: }
19:
20:
21: //=====================================================================================//
22: //Name: VOID EnableWriteProtect() //
23: // //
24: //Descripion: 用来去掉内存的只读保护,从而实现可以写内存 //
25: // //
26: //=====================================================================================//
27: VOID EnableWriteProtect(PULONG pOldAttr)
28: {
29: ULONG uAttr;
30:
31: _asm
32: {
33: cli;
34: mov eax, cr0;
35: mov uAttr, eax;
36: and eax, 0FFFEFFFFh; // CR0 16 BIT = 0
37: mov cr0, eax;
38: };
39:
40: //保存原有的 CRO 属性
41: *pOldAttr = uAttr;
42: }
43:
44:
45: //=====================================================================================//
46: //Name: VOID BackupSysServicesTable() //
47: // //
48: //Descripion: 备份 SSDT 中原有服务的地址,因为在解除 Hook 时需要还原 SSDT 中原有地址 //
49: // //
50: //=====================================================================================//
51: VOID BackupSysServicesTable()
52: {
53: ULONG i;
54:
55: for(i = 0; (i < KeServiceDescriptorTable->ntoskrnl.NumberOfService) && (i < MAX_SYSTEM_SERVICE_NUMBER); i++)
56: {
57: oldSysServiceAddr[i] = KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[i];
58: //oldSysServiceAddr[i] = *(PULONG)((ULONG)KeServiceDescriptorTable->ntoskrnl.ServiceTableBase + 4 * i);
59:
60: KdPrint(("\Function Information { Number: 0x%04X , Address: %08X}", i, oldSysServiceAddr[i]));
61: }
62: }
63:
64:
65: //=====================================================================================//
66: //Name: NTSTATUS InstallSysServiceHook() //
67: // //
68: //Descripion: 实现 Hook 的安装,主要是在 SSDT 中用 newService 来替换掉 oldService //
69: // //
70: //=====================================================================================//
71: NTSTATUS InstallSysServiceHook(ULONG oldService, ULONG newService)
72: {
73: ULONG uOldAttr = 0;
74:
75: EnableWriteProtect(&uOldAttr);
76:
77: SYSCALL_FUNCTION(oldService) = newService;
78: //KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(oldService)] = newService;
79:
80: DisableWriteProtect(uOldAttr);
81:
82: return STATUS_SUCCESS;
83: }
84:
85:
86: //=====================================================================================//
87: //Name: NTSTATUS UnInstallSysServiceHook() //
88: // //
89: //Descripion: 实现 Hook 的解除,主要是在 SSDT 中用备份下的服务地址来替换掉 oldService //
90: // //
91: //=====================================================================================//
92: NTSTATUS UnInstallSysServiceHook(ULONG oldService)
93: {
94: ULONG uOldAttr = 0;
95:
96: EnableWriteProtect(&uOldAttr);
97:
98: SYSCALL_FUNCTION(oldService) = oldSysServiceAddr[SYSCALL_INDEX(oldService)];
100:
101: DisableWriteProtect(uOldAttr);
102:
103: return STATUS_SUCCESS;
104: }
可以注意到上面有两个很重要的宏,即 SYSCALL_FUNCTION 和 SYSCALL_INDEX 宏,
关于这两个宏的具体作用,可以看注释的 ~
1: //根据 Zw_ServiceFunction 获取 Zw_ServiceFunction 在 SSDT 中所对应的服务的索引号
2: #define SYSCALL_INDEX(ServiceFunction) (*(PULONG)((PUCHAR)ServiceFunction + 1))
3:
4:
5: //根据 Zw_ServiceFunction 来获得服务在 SSDT 中的索引号,
6: //然后再通过该索引号来获取 Nt_ServiceFunction的地址
7: #define SYSCALL_FUNCTION(ServiceFunction)
8: KeServiceDescriptorTable->ntoskrnl.ServiceTableBase[SYSCALL_INDEX(ServiceFunction)]
4. Ring0 实现进程隐藏:
有了 SSDT Hook 框架后,其实要实现进程的隐藏是很简单的了,
根据前面的介绍,要想实现进程隐藏,你可以通过 Hook NtQuerySystemInformation 来实现,
所以剩下的任务就只需要在我们自己的 Hook 处理函数中来将进程隐藏掉就 OK 了 ~
由于 NtQuerySystemInformation 这个系统服务在 ntddk.h 中并没有被声明,
虽然这个系统服务在 ntoskrnl.exe 中被导出了,但是没有它的声明,我们仍然是无法使用的,
所以我们就需要手动的声明一下这个函数 ~
还有需要注意的是,我们在前面知道,在 ntoskrnl.exe 中实质上是存在 ZwQuerySystemInformation
以及 NtQuerySystemInformation 这两个 API 的,
而在 SSDT Hook 中我们是根据 ZwQuerySystemInformation
来推算出在 SSDT 中保存有 NtQuerySystemInformation 的地址所在的索引号的 ~
关于这个,你可以查看 SYSCALL_INDEX 这个宏来再次确认一下 ~
然后有了这个索引号,我们才可以进行对 NtQuerySystemInformation 系统服务的 Hook,
所以在声明时,我们需要声明两个 API,当然如果这些 API 在 ntddk.h 中声明了就不需要了,
但是由于 ZwQuerySystemInformation 和 NtQuerySystemInformation 在 ntddk.h 中都没有声明,
所以需要在我们自己的代码中手动声明 ~
1: NTSYSAPI NTSTATUS NTAPI ZwQuerySystemInformation (
2: __in SYSTEM_INFORMATION_CLASS SystemInformationClass,
3: __out_bcount_opt(SystemInformationLength) PVOID SystemInformation,
4: __in ULONG SystemInformationLength,
5: __out_opt PULONG ReturnLength
6: );
7:
8: typedef NTSTATUS (* NTQUERYSYSTEMINFORMATION)(
9: __in SYSTEM_INFORMATION_CLASS SystemInformationClass,
10: __out_bcount_opt(SystemInformationLength) PVOID SystemInformation,
11: __in ULONG SystemInformationLength,
12: __out_opt PULONG ReturnLength
13: );
(暂停一下,肚子饿了 ~ 吃饭去 ~ )
(好,饭给吃了,酒也喝了,精神亢奋中,现在咱继续哈 ~ 嘿嘿 ~)
完成了这些声明后,我们就可以来实现自己的 NtQuerySystemInformation Hook 函数了,
在这个 Hook 函数中,我们需要对我们感兴趣的进程进行隐藏 ~
然后这里需要注意的是,我是如何来实现对进程隐藏的,
首先我是判断这个进程的 ID 是否是需要隐藏的进程 ID,
这是通过 ValidateProcessNeedHide 函数来判断的 ~ 这个函数会在后面给出 ~
注意结合代码中的注释来看(虽然注释比较少 ~ 嘿嘿 ~ )
1: NTSTATUS HookNtQuerySystemInformation (
2: __in SYSTEM_INFORMATION_CLASS SystemInformationClass,
3: __out_bcount_opt(SystemInformationLength) PVOID SystemInformation,
4: __in ULONG SystemInformationLength,
5: __out_opt PULONG ReturnLength
6: );
1: //=====================================================================================//
2: //Name: NTSTATUS HookNtQuerySystemInformation() //
3: // //
4: //Descripion: 自定义的 NtQuerySystemInformation,用来实现 Hook Kernel API //
5: // //
6: //=====================================================================================//
7: NTSTATUS HookNtQuerySystemInformation (
8: __in SYSTEM_INFORMATION_CLASS SystemInformationClass,
9: __out_bcount_opt(SystemInformationLength) PVOID SystemInformation,
10: __in ULONG SystemInformationLength,
11: __out_opt PULONG ReturnLength
12: )
13: {
14: NTSTATUS rtStatus;
15:
16: pOldNtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)
17: oldSysServiceAddr[SYSCALL_INDEX(ZwQuerySystemInformation)];
18:
19: rtStatus = pOldNtQuerySystemInformation(SystemInformationClass, SystemInformation,
20: SystemInformationLength, ReturnLength);
21: if(NT_SUCCESS(rtStatus))
22: {
23: if(SystemProcessInformation == SystemInformationClass)
24: {
25: PSYSTEM_PROCESS_INFORMATION pPrevProcessInfo = NULL;
26: PSYSTEM_PROCESS_INFORMATION pCurrProcessInfo =
27: (PSYSTEM_PROCESS_INFORMATION)SystemInformation;
28:
29: while(pCurrProcessInfo != NULL)
30: {
31: //获取当前遍历的 SYSTEM_PROCESS_INFORMATION 节点的进程名称和进程 ID
32: ULONG uPID = (ULONG)pCurrProcessInfo->UniqueProcessId;
33: UNICODE_STRING strTmpProcessName = pCurrProcessInfo->ImageName;
34:
35: //判断当前遍历的这个进程是否为需要隐藏的进程
36: if(ValidateProcessNeedHide(uPID) != -1)
37: {
38: if(pPrevProcessInfo)
39: {
40: if(pCurrProcessInfo->NextEntryOffset)
41: {
42: //将当前这个进程(即要隐藏的进程)从 SystemInformation 中摘除(更改链表偏移指针实现)
43: pPrevProcessInfo->NextEntryOffset += pCurrProcessInfo->NextEntryOffset;
44: }
45: else
46: {
47: //说明当前要隐藏的这个进程是进程链表中的最后一个
48: pPrevProcessInfo->NextEntryOffset = 0;
49: }
50: }
51: else
52: {
53: //第一个遍历到得进程就是需要隐藏的进程
54: if(pCurrProcessInfo->NextEntryOffset)
55: {
56: (PCHAR)SystemInformation += pCurrProcessInfo->NextEntryOffset;
57: }
58: else
59: {
60: SystemInformation = NULL;
61: }
62: }
63: }
64:
65: //遍历下一个 SYSTEM_PROCESS_INFORMATION 节点
66: pPrevProcessInfo = pCurrProcessInfo;
67:
68: //遍历结束
69: if(pCurrProcessInfo->NextEntryOffset)
70: {
71: pCurrProcessInfo = (PSYSTEM_PROCESS_INFORMATION)
72: (((PCHAR)pCurrProcessInfo) + pCurrProcessInfo->NextEntryOffset);
73: }
74: else
75: {
76: pCurrProcessInfo = NULL;
77: }
78: }
79: }
80: }
81: return rtStatus;
82: }
既然有了自己的 Hook NtQuerySystemInformation 了,自然我们就可以通过利用 SSDT 框架来实现 Hook 了,
这部分的代码其实是最简单的,因为我只需要在 DriverEntry 中 Hook 掉 NtQuerySystemInformation 即可,
这里需要注意的是,在执行 Hook 之前需要备份一次 SSDT,即在 DriverEntry 中最先需要备份 SSDT ~
当然为了保证系统的安全以及其他诸多方面,我们在 DriverUnload 中会将 Hook 解除掉 ~
从下面的代码中,我们看到在安装 Hook 和解除 Hook 时参数传递进去的是 ZwQuerySystemInformation,
这样很有可能会让很多朋友认为我们在 Ring0 下的 Hook 的是 ZwQuerySystemInformation,
如果你这样认为的话,那就大错特错了,确实在 Google 上搜索出的一大堆关于 SSDT Hook 中,
很多文章都说是 Hook 的 ZwQuerySystemInformation,而事实上这是大错特错的,
我们这里传入 ZwQuerySystemInformation ,是因为我们需要调用 SYS_INDEX(ZwQuerySystemInformation)
来获得 NtQuerySystemInformation 在 SSDT 中的地址所在的索引号,
然后我们根据这个索引号来 Hook NtQuerySystemInformation,
认识到这一点是非常重要的,因为我一开始也认为是 Hook 的 ZwQuerySystemInformation,
从而导致蓝屏了 n 次,在这里非常鄙视那些把文章从别处拷贝过来也不加验证就乱发表的 ~ 害死人 ~ 当然也要怪自己懒 ~
5. Ring0 实现进程保护:
有了上面实现进程隐藏的基础,要再来实现进程保护,其实也就是过过场子了 ~
进程保护呢,上面也说了,是要 Hook NtTermianteProcess 这个系统服务 ~
由于 ZwTerminateProcess 呢,在 ntddk.h 中已经声明了,
所以在我们自己的代码中就不需要声明 ZwTermianteProcess 了,
而只需要声明 NtTerminateProcess 以及 Hook 函数就 OK 了 ~
1: typedef NTSTATUS (* NTTERMINATEPROCESS)(
2: __in_opt HANDLE ProcessHandle,
3: __in NTSTATUS ExitStatus
4: );
5:
6: NTSTATUS HookNtTerminateProcess(
7: __in_opt HANDLE ProcessHandle,
8: __in NTSTATUS ExitStatus
9: );
10:
11: NTTERMINATEPROCESS pOldNtTerminateProcess;
至于安装 Hook 以及卸载 Hook ,都可以根据进程隐藏中的代码来完成,因为有了 SSDT Hook 框架,
这一切也就变得很简单了,只要在 DriverEntry 中 InstallHook ,然后再在 DriverUnload 中 UnInstallHook 即 OK ~
下面我们重点来看一看我们自己的 Hook NtTerminateProcess 中是如何实现进程保护的 ~
进程保护呢其实也是比较简单的,因为从上面一层的调用会传递一个进程句柄下来,
而后我们可以根据这个进程句柄来获得进程的 EPROCESS 对象(进程位于执行体层得对象),
通过这个 EPROCESS 对象,我们就可以获得这个请求被结束的进程的 PID,
我们再判断这个 PID 是否是我们已经保护了的 PID,如果是的话,直接返回一个请求被拒绝即可,
而如果这个 PID 未被保护,自然我们就交给原来的 NtTerminateProcess 处理即可 ~
1: //=====================================================================================//
2: //Name: NTSTATUS HookNtTerminateProcess() //
3: // //
4: //Descripion: 自定义的 NtTerminateProcess,用来实现 Hook Kernel API //
5: // //
6: //=====================================================================================//
7: NTSTATUS HookNtTerminateProcess(
8: __in_opt HANDLE ProcessHandle,
9: __in NTSTATUS ExitStatus
10: )
11: {
12: ULONG uPID;
13: NTSTATUS rtStatus;
14: PCHAR pStrProcName;
15: PEPROCESS pEProcess;
16: ANSI_STRING strProcName;
17:
18: //通过进程句柄来获得该进程所对应的 FileObject 对象,由于这里是进程对象,自然获得的是 EPROCESS 对象
19: rtStatus = ObReferenceObjectByHandle(ProcessHandle,
20: FILE_READ_DATA, NULL, KernelMode, &pEProcess, NULL);
21: if(!NT_SUCCESS(rtStatus))
22: {
23: return rtStatus;
24: }
25:
26: //保存 SSDT 中原来的 NtTerminateProcess 地址
27: pOldNtTerminateProcess =
28: (NTTERMINATEPROCESS)oldSysServiceAddr[SYSCALL_INDEX(ZwTerminateProcess)];
29:
30: //通过该函数可以获取到进程名称和进程 ID,该函数在内核中实质是导出的(在 WRK 中可以看到)
31: //但是 ntddk.h 中并没有到处,所以需要自己声明才能使用
32: uPID = (ULONG)PsGetProcessId(pEProcess);
33: pStrProcName = (PCHAR)PsGetProcessImageFileName(pEProcess);
34:
35: //通过进程名来初始化一个 ASCII 字符串
36: RtlInitAnsiString(&strProcName, pStrProcName);
37:
38: if(ValidateProcessNeedProtect(uPID) != -1)
39: {
40: //确保调用者进程能够结束(这里主要是指 taskmgr.exe)
41: if(uPID != (ULONG)PsGetProcessId(PsGetCurrentProcess()))
42: {
43: //如果该进程是所保护的的进程的话,则返回权限不够的异常即可
44: return STATUS_ACCESS_DENIED;
45: }
46: }
47:
48: //对于非保护的进程可以直接调用原来 SSDT 中的 NtTerminateProcess 来结束进程
49: rtStatus = pOldNtTerminateProcess(ProcessHandle, ExitStatus);
50:
51: return rtStatus;
52: }
6. 隐藏进程列表和保护进程列表的维护:
由于需要隐藏的进程以及需要被保护的进程都是由应用程序传递进来的,
也就是说这个内核程序是需要和应用程序通信的,自然就需要创建一个 Device,
然后我就采用了简单的 DeviceIoControl 来实现了内核程序和应用程序的通信,
对于需要隐藏的进程或者是需要保护的进程,其由应用程序通过 DeviceIoControl 来将这个进程的 PID 传递给内核程序,
然后在内核程序中呢,维护了两个数组,一个数组用来存放需要隐藏的进程的 PID,
另外一个数组自然就是用来存放需要保护的进程的 PID,
1: ULONG g_PIDHideArray[MAX_PROCESS_ARRARY_LENGTH];
2: ULONG g_PIDProtectArray[MAX_PROCESS_ARRARY_LENGTH];
3:
4: ULONG g_currHideArrayLen = 0;
5: ULONG g_currProtectArrayLen = 0;
为了维护上面的这两个数组呢,又衍生出了几个 API,即实现对数组中的 PID 进行增删查 ~
1: //验证 uPID 所代表的进程是否存在于隐藏进程列表中,即判断 uPID 这个进程是否需要隐藏
2: ULONG ValidateProcessNeedHide(ULONG uPID);
3:
4: //验证 uPID 所代表的进程是否存在于保护进程列表中,即判断 uPID 这个进程是否需要保护
5: ULONG ValidateProcessNeedProtect(ULONG uPID);
6:
7: //往隐藏进程列表中插入 uPID
8: ULONG InsertHideProcess(ULONG uPID);
9:
10: //从隐藏进程列表中移除 uPID
11: ULONG RemoveHideProcess(ULONG uPID);
12:
13: //往保护进程列表中插入 uPID
14: ULONG InsertProtectProcess(ULONG uPID);
15:
16: //从隐藏进程列表中移除 uPID
17: ULONG RemoveProtectProcess(ULONG uPID);
对于前面谈及的 HookNtQuerySystemInformation 和 HookNtTerminateProcess 的话,
需要判断一个进程是否是需要被保护或者需要被隐藏的进程就是通过上面的数组来完成的,
即判断一个进程是否需要被隐藏时,只需要判断这个进程在隐藏列表中是否存在即可,
而对于实现进程保护的话,道理也是一样的 ~
上面说过,应用程序和内核程序的通信是通过 DeviceIoControl 来完成的,
下面我们就来看看 DeviceIoControl 的代码:
1: //=====================================================================================//
2: //Name: NTSTATUS SSDT01DeviceIoControlDispatcher() //
3: // //
4: //Descripion: 分发函数 //
5: // //
6: //=====================================================================================//
7: NTSTATUS SSDT01DeviceIoControlDispatcher(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
8: {
9: NTSTATUS rtStatus;
10:
11: ULONG uPID;
12: ULONG uInLen;
13: ULONG uOutLen;
14: ULONG uCtrlCode;
15:
16: PCHAR pInBuffer;
17:
18: PIO_STACK_LOCATION pStack;
19:
20: uPID = 0;
21: rtStatus = STATUS_SUCCESS;
22: pStack = IoGetCurrentIrpStackLocation(pIrp);
23:
24: uInLen = pStack->Parameters.DeviceIoControl.InputBufferLength;
25: uOutLen = pStack->Parameters.DeviceIoControl.OutputBufferLength;
26: uCtrlCode = pStack->Parameters.DeviceIoControl.IoControlCode;
27:
28: //使用缓冲区方式与应用程序进行通信
29: pInBuffer = (PCHAR)pIrp->AssociatedIrp.SystemBuffer;
30:
31: if(uInLen >= 4)
32: {
33: //stdlib.h(atol = Array To LONG)
34: uPID = atol(pInBuffer);
35:
36: switch(uCtrlCode)
37: {
38: case IO_INSERT_PROTECT_PROCESS:
39: {
40: if(InsertProtectProcess(uPID) == FALSE)
41: {
42: rtStatus = STATUS_PROCESS_IS_TERMINATING;
43: }
44: break;
45: }
46: case IO_REMOVE_PROTECT_PROCESS:
47: {
48: if(RemoveProtectProcess(uPID) == FALSE)
49: {
50: rtStatus = STATUS_PROCESS_IS_TERMINATING;
51: }
52: break;
53: }
54: case IO_INSERT_HIDE_PROCESS:
55: {
56: if(InsertHideProcess(uPID) == FALSE)
57: {
58: rtStatus = STATUS_PROCESS_IS_TERMINATING;
59: }
60: break;
61: }
62: case IO_REMOVE_HIDE_PROCESS:
63: {
64: if(RemoveHideProcess(uPID) == FALSE)
65: {
66: rtStatus = STATUS_PROCESS_IS_TERMINATING;
67: }
68: break;
69: }
70: default:
71: {
72: rtStatus = STATUS_INVALID_VARIANT;
73: break;
74: }
75: }
76: }
77: else
78: {
79: rtStatus = STATUS_INVALID_PARAMETER;
80: }
81:
82: //输出信息总是为空,即该驱动程序不返回输出信息
83: pIrp->IoStatus.Status = rtStatus;
84: pIrp->IoStatus.Information = 0;
85: IoCompleteRequest(pIrp, IO_NO_INCREMENT);
86:
87: return rtStatus;
88: }
7. 小结:
这篇博文呢,是承接前一篇博文《进程隐藏与进程保护(SSDT Hook 实现)(一)》来的,
前面的博文主要介绍了 SSDT 是个什么东西,以及我们做内核 Hook 的一些基础,
而这篇博文则完整的介绍了 SSDT Hook 的具体实现,其中涉及到了很多底层的知识的,
对于绝大部分的代码呢,大伙是可以参考代码来进行理解的,而后我会将内核部分的代码先公开出来 ~
本来呢是打算将这个 SSDT Hook 做两篇博文就给结束得了,
不过第二篇博文写了这么长了,但是在应用程序中的实现都还没有开始介绍,
而今晚真的又太晚了,再写下去天就亮了,还说要尽量不熬夜的 ~ 唉 ~ 算了,权当周末给自己找个借口吧 ~
下一篇博文将介绍的是如何在应用程序中获取到所有的进程啊,以及应用程序如何和内核程序设备进行通信之类的知识,
知识重点是放在 Ring3 了,其中不会涉及到很多 Ring0 的内容了 ~
开发工具以及环境搭建:
Visual Studio 2010 + VirtualDDK + WDK + VMware + Windows Service 2003 SP1,至于具体环境的搭建,
可以参考我的博文《驱动程序环境搭建(VS2010 + WDK + VirtualDDK + VMware)》,博文地址如下:
http://www.cnblogs.com/BoyXiao/archive/2011/07/31/2122755.html
和前一篇文章一样,我将我近来遇到的一些疑问放到博文的最后面,看是否有看官遇到过,如果有遇到过的,
还请不吝赐教 ~ 在下感激不尽 ~
疑问 1:
在 .Net WinForm 中,我有一个定时器,然后又有一个按钮,
那么对应的就有一个定时器 Tick 处理事件,按钮也会有一个 Click 事件,
考虑在多核处理器上,有没有这样一种可能,按钮的 Click 事件和 Tick 处理事件并发执行 ?
如果有的话,大伙一般都是如何做处理的 ?
疑问 2:
大家都有用过 SVN 的,当我们安装好客户端工具 TortoiseSVN 后,
每次启动 PC ,均会有一个 TSVNCache.exe 的进程随 PC 自动启动,
请教一下大家这个进程是如何启动的 ?
进程隐藏与进程保护(SSDT Hook 实现)(三)
文章目录:
1. 引子:
2. 获取当前系统下所有进程:
3. 服务管理(安装,启动,停止,卸载):
4. 应用程序和内核程序通信:
5. 小结:
1. 引子:
关于这个 SSDT Hook 实现进程隐藏和进程保护呢,这是最后一篇博文了,
在文章的结尾处呢你可以下载到整个项目的实例程序以及代码,
程序可以在 XP、Server、Win7 上运行的,当然我是说的 32 位操作系统。
《进程隐藏与进程保护(SSDT Hook 实现)(一)》呢把 SSDT Hook 的原理说得差不多了,
博文地址:http://www.cnblogs.com/BoyXiao/archive/2011/09/03/2164574.html
《进程隐藏与进程保护(SSDT Hook 实现)(二)》则把 SSDT Hook 的实现说得差不多了,
博文地址:http://www.cnblogs.com/BoyXiao/archive/2011/09/04/2166596.html
这一篇博文介绍的则是在 Ring3 下编写 MFC 应用程序,并且让应用程序与内核程序通信,
即由应用程序将需要隐藏的进程或者是需要保护的进程的 PID 传递给内核程序,
然后在内核程序中就会将传递进来的这个 PID 进行隐藏或者保护 ~
在这里再给出这个应用程序的一张截图:
2. 获取当前系统下所有进程:
前面提到过,要想获取到系统下的所有进程,有三种方法,
第一种即是使用 ToolHelp 来获取,
第二种则是使用 PSAPI 来获取,
第三种则是使用 ntdll.dll 中的未文档化的 NtQuerySystemInformation 之类的 API 来获取(比较麻烦)。
而在这里我使用最简单的方式,即通过 PSAPI 中的 EnumProcesses 这个 API 来获取,
EnumProcesses API 可以获取到当前系统下所有进程的 PID,并且将 PID 存放在作为输出参数的数组当中,
其原型如下(可以看 MSDN):
1: BOOL WINAPI EnumProcesses(
2: __out DWORD* pProcessIds,
3: __in DWORD cb,
4: __out DWORD* pBytesReturned
5: );
6:
代码中使用(将获取到所有的 PID,然后将 PID 保存到 vector 容器中):
1: //遍历当前所有的进程,并且将进程 ID 填充到容器 vectorPID 中
2: void CSSDTProcessDlg::FillPIDVector()
3: {
4: DWORD dwPIDArray[MAX_PROCESS_COUNT];
5: DWORD dwNeededBytes;
6: DWORD dwProcCount;
7:
8: dwNeededBytes = 0;
9: dwProcCount = 0;
10: memset(dwPIDArray, 0, sizeof(DWORD) * MAX_PROCESS_COUNT);
11: if(NULL != EnumProcesses(dwPIDArray, sizeof(dwPIDArray), &dwNeededBytes))
12: {
13: dwProcCount = dwNeededBytes / sizeof(DWORD);
14: }
15:
16: BubbleSort(dwPIDArray, dwProcCount);
17:
18: ClearVector();
19: for(int i=0; i<dwProcCount; i++)
20: {
21: PROCESS_BIND procBind;
22: procBind.dwPID = dwPIDArray[i];
23: if(dwPIDArray[i] == 0)
24: {
25: procBind.state = ProcessStateUnknown;
26: }
27: else
28: {
29: procBind.state = ProcessStateGeneral;
30: }
31: this->m_vctAllProcess.push_back(procBind);
32: }
33: }
3. 服务管理(安装,启动,停止,卸载):
在 Windows 内核程序中,现在大体可以分为三类了,
第一类是 NT 式驱动程序;
第二类为 WDM 驱动程序;
第三类为 WDF 驱动程序;
其中,对于 NT 式驱动程序,其安装方式是很简单的,因为你可以将 NT 式驱动程序看做一个服务,
既然是服务的话,自然在 Windows 中可以通过 SCM API 来完成其安装,启动,停止和卸载等功能 ~
而至于 WDM 和 WDF 的话,如果其中涉及到了设备的话,还必须使用 INF 文件来实现安装 ~
而我们前面的那个 SSDT 内核程序就是基于 NT 式的驱动程序,所以可以通过 SCM API 来实现上面的这些功能,
至于如何使用 SCM API 来完成服务的安装、启动、停止和卸载功能的话,
可以参见笔者的另外一篇博文《Windows 服务(附服务开发辅助工具)》,
博文地址为:http://www.cnblogs.com/BoyXiao/archive/2011/08/07/2130208.html
下面就只是将服务的安装 API、启动 API、停止 API 和卸载 API 贴出来了 ~
至于这些代码的细细道来的话,可以参加上面给出的那篇博文的 ~
1: //=====================================================================================//
2: //Name: bool InstallSvc() //
3: // //
4: //Descripion: 安装服务 //
5: // lpszSvcName 为服务名称, //
6: // lpszDisplay 为显示在服务控制管理器中的名称, //
7: // lpszSvcBinaryPath 为服务映像文件所在路径, //
8: // dwSvcType 为服务类型 //
9: // dwStartType 为服务启动类型 //
10: //=====================================================================================//
11: bool CSSDTProcessDlg::InstallSvc(LPTSTR lpszSvcName, LPTSTR lpszDisplayName,
12: LPTSTR lpszSvcBinaryPath, DWORD dwSvcType, DWORD dwStartType)
13: {
14: SC_HANDLE hSCM = NULL;
15: SC_HANDLE hSvc = NULL;
16:
17: AdjustProcessTokenPrivilege();
18:
19: hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
20: if(NULL == hSCM)
21: {
22: OutputErrorMessage(TEXT("InstallSvc - OpenSCManager Failed , Error Code Is %d , Error Message Is %s !"));
23:
24: return FALSE;
25: }
26:
27: for(int i = 0; i < 3 && (NULL == hSvc); i++)
28: {
29: //SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS
30: hSvc = CreateService(hSCM, lpszSvcName, lpszDisplayName, SERVICE_ALL_ACCESS,
31: dwSvcType, dwStartType, SERVICE_ERROR_NORMAL,
32: lpszSvcBinaryPath, NULL, NULL, NULL, NULL, NULL);
33: if(NULL != hSvc)
34: {
35: if(NULL != hSvc)
36: {
37: CloseServiceHandle(hSvc);
38: }
39: CloseServiceHandle(hSCM);
40: return TRUE;
41: }
42: }
43:
44: OutputErrorMessage(TEXT("InstallSvc - CreateService Failed , Error Code Is %d , Error Message Is %s !"));
45:
46: CloseServiceHandle(hSCM);
47:
48: return FALSE;
49: }
50:
51:
52: //=====================================================================================//
53: //Name: bool UnInstallSvc() //
54: // //
55: //Descripion: 实现卸载服务 //
56: //=====================================================================================//
57: bool CSSDTProcessDlg::UnInstallSvc(LPTSTR lpszSvcName)
58: {
59: SC_HANDLE hSCM = NULL;
60: SC_HANDLE hSvc = NULL;
61: bool rtResult = FALSE;
62:
63: AdjustProcessTokenPrivilege();
64:
65: hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
66: if(NULL == hSCM)
67: {
68: OutputErrorMessage(TEXT("UnInstallSvc - OpenSCManager Failed , Error Code Is %d , Error Message Is %s !"));
69:
70: return FALSE;
71: }
72:
73: hSvc = OpenService(hSCM, lpszSvcName, SERVICE_ALL_ACCESS);
74: if(NULL == hSvc)
75: {
76: OutputErrorMessage(TEXT("UnInstallSvc - OpenService Failed , Error Code Is %d , Error Message Is %s !"));
77:
78: CloseServiceHandle(hSCM);
79:
80: return FALSE;
81: }
82:
83: rtResult = DeleteService(hSvc);
84:
85: CloseServiceHandle(hSvc);
86: CloseServiceHandle(hSCM);
87:
88: return rtResult;
89: }
90:
91:
92: //=====================================================================================//
93: //Name: bool StartSvc() //
94: // //
95: //Descripion: 实现启动服务 //
96: //=====================================================================================//
97: bool CSSDTProcessDlg::StartSvc(LPTSTR lpszSvcName)
98: {
99: SC_HANDLE hSCM = NULL;
100: SC_HANDLE hSvc = NULL;
101: bool rtResult = FALSE;
102:
103: AdjustProcessTokenPrivilege();
104:
105: hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
106: if(NULL == hSCM)
107: {
108: OutputErrorMessage(TEXT("StartSvc - OpenSCManager Failed , Error Code Is %d , Error Message Is %s !"));
109:
110: return FALSE;
111: }
112:
113: hSvc = OpenService(hSCM, lpszSvcName, SERVICE_ALL_ACCESS);
114: if(NULL == hSvc)
115: {
116: OutputErrorMessage(TEXT("StartSvc - OpenService Failed , Error Code Is %d , Error Message Is %s !"));
117:
118: CloseServiceHandle(hSCM);
119:
120: return FALSE;
121: }
122:
123: rtResult = StartService(hSvc, NULL, NULL);
124:
125: CloseServiceHandle(hSvc);
126: CloseServiceHandle(hSCM);
127:
128: if(FALSE == rtResult)
129: {
130: if(ERROR_SERVICE_ALREADY_RUNNING == GetLastError())
131: {
132: return TRUE;
133: }
134: else
135: {
136: OutputErrorMessage(TEXT("StartSvc - StartService Failed , Error Code Is %d , Error Message Is %s !"));
137:
138: return FALSE;
139: }
140: }
141: else
142: {
143: return TRUE;
144: }
145: }
146:
147:
148: //=====================================================================================//
149: //Name: bool StopSvc() //
150: // //
151: //Descripion: 实现停止服务 //
152: //=====================================================================================//
153: bool CSSDTProcessDlg::StopSvc(LPTSTR lpszSvcName)
154: {
155: SC_HANDLE hSCM = NULL;
156: SC_HANDLE hSvc = NULL;
157: bool rtResult = FALSE;
158:
159: SERVICE_STATUS svcStatus;
160:
161: AdjustProcessTokenPrivilege();
162:
163: hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
164: if(NULL == hSCM)
165: {
166: OutputErrorMessage(TEXT("StopSvc - OpenSCManager Failed , Error Code Is %d , Error Message Is %s !"));
167:
168: return FALSE;
169: }
170:
171: hSvc = OpenService(hSCM, lpszSvcName, SERVICE_ALL_ACCESS);
172: if(NULL == hSvc)
173: {
174: OutputErrorMessage(TEXT("StopSvc - OpenService Failed , Error Code Is %d , Error Message Is %s !"));
175:
176: CloseServiceHandle(hSCM);
177:
178: return FALSE;
179: }
180:
181: rtResult = ControlService(hSvc, SERVICE_CONTROL_STOP, &svcStatus);
182: if(rtResult == FALSE)
183: {
184: OutputErrorMessage(TEXT("StopSvc - ControlService Failed , Error Code Is %d , Error Message Is %s !"));
185: }
186: CloseServiceHandle(hSvc);
187: CloseServiceHandle(hSCM);
188:
189: return rtResult;
190: }
那么服务的安装和启动放在那里比较合适,而服务的关闭和卸载又放在那里比较合适呢 ?
由于这个应用程序采用 MFC 开发,自然可以在 OnInitDialog()中安装和启动服务比较合适,
而后可以在对话框类的析构函数中关闭和卸载掉服务 ~
安装和启动服务:
1: wstring wStrSysPath = GetSysFilePath();
2: BOOL bResult = InstallSvc(((LPTSTR)(LPCTSTR)SSDT01_SERVICE_NAME),
3: ((LPTSTR)(LPCTSTR)SSDT01_SERVICE_NAME),
4: ((LPTSTR)(LPCTSTR)wStrSysPath.c_str()),
5: SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START);
6: if(FALSE == bResult)
7: {
8: MessageBox(_TEXT(" Install SSDT Service Failed , Application Auto Exit ! "),
9: _TEXT("Application Error"), MB_OK | MB_ICONSTOP);
10: CDialogEx::OnCancel();
11: return FALSE;
12: }
13: else
14: {
15: bResult = StartSvc(SSDT01_SERVICE_NAME);
16: if(FALSE == bResult)
17: {
18: MessageBox(_TEXT(" Start SSDT Service Failed , Application Auto Exit ! "),
19: _TEXT("Application Error"), MB_OK | MB_ICONSTOP);
20: CDialogEx::OnCancel();
21: return FALSE;
22: }
23: }
停止并且将服务卸载掉:
1: ~CSSDTProcessDlg()
2: {
3: //在析构函数中关闭 SSDT 设备句柄
4: if(this->m_hDevice)
5: {
6: CloseHandle(this->m_hDevice);
7: }
8:
9: //当发生析构函数时,停止服务并且卸载服务
10: StopSvc(SSDT01_SERVICE_NAME);
11: UnInstallSvc(SSDT01_SERVICE_NAME);
12: }
4. 应用程序和内核程序通信:
由前面的第二篇博文,可以知道,应用程序和内核程序的通信我是通过 DeviceIoControl 来完成的,
开发过内核程序的都清楚,应用程序和内核程序的通信最普遍的也就通过三个 API 来实现,
一个 ReadFile,一个 WriteFile,一个 DeviceIoContrl,
当然其中属 DeviceIoControl 功能最为强大,完全可以用其替换掉 ReadFile 和 WriteFile,
DeviceIoControl 原型(详细信息可以参考 MSDN):
1: BOOL WINAPI DeviceIoControl(
2: __in HANDLE hDevice,
3: __in DWORD dwIoControlCode,
4: __in LPVOID lpInBuffer,
5: __in DWORD nInBufferSize,
6: __out LPVOID lpOutBuffer,
7: __in DWORD nOutBufferSize,
8: __out LPDWORD lpBytesReturned,
9: __in LPOVERLAPPED lpOverlapped
10: );
11:
至于如何实现应用程序和内核程序的通信的话,在我的 Demo 中是这样做处理的,
首先在 OnInitDialog 事件中通过 CreateFile 打开我们所安装的服务中创建的设备,
(在 NT 式驱动程序中我创建了一个设备,这个设备用来实现应用程序和内核程序的通信),
然后在对话框类中保存有一个全局变量,这个全局变量即代表所打开的这个设备的句柄,
既然这个全局变量是保存的我们的设备的句柄,自然我们需要来获取到设备的句柄,并且将句柄赋值给该全局变量,
而这个呢,又是在 OnInitDialog 中完成的 ~
1: this->m_hDevice = CreateFile(SSDT01_DEVICE_NAME, GENERIC_READ | GENERIC_WRITE, 0,
2: NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
3: if(INVALID_HANDLE_VALUE == this->m_hDevice)
4: {
5: MessageBox(_TEXT(" Open SSDT Device Failed , Application Auto Exit ! "),
6: _TEXT("Application Error"), MB_OK | MB_ICONSTOP);
7:
8: CDialogEx::OnCancel();
9: return FALSE;
10: }
有了这个设备句柄,我们就可以通过其来实现和内核程序的通信了,
因为通过在应用程序中调用 DeviceIoControl 可以产生 IRP_MJ_DEVICE_CONTROL 的 IRP,
然后该 IRP 可以被驱动程序中的 DeviceIoControl 分发函数所处理 ~
我们的应用程序只需要将我们所要隐藏或者是需要保护的进程的 PID 通过 DeviceIoControl 传递给内核程序即可 !!!
所以我们在应用程序中只需要调用 DeviceIoContrl 即可 ~
下面给出的代码比较凌乱(重点请看 DeviceIoControl 的调用)
1: //隐藏进程或者取消对进程的隐藏
2: void CSSDTProcessDlg::OnBnClickedBtnHideorunhide()
3: {
4: int nIndex;
5: DWORD dwPID;
6: CString cStrText;
7: CString cStrState;
8:
9: DWORD dwOutput;
10: BOOL bRet;
11: CHAR inBuffer[10];
12: CHAR outBuffer[10];
13: memset(inBuffer, 0, 10);
14: memset(outBuffer, 0, 10);
15:
16: dwPID = this->GetDlgItemInt(IDC_STATIC_SELECTED_PID);
17: this->GetDlgItemText(ID_BTN_HIDEORUNHIDE, cStrText);
18:
19: ultoa(dwPID, inBuffer, 10);
20:
21: nIndex = QueryItemIndexByPID(dwPID);
22: cStrState = this->m_ListCtrlProcess.GetItemText(nIndex, 4);
23:
24: if(cStrText.CompareNoCase(_TEXT("Hide")) == 0)
25: {
26: //隐藏 dwPID
27: bRet = DeviceIoControl(this->m_hDevice, IO_INSERT_HIDE_PROCESS, inBuffer, 10,
28: &outBuffer, 10, &dwOutput, NULL);
29: if(bRet)
30: {
31: this->SetDlgItemText(ID_BTN_HIDEORUNHIDE, _TEXT("UnHide"));
32: if(cStrState.CompareNoCase(_TEXT("Protect")) == 0)
33: {
34: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("HideAndProtect"));
35: }
36: else
37: {
38: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("Hide"));
39: }
40: MessageBox(_TEXT(" Hide Process Sucess ! "), _TEXT("Information"), MB_OK |
41: MB_ICONINFORMATION);
42: }
43: else
44: {
45: MessageBox(_TEXT(" Hide Process Failed ! "), _TEXT("Warning"), MB_OK | MB_ICONERROR);
46: }
47: }
48: else
49: {
50: //解除 dwPID 隐藏
51: bRet = DeviceIoControl(this->m_hDevice, IO_REMOVE_HIDE_PROCESS, inBuffer, 10,
52: &outBuffer, 10, &dwOutput, NULL);
53: if(bRet)
54: {
55: this->SetDlgItemText(ID_BTN_HIDEORUNHIDE, _TEXT("Hide"));
56: if(cStrState.CompareNoCase(_TEXT("Protect")) == 0 ||
57: cStrState.CompareNoCase(_TEXT("HideAndProtect"))== 0)
58: {
59: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("Protect"));
60: }
61: else
62: {
63: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("General"));
64: }
65: MessageBox(_TEXT(" UnHide Process Sucess ! "), _TEXT("Information"), MB_OK |
66: MB_ICONINFORMATION);
67: }
68: else
69: {
70: MessageBox(_TEXT(" UnHide Process Failed ! "), _TEXT("Warning"), MB_OK | MB_ICONERROR);
71: }
72: }
73: }
74:
75:
76: //保护进程或者取消对进程的保护操作
77: void CSSDTProcessDlg::OnBnClickedBtnProtectorunprotect()
78: {
79: int nIndex;
80: DWORD dwPID;
81: CString cStrText;
82: CString cStrState;
83:
84: DWORD dwOutput;
85: BOOL bRet;
86: CHAR inBuffer[10];
87: CHAR outBuffer[10];
88: memset(inBuffer, 0, 10);
89: memset(outBuffer, 0, 10);
90:
91: dwPID = this->GetDlgItemInt(IDC_STATIC_SELECTED_PID);
92: this->GetDlgItemText(ID_BTN_PROTECTORUNPROTECT, cStrText);
93:
94: ultoa(dwPID, inBuffer, 10);
95:
96: nIndex = QueryItemIndexByPID(dwPID);
97: cStrState = this->m_ListCtrlProcess.GetItemText(nIndex, 4);
98:
99: if(cStrText.CompareNoCase(_TEXT("Protect")) == 0)
100: {
101: //保护 dwPID 保护
102: bRet = DeviceIoControl(this->m_hDevice, IO_INSERT_PROTECT_PROCESS, inBuffer, 10,
103: &outBuffer, 10, &dwOutput, NULL);
104: if(bRet)
105: {
106: this->SetDlgItemText(ID_BTN_PROTECTORUNPROTECT, _TEXT("UnProtect"));
107: if(cStrState.CompareNoCase(_TEXT("Hide"))== 0)
108: {
109: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("HideAndProtect"));
110: }
111: else
112: {
113: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("Protect"));
114: }
115: MessageBox(_TEXT(" Protect Process Sucess ! "), _TEXT("Information"), MB_OK |
116: MB_ICONINFORMATION);
117: }
118: else
119: {
120: MessageBox(_TEXT(" Protect Process Failed ! "), _TEXT("Warning"), MB_OK | MB_ICONERROR);
121: }
122: }
123: else
124: {
125: //解除 dwPID 保护
126: bRet = DeviceIoControl(this->m_hDevice, IO_REMOVE_PROTECT_PROCESS, inBuffer, 10,
127: &outBuffer, 10, &dwOutput, NULL);
128: if(bRet)
129: {
130: this->SetDlgItemText(ID_BTN_PROTECTORUNPROTECT, _TEXT("Protect"));
131: if(cStrState.CompareNoCase(_TEXT("Hide")) == 0 ||
132: cStrState.CompareNoCase(_TEXT("HideAndProtect")) == 0)
133: {
134: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("Hide"));
135: }
136: else
137: {
138: this->m_ListCtrlProcess.SetItemText(nIndex, 4, _TEXT("General"));
139: }
140: MessageBox(_TEXT(" UnProtect Process Sucess ! "), _TEXT("Information"), MB_OK |
141: MB_ICONINFORMATION);
142: }
143: else
144: {
145: MessageBox(_TEXT(" UnProtect Process Failed ! "), _TEXT("Warning"), MB_OK | MB_ICONERROR);
146: }
147: }
148: }
5. 小结:
介绍这个应用程序呢,还真是不好写,因为感觉整个 Demo 里面却是没有什么好介绍的,
无非就是获取到所有的进程,然后通过一个 ListCtrl 来显示这些数据,
然后用户选择一个进程,单击一下隐藏呢,我就在这个按钮的消息处理函数中和内核程序通过 DeviceIoControl 通信一下,
将这个进程的 PID 传递给内核程序,其他的就都不需要理会了 ~ 所以转来转去的,也没什么好些的,干脆就写到这里得了,
等下将整个 Demo 打个包,直接提供下载,我这里说得口干舌燥也没什么用,感兴趣的自己下载了源码去慢慢玩得了 ~
最后再总结一个 SSDT Hook 的优点,那就是 SSDT Hook 无论你是 Windows XP 还是 Server 或者 Vista 或者 Win7,
你都是可以很好的运行程序的,所以你下载的 Demo 你可以放心的在上面的这些操作系统上运行,当然 64 位的除外,
64 位的操作系统虽然我没有做过测试,但是我估摸着会蓝屏的 ~ 有兴趣的可以去蓝一次 ~
下载 Demo Source Code
版权所有,欢迎转载,但转载请注明: 转载自 Zachary.XiaoZhen - 梦想的天空