回调监控注册表

在 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. C++树——遍历二叉树

    在讲遍历之前,我们要先创建一个树: #include <iostream> using namespace std; typedef struct node; typedef node * ...

  2. apicloud打包的ios证书的获取方法

    apicloud云编译的时候,需要测试证书或者正式证书进行编译. 那么这个证书是怎么来的呢?通过什么渠道可以获取呢? 这里我介绍下使用香蕉云编这个在线工具来生成: 1.登录香蕉云编,生成证书的csr文 ...

  3. CMDB项目要点总结之中控机

    1.基于paramiko对远程主机执行命令操作 秘钥形式 private_key = paramiko.RSAKey.from_private_key_file('c:/Users/用户名/.ssh/ ...

  4. Django之缓存、信号和图片验证码、ORM性能

    一. 缓存 1. 介绍 缓存通俗来说:就是把数据先保存在某个地方,下次再读取的时候不用再去原位置读取,让访问速度更快. 缓存机制图解 2.Django中提供了6种缓存方式 1. 开发调试 2. 内存 ...

  5. 别再面向 for 循环编程了,JDK 自带的观察者模式就很香!

    大家好,你还在面向 for 循环编程吗? 还有谁不会用观察者模式吗? 本篇栈长带来<观察者模式>理论及实战- 什么是观察者模式? 观察者模式(Observer Pattern)定义了对象间 ...

  6. ffmpeg第五篇:让水印图片旋转起来

    这篇把上次挖的坑填上 ffmpeg正式篇的上一篇(传送门)说了,这一篇要让水印旋转起来,但是后面有事情一直没有时间搞,今天,它来了............ 如果想实现旋转的功能,需要使用ffmpeg过 ...

  7. TreeMap和HashMap的元素比较

    写在前面的话 2021.04,准备面试和CCF CSP认证的我准备做一套CCF模拟题,然后就有了此篇博客(x 题目:201912-2 回收站报数 题目截图: 第一个想法:读取每个垃圾的位置,存入Tre ...

  8. python3使用kivy生成安卓程序

    技术背景 虽然现在苹果占据了很大一部分的市场,但是从销量数据来看,安卓还是占据了人口的高地.这里我们介绍一个用python的kivy+buildozer来进行安卓APP开发的简单教程,从整个过程中来看 ...

  9. 学习笔记-vue hash模式打包

    1.打包设置config->index.js 2.图片资源路径出现问题 设置下utils.js文件

  10. 给我一个shell我能干翻你内网

    0x00 前言 在去年小菜鸡学了点内网知识就闲着没事跑点jboss的站看看,在经历过很多次内网横向失败之后终于算是人生圆满了一把,阿三的站一般进去之后很难横向,不知道是不是我太菜的原因,反正阿三的站能 ...