_OBJECT_TYPE结构分析
在介绍什么是对象回调前,首先要熟悉下结构
以我们上篇线程回调介绍过的导出的PsProcessType 结构为例,用_OBJECT_TYPE这个结构来解析它,0x80处就是今天要介绍的回调链表,但是先不着急,先把目光聚焦到0x28这个TypeInfo结构上
1: kd> dd PsProcessType
83f7a02c 86ae5e38 86ae5f00 86ae5ca8 86ae5be0
83f7a03c 86ae58a8 83f45640 8fe87988 00010000
83f7a04c 00000cd8 00000258 00000000 00000001
83f7a05c 00000000 0000004b 00000001 00989680
83f7a06c 00000000 00000029 00000002 c0ffffff
83f7a07c c0607ff8 00000000 00000000 c0403080
83f7a08c 00000000 00010100 00000500 00000000
83f7a09c 00000001 00000000 0000000d 00040107
1: kd> dt _OBJECT_TYPE 86ae5e38
ntdll!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY [ 0x86ae5e38 - 0x86ae5e38 ]
+0x008 Name : _UNICODE_STRING "Process"
+0x010 DefaultObject : (null)
+0x014 Index : 0x7 ''
+0x018 TotalNumberOfObjects : 0x21
+0x01c TotalNumberOfHandles : 0xc1
+0x020 HighWaterNumberOfObjects : 0x2a
+0x024 HighWaterNumberOfHandles : 0xe3
+0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER//
+0x078 TypeLock : _EX_PUSH_LOCK
+0x07c Key : 0x636f7250
+0x080 CallbackList : _LIST_ENTRY [ 0x86ae5eb8 - 0x86ae5eb8 ]//
这个TypeInfo里面也存着一部分值得关注的信息,从0x30开始的回调函数的调用会优先于我们将要介绍的对象回调,比如0x34的这个回调就是在OpenProcess执行之后先被执行,然后其中会进行一系列处理(比如之前也介绍过的通过进程id打开进程对象)
1: kd> dt _OBJECT_TYPE_INITIALIZER 86ae5e38+0x28
ntdll!_OBJECT_TYPE_INITIALIZER
+0x000 Length : 0x50
+0x002 ObjectTypeFlags : 0x4a 'J'
+0x002 CaseInsensitive : 0y0
+0x002 UnnamedObjectsOnly : 0y1
+0x002 UseDefaultObject : 0y0
+0x002 SecurityRequired : 0y1
+0x002 MaintainHandleCount : 0y0
+0x002 MaintainTypeList : 0y0
+0x002 SupportsObjectCallbacks : 0y1//是否支持回调标志位,如果修改会被PG
+0x004 ObjectTypeCode : 0
+0x008 InvalidAttributes : 0xb0
+0x00c GenericMapping : _GENERIC_MAPPING
+0x01c ValidAccessMask : 0x1fffff//权限掩码
+0x020 RetainAccess : 0x101000
+0x024 PoolType : 0 ( NonPagedPool )
+0x028 DefaultPagedPoolCharge : 0x1000
+0x02c DefaultNonPagedPoolCharge : 0x2f0
+0x030 DumpProcedure : (null)
+0x034 OpenProcedure : 0x84021ccb long nt!PspProcessOpen+0//win官方注册的回调
+0x038 CloseProcedure : 0x84081d2e void nt!PspProcessClose+0
+0x03c DeleteProcedure : 0x840845f5 void nt!PspProcessDelete+0
+0x040 ParseProcedure : (null)
+0x044 SecurityProcedure : 0x840765b6 long nt!SeDefaultObjectMethod+0
+0x048 QueryNameProcedure : (null)
+0x04c OkayToCloseProcedure : (null)
可以用pchunter看这些钩子,他们都是挂在内核里面的
这些对象的作用是全局性的,那么可以怎么用呢
DebugObject使用示例
在内核中的DbgkpInitializePhase0这个函数里面,有DbgObject的定义,这说明官方的符号表里面有这个对象
我们在Windbg中来看这个对象(DbgkDebugObjectType),
1: kd> dd DbgkDebugObjectType
83f48dac 86ae5528 00000000 8d86a330 00000003
83f48dbc 00000012 0000002a 000000a1 00000024
83f48dcc 8caa5b3c 01010000 00000002 8322ee0d
83f48ddc 83ee1508 00000000 00000000 00000000
83f48dec 00000003 83e10550 87dbb030 00000000
83f48dfc 00000000 00000000 00000000 00000003
83f48e0c 00000001 00000001 00000001 00000001
83f48e1c 83212408 83212006 8320c006 832122fa
1: kd> dt _OBJECT_TYPE 86ae5528
nt!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY [ 0x86ae5528 - 0x86ae5528 ]
+0x008 Name : _UNICODE_STRING "DebugObject"//对象名字
+0x010 DefaultObject : (null)
+0x014 Index : 0xb ''
+0x018 TotalNumberOfObjects : 0
+0x01c TotalNumberOfHandles : 0
+0x020 HighWaterNumberOfObjects : 0
+0x024 HighWaterNumberOfHandles : 0
+0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x078 TypeLock : _EX_PUSH_LOCK
+0x07c Key : 0x75626544
+0x080 CallbackList : _LIST_ENTRY [ 0x86ae55a8 - 0x86ae55a8 ]
同理,也来看看它的TypeInfo
1: kd> dt _OBJECT_TYPE_INITIALIZER 86ae5528+0x28
nt!_OBJECT_TYPE_INITIALIZER
+0x000 Length : 0x50
+0x002 ObjectTypeFlags : 0x8 ''
+0x002 CaseInsensitive : 0y0
+0x002 UnnamedObjectsOnly : 0y0
+0x002 UseDefaultObject : 0y0
+0x002 SecurityRequired : 0y1
+0x002 MaintainHandleCount : 0y0
+0x002 MaintainTypeList : 0y0
+0x002 SupportsObjectCallbacks : 0y0
+0x004 ObjectTypeCode : 0
+0x008 InvalidAttributes : 0
+0x00c GenericMapping : _GENERIC_MAPPING
+0x01c ValidAccessMask : 0x1f000f//这是这个对象的权限掩码
+0x020 RetainAccess : 0
+0x024 PoolType : 0 ( NonPagedPool )
+0x028 DefaultPagedPoolCharge : 0
+0x02c DefaultNonPagedPoolCharge : 0x30
+0x030 DumpProcedure : (null)
+0x034 OpenProcedure : (null)
+0x038 CloseProcedure : 0x840be98b void nt!DbgkpCloseObject+0
+0x03c DeleteProcedure : 0x8408e87e void nt!ExpDeleteCallback+0
+0x040 ParseProcedure : (null)
+0x044 SecurityProcedure : 0x840765b6 long nt!SeDefaultObjectMethod+0
+0x048 QueryNameProcedure : (null)
+0x04c OkayToCloseProcedure : (null)
如果我们将它的ValidAccessMask置零,那么其他进程申请调试时就无法正确创建调试对象,从而达到简单反调试的作用,当然,这样做非常暴力。
1: kd> ed 86ae5528+0x28+1c 0
WriteVirtual: 86ae556c not properly sign extended
1: kd> dt _OBJECT_TYPE_INITIALIZER 86ae5528+0x28
nt!_OBJECT_TYPE_INITIALIZER
+0x000 Length : 0x50
+0x002 ObjectTypeFlags : 0x8 ''
+0x002 CaseInsensitive : 0y0
+0x002 UnnamedObjectsOnly : 0y0
+0x002 UseDefaultObject : 0y0
+0x002 SecurityRequired : 0y1
+0x002 MaintainHandleCount : 0y0
+0x002 MaintainTypeList : 0y0
+0x002 SupportsObjectCallbacks : 0y0
+0x004 ObjectTypeCode : 0
+0x008 InvalidAttributes : 0
+0x00c GenericMapping : _GENERIC_MAPPING
+0x01c ValidAccessMask : 0
+0x020 RetainAccess : 0
+0x024 PoolType : 0 ( NonPagedPool )
+0x028 DefaultPagedPoolCharge : 0
+0x02c DefaultNonPagedPoolCharge : 0x30
+0x030 DumpProcedure : (null)
+0x034 OpenProcedure : (null)
+0x038 CloseProcedure : 0x840be98b void nt!DbgkpCloseObject+0
+0x03c DeleteProcedure : 0x8408e87e void nt!ExpDeleteCallback+0
+0x040 ParseProcedure : (null)
+0x044 SecurityProcedure : 0x840765b6 long nt!SeDefaultObjectMethod+0
+0x048 QueryNameProcedure : (null)
+0x04c OkayToCloseProcedure : (null)
这时候我们来看看电脑上的调试功能,比如CE
然后就是Dbgview,图有点看不清,但是意思就是调试没开始就结束了
我们在手动修改的层面上去实践了这种做法,这样我们就可以更好的理解了待会要做的事情。
注册我们的对象回调
在win中,注册对象我们一般使用ObRegisterCallbacks
先来看看这个函数的参数,参数有两个一个是CallbackRegistration,这是一个记录我们回调信息的结构;第二个参数是RegistrationHandle,这是一个用来接收我们用来标识注册回调例程的值
NTSTATUS ObRegisterCallbacks(
[in] POB_CALLBACK_REGISTRATION CallbackRegistration,
[out] PVOID *RegistrationHandle
);
[in] CallbackRegistration
指向 OB_CALLBACK_REGISTRATION 结构的指针,该结构指定回调例程和其他注册信息的列表。
[out] RegistrationHandle
指向变量的指针,该变量接收标识注册的回调例程集的值。 调用方将此值传递给 ObUnRegisterCallbacks 例程,以取消注册回调集。
首先要了解这个POB_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
一个 Unicode 字符串,指定驱动程序的高度。 有关海拔的详细信息,请参阅 微型筛选器驱动程序的加载顺序组和高度。
RegistrationContext
运行回调例程时,系统将 RegistrationContext 值传递给回调例程。 此值的含义是驱动程序定义的。
OperationRegistration
指向 OB_OPERATION_REGISTRATION 结构的数组的指针。 每个结构指定 ObjectPreCallback 和 ObjectPostCallback 回调例程以及调用例程的作类型。
第一个参数就是Version,这里建议我们使用OB_FLT_REGISTRATION_VERSION这个宏来获取版本号
它其实就是这么一个值
#define OB_FLT_REGISTRATION_VERSION_0100 0x0100
这里我们建议使用ObGetFilterVersion这个函数,可以看见返回的值是一样的,但是用函数有一个好处,不管什么版本的内核,都会返回正确的版本,不会因为环境的改变而受影响。
第二个参数是OperationRegistrationCount,也就是下面第五个参数OperationRegistration是一个回调函数数组,第二个参数的意义就是说明这个数组里面有多少个回调
第三个参数被称为Altitude,它是海拔,现在先简单理解,这里说的“指定驱动程序的高度”,其实应该是驱动的优先级。这个东西比较复杂这里先不详细介绍。
这里稍微说明一下,不是必须要规定在这些海拔范围内才能生效,这些海拔本身只是一个参考值
第四个参数是RegistrationContext,这是我们传给回调例程的参数
第五个参数就是OperationRegistration,这也有一个结构OB_OPERATION_REGISTRATION,看看这个结构的样子,要填的包括例子官方已经给过了,需要注意的是PreOperation和PostOperation,这分别是我们前回调和后回调。
比如,我Operations填了OB_OPERATION_HANDLE_CREATE,那么就会在我创建这个对象前执行PreOperation,创建后执行PostOperation,这两个回调可以只填一个,但是不能两个都不填
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 例程的指针。 系统在请求的作发生后调用此例程。
关于这两个回调的结构,我同样给出。
typedef OB_PREOP_CALLBACK_STATUS
(*POB_PRE_OPERATION_CALLBACK) (
_In_ PVOID RegistrationContext,
_Inout_ POB_PRE_OPERATION_INFORMATION OperationInformation
);
typedef VOID
(*POB_POST_OPERATION_CALLBACK) (
_In_ PVOID RegistrationContext,
_In_ POB_POST_OPERATION_INFORMATION OperationInformation
);
以及回调中的POB_POST_OPERATION_INFORMATION
typedef struct _OB_PRE_OPERATION_INFORMATION {
_In_ OB_OPERATION Operation;
union {
_In_ ULONG Flags;
struct {
_In_ ULONG KernelHandle:1;
_In_ ULONG Reserved:31;
};
};
_In_ PVOID Object;
_In_ POBJECT_TYPE ObjectType;
_Out_ PVOID CallContext;
_In_ POB_PRE_OPERATION_PARAMETERS Parameters;
} OB_PRE_OPERATION_INFORMATION, *POB_PRE_OPERATION_INFORMATION;
现在我们所有的材料已经备齐了,可以来写一些demo
我们知道,任务管理器里面结束进程也是通过R3的API,它也是调用类似TerminateProcess这样的API对进程进行终结,让我们来回顾一下这个函数的参数,可以看见,我们就需要向其中传入句柄,传入句柄,也就是需要打开句柄OpenProcess。
BOOL TerminateProcess(
[in] HANDLE hProcess,
[in] UINT uExitCode
);
所以,我们能不能用注册一个对象回调,让进程对象在被申请的时候检查,如果是我们的程序,就清空当前进程权限位,使其无法打开我们的程序。
demo
#include<ntifs.h>
//0x78 bytes (sizeof)
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks; //0x0
LIST_ENTRY InMemoryOrderLinks; //0x8
LIST_ENTRY InInitializationOrderLinks; //0x10
VOID* DllBase; //0x18
VOID* EntryPoint; //0x1c
ULONG SizeOfImage; //0x20
UNICODE_STRING FullDllName; //0x24
UNICODE_STRING BaseDllName; //0x2c
ULONG Flags; //0x34
可以不要之后的内容
}LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
PVOID RegistrationHandle = NULL;
EXTERN_C PUCHAR NTAPI PsGetProcessImageFileName(PEPROCESS Process);
//前回调
OB_PREOP_CALLBACK_STATUS PreCallBack(
_In_ PVOID RegistrationContext,
_Inout_ POB_PRE_OPERATION_INFORMATION OperationInformation
){
//获取当前进程
PEPROCESS CurProcess = IoGetCurrentProcess();
//获取被操作的进程
PEPROCESS TargetProcess = (PEPROCESS)OperationInformation->Object;
//获取当前进程名字
PUCHAR CurName = PsGetProcessImageFileName(CurProcess);
//获取目标进程名字
PUCHAR TargetName = PsGetProcessImageFileName(TargetProcess);
//获取当前操作
PUCHAR CurOperation = OperationInformation->Operation;
//获取Flags
ULONG KernelMode = OperationInformation->Flags&0x1;
PUCHAR CurOperationName = NULL;
if (CurOperation == 0x1) {
CurOperationName = "Create";
DbgPrint("[+] CurrentName is %s,TargeName is %s,Operation is %s in ring%d\n", CurName, TargetName, CurOperationName, KernelMode * 3);
}
else if(CurOperation == 0x10) {
CurOperationName = "Duplicate";
}
else {
CurOperationName = "Create and Duplicate";
}
if (strstr("my.exe", TargetName)) {
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess = 0;
}
return OB_PREOP_SUCCESS;
}
//后回调
VOID PostCallBack(
_In_ PVOID RegistrationContext,
_In_ POB_POST_OPERATION_INFORMATION OperationInformation
) {
}
VOID DriverUnload(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) {
DbgBreakPoint();
if (RegistrationHandle) {
ObUnRegisterCallbacks(RegistrationHandle);
}
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) {
//初始化回调信息结构
OB_CALLBACK_REGISTRATION obRegInfo = { 0 };
obRegInfo.Version = ObGetFilterVersion();
obRegInfo.OperationRegistrationCount = 1;
obRegInfo.RegistrationContext = NULL;
RtlInitUnicodeString(&obRegInfo.Altitude, L"123456");
//回调函数结构
OB_OPERATION_REGISTRATION obCallBcakInfo = { 0 };
obCallBcakInfo.ObjectType = *PsProcessType;
//OpenProcess和DunmplicateHandle都回调
obCallBcakInfo.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
obCallBcakInfo.PreOperation = PreCallBack;
obCallBcakInfo.PostOperation = PostCallBack;
PLDR_DATA_TABLE_ENTRY ex = (PLDR_DATA_TABLE_ENTRY)pDriver->DriverSection;
ex->Flags |= 0x20;
//注册
obRegInfo.OperationRegistration = &obCallBcakInfo;
NTSTATUS state = ObRegisterCallbacks(&obRegInfo, &RegistrationHandle);
DbgPrint("[+] The state is %x\n", state);
if (NT_SUCCESS(state)) {
}
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
这里只略微提到两点:1.这类回调如果出现了C0x000022的问题,那么大概率是Flags有问题,Flags检查没过,具体的东西我之前的文章里面说过了
2.PreCallBack和PostCallBack里面一般拦截放在PreCallBack,也就是Create或Delicate对象前。
通过上述代码,我们就可以实现类似杀软那种别人用任务管理器关不掉的效果
为了方便测试,又用的CPU-z,改了下名字
程序启动之后,加载驱动
这里说一下发现,上了驱动之后,任务管理器一直在不间断的操作几个进程的对象
这时候我们尝试使用任务管理器结束进程,可以看见我们操作相关对象的时候就被拒绝了,这就达到了我们的目的。
最后的问题
这里有一个问题我没有解决,那就是在停止驱动的时候会蓝屏,0x00000039,在取消回调的时候回调句柄是被赋了值的,但是我一直没搞清楚为什么会蓝屏。中间还修改了DriverSection的Flags的值,把它改了回去,但是还是无济于事。