遍历Windows内核ObjectType
一、背景
Windows内核中有很多类型,例如PROCESS、THREAD、FILE、MUTANT,这些类型都由对象管理器集中管理,见下图:
其中有些对象类型是导出的,例如有IoDriverObjectType, PsProcessType等,有些是文档化的,有些是导出的,还有的是未导出的。在使用未导出的对象指针时就得想办法获得,在此遍历对象类型及其相关信息,使用未导出的对象类型时可以直接使用,对象类型结构之类不作详细分析,直接上相关实现的分析。
二、几个主要的内核变量
ObpTypeObjectType、ObpObjectTypes、ObTypeIndexTable。
- ObpTypeObjectType
这个表保存的是原始对象名称为"Type"的对象类型。Windbg调试Win7格式化如下:
5: kd> dq nt!ObpTypeObjectType fffff800`0666e5a8 fffffa80`30e4c530 00000000`00000000 fffff800`0666e5b8 00000000`00000000 fffffa80`30e4c530 fffff800`0666e5c8 fffffa80`30e4c3e0 fffffa80`30e4c290 fffff800`0666e5d8 fffffa80`30e525a0 fffffa80`30e52380 fffff800`0666e5e8 fffffa80`30e52230 fffffa80`30ee0080 fffff800`0666e5f8 fffffa80`30ee0f30 fffffa80`30ee0de0 fffff800`0666e608 fffffa80`30ee1080 fffffa80`30f5a940 fffff800`0666e618 fffffa80`30f4ca10 fffffa80`30f4c8c0
然后取第一个数据查看如下:
5: kd> dt _object_type fffffa80`30e4c530 ntdll!_OBJECT_TYPE +0x000 TypeList : _LIST_ENTRY [ 0xfffffa80`30e4c4e0 - 0xfffffa80`3205db20 ] +0x010 Name : _UNICODE_STRING "Type" +0x020 DefaultObject : 0xfffff800`0666e7a0 Void +0x028 Index : 0x2 '' +0x02c TotalNumberOfObjects : 0x2a +0x030 TotalNumberOfHandles : 0 +0x034 HighWaterNumberOfObjects : 0x2a +0x038 HighWaterNumberOfHandles : 0 +0x040 TypeInfo : _OBJECT_TYPE_INITIALIZER +0x0b0 TypeLock : _EX_PUSH_LOCK +0x0b8 Key : 0x546a624f +0x0c0 CallbackList : _LIST_ENTRY [ 0xfffffa80`30e4c5f0 - 0xfffffa80`30e4c5f0 ]
这个就是第一个名为Type的ObjectType。再取后边第五个非fffffa80`30e4c530的看:
5: kd> dt _object_type fffffa80`30e4c3e0 ntdll!_OBJECT_TYPE +0x000 TypeList : _LIST_ENTRY [ 0xfffffa80`30e4c3e0 - 0xfffffa80`30e4c3e0 ] +0x010 Name : _UNICODE_STRING "Directory" +0x020 DefaultObject : 0xfffff800`0666e7a0 Void +0x028 Index : 0x3 '' +0x02c TotalNumberOfObjects : 0x25 +0x030 TotalNumberOfHandles : 0x70 +0x034 HighWaterNumberOfObjects : 0x29 +0x038 HighWaterNumberOfHandles : 0x83 +0x040 TypeInfo : _OBJECT_TYPE_INITIALIZER +0x0b0 TypeLock : _EX_PUSH_LOCK +0x0b8 Key : 0x65726944 +0x0c0 CallbackList : _LIST_ENTRY [ 0xfffffa80`30e4c4a0 - 0xfffffa80`30e4c4a0 ]
其为目录对象。
那似乎通过ObpTypeObjectType这个就可以遍历所有的对象类型,之前查看其它的文章也有这个指出的。
那如何获取到ObpTypeObjectType,根据Windbg调试发现ObCreateObjectTypeEx中有对其的引用 :
nt!ObCreateObjectTypeEx+0x2e3: fffff800`068a1373 0fb74602 movzx eax,word ptr [rsi+2] fffff800`068a1377 488d4c2460 lea rcx,[rsp+60h] fffff800`068a137c 488bd6 mov rdx,rsi fffff800`068a137f 6689442462 mov word ptr [rsp+62h],ax fffff800`068a1384 e827fec0ff call nt!RtlCopyUnicodeString (fffff800`064b11b0) fffff800`068a1389 4c8b2518d2dcff mov r12,qword ptr [nt!ObpTypeObjectType (fffff800`0666e5a8)] fffff800`068a1390 4d3be6 cmp r12,r14 fffff800`068a1393 7555 jne nt!ObCreateObjectTypeEx+0x35a (fffff800`068a13ea) Branch
而ObCreateObjectTypeEx又被ObCreateObjectType调用:
5: kd> uf nt!ObCreateObjectType nt!ObCreateObjectType: fffff800`068a1a90 4883ec38 sub rsp,38h fffff800`068a1a94 4c894c2420 mov qword ptr [rsp+20h],r9 fffff800`068a1a99 4533c9 xor r9d,r9d fffff800`068a1a9c e8eff5ffff call nt!ObCreateObjectTypeEx (fffff800`068a1090) fffff800`068a1aa1 4883c438 add rsp,38h fffff800`068a1aa5 c3 ret
而ObCreateObjectType是导出的。
似乎得出结论可以用此来定位。
但在Win10系统上格式化结构却得出来的数据不正确,见下:
0: kd> dq nt!ObpTypeObjectType fffff806`11c25b30 ffffb386`f147fea0 ffffe001`81a2fce0 fffff806`11c25b40 00000000`00000000 00000000`00000000 fffff806`11c25b50 00000000`00000000 00000000`00000000 fffff806`11c25b60 00000000`00000000 00000000`00000000 fffff806`11c25b70 00000000`00000000 00000000`00000000 fffff806`11c25b80 00000000`00000000 00000000`00000000 fffff806`11c25b90 00000000`00000000 00000000`00000000 fffff806`11c25ba0 00000000`00000000 00000000`00000000
发现除了第一个头外后边都是空的。
其原因就是在Win7上后边连续填充的数据对应的是另一个ObpObjectTypes的数据。
5: kd> dq nt!ObpTypeObjectType fffff800`0666e5a8 fffffa80`30e4c530 00000000`00000000 fffff800`0666e5b8 00000000`00000000 fffffa80`30e4c530 fffff800`0666e5c8 fffffa80`30e4c3e0 fffffa80`30e4c290 fffff800`0666e5d8 fffffa80`30e525a0 fffffa80`30e52380 fffff800`0666e5e8 fffffa80`30e52230 fffffa80`30ee0080 fffff800`0666e5f8 fffffa80`30ee0f30 fffffa80`30ee0de0 fffff800`0666e608 fffffa80`30ee1080 fffffa80`30f5a940 fffff800`0666e618 fffffa80`30f4ca10 fffffa80`30f4c8c0 5: kd> dq nt!ObpObjectTypes fffff800`0666e5c0 fffffa80`30e4c530 fffffa80`30e4c3e0 fffff800`0666e5d0 fffffa80`30e4c290 fffffa80`30e525a0 fffff800`0666e5e0 fffffa80`30e52380 fffffa80`30e52230 fffff800`0666e5f0 fffffa80`30ee0080 fffffa80`30ee0f30 fffff800`0666e600 fffffa80`30ee0de0 fffffa80`30ee1080 fffff800`0666e610 fffffa80`30f5a940 fffffa80`30f4ca10 fffff800`0666e620 fffffa80`30f4c8c0 fffffa80`30f30c30 fffff800`0666e630 fffffa80`30f30ae0 fffffa80`30f538c0
可以看到 ObpTypeObjectType之后第三个数据的地址及为ObpObjectTypes的开始地址。
而在Win10上是不连续的,没办法用ObpTypeObjectType来遍历了。
0: kd> dq nt!ObpTypeObjectType fffff806`11c25b30 ffffb386`f147fea0 ffffe001`81a2fce0 fffff806`11c25b40 00000000`00000000 00000000`00000000 fffff806`11c25b50 00000000`00000000 00000000`00000000 fffff806`11c25b60 00000000`00000000 00000000`00000000 fffff806`11c25b70 00000000`00000000 00000000`00000000 fffff806`11c25b80 00000000`00000000 00000000`00000000 fffff806`11c25b90 00000000`00000000 00000000`00000000 fffff806`11c25ba0 00000000`00000000 00000000`00000000 0: kd> dq nt!ObpObjectTypes fffff806`11c25280 ffffb386`f147fea0 ffffb386`f1460a10 fffff806`11c25290 ffffb386`f145fe70 ffffb386`f14c7e80 fffff806`11c252a0 ffffb386`f14c77a0 ffffb386`f14c7220 fffff806`11c252b0 ffffb386`f14c7640 ffffb386`f14c70c0 fffff806`11c252c0 ffffb386`f14c7900 ffffb386`f14c7a60 fffff806`11c252d0 ffffb386`f14c7380 ffffb386`f14c74e0 fffff806`11c252e0 ffffb386`f14c7d20 ffffb386`f14c7bc0 fffff806`11c252f0 ffffb386`f14f1f00 ffffb386`f14f1c40
- ObpObjectTypes
这个变量来说确实是保存了所有的对象类型,但有个问题是不太容易从导出的函数来定位这个数据。IDA查看其代码引用处如下:
第2、3、4个函数不是直接使用的ObpObjectTypes,而其它的都不能方便的从导出函数进行定位。
所以此方法不通。
- ObTypeIndexTable
此变量是以索顺序来存放对象类型的指针的,结构数据如下:
5: kd> dq ObTypeIndexTable
fffff800`06670100 00000000`00000000 00000000`bad0b0b0
fffff800`06670110 fffffa80`30e4c530 fffffa80`30e4c3e0
fffff800`06670120 fffffa80`30e4c290 fffffa80`30e525a0
fffff800`06670130 fffffa80`30e52380 fffffa80`30e52230
fffff800`06670140 fffffa80`30ee0080 fffffa80`30ee0f30
fffff800`06670150 fffffa80`30ee0de0 fffffa80`30ee1080
fffff800`06670160 fffffa80`30f5a940 fffffa80`30f4ca10
fffff800`06670170 fffffa80`30f4c8c0 fffffa80`30f30c30
5: kd> dt _object_type fffffa80`30e4c530
nt!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY [ 0xfffffa80`30e4c4e0 - 0xfffffa80`3205db20 ]
+0x010 Name : _UNICODE_STRING "Type"
+0x020 DefaultObject : 0xfffff800`0666e7a0 Void
+0x028 Index : 0x2 ''
+0x02c TotalNumberOfObjects : 0x2a
+0x030 TotalNumberOfHandles : 0
+0x034 HighWaterNumberOfObjects : 0x2a
+0x038 HighWaterNumberOfHandles : 0
+0x040 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x0b0 TypeLock : _EX_PUSH_LOCK
+0x0b8 Key : 0x546a624f
+0x0c0 CallbackList : _LIST_ENTRY [ 0xfffffa80`30e4c5f0 - 0xfffffa80`30e4c5f0 ]
5: kd> dt _object_type fffffa80`30e4c3e0
nt!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY [ 0xfffffa80`30e4c3e0 - 0xfffffa80`30e4c3e0 ]
+0x010 Name : _UNICODE_STRING "Directory"
+0x020 DefaultObject : 0xfffff800`0666e7a0 Void
+0x028 Index : 0x3 ''
+0x02c TotalNumberOfObjects : 0x29
+0x030 TotalNumberOfHandles : 0x76
+0x034 HighWaterNumberOfObjects : 0x29
+0x038 HighWaterNumberOfHandles : 0x83
+0x040 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x0b0 TypeLock : _EX_PUSH_LOCK
+0x0b8 Key : 0x65726944
+0x0c0 CallbackList : _LIST_ENTRY [ 0xfffffa80`30e4c4a0 - 0xfffffa80`30e4c4a0 ]
从第2个 fffffa80`30e4c530的地址结构格式化可以看出其Index为2,对应表中的地址索引为2。其这个表就是从第2号索引开始, 第1号索引00000000`bad0b0b0 指向的实际是无效对象类型。
IDA查看其引用如下,有很多函数中都用到:
取其中较实现查找的ObGetObjectType,通过查找特征码就可以获得:
5: kd> u nt!ObGetObjectType
nt!ObGetObjectType:
fffff800`06796e60 0fb641e8 movzx eax,byte ptr [rcx-18h]
fffff800`06796e64 488d0d9592edff lea rcx,[nt!ObTypeIndexTable (fffff800`06670100)]
fffff800`06796e6b 488b04c1 mov rax,qword ptr [rcx+rax*8]
fffff800`06796e6f c3 ret
fffff800`06796e70 cc int 3
fffff800`06796e71 cc int 3
fffff800`06796e72 cc int 3
fffff800`06796e73 cc int 3
#pragma once #include <ntddk.h> #if DBG #define KDPRINT(projectName, format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL,\ projectName "::【" __FUNCTION__ "】" ##format, \ ##__VA_ARGS__ ) #else #define KDPRINT(format, ...) #endif typedef struct _OBJECT_TYPE_FLAGS { UCHAR CaseInsensitive : 1; UCHAR UnnamedObjectsOnly : 1; UCHAR UseDefaultObject : 1; UCHAR SecurityRequired : 1; UCHAR MaintainHandleCount : 1; UCHAR MaintainTypeList : 1; UCHAR SupportsObjectCallbacks : 1; UCHAR CacheAligned : 1; }OBJECT_TYPE_FLAGS, * P_OBJECT_TYPE_FLAGS; typedef struct _OBJECT_TYPE_INITIALIZER { USHORT wLength; OBJECT_TYPE_FLAGS ObjectTypeFlags; ULONG ObjcetTypeCode; ULONG InvalidAttributes; GENERIC_MAPPING GenericMapping; ULONG ValidAccessMask; ULONG RetainAccess; ULONG PoolType; ULONG DefaultPagedPoolCharge; ULONG DefaultNonPagedPoolCharge; PVOID DumpProcedure; PVOID OpenProcedure; PVOID CloseProcedure; PVOID DeleteProcedure; PVOID ParseProcedure; PVOID SecurityProcedure; PVOID QueryNameProcedure; PVOID OkayToCloseProcedure; }OBJECT_TYPE_INITIALIZER, * POBJECT_TYPE_INITIALIZER; typedef struct _OBJECT_TYPE_EX { LIST_ENTRY TypeList; UNICODE_STRING Name; ULONGLONG DefaultObject; ULONG Index; ULONG TotalNumberOfObjects; ULONG TotalNumberOfHandles; ULONG HighWaterNumberOfObjects; ULONG HighWaterNumberOfHandles; OBJECT_TYPE_INITIALIZER TypeInfo; ULONGLONG TypeLock; ULONG Key; LIST_ENTRY CallbackList; }OBJECT_TYPE_EX, * POBJECT_TYPE_EX;
PrintObjectTypeList.cpp
#include "PrintObjectTypeList.h" VOID DriverUnload(PDRIVER_OBJECT pDriverObject) { UNREFERENCED_PARAMETER(pDriverObject); KDPRINT("【PrintObjectTypeList】", "CurrentProcessId : 0x%p CurrentIRQL : 0x%u \r\n", PsGetCurrentProcessId(), KeGetCurrentIrql()); } void PrintObTypeIndexList(PVOID pObTypeIndexTable) { if (pObTypeIndexTable) { PUCHAR pStartAddress = ((PUCHAR)pObTypeIndexTable + 8 * 2); //从第2个开始 POBJECT_TYPE_EX *pTempObjectType = (POBJECT_TYPE_EX*)(pStartAddress); ULONG ulIndex = 0; while(*pTempObjectType != NULL) { KDPRINT("【PrintObjectTypeList】", "Index:%02ld Address:0x%p Name:%wZ\r\n", ulIndex, *pTempObjectType, &(*pTempObjectType)->Name); pTempObjectType++; ulIndex++; } } } PVOID GetObTypeIndexTable() { UNICODE_STRING usObGetObjectType = RTL_CONSTANT_STRING(L"ObGetObjectType"); PVOID pGetObTypeIndexTable = NULL; PVOID pObGetObjectType = (PVOID)MmGetSystemRoutineAddress(&usObGetObjectType); do { if (!pObGetObjectType) { KDPRINT("【PrintObjectTypeList】", "MmGetSystemRoutineAddress Failed! \r\n"); break; } PUCHAR pStartAddress = (PUCHAR)pObGetObjectType; PUCHAR pTempAddress = pStartAddress; for (; pTempAddress < pStartAddress + PAGE_SIZE; pTempAddress++) { if ((*(pTempAddress - 3) == 0x48) && (*(pTempAddress - 2) == 0x8d) && (*(pTempAddress - 1) == 0x0d) && (*(pTempAddress + 4) == 0x48) && (*(pTempAddress + 5) == 0x8b) && (*(pTempAddress + 6) == 0x04) && (*(pTempAddress + 7) == 0xc1)) { LONG lOffset = *(PLONG)(pTempAddress); pGetObTypeIndexTable = pTempAddress + 4 + lOffset; break; } } } while (false); if (pGetObTypeIndexTable) { KDPRINT("【ObRegisterCallback】", "Found ObTypeIndexTable Address:0x%p \r\n", pGetObTypeIndexTable); } else { KDPRINT("【PrintObjectTypeList】", "ObTypeIndexTable Not Found!\r\n"); } return pGetObTypeIndexTable; } EXTERN_C NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath) { UNREFERENCED_PARAMETER(pDriverObject); UNREFERENCED_PARAMETER(pRegistryPath); KDPRINT("【PrintObjectTypeList】", " Hello Kernel World! CurrentProcessId:0x%p CurrentIRQL:0x%u\r\n", PsGetCurrentProcessId(), KeGetCurrentIrql()); pDriverObject->DriverUnload = DriverUnload; NTSTATUS ntStatus = STATUS_SUCCESS; PVOID pGetObTypeIndexTable = GetObTypeIndexTable(); if (pGetObTypeIndexTable) { PrintObTypeIndexList(pGetObTypeIndexTable); } return ntStatus; }
四、调试结果
- Win7 x64
- Win10 x64
五、结束语
本次代码只考虑了x64环境下的Win7 和 Win10,32位环境下以及XP未考虑,但思路大同小异。
通过对对象类型的遍历,可以用在使用未导出的对象类型的时候取消对象指针。