zoukankan      html  css  js  c++  java
  • Win64 驱动内核编程-15.回调监控注册表

    回调监控注册表

        在 WIN32 平台上,监控注册表的手段通常是 SSDT HOOK。不过用 SSDT HOOK

    的方式监控注册表实在是太麻烦了,要 HOOK 一大堆函数,还要处理一些 NT6 系统有而 NT5 系统没有的函数。下面我就来介绍一种完胜 SSDT HOOK 监控注册表的方法,效果跟 SSDT HOOK 一样好。这个方法就是使用微软推荐的注册表监控函数:CmRegisterCallbak。此函数其实在 XP 系统上就有了,不过那时功能不完善,只能简单的禁止或允许,无法获得完整的注册表修改信息(即做不到监控);在 VISTA 以及之后的系统里,微软对此函数做了相当大的改进,使之能获得完整的注册表修改信息。本文最后实现的效果就是:把“注册表编辑器”(regedit.exe)所有对注册表添加、删除、重命名的操作都通过 DbgView 打印

    出来,并拒绝访问(只针对 regedit.exe 是因为系统对注册表的操作太频繁了,这么做是为了方便大家实验)。

     

    函数原型:

    NTSTATUS CmRegisterCallback

    (

    _In_ PEX_CALLBACK_FUNCTION Function,

    _In_opt_ PVOID Context,

    _Out_ PLARGE_INTEGER Cookie

    );

    这三个参数分别为:回调函数的地址,随便设置的值(直接传入 NULL 即可),回调的句柄。相反还有个函数用于销毁回调,它是 CmUnRegisterCallback,原型如下

    NTSTATUS CmUnRegisterCallback( _In_ LARGE_INTEGER Cookie);

    CmUnRegisterCallback 函数唯一的的参数就是 cookie,也就是我所说的“回调的句柄”。创建和销毁回调的代码如下:


    LARGE_INTEGER CmHandle;

    NTSTATUS CmSt;

    CmSt=CmRegisterCallback(RegistryCallback,NULL,&CmHandle);

    if(NT_SUCCESS(CmSt))

        DbgPrint("CmRegisterCallback SUCCESS!");

    else

        DbgPrint("CmRegisterCallback Failed!");

    CmUnRegisterCallback(CmHandle);


    接下来看看回调函数的原型:

    NTSTATUS RegistryCallback

    (

    _In_ PVOID CallbackContext,

    _In_opt_ PVOID Argument1, //操作类型(只是操作编号,不是指针)

    _In_opt_ PVOID Argument2 //操作详细信息的结构体指针

    )

    CallbackContext 基本可以忽略,重要的就是下面的两个参数 Argument1和 Argument21 Argument1  记录的是操作类型(这个参数不是指针,只是操作类型的编号而已),2 Argument2  记录的是有关操作信息的结构体指针。接下来举个例子。比如我们已经注册了一个注册表回调,当有删除注册表项的操作发生时,我们注册的回调就会被调用,Argument1 的信息是 RegNtPreDeleteKeypre 是“操作前”的意思),Argument2 的信息是一个指向

    REG_DELETE_KEY_INFORMATION 结构体的指针。当操作完成后,我们的注册表回调又会被调用一次,此时 Argument1 的信息是 RegNtPostDeleteKeypost 是“操作后”的意思),Argument2 的信息是一个指向REG_POST_OPERATION_INFORMATION 结构体的指针。在所有的结构体里,有一项是肯定有的,就是 Object,它是你操作了那个项或者根项的对象指针(相对于新建项而言,就是根项的对象指针;相对于新建/设置/删除/重命名键值和删除项而言,就是项的对象指针)。

        需要注意的是,此函数如果返回 STATUS_SUCCESS,注册表操作就会继续执

    行,如果返回 STATUS_ACCESS_DENIED,注册表操作就不会执行执行了。这样子

    就达到了“监控”的效果。最终效果如下:



    最后附上测试代码:

     

    注册回调:
    CmSt=CmRegisterCallback(RegistryCallback,NULL,&CmHandle);
    注销回调:
    if(NT_SUCCESS(CmSt))
    CmUnRegisterCallback(CmHandle);
     
    回调函数处理,同时禁止regedit一切操作:
    #include <ntddk.h>
     
    #define REGISTRY_POOL_TAG 'pRE'
     
    NTKERNELAPI NTSTATUS ObQueryNameString
    (
        IN  PVOID Object,
        OUT POBJECT_NAME_INFORMATION ObjectNameInfo,
        IN  ULONG Length,
        OUT PULONG ReturnLength
    );
     
    NTKERNELAPI NTSTATUS RtlUnicodeStringCopy
    (
      __out  PUNICODE_STRING DestinationString,
      __in   PUNICODE_STRING SourceString
    );
     
    NTKERNELAPI UCHAR* PsGetProcessImageFileName(PEPROCESS Process);
     
    LARGE_INTEGER CmHandle;
    NTSTATUS CmSt;
     
    BOOLEAN IsProcessName(char *string, PEPROCESS eprocess)
    {
    char xx[260]={0};
    strcpy(xx,PsGetProcessImageFileName(eprocess));
    if(!_stricmp(xx,string))
    return TRUE;
    else
    return FALSE;
    }
     
    BOOLEAN GetRegistryObjectCompleteName(PUNICODE_STRING pRegistryPath, PUNICODE_STRING pPartialRegistryPath, PVOID pRegistryObject)
    {
    BOOLEAN foundCompleteName = FALSE;
    BOOLEAN partial = FALSE;
    if((!MmIsAddressValid(pRegistryObject)) || (pRegistryObject == NULL))
    return FALSE;
    /* Check to see if the partial name is really the complete name */
    if(pPartialRegistryPath != NULL)
    {
    if( (((pPartialRegistryPath->Buffer[0] == '\') || (pPartialRegistryPath->Buffer[0] == '%')) ||
            ((pPartialRegistryPath->Buffer[0] == 'T') && (pPartialRegistryPath->Buffer[1] == 'R') && 
    (pPartialRegistryPath->Buffer[2] == 'Y') && (pPartialRegistryPath->Buffer[3] == '\'))) )
    {
    RtlCopyUnicodeString(pRegistryPath, pPartialRegistryPath);
    partial = TRUE;
    foundCompleteName = TRUE;
    }
    }
    if(!foundCompleteName)
    {
    /* Query the object manager in the kernel for the complete name */
    NTSTATUS status;
    ULONG returnedLength;
    PUNICODE_STRING pObjectName = NULL;
    status = ObQueryNameString(pRegistryObject, (POBJECT_NAME_INFORMATION)pObjectName, 0, &returnedLength );
    if(status == STATUS_INFO_LENGTH_MISMATCH)
    {
    pObjectName = ExAllocatePoolWithTag(NonPagedPool, returnedLength, REGISTRY_POOL_TAG);
    status = ObQueryNameString(pRegistryObject, (POBJECT_NAME_INFORMATION)pObjectName, returnedLength, &returnedLength );
    if(NT_SUCCESS(status))
    {
    RtlCopyUnicodeString(pRegistryPath, pObjectName);
    foundCompleteName = TRUE;
    }
    ExFreePoolWithTag(pObjectName, REGISTRY_POOL_TAG);
    }
    }
    return foundCompleteName;
    }
     
    NTSTATUS RegistryCallback
    (
        IN PVOID CallbackContext,
        IN PVOID Argument1,
        IN PVOID Argument2
    )
    {
    long type;
    NTSTATUS CallbackStatus=STATUS_SUCCESS;
    UNICODE_STRING registryPath;
    registryPath.Length = 0;
    registryPath.MaximumLength = 2048 * sizeof(WCHAR);
    registryPath.Buffer = ExAllocatePoolWithTag(NonPagedPool, registryPath.MaximumLength, REGISTRY_POOL_TAG);
    if(registryPath.Buffer == NULL)
    return STATUS_SUCCESS;
    type = (REG_NOTIFY_CLASS)Argument1;
    switch(type)
    {
    case RegNtPreCreateKeyEx:	//出现两次是因为一次是OpenKey,一次是createKey
    {
    if(IsProcessName("regedit.exe",PsGetCurrentProcess()))
    {
    GetRegistryObjectCompleteName(®istryPath,NULL,((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);
    DbgPrint("[RegNtPreCreateKeyEx]KeyPath: %wZ",®istryPath);	//新键的路径
    DbgPrint("[RegNtPreCreateKeyEx]KeyName: %wZ",
    ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);//新键的名称
    CallbackStatus=STATUS_ACCESS_DENIED;
    }
    break;
    }
    case RegNtPreDeleteKey:
    {
    if(IsProcessName("regedit.exe",PsGetCurrentProcess()))
    {
    GetRegistryObjectCompleteName(®istryPath,NULL,((PREG_DELETE_KEY_INFORMATION)Argument2)->Object);
    DbgPrint("[RegNtPreDeleteKey]%wZ",®istryPath);	//新键的路径
    CallbackStatus=STATUS_ACCESS_DENIED;
    }
    break;
    }
    case RegNtPreSetValueKey:
    {
    if(IsProcessName("regedit.exe",PsGetCurrentProcess()))
    {
    GetRegistryObjectCompleteName(®istryPath,NULL,((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->Object);
    DbgPrint("[RegNtPreSetValueKey]KeyPath: %wZ",®istryPath);
    DbgPrint("[RegNtPreSetValueKey]ValName: %wZ",((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->ValueName);
    CallbackStatus=STATUS_ACCESS_DENIED;
    }
    break;
    }
    case RegNtPreDeleteValueKey:
    {
    if(IsProcessName("regedit.exe",PsGetCurrentProcess()))
    {
    GetRegistryObjectCompleteName(®istryPath,NULL,((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object);
    DbgPrint("[RegNtPreDeleteValueKey]KeyPath: %wZ",®istryPath);
    DbgPrint("[RegNtPreDeleteValueKey]ValName: %wZ",((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->ValueName);
    CallbackStatus=STATUS_ACCESS_DENIED;
    }
    break;
    }
    case RegNtPreRenameKey:
    {
    if(IsProcessName("regedit.exe",PsGetCurrentProcess()))
    {
    GetRegistryObjectCompleteName(®istryPath,NULL,((PREG_RENAME_KEY_INFORMATION)Argument2)->Object);
    DbgPrint("[RegNtPreRenameKey]KeyPath: %wZ",®istryPath);
    DbgPrint("[RegNtPreRenameKey]NewName: %wZ",((PREG_RENAME_KEY_INFORMATION)Argument2)->NewName);
    CallbackStatus=STATUS_ACCESS_DENIED;
    }
    break;
    }
    //『注册表编辑器』里的“重命名键值”是没有直接函数的,是先SetValueKey再DeleteValueKey
    default:
    break;
    }
    if(registryPath.Buffer != NULL)
    ExFreePoolWithTag(registryPath.Buffer, REGISTRY_POOL_TAG);
    return CallbackStatus;
    }
     

  • 相关阅读:
    还原大备份mysql文件失败的解决方法
    第一次招聘
    Data Transformation Services (DTS) Package Example
    .net Page 执行事件顺序
    批量修改相同結構的table layout
    js 中exec、test、match、search、replace、split用法
    存储过程分页,按多条件排序(原创)
    Lisp 的本质 [转]
    【原创】jQuery方法的写法不同导致的兼容性
    WinForm 图片变灰方法
  • 原文地址:https://www.cnblogs.com/csnd/p/12062017.html
Copyright © 2011-2022 走看看