云山雾隐 端隐SDP

企业博客

基于ObRegisterCallbacks实现的简单进程保护功能

本文将简单讲下如何使用ObRegisterCallbacks,实现进程保护功能。

前置条件:驱动开发相关环境已配置完成。

本文的开发环境:

认识ObRegisterCallbacks函数

通过该函数设置的回调函数,会在我们对某个进程或线程Handle进行操作的前或后执行。具体是前还是后,可根据后面设置的是PreOperation还是PostOperation进行判断。

首先看MSDN上的函数签名,如下:

NTSTATUS ObRegisterCallbacks( [in]  POB_CALLBACK_REGISTRATION CallbackRegistration, [out] PVOID                     *RegistrationHandle ); copy

参数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 该结构体的数组 copy

在上述的结构体中,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: 操作执行后的回调函数 copy

参数RegistrationHandle:接受注册成功后的handle,在驱动卸载或反注册时需要调用ObUnRegisterCallbacks,并把该参数传入。

使用ObRegisterCallbacks函数实现进程保护

前面简单讲述了ObRegisterCallbacks函数及其相关参数,下面开始演示如何使用该函数实现简单的进程保护功能。

初始化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 copy

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); } copy

以上部分完成了ObRegisterCallbacks相关参数的初始化,包括指定我们的回调函数,我们只需要在驱动程序的入口调用InitObRegistration即可。

PreOperationCallback回调函数的实现

该回调函数,有两个参数:

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; copy

通过该结构体,可以获取到当前操作的一些信息,我们主要关注Parameters参数,里面是我们需要的权限相关的内容。通过Object成员获取进程或线程的ID,然后通过PsGetProcessImageFileName获取到进程名,最后比较进程名是否是我们想要保护的进程。

此处需要注意的是PsGetProcessImageFileName,这个函数只在内核中导出,需要使用的话只需要申明一下该函数就行。

该函数申明如下:

NTKERNELAPI UCHAR* PsGetProcessImageFileName( __in PEPROCESS Process ); copy

当我们尝试去关闭一个我们保护的进程时,会在操作前触发我们的回调,在回调里我们通过对POB_PRE_OPERATION_INFORMATION中成员Parameters中的DesiredAccess来指定相关权限。

DesiredAccess是一个ACCESS_MASK,表明授予该操作的权限,该值默认情况下和OriginalDesiredAccess是一样的,即拥有全部权限。通过设置DesiredAccess,取消掉相应权限即可。

PreInfo->Parameters->CreateHandleInformation.DesiredAccess &= ~AccessBitsToClear; copy

具体逻辑实现如下:

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; } copy

测试驱动

相关工具:

安装启动

使用管理员权限打开DriverMonitor,选择驱动文件并安装,点击Go按钮,启动驱动程序;

点击go按钮后,发现启动驱动程序失败,提示拒绝访问。

解决STATUS_ACCESS_DENIED

搜索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 copy

最终效果

驱动打开后,记事本这个程序已经无法从任务管理器关闭了。

扩展

通过任务管理器关闭进程,实际上有两种方式:

本文完整代码库:https://github.com/yunshanwuyin/ProcessProtect

References