PspCidTable存放着系统中全部的进程和线程对象,其索引也就是进程ID(PID)或线程ID(TID).先经过它来看看windbg里的HANDLE_TABLE结构:html
能够看到地址 0x83f41bc4中存放的内容是 0x 8da010a8,这是系统的_HANDLE_TABLE的结构。spa
好了,如今windbg是获得HANDLE_TABLE结构了,仍是要代码实现的。这里只简单用一下加偏移:线程
//system进程的eprocess地址3d
PEPROCESS EProcess = (PEPROCESS)0x86aee798;指针
#define _HANDLE_TABLE_OFFSET_EPROCESS 0x0f4code
HandleTable = (PHANDLE_TABLE)(*((ULONG*)((UINT8*)EProcess + _HANDLE_TABLE_OFFSET_EPROCESS)));orm
HANDLE_TABLE这里有两个须要关注的内容,第一个是TableCode,另外一个是NextHandleNeedingPool.htm
TableCode成员,能够认为它是一个指向句柄表的地址,其中这个数值的低2位表示的是句柄表的层数,因此咱们实际获得的句柄表的地址是要掩掉低2位的,也就是 TableCode&& 0xFFFFFFF8(TableCode&~0x3) ,其中低两位 为0时表示 1层索引,为1时表示2层索引,为2时表示3层索引,最后咱们索引到的是一个_HANDLE_TABLE_ENTRY的结构,这个结构里面有咱们要的_EPROCESS对象地址。对象
借某位博主图一用(图片源自:http://www.cnblogs.com/ck1020/p/5897460.html)blog
这个图能够说是很是清晰了(有一处小错误,TableCode低两位为2是三级索引,不过瑕不掩瑜呐)
对于每个索引表大小都为1页 4KB,其中一级表存放的是8Byte的_HANDLE_TABLE_ENTRY的结构,因此每个1级表就只能存放512个项;
2级表存放的是1级表的地址(4Byte)那么每个2级表可以存放4KB/4B = 1024个1级表的地址,若是存在3级表的话,至关庞大的数目了。
回到上面能够看到TableCode为0x8f71001, 低2位为01 ,能够知道当前是2层索引结构,其中句柄表的地址为0x8f71000,经过命令 dd 0x0x8f71000看到对应的2级索引表:
能够看到就只有两项,也就是说有两个1级表,那么当前的句柄表可以容纳512*2=1024个句柄.
经过地址0x8da04000访问到第一个1级索引表来看看 :
里面是不少8字节的_HANDLE_TABLE_ENTRY,查看WRK的源码看它的结构:
typedef struct _HANDLE_TABLE_ENTRY {
//
// The pointer to the object overloaded with three ob attributes bits in
// the lower order and the high bit to denote locked or unlocked entries
//
union {
PVOID Object;
ULONG ObAttributes;
PHANDLE_TABLE_ENTRY_INFO InfoTable;
ULONG_PTR Value;
};
//
// This field either contains the granted access mask for the handle or an
// ob variation that also stores the same information. Or in the case of
// a free entry the field stores the index for the next free entry in the
// free list. This is like a FAT chain, and is used instead of pointers
// to make table duplication easier, because the entries can just be
// copied without needing to modify pointers.
//
union {
union {
ACCESS_MASK GrantedAccess;
struct {
USHORT GrantedAccessIndex;
USHORT CreatorBackTraceIndex;
};
};
LONG NextFreeTableEntry;
};
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;
typedef struct _HANDLE_TABLE_ENTRY {
//
// The pointer to the object overloaded with three ob attributes bits in
// the lower order and the high bit to denote locked or unlocked entries
//
union {
PVOID Object;
ULONG ObAttributes;
PHANDLE_TABLE_ENTRY_INFO InfoTable;
ULONG_PTR Value;
};
//
// This field either contains the granted access mask for the handle or an
// ob variation that also stores the same information. Or in the case of
// a free entry the field stores the index for the next free entry in the
// free list. This is like a FAT chain, and is used instead of pointers
// to make table duplication easier, because the entries can just be
// copied without needing to modify pointers.
//
union {
union {
ACCESS_MASK GrantedAccess;
struct {
USHORT GrantedAccessIndex;
USHORT CreatorBackTraceIndex;
};
};
LONG NextFreeTableEntry;
};
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;
经过这个结构能够看出HANDLE_TABLE_ENTRY 8Byte的前4Byte是一个Object对象,这也就是咱们要找的_EPROCESS指针,可是须要注意的是,句柄表中的Object指针的低3位则是有另外意义的:
①第0位OBJ_PROTECT_CLOSE,表示调用者是否容许关闭该句柄;
②第1位OBJ_ INHERIT,指示该进程所建立的子进程是否能够继承该句柄,便是否将该句柄项拷贝到子进程的句柄表中;
③第2位OBJ_ AUDIT_OBJECT_ CLOSE。指示关闭该对象时是否产生一个审计事件。
因此咱们在使用该指针的时候要掩掉低3Bit
也就是说对于Object=86ae88a9应该变为ObjectHeader=0x86aee799& 0xFFFFFFF8 (0x86aee799&~0x07)&~0x07 = 0x86aee798,这才是须要的_EPROCESS 的地址;
说了这么多,咱们的代码也来实现实现:
TableCode = HandleTable->TableCode;
TableCode = (ULONG)TableCode & 0xFFFFFFFC; //去掉低两位
#define _SPECIAL_PURPOSE 8
//越过特殊用途8字节到第一个HandleTableEntry
HandleTableEntry = (PHANDLE_TABLE_ENTRY)((ULONG*)((UINT8*)TableCode + _SPECIAL_PURPOSE));
//去掉低3位掩码标志,转换为对象(体)指针
//EPROCESS地址
PVOID ObjectHeader = (PVOID)((ULONG)(HandleTableEntry->Object) & 0xFFFFFFF8);
Windbg继续开进,验证一下0x86aee798,这个地址的对象类型:
(在写这个程序代码的时候,就已经WINDBG查看了system进程的EPROCESS地址写死到了代码中,回头看看,正是这个0x86aee798!!!!)
一个Process对象,
Bingo!
代码无误,代接下来循环读取HandleTableEntry结构,打印出对象头和对象体:
NTSTATUS EnumTable0(PVOID TableCode) { PHANDLE_TABLE_ENTRY HandleTableEntry = NULL; ULONG i = 0; //越过特殊用途8字节到第一个HandleTableEntry HandleTableEntry = (PHANDLE_TABLE_ENTRY)((ULONG*)((UINT8*)TableCode + _SPECIAL_PURPOSE)); for (i = 0; i<_MAX; i++) { if (MmIsAddressValid((PVOID)HandleTableEntry)) //判断该虚拟内存是否合法 { //去掉低3位掩码标志,转换为对象(体)指针 //EPROCESS地址 PVOID ObjectHeader = (PVOID)((ULONG)(HandleTableEntry->Object) & 0xFFFFFFF8); if (MmIsAddressValid(ObjectHeader)) { DbgPrint("ObjectHeader:%p\r\n",ObjectHeader); PVOID ObjectBody = (PVOID)((UINT8*)ObjectHeader + _BODY_OFFSET_OBJECT_HEADER); if (MmIsAddressValid(ObjectBody)) //这里应当判断对象是否合法 { DbgPrint("Object:%p\r\n", ObjectBody); __ObjectCount++; } } } HandleTableEntry++; //结构体指针++ 一加一个结构体 } return STATUS_SUCCESS; }
接下来回头谈谈_HANDLE_TABLE中的一个叫作NextHandleNeedingPool的成员:
这个成员描述了下次句柄表增加的时,起始的句柄值(别忘了句柄是以4为步长增加的),上面的分析咱们知道系统有2个2级索引那么最多能描述512*2=1024个_HANDLE_ENTRY,也就是说最大能表示的句柄值为1024*4=4096=0x1000,由于是从0x00开始的,因此当前的索引表状态可以描述的最大句柄上限为0x1000,这个值也就是下次句柄表扩展的起始句柄值.
最后来验证一下经过PspCidTable和进程的PID找到进程对应的_EPROCESS:
以audiodg.exe做为检验对象。
它的PID为1048(十进制),应当位于第1048/4=262 个表项,咱们每个1级索引表能容纳512个表项,PID为1048应该在第1个1级索引的第262=0x106个表项(每一个表项8Byte)
BINGO!
最后的最后总结一下流程:
(1)(WINDBG )获取到PspCidTable的地址,根据Tablecode低2位判断句柄表的层数。
(2)遍历句柄表:只有一级句柄表才是_HANDLE_TABLE_ENTRY(8字节),二级和三级都是指针(4字节),每个表都是1页(4KB)大小,
(3)获取到Object以后,能够经过ObjectHeader的TypeIndex看看是否是Process.
(4)HANDLE_TABLE_ENTRY 8Byte的前4Byte是一个Object对象,去掉低三位才是才是须要的_EPROCESS 对象的地址,_EPROCESS 对象偏移0x18处是相对于对象头的对象体。
源代码:
ScanProcessHandleTable.c
#include "ScanProcessHandleTable.h" #define _HANDLE_TABLE_OFFSET_EPROCESS 0x0f4 #define _SPECIAL_PURPOSE 8 #define _MAX 511 #define _BODY_OFFSET_OBJECT_HEADER 0x18 ULONG64 __ObjectCount = 0; NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath) { UNREFERENCED_PARAMETER(RegisterPath); NTSTATUS Status = STATUS_SUCCESS; //system进程的eprocess地址 PEPROCESS EProcess = (PEPROCESS)0x86aee798; PDEVICE_OBJECT DeviceObject = NULL; DriverObject->DriverUnload = DriverUnload; SeScanProcessHandleTable(EProcess); return Status; } NTSTATUS SeScanProcessHandleTable(PEPROCESS EProcess) { NTSTATUS Status = STATUS_UNSUCCESSFUL; PHANDLE_TABLE HandleTable = NULL; PVOID TableCode = NULL; ULONG Flag = 0; if (EProcess==NULL) { return Status; } HandleTable = (PHANDLE_TABLE)(*((ULONG*)((UINT8*)EProcess + _HANDLE_TABLE_OFFSET_EPROCESS))); if (HandleTable==NULL) { return Status; } TableCode = HandleTable->TableCode; TableCode = (ULONG)TableCode & 0xFFFFFFFC; //去掉低两位 Flag = (ULONG)(HandleTable->TableCode) & 0x03; //00 01 10 11 switch (Flag) { case 0: { EnumTable0(TableCode); break; } case 1: { EnumTable1(TableCode); break; } case 2: { EnumTable2(TableCode); break; } case 3: { EnumTable3(TableCode); break; } } } NTSTATUS EnumTable0(PVOID TableCode) { PHANDLE_TABLE_ENTRY HandleTableEntry = NULL; ULONG i = 0; //越过特殊用途8字节到第一个HandleTableEntry HandleTableEntry = (PHANDLE_TABLE_ENTRY)((ULONG*)((UINT8*)TableCode + _SPECIAL_PURPOSE)); for (i = 0; i<_MAX; i++) { if (MmIsAddressValid((PVOID)HandleTableEntry)) //判断该虚拟内存是否合法 { //去掉低3位掩码标志,转换为对象(体)指针 //EPROCESS地址 PVOID ObjectHeader = (PVOID)((ULONG)(HandleTableEntry->Object) & 0xFFFFFFF8); if (MmIsAddressValid(ObjectHeader)) { DbgPrint("ObjectHeader:%p\r\n",ObjectHeader); PVOID ObjectBody = (PVOID)((UINT8*)ObjectHeader + _BODY_OFFSET_OBJECT_HEADER); if (MmIsAddressValid(ObjectBody)) //这里应当判断对象是否合法 { DbgPrint("Object:%p\r\n", ObjectBody); __ObjectCount++; } } } HandleTableEntry++; //结构体指针++ 一加一个结构体 } return STATUS_SUCCESS; } NTSTATUS EnumTable1(PVOID TableCode) { do { EnumTable0(*(ULONG*)TableCode); (UINT8*)TableCode += sizeof(ULONG); } while (*(ULONG*)TableCode != 0 && MmIsAddressValid(*(ULONG*)TableCode)); return STATUS_SUCCESS; } NTSTATUS EnumTable2(PVOID TableCode) { do { EnumTable1(*(ULONG*)TableCode); (UINT8*)TableCode += sizeof(ULONG); } while (*(ULONG*)TableCode != 0 && MmIsAddressValid(*(ULONG*)TableCode)); return STATUS_SUCCESS; } NTSTATUS EnumTable3(PVOID TableCode) { do { EnumTable2(*(ULONG*)TableCode); (UINT8*)TableCode += sizeof(ULONG); } while (*(ULONG*)TableCode != 0 && MmIsAddressValid(*(ULONG*)TableCode)); return STATUS_SUCCESS; } VOID DriverUnload(PDRIVER_OBJECT DriverObject) { UNREFERENCED_PARAMETER(DriverObject); DbgPrint("DriverUnload()\r\n"); }
ScanProcessHandleTable.h
#pragma once #include <ntifs.h> typedef struct _HANDLE_TABLE { PVOID TableCode; PEPROCESS QuotaProcess; HANDLE UniqueProcessId; ULONG HandleTableLock; LIST_ENTRY HandleTableList; ULONG HandleContentionEvent; PVOID DebugInfo; LONG ExtraInfoPages; ULONG Flags; ULONG FirstFreeHandle; PVOID LastFreeHandleEntry; ULONG HandleCount; ULONG NextHandleNeedingPool; ULONG HandleCountHighWatermark; } HANDLE_TABLE, *PHANDLE_TABLE; typedef struct _HANDLE_TABLE_ENTRY { union { PVOID Object; ULONG ObAttributes; PVOID InfoTable; PVOID Value; }; union { union { ULONG GrantedAccess; struct { USHORT GrantedAccessIndex; USHORT CreatorBackTraceIndex; }; }; ULONG NextFreeTableEntry; }; } HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY; NTSTATUS EnumTable0(PVOID TableCode); NTSTATUS EnumTable1(PVOID TableCode); NTSTATUS EnumTable2(PVOID TableCode); NTSTATUS EnumTable3(PVOID TableCode); NTSTATUS SeScanProcessHandleTable(PEPROCESS EProcess); VOID DriverUnload(PDRIVER_OBJECT DriverObject);