基于CmRegisterCallback实现监控监控注册表并拒绝注册表操作

大葱

发布日期: 2019-02-18 17:15:57 浏览量: 2947
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

背景

对于内核层实现监控进程的创建或者退出,你可能第一时间会想到 HOOK 内核函数 ZwOpenProcess、ZwTerminateProcess 等。确定,在内核层中的 HOOK 已经给人留下太多深刻的印象了,有 SSDT HOOK、Inline HOOK、IRP HOOK、过滤驱动等等。

但是,Windows 其实给我们提供现成的内核函数接口,方便我们在内核下监控用户层上进程的创建和退出的情况。即 PsSetCreateProcessNotifyRoutineEx 内核函数,可以设置一个回调函数,来监控进程的创建和退出,同时还能控制是否允许创建进程。

现在,本文就使用 PsSetCreateProcessNotifyRoutineEx 实现监控进程的创建的实现过程和原理进行整理,形成文档,分享给大家。

函数介绍

CmRegisterCallback 函数

CmRegisterCallback例程对于Windows Vista和更高版本的操作系统而言已经过时了。 改用CmRegisterCallbackEx。
CmRegisterCallback例程注册一个RegistryCallback例程。

函数声明

  1. NTSTATUS CmRegisterCallback(
  2. _In_ PEX_CALLBACK_FUNCTION Function,
  3. _In_opt_ PVOID Context,
  4. _Out_ PLARGE_INTEGER Cookie
  5. );

参数

Function[in]
指向RegistryCallback例程的指针。

Context[in]
配置管理器将作为CallbackContext参数传递给RegistryCallback例程的驱动程序定义的值。

Cookie [out]
指向LARGE_INTEGER变量的指针,该变量接收标识回调例程的值。 当您注销回调例程时,将此值作为Cookie参数传递给CmUnRegisterCallback。

返回值

  • 成功,则返回 STATUS_SUCCESS;否则,返回其它失败错误码 NTSTATUS。

PEX_CALLBACK_FUNCTION 回调函数

函数声明

  1. EX_CALLBACK_FUNCTION RegistryCallback;
  2. NTSTATUS RegistryCallback(
  3. _In_ PVOID CallbackContext,
  4. _In_opt_ PVOID Argument1,
  5. _In_opt_ PVOID Argument2
  6. )
  7. { ... }

参数

  • CallbackContext [in]
    当注册该RegistryCallback例程时,驱动程序作为Context参数传递给CmRegisterCallback或CmRegisterCallbackEx的值。
  • Argument1 [in]
    一个REG_NOTIFY_CLASS类型的值,用于标识正在执行的注册表操作的类型,以及是否在执行注册表操作之前或之后调用RegistryCallback例程。
  • Argument2 [in]
    指向包含特定于注册表操作类型的信息的结构的指针。 结构类型取决于Argument1的REG_NOTIFY_CLASS类型值,如下表所示。 有关哪些REG_NOTIFY_CLASS类型的值可用于哪些操作系统版本的信息,请参阅REG_NOTIFY_CLASS。

返回值

  • 成功,则返回 STATUS_SUCCESS;否则,返回其它失败错误码 NTSTATUS。

实现原理

我们根据上面的函数介绍,大概知道实现的流程了吧。对于设置回调函数,直接调用 CmRegisterCallback 函数来设置就好。传入设置的回调函数名称以及获取回调函数的Cookie。这样,就可以成功设置注册表监控的回调函数了。

那么,我们的回调函数也并不复杂,它的函数声明为:

  1. NTSTATUS RegistryCallback(
  2. _In_ PVOID CallbackContext,
  3. _In_opt_ PVOID Argument1,
  4. _In_opt_ PVOID Argument2
  5. );

回调函数的名称可以任意,但是返回值类型以及函数参数类型必须是固定的,不能变更。回调函数的第一个参数 ParentId 表示从 CmRegisterCallback 传入的参数值;第二个参数 Argument1表示在执行的注册表操作的类型;第三个参数 Argument2 表示指向 Argument1 操作类型对应的存储数据的结构体指针,不同的操作类型,对应存储数据的结构体也不相同。

当我们需要拒绝对注册表指定键的指定操作时,直接修改回调函数的返回值,返回除 STATUS_SUCCESS 之外的错误码,如 STATUS_ACCESS_DENIED。那么,系统就会拒绝操作相应注册表。这需要我们先根据回调函数的第一个参数 Argument1 判断注册表的操作类型,然后,根据 Argument2 参数获取操作类型对应的结构体数据,从结构体数据中,可以获取注册表路径对象,并调用 ObQueryNameString 函数根据路径对象获取字符串表示的路径。以此判断是否要拒绝操作的注册表路径,若是,则返回 STATUS_ACCESS_DENIED 拒绝操作。

当我们要删除回调设置的时候,只需要调用 CmUnRegisterCallback 函数,传入从 CmRegisterCallback 函数中获取的 Cookie。这样,就可以成功删除设置的回调函数了。

编码实现

设置回调

  1. // 设置回调函数
  2. NTSTATUS SetRegisterCallback()
  3. {
  4. NTSTATUS status = CmRegisterCallback(RegisterMonCallback, NULL, &g_liRegCookie);
  5. if (!NT_SUCCESS(status))
  6. {
  7. ShowError("CmRegisterCallback", status);
  8. g_liRegCookie.QuadPart = 0;
  9. return status;
  10. }
  11. return status;
  12. }

回调函数

  1. // 回调函数
  2. NTSTATUS RegisterMonCallback(
  3. _In_ PVOID CallbackContext,
  4. // 操作类型(只是操作编号,不是指针)
  5. _In_opt_ PVOID Argument1,
  6. // 操作详细信息的结构体指针
  7. _In_opt_ PVOID Argument2
  8. )
  9. {
  10. NTSTATUS status = STATUS_SUCCESS;
  11. UNICODE_STRING ustrRegPath;
  12. // 获取操作类型
  13. LONG lOperateType = (REG_NOTIFY_CLASS)Argument1;
  14. // 申请内存
  15. ustrRegPath.Length = 0;
  16. ustrRegPath.MaximumLength = 1024 * sizeof(WCHAR);
  17. ustrRegPath.Buffer = ExAllocatePool(NonPagedPool, ustrRegPath.MaximumLength);
  18. if (NULL == ustrRegPath.Buffer)
  19. {
  20. ShowError("ExAllocatePool", 0);
  21. return status;
  22. }
  23. // 判断操作
  24. switch (lOperateType)
  25. {
  26. // 创建注册表之前
  27. case RegNtPreCreateKey:
  28. {
  29. // 获取注册表路径
  30. GetRegisterObjectCompletePath(&ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);
  31. // 判断是否是被保护的注册表
  32. if (IsProtectReg(ustrRegPath))
  33. {
  34. // 拒绝操作
  35. status = STATUS_ACCESS_DENIED;
  36. }
  37. // 显示
  38. DbgPrint("[RegNtPreCreateKey][%wZ][%wZ]\n", &ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);
  39. break;
  40. }
  41. // 打开注册表之前
  42. case RegNtPreOpenKey:
  43. {
  44. // 获取注册表路径
  45. GetRegisterObjectCompletePath(&ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject);
  46. // 判断是否是被保护的注册表
  47. if (IsProtectReg(ustrRegPath))
  48. {
  49. // 拒绝操作
  50. status = STATUS_ACCESS_DENIED;
  51. }
  52. // 显示
  53. DbgPrint("[RegNtPreOpenKey][%wZ][%wZ]\n", &ustrRegPath, ((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName);
  54. break;
  55. }
  56. // 删除键之前
  57. case RegNtPreDeleteKey:
  58. {
  59. // 获取注册表路径
  60. GetRegisterObjectCompletePath(&ustrRegPath, ((PREG_DELETE_KEY_INFORMATION)Argument2)->Object);
  61. // 判断是否是被保护的注册表
  62. if (IsProtectReg(ustrRegPath))
  63. {
  64. // 拒绝操作
  65. status = STATUS_ACCESS_DENIED;
  66. }
  67. // 显示
  68. DbgPrint("[RegNtPreDeleteKey][%wZ]\n", &ustrRegPath);
  69. break;
  70. }
  71. // 删除键值之前
  72. case RegNtPreDeleteValueKey:
  73. {
  74. // 获取注册表路径
  75. GetRegisterObjectCompletePath(&ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object);
  76. // 判断是否是被保护的注册表
  77. if (IsProtectReg(ustrRegPath))
  78. {
  79. // 拒绝操作
  80. status = STATUS_ACCESS_DENIED;
  81. }
  82. // 显示
  83. DbgPrint("[RegNtPreDeleteValueKey][%wZ][%wZ]\n", &ustrRegPath, ((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->ValueName);
  84. break;
  85. }
  86. // 修改键值之前
  87. case RegNtPreSetValueKey:
  88. {
  89. // 获取注册表路径
  90. GetRegisterObjectCompletePath(&ustrRegPath, ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->Object);
  91. // 判断是否是被保护的注册表
  92. if (IsProtectReg(ustrRegPath))
  93. {
  94. // 拒绝操作
  95. status = STATUS_ACCESS_DENIED;
  96. }
  97. // 显示
  98. DbgPrint("[RegNtPreSetValueKey][%wZ][%wZ]\n", &ustrRegPath, ((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->ValueName);
  99. break;
  100. }
  101. default:
  102. break;
  103. }
  104. // 释放内存
  105. if (NULL != ustrRegPath.Buffer)
  106. {
  107. ExFreePool(ustrRegPath.Buffer);
  108. ustrRegPath.Buffer = NULL;
  109. }
  110. return status;
  111. }

删除回调

  1. // 删除回调函数
  2. VOID RemoveRegisterCallback()
  3. {
  4. if (0 < g_liRegCookie.QuadPart)
  5. {
  6. CmUnRegisterCallback(g_liRegCookie);
  7. }
  8. }

程序测试

在 Win7 32 位系统下,驱动程序正常执行:

在 Win10 64 位系统下,驱动程序正常执行:

总结

这个程序实现起来并不复杂,关键是对 CmRegisterCallback 函数要理解透彻,理解清楚回调函数中。其中,要注意一点就是,在回调函数中,不同的注册表操作类型 Argument1,Argument2指向的对应的结构体类型指针也不相同。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download CmRegisterCallback_Test.7z ( 10.30kb, 71次下载 )

发送私信

这一切都不是我的,但总有一天,会是我的

83
文章数
69
评论数
最近文章
eject