概述
ObRegisterCallbacks()有什么用?
继从Windows Vista以后的64位系统都PG(PatchGuard)的存在,所以应用程序不能随意的通过 HOOK SSDT 等方式来修改内核,因为会触发 PatchGuard 保护造成蓝屏。所以现在的64系统的程序通过内核函数ObRegisterCallbacks来实现对自身的保护 。
本文不会介绍原理之类的,其内部实现可以使用IDA工具查看其反汇编代码,另外,该函数不能运行在XP环境下,且需要在驱动中调用,应用层的程序是调用不了的,并且,如今的驱动程序运行需要微软驱动程序的数字签名,也就是说在如今的系统之下你自己写的驱动程序一般是无法运行的,有3种解决办法,一是禁用自己系统的签名认证,二是给微软交250美金,三是过掉签名认证检测,其中的细节等会再说
ObRegisterCallbacks保护进程
ObRegisterCallbacks()
NTSTATUS ObRegisterCallbacks(
_IN POB_CALLBACK_REGISTRATION CallbackRegistration,
_OUT PVOID *RegistrationHandle
);
学明白ObRegisterCallbacks这个函数关键是要搞清楚这个函数内部的参数,以及它做了些什么事,MSDN是这么介绍这个函数的
The ObRegisterCallbacks routine registers a list of callback routines for thread, process, and desktop handle operations.
大致意思是ObRegisterCallbacks 注册一个存储了一个或多个回调函数的列表,当线程句柄,进程句柄,窗口句柄被操作时就会调用这个回调函数列表中的回调函数
所以ObRegisterCallbacks的第一个参数就是给它传入一个结构体,这个结构体描述了这个列表的相关信息
第二个参数是_OUT类型,它是一个ID,标识了我们刚才注册的回调函数的列表,当我们调用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是传入要注册的回调的版本,MSDN说如果是注册的对象是drivers的话就填入OB_FLT_REGISTRATION_VERSION,那我们就填入这个值吧
OperationRegistrationCount是OperationRegistration数组的成员的数量,也就是我们要注册的回调函数的数量,我只想注册一个,所以我填1
Altitude顾名思义是海拔,Altitude需要单独向微软申请.微软提供了一个表来记录这些值,这个值决定了驱动程序加载到内存的优先级,值越低越先加载,网络上都用的321000这个值.他是Norman的nvcmflt.sys驱动的值,不知道为何网络上都是这个.这里咱们谨慎起见也先用这个吧(ps:该值在同一台虚拟机运行的驱动中不允许重复,否则ObRegisterCallbacks调用失败)
RegistrationContext在MSDN的解释是
The system passes the RegistrationContext value to the callback routine when the callback routine is run. The meaning of this value is driver-defined.
大致意思是当回调函数列表被加载时,系统会将这个值传给回调函数列表,而这个值是我们定义的,也就是说应该可以随便写,你可以传&CBCallbackRegistration,如果你不想在回调函数中用到它的话也可以传NULL
POB_CALLBACK_REGISTRATION最关键的地方来了,这是一个指向_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,ObjectType标识了我们的回调函数是在什么类型的句柄被操作时触发,是在进程句柄被操作时,还是线程句柄被操作时,还是窗口句柄被操作时
Operations 指定了当处理的对象发生什么行为时触发,我们可以指定两个值,OB_OPERATION_HANDLE_CREATE,OB_OPERATION_HANDLE_DUPLICATE,第一个参数是当ObjectType对象句柄被打开或即将被打开时触发,第二个参数是当ObjectType对象句柄被复制或即将被复制时触发
微软说Specify one or more of the following flags那么意思就是说可以同时指定…那么…我们可以用|来同时指定
PreOperation是操作前回调函数的指针.该回调会在操作发生前被调用
该成员要求类型为ObjectPreCallback,原型如下:
OB_PREOP_CALLBACK_STATUS ObjectPreCallback(
_In_ PVOID RegistrationContext,
_In_ POB_PRE_OPERATION_INFORMATION OperationInformation
);
其中函数的第一个参数是我们之前指定的POB_CALLBACK_REGISTRATION类型中的RegistrationContext
第二个参数则是操作信息,它是POB_PRE_OPERATION_INFORMATION类型的结构体,向回调函数提供了关于对线程句柄或者是进程句柄操作时记录的信息
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表示了对象发生什么行为而触发此次回调函数,就是之前的那两个类型之一(OB_OPERATION_HANDLE_CREATE或OB_OPERATION_HANDLE_DUPLICATE)
objectType表示了触发此次回调函数的对象的是进程句柄还是线程句柄还是窗口句柄(PsProcessType,PsThreadType,ExDesktopObjectType)
Object传入的是触发此次回调的句柄对应的对象(例如PEPROCESS)
KernelHandle表示触发此次回调的句柄是否是内核句柄(句柄所对应的对象是否是内核对象)
PS:对象的分类
用户对象:加速键(HACCEL),Caret,光标(HCURSOR),钩子(HHOOK),图标(HICON),菜单(HMENU),窗口(HWND),HDWP
GDI对象:位图(HBITMAP),画刷(HBRUSH),设备上下文(HDC),字体(HFONT),内存DC(HDC),调色板(HPALETTE),画笔(HPEN),区域(HRGN)
内核对象:事件对象,文件对象,文件映射对象,I/O完成对象,作业对象,邮件槽对象,互斥对象,管道对象,进程对象,信号量对象,线程对象,等待计时器对象。
Parameters指向了一个OB_POST_OPERATION_PARAMETERS类型的联合体
根据上面的Operation的值(触发的操作类型的不同)来确定是访问哪个成员,如果Operation是OB_OPERATION_HANDLE_CREATE那么我们就访问CreateHandleInformation,如果Operation是OB_OPERATION_HANDLE_DUPLICATE那么我们就访问DuplicateHandleInformation
typedef union _OB_POST_OPERATION_PARAMETERS {
OB_POST_CREATE_HANDLE_INFORMATION CreateHandleInformation;
OB_POST_DUPLICATE_HANDLE_INFORMATION DuplicateHandleInformation;
} OB_POST_OPERATION_PARAMETERS, *POB_POST_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在MSDN里面的解释是
An ACCESS_MASK value that specifies the access rights to grant for the handle. By default, this member equals OriginalDesiredAccess, but the ObjectPreCallback routine can modify this value to restrict the access that is granted.
大致意思是说DesiredAccess这个值标志了对这个句柄所授予的访问权限,通常情况下,DesiredAccess和OriginalDesiredAccess值是相等的,但是我们可以修改这个值来限制访问权限(例如修改DesiredAccess来取消某些权限如THREAD_TERMINATE实现保护的目的),这个值有很多,对线程的有8个,对进程的有9个,这些值都可以在MSDN上查到
OriginalDesiredAccess自然就是最初给句柄的访问权限了
再说一下DuplicateHandleInformation这个成员,其结构体如下
typedef struct _OB_PRE_DUPLICATE_HANDLE_INFORMATION {
ACCESS_MASK DesiredAccess;
ACCESS_MASK OriginalDesiredAccess;
PVOID SourceProcess;
PVOID TargetProcess;
} OB_PRE_DUPLICATE_HANDLE_INFORMATION, *POB_PRE_DUPLICATE_HANDLE_INFORMATION;
前两个与CreateHandleInformation相同,多了两个成员
SourceProcess是要复制的句柄的所属进程的内核对象的指针
TargetProcess是要复制的句柄的目标进程的内核对象的指针
我们可以通过判断TargetProcess来确定得到句柄的进程,需要注意的是.在dup(复制句柄)的时候,我们不能向DesiredAccess中添加SourceProcess没有的权限,当然还是可以删除权限的,可以查看OB_POST_CREATE_HANDLE_INFORMATION结构体知道SourceProcess有哪些权限.
PostOperation说了这么多ObjectPreCallback差点忘说_OB_OPERATION_REGISTRATION的第四个参数PostOperation
该成员要求ObjectPostCallback类型,定义如下:
VOID ObjectPostCallback(
_In_ PVOID RegistrationContext,
_In_ POB_POST_OPERATION_INFORMATION OperationInformation
);
该类型为POB_POST_OPERATION_INFORMATION,,定义如下:
typedef struct _OB_POST_OPERATION_INFORMATION {
OB_OPERATION Operation;
union {
ULONG Flags;
struct {
ULONG KernelHandle : 1;
ULONG Reserved : 31;
};
};
PVOID Object;
POBJECT_TYPE ObjectType;
PVOID CallContext;
NTSTATUS ReturnStatus;
POB_POST_OPERATION_PARAMETERS Parameters;
} OB_POST_OPERATION_INFORMATION, *POB_POST_OPERATION_INFORMATION;
Parameters指向了一个OB_POST_OPERATION_PARAMETERS类型的联合体
typedef union _OB_POST_OPERATION_PARAMETERS {
OB_POST_CREATE_HANDLE_INFORMATION CreateHandleInformation;
OB_POST_DUPLICATE_HANDLE_INFORMATION DuplicateHandleInformation;
} OB_POST_OPERATION_PARAMETERS, *POB_POST_OPERATION_PARAMETERS;
和_OB_PRE_OPERATION_INFORMATION结构体不同的是_OB_POST_OPERATION_INFORMATION结构体中 CreateHandleInformation和DuplicateHandleInformation指向了一个相同的结构体,且只有一个成员
typedef struct _OB_POST_CREATE_HANDLE_INFORMATION {
ACCESS_MASK GrantedAccess;
} OB_POST_CREATE_HANDLE_INFORMATION, *POB_POST_CREATE_HANDLE_INFORMATION;
GrantedAccess标示着这个句柄所被授予的访问权限
根据以上信息设置好ObRegisterCallbacks()之后,启动 Kmd manager 启动我们的驱动文件(如何在win10上正常启动驱动文件看我另一篇文章),会出现以下问题,ObRegisterCallbacks()会调用失败,返回值不是NT_SUCCESS
关于MmVerifyCallbackFunction():
原因是ObRegisterCallbacks()内部会调用MmVerifyCallbackFunction()函数,这个函数会检测驱动程序是否有数字签名认证(我们能成功启动驱动文件只是因为我们暂时禁用了系统数字签名检测,而不是因为我们有数字签名认证),显而易见,微软的初衷是想要有数字签名的驱动才能使用ObRegisterCallbacks()
但是MmVerifyCallbackFunction()此函数只是简单的验证了一下 DriverObject->DriverSection->Flags 的值是不是为 0x20,这样我们就能轻松绕过,在 DriverEntry中设置该值为0x20就可以了
DriverSection指向的结构体是未导出类型,所以需要手动定义如下结构体
#ifdef _WIN64
typedef struct _LDR_DATA
{
//注意结构体每个成员的大小
//且结构体需要内存对齐
0x000 LIST_ENTRY listEntry;//16
0x010 ULONG64 __Undefined1;//8
0x018 ULONG64 __Undefined2;//8
0x020 ULONG64 __Undefined3;//8
0x028 ULONG64 NonPagedDebugInfo;//8
0x030 ULONG64 DllBase;//8
0x038 ULONG64 EntryPoint;//8
0x040 ULONG SizeOfImage;//4
0x050 UNICODE_STRING path;//16
0x060 UNICODE_STRING name;//16
0x068 ULONG Flags;
}LDR_DATA, * PLDR_DATA;
#else
typedef struct _LDR_DATA
{
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;
}LDR_DATA, * PLDR_DATA;
#endif
过掉MmVerifyCallbackFunction()的代码
PLDR_DATA pld;
pld = (PLDR_DATA)pDriver->DriverSection;
pld->Flags |= 0x20;
至此,ObRegisterCallbacks调用成功
完整代码如下图所示,该代码在win10 x64环境运行成功
#include <ntddk.h>
# define PROTECT_NAME "520.exe"
PUCHAR PsGetProcessImageFileName(__in PEPROCESS Process);
PVOID pRegistrationHandle;
//进程管理器详细界面结束代码
#define PROCESS_TERMINATE_0 0x1001
//taskkill指令结束代码
#define PROCESS_TERMINATE_1 0x0001
//taskkill指令加/f参数强杀进程结束码
#define PROCESS_KILL_F 0x1401
//进程管理器结束代码
#define PROCESS_TERMINATE_2 0x1041
// _LDR_DATA_TABLE_ENTRY ,注意32位与64位的对齐大小
#ifdef _WIN64
typedef struct _LDR_DATA
{
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;
}LDR_DATA, * PLDR_DATA;
#else
typedef struct _LDR_DATA
{
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;
}LDR_DATA, * PLDR_DATA;
#endif
VOID DriverUnload(PDRIVER_OBJECT pDriver)
{
KdPrint(("驱动正在被关闭n"));
// 卸载驱动
if (NULL != pRegistrationHandle)
{
KdPrint(("卸载回调成功n"));
ObUnRegisterCallbacks(pRegistrationHandle);
pRegistrationHandle = NULL;
}
}
OB_PREOP_CALLBACK_STATUS PreProcessHandle(
PVOID RegistrationContext,
POB_PRE_OPERATION_INFORMATION pOperationInformation
)
{
PEPROCESS pEProcess = NULL;
// 判断对象类型
if (*PsProcessType != pOperationInformation->ObjectType)
{
return OB_PREOP_SUCCESS;
}
//获取该进程结构对象的名称
pEProcess = (PEPROCESS)pOperationInformation->Object;
PUCHAR pProcessName = PsGetProcessImageFileName(pEProcess);
// 判断是否为保护进程,不是则放行
if (NULL != pProcessName)
{
if (0!=_stricmp(pProcessName, PROTECT_NAME))
{
return OB_PREOP_SUCCESS;
}
}
// 判断操作类型,如果该句柄是终止操作,则拒绝该操作
switch (pOperationInformation->Operation)
{
case OB_OPERATION_HANDLE_DUPLICATE:
break;
case OB_OPERATION_HANDLE_CREATE:
{
//如果要结束进程,进程管理器结束进程发送0x1001,taskkill指令结束进程发送0x0001,taskkil加/f参数结束进程发送0x1401
int code = pOperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess;
if ((code == PROCESS_TERMINATE_0) || (code == PROCESS_TERMINATE_1) || (code == PROCESS_KILL_F))
{
//给进程赋予新权限
pOperationInformation->Parameters->CreateHandleInformation.DesiredAccess = 0;
DbgPrint("拒绝执行当前操作");
}
if (code == PROCESS_TERMINATE_2)
pOperationInformation->Parameters->CreateHandleInformation.DesiredAccess = STANDARD_RIGHTS_ALL;
break;
}
}
return OB_PREOP_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING reg_path)
{
DbgPrint("驱动正在被启动n");
OB_OPERATION_REGISTRATION oor;
OB_CALLBACK_REGISTRATION ocr;
PLDR_DATA pld;//指向_LDR_DATA_TABLE_ENTRY结构体的指针
//初始化
pRegistrationHandle = 0;
RtlZeroMemory(&oor, sizeof(OB_OPERATION_REGISTRATION));
RtlZeroMemory(&ocr,sizeof(OB_CALLBACK_REGISTRATION));
//初始化 OB_OPERATION_REGISTRATION
//设置监听的对象类型
oor.ObjectType = PsProcessType;
//设置监听的操作类型
oor.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
//设置操作发生前执行的回调
oor.PreOperation = PreProcessHandle;
//设置操作发生前执行的回调
//oor.PostOperation = ?
//初始化 OB_CALLBACK_REGISTRATION
// 设置版本号,必须为OB_FLT_REGISTRATION_VERSION
ocr.Version = OB_FLT_REGISTRATION_VERSION;
//设置自定义参数,可以为NULL
ocr.RegistrationContext = NULL;
// 设置回调函数个数
ocr.OperationRegistrationCount = 1;
//设置回调函数信息结构体,如果个数有多个,需要定义为数组.
ocr.OperationRegistration = &oor;
RtlInitUnicodeString(&ocr.Altitude, L"321000"); // 设置加载顺序
// 绕过MmVerifyCallbackFunction。
pld = (PLDR_DATA)pDriver->DriverSection;
pld->Flags |= 0x20;
if (NT_SUCCESS(ObRegisterCallbacks(&ocr, &pRegistrationHandle)))
{
KdPrint(("ObRegisterCallbacks注册成功"));
}
else
{
KdPrint(("ObRegisterCallbacks失败"));
}
// 指定卸载函数
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
查找系统中的ObCallbacks
我们学会了使用ObRegisterCallbacks来保护自己的程序不被破坏,或许现在我们更想知道是否其他程序或者系统也用同样的方式使用ObRegisterCallbacks()注册了ObCallbacks,当我们知道了这些回调函数的位置,我们可以更改,删除或其他操作来实现自己的目的
第一种方法
ObRegisterCallbacks()的第二个参数 _OUT PVOID *RegistrationHandle
吸引了我们的注意,以上提到过这个参数可以用来注销设置的回调,而该指针指向的数据结构必然用于描述本次注册的回调钩子(否则 ObUnRegisterCallbacks 就摘不掉钩子了),该指针实际指向如下结构体
typedef struct _CALLBACK_NODE {
// 版本号,目前是 0x100, 可通过 ObGetFilterVersion 获取该值
USHORT usVersion;
// 本节点上CallbackBody的数量
USHORT usCallbackBodyCount;
// 注册回调时设定的 OB_CALLBACK_REGISTRATION.RegistrationContext
PVOID pContext;
// 指向 Altitude 字符串
UNICODE_STRING Altitude;
// CALLBACK_BODY 数组,其元素个数为 ulCallbackCount
CALLBACK_BODY CallbackBody[1];
}CALLBACK_NODE, * PCALLBACK_NODE;
该结构体最后一个成员是一个CALLBACK_NODE类型的数组
CALLBACK_NODE类型定义如下
typedef struct _CALLBACK_BODY {
// 所有的 CALLBACK_NODE 通过这个链表串在一起
LIST_ENTRY ListEntry;
// 注册回调时设定的 OB_OPERATION_REGISTRATION.Operations成员( OB_OPERATION_HANDLE_CREATE...)
OB_OPERATION Operations;
// 这里似乎始终是 1
ULONG Active;
// 指向该 CallbackBody 对应的 CallbackNode
PCALLBACK_NODE pCallbackNode;
// 注册回调时设定的 OB_OPERATION_REGISTRATION.pObjectType(PsProcessType,PsThreadType...)
POBJECT_TYPE ObjectType;
// 这就是注册回调时设定的 PreCallbackRoutine 函数地址
POB_PRE_OPERATION_CALLBACK pPreCallbackRoutine;
// 这就是注册回调时设定的 PostCallbackRoutine 函数地址
POB_POST_OPERATION_CALLBACK pPostCallbackRoutine;
//Run-down Protection
EX_RUNDOWN_REF RundownProtection;
}CALLBACK_BODY, * PCALLBACK_BODY;
_CALLBACK_NODE 和CALLBACK_BODY与之前提到的OB_CALLBACK_REGISTRATION和OB_OPERATION_REGISTRATION相似却又不完全相同,少了一些成员,多了一些数据结构
CALLBACK_BODY很重要,它的第一个成员是LIST_ENTRY类型,它是一个循环双向链表,将系统所有的CALLBACK_BODY串在一起,CALLBACK_BODY中有我们想要的PreCallback和PostCallback的函数地址,我们可以通过注册一个假的回调来获取同一个对象类型下这两个结构体的所有内容,其代码如下
/*++
Description:
遍历系统中所有的CallbackNode和CallbackBody中的成员
Arguments:
IN PCALLBACK_NODE pFakeCallbackNode 调用ObRegisterCallbacks得来的PVOID pRegistrationHandle;
IN PLDR_DATA dllInfo 当前驱动的PLDR_DATA_TABLE_ENTRY(DriverObject->DriverSection)
Return Value:
None.
Comments:
该函数遍历链表并没有考虑加锁的问题,在实际使用中需要注意。
--*/
VOID DumpObCallbacks(PCALLBACK_NODE pFakeCallbackNode, PLDR_DATA dllInfo)
{
UNICODE_STRING ObjectName;
PCALLBACK_NODE pCallbackNode;
PCALLBACK_BODY pCallbackBody = pFakeCallbackNode->CallbackBody;
PLDR_DATA bRet;
do {
pCallbackNode = pCallbackBody->pCallbackNode;
//如果pCallbackNode并不是有效的地址,usVersion版本也不对CallbackNode对应的CallbackNode数量为0的话,查找下一个
if (!MmIsAddressValid(pCallbackNode) ||
pCallbackNode->usVersion != ObGetFilterVersion() ||
pCallbackNode->usCallbackBodyCount < 1)
{
// 跳过头节点
pCallbackBody = CONTAINING_RECORD(pCallbackBody->ListEntry.Flink, CALLBACK_BODY, ListEntry);
continue;
}
//打印 CallbackBody 的各成员信息
for (int i = 0; i < pCallbackNode->usCallbackBodyCount; i++)
{
PCALLBACK_BODY p = pCallbackNode->CallbackBody + i;
//
//
//取地函数对应的ObjectType
if(!NT_SUCCESS(GetObjNameByObjType(p->ObjectType, &ObjectName))){
ObjectName.Length = 0;
}
//
//
//取得函数所属模块名
if (p->pPreCallbackRoutine!=0x0)
{
bRet = GetModuleByObCallbackAddr((PVOID)p->pPreCallbackRoutine, dllInfo);
if (bRet)
DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, "preCallback地址:%p,Object名称:%wZ,模块名:%wZn", p->pPreCallbackRoutine, ObjectName, bRet->FullDllName);
}
if (p->pPostCallbackRoutine != 0x0)
{
bRet = GetModuleByObCallbackAddr((PVOID)p->pPostCallbackRoutine, dllInfo);
if (bRet)
DbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, "postCallback地址:%p,Object名称:%wZ,模块名:%wZn", p->pPostCallbackRoutine, ObjectName, bRet->FullDllName);
}
}
pCallbackBody = CONTAINING_RECORD(pCallbackBody->ListEntry.Flink, CALLBACK_BODY, ListEntry);
} while (pCallbackBody != pFakeCallbackNode->CallbackBody);
}
其中可以通过获得的回调函数的地址与系统中内核模块的位置相比较从而知道该函数所处于的模块是谁,而内核模块的位置可以根据(PLDR_DATA)DriverObject->DriverSection中的DllBase和SizeOfImage成员来确定,其代码如下
/*++
Description:
根据ObCallback的地址获取所属模块的基址
Arguments:
PVOID CallbackAddr ObCallback的地址
PLDR_DATA dllInfo 当前驱动的 PLDR_DATA
Return Value:
成功则返回所属模块的基址,失败返回 FALSE 。
Comments:
None.
--*/
PLDR_DATA GetModuleByObCallbackAddr(PVOID CallbackAddr, PLDR_DATA dllInfo)
{
PLDR_DATA CurrentDllInfo = dllInfo;
do
{
dllInfo = (PLDR_DATA)(dllInfo->listEntry.Flink); //先查询下一个
if (dllInfo->DllBase <= (ULONG64)CallbackAddr && dllInfo->DllBase + dllInfo->SizeOfImage >= (ULONG64)CallbackAddr)
{
return dllInfo;
}
} while (CurrentDllInfo != dllInfo); //遍历到当前驱动LDR_DATA_TABLE_ENTRY地址时,说明查询结束
return FALSE;
}
还可以通过调用#include <ntifs.h>中的ObQueryNameString()函数,该函数通过获取的内核对象(CALLBACK_BODY中的ObjectType)来获得该内核对象的名称,其封装代码如下
/*++
Description:
根据内核对象(OBJECT_TYPE)获得内核对象的名称
Arguments:
IN PVOID pObject 内核对象
OUT PUNICODE_STRING 内核对象的名称
Return Value:
返回ObQueryNameString()的返回值。
Comments:
None.
--*/
NTSTATUS GetObjNameByObj(IN PVOID pObject, OUT PUNICODE_STRING pObjectName)
{
NTSTATUS status;
UCHAR buf[128]; // 超出长度的对象名就不取了
POBJECT_NAME_INFORMATION pNameInfo = (POBJECT_NAME_INFORMATION)buf;
ULONG ulRet;
status = ObQueryNameString(pObject, pNameInfo, sizeof(buf), &ulRet);
if (NT_SUCCESS(status)) { // 不考虑 STATUS_INFO_LENGTH_MISMATCH 这种情况了
RtlCopyUnicodeString(pObjectName, &pNameInfo->Name);
}
return status;
}
以上代码均在win10 x64环境下运行成功
第二种办法
其实不止一种办法获得CALLBACK_BODY,
我们知道windows有很对对象(Object),例如File Object, Driver Object, Device Object等等,然而这些对象(Object)其实都是某个结构体的Object Body,这个Object Body的不同划分了不同的对象类型,对象既然有不同的地方那也会有相同的地方,既然有Object Body 那也会有Object Header,所以各种Object的共有的信息(例如,对象类型、对象的引用计数、句柄数等信息)保存在OBJECT_HEADER与其他的几个结构体中,之所以说还有其他结构体,是因为在OBJECT_HEADER存在于Object Body的上方,而OBJECT_HEADER上方又有1-4个不等的结构体,因为其数量是根据对象的类型而不同的,所以称作这4个结构体所在的区域为变长的。
windows所有的Object(结构体)都长这样
1.变长区
2.OBJECT_HEADER
3.OBJECT BODY
知识点介绍完毕,下面来说一下OBJECT_HEADER,在win10 x64中其结构体定义如下(注意,该结构体在win10和win7差异不大,但是和XP有非常大的区别)
kd> dt nt!_object_header
+0x000 PointerCount : Int8B
+0x008 HandleCount : Int8B
+0x008 NextToFree : Ptr64 Void
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : UChar
+0x019 TraceFlags : UChar
+0x019 DbgRefTrace : Pos 0, 1 Bit
+0x019 DbgTracePermanent : Pos 1, 1 Bit
+0x01a InfoMask : UChar
+0x01b Flags : UChar
+0x01b NewObject : Pos 0, 1 Bit
+0x01b KernelObject : Pos 1, 1 Bit
+0x01b KernelOnlyAccess : Pos 2, 1 Bit
+0x01b ExclusiveObject : Pos 3, 1 Bit
+0x01b PermanentObject : Pos 4, 1 Bit
+0x01b DefaultSecurityQuota : Pos 5, 1 Bit
+0x01b SingleHandleEntry : Pos 6, 1 Bit
+0x01b DeletedInline : Pos 7, 1 Bit
+0x01c Reserved : Uint4B
+0x020 ObjectCreateInfo : Ptr64 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : Ptr64 Void
+0x028 SecurityDescriptor : Ptr64 Void
+0x030 Body : _QUAD
注意 +0x018 TypeIndex这个成员,它是ObTypeIndexTable表的一个索引值ObTypeIndexTable存放了系统中所有Object的_object_type的指针,
_object_type对应的结构体定义如下
kd> dt _OBJECT_TYPE
nt!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY
+0x010 Name : _UNICODE_STRING
+0x020 DefaultObject : Ptr64 Void
+0x028 Index : UChar
+0x02c TotalNumberOfObjects : Uint4B
+0x030 TotalNumberOfHandles : Uint4B
+0x034 HighWaterNumberOfObjects : Uint4B
+0x038 HighWaterNumberOfHandles : Uint4B
+0x040 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x0b8 TypeLock : _EX_PUSH_LOCK
+0x0c0 Key : Uint4B
+0x0c8 CallbackList : _LIST_ENTRY
其中的 +0x0c8 CallbackList就是我们想要的CALLBACK_BODY的链表
所以第二种办法的思路就是根据自己驱动的DriverObject找DriverObject的ObjectHeader,从而找到_OBJECT_TYPE,进而找到链表,该链表将相同对象类型(_OBJECT_TYPE)注册的回调全部串在一起
注销指定的ObCallbacks
既然能遍历别人的ObCallbacks,那就可以获得ObCallbacks对应的CallbackNode,然后调用ObUnRegisterCallbacks(p->pCallbackNode)来注销别人注册的所有回调了,有了前面的思路,写起注销的代码也轻而易举
对应的代码如下,这是最简单的注销方式,缺点是很容易被发现,其实还有很多更隐蔽的方式来达到目的,以后再说
/*++
Description:
注销指定内核模块名称的模块注册的所有ObCallbacks
Arguments:
IN PCALLBACK_NODE pFakeCallbackNode 调用ObRegisterCallbacks得来的PVOID pRegistrationHandle;
IN PVOID pObject 内核对象
OUT PUNICODE_STRING 内核对象的名称
Return Value:
如果注销成功返回TRUE,如果找不到该内核模块返回FALSE。
Comments:
None.
--*/
BOOLEAN RemoveObCallbacksByDll(PCALLBACK_NODE pFakeCallbackNode, UNICODE_STRING sysName, PLDR_DATA driverInfo)
{
PCALLBACK_NODE pCallbackNode;
PCALLBACK_BODY pCallbackBody = pFakeCallbackNode->CallbackBody;
PLDR_DATA bRet;
do {
pCallbackNode = pCallbackBody->pCallbackNode;
//如果pCallbackNode并不是有效的地址,usVersion版本也不对CallbackNode对应的CallbackNode数量为0的话,查找下一个
if (!MmIsAddressValid(pCallbackNode) ||
pCallbackNode->usVersion != ObGetFilterVersion() ||
pCallbackNode->usCallbackBodyCount < 1)
{
// 跳过头节点
pCallbackBody = CONTAINING_RECORD(pCallbackBody->ListEntry.Flink, CALLBACK_BODY, ListEntry);
continue;
}
for (int i = 0; i < pCallbackNode->usCallbackBodyCount; i++)
{
PCALLBACK_BODY p = pCallbackNode->CallbackBody + i;
//
//
//遍历每个ObCallbacks所属的内核模块名,如果和传入的名字一样就注销该ObCallbacks的Node
if (p->pPreCallbackRoutine != 0x0)
{
bRet = GetModuleByObCallbackAddr((PVOID)p->pPreCallbackRoutine, driverInfo);
if (bRet)
if (!RtlCompareUnicodeString(&bRet->BaseDllName,&sysName,TRUE))
{
ObUnRegisterCallbacks(p->pCallbackNode);
return TRUE;
}
}
if (p->pPostCallbackRoutine != 0x0)
{
bRet = GetModuleByObCallbackAddr((PVOID)p->pPostCallbackRoutine, driverInfo);
if (bRet)
if (!RtlCompareUnicodeString(&bRet->BaseDllName, &sysName, TRUE))
{
ObUnRegisterCallbacks(p->pCallbackNode);
return TRUE;
}
}
}
pCallbackBody = CONTAINING_RECORD(pCallbackBody->ListEntry.Flink, CALLBACK_BODY, ListEntry);
} while (pCallbackBody != pFakeCallbackNode->CallbackBody);
return FALSE;
}
最后
以上就是感性月光为你收集整理的ObRegisterCallbacks的运用的全部内容,希望文章能够帮你解决ObRegisterCallbacks的运用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复