Dissecting the Windows Kernel - 关于ObReferenceObjectByHandle中对句柄的处理

Dissecting the Windows Kernel

-   关于ObReferenceObjectByHandle中对句柄的处理

一、   摘要

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

二、   相关补充

2.1 ETHREAD或KTHREAD中的EPROCESS域

l  ETHREAD中的域ThreadsProcess(PEPROCESS)在Windows XP中有,Windows 7/8中没有。指向当前线程所属的进程,这是在线程初始创建时赋值的。通过此域,可以很方便地从一个线程访问到它所属的进程。WRK中base\ntos\inc\Ps.h中的宏THREAD_TO_PROCESS据此实现:

#define THREAD_TO_PROCESS(Thread) ((Thread)->ThreadsProcess)

l  KTHREAD中Process(PKPROCESS)在Windows 7/8中有,Windows XP中没有,但本人也没有找到该域的作用。如有知情人,请告知。但我猜测作用同上。

注:以上两个域在WRK-1.2中全都存在。

l  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 (

    __inHANDLE Handle,

    __inACCESS_MASK DesiredAccess,

    __in_optPOBJECT_TYPE ObjectType,

    __inKPROCESSOR_MODE AccessMode,

    __outPVOID *Object,

    __out_optPOBJECT_HANDLE_INFORMATION HandleInformation

    )

{

    ACCESS_MASKGrantedAccess;

    PHANDLE_TABLE HandleTable;

    POBJECT_HEADER ObjectHeader;

    PHANDLE_TABLE_ENTRY ObjectTableEntry;

    PEPROCESSProcess;

    NTSTATUSStatus;

    PETHREADThread;

 

    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;

            }

 

            returnStatus;

        } elseif (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;

            }

            returnStatus;

        } elseif (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.

            //

            returnSTATUS_INVALID_HANDLE;

        }

 

    } else{

       //

       // 如果Handle大于0(即没有KERNEL_HANDLE_MASK标识),

       // 说明不是System进程的句柄,

       // 就从当前ETHREAD得到EPROCESS,从而得到HandleTable

       //

       //#definePsGetCurrentProcessByThread(xCurrentThread) \

       //  (ASSERT((xCurrentThread) == PsGetCurrentThread()),\

       //  CONTAINING_RECORD(\

//  ((xCurrentThread)->Tcb.ApcState.Process),\

       //  EPROCESS,Pcb))

       //

        HandleTable= PsGetCurrentProcessByThread(Thread)->ObjectTable;

    }

    //

    // 这之后便是在得到的HandleTable里查找ObjectHeaderObject

    //

    // 以下代码引用省略……

    //

    returnStatus;

}

 

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

 

3.2 Windows 7 32-Bit

Dissecting the Windows Kernel - 关于ObReferenceObjectByHandle中对句柄的处理_第1张图片

此处实际调用_ObReferenceObjectByHandleWithTag。

Dissecting the Windows Kernel - 关于ObReferenceObjectByHandle中对句柄的处理_第2张图片

代码比较多,将图放大即可看清。

注:

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

2.图中以大写字母A, B, C ……为序号的流程是输入普通句柄后查找HandleTable的处理逻辑。

 

在Windows7 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

你可能感兴趣的:(Dissecting the Windows Kernel - 关于ObReferenceObjectByHandle中对句柄的处理)