• 驱动开发:内核监控Register注册表回调


    在笔者前一篇文章《驱动开发:内核枚举Registry注册表回调》中实现了对注册表的枚举,本章将实现对注册表的监控,不同于32位系统在64位系统中,微软为我们提供了两个针对注册表的专用内核监控函数,通过这两个函数可以在不劫持内核API的前提下实现对注册表增加,删除,创建等事件的有效监控,注册表监视通常会通过CmRegisterCallback创建监控事件并传入自己的回调函数,与该创建对应的是CmUnRegisterCallback当注册表监控结束后可用于注销回调。

    • CmRegisterCallback 设置注册表回调
    • CmUnRegisterCallback 注销注册表回调

    默认情况下CmRegisterCallback需传入三个参数,参数一回调函数地址,参数二空余,参数三回调句柄,微软定义如下。

    // 署名权
    // right to sign one's name on a piece of work
    // PowerBy: LyShark
    // Email: me@lyshark.com
    
    // 参数1:回调函数地址
    // 参数2:无作用
    // 参数3:回调句柄
    NTSTATUS CmRegisterCallback(
      [in]           PEX_CALLBACK_FUNCTION Function,
      [in, optional] PVOID                 Context,
      [out]          PLARGE_INTEGER        Cookie
    );
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    自定义注册表回调函数MyLySharkCallback需要保留三个参数,CallbackContext回调上下文,Argument1是操作类型,Argument2定义详细结构体指针。

    NTSTATUS MyLySharkCallback(_In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2)
    
    • 1

    在自定义回调函数内Argument1则可获取到操作类型,类型是一个REG_NOTIFY_CLASS枚举结构,微软对其的具体定义如下所示。

    // 署名权
    // right to sign one's name on a piece of work
    // PowerBy: LyShark
    // Email: me@lyshark.com
    
    typedef enum _REG_NOTIFY_CLASS {
        RegNtDeleteKey,
        RegNtPreDeleteKey = RegNtDeleteKey,
        RegNtSetValueKey,
        RegNtPreSetValueKey = RegNtSetValueKey,
        RegNtDeleteValueKey,
        RegNtPreDeleteValueKey = RegNtDeleteValueKey,
        RegNtSetInformationKey,
        RegNtPreSetInformationKey = RegNtSetInformationKey,
        RegNtRenameKey,
        RegNtPreRenameKey = RegNtRenameKey,
        RegNtEnumerateKey,
        RegNtPreEnumerateKey = RegNtEnumerateKey,
        RegNtEnumerateValueKey,
        RegNtPreEnumerateValueKey = RegNtEnumerateValueKey,
        RegNtQueryKey,
        RegNtPreQueryKey = RegNtQueryKey,
        RegNtQueryValueKey,
        RegNtPreQueryValueKey = RegNtQueryValueKey,
        RegNtQueryMultipleValueKey,
        RegNtPreQueryMultipleValueKey = RegNtQueryMultipleValueKey,
        RegNtPreCreateKey,
        RegNtPostCreateKey,
        RegNtPreOpenKey,
        RegNtPostOpenKey,
        RegNtKeyHandleClose,
        RegNtPreKeyHandleClose = RegNtKeyHandleClose,
        //
        // .Net only
        //    
        RegNtPostDeleteKey,
        RegNtPostSetValueKey,
        RegNtPostDeleteValueKey,
        RegNtPostSetInformationKey,
        RegNtPostRenameKey,
        RegNtPostEnumerateKey,
        RegNtPostEnumerateValueKey,
        RegNtPostQueryKey,
        RegNtPostQueryValueKey,
        RegNtPostQueryMultipleValueKey,
        RegNtPostKeyHandleClose,
        RegNtPreCreateKeyEx,
        RegNtPostCreateKeyEx,
        RegNtPreOpenKeyEx,
        RegNtPostOpenKeyEx,
        //
        // new to Windows Vista
        //
        RegNtPreFlushKey,
        RegNtPostFlushKey,
        RegNtPreLoadKey,
        RegNtPostLoadKey,
        RegNtPreUnLoadKey,
        RegNtPostUnLoadKey,
        RegNtPreQueryKeySecurity,
        RegNtPostQueryKeySecurity,
        RegNtPreSetKeySecurity,
        RegNtPostSetKeySecurity,
        //
        // per-object context cleanup
        //
        RegNtCallbackObjectContextCleanup,
        //
        // new in Vista SP2 
        //
        RegNtPreRestoreKey,
        RegNtPostRestoreKey,
        RegNtPreSaveKey,
        RegNtPostSaveKey,
        RegNtPreReplaceKey,
        RegNtPostReplaceKey,
    
        MaxRegNtNotifyClass //should always be the last enum
    } REG_NOTIFY_CLASS;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    其中对于注册表最常用的监控项为以下几种类型,当然为了实现监控则我们必须要使用之前,如果使用之后则只能起到监视而无法做到监控的目的。

    • RegNtPreCreateKey 创建注册表之前
    • RegNtPreOpenKey 打开注册表之前
    • RegNtPreDeleteKey 删除注册表之前
    • RegNtPreDeleteValueKey 删除键值之前
    • RegNtPreSetValueKey 修改注册表之前

    如果需要实现监视则,首先CmRegisterCallback注册一个自定义回调,当有消息时则触发MyLySharkCallback其内部获取到lOperateType操作类型,并通过switch选择不同的处理例程,每个处理例程都通过GetFullPath得到注册表完整路径,并打印出来,这段代码实现如下。

    // 署名权
    // right to sign one's name on a piece of work
    // PowerBy: LyShark
    // Email: me@lyshark.com
    
    #include 
    #include 
    
    // 未导出函数声明 pEProcess -> PID
    PUCHAR PsGetProcessImageFileName(PEPROCESS pEProcess);
    
    NTSTATUS ObQueryNameString(
    	_In_ PVOID Object,
    	_Out_writes_bytes_opt_(Length) POBJECT_NAME_INFORMATION ObjectNameInfo,
    	_In_ ULONG Length,
    	_Out_ PULONG ReturnLength
    	);
    
    // 注册表回调Cookie
    LARGE_INTEGER g_liRegCookie;
    
    // 获取注册表完整路径
    BOOLEAN GetFullPath(PUNICODE_STRING pRegistryPath, PVOID pRegistryObject)
    {
    	// 判断数据地址是否有效
    	if ((FALSE == MmIsAddressValid(pRegistryObject)) ||
    		(NULL == pRegistryObject))
    	{
    		return FALSE;
    	}
    	// 申请内存
    	ULONG ulSize = 512;
    	PVOID lpObjectNameInfo = ExAllocatePool(NonPagedPool, ulSize);
    	if (NULL == lpObjectNameInfo)
    	{
    		return FALSE;
    	}
    	// 获取注册表路径
    	ULONG ulRetLen = 0;
    	NTSTATUS status = ObQueryNameString(pRegistryObject, (POBJECT_NAME_INFORMATION)lpObjectNameInfo, ulSize, &ulRetLen);
    	if (!NT_SUCCESS(status))
    	{
    		ExFreePool(lpObjectNameInfo);
    		return FALSE;
    	}
    	// 复制
    	RtlCopyUnicodeString(pRegistryPath, (PUNICODE_STRING)lpObjectNameInfo);
    	// 释放内存
    	ExFreePool(lpObjectNameInfo);
    	return TRUE;
    }
    
    // 注册表回调函数
    NTSTATUS MyLySharkCallback(_In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2)
    {
    	NTSTATUS status = STATUS_SUCCESS;
    	UNICODE_STRING ustrRegPath;
    
    	// 获取操作类型
    	LONG lOperateType = (REG_NOTIFY_CLASS)Argument1;
    	
    	// 申请内存
    	ustrRegPath.Length = 0;
    	ustrRegPath.MaximumLength = 1024 * sizeof(WCHAR);
    	ustrRegPath.Buffer = ExAllocatePool(NonPagedPool, ustrRegPath.MaximumLength);
    	if (NULL == ustrRegPath.Buffer)
    	{
    		return status;
    	}
    	RtlZeroMemory(ustrRegPath.Buffer, ustrRegPath.MaximumLength);
    	
    	// 判断操作
    	switch (lOperateType)
    	{
    		// 创建注册表之前
    	case RegNtPreCreateKey:
    	{
    		// 获取注册表路径
    		GetFullPath(&ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);
    		DbgPrint("[创建注册表][%wZ][%wZ]\n", &ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);
    		break;
    	}
    	// 打开注册表之前
    	case RegNtPreOpenKey:
    	{
    		// 获取注册表路径
    		GetFullPath(&ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);
    		DbgPrint("[打开注册表][%wZ][%wZ]\n", &ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);
    		break;
    	}
    	// 删除键之前
    	case RegNtPreDeleteKey:
    	{
    		// 获取注册表路径
    		GetFullPath(&ustrRegPath, ((PREG_DELETE_KEY_INFORMATION)Argument2)->Object);
    		DbgPrint("[删除键][%wZ] \n", &ustrRegPath);
    		break;
    	}
    	// 删除键值之前
    	case RegNtPreDeleteValueKey:
    	{
    		// 获取注册表路径
    		GetFullPath(&ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object);
    		DbgPrint("[删除键值][%wZ][%wZ] \n", &ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->ValueName);
    
    		// 获取当前进程, 即操作注册表的进程
    		PEPROCESS pEProcess = PsGetCurrentProcess();
    		if (NULL != pEProcess)
    		{
    			UCHAR *lpszProcessName = PsGetProcessImageFileName(pEProcess);
    			if (NULL != lpszProcessName)
    			{
    				DbgPrint("进程 [%s] 删除了键值对 \n", lpszProcessName);
    			}
    		}
    		break;
    	}
    	// 修改键值之前
    	case RegNtPreSetValueKey:
    	{
    		// 获取注册表路径
    		GetFullPath(&ustrRegPath, ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->Object);
    		DbgPrint("[修改键值][%wZ][%wZ] \n", &ustrRegPath, ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->ValueName);
    		break;
    	}
    	default:
    		break;
    	}
    
    	// 释放内存
    	if (NULL != ustrRegPath.Buffer)
    	{
    		ExFreePool(ustrRegPath.Buffer);
    		ustrRegPath.Buffer = NULL;
    	}
    
    	return status;
    }
    
    VOID UnDriver(PDRIVER_OBJECT driver)
    {
    	DbgPrint(("Uninstall Driver Is OK \n"));
    
    	// 注销当前注册表回调
    	if (0 < g_liRegCookie.QuadPart)
    	{
    		CmUnRegisterCallback(g_liRegCookie);
    	}
    }
    
    NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
    {
    	DbgPrint(("hello lyshark.com \n"));
    
    	// 设置注册表回调
    	NTSTATUS status = CmRegisterCallback(MyLySharkCallback, NULL, &g_liRegCookie);
    	if (!NT_SUCCESS(status))
    	{
    		g_liRegCookie.QuadPart = 0;
    		return status;
    	}
    
    	Driver->DriverUnload = UnDriver;
    	return STATUS_SUCCESS;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165

    运行驱动程序,则会输出当前系统中所有针对注册表的操作,如下图所示。

    如上的代码只能实现注册表项的监视,而如果需要监控则需要在回调函数MyLySharkCallback判断,如果指定注册表项是需要保护的则直接返回status = STATUS_ACCESS_DENIED;从而达到保护注册表的目的,核心代码如下所示。

    // 反注册表删除回调
    NTSTATUS MyLySharkCallback(_In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2)
    {
    	NTSTATUS status = STATUS_SUCCESS;
    	UNICODE_STRING ustrRegPath;
    
    	// 获取操作类型
    	LONG lOperateType = (REG_NOTIFY_CLASS)Argument1;
    	ustrRegPath.Length = 0;
    	ustrRegPath.MaximumLength = 1024 * sizeof(WCHAR);
    	ustrRegPath.Buffer = ExAllocatePool(NonPagedPool, ustrRegPath.MaximumLength);
    	if (NULL == ustrRegPath.Buffer)
    	{
    		return status;
    	}
    
    	RtlZeroMemory(ustrRegPath.Buffer, ustrRegPath.MaximumLength);
    	// 判断操作
    	switch (lOperateType)
    	{
    		// 删除键值之前
    	case RegNtPreDeleteValueKey:
    	{
    		// 获取注册表路径
    		GetFullPath(&ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object);
    		DbgPrint("[删除键值][%wZ][%wZ]\n", &ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->ValueName);
    
    		// 如果要删除指定注册表项则拒绝
    		PWCH pszRegister = L"\\REGISTRY\\MACHINE\\SOFTWARE\\lyshark.com";
    		if (wcscmp(ustrRegPath.Buffer, pszRegister) == 0)
    		{
    			DbgPrint("[lyshark] 注册表项删除操作已被拦截! \n");
    			// 拒绝操作
    			status = STATUS_ACCESS_DENIED;
    		}
    		break;
    	}
    	default:
    		break;
    	}
    
    	// 释放内存
    	if (NULL != ustrRegPath.Buffer)
    	{
    		ExFreePool(ustrRegPath.Buffer);
    		ustrRegPath.Buffer = NULL;
    	}
    
    	return status;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    运行驱动程序,然后我们尝试删除\\LyShark\HKEY_LOCAL_MACHINE\SOFTWARE\lyshark.com里面的子项,则会提示如下信息。

    当然这里的RegNtPreDeleteValueKey是指的删除操作,如果将其替换成RegNtPreSetValueKey,那么只有当注册表被创建才会拦截,此时就会变成拦截创建。

    // 拦截创建操作
    NTSTATUS MyLySharkCallback(_In_ PVOID CallbackContext, _In_opt_ PVOID Argument1, _In_opt_ PVOID Argument2)
    {
    	NTSTATUS status = STATUS_SUCCESS;
    	UNICODE_STRING ustrRegPath;
    
    	// 获取操作类型
    	LONG lOperateType = (REG_NOTIFY_CLASS)Argument1;
    
    	// 申请内存
    	ustrRegPath.Length = 0;
    	ustrRegPath.MaximumLength = 1024 * sizeof(WCHAR);
    	ustrRegPath.Buffer = ExAllocatePool(NonPagedPool, ustrRegPath.MaximumLength);
    	if (NULL == ustrRegPath.Buffer)
    	{
    		return status;
    	}
    	RtlZeroMemory(ustrRegPath.Buffer, ustrRegPath.MaximumLength);
    
    	// 判断操作
    	switch (lOperateType)
    	{
    	// 修改键值之前
    	case RegNtPreSetValueKey:
    	{
    		// 获取注册表路径
    		GetFullPath(&ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object);
    
    		// 拦截创建
    		PWCH pszRegister = L"\\REGISTRY\\MACHINE\\SOFTWARE\\lyshark.com";
    		if (wcscmp(ustrRegPath.Buffer, pszRegister) == 0)
    		{
    			DbgPrint("[lyshark] 注册表项创建操作已被拦截! \n");
    			status = STATUS_ACCESS_DENIED;
    		}
    		break;
    	}
    	default:
    		break;
    	}
    
    	// 释放内存
    	if (NULL != ustrRegPath.Buffer)
    	{
    		ExFreePool(ustrRegPath.Buffer);
    		ustrRegPath.Buffer = NULL;
    	}
    
    	return status;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    加载驱动保护,然后我们尝试在\\LyShark\HKEY_LOCAL_MACHINE\SOFTWARE\lyshark.com里面创建一个子项,则会提示创建失败。

  • 相关阅读:
    数据结构算法之——时间复杂度和空间复杂度
    【Linux系统KVM虚拟机实战】LVM逻辑卷之磁盘扩容
    (经验贴)怎么对HTML文件进行分析
    【数据结构】线性表的顺序存储结构
    面试官:你确定Redis是单线程的进程吗?
    SpringBoot 整合多数据源的事务问题
    LeetCode每日一题(1849. Splitting a String Into Descending Consecutive Values)
    vuedevtools图标不亮不能使用,显示vue.js not detected
    Centos7 gcc/g++安装以及运行程序
    阿里云新品云服务器实例,经济型e实例,价格便宜,性价比高
  • 原文地址:https://blog.csdn.net/lyshark_csdn/article/details/127548021