Windows内核除了可以监听进程,线程、dll还可以监听特定的对象和注册表。这里先讲一下监听对象。
内核提供了一种可以监听对特定的对象类型的句柄进行打开或复制的机制。正式支持的对象类型有进程和线程,Windows10还支持一个桌面对象(桌面对象这个先不考虑)。
这个和前面的监听进程线程以及模块加载是有区别的。这个是相对于对象的句柄的,比如说一个进程的句柄,线程的句柄,别的进程通过关闭这个进程的句柄来关闭这个进程这种。是这个意思。
这里也有很多和前面相似的地方。
比如说,首先得注册通知。
注册对象通知的主要API:
NTSTATUS ObRegisterCallbacks( POB_CALLBACK_REGISTRATION CallbackRegistration, PVOID *RegistrationHandle );
如果采用这个API,必须在链接是开启/integritycheck
void ObUnRegisterCallbacks( PVOID RegistrationHandle );//关闭注册的API
整体说明下这两个API如何使用:
首先有一个公用的参数 RegistrationHandle,这个参数在注册的API里面是一个输出参数,然后在取消注册里面是一个输入参数,相当于这个参数会和注册的对象进行一个绑定,通过注册API会给它赋一个值,然后取消注册API会通过它的值,来唯一标识一个对象进行取消注册这样子。
比较重要的是注册API的第一个参数:CallbackRegistration。
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 | 必须为设置为OB_FLT_REGISTRATION_VERSION |
OperationRegistrationCount | 被注册的数量,指定了OperationRegistration指向的结构体数量。 |
Altitude | 这个高度值,用来标榜被调用的顺序,值越高在调用链中就越早。但是不能和之前的值有重复值,这个其实没啥用,最主要的是防止重复了。这个值是一个无限精度的十进制数字,所以小数或者随机数都可以,最主要的别重复了。一般会多写点,比如说 123145,111111这种,就很大程度上可以规避重复。 |
RegistrationContext | 这个值由系统和驱动自动赋值,传个NULL就行 |
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 | 标记要通知的对象的类型:进程、线程、桌面。PsProcessType PsThreadType ExDesktopObjectType。 |
Operations | 标记要做的操作,打开创建还是复制。OB_OPERATION_HANDLE_CREATE OB_OPERATION_HANDLE_DUPLICATE |
PreOperation | 一个函数指针PobPreOperationCallback,在系统调用涉及到对象的时候之前就采用这个回调函数。 |
PostOperation | 一个函数指针PobPreOperationCallback,在系统调用涉及到对象的时候之后就采用这个回调函数。 |
POB_PRE_OPERATION_CALLBACK PobPreOperationCallback; OB_PREOP_CALLBACK_STATUS PobPreOperationCallback( PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation ) {...} POB_POST_OPERATION_CALLBACK PobPostOperationCallback; void PobPostOperationCallback( PVOID RegistrationContext, POB_POST_OPERATION_INFORMATION OperationInformation ) {...}
这里面的RegistrationContext是上下文寄存器环境OperationInformation是记录着一些相关的对象信息。
这个OperationInformation大同小异,比如说调用前的OperationInformation就是这个结构体:
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;
字段 | 意义 |
---|---|
Operation | 操作类型 |
KernelHandle | 是否是内核对象 |
Object | 进程就是EPROCESS,线程就是PETHREAD |
ObjectType | 对象的类型,和前面一样 |
CallContext | 驱动自动赋值,不是很重要,这里用不到 |
Parameters | 基于操作的附加信息 |
针对parameter又有扩展内容:
typedef union _OB_PRE_OPERATION_PARAMETERS { OB_PRE_CREATE_HANDLE_INFORMATION CreateHandleInformation; OB_PRE_DUPLICATE_HANDLE_INFORMATION DuplicateHandleInformation; } OB_PRE_OPERATION_PARAMETERS, *POB_PRE_OPERATION_PARAMETERS;
然后这里的CreateHandleInformation我们用得上:
typedef struct _OB_PRE_CREATE_HANDLE_INFORMATION { ACCESS_MASK DesiredAccess; ACCESS_MASK OriginalDesiredAccess; } OB_PRE_CREATE_HANDLE_INFORMATION, *POB_PRE_CREATE_HANDLE_INFORMATION;
DesiredAccess表示的是访问掩码,也就是获得对进程操作的权限,我们只要在这里把关闭进程的权限给删除了就好了。
由于各种API啊,各种结构体啊,调用起来层次复杂,光这样讲肯定是少了很多东西,用一个实际场景来讲会更好。
这里提供一个保护进程的思想,就是通过进程的PID来进行保护,删除除掉别的进程打开保护进程的句柄时的关闭权限。就行了。
思路的话就是通过调用前,判断是不是我们保护的进程,如果是就不让关闭了,删除掉PROCESS_TERMINATE权限。,其实比较简单。
整个驱动的代码我写好打包上github了:
https://github.com/skrandy/ProtectYourProcess。
添加了保护后,再用任务管理器就无法关闭了。
小结
监听对象,通过对其它进程对别的进程、线程以及桌面对象的处理而进行一个处理。说起来有的绕口了,就是说比如说正常的用r3的API来打开一个进程,肯定是会有权限,然后可以再内核中添加保护代码,当打开你的进程句柄时,关闭掉一些权限,比如说这里的关闭权限,那么正常的API关闭流程,比如说OpenProcess,然后CloseProcess这种就无法关闭了。