用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_PARTIAL_INFORMATION) ExAllocatePool(NonPagedPool, valueInfoLength);Status = ZwQueryValueKey(KeyHandle,&UnicodeValueName,KeyValuePartialInformation,valueInfoP,valueInfoLength,&returnLength);
if (!NT_SUCCESS(Status)){DbgPrint("ZwQueryValueKey Wrong:x\n",Status);return Status;}else{RtlCopyMemory((PCHAR)Value, (PCHAR)valueInfoP->Data, valueInfoP->DataLength);ReturnValue = 1;}
if(!valueInfoP);ExFreePool(valueInfoP);ZwClose(KeyHandle);return ReturnValue;}
VOID MyRemoveCraeteThreadNotifyRoutine(IN PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine){//PsRemoveCreateThreadNotifyRoutine(ThreadCreateMon);PVOID ptr=NULL;if(BuildNumber==2195) //Windows 2000 Sp4,2195//低于sp4的我没有调试{ptr=0x80484520;}else if(BuildNumber==2600) {if(wcscmp(Version,L"Service Pack 1")==0) //Windows Xp Sp1,2600ptr=0x8054efc0;else if(wcscmp(Version,L"Service Pack 2")==0) //Windows Xp Sp2,2600ptr=0x80561d20;}else if(BuildNumber==3790) //Windows 2003 server,3790 {ptr=0x80570f40;}if(ptr!=NULL)memset(ptr, 0, sizeof(ULONG)*8);}
VOID ThreadCreateMon (IN HANDLE PId, IN HANDLE TId, IN BOOLEAN bCreate){
PEPROCESS EProcess,PEProcess;NTSTATUS status;HANDLE dwParentPID;
status = PsLookupProcessByProcessId( (ULONG)PId, &EProcess);if (!NT_SUCCESS( status )){DbgPrint("PsLookupProcessByProcessId()\n");return ;}
if ( bCreate ){dwParentPID=PsGetCurrentProcessId();status = PsLookupProcessByProcessId( (ULONG)dwParentPID, &PEProcess);if (!NT_SUCCESS( status )){DbgPrint("PsLookupProcessByProcessId()\n");return ;}if(PId==4) //System进程创建的东东我们不管//在2000下是0,在XP后是4return;if((g_bMainThread==TRUE)&&(g_dwParentId != dwParentPID)&&(dwParentPID != PId)){g_bMainThread=FALSE;sprintf(outBuf, "==============================""Remote Thread :""==============================""\nT:s潩%s漒\ ""======================================""======================================\n", (char *)((char *)EProcess ProcessNameOffset),PId, TId,(char *)((char *)PEProcess ProcessNameOffset),dwParentPID);if(gpEventObject!=NULL)KeSetEvent((PRKEVENT)gpEventObject, 0, FALSE);}if(CheckList.ONLYSHOWREMOTETHREAD) //只显示远线程return;DbgPrint( "T:s潩%s漒\ ", (char *)((char *)EProcess ProcessNameOffset),PId, TId,(char *)((char *)PEProcess ProcessNameOffset),dwParentPID);sprintf(outBuf, "T:s潩%s漒\ ", (char *)((char *)EProcess ProcessNameOffset),PId, TId,(char *)((char *)PEProcess ProcessNameOffset),dwParentPID);if(gpEventObject!=NULL)KeSetEvent((PRKEVENT)gpEventObject, 0, FALSE);}else if(CheckList.SHOWTERMINATETHREAD){DbgPrint( "TERMINATED == THREAD ID: %d\n", TId);sprintf(outBuf,"TERMINATED == THREAD ID: %d\n", TId);if(gpEventObject!=NULL)KeSetEvent((PRKEVENT)gpEventObject, 0, FALSE);}}
VOID ProcessCreateMon ( HANDLE hParentId, HANDLE PId, BOOLEAN bCreate ){
PEPROCESS EProcess,PProcess;NTSTATUS status;HANDLE TId;
g_dwParentId = hParentId;status = PsLookupProcessByProcessId((ULONG)PId, &EProcess);if (!NT_SUCCESS( status )){DbgPrint("PsLookupProcessByProcessId()\n");return ;}status = PsLookupProcessByProcessId((ULONG)hParentId, &PProcess);if (!NT_SUCCESS( status )){DbgPrint("PsLookupProcessByProcessId()\n");return ;}
if ( bCreate ){g_bMainThread = TRUE;DbgPrint( "P:s潩%s漒\ ",(char *)((char *)EProcess ProcessNameOffset),PId,PsGetCurrentThreadId(),(char *)((char *)PProcess ProcessNameOffset),hParentId);sprintf(outBuf, "P:s潩%s漒\ ",(char *)((char *)EProcess ProcessNameOffset),PId,PsGetCurrentThreadId(),(char *)((char *)PProcess ProcessNameOffset),hParentId);if(gpEventObject!=NULL)KeSetEvent((PRKEVENT)gpEventObject, 0, FALSE);}else if(CheckList.SHOWTERMINATEPROCESS){DbgPrint( "TERMINATED == PROCESS ID: %d\n", PId);sprintf(outBuf,"TERMINATED == PROCESS ID: %d\n", PId);if(gpEventObject!=NULL)KeSetEvent((PRKEVENT)gpEventObject, 0, FALSE);}
}
NTSTATUS OnUnload( IN PDRIVER_OBJECT pDriverObject ){NTSTATUS status;DbgPrint("OnUnload called\n");if(gpEventObject)ObDereferenceObject(gpEventObject); PsSetCreateProcessNotifyRoutine(ProcessCreateMon, TRUE);MyRemoveCraeteThreadNotifyRoutine(ThreadCreateMon);if(pDriverObject->DeviceObject != NULL){status=IoDeleteSymbolicLink( &devLinkUnicd );if ( !NT_SUCCESS( status ) ){DbgPrint(( "IoDeleteSymbolicLink() failed\n" ));return status; }IoDeleteDevice( pDriverObject->DeviceObject );}return STATUS_SUCCESS;}
NTSTATUS DeviceIoControlDispatch(IN PDEVICE_OBJECT DeviceObject,IN PIRP pIrp){PIO_STACK_LOCATION irpStack;NTSTATUS status;PVOID inputBuffer;ULONG inputLength;PVOID outputBuffer;ULONG outputLength;OBJECT_HANDLE_INFORMATION objHandleInfo;
status = STATUS_SUCCESS;// 取出IOCTL请求代码irpStack = IoGetCurrentIrpStackLocation(pIrp);
switch (irpStack->MajorFunction){case IRP_MJ_CREATE :DbgPrint("Call IRP_MJ_CREATE\n");break;case IRP_MJ_CLOSE:DbgPrint("Call IRP_MJ_CLOSE\n");break;case IRP_MJ_DEVICE_CONTROL:DbgPrint("IRP_MJ_DEVICE_CONTROL\n");inputLength=irpStack->Parameters.DeviceIoControl.InputBufferLength;outputLength=irpStack->Parameters.DeviceIoControl.OutputBufferLength;switch (irpStack->Parameters.DeviceIoControl.IoControlCode) {case IOCTL_PASSEVENT: //用事件做通信 inputBuffer = pIrp->AssociatedIrp.SystemBuffer;
DbgPrint("inputBuffer:x\n", (HANDLE)inputBuffer);status = ObReferenceObjectByHandle(*(HANDLE *)inputBuffer,GENERIC_ALL,NULL,KernelMode,&gpEventObject,&objHandleInfo);
if(status!=STATUS_SUCCESS){DbgPrint("wrong\n");break;}break;case IOCTL_UNPASSEVENT:if(gpEventObject)ObDereferenceObject(gpEventObject); DbgPrint("UNPASSEVENT called\n");break;case IOCTL_PASSBUF:RtlCopyMemory(pIrp->UserBuffer, outBuf, outputLength);break;case IOCTL_PASSEVSTRUCT:inputBuffer = pIrp->AssociatedIrp.SystemBuffer;memset(&CheckList, 0, sizeof(CheckList));RtlCopyMemory(&CheckList, inputBuffer, sizeof(CheckList));DbgPrint("%d:%d\n", CheckList.ONLYSHOWREMOTETHREAD, CheckList.SHOWTHREAD);break;default:break;}break;default:DbgPrint("Call IRP_MJ_UNKNOWN\n");break;}
pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = 0; IoCompleteRequest (pIrp, IO_NO_INCREMENT);return status;}
NTSTATUS DriverEntry( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING theRegistryPath ){NTSTATUS Status; PDEVICE_OBJECT pDevice;
DbgPrint("DriverEntry called!\n");g_bMainThread = FALSE;
if(1!=GetRegValue(L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", L"CSDVersion", Version)){DbgPrint("GetRegValueDword Wrong\n");}PsGetVersion(NULL, NULL, &BuildNumber, NULL);DbgPrint("[[[%d]]]:[[[%ws]]]", BuildNumber, Version);
RtlInitUnicodeString (&devNameUnicd, devName );RtlInitUnicodeString (&devLinkUnicd, devLink );
Status = IoCreateDevice ( pDriverObject,0,&devNameUnicd,FILE_DEVICE_UNKNOWN,0,TRUE,&pDevice );if( !NT_SUCCESS(Status)) {DbgPrint(("Can not create device.\n"));return Status;}
Status = IoCreateSymbolicLink (&devLinkUnicd, &devNameUnicd);if( !NT_SUCCESS(Status)) {DbgPrint(("Cannot create link.\n"));return Status;}
ProcessNameOffset = GetProcessNameOffset();
pDriverObject->DriverUnload = OnUnload; pDriverObject->MajorFunction[IRP_MJ_CREATE] = pDriverObject->MajorFunction[IRP_MJ_CLOSE] =pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceIoControlDispatch;
Status = PsSetCreateProcessNotifyRoutine(ProcessCreateMon, FALSE);if (!NT_SUCCESS( Status )){DbgPrint("PsSetCreateProcessNotifyRoutine()\n");return Status;}
Status = PsSetCreateThreadNotifyRoutine(ThreadCreateMon);if (!NT_SUCCESS( Status )){DbgPrint("PsSetCreateThreadNotifyRoutine()\n");return Status;}
return STATUS_SUCCESS;}////////////////////////////////////////////////////////////////////////////////////////////////////////////
main.c, 这里我用事件做为通信驱动////////////////////////////////////////////////////////////////////////////////////////////////////////////// Made By ZwelL
#include <windows.h>#include <stdio.h>#include "define.h"
int main(){HANDLE hDevice; bool status; HANDLE m_hCommEvent;ULONG dwReturn;char outbuf[255];CHECKLIST CheckList;
hDevice = NULL;m_hCommEvent = NULL;hDevice = CreateFile( "\\\\.\\MyEvent",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if(hDevice == INVALID_HANDLE_VALUE){printf("createfile wrong\n");getchar();return 0;}
m_hCommEvent = CreateEvent(NULL,false,false,NULL);printf("hEvent:x\n", m_hCommEvent);
status =DeviceIoControl(hDevice,IOCTL_PASSEVENT,&m_hCommEvent,sizeof(m_hCommEvent),NULL,0,&dwReturn,NULL); if( !status){printf("IO wrong %d\n", GetLastError());getchar();return 0;}
CheckList.ONLYSHOWREMOTETHREAD=TRUE;CheckList.SHOWTHREAD=TRUE;CheckList.SHOWTERMINATETHREAD=FALSE;CheckList.SHOWTERMINATEPROCESS=FALSE;status =DeviceIoControl(hDevice,IOCTL_PASSEVSTRUCT,&CheckList,sizeof(CheckList),NULL,0,&dwReturn,NULL); if( !status){printf("IO wrong %d\n", GetLastError());getchar();return 0;}
printf(" [Process Name] [PID] [TID] [Parent Process Name] [PID] [TID]\n");while(1){ResetEvent(m_hCommEvent);WaitForSingleObject(m_hCommEvent, INFINITE);status =DeviceIoControl(hDevice,IOCTL_PASSBUF,NULL,0,&outbuf,sizeof(outbuf),&dwReturn,NULL); if( !status){printf("IO wrong %d\n", GetLastError()); getchar();return 0;}printf("%s", outbuf);}
status =DeviceIoControl(hDevice,IOCTL_UNPASSEVENT,NULL,0,NULL,0,&dwReturn,NULL); if( !status){printf("UNPASSEVENT wrong %d\n", GetLastError());getchar();return 0;}
status = CloseHandle( hDevice );status = CloseHandle(m_hCommEvent);getchar();return 0;}
/////////////////////////////////////////////////////////////////////////////////////////////////////////
define.h/////////////////////////////////////////////////////////////////////////////////////////////////////////#include "stdio.h"
#define FILE_DEVICE_EVENT 0x8000
// Define Interface reference/dereference routines for// Interfaces exported by IRP_MN_QUERY_INTERFACE
#define EVENT_IOCTL(index) \CTL_CODE(FILE_DEVICE_EVENT, index, METHOD_BUFFERED, FILE_READ_DATA)
#define IOCTL_PASSEVENT \CTL_CODE(FILE_DEVICE_EVENT, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)#define IOCTL_PASSBUF \CTL_CODE(FILE_DEVICE_EVENT, 0x802, METHOD_BUFFERED, FILE_ANY_ACCESS)#define IOCTL_UNPASSEVENT \CTL_CODE(FILE_DEVICE_EVENT, 0x803, METHOD_BUFFERED, FILE_ANY_ACCESS)#define IOCTL_PASSEVSTRUCT \CTL_CODE(FILE_DEVICE_EVENT, 0x804, METHOD_BUFFERED, FILE_ANY_ACCESS)
typedef struct //这个结构主要用于调试用{BOOL SHOWTHREAD;BOOL ONLYSHOWREMOTETHREAD;BOOL SHOWTERMINATEPROCESS;BOOL SHOWTERMINATETHREAD;}CHECKLIST, *PCHECKLIST;
////////////////////////////////////////////////////////////////////////////////////////////////////////////
先用驱动加载工具加载驱动,再运行程序,可以监视到进程线的操作信息,并且可以实现监视远线程的创建.个人认为很完美.如果您有更好的方法,请告知我一声,谢谢了. ^_^下面的运行结果:
hEvent:00000010[Process Name] [PID] [TID] [Parent Process Name] [PID] [TID]T: svchost.exe 940 3540 svchost.exe 940T: explorer.exe 1680 3564 explorer.exe 1680P: notepad.exe 3568 1684 explorer.exe 1680T: notepad.exe 3568 3572 explorer.exe 1680T: svchost.exe 1036 3576 svchost.exe 1036T: cmd.exe 3580 3084 explorer.exe 1680P: doskey.exe 3608 3084 cmd.exe 3580T: taskmgr.exe 352 3752 explorer.exe 1680T: svchost.exe 1036 2492 svchost.exe 1036T: remote.exe 3824 3828 cmd.exe 3580==============================Remote Thread :==============================T: hh.exe 3116 3832 remote.exe 3824============================================================================
参考资料:1. 编写进程/线程监视器 -sinisterhttp://www.xfocus.net/articles/200303/495.html2. 监视远程线程的创建 -一块三毛钱http://www.luocong.com/bbs/dispbbs.asp?boardID=2&ID=6895&page=23. Windows 2000源代码