要实现监控系统线程和进程,并实现对指定线程和进程的保护,在 32 位系统上可以使用 HOOK 技术,HOOK 相关的函数来实现。但是,到了 64 位平台上,就不能继续按常规的 HOOK 方法去实现了。
好在 Windows 给我们提供了 ObRegisterCallbacks 内核函数来注册系统回调,可以用来注册系统线程回调,监控系统的线程创建、退出等情况,而且还能进行控制;也可以用来注册系统进程回调,可以监控系统的进程创建、退出等情况,而且也能进行控制。这使得我们实现保护指定线程、进程不被结束,提供了可能。
现在,我就对程序的实现过程和原理进行整理,形成文档,分享给大家。
函数介绍
ObRegisterCallbacks 函数
注册线程、进程和桌面句柄操作的回调函数。
函数声明
NTSTATUS ObRegisterCallbacks(
_In_ POB_CALLBACK_REGISTRATION CallBackRegistration,
_Out_ PVOID *RegistrationHandle
);
参数
- CallBackRegistration [in]
指向指定回调例程列表和其他注册信息的OB_CALLBACK_REGISTRATION结构的指针。- RegistrationHandle [out]
指向变量的指针,该变量接收一个标识已注册的回调例程集合的值。 调用者将此值传递给ObUnRegisterCallbacks例程以注销该回调集。返回值
- 成功,则返回 STATUS_SUCCESS,否则,返回其它 NTSTATUS 错误码。
备注
- 驱动程序必须在卸载之前注销所有回调例程。 您可以通过调用ObUnRegisterCallbacks例程来注销回调例程。
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
请求的对象回调注册版本。 驱动程序应指定OB_FLT_REGISTRATION_VERSION。- OperationRegistrationCount
OperationRegistration数组中的条目数。- Altitude
指定驱动程序Altitude的Unicode字符串。一定要存在,不能置为空 NULL,可以任意指定。- RegistrationContext
当回调例程运行时,系统将RegistrationContext值传递给回调例程。 该值的含义是由驱动程序自定义的。- OperationRegistration
指向OB_OPERATION_REGISTRATION结构数组的指针。 每个结构指定ObjectPreCallback和ObjectPostCallback回调例程以及调用例程的操作类型。
OB_OPERATION_REGISTRATION 结构体
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。 此值在Windows 10中受支持,而不在早期版本的操作系统中。- Operations
指定以下一个或多个标志:
OB_OPERATION_HANDLE_CREATE
一个新的进程,线程或桌面句柄已被打开或将被打开。
OB_OPERATION_HANDLE_DUPLICATE
进程,线程或桌面句柄已被或将被复制。- PreOperation
指向ObjectPreCallback例程的指针。 在请求的操作发生之前,系统调用此例程。- PostOperation
指向ObjectPostCallback例程的指针。 在请求的操作发生后,系统调用此例程。
IoThreadToProcess 函数
返回指向指定线程的进程的指针。
函数声明
PEPROCESS IoThreadToProcess(
_In_ PETHREAD Thread
);
参数
- Thread[in]
- 要返回进程的指定线程对象。
返回值
- 返回线程对象对应的进程对象的指针。
PsGetProcessId 函数
返回与指定进程关联的进程标识符(进程ID)。
函数声明
HANDLE PsGetProcessId(
_In_ PEPROCESS Process
);
参数
Process[in]
指向进程对象结构的指针。
返回值
- 返回进程ID。
实现原理
破解 ObRegisterCallbacks 函数的使用限制
第一种方法
在讲解怎么使用 ObRegisterCallbacks 函数来注册系统线程、进程回调的之前,先来讲解下 Windows 对这个函数做的限制:驱动程序必须有数字签名才能使用此函数。不过国外的黑客对此限制很不满,通过逆向 ObRegisterCallbacks,找到了
破解这个限制的方法。经研究,内核通过 MmVerifyCallbackFunction 验证此回调
是否合法, 但此函数只是简单的验证了一下 DriverObject->DriverSection->Flags 的值是不是为 0x20:
nt!MmVerifyCallbackFunction+0x75:
fffff800`01a66865 f6406820 test byte ptr [rax+68h],20h
fffff800`01a66869 0f45fd cmovne edi,ebp
所以破解方法非常简单,只要把 DriverObject->DriverSection->Flags 的值按位或 0x20 即可。其中,DriverSection 是指向 LDR_DATA_TABLE_ENTRY 结构的值,要注意该结构在 32 位和 64 位系统下的定义。
// 注意32位与64位的对齐大小
// 注意32位与64位的对齐大小
#ifndef _WIN64
#pragma pack(1)
#endif
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union
{
ULONG TimeDateStamp;
PVOID LoadedImports;
};
PVOID EntryPointActivationContext;
PVOID PatchInformation;
LIST_ENTRY ForwarderLinks;
LIST_ENTRY ServiceTagLinks;
LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
#ifndef _WIN64
#pragma pack()
#endif
第二种方法
使用此函数, 一定要设置 IMAGE_OPTIONAL_HEADER 中的 DllCharacterisitics 字段设置为:IMAGE_DLLCHARACTERISITICS_FORCE_INTEGRITY 属性,该属性是一个驱动强制签名属性。
使用 VS2013 开发环境设置方式是:
- 右击项目,选择属性;
- 选中配置属性中的链接器,点击命令行;
- 在其它选项中输入: /INTEGRITYCHECK 表示设置; /INTEGRITYCHECK:NO 表示不设置。
- 对于使用sources文件编译的方式,增加LINKER_FLAGS=/INTEGRITYCHECK
这样,设置之后,驱动程序必须要进行驱动签名才可正常运行!
调用 ObRegisterCallbacks 注册线程回调以及进程回调
我们从上面的函数介绍中,可以知道大概的实现流程:
- 首先,在调用 ObRegisterCallbacks 函数注册系统回调之前,我们要先对结构体 OB_CALLBACK_REGISTRATION 进行初始化。设置回调的版本 Version;设置回调的 Altitude,任意指定;设置回调函数的数量 OperationRegistrationCount;设置回调函数 OperationRegistration。其中,OperationRegistration 是一个 OB_OPERATION_REGISTRATION 结构体数组,里面存储着回调对象的类型、操作类型以及回调函数,它的数量要和 OperationRegistrationCount 对应。
- 然后,再调用 ObRegisterCallbacks 进行注册,并保留系统回调对象句柄。
- 最后,在不使用回调的时候,调用 ObUnRegisterCallbacks 函数传入系统回调对象句柄,删除回调。
其中,线程回调和进程回调的注册,只有 OB_OPERATION_REGISTRATION 结构体的成员 ObjectType 不同。对于线程,ObjectType 为 PsThreadType;对于进程,ObjectType 为 PsProcessType。其它的操作及其含义,均相同。
线程、进程回调函数中实现线程、进程保护
由于在注册系统回调的时候,我们设置监控线程以及进程的操作类型为:OB_OPERATION_HANDLE_CREATE 和 OB_OPERATION_HANDLE_DUPLICATE。要想实现,拒绝结束线程或者进程的操作,我们只需从操作类型句柄信息中去掉相应的结束线程或者进程的权限即可:
// OB_OPERATION_HANDLE_CREATE 操作类型
pObPreOperationInfo->Parameters->CreateHandleInformation.DesiredAccess = 0;
// OB_OPERATION_HANDLE_DUPLICATE 操作类型
pObPreOperationInfo->Parameters->DuplicateHandleInformation.DesiredAccess = 0;
那么,我们怎么从线程对象或者进程对象 pObPreOperationInfo->Object 中判断是否是保护线程或者进程呢。
对于进程,我们可以调用 PsGetProcessImageFileName 函数,从进程结构对象获取进程名称进行判断。
对于线程,我们可以通过 IoThreadToProcess 函数,根据线程结构对象获取相应的进程结构对象,再根据 PsGetProcessImageFileName 函数,从进程结构对象获取进程名称进行判断。
编码实现
破解 ObRegisterCallbacks 的使用限制
// 编程方式绕过签名检查
BOOLEAN BypassCheckSign(PDRIVER_OBJECT pDriverObject)
{
#ifdef _WIN64
typedef struct _KLDR_DATA_TABLE_ENTRY
{
LIST_ENTRY listEntry;
ULONG64 __Undefined1;
ULONG64 __Undefined2;
ULONG64 __Undefined3;
ULONG64 NonPagedDebugInfo;
ULONG64 DllBase;
ULONG64 EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING path;
UNICODE_STRING name;
ULONG Flags;
USHORT LoadCount;
USHORT __Undefined5;
ULONG64 __Undefined6;
ULONG CheckSum;
ULONG __padding1;
ULONG TimeDateStamp;
ULONG __padding2;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
#else
typedef struct _KLDR_DATA_TABLE_ENTRY
{
LIST_ENTRY listEntry;
ULONG unknown1;
ULONG unknown2;
ULONG unknown3;
ULONG unknown4;
ULONG unknown5;
ULONG unknown6;
ULONG unknown7;
UNICODE_STRING path;
UNICODE_STRING name;
ULONG Flags;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
#endif
PKLDR_DATA_TABLE_ENTRY pLdrData = (PKLDR_DATA_TABLE_ENTRY)pDriverObject->DriverSection;
pLdrData->Flags = pLdrData->Flags | 0x20;
return TRUE;
}
注册线程回调
// 设置线程回调函数 NTSTATUS SetThreadCallbacks() { NTSTATUS status = STATUS_SUCCESS; OB_CALLBACK_REGISTRATION obCallbackReg = { 0 }; OB_OPERATION_REGISTRATION obOperationReg = { 0 }; RtlZeroMemory(&obCallbackReg, sizeof(OB_CALLBACK_REGISTRATION)); RtlZeroMemory(&obOperationReg, sizeof(OB_OPERATION_REGISTRATION)); // 设置 OB_CALLBACK_REGISTRATION obCallbackReg.Version = ObGetFilterVersion(); obCallbackReg.OperationRegistrationCount = 1; obCallbackReg.RegistrationContext = NULL; RtlInitUnicodeString(&obCallbackReg.Altitude, L"321001"); obCallbackReg.OperationRegistration = &obOperationReg; // 设置 OB_OPERATION_REGISTRATION // Thread 和 Process 的区别所在 obOperationReg.ObjectType = PsThreadType; obOperationReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE; // Thread 和 Process 的区别所在 obOperationReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)(&ThreadPreCall); // 注册回调函数 status = ObRegisterCallbacks(&obCallbackReg, &g_obThreadHandle); if (!NT_SUCCESS(status)) { DbgPrint("ObRegisterCallbacks Error[0x%X]\n", status); return status; } return status; }
注册进程回调
// 设置进程回调函数 NTSTATUS SetProcessCallbacks() { NTSTATUS status = STATUS_SUCCESS; OB_CALLBACK_REGISTRATION obCallbackReg = { 0 }; OB_OPERATION_REGISTRATION obOperationReg = { 0 }; RtlZeroMemory(&obCallbackReg, sizeof(OB_CALLBACK_REGISTRATION)); RtlZeroMemory(&obOperationReg, sizeof(OB_OPERATION_REGISTRATION)); // 设置 OB_CALLBACK_REGISTRATION obCallbackReg.Version = ObGetFilterVersion(); obCallbackReg.OperationRegistrationCount = 1; obCallbackReg.RegistrationContext = NULL; RtlInitUnicodeString(&obCallbackReg.Altitude, L"321000"); obCallbackReg.OperationRegistration = &obOperationReg; // 设置 OB_OPERATION_REGISTRATION // Thread 和 Process 的区别所在 obOperationReg.ObjectType = PsProcessType; obOperationReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE; // Thread 和 Process 的区别所在 obOperationReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)(&ProcessPreCall); // 注册回调函数 status = ObRegisterCallbacks(&obCallbackReg, &g_obProcessHandle); if (!NT_SUCCESS(status)) { DbgPrint("ObRegisterCallbacks Error[0x%X]\n", status); return status; } return status; }
线程回调函数
// 线程回调函数 OB_PREOP_CALLBACK_STATUS ThreadPreCall(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInfo) { PEPROCESS pEProcess = NULL; // 判断对象类型 if (*PsThreadType != pObPreOperationInfo->ObjectType) { return OB_PREOP_SUCCESS; } // 获取线程对应的进程 PEPROCESS pEProcess = IoThreadToProcess((PETHREAD)pObPreOperationInfo->Object); // 判断是否市保护PID, 若是, 则拒绝结束线程 if (IsProtectProcess(pEProcess)) { // 操作类型: 创建句柄 if (OB_OPERATION_HANDLE_CREATE == pObPreOperationInfo->Operation) { if (1 == (1 & pObPreOperationInfo->Parameters->CreateHandleInformation.OriginalDesiredAccess)) { pObPreOperationInfo->Parameters->CreateHandleInformation.DesiredAccess = 0; } } // 操作类型: 复制句柄 else if (OB_OPERATION_HANDLE_DUPLICATE == pObPreOperationInfo->Operation) { if (1 == (1 & pObPreOperationInfo->Parameters->DuplicateHandleInformation.OriginalDesiredAccess)) { pObPreOperationInfo->Parameters->DuplicateHandleInformation.DesiredAccess = 0; } } } return OB_PREOP_SUCCESS; }
进程回调函数
// 进程回调函数 OB_PREOP_CALLBACK_STATUS ProcessPreCall(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInfo) { PEPROCESS pEProcess = NULL; // 判断对象类型 if (*PsProcessType != pObPreOperationInfo->ObjectType) { return OB_PREOP_SUCCESS; } // 获取进程结构对象 pEProcess = (PEPROCESS)pObPreOperationInfo->Object; // 判断是否市保护PID, 若是, 则拒绝结束进程 if (IsProtectProcess(pEProcess)) { // 操作类型: 创建句柄 if (OB_OPERATION_HANDLE_CREATE == pObPreOperationInfo->Operation) { if (1 == (1 & pObPreOperationInfo->Parameters->CreateHandleInformation.OriginalDesiredAccess)) { pObPreOperationInfo->Parameters->CreateHandleInformation.DesiredAccess = 0; } } // 操作类型: 复制句柄 else if (OB_OPERATION_HANDLE_DUPLICATE == pObPreOperationInfo->Operation) { if (1 == (1 & pObPreOperationInfo->Parameters->DuplicateHandleInformation.OriginalDesiredAccess)) { pObPreOperationInfo->Parameters->DuplicateHandleInformation.DesiredAccess = 0; } } } return OB_PREOP_SUCCESS; }
总结
其中要注意两个问题:
一是,破解 ObRegisterCallbacks 函数的使用限制有两种方式,一种是通过编程来解决;一种是通过 VS 开发环境和数字签名来解决。在使用编程方式解决限制的时候,一定要注意 32 位与 64 位下,LDR_DATA_TABLE_ENTRY 结构体的对齐差别。
二是,OB_CALLBACK_REGISTRATION 中的 Altitude 成员一定要存在,不能置为空 NULL,可以任意指定。(下面的关于Altitude值的具体函义定义)
About load order groups and altitudes
Windows uses a dedicated set of load order groups for file system filter drivers and legacy filter drivers that are loaded at system startup.
Each load order group has a defined range of altitudes. Every filter driver must have a unique altitude identifier. The filter's altitude defines its position relative to other filter drivers in the I/O stack when it is loaded. The altitude is an infinite-precision string interpreted as a decimal number. A filter driver that has a low numerical altitude is loaded into the I/O stack below a filter driver that has a higher numerical value.
Altitude allocation is managed by Microsoft. To request an altitude for your filter driver, see Filter Altitude Request.
Altitude values for a filter driver are specified in the Instance definitions of the Strings Section in the filter driver's INF file. Instance definitions can also be specified in calls to the InstanceSetupCallback routine in the FLT_REGISTRATION structure. Multiple instances and altitudes can be defined for a filter driver. These instance definitions apply across all volumes.
Table of load order groups and altitude ranges
The following table lists the system-defined load order groups and altitude ranges for filter drivers. For each load order group, the Load order group column contains the value that should be specified for that group in the LoadOrderGroup entry in the ServiceInstall Section of a filter's INF file. The Altitude range column contains the range of altitudes for a particular load order group.
Note that the load order groups and altitude ranges are listed as they appear on the stack, which is the reverse of the order in which they are loaded.
Load order group | Altitude range | Description |
---|---|---|
Filter | 420000-429999 | This group is the same as the Filter load order group that was available on Windows 2000 and earlier. This group loads last and thus attaches furthest from the file system. |
FSFilter Top | 400000-409999 | This group is provided for filter drivers that must attach above all other FSFilter types. |
FSFilter Activity Monitor | 360000-389999 | This group includes filter drivers that observe and report on file I/O. |
FSFilter Undelete | 340000-349999 | This group includes filters that recover deleted files. |
FSFilter Anti-Virus | 320000-329999 | This group includes filter drivers that detect and disinfect viruses during file I/O. |
FSFilter Replication | 300000-309999 | This group includes filter drivers that replicate file data to remote servers. |
FSFilter Continuous Backup | 280000-289999 | This group includes filter drivers that replicate file data to backup media. |
FSFilter Content Screener | 260000-269999 | This group includes filter drivers that prevent the creation of specific files or file content. |
FSFilter Quota Management | 240000-249999 | This group includes filter drivers that provide enhanced file system quotas. |
FSFilter System Recovery | 220000-229999 | This group includes filter drivers that perform operations to maintain operating system integrity, such as the System Restore (SR) filter. |
FSFilter Cluster File System | 200000-209999 | This group includes filter drivers that are used in products that provide file server metadata across a network. |
FSFilter HSM | 180000-189999 | This group includes filter drivers that perform hierarchical storage management. |
FSFilter Imaging | 170000-175000 | This group includes ZIP-like filter drivers that provide a virtual namespace. |
FSFilter Compression | 160000-169999 | This group includes filter drivers that perform file data compression. |
FSFilter Encryption | 140000-149999 | This group includes filter drivers that encrypt and decrypt data during file I/O. |
FSFilter Virtualization | 130000- 139999 | This group includes filter drivers that virtualize the file path, such as the Least Authorized User (LUA) filter driver added in Windows Vista. |
FSFilter Physical Quota Management | 120000-129999 | This group includes filter drivers that manage quotas by using physical block counts. |
FSFilter Open File | 100000-109999 | This group includes filter drivers that provide snapshots of already open files. |
FSFilter Security Enhancer | 80000-89999 | This group includes filter drivers that apply lockdown and enhanced access control lists (ACLs). |
FSFilter Copy Protection | 60000-69999 | This group includes filter drivers that check for out-of-band data on media. |
FSFilter Bottom | 40000-49999 | This group is provided for filter drivers that must attach below all other FSFilter types. |
FSFilter System | 20000-29999 | Reserved for internal use. |
FSFilter Infrastructure | Reserved for internal use. This group loads first and thus attaches closest to the file system. |