首页
社区
课程
招聘
监视远程线程的创建
发表于: 2005-2-21 15:23 11536

监视远程线程的创建

2005-2-21 15:23
11536

监视远程线程的创建

作者: 一块三毛钱
邮件: 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

    这段代码是线程回调函数,也是我们的核心代码。先判断是不是一个进程的主线程创建,如果不是继续判断。是不是创建线程?如果是则继续判断。进程是否是 SYSTEM 进程?如果是则忽略。这是因为每次打开文件夹、切换文件夹 Explorer.exe 都会在 SYSTEM 进程当中创建一个远程线程,所以我们忽略它。大家可以把这两句注释掉再看看程序的输出就能明白。

    因为回调函数中只有两个参数,一个是 ProcessId 表示包含线程的进程号,另外一个是 ThreadId 表示创建的线程号。所以我们还需要找出那个创建线程的进程号,才能够比较创建线程的进程和包含线程的进程是不是同一个进程,从而判断是不是远程线程。这里就要提到一个问题,当某一个进程创建线程的时候,系统是在该进程上下文中调用我们的线程回调函数,所以我们可以通过 PsGetCurrentProcessId 函数来取得该进程号。再通过 PsGetCurrentThreadId 取得线程号,注意这个线程不是要创建的线程,而是包含创建线程代码的线程。

    代码接着又调用一个未公开函数 PsLookupProcessByProcessId 来取得某个进程的 EPROCESS 结构,EPROCESS 结构在 KmdKit 所带的 w2kundoc.inc 中有详细的说明,在 EPROCESS 结构的 ImageFileName 成员中保存着进程的名字。因为系统不同 ImageFileName 成员的偏移位置也不同,所以,根据系统的不同代码中采用了一个全局变量 g_dwOffset 来保存这个偏移。下面是判断系统的代码:

invoke        PsGetVersion, NULL, addr g_dwSystemMinorVersion, NULL, NULL
.if g_dwSystemMinorVersion==0
        mov        g_dwOffset, 1FCh
.elseif g_dwSystemMinorVersion==1
        mov        g_dwOffset, 174h
.elseif g_dwSystemMinorVersion==2
        mov        g_dwOffset, 154h
.endif

所有的工作做完之后就是把收集到的信息输出来,通过 DbgPrint 函数可以达到这个目的。

    大家可以通过 KmdKit 自带的 KmdManager 工具注册/运行本文代码生成的 RemoteThreadMonitor.sys 文件,然后通过 DbgView 或者 SoftICE 工具查看代码的输出。知道了远程线程的线程号可以用 Process Explorer 等工具杀掉远程线程。

参考资料:
(1) sinister 编写进程/线程监视器
    http://www.xfocus.net/articles/200303/495.html
(2) DDK
(3) KmdKit
    http://www.freewebs.com/four-f/
(4) KmdTut 中文翻译
    http://asm.yeah.net

附件:Monitor.rar


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (8)
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
2
学习!

高手来了。。。。
2005-2-21 15:44
0
雪    币: 238
活跃值: (250)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
3
这么多精华,我看的眼都受不了
慢慢学啊
顶!!
2005-2-21 16:27
0
雪    币: 161
活跃值: (231)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
强顶啊。老家伙们都出来了。哈哈
2005-2-21 16:29
0
雪    币: 389
活跃值: (912)
能力值: ( LV9,RANK:770 )
在线值:
发帖
回帖
粉丝
5
学无止境啊
2005-2-22 16:42
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
print输出看的比较累
2005-2-22 19:20
0
雪    币: 142
活跃值: (278)
能力值: ( LV9,RANK:140 )
在线值:
发帖
回帖
粉丝
7
前段时间学了些汇编,现在又放下了,可空还是要继续学习呀!落后好长一截了。
2005-2-22 21:29
0
雪    币: 162
活跃值: (63)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
8

路漫漫啊!!
顶!!
2005-2-23 08:03
0
雪    币: 117
活跃值: (20)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9

高手来了。。。
2005-2-23 09:47
0
游客
登录 | 注册 方可回帖
返回
//