您现在的位置: 万盛学电脑网 >> 电脑基础 >> 健康知识 >> 正文

安全稳定的实现进线程监控

作者:佚名    责任编辑:admin    更新时间:2022-06-22

·以假乱真 Linux中实现Vista界面·Max粒子系统轻松实现群蝶飞舞·利用VB6.0实现五线谱作曲工具·轻松实现电脑一分为二:Betwin·用VC实现小型矢量图形系统的开发(上)·盗QQ的密码软件模拟实现(VC)·让Excel 2007实现双面打印的两种另类方·洪水攻击原理及代码实现全攻略·用ATL实现VC插件·ASP.NET系统用户权限设计与实现   

用PsSetCreateProcessNotifyRoutine,PsSetCreateThreadNotifyRoutine来进行进程线程监控我想大家已经都非常熟练了.sinister在<<编写进程/线程监视器>>一文中已经实现得很好了.前一段时间看到网上有人在研究监视远线程的文章,比较有意思.就写代码玩一玩.这之中就出现了一些问题.比方说直接用sinister的代码的话,是不能动态卸载的,因为他在安装了进线程监视函数后没有进行清除动作,造成在动态卸载时蓝屏,BUGCHECK为0x000000ce,错误码为:DRIVER_UNLOADED_WITHOUT_CANCELLING_PENDING_OPERATIONS.很显然,在驱动退出后,一些进线程操作仍然在访问原来的地址,造成出错.在XP后,微软给出了一个函数PsRemoveCreateThreadNotifyRoutine用来清除线程监视函数(清除进程监视的就是PsSetCreateProcessNotifyRoutine).我一直奇怪ICESWORD在2000中是怎么做到进线程监视的.后来才发现,在运行icesword后释放出一个detport.sys文件,然后一直在系统中存在着没有卸载掉.只是把它隐藏了而已^_^.这不是个好消息,难道我为了测试一个驱动,测试一次就得重启一次吗?呵呵,肯定不是啊,所以想办法搞定它.

我们来看一下进线程监视在底层是如何实现的,在win2000源代码中先找到创建线程的函数实现:////////////////////////////////////////////////////////////////////////////////////////////////////////// \win2k\private\ntos\ps\create.h////////////////////////////////////////////////////////////////////////////////////////////////////////NTSTATUSPspCreateThread(......){...if (PspCreateProcessNotifyRoutineCount != 0) { //首先调用进程监控函数ULONG i;for (i=0; i<PSP_MAX_CREATE_PROCESS_NOTIFY; i ) {if (PspCreateProcessNotifyRoutine[i] != NULL) {(*PspCreateProcessNotifyRoutine[i])( Process->InheritedFromUniqueProcessId,Process->UniqueProcessId,TRUE);}}}

}......if (PspCreateThreadNotifyRoutineCount != 0) {ULONG i;

for (i=0; i<PSP_MAX_CREATE_THREAD_NOTIFY; i ) { //再调用线程监控函数if (PspCreateThreadNotifyRoutine[i] != NULL) {(*PspCreateThreadNotifyRoutine[i])( Thread->Cid.UniqueProcess,Thread->Cid.UniqueThread,TRUE);}}}......}

从上面可以看到,在每创建一个线程后会调用PspCreateProcessNotifyRoutine[i]地址指向的函数.而PsSetCreateThreadNotifyRoutine的作用就是将PspCreateThreadNotifyRoutine[i]数组设置值,该值就是监视函数的地址.

NTSTATUSPsSetCreateThreadNotifyRoutine(IN PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine){ULONG i;NTSTATUS Status;

Status = STATUS_INSUFFICIENT_RESOURCES;for (i = 0; i < PSP_MAX_CREATE_THREAD_NOTIFY; i = 1) {if (PspCreateThreadNotifyRoutine[i] == NULL) {PspCreateThreadNotifyRoutine[i] = NotifyRoutine;PspCreateThreadNotifyRoutineCount = 1;Status = STATUS_SUCCESS;break;}}

return Status;}上面的一些结构如下:////////////////////////////////////////////////////////////////////////////////////////////////////////// \win2k\private\ntos\ps\psp.h////////////////////////////////////////////////////////////////////////////////////////////////////////#define PSP_MAX_CREATE_THREAD_NOTIFY 8 //最大监视数目

ULONG PspCreateThreadNotifyRoutineCount; //用来记数PCREATE_THREAD_NOTIFY_ROUTINE PspCreateThreadNotifyRoutine[ PSP_MAX_CREATE_THREAD_NOTIFY ]; //函数地址数组

而PCREATE_THREAD_NOTIFY_ROUTINE定义如下:typedefVOID(*PCREATE_THREAD_NOTIFY_ROUTINE)(IN HANDLE ProcessId,IN HANDLE ThreadId,IN BOOLEAN Create);

相应的,进程的结构也是一样的.通过上面,我们可以看到,只要我们找出该函数数组地址,在我们退出驱动时先将其全部清零,清零的大小为PSP_MAX_CREATE_THREAD_NOTIFY,这样的话下一次的进线程操作就不会调用这个函数指针了.也就让系统回到正常,我们再通过PsSetCreateProcessNotifyRoutine来验证一下:

NTSTATUSPsSetCreateProcessNotifyRoutine(IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,IN BOOLEAN Remove){ULONG i;

for (i=0; i < PSP_MAX_CREATE_PROCESS_NOTIFY; i ) {if (Remove) { if (PspCreateProcessNotifyRoutine[i] == NotifyRoutine) { //清除时就是简单的赋植操作PspCreateProcessNotifyRoutine[i] = NULL;PspCreateProcessNotifyRoutineCount -= 1; //将计数器减一return STATUS_SUCCESS;}} else {if (PspCreateProcessNotifyRoutine[i] == NULL) { //设置时也是简单的赋值操作PspCreateProcessNotifyRoutine[i] = NotifyRoutine;PspCreateProcessNotifyRoutineCount = 1; //将计数器加一return STATUS_SUCCESS;}}}

return Remove ? STATUS_PROCEDURE_NOT_FOUND : STATUS_INVALID_PARAMETER;}


好了,方法已经知道了,只要找出地址,我们就能够"全身而退"了.看一下windows2003下面的PsRemoveCreateThreadNotifyRoutine实现: lkd> u PsRemoveCreateThreadNotifyRoutine l 20nt!PsRemoveCreateThreadNotifyRoutine:80651d7b 53 push ebx80651d7c 56 push esi80651d7d 57 push edi80651d7e 33db xor ebx,ebx80651d80 bf400f5780 mov edi,0x80570f40 //起始地址80651d85 57 push edi80651d86 e8a7500100 call nt!ExWaitForRundownProtectionRelease 0x5cf (80666e32)80651d8b 8bf0 mov esi,eax80651d8d 85f6 test esi,esi80651d8f 7420 jz nt!PsRemoveCreateThreadNotifyRoutine 0x36 (80651db1)80651d91 56 push esi80651d92 e8ba1bffff call nt!IoReportTargetDeviceChange 0x7aa0 (80643951)80651d97 3b442410 cmp eax,[esp 0x10]80651d9b 750d jnz nt!PsRemoveCreateThreadNotifyRoutine 0x2f (80651daa)80651d9d 56 push esi80651d9e 6a00 push 0x080651da0 57 push edi80651da1 e8c54f0100 call nt!ExWaitForRundownProtectionRelease 0x508 (80666d6b)80651da6 84c0 test al,al80651da8 751b jnz nt!PsRemoveCreateThreadNotifyRoutine 0x4a (80651dc5)80651daa 56 push esi80651dab 57 push edi80651dac e892510100 call nt!ExWaitForRundownProtectionRelease 0x6e0 (80666f43)80651db1 43 inc ebx80651db2 83c704 add edi,0x480651db5 83fb08 cmp ebx,0x8 //看是否到了最大数(8)80651db8 72cb jb nt!PsRemoveCreateThreadNotifyRoutine 0xa (80651d85)80651dba b87a0000c0 mov eax,0xc000007a80651dbf 5f pop edi80651dc0 5e pop esi80651dc1 5b pop ebx80651dc2 c20400 ret 0x4

lkd> dd 0x80570f40 //设置了监视函数后80570f40 e316e557 00000000 00000000 00000000.............................

lkd> dd 0x80570f40 //清除了监视函数后80570f40 00000000 00000000 00000000 00000000

哈哈.下面是实现代码,代码中实现了进线的的监视,并且实现了远线程的监视:

Drivers.c/////////////////////////////////////////////////////////////////////////////////////////////////////////// // Made By ZwelL

#include "ntddk.h"#include "windef.h"#include "define.h"

#define SYSNAME "System"#define VERSIONLEN 100

const WCHAR devLink[] = L"\\??\\MyEvent";const WCHAR devName[] = L"\\Device\\MyEvent";UNICODE_STRING devNameUnicd;UNICODE_STRING devLinkUnicd; PVOID gpEventObject = NULL; // 与应用程序通信的 Event 对象ULONG ProcessNameOffset =0;PVOID outBuf[255];BOOL g_bMainThread; ULONG g_dwParentId;CHECKLIST CheckList;ULONG BuildNumber; //系统版本号 ULONG SYSTEMID; //System进程的IDPWCHAR Version[VERSIONLEN];

NTSTATUS PsLookupProcessByProcessId(IN ULONG ulProcId, OUT PEPROCESS * pEProcess);

ULONG GetProcessNameOffset(){PEPROCESS curproc;int i;

curproc = PsGetCurrentProcess();

for( i = 0; i < 3*PAGE_SIZE; i ) {if( !strncmp( SYSNAME, (PCHAR) curproc i, strlen(SYSNAME) )) {return i;}}

return 0;}

NTSTATUS GetRegValue(PCWSTR RegPath,PCWSTR ValueName,PWCHAR Value){int ReturnValue = 0;NTSTATUS Status;OBJECT_ATTRIBUTES ObjectAttributes;HANDLE KeyHandle;PKEY_VALUE_PARTIAL_INFORMATION valueInfoP;ULONG valueInfoLength,returnLength;UNICODE_STRING UnicodeRegPath;UNICODE_STRING UnicodeValueName;

RtlInitUnicodeString(&UnicodeRegPath, RegPath);RtlInitUnicodeString(&UnicodeValueName, ValueName);

InitializeObjectAttributes(&ObjectAttributes,&UnicodeRegPath,OBJ_CASE_INSENSITIVE, // FlagsNULL, // Root directoryNULL); // Security descriptor

Status = ZwOpenKey(&KeyHandle,KEY_ALL_ACCESS,&ObjectAttributes);if (Status != STATUS_SUCCESS){DbgPrint("ZwOpenKey Wrong\n");return 0;}

valueInfoLength = sizeof(KEY_VALUE_PARTIAL_INFORMATION) VERSIONLEN;valueInfoP = (PKEY_VALUE_P