企业博客
本文将简单讲下如何使用ObRegisterCallbacks,实现进程保护功能。
前置条件:驱动开发相关环境已配置完成。
本文的开发环境:
通过该函数设置的回调函数,会在我们对某个进程或线程Handle进行操作的前或后执行。具体是前还是后,可根据后面设置的是PreOperation还是PostOperation进行判断。
首先看MSDN上的函数签名,如下:
NTSTATUS ObRegisterCallbacks(
[in] POB_CALLBACK_REGISTRATION CallbackRegistration,
[out] PVOID *RegistrationHandle
);
参数CallbackRegistration:调用ObRegisterCallbacks时需传入POB_CALLBACK_REGISTRATION类型,该类型是一个_OB_CALLBACK_REGISTRATION的一个指针类型。
该结构体的结构如下:
typedef struct _OB_CALLBACK_REGISTRATION {
USHORT Version;
USHORT OperationRegistrationCount;
UNICODE_STRING Altitude;
PVOID RegistrationContext;
OB_OPERATION_REGISTRATION *OperationRegistration;
} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;
//Version: 使用ObGetFilterVersion()取得
//OperationRegistrationCount:OB_OPERATION_REGISTRATION 该结构体的数组的长度
//Altitude:可理解为驱动的加载顺序
//RegistrationContext:传递给回调的参数
//OperationRegistration:OB_OPERATION_REGISTRATION 该结构体的数组
在上述的结构体中,OperationRegistration这个参数是指向如下结构体的数组指针:
typedef struct _OB_OPERATION_REGISTRATION {
POBJECT_TYPE *ObjectType;
OB_OPERATION Operations;
POB_PRE_OPERATION_CALLBACK PreOperation;
POB_POST_OPERATION_CALLBACK PostOperation;
} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
//ObjectType: 触发回调对象的类型
//Operations: 触发回调的操作
//PreOperation: 操作执行前的回调函数
//PostOperation: 操作执行后的回调函数
参数RegistrationHandle:接受注册成功后的handle,在驱动卸载或反注册时需要调用ObUnRegisterCallbacks,并把该参数传入。
前面简单讲述了ObRegisterCallbacks函数及其相关参数,下面开始演示如何使用该函数实现简单的进程保护功能。
这里分别注册了两种回调触发类型PsProcessType、PsThreadType,并且我们需要在操作执行前,执行我们的回调,所以我们只设置了PreOperation。Operations:表示我们对某个句柄需要关系的操作,我们将句柄的创建和复制都注册进去,MSDN相关解释如下:
Operations
Specify one or more of the following flags:
OB_OPERATION_HANDLE_CREATE
A new process, thread, or desktop handle was or will be opened.
OB_OPERATION_HANDLE_DUPLICATE
PreOperationCallback:是我们的回调函数,当设置的操作触发时(Operations 参数)会调用该函数,也是我们逻辑的主要实现。
该部分具体实现代码如下:
NTSTATUS InitObRegistration()
{
//进程类型
obOperationRegistrations[0].ObjectType = PsProcessType;
obOperationRegistrations[0].Operations |= OB_OPERATION_HANDLE_CREATE;
obOperationRegistrations[0].Operations |= OB_OPERATION_HANDLE_DUPLICATE;
obOperationRegistrations[0].PreOperation = PreOperationCallback;
//线程类型
obOperationRegistrations[1].ObjectType = PsThreadType;
obOperationRegistrations[1].Operations |= OB_OPERATION_HANDLE_CREATE;
obOperationRegistrations[1].Operations |= OB_OPERATION_HANDLE_DUPLICATE;
obOperationRegistrations[1].PreOperation = PreOperationCallback;
RtlInitUnicodeString(&altitude, L"1000");
obCallbackRegistration.Version = OB_FLT_REGISTRATION_VERSION;
obCallbackRegistration.OperationRegistrationCount = 2;
obCallbackRegistration.RegistrationContext = NULL;
obCallbackRegistration.Altitude = altitude;
obCallbackRegistration.OperationRegistration = obOperationRegistrations;
return ObRegisterCallbacks(&obCallbackRegistration, RegistrationHandle);
}
以上部分完成了ObRegisterCallbacks相关参数的初始化,包括指定我们的回调函数,我们只需要在驱动程序的入口调用InitObRegistration即可。
该回调函数,有两个参数:
typedef struct _OB_PRE_OPERATION_INFORMATION {
OB_OPERATION Operation;
union {
ULONG Flags;
struct {
ULONG KernelHandle : 1;
ULONG Reserved : 31;
};
};
PVOID Object;
POBJECT_TYPE ObjectType;
PVOID CallContext;
POB_PRE_OPERATION_PARAMETERS Parameters;
} OB_PRE_OPERATION_INFORMATION, *POB_PRE_OPERATION_INFORMATION;
通过该结构体,可以获取到当前操作的一些信息,我们主要关注Parameters参数,里面是我们需要的权限相关的内容。通过Object成员获取进程或线程的ID,然后通过PsGetProcessImageFileName获取到进程名,最后比较进程名是否是我们想要保护的进程。
此处需要注意的是PsGetProcessImageFileName,这个函数只在内核中导出,需要使用的话只需要申明一下该函数就行。
该函数申明如下:
NTKERNELAPI
UCHAR*
PsGetProcessImageFileName(
__in PEPROCESS Process
);
当我们尝试去关闭一个我们保护的进程时,会在操作前触发我们的回调,在回调里我们通过对POB_PRE_OPERATION_INFORMATION中成员Parameters中的DesiredAccess来指定相关权限。
DesiredAccess是一个ACCESS_MASK,表明授予该操作的权限,该值默认情况下和OriginalDesiredAccess是一样的,即拥有全部权限。通过设置DesiredAccess,取消掉相应权限即可。
PreInfo->Parameters->CreateHandleInformation.DesiredAccess &= ~AccessBitsToClear;
具体逻辑实现如下:
OB_PREOP_CALLBACK_STATUS PreOperationCallback(_In_ PVOID RegistrationContext,
_Inout_ POB_PRE_OPERATION_INFORMATION PreInfo)
{
UNREFERENCED_PARAMETER(RegistrationContext);
//这里我们演示取消PROCESS_TERMINATE 权限
ACCESS_MASK AccessBitsToClear = PROCESS_TERMINATE;
//获取进程
PEPROCESS process = (PEPROCESS)PreInfo->Object;
if (PreInfo->ObjectType == *PsThreadType) {
process = IoThreadToProcess((PETHREAD)PreInfo->Object);
}
if (PreInfo->ObjectType == *PsProcessType) {
process = (PEPROCESS)PreInfo->Object;
}
//获取进程名
PUCHAR processName = PsGetProcessImageFileName(process);
if (_stricmp((char *)processName, "Notepad.exe") != 0) {
//不是我们关心的进程,直接return
return OB_PREOP_SUCCESS;
}
if (PreInfo->Operation == OB_OPERATION_HANDLE_CREATE) {
PreInfo->Parameters->CreateHandleInformation.DesiredAccess &= ~AccessBitsToClear;
}
if (PreInfo->Operation == OB_OPERATION_HANDLE_DUPLICATE) {
PreInfo->Parameters->DuplicateHandleInformation.DesiredAccess &= ~AccessBitsToClear;
}
return OB_PREOP_SUCCESS;
}
相关工具:
使用管理员权限打开DriverMonitor,选择驱动文件并安装,点击Go按钮,启动驱动程序;
点击go按钮后,发现启动驱动程序失败,提示拒绝访问。
搜索MSDN后发现啥上述问题是因为签名导致的;
正常情况下,由于我们的驱动是签名之后才能运行,所以没问题。但是我们在开发调试时,一般是没有签名的,所以需要在debug模式下绕过检查:
//定义如下结构体
typedef struct _LDR_DATA
{
struct _LIST_ENTRY InLoadOrderLinks;
struct _LIST_ENTRY InMemoryOrderLinks;
struct _LIST_ENTRY InInitializationOrderLinks;
VOID* DllBase;
VOID* EntryPoint;
ULONG32 SizeOfImage;
UINT8 _PADDING0_[0x4];
struct _UNICODE_STRING FullDllName;
struct _UNICODE_STRING BaseDllName;
ULONG32 Flags;
}LDR_DATA, * PLDR_DATA;
//在调用ObRegisterCallbacks前到调用就可以了。
#ifdef DBG
PLDR_DATA ldr;
ldr = (PLDR_DATA)DriverObject->DriverSection;
ldr->Flags |= 0x20;
#endif
驱动打开后,记事本这个程序已经无法从任务管理器关闭了。
通过任务管理器关闭进程,实际上有两种方式:
本文完整代码库:https://github.com/yunshanwuyin/ProcessProtect