回调监控注册表

在 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和 Argument2。1 Argument1  记录的是操作类型(这个参数不是指针,只是操作类型的编号而已),2 Argument2  记录的是有关操作信息的结构体指针。接下来举个例子。比如我们已经注册了一个注册表回调,当有删除注册表项的操作发生时,我们注册的回调就会被调用,Argument1 的信息是 RegNtPreDeleteKey(pre 是“操作前”的意思),Argument2 的信息是一个指向

REG_DELETE_KEY_INFORMATION 结构体的指针。当操作完成后,我们的注册表回调又会被调用一次,此时 Argument1 的信息是 RegNtPostDeleteKey(post 是“操作后”的意思),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;
}

Win64 驱动内核编程-15.回调监控注册表的更多相关文章

  1. Win64 驱动内核编程-14.回调监控文件

    回调监控文件 使用 ObRegisterCallbacks 实现保护进程,其实稍微 PATCH 下内核,这个函数还能实现文件操作监视.但可惜只能在 WIN7X64 上用.因为在 WIN7X64 上 P ...

  2. Win64 驱动内核编程-12.回调监控进线程创建和退出

    回调监控进线程创建和退出 两个注册回调的函数:PsSetCreateProcessNotifyRoutine   进程回调PsSetCreateThreadNotifyRoutine    线程回调分 ...

  3. Win64 驱动内核编程-11.回调监控进线程句柄操作

    无HOOK监控进线程句柄操作 在 NT5 平台下,要监控进线程句柄的操作. 通常要挂钩三个API:NtOpenProcess.NtOpenThread.NtDuplicateObject.但是在 VI ...

  4. Win64 驱动内核编程-13.回调监控模块加载

    回调监控模块加载 模块加载包括用户层模块(.DLL)和内核模块(.SYS)的加载.传统方法要监控这两者加在必须 HOOK 好几个函数,比如 NtCreateSection 和 NtLoadDriver ...

  5. Win64 驱动内核编程-32.枚举与删除注册表回调

    枚举与删除注册表回调 注册表回调是一个监控注册表读写的回调,它的效果非常明显,一个回调能实现在SSDT 上 HOOK 十几个 API 的效果.部分游戏保护还会在注册表回调上做功夫,监控 service ...

  6. Win64 驱动内核编程-6.内核里操作注册表

    内核里操作注册表 RING0 操作注册表和 RING3 的区别也不大,同样是"获得句柄->执行操作->关闭句柄"的模式,同样也只能使用内核 API 不能使用 WIN32 ...

  7. Win64 驱动内核编程-3.内核里使用内存

    内核里使用内存 内存使用,无非就是申请.复制.设置.释放.在 C 语言里,它们对应的函数是:malloc.memcpy.memset.free:在内核编程里,他们分别对应 ExAllocatePool ...

  8. Win64 驱动内核编程-18.SSDT

    SSDT 学习资料:http://blog.csdn.net/zfdyq0/article/details/26515019 学习资料:WIN64内核编程基础 胡文亮 SSDT(系统服务描述表),刚开 ...

  9. Win64 驱动内核编程-17. MINIFILTER(文件保护)

     MINIFILTER(文件保护) 使用 HOOK 来监控文件操作的方法有很多,可以在 SSDT 上 HOOK 一堆和 FILE 有关的函数,也可以对 FSD 进行 IRP HOOK,不过这些方法既不 ...

随机推荐

  1. Java基础:重文本markdown

    说一说markdown 前言 在系统学习Java等开发语言之前,我们应该养成写"日记"的习惯,也就是写博客:写博客的地方有博客园,csdn等.而写博客又得知道markdown重文本 ...

  2. Apache配置 4.访问日志

    (1)介绍 访问日志作用很大,不仅可以记录网站的访问情况,还可以在网站有异常发生时帮助我们定位问题. (2)配置 # vi /usr/local/apache2.4/conf/extra/httpd- ...

  3. java 动态规划解决上楼梯问题

    问题描述: 你正在爬楼梯. 它需要n步才能达到顶峰. 每次你可以爬1或2步. 您可以通过多少不同的方式登顶? 注意:给定n将是一个正整数. Example 1: Input: 2 Output: 2 ...

  4. crf++分词

    1.linux下安装crf工具包 先下载CRF++-0.58.tar.xz,在Linux环境下安装CRF工具包 https://github.com/taku910/crfpp 解压到某一个目录下面 ...

  5. [倍增]luogu P4155 [SCOI2015]国旗计划

    题面 https://www.luogu.com.cn/problem/P4155 问在环上最少取多少个区间能完全覆盖环 分析 首先发现是环,先把端点变为2n方便处理,注意离散化 其次要删去贡献不如其 ...

  6. 「HTML+CSS」--自定义加载动画【007】

    前言 Hello!小伙伴! 首先非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出- 哈哈 自我介绍一下 昵称:海轰 标签:程序猿一只|C++选手|学生 简介:因C语言结识编程,随后转入计算机 ...

  7. [BFS]骑士旅行

    骑士旅行 Description 在一个n m 格子的棋盘上,有一只国际象棋的骑士在棋盘的左下角 (1;1)(如图1),骑士只能根据象棋的规则进行移动,要么横向跳动一格纵向跳动两格,要么纵向跳动一格横 ...

  8. vs2019新建数据库后插入中文变问号

    在使用VS创建了数据库后如果直接给字符类型插入中文内容的话查询结果插入的中文会以"?"的格式展现. 原因是因为默认创建的数据库的排序类型为拉丁文不支持中文. 所以需要讲这个排序的字 ...

  9. 数据库MySQL四

    一.测试题 二.复习 说明:sql中的函数分为单行函数和分组函数 调用语法:select 函数名(实参列表); 1>字符函数 concat(str1,str2,..):拼接字符 substr(s ...

  10. linux 在某个路径下,查找某个文件

    find /cephfs/netdisk/ -name "*.sql"