首页
社区
课程
招聘
[原创]- 关于ObReferenceObjectByHandle中对句柄的处理
发表于: 2013-11-18 15:01 10143

[原创]- 关于ObReferenceObjectByHandle中对句柄的处理

2013-11-18 15:01
10143

关于ObReferenceObjectByHandle中对句柄的处理

一、  摘要
在开发一个 Anti-Rootkit工具中的枚举进程句柄时,发现枚举System Process进程的句柄信息在Windows XP和Windows 7/8的处理细节有一些不一样,遂想刨根问底,一探究竟。在获取句柄对象名和类型名的时候都会使用NtQueryObject函数,而NtQueryObject函数中是通过ObReferenceObjectByHandle根据Handle获得Object的。此时内核句柄的所有秘密都在ObReferenceObjectByHandle中。

二、  相关补充
2.1 ETHREAD或KTHREAD中的EPROCESS域
  ETHREAD中的域ThreadsProcess(PEPROCESS)在Windows XP中有,Windows 7/8中没有。指向当前线程所属的进程,这是在线程初始创建时赋值的。通过此域,可以很方便地从一个线程访问到它所属的进程。WRK中base\ntos\inc\Ps.h中的宏THREAD_TO_PROCESS据此实现:
#define THREAD_TO_PROCESS(Thread) ((Thread)->ThreadsProcess)
  KTHREAD中Process(PKPROCESS)在Windows 7/8中有,Windows XP中没有,但本人也没有找到该域的作用。如有知情人,请告知。但我猜测作用同上。
注:以上两个域在WRK-1.2中全都存在。
  KTHREAD中的ApcState(KAPC_STATE)中的Process(KPROCESS)也可以EPROCESS。PsGetCurrentProcess函数就是据此实现:
#define _PsGetCurrentProcess() \
    (CONTAINING_RECORD(\
    ((KeGetCurrentThread())->ApcState.Process),\
    EPROCESS,Pcb))
  这个域会根据线程所属的进程动态变化。当当前线程Attach到其他进程中时(KeAttachProcess/KeStackAttachProcess),原线程中的ApcState保存到SavedApcState中,ApcState保存新进程环境中的信息,ApcState.Process就指向新进程的EPROCESS,所以这里获取的才是真正的当前EPROCESS。Detach到原先进程中时(KeDetachProcess/KeUnstackDetachProcess),该过程与前一步相反。这里可参考WRK中相关API的实现。

三、  ObReferencObjectByHandle在不同版本Windows实现差异
3.1 Windows XP x86
虽然WRK是从Windows XP AMD64和Windows Server 2003 SP1抓取出来的,但ObReferenceObjectByHandle的处理结果和32位XP是以一样,所以这里以WRK中的源码为例,这样更为清晰。
//
// 摘自:wrk-1.2\base\ntos\ob\Obref.c
// 
NTSTATUS
ObReferenceObjectByHandle (
    __in HANDLE Handle,
    __in ACCESS_MASK DesiredAccess,
    __in_opt POBJECT_TYPE ObjectType,
    __in KPROCESSOR_MODE AccessMode,
    __out PVOID *Object,
    __out_opt POBJECT_HANDLE_INFORMATION HandleInformation
    )
{
    ACCESS_MASK GrantedAccess;
    PHANDLE_TABLE HandleTable;
    POBJECT_HEADER ObjectHeader;
    PHANDLE_TABLE_ENTRY ObjectTableEntry;
    PEPROCESS Process;
    NTSTATUS Status;
    PETHREAD Thread;

    ObpValidateIrql("ObReferenceObjectByHandle");

    Thread = PsGetCurrentThread ();
    *Object = NULL;

    //
    // 检查Handle是不是内核句柄
    // (即小于0,也就是最高位带KERNEL_HANDLE_MASK标识)。
    // 这里有两个句柄要区别处理:
    // 当前进程句柄-1(0xFFFFFFFF)和当前线程句柄-2(0xFFFFFFFE)。
    //
    if ((LONG)(ULONG_PTR) Handle < 0) {
        if (Handle == NtCurrentProcess()) {
            if ((ObjectType == PsProcessType)
        || (ObjectType == NULL)) {
                Process = PsGetCurrentProcessByThread(Thread);
                GrantedAccess = Process->GrantedAccess;
                if ((SeComputeDeniedAccesses(
          GrantedAccess, DesiredAccess) == 0) ||
                    (AccessMode == KernelMode)) {
                    ObjectHeader = OBJECT_TO_OBJECT_HEADER(Process);

                    if (ARGUMENT_PRESENT(HandleInformation)) {
                        HandleInformation->GrantedAccess
              = GrantedAccess;
                        HandleInformation->HandleAttributes = 0;
                    }
                    ObpIncrPointerCount(ObjectHeader);
                    *Object = Process;
                    ASSERT( *Object != NULL );
                    Status = STATUS_SUCCESS;
                } else {
                    Status = STATUS_ACCESS_DENIED;
                }
            } else {
                Status = STATUS_OBJECT_TYPE_MISMATCH;
            }

            return Status;
        } else if (Handle == NtCurrentThread()) {
            if ((ObjectType == PsThreadType)
        || (ObjectType == NULL)) {
                GrantedAccess = Thread->GrantedAccess;
                if ((SeComputeDeniedAccesses(
          GrantedAccess, DesiredAccess) == 0) ||
                    (AccessMode == KernelMode)) {
                    ObjectHeader = OBJECT_TO_OBJECT_HEADER(Thread);
                    if (ARGUMENT_PRESENT(HandleInformation)) {
                        HandleInformation->GrantedAccess = GrantedAccess;
                        HandleInformation->HandleAttributes = 0;
                    }
                    ObpIncrPointerCount(ObjectHeader);
                    *Object = Thread;
                    ASSERT( *Object != NULL );
                    Status = STATUS_SUCCESS;
                } else {
                    Status = STATUS_ACCESS_DENIED;
                }
            } else {
                Status = STATUS_OBJECT_TYPE_MISMATCH;
            }
            return Status;
        } else if (AccessMode == KernelMode) {
            //
            // 这里才是真正处理内核句柄的地方。
            // 注意:内核句柄只能在内核模式访问。
            // #define KERNEL_HANDLE_MASK\
// ((ULONG_PTR)((LONG)0x80000000))
      // #define EncodeKernelHandle(H) \
      //  (HANDLE)(KERNEL_HANDLE_MASK | (ULONG_PTR)(H))
      // #define DecodeKernelHandle(H) \
      //  (HANDLE)(KERNEL_HANDLE_MASK ^ (ULONG_PTR)(H))
      // 内核句柄是还KERNEL_HANDLE_MASK标识的(即最高位为1)。
      // 记住,这里的Handle不是一个真正的Handle,
// 在使用前必须转化成正规Handle。
      // 这里的DecodeKernelHandle就是将最高位的标识去除。
      // 
            Handle = DecodeKernelHandle( Handle );
            //
            //  这里得到的句柄表就是全局的内核句柄表ObpKernelHandleTable。
            //
            HandleTable = ObpKernelHandleTable;
        } else {
            //
            // The previous mode was user for this kernel handle value. 
            // Reject it here.
            //
            return STATUS_INVALID_HANDLE;
        }

    } else {
    //
    // 如果Handle大于0(即没有KERNEL_HANDLE_MASK标识),
    // 说明不是System进程的句柄,
    // 就从当前ETHREAD得到EPROCESS,从而得到HandleTable。
    //
    //#define PsGetCurrentProcessByThread(xCurrentThread) \
    //  (ASSERT((xCurrentThread) == PsGetCurrentThread ()),\
    //  CONTAINING_RECORD(\
//  ((xCurrentThread)->Tcb.ApcState.Process),\
    //  EPROCESS,Pcb))
    // 
        HandleTable = PsGetCurrentProcessByThread(Thread)->ObjectTable;
    }
    //
  // 这之后便是在得到的HandleTable里查找ObjectHeader和Object。
  //
  // 以下代码引用省略……
  //
    return Status;
}

从以上代码可以看出:在Windows XP 32位下无论从何种途径获取到的内核句柄(最高位有或没有KERNEL_HANDLE_MASK标识),在正确Attach到内核句柄和内核句柄表所在的System 进程,通过ObReferenceObjectByHandle都能得到正确的对象。正确带KERNEL_HANDLE_MASK标识的句柄当然没有问题,但没有该标识的句柄因为Attach到System进程以后,是通过ApcState.Process来获取HandleTable的,此时的当前进程就是System进程,所以也可以得到正确的对象。

3.2 Windows 7 32-Bit
 
此处实际调用_ObReferenceObjectByHandleWithTag。
 
代码比较多,将图放大即可看清。在新标签页中打开,然后放大图片即可看清。
注:

1.  图中以数字1, 2, 3……为序号的流程是输入正确的内核句柄(带KERNEL_HANDLE_TABLE)后查找HandleTable的处理逻辑。
2.  图中以大写字母A, B, C ……为序号的流程是输入普通句柄后查找HandleTable的处理逻辑。

在Windows 7 32位下,在Attach到System进程之后,只有在输入正确(带KERNEL_HANDLE_MASK标识)的句柄之后,调用ObReferenceObjectByHandle才会得到正确的对象。从IDA分析中可看出,当句柄不是正确的内核句柄时,如果得到的句柄表却是内核句柄表(ApcState.Process),则返回0xC0000008(STATUS_INVALID_HANDLE),这里对Handle做了正确性验证,更为安全。

3.3 NOTE
一、逆向ObpCreateHandle可知:内核中创建或打开句柄时,如果是内核句柄,则获取得到的句柄都是带KERNEL_HANDLE_TABLE标识的,但内核句柄在内核句柄表里是没有该标识的。也就是该标识只是对调用者用于区别,在内部实现和保存时和其它句柄没有区别。
二、Windows 7/8 64位下KERNEL_HANDLE_MASK为0xFFFFFFFF80000000。
在Windows 7 64位和Windows 8 32/64中ObReferenceObjectByHandle查找HandleTable的处理过程和Windows 7 32位是一样的。

四、  参考
1.  毛德操. Windows内核情景分析
2.  潘爱民. Windows内核原理与实现
3.  WRK-1.2

原文WORD: Dissecting the Windows Kernel - 关于ObReferenceObjectByHandle中对句柄的处理.zip
可将图另存为,再放大看。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

上传的附件:
收藏
免费 5
支持
分享
最新回复 (2)
雪    币: 124
活跃值: (469)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
沙发抢到了一个
2013-11-18 20:42
0
雪    币: 310
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
板蹬啊板蹬,很好,讲的很透彻,学习啦
2013-11-21 18:57
0
游客
登录 | 注册 方可回帖
返回
//