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和 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.回调监控注册表的更多相关文章
- Win64 驱动内核编程-14.回调监控文件
回调监控文件 使用 ObRegisterCallbacks 实现保护进程,其实稍微 PATCH 下内核,这个函数还能实现文件操作监视.但可惜只能在 WIN7X64 上用.因为在 WIN7X64 上 P ...
- Win64 驱动内核编程-12.回调监控进线程创建和退出
回调监控进线程创建和退出 两个注册回调的函数:PsSetCreateProcessNotifyRoutine 进程回调PsSetCreateThreadNotifyRoutine 线程回调分 ...
- Win64 驱动内核编程-11.回调监控进线程句柄操作
无HOOK监控进线程句柄操作 在 NT5 平台下,要监控进线程句柄的操作. 通常要挂钩三个API:NtOpenProcess.NtOpenThread.NtDuplicateObject.但是在 VI ...
- Win64 驱动内核编程-13.回调监控模块加载
回调监控模块加载 模块加载包括用户层模块(.DLL)和内核模块(.SYS)的加载.传统方法要监控这两者加在必须 HOOK 好几个函数,比如 NtCreateSection 和 NtLoadDriver ...
- Win64 驱动内核编程-32.枚举与删除注册表回调
枚举与删除注册表回调 注册表回调是一个监控注册表读写的回调,它的效果非常明显,一个回调能实现在SSDT 上 HOOK 十几个 API 的效果.部分游戏保护还会在注册表回调上做功夫,监控 service ...
- Win64 驱动内核编程-6.内核里操作注册表
内核里操作注册表 RING0 操作注册表和 RING3 的区别也不大,同样是"获得句柄->执行操作->关闭句柄"的模式,同样也只能使用内核 API 不能使用 WIN32 ...
- Win64 驱动内核编程-3.内核里使用内存
内核里使用内存 内存使用,无非就是申请.复制.设置.释放.在 C 语言里,它们对应的函数是:malloc.memcpy.memset.free:在内核编程里,他们分别对应 ExAllocatePool ...
- Win64 驱动内核编程-18.SSDT
SSDT 学习资料:http://blog.csdn.net/zfdyq0/article/details/26515019 学习资料:WIN64内核编程基础 胡文亮 SSDT(系统服务描述表),刚开 ...
- Win64 驱动内核编程-17. MINIFILTER(文件保护)
MINIFILTER(文件保护) 使用 HOOK 来监控文件操作的方法有很多,可以在 SSDT 上 HOOK 一堆和 FILE 有关的函数,也可以对 FSD 进行 IRP HOOK,不过这些方法既不 ...
随机推荐
- groovy-map.each{}
ConfigDetail postEdiUrl(TtxSession sess, String code) { return cdSvc.getByRecordTypeAndIdentifier(se ...
- Python爬虫学习二------爬虫基本原理
爬虫是什么?爬虫其实就是获取网页的内容经过解析来获得有用数据并将数据存储到数据库中的程序. 基本步骤: 1.获取网页的内容,通过构造请求给服务器端,让服务器端认为是真正的浏览器在请求,于是返回响应.p ...
- react第三方库
作者:慕课网链接:https://www.zhihu.com/question/59073695/answer/1071631250来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请 ...
- flutter简易教程
跟Java等很多语言不同的是,Dart没有public protected private等关键字,如果某个变量以下划线 _ 开头,代表这个变量在库中是私有的.Dart中变量可以以字母或下划线开头,后 ...
- python中数组切片[:,i] [i:j:k] [:-i] [i,j,:k]
逗号","分隔各个维度,":"表示各个维度内的切片,只有:表示取这个维度的全部值,举例说明如下 1 1.二维数组 2 3 X[:,0]取所有行的第0个数据,第二 ...
- 面试准备——计算机网络(TCP的三次握手和四次挥手)
一.TCP的报文结构 红色圈标出的是在讨论三次握手和四次挥手时会用到的首部字段: 顺序号(seq):TCP对从网络层传下来的数据报文进行分组,分成一段一段的TCP报文段,并对这些报文段进行编号.seq ...
- 源码级深挖AQS队列同步器
我们知道,在java中提供了两类锁的实现,一种是在jvm层级上实现的synchrinized隐式锁,另一类是jdk在代码层级实现的,juc包下的Lock显示锁,而提到Lock就不得不提一下它的核心队列 ...
- 201871030131-谢林江 实验二 个人项目—《D{0-1} KP》项目报告
项目 内容 课程班级博客链接 班级博客 这个作业要求链接 作业要求 我的课程学习目标 1.学习编写PSP2.完成个人项目实验要求3.在Github建仓 这个作业在哪些方面帮助我实现学习目标 1.首次编 ...
- 【Prolog - 2.0 基础应用】
[术语统一 terms unify] 两者统一,只需满足下面两条件之一 1.原本就是相同的 2.包含变量,这些变量可以用术语统一实例化,从而得到相等的术语 mia和mia是统一的,42和42是统一的, ...
- leetcode 刷题(数组篇)15题 三数之和 (双指针)
很有意思的一道题,值得好好思考,虽然难度只有Mid,但是个人觉得不比Hard简单 题目描述 给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b ...