网络安全 频道

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

用PsSetCreateProcessNotifyRoutine,PsSetCreateThreadNotifyRoutine来进行进程线程监控我想大家已经都非常熟练了。前一段时间看到网上有人在研究监视远线程的文章,比较有意思。就写代码玩一玩。这之中就出现了一些问题,比方说直接用sinister的代码的话,是不能动态卸载的,因为他在安装了进线程监视函数后没有进行清除动作,造成在动态卸载时蓝屏。

BUGCHECK为0x000000ce,错误码为:

DRIVER_UNLOADED_WITHOUT_CANCELLING_PENDING_OPERATIONS

很显然,在驱动退出后,一些进线程操作仍然在访问原来的地址,造成出错。在XP后,微软给出了一个函数PsRemoveCreateThreadNotifyRoutine用来清除线程监视函数(清除进程监视的就是PsSetCreateProcessNotifyRoutine)。我一直奇怪ICESWORD在Windows2000中是怎么做到进线程监视的。后来才发现,在运行icesword后释放出一个detport.sys文件,然后一直在系统中存在着没有卸载掉。只是把它隐藏了而已。这不是个好消息,难道我为了测试一个驱动,测试一次就得重启一次吗?呵呵,肯定不是啊,所以想办法搞定它。

我们来看一下进线程监视在底层是如何实现的,在Windows2000源代码中先找到创建线程的函数实现:

//////////////////////////////////////////////////////////////////////////////////////////
//
//   \win2k\private\ntos\ps\create.h
//
//////////////////////////////////////////////////////////////////////////////////////////
NTSTATUS
PspCreateThread(
    ...
    ...
    )
{
    ...
        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]数组设置值,该值就是监视函数的地址。

NTSTATUS
PsSetCreateThreadNotifyRoutine(
    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定义如下:

typedef
VOID
(*PCREATE_THREAD_NOTIFY_ROUTINE)(
    IN HANDLE ProcessId,
    IN HANDLE ThreadId,
    IN BOOLEAN Create
    );

相应的,进程的结构也是一样的。

通过上面,我们可以看到,只要我们找出该函数数组地址,在我们退出驱动时先将其全部清零,清零的大小为PSP_MAX_CREATE_THREAD_NOTIFY。

这样的话下一次的进线程操作就不会调用这个函数指针了,也就让系统回到正常。我们再通过PsSetCreateProcessNotifyRoutine来验证一下:

NTSTATUS
PsSetCreateProcessNotifyRoutine(
    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 20
nt!PsRemoveCreateThreadNotifyRoutine:
80651d7b 53               push    ebx
80651d7c 56               push    esi
80651d7d 57               push    edi
80651d7e 33db             xor     ebx,ebx
80651d80 bf400f5780       mov     edi,0x80570f40    //起始地址
80651d85 57               push    edi
80651d86 e8a7500100 call nt!ExWaitForRundownProtectionRelease+0x5cf (80666e32)
80651d8b 8bf0             mov     esi,eax
80651d8d 85f6             test    esi,esi
80651d8f 7420         jz nt!PsRemoveCreateThreadNotifyRoutine+0x36 (80651db1)
80651d91 56               push    esi
80651d92 e8ba1bffff      call nt!IoReportTargetDeviceChange+0x7aa0 (80643951)
80651d97 3b442410         cmp     eax,[esp+0x10]
80651d9b 750d        jnz nt!PsRemoveCreateThreadNotifyRoutine+0x2f (80651daa)
80651d9d 56               push    esi
80651d9e 6a00             push    0x0
80651da0 57               push    edi
80651da1 e8c54f0100 call nt!ExWaitForRundownProtectionRelease+0x508 (80666d6b)
80651da6 84c0             test    al,al
80651da8 751b        jnz nt!PsRemoveCreateThreadNotifyRoutine+0x4a (80651dc5)
80651daa 56               push    esi
80651dab 57               push    edi
80651dac e892510100 call nt!ExWaitForRundownProtectionRelease+0x6e0 (80666f43)
80651db1 43               inc     ebx
80651db2 83c704           add     edi,0x4
80651db5 83fb08           cmp     ebx,0x8    //看是否到了最大数(8)
80651db8 72cb          jb nt!PsRemoveCreateThreadNotifyRoutine+0xa (80651d85)
80651dba b87a0000c0       mov     eax,0xc000007a
80651dbf 5f               pop     edi
80651dc0 5e               pop     esi
80651dc1 5b               pop     ebx
80651dc2 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进程的ID
PWCHAR                    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, // Flags
        NULL, // Root directory
        NULL); // 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:%08x\n",Status);
        return Status;
    }
    else
    {
        RtlCopyMemory((PCHAR)Value, (PCHAR)valueInfoP->Data, valueInfoP->DataLength);
        ReturnValue = 1;
    }

    if(!valueInfoP);
        ExFreePool(valueInfoP);
    ZwClose(KeyHandle);
    return ReturnValue;
}

0
相关文章