首页
社区
课程
招聘
[旧帖] [原创]之前学习句柄表时的笔记 0.00雪花
发表于: 2014-11-24 21:12 2046

[旧帖] [原创]之前学习句柄表时的笔记 0.00雪花

2014-11-24 21:12
2046
Windows句柄表学习笔记
遍历系统进程有一种方法就是通过解析句柄表来遍历,于是学习了一下Windows句柄表。
句柄表位于EPROCESS结构的+0x0f4 偏移处(win7x86),先来看一下句柄表的结构:

kd> dt 0x83d58fc0 _HANDLE_TABLE
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0x94bac001                                //句柄表
   +0x004 QuotaProcess     : 0x884c2d40 _EPROCESS                //所属进程的EPROCESS
   +0x008 UniqueProcessId  : 0x000001ec Void                        //进程PID
   +0x00c HandleLock       : _EX_PUSH_LOCK                        //句柄锁
   +0x010 HandleTableList  : _LIST_ENTRY [ 0x83d63538 - 0x83d42538 ]//句柄表的双向链表
   +0x018 HandleContentionEvent : _EX_PUSH_LOCK
   +0x01c DebugInfo        : (null)
   +0x020 ExtraInfoPages   : 0n0
   +0x024 Flags            : 0
   +0x024 StrictFIFO       : 0y0
   +0x028 FirstFreeHandle  : 0x5c4
   +0x02c LastFreeHandleEntry : 0x94babff8 _HANDLE_TABLE_ENTRY
   +0x030 HandleCount      : 0x340                        //进程中句柄的总数
   +0x034 NextHandleNeedingPool : 0x1000                //当前句柄池的上界
   +0x038 HandleCountHighWatermark : 0x350

其中+0x010处 HandleTableList 句柄表的双向链表把所有进程的句柄表连接起来,遍历进程可以通过此链,隐藏进程也可以在这个地方做手脚。
里面最重要的数据也就是TableCode它了,TableCode的低两位用于表示当前句柄表的级数:0、1、2分别表示一级表、二级表和三级表。
其结构,在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;

表示的意义: 
1. 对象指针Object有效则第二个域为访问掩码GrantedAccess 
2. 第一个域为0,第二个域可能是NextFreeTableEntry,也可能为审计,后面会
有相关算法用到这个域,要根据上下文来判断。 
这里的Object并不是“真正”的对象指针,而是包括了对象的指针域对象的属性域,由于在内核中对象总是8字节对齐的,那么指向对象的指针最低3位总是0,微软把这3位也利用上,Object的最低3位做为对象的属性,看下面的一组宏定义: 
#define OBJ_HANDLE_ATTRIBUTES (OBJ_PROTECT_CLOSE | OBJ_INHERIT | OBJ_AUDIT_OBJECT_CLOSE) 
第0位 OBJ_PROTECT_CLOSE:句柄表项是否被锁定,1锁定,0未锁定 
第1位 OBJ_INHERIT:指向该进程所创建的子进程是否可以继承该句柄,既是否将该句柄项 拷贝到它的句柄表中 
第2位 OBJ_AUDIT_OBJECT_CLOSE:关闭该对象时是否产生一个审计事件

首先看一下为什么要把句柄表设计成分级,先计算一下一张句柄表能装下多少句柄,
一张表的大小为一页,也就是4 K=0x1000 Byte,HANDLE_TABLE_ENTRY的大小为8Byte,
一张表能装 0x1000 / 8 = 0x200 =512 个HANDLE_TABLE_ENTRY,而句柄大小为4Byte,所以一张表能装0x200 * 4 = 0x800个句柄,也就是不超过0x800个句柄(0x800 - 4)有的进程如果服务比较多,句柄很定少不了,当大于0x800 个一张表会装不下,所以设计成了分级表。三级表装的是指向二级表的指针,二级表装的是指向一级表的指针。
下面看一下一个一级表的内容
+0x000 TableCode        : 0x94bac001                                //句柄表
由于TableCode的低2位记录了是几级表,所以0x94bac001后两位为1,表示这是一个二级句柄表,句柄表的真是地址是掩去低两位0x94bac000
kd> dd 0x94bac000
94bac000  83d64000 94bab000 00000000 00000000
94bac010  00000000 00000000 00000000 00000000
94bac020  00000000 00000000 00000000 00000000
94bac030  00000000 00000000 00000000 00000000
94bac040  00000000 00000000 00000000 00000000
94bac050  00000000 00000000 00000000 00000000
94bac060  00000000 00000000 00000000 00000000
94bac070  00000000 00000000 00000000 00000000
这个二级句柄表中存放了两个一级表的指针,下面看一下一级表的内容:
kd> dd 83d64000
83d64000  00000000 fffffffe 91719841 00000003
83d64010  87caf099 001fffff 83c765c9 00020019
83d64020  884d9d29 001f0001 884c7e69 001f0001
83d64030  884d9be1 001f0001 884d94c9 001f0001
83d64040  83c7b489 000f003f 87cb3601 001f0001
83d64050  83cb71d9 00000001 8782f421 00100003
83d64060  880183e1 00100003 88002311 001f0001
83d64070  880022d1 001f0003 878b5659 00000804

其中91719841 00000003分别对应HANDLE_TABLE_ENTRY中的Object和GrantedAccess,
其中Object的最低3位做为对象的属性,所以_OBJECT_HEADER真正的地址为掩去低三位,即:91719840
kd> dt _object_header 91719840
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n84               
   +0x004 HandleCount      : 0n46
   +0x004 NextToFree       : 0x0000002e Void
   +0x008 Lock             : _EX_PUSH_LOCK        //推锁
   +0x00c TypeIndex        : 0x3 ''                                        //类型序号
   +0x00d TraceFlags       : 0 ''
   +0x00e InfoMask         : 0xa ''
   +0x00f Flags            : 0x10 ''
   +0x010 ObjectCreateInfo : 0x8418bcc0 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x8418bcc0 Void
   +0x014 SecurityDescriptor : 0x91699c45 Void
   +0x018 Body             : _QUAD

其中偏移+0x018出的Body是Object的的值。这里win7 与 xp有所不同的就是 偏移0x008处,Win7把 XP 的 Type 类型改成了一个推锁,取而代之的便是下面的TypeIndex类型序号,Win7加了一个新函数ObGetObjectType(),反汇编此函数发现:
kd> u ObGetObjectType
nt!ObGetObjectType:
842ae7bd 8bff            mov     edi,edi
842ae7bf 55              push    ebp
842ae7c0 8bec            mov     ebp,esp
842ae7c2 8b4508          mov     eax,dword ptr [ebp+8]
842ae7c5 0fb640f4        movzx   eax,byte ptr [eax-0Ch]
842ae7c9 8b048500991984  mov     eax,dword ptr nt!ObTypeIndexTable (84199900)[eax*4]
842ae7d0 5d              pop     ebp
842ae7d1 c20400          ret     4

函数ObGetObjectType()中 mov     eax,dword ptr [ebp+8] 来获取TypeIndex的值,之后movzx   eax,byte ptr [eax-0Ch] 地址计算方法:OBJECT地址 - sizeof(_OBJECT_HEADER) + TypeIndex的偏移,即:eax - 0x018 + 0x00c = eax-0Ch 。之后根据eax在全局变量ObTypeIndexTable 表中按序号查找。下面看一下ObTypeIndexTable 表:
kd> dd ObTypeIndexTable
84199900  00000000 bad0b0b0 86744830 86744768
84199910  867446a0 86744470 867ddf40 867dde78
84199920  867dddb0 867ddce8 867ddc20 867dd568
84199930  867f9418 867f9350 86804418 86804350
84199940  86804288 867f8588 867f84c0 867f83f8
84199950  867fac90 867fabc8 867fab00 867faa38
84199960  867fa970 867fa8a8 867fa7e0 867fa718
84199970  867fa650 86801f78 86801eb0 86801de8

最后看一下 序号为0x3 处的对象类型:
kd> !object 86744768
Object: 86744768  Type: (86744830) Type
    ObjectHeader: 86744750 (new version)
    HandleCount: 0  PointerCount: 2
    Directory Object: 8b605950  Name: Directory

验证此对象:
kd> !object 91719840+0x018
Object: 91714d50  Type: (86744768) Directory
    ObjectHeader: 91714d38 (new version)
    HandleCount: 47  PointerCount: 85
    Directory Object: 8b605e00  Name: KnownDlls

kd> dt _object_type 86744768
nt!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY [ 0x86744768 - 0x86744768 ]
   +0x008 Name             : _UNICODE_STRING "Directory"
   +0x010 DefaultObject    : 0x8418dbe0 Void
   +0x014 Index            : 0x3 ''
   +0x018 TotalNumberOfObjects : 0x30
   +0x01c TotalNumberOfHandles : 0x82
   +0x020 HighWaterNumberOfObjects : 0x30
   +0x024 HighWaterNumberOfHandles : 0x88
   +0x028 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x078 TypeLock         : _EX_PUSH_LOCK
   +0x07c Key              : 0x65726944
   +0x080 CallbackList     : _LIST_ENTRY [ 0x867447e8 - 0x867447e8 ]

对比无误!

关于PspCidTable

PspCidTable为一个全局变量,其格式与普通的句柄表是完全一样的.
但它与每个进程私有的句柄表有以下不同: 
1.PspCidTable中存放的对象是系统中所有的进线程对象指针,其索引就是PID和CID 
2.PspCidTable中存放是对象体(指向EPROCESS和ETHREAD),而每个进程私有的句柄表则存放的是对象头(OBJECT_HEADER) 
3.PspCidTable是一个独立的句柄表,而每个进程私有的句柄表以一个双链连接起来

如何寻找PspCidTable:
1.PspCidTable没有导出,通常的方法是找特征码:PsLookUpProcessByProcessId
2另一种方法是从KPCR====>KdVersionBlock

首先还是先看一下PspCidTable
kd> dd pspcidtable
84198f34  8b801100 00000000 80000020 00000101
84198f44  80000330 80000024 00000000 00000000
84198f54  00000000 00000000 00000000 00000113
84198f64  00000000 00000000 8414935a 00000000
84198f74  00000000 00000000 00000000 00000008
84198f84  00000000 84198f88 84198f88 00000000
84198f94  00000000 00000000 00000000 00000000
84198fa4  00000000 807ccc38 807c8c38 00000000

8b801100 便是_HANDLE_TABLE:

kd> dt _handle_table 8b801100
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0x9bd25001
   +0x004 QuotaProcess     : (null)
   +0x008 UniqueProcessId  : (null)
   +0x00c HandleLock       : _EX_PUSH_LOCK
   +0x010 HandleTableList  : _LIST_ENTRY [ 0x8b801110 - 0x8b801110 ]
   +0x018 HandleContentionEvent : _EX_PUSH_LOCK
   +0x01c DebugInfo        : (null)
   +0x020 ExtraInfoPages   : 0n0
   +0x024 Flags            : 1
   +0x024 StrictFIFO       : 0y1
   +0x028 FirstFreeHandle  : 0x11c
   +0x02c LastFreeHandleEntry : 0x9bd26740 _HANDLE_TABLE_ENTRY
   +0x030 HandleCount      : 0x31f
   +0x034 NextHandleNeedingPool : 0x1000
   +0x038 HandleCountHighWatermark : 0x32f

之后句柄表   +0x000 TableCode        : 0x9bd25001
可以看到这是一张二级表:

kd> dd 0x9bd25000
9bd25000  8b804000 9bd26000 00000000 00000000
9bd25010  00000000 00000000 00000000 00000000
9bd25020  00000000 00000000 00000000 00000000
9bd25030  00000000 00000000 00000000 00000000
9bd25040  00000000 00000000 00000000 00000000
9bd25050  00000000 00000000 00000000 00000000
9bd25060  00000000 00000000 00000000 00000000
9bd25070  00000000 00000000 00000000 00000000

查看一级表的内容:

kd> dd 8b804000
8b804000  00000000 fffffffe 869dd8e9 00000000
8b804010  869dd611 00000000 869fcc81 00000000
8b804020  86a00c81 00000000 86a009a9 00000000
8b804030  86a08021 00000000 86a08919 00000000
8b804040  86a08641 00000000 86a08369 00000000
8b804050  86a04d49 00000000 86a04a71 00000000
8b804060  86a04799 00000000 86a044c1 00000000
8b804070  869f9021 00000000 869f9d49 00000000

由于 PspCidTable中存放是对象体(指向EPROCESS和ETHREAD),869dd8e9 抹去后三位便是指向 System 的 _EPROCESS的指针:

抹去后三位是869dd8e8

kd> !process 0 0 system
PROCESS 869dd8e8  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 00185000  ObjectTable: 8b801b80  HandleCount: 648.
    Image: System

验证正确!

句柄表学习记录.doc

[课程]FART 脱壳王!加量不加价!FART作者讲授!

上传的附件:
收藏
免费 0
支持
分享
最新回复 (4)
雪    币: 118
活跃值: (72)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
多谢楼主分享,看雪有你更精彩
2014-11-24 23:52
0
雪    币: 294
活跃值: (119)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
3
多谢楼主分享,看雪有你更精彩
2014-11-25 00:15
0
雪    币: 37
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
不错,学习了
2014-11-26 11:36
0
雪    币: 14
活跃值: (77)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5

一张表能装 0x1000 / 8 = 0x200 =512 个HANDLE_TABLE_ENTRY,而句柄大小为4Byte,所以一张表能装0x200 * 4 = 0x800个句柄

敢问楼主, 这里0x800个句柄是如何计算的? 我怎么看不懂呢? 我的思路是, 一张表大小为4KB, 而句柄大小是4Byte, 所以一张表能装 4KB/4B=1K个句柄呀.还望楼主解惑, 如果有什么说的不对, 让楼主见笑了
2015-6-4 19:57
0
游客
登录 | 注册 方可回帖
返回
//