文章目录:
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