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里查找ObjectHeader和Object。
//
// 以下代码引用省略……
//
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
此处实际调用_ObReferenceObjectByHandleWithTag。
代码比较多,将图放大即可看清。
注:
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