0%

使用注册表回调实现注册表过滤(1)

回调函数的注册与卸载

在驱动中进行注册表保护,可以通过 注册表回调 函数来实现。
在VISTA以下的系统中,使用 CmRegisterCallback 函数来注册回调,
在VISTA及以上的系统中,微软提供了新的 CmRegisterCallbackEx 函数来进行替代,
并引入了 Altitude 的概念,数值越大越先调用,比如 423000 -> 422000 -> 421000
对于旧的 CmRegisterCallback 函数,系统会使用 nt!CmLegacyAltitude 的全局变量,
内容为 L"425000"UNICODE_STRING ,注意:其他 Altitude 值无法注册两个相同的

1
2
3
4
5
NTSTATUS CmRegisterCallback(
_In_ PEX_CALLBACK_FUNCTION Function,
_In_opt_ PVOID Context,
_Out_ PLARGE_INTEGER Cookie
);
1
2
3
4
5
6
7
8
NTSTATUS CmRegisterCallbackEx(
_In_ PEX_CALLBACK_FUNCTION Function,
_In_ PCUNICODE_STRING Altitude,
_In_ PVOID Driver,
_In_opt_ PVOID Context,
_Out_ PLARGE_INTEGER Cookie,
_Reserved_ PVOID Reserved
);

两个函数注册的回调函数格式相同,如下所示。参数 CallbackContext 对应注册时 Context
参数 Argument1 为本次操作类型,参数 Argument2 为本次操作类型对应的结构体信息。

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

卸载回调函数时也是使用同一个卸载函数,如果驱动自身提供卸载功能,
需要在 DriverUnload 中卸载注册时生成的 Cookie 信息,通常情况下都不会提供卸载。

1
2
3
NTSTATUS CmUnRegisterCallback(
_In_ LARGE_INTEGER Cookie
);

操作注册表时触发的动作

需要处理的注册表操作共有以下几类:
(1)创建注册表键或值 (2)修改注册表键或值 (3)删除注册表键或值

在回调函数中,操作类型分为 动作之前动作之后 两种:
做拦截和保护时,需要在 动作之前 进行处理。做行为监控时,可以在 动作之后 进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
NTSTATUS RegCallback(IN PVOID Context, IN PVOID Argument1, IN PVOID Argument2)
{
UNREFERENCED_PARAMETER(Context);
// 本次操作对应的进程
PsGetCurrentProcessId();
// 本次注册表操作类型
switch ((ULONG_PTR)Argument1)
{
case RegNtPreDeleteKey: // 删除键
// 键的路径,使用ObQueryNameString查询信息
((PREG_DELETE_KEY_INFORMATION)Argument2)->Object;
break;

case RegNtPreSetValueKey: // 创建或修改值
// 键的路径,使用ObQueryNameString查询信息
((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->Object;
// 值的名称,是UNICODE_STRING类型数据
((PREG_SET_VALUE_KEY_INFORMATION)Argument2)->ValueName;
break;

case RegNtPreDeleteValueKey: // 删除值
// 键的路径,使用ObQueryNameString查询信息
((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->Object;
// 值的名称,是UNICODE_STRING类型数据
((PREG_DELETE_VALUE_KEY_INFORMATION)Argument2)->ValueName;
break;

// Vista以下系统,是用新建和删除来实现的修改键
case RegNtPreRenameKey: // Vista修改键
// 键的路径,使用ObQueryNameString查询信息
((PREG_RENAME_KEY_INFORMATION)Argument2)->Object;
// 新键的路径,是UNICODE_STRING类型数据
((PREG_RENAME_KEY_INFORMATION)Argument2)->NewName;
break;

case RegNtPreCreateKey: // 创建键
// 键的路径,是UNICODE_STRING类型数据
((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName;
break;

case RegNtPreCreateKeyEx: // Vista创建键
// 键的父路径,使用ObQueryNameString查询信息
((PREG_CREATE_KEY_INFORMATION)Argument2)->RootObject;
// 键的子路径,是UNICODE_STRING类型数据
((PREG_CREATE_KEY_INFORMATION)Argument2)->CompleteName;
break;
}
// 返回STATUS_ACCESS_DENIED表示拦截
return STATUS_SUCCESS;
}

重要的注意事项

在WIN7及以上系统中(未测VISTA),在当前注册表回调中,再次操作注册表比如打开,
会触发回调链表中,当前回调的下一个回调,并不会从初始位置调用,所以就不会导致重入,
比如 A -> B -> C 三层,如果在B层中,执行打开,就只会触发C层,而在XP系统是从A开始