回调监控注册表

在 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. git的工作管理和基础操作

    git的工作管理和基础操作 在本地创建git仓库管理我们的代码 初次使用git,先在本地配置一些基础信息 $ git config -l $ git config --global user.name ...

  2. 精通模块化JavaScript

    近日读了一本名为<精通模块化JavaScript>的书,并记录了其中的精髓. 一.模块化思维 精通模块化开发并不是指要遵循一套定义明确的规则,而是指能够将自己置身于使用者的角度,为可能即将 ...

  3. 攻防世界 reverse evil

    这是2017 ddctf的一道逆向题, 挑战:<恶意软件分析> 赛题背景: 员工小A收到了一封邮件,带一个文档附件,小A随手打开了附件.随后IT部门发现小A的电脑发出了异常网络访问请求,进 ...

  4. 提高ASP.NET Web应用性能的24种方法和技巧

    那性能问题到底该如何解决?以下是应用系统发布前,作为 .NET 开发人员需要检查的点. 1.debug=「false」 当创建 ASP.NET Web应用程序,默认设置为「true」.开发过程中,设置 ...

  5. Typora常用编辑方法-一个能将写博客变作享受的工具

    1,标题 ctrl+数字(1~5) 2,序号 数字序号 数字 + . +空格,之后回车换行会自动产生数字序号 非数字序号 有三种 实心圆 ,非实心圆与实心方框 都是 +空格 ,之后按tab键向内缩进, ...

  6. 我的开源GIS解决方案之路

    好久没更新了,因为我在--憋--大--招--,对,就是今天这篇. 今天跟大家分享一下我的开源GIS解决方案经历. --额-- 考虑到单聊技术解决方案你可能会很快睡着,所以我今天会把重点放在我封装地图A ...

  7. Java例题_26 请输入星期几的第一个字母来判断一下是星期几,如果第一个字母一样,则继续 判断第二个字母。

    1 /*26 [程序 26 求星期] 2 题目:请输入星期几的第一个字母来判断一下是星期几,如果第一个字母一样,则继续 判断第二个字母. 3 程序分析:用情况语句比较好,如果第一个字母一样,则判断用情 ...

  8. 【设计模式】- 生成器模式(Builder)

    生成器模式 建造者模式.Builder 生成器模式 也叫建造者模式,可以理解成可以分步骤创建一个复杂的对象.在该模式中允许你使用相同的创建代码生成不同类型和形式的对象. 生成器的结构模式 生成器(Bu ...

  9. 201871010130-周学铭 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告

    项目 内容 课程班级博客链接 18卓越班 这个作业要求链接 实验三结对编程要求 我的课程学习目标 体验软件项目开发中的两人合作,练习结对编程(Pair programming).掌握Github协作开 ...

  10. AutoAssign源码分析

    目录 AutoAssign源码分析 一. 简介 二. 论文理论 2.1 联合表示 2.2 正样本权重 2.3 负样本权重 2.4 总的loss 2.5 补充loss 三. 论文代码 四. 总结 五. ...