【原创】OBJECT_METHOD初窥
phpskycn2013/08/01软件综合 IP:浙江
OBJECT_METHOD初窥
一、    背景:Windows NT 的对象机制

Windows NT系统将各种资源以对象的方式进行组织和管理。虽然Windows NT内核使用C语言和汇编语言编写的,本身并未使用到C++中的面向对象机制。但依然通过抽象化的对象概念来对各类资源进行管理。

    对象分为对象头和对象体两部分,对象头又分为标准的对象头和可选头部,后者这里不介绍。标准头部的定义如下:
typedef struct _OBJECT_HEADER {
    LONG_PTR PointerCount;
    union {
        LONG_PTR HandleCount;
        PVOID NextToFree;
    };
    POBJECT_TYPE Type;
    UCHAR NameInfoOffset;
    UCHAR HandleInfoOffset;
    UCHAR QuotaInfoOffset;
    UCHAR Flags;

    union {
        POBJECT_CREATE_INFORMATION ObjectCreateInfo;
        PVOID QuotaBlockCharged;
    };

    PSECURITY_DESCRIPTOR SecurityDescriptor;
    QUAD Body;
} OBJECT_HEADER, *POBJECT_HEADE可以看到,标准的对象头的前面部分长度是固定的,长度正好为0x18,紧随其后就是对象体,并且对象体长度不定。
    在_OBJECT_HEADER 结构中,POBJECT_TYPE Type是对象类型的指针。Windows已经预先定义好了基本的对象类型。例如ProcessType。(进程对象),其对象体就是EPROCESS结构体(包含KPROCESS)。内核在PsCreatProcess()中建立新进程时会同时建立一个新的对象。
    下面看一下对象类型相关的OBJECT_TYPE结构体:
typedef struct _OBJECT_TYPE {
    ERESOURCE Mutex;
    LIST_ENTRY TypeList;
    UNICODE_STRING Name;            // Copy from object header for convenience
    PVOID DefaultObject;
    ULONG Index;
    ULONG TotalNumberOfObjects;
    ULONG TotalNumberOfHandles;
    ULONG HighWaterNumberOfObjects;
    ULONG HighWaterNumberOfHandles;
    OBJECT_TYPE_INITIALIZER TypeInfo;
#ifdef POOL_TAGGING
    ULONG Key;
#endif //POOL_TAGGING
    ERESOURCE ObjectLocks[ OBJECT_LOCK_COUNT ];
} OBJECT_TYPE, *POBJECT_TYPE;其中包含的一个特殊结构体OBJECT_TYPE_INITIALIZER:
typedef struct _OBJECT_TYPE_INITIALIZER {
    USHORT Length;
    BOOLEAN UseDefaultObject;
    BOOLEAN CaseInsensitive;
    ULONG InvalidAttributes;
    GENERIC_MAPPING GenericMapping;
    ULONG ValidAccessMask;
    BOOLEAN SecurityRequired;
    BOOLEAN MaintainHandleCount;
    BOOLEAN MaintainTypeList;
    POOL_TYPE PoolType;
    ULONG DefaultPagedPoolCharge;
    ULONG DefaultNonPagedPoolCharge;
    OB_DUMP_METHOD DumpProcedure;
    OB_OPEN_METHOD OpenProcedure;
    OB_CLOSE_METHOD CloseProcedure;
    OB_DELETE_METHOD DeleteProcedure;
    OB_PARSE_METHOD ParseProcedure;
    OB_SECURITY_METHOD SecurityProcedure;
    OB_QUERYNAME_METHOD QueryNameProcedure;
    OB_OKAYTOCLOSE_METHOD OkayToCloseProcedure;
} OBJECT_TYPE_INITIALIZER, *POBJECT_TYPE_INITIALIZER;
可以看到,其中包含了许多OB_XXXX_METHOD类型的成员,它们是一些函数指针。潘爱民老师在《Windows内核原理与实现》一书中说这里定义的是一些对象操作的方法基本方法。莫非打开进程时使用的就是OB_OPEN_METHOD OpenProcedure指定的方法(例程)么?下面就拿Explorer.exe验证一下。
二、实战
先在WinDBG中通过!procress 0 0命令得到Explorer.exe的PEPROCESS:0x8ad84720,这也是该进程的对象体的开始。那么减掉紧挨着的对象头的大小0x18,就可以得到对象头的起始地址:0x8ad84708。之后执行dt_OBJECT_HEADER 0x8ad84708命令观察explorer.exe进程的对象头:
0: kd> dt_OBJECT_HEADER 0x8ad84708
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n108
   +0x004 HandleCount      : 0n4
   +0x004 NextToFree       : 0x00000004 Void
   +0x008 Type             : 0x8af7cca0 _OBJECT_TYPE
   +0x00c NameInfoOffset   : 0 ''
   +0x00d HandleInfoOffset : 0 ''
   +0x00e QuotaInfoOffset  : 0 ''
   +0x00f Flags            : 0x20 ' '
   +0x010 ObjectCreateInfo : 0x8a5a7008 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x8a5a7008 Void
   +0x014 SecurityDescriptor : 0xe16cd8c5 Void
   +0x018 Body             : _QUAD直接得到ProcessType的指针:0x8af7cca0,然后观察:
0: kd> dt_OBJECT_TYPE 0x8af7cca0
nt!_OBJECT_TYPE
   +0x000 Mutex            : _ERESOURCE
   +0x038 TypeList         : _LIST_ENTRY [ 0x8af7ccd8 - 0x8af7ccd8 ]
   +0x040 Name             : _UNICODE_STRING "[s:9]rocess"
   +0x048 DefaultObject    : (null)
   +0x04c Index            : 5
   +0x050 TotalNumberOfObjects : 0x1c
   +0x054 TotalNumberOfHandles : 0x7d
   +0x058 HighWaterNumberOfObjects : 0x20
   +0x05c HighWaterNumberOfHandles : 0x82
   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0ac Key              : 0x636f7250
   +0x0b0 ObjectLocks      : [4] _ERESOURCE展开TypeInfo:
0: kd> dt _OBJECT_TYPE_INITIALIZER 0x8af7cd00
nt!_OBJECT_TYPE_INITIALIZER
   +0x000 Length           : 0x4c
   +0x002 UseDefaultObject : 0 ''
   +0x003 CaseInsensitive  : 0 ''
   +0x004 InvalidAttributes : 0xb0
   +0x008 GenericMapping   : _GENERIC_MAPPING
   +0x018 ValidAccessMask  : 0x1f0fff
   +0x01c SecurityRequired : 0x1 ''
   +0x01d MaintainHandleCount : 0 ''
   +0x01e MaintainTypeList : 0 ''
   +0x020 PoolType         : 0 ( NonPagedPool )
   +0x024 DefaultPagedPoolCharge : 0x1000
   +0x028 DefaultNonPagedPoolCharge : 0x2a8
   +0x02c DumpProcedure    : (null)
   +0x030 OpenProcedure    : (null)
   +0x034 CloseProcedure   : (null)
   +0x038 DeleteProcedure  : 0x80932460     void  nt!PspProcessDelete+0
   +0x03c ParseProcedure   : (null)
   +0x040 SecurityProcedure : 0x8091ce64     long  nt!SeDefaultObjectMethod+0
   +0x044 QueryNameProcedure : (null)
   +0x048 OkayToCloseProcedure : (null)

显然,这一堆的Procedure中,只有DeleteProcedure和SecurityProcedure为非零值,显然系统不会在内核去call 0x00000000。难道系统对于这些Procedure还有特殊的处理方式?
到这里,似乎得去翻WRK源码查找这些成员的所有引用了。但这似乎工作量不小,在这我们采取比较偷懒的方式:下断点。这里只选取了两个:OpenProcedure和SecurityProcedure。它们一个为NULL一个为非NULL指,并且从命名上来看一个是在打开对象时执行的,一个是在安全检查时执行的。
0: kd> ba r4 0x8af7cd30
0: kd> ba r4 0x8af7cd40

3: kd> bl
0 e 8af7cd30 r 4 0001 (0001)
1 e 8af7cd40 r 4 0001 (0001)似乎系统在登录后会调用NtOpenProcess()遍历一遍进程,随后我们静静地等待断点命中……
很快:
Breakpoint 1 hit
nt!ObGetObjectSecurity+0x22:
80924f0a 7518            jne     nt!ObGetObjectSecurity+0x3c (80924f24)对应的源码:
if (ObpCentralizedSecurity(ObjectType))  {

        *SecurityDescriptor = ObpReferenceSecurityDescriptor( ObjectHeader );

        *MemoryAllocated = FALSE;

        return( STATUS_SUCCESS );
    }其中ObpCentralizedSecurity(ObjectType)是个宏:
#define ObpCentralizedSecurity(_ObjectType)                              \
    ((_ObjectType)->XXXXXXXXXXXcurityProcedure == SeDefaultObjectMethod)

//
//  Declare a global table of object types.
//看起来没啥意思,只是做了一下检查就返回成功了。
本来想直接GO掉继续等待,但是接着往下看代码会发现:

    //
    //  The security method will return an absolute format
    //  security descriptor that just happens to be in a self
    //  contained buffer (not to be confused with a self-relative
    //  security descriptor).
    //

    ObpBeginTypeSpecificCallOut( SaveIrql );

    Status = (*ObjectType->XXXXXXXXXXXcurityProcedure)( Object,
                                                        QuerySecurityDescriptor,
                                                        &SecurityInformation,
                                                        *SecurityDescriptor,
                                                        &Length,
                                                        &ObjectHeader->SecurityDescriptor,
                                                        ObjectType->TypeInfo.PoolType,
                                                        &ObjectType->XXXXXXXXXXXnericMapping );

    ObpEndTypeSpecificCallOut( SaveIrql, "Security", ObjectType, Object );可以看到,在ObjectType->XXXXXXXXXXXcurityProcedure!= SeDefaultObjectMethod时,内核最终会去调用SecurityProcedure所指定的例程。同时也可以看到,内核对SecurityProcedure指定的地址未作任何检查,所以假如我们要修改它的话,必须保证其有效性。
    那么这个NTSTATUS ObGetObjectSecurity ()会在什么时候被调用呢?莫非翻翻NtDesignedBook看看么?还是查看一下调用层次吧:
ObCheckCreateObjectAccess();
    - ObpLookupObjectName();
        - ObInsertObject();
        - ObOpenObjectByName();
        -ObReferenceObjectByName();
ObCheckObjectAccess();
ObInsertObject();
ObpCheckObjectReference();
ObpCheckTraverseAccess();
PsCreateProcess()
PsCreateThread();
PspSetPrimaryToken();其中仅对ObCheckCreateObjectAccess()深入追踪,在扒开几层外壳之后我们可以看到一个熟悉的函数:ObOpenObjectByName()。调用它的函数实在太多,这里就不列了,NtOpenProcess、NtOpenThread均在其中。此处可谓牵一发而动全身。
    继续追踪,GO之后断点0命中:
Breakpoint 0 hit
nt!ObpIncrementHandleCount+0x2b9:
80921d3f 85c0            test    eax,eax对应的代码:
if (ObjectType->TypeInfo.OpenProcedure != NULL) {

#if DBG
            KIRQL SaveIrql;
#endif

            //
            //  Leave the object type mutex when call the OpenProcedure. If an exception
            //  while OpenProcedure the HoldObjectTypeMutex disable leaving the mutex
            //  on finally block
            //

            ObpBeginTypeSpecificCallOut( SaveIrql );

            Status = (*ObjectType->TypeInfo.OpenProcedure)( OpenReason,
                                                            Process,
                                                            Object,
                                                            AccessState ?
                                                                AccessState->[s:9]reviouslyGrantedAccess :
                                                                0,
                                                            ProcessHandleCount );      
ObpEndTypeSpecificCallOut( SaveIrql, "Open", ObjectType, Object );
      这里很简洁明了,检查一下ObjectType->TypeInfo.OpenProcedure不是NULL就直接去调用OpenProcedure了(这样还是不够严格,好歹检查一下是否在内核地址范围内吧)。ObpIncrementHandleCount()看上去是增加句柄计数的,还是查看下调用层次看看最终谁会用到它:
ObDupHandleProcedure();
ObDuplicateObject();
ObpCreateHandle();
    - ObInsertObject();
    - ObOpenObjectByName();
    -ObOpenObjectByPointer();这里也仅列出对ObpCreateHandle()深入追踪的结果,可以看到两个熟悉的函数:ObOpenObjectByName()、ObOpenObjectByPointer(),其意义和前面的ObCheckCreateObjectAccess()完全是一个水平,这里就不再重复了。

三、总结
小小的OBJECT_METHOD暗藏玄机,通过分析我们可以发现:这些OBJECT_METHOD虽然卡上去是“可选”的,甚至某些情况下默认值为NULL,,但这并不意味着它们不重要:一旦它们被设置为特殊的函数指针,其被调用的机会相当高,可以用来HOOK的事情也非常多。本文只是初窥,至于具体的应用,有兴趣的童鞋可以找出所有可能调用的地方,然后加以利用。
+200  科创币    彼岸江山    2013/08/01 高质量发帖
+50  科创币    smrken    2013/08/02
+1  学术分    warmonkey    2013/08/01 高质量发帖
来自:计算机科学 / 软件综合
4
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
phpskycn 作者
10年10个月前 IP:未同步
554231
Windows内核调试环境
放一张Windows内核调试环境,源码+调试符号+调试器,配上VS还是相当爽的
work.png
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
_________
10年10个月前 IP:未同步
554233
不错,至少Linux内核不能在本机上调试。
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论
phpskycn作者
10年10个月前 IP:未同步
554234
回 2楼(_________) 的帖子
被调试系统运行在虚拟机里面……
本地调试器也有,但是俺不会用 [s:274]
引用
评论
加载评论中,请稍候...
200字以内,仅用于支线交流,主线讨论请采用回复功能。
折叠评论

想参与大家的讨论?现在就 登录 或者 注册

所属专业
上级专业
同级专业
phpskycn
专家 老干部 学者 机友 笔友
文章
402
回复
4591
学术分
8
2009/03/15注册,3天15时前活动

CV

主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}