Windbg 手工玩转句柄表

句柄是Windows对象管理中引入的一个东西,它的实际意义是对象在句柄表中的索引。Windows2000使用的是固定的三层句柄表,而WindowsXP和Windows2003都是使用的动态可扩展的三层句柄表,这是一种很优秀的结构,易扩展,且查找迅速,值得学习。通常情况下每个进程的句柄表都是一级表,当句柄数超过一级表的容量时,就会扩展为二级表,此时二级表中放的是指向一级表的指针。同样,当二级表也放满时,就会扩展为三级表,里面放指向二级表的指针。但是通常我们看不到这种情况,因为就目前的大多数情况来说,二级表的容量已经够我们用了。

接下来具体地看一下进程的句柄表。每个进程都有一个句柄表,包括System进程(不过System的句柄表稍有点特殊),但是Idle除外,因为它并不是一个事实上真正的进程。在进程对象EPROCESS中,可以直接找到句柄表指针。

来找个进程对象看看:

lkd> dt _EPROCESS 8a92cb98

nt!_EPROCESS

   ...

   +0x0c4 ObjectTable      : 0xe23d3690 _HANDLE_TABLE

   ...

句柄表是一个HANDLE_TABLE结构,继续来看:

lkd> dt _HANDLE_TABLE 0xe23d3690

nt!_HANDLE_TABLE

   +0x000 TableCode        : 0xe3202001 //句柄表

   +0x004 QuotaProcess     : 0x89833da0 _EPROCESS //所属进程的EPROCESS

   +0x008 UniqueProcessId : 0x00000430 //所属进程的pid

   +0x00c HandleTableLock : [4] _EX_PUSH_LOCK //句柄表锁,用于进程某些操作时锁住句柄表

   +0x01c HandleTableList : _LIST_ENTRY [ 0xe216735c - 0xe2394fd4 ] //句柄表的双链,所有进程的句柄表链在一起,如果搞隐藏进程的话,这个地方是必须要照顾到的

   +0x024 HandleContentionEvent : _EX_PUSH_LOCK

   +0x028 DebugInfo        : (null)

   +0x02c ExtraInfoPages   : 0

   +0x030 FirstFree        : 0x114c

   +0x034 LastFree         : 0

   +0x038 NextHandleNeedingPool : 0x1800 //当前句柄表池的上界

   +0x03c HandleCount      : 1152 //当前进程中句柄总数

   +0x040 Flags            : 0

   +0x040 StrictFIFO       : 0y0

这里面最重要的,当然就是TableCode了。

TableCode的低两位被用作标志位,用于表示当前句柄表的级数,0,1,2分别表示一级表,二级表,三级表。

比如这里的0xe3202001 ,掩去高位,低两位的值为1,即为二级表。对于二级表,表中存放的是指向一级表的指针,一级表又被称为基本表,这实际上一个HANDLE_TABLE_ENTRY数组,它里面存放的HANDLE_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;

一级表可以放多大的句柄呢?按WRK中的宏展开,有

(TABLE_PAGE_SIZE / sizeof(HANDLE_TABLE_ENTRY))*HANDLE_VALUE_INC

即0x1000/8*4=0x800

计算过程:一级表(基本表)的大小为一页即4K=0x1000 Byte,而HANDLE_TABLE_ENRY大小为8字节,则可存放的个数为0x1000/8=0x200=512,而句柄以4为步进,因此最大句柄为0x200*4=0x800.其中可存放的最大句柄不超过0x800(最大为0x800-4),而每个一级表的第一个HANDLE_TABLE_ENTRY的Object总是为0,因为我们都知道0是一个无效的句柄,它不指向一个有效的对象。因此,每个一个级实际存放的句柄数为511个。我们当前查看的句柄表中共有句柄1152个,显然一个表是不够的,1152=511*2+130,这里显然需要三个表,且第三个表未放满。而TableCode值为0xe3202001,其低两位也说明了这是个二级表。掩去低两位之后才是二级表的真正地址。来查看一下:

lkd> dd 0xe3202000

e3202000 e2a7b000 e3203000 e2c1e000 00000000

e3202010 00000000 00000000 00000000 00000000

e3202020 00000000 00000000 00000000 00000000

e3202030 00000000 00000000 00000000 00000000

e3202040 00000000 00000000 00000000 00000000

没错的,二级表中放了三个一级表指针。再来查看一下第一个一级表的内容。

lkd> dd e2a7b000

e2a7b000 00000000 fffffffe e100b4e9 000f0003

e2a7b010 e1a9ef41 00000003 898343b3 00100020

e2a7b020 8982ece9 021f0003 e1a794e9 000f000f

e2a7b030 e23a61b9 021f0001 e2390a59 020f003f

明显看到,第一个HANDLE_TABLE_ENTRY的Object为0.从第二个起才是有效的对象(如果某对象所对应的句柄被关闭,那么该对象就会从相应的句柄表中删除,所以并非句柄表中的每个位置存放的都是有效对象)。

接下来看HANDLE_TABLE_ENTRY,e100b4e9 000f0003这一对数据就是HANDLE_TABLE_ENTRY结构中的Object和GrantedAccess了。但是这里的Object并不是直接指向对象的。因为对象体总是8字节对齐,所以地址的低三位总是0,因此被用于标志位,表明该对象所对应的句柄的一些属性,比如可继承等等。因此,要对这里得到的Object掩去低三位,才会得到一个指向对象头OBJECT_HEADER的指针。

e100b4e9这里掩去之后为e100b4e8

lkd> dt _OBJECT_HEADER e100b4e8

nt!_OBJECT_HEADER

   +0x000 PointerCount     : 28

   +0x004 HandleCount      : 27

   +0x004 NextToFree       : 0x0000001b

   +0x008 Type             : 0x8a928e70 _OBJECT_TYPE

   +0x00c NameInfoOffset   : 0x20 ' '

   +0x00d HandleInfoOffset : 0 ''

   +0x00e QuotaInfoOffset : 0 ''

   +0x00f Flags            : 0x36 '6'

   +0x010 ObjectCreateInfo : 0x00000001 _OBJECT_CREATE_INFORMATION

   +0x010 QuotaBlockCharged : 0x00000001

   +0x014 SecurityDescriptor : 0xe100b44d

   +0x018 Body             : _QUAD

继续来看一下这个OBJECT_TYPE

lkd> dt _OBJECT_TYPE 0x8a928e70

nt!_OBJECT_TYPE

   +0x000 Mutex            : _ERESOURCE

   +0x038 TypeList         : _LIST_ENTRY [ 0xe100b4d8 - 0xe100b4d8 ]

   +0x040 Name             : _UNICODE_STRING "KeyedEvent"

   +0x048 DefaultObject    : 0x80568b40

   +0x04c Index            : 0x10

   +0x050 TotalNumberOfObjects : 1

   +0x054 TotalNumberOfHandles : 0x1b

   +0x058 HighWaterNumberOfObjects : 1

   +0x05c HighWaterNumberOfHandles : 0x1b

   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER

   +0x0ac Key              : 0x6579654b

   +0x0b0 ObjectLocks      : [4] _ERESOURCE

是个KeyedEvent对象。对象头再加上它本身的大小0x18之后就到了对象体OJBECT_BODY了,这个因不同对象而异。直接来看一下:

lkd> !object e100b4e8+0x18

Object: e100b500 Type: (8a928e70) KeyedEvent

    ObjectHeader: e100b4e8 (old version)

    HandleCount: 27 PointerCount: 28

    Directory Object: e10000a8 Name: CritSecOutOfMemoryEvent

跟前面看到的对比一下,完全是一致的。这样就了解了如何从进程的句柄表中得到实际的对象。

下面以该进程中的一个句柄0x1078为例说明句柄如何索引到对象。

0x1078/0x800=2

0x1078%0x800=0x78

前面已经知道,该进程的句柄表为二级表,而上面的计算结果表明,该句柄大小已经超出了两个句柄表的范围,在第三个句柄表中,且偏移为0x78*2.(因为句柄按4递增,而HANDLE_TABLE_ENTRY结构大小为8,所以正确公式应该为0x78除以4得到索引值,再乘以8得到以字节计算的偏移值,结果即0x78*2)

而第三张表基址为e2c1e000 。来看一下:

lkd> dd e2c1e000+0x78*2

e2c1e0f0 896b7019 001f03ff 8984e659 00100003

e2c1e100 8984e611 00100003 e2d183d9 00020019

e2c1e110 898003f1 001f0003 896c0701 0012019f

e2c1e120 8984e5c9 00100003 89834691 00100003

可以得到Object为896b7019,掩去低两位,则对象头指针为896b7018,对象体指针为896b7018+0x18

lkd> !object 896b7018+0x18

Object: 896b7030 Type: (8a92c040) Thread

    ObjectHeader: 896b7018 (old version)

    HandleCount: 3 PointerCount: 5

结果显示这是一个线程对象,与Process Explorer中显示的结果完全一致。以上过程很容易转化为程序实现并且不依赖任何系统函数,具体实现过程可参考WRK中的ExpLookupHandleTableEntry 函数,其原型为

PHANDLE_TABLE_ENTRY

ExpLookupHandleTableEntry (

    IN PHANDLE_TABLE HandleTable,

    IN EXHANDLE tHandle

    );

 

参考连接:

http://wangjiajun53880.blog.163.com/blog/static/117001394200941015444951/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Windbg 是一种在 Windows 操作系统下进行调试的工具,可以帮助开发人员识别并解决程序中的错误。由于 Windows 使用符号(Symbol tables)来存储可执行文件和模块文件中的符号名称和地址信息,所以为了更准确地调试程序,我们需要下载符号Windbg 自带了一个命令行程序名为 SymChk,它可以搜索符号服务器以获取可执行文件和程序库的符号。下面是下载符号的步骤: 1. 打开 Windbg。 2. 在 Windbg 中打开需要调试的程序。此时,Windbg 可能会提示无法加载符号文件。 3. 在 Windbg 的命令行界面中,输入以下命令: .sympath SRV*c:\symbols*http://msdl.microsoft.com/download/symbols 其中,c:\symbols 是本地符号目录,通常情况下是新建一个空文件夹作为符号目录;http://msdl.microsoft.com/download/symbols 是微软的符号服务器地址。 4. 输入以下命令以开始下载符号: .symfix c:\symbols 这个命令是告诉 Windbg 将符号文件存储到指定的符号目录。 5. 最后,在 Windbg 的命令行中输入命令: .reload /f 这个命令告诉 Windbg 重新加载程序和所有的符号文件。 完成上述步骤之后,Windbg 就可以正确地显示程序的符号信息了。需要注意的是,如果程序使用的是自定义的符号文件而不是 Microsoft 的符号文件,则需要将符号文件添加到符号路径中。 ### 回答2: Windbg是Microsoft Windows操作系统上一种强大的调试工具,能够帮助开发者/程序员追踪和解决程序在运行时出现的各种问题。在进行Windbg调试的过程中,符号显得尤为重要。符号是一种包含源代码、二进制代码和调试信息的文件,它可以帮助调试器将二进制代码映射到源代码的行数和函数名上。 在Windbg中下载符号有以下几个步骤: 第一步:打开Windbg,按"F12"键打开"命令"窗口。 第二步:在"命令"窗口中输入下列命令之一,以下载目标文件的符号: - .symfix c:\symbols:将符号下载到c盘上的symbols文件夹中。 - .sympath+ C:\path_to_your_symbols:在已有的符号路径中添加一个路径。 - .symproxy (proxy server):(port):在符号路径前面添加代理。 第三步:输入"!sym noisy"命令可以打开符号下载的详细输出,并且确认符号下载正在进行。 第四步:下载完成后,在Windbg的左部窗口"Modules"中选择想要观察的模块,右键点击该模块,在弹出的菜单中选择"Symbol Load Information",即可查看符号加载的情况。 通过以上步骤,我们可以轻松下载到符号,并且在调试程序时得到更准确和详细的调试信息,从而更好地定位和解决程序的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值