监视远程线程的创建[收藏]
监视远程线程的创建
作者: 一块三毛钱
邮件: zhongts@163.com
日期: 2004.12.29
远程线程技术被大量的使用在木马、蠕虫等软件当中,通过在别的进程中插入线程的方式运行代码,具有相当高的隐蔽性。比如常见的 Explorer.exe 进程中有十几个线程同时运行,在其中插入一个线程后,谁也分辨不出来哪个就是插入的远程线程。本文提供了一种方法可以监视远程线程的创建活动,记录下来远程线程的 ID 等重要数据,这样就可以方便大家查出哪个进程往哪个进程中插入了远程线程。
下面分别是 IceSword v1.06 和本文代码所记录下来的远程线程创建的情况:

由于本文需要编写驱动程序,所以不熟悉驱动程序编写的读者可以找一些驱动方面的书籍先看看,这里推荐大家到罗云彬的网站上去下载翻译的 KmdTut 来看。同时把 KmdKit 也下载下来,因为本文代码用到了这个软件包。安装好 Masm32 和 KmdKit 之后才能编译本文提供的代码。如果编译代码时提示 error LNK2001: unresolved external symbol _PsRemoveCreateThreadNotifyRoutine@4 错误,则把本文提供的 ntoskrnl.lib 复制到 lib\w2k 文件夹中覆盖原文件即可。我也是刚学驱动编程,下面提供的只是一个很简单的例子,要想实用还有很多事情要做。
首先是监视线程的创建问题,然后再区分哪些是远程线程。要想监视线程的创建需要用到这样的一个函数 PsSetCreateThreadNotifyRoutine。通过该函数我们注册一个回调函数,每次当系统中有新的线程创建的时候就会调用我们的回调函数。在这个回调函数中我们就可以把所有的线程的创建记录下来。如果要监视进程的创建则还有另外一个函数 PsSetCreateProcessNotifyRoutine 可以完成这个功能。监视线程创建的回调函数的函数原型如下:
VOID
(*PCREATE_THREAD_NOTIFY_ROUTINE) (
IN HANDLE ProcessId,
IN HANDLE ThreadId,
IN BOOLEAN Create
);
ProcessId 是进程号,这里的进程号是指向包括该线程的进程,而不是创建该线程的进程。ThreadId 是将要创建的线程的线程号。Create 用来指出是创建线程还是销毁线程。监视进程创建的回调函数的函数原型如下:
VOID
(*PCREATE_PROCESS_NOTIFY_ROUTINE) (
IN HANDLE ParentId,
IN HANDLE ProcessId,
IN BOOLEAN Create
);
ParentId 是父进程号,ProcessId 是进程号,Create 表示创建还是销毁进程。
有了这两个函数我们就可以监视所有的进程和线程的创建和销毁活动了。下面来看看代码,我把主要的代码都列了出来。
DriverEntry proc uses esi, pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
LOCAL status : NTSTATUS
LOCAL pDeviceObject : PDEVICE_OBJECT
......
mov g_dwProcessId, 0
mov g_bMainThread, FALSE
lea eax, _ProcessCallback
invoke PsSetCreateProcessNotifyRoutine, eax, FALSE
lea eax, _ThreadCallback
invoke PsSetCreateThreadNotifyRoutine, eax
mov status, eax
......
DriverEntry endp
上面就是注册回调函数的代码部分,_ProcessCallback 和 _ThreadCallback 分别是进程和线程监视函数。在驱动程序的启动部分注册了回调函数,还需要在驱动的卸载部分移去注册的回调函数。代码如下:
_DriverUnload proc pDriverObject:PDRIVER_OBJECT
lea eax, _ProcessCallback
invoke PsSetCreateProcessNotifyRoutine, eax, TRUE
lea eax, _ThreadCallback
invoke PsRemoveCreateThreadNotifyRoutine, eax
invoke IoDeleteSymbolicLink, addr g_usSymbolicLinkName
mov eax, pDriverObject
invoke IoDeleteDevice, (DRIVER_OBJECT PTR [eax]).DeviceObject
ret
_DriverUnload endp
给 PsSetCreateProcessNotifyRoutine 函数的第二个参数传递 TRUE 就可以移去注册的进程回调函数。移去注册的线程回调函数需要调用 PsRemoveCreateThreadNotifyRoutine 函数,这个函数是一个未公开函数,从 Windows XP 以后提供,由于手边没有 Windows 2000 系统,不能验证,大家可以看看自己的 Windows 2000 系统中有没有这个函数。因为这个一个未公开函数,所以调用的时候不能直接调用,需要引入库才行。生成引入库的办法也很简单,利用 Masm32 软件包中自带的 inc2l 工具即可,使用办法大家可以参考 Masm32 自己生成引入库的方法。上文之所以提到要覆盖 ntoskrnl.lib 文件就是这个原因。
本来监视远程线程只需要注册一个线程回调函数即可,因为要判断是否是远程线程,要根据创建线程的进程和包含线程的进程的不同才能判断是否是远程线程。所以,我们还需要注册一个进程回调函数。
_ProcessCallback proc uses esi,ParentId:DWORD, ProcessId:DWORD, bCreate:DWORD
.if bCreate
mov eax, ProcessId
mov g_dwProcessId, eax
mov g_bMainThread, TRUE
.endif
ret
_ProcessCallback endp
这个就是进程回调函数,如果新创建一个进程,则把 g_bMainThread 设置为 TRUE,把进程 ID 保存到 g_dwProcessId 中。因为一个新的进程被创建时,它的主线程不是它自己创建的,而是它的父进程创建的。这里父进程和它自己的进程肯定不是同一个进程,但这个时候创建的主线程不是远程线程。上面的代码就是记录进程的创建,那么紧接着创建的线程就不是远程线程。
_ThreadCallback proc uses ebx esi edi, ProcessId:DWORD, ThreadId:DWORD, bCreate:DWORD
LOCAL lpParentEProcess, lpEProcess
LOCAL dwParentPID, dwParentTID
cmp g_bMainThread, TRUE
je exit_0
cmp bCreate, 0
je exit_0
cmp ProcessId, 4
je exit_0
invoke PsGetCurrentProcessId
mov dwParentPID, eax
cmp eax, ProcessId
je exit_0
invoke PsGetCurrentThreadId
mov dwParentTID, eax
invoke PsLookupProcessByProcessId, dwParentPID, addr lpParentEProcess
cmp eax, STATUS_SUCCESS
jne exit_0
invoke PsLookupProcessByProcessId, ProcessId, addr lpEProcess
cmp eax, STATUS_SUCCESS
jne exit_0
mov esi, lpParentEProcess
add esi, g_dwOffset
mov edi, lpEProcess
add edi, g_dwOffset
invoke DbgPrint, $CTA0("调用方: Name=%s PID=%d TID=%d\t\t被调用方: Name=%s PID=%d TID=%d\n"), \
esi, dwParentPID, dwParentTID, edi, ProcessId, ThreadId
exit_0:
mov g_bMainThread, FALSE
ret
_ThreadCallback endp
这段代码是线程回调函数,也是我们的核心代码。先判断是不是一个进程的主线程创建,如果不

