几个常用内核函数(《Windows内核情景分析》)
参考:《Windows内核情景分析》
0x01 ObReferenceObjectByHandle
这个函数从句柄得到对应的内核对象,并递增其引用计数。
NTSTATUS ObReferenceObjectByHandle( IN HANDLE Handle, IN ACCESS_MASK DesiredAccess, IN POBJECT_TYPE ObjectType,IN KPROCESSOR_MODE AccessMode, OUT PVOID* Object, OUT POBJECT_HANDLE_INFORMATION HandleInformation) { *Object = NULL; //若句柄是一个内核句柄或当前进程、线程的句柄 if (HandleToLong(Handle) < 0) { if (Handle == NtCurrentProcess())//若句柄值是当前进程的句柄(-1),特殊处理 { if ((ObjectType == PsProcessType) || !(ObjectType)) { CurrentProcess = PsGetCurrentProcess(); GrantedAccess = CurrentProcess->GrantedAccess; //if内核模式/要求的权限<=进程对象支持的权限(权限检查) if ((AccessMode == KernelMode) ||!(~GrantedAccess & DesiredAccess)) { if (HandleInformation) { HandleInformation->HandleAttributes = 0; HandleInformation->GrantedAccess = GrantedAccess; } ObjectHeader = OBJECT_TO_OBJECT_HEADER(CurrentProcess); InterlockedExchangeAdd(&ObjectHeader->PointerCount, 1);//递增引用计数 *Object = CurrentProcess;//返回得到的对象指针 Status = STATUS_SUCCESS; } Else //权限检查不通过 Status = STATUS_ACCESS_DENIED; } else Status = STATUS_OBJECT_TYPE_MISMATCH; return Status; } else if (Handle == NtCurrentThread())//若句柄值是当前线程的句柄(-2),特殊处理 { if ((ObjectType == PsThreadType) || !(ObjectType)) { CurrentThread = PsGetCurrentThread(); GrantedAccess = CurrentThread->GrantedAccess; if ((AccessMode == KernelMode) ||!(~GrantedAccess & DesiredAccess)) { if (HandleInformation) { HandleInformation->HandleAttributes = 0; HandleInformation->GrantedAccess = GrantedAccess; } ObjectHeader = OBJECT_TO_OBJECT_HEADER(CurrentThread); InterlockedExchangeAdd(&ObjectHeader->PointerCount, 1); *Object = CurrentThread; Status = STATUS_SUCCESS; } else Status = STATUS_ACCESS_DENIED; } else Status = STATUS_OBJECT_TYPE_MISMATCH; return Status; } else if (AccessMode == KernelMode)//若句柄是一个内核句柄 { Handle = ObKernelHandleToHandle(Handle);//去掉最高位的1,转为普通句柄 HandleTable = ObpKernelHandleTable;//采用内核句柄表 } } Else //最典型的情况,普通句柄,就使用当前进程的句柄表 HandleTable = PsGetCurrentProcess()->ObjectTable; //以该句柄的值为“索引”,找到句柄表中对应的句柄表项 HandleEntry = ExMapHandleToPointer(HandleTable, Handle) if (HandleEntry)//如果找到了,这就是一个有效句柄 { ObjectHeader = ObpGetHandleObject(HandleEntry);//关键。获得该句柄指向的对应对象 if (!(ObjectType) || (ObjectType == ObjectHeader->Type)) { GrantedAccess = HandleEntry->GrantedAccess; if ((AccessMode == KernelMode) ||!(~GrantedAccess & DesiredAccess))//通过权限检查 { InterlockedIncrement(&ObjectHeader->PointerCount); Attributes = HandleEntry->ObAttributes & OBJ_HANDLE_ATTRIBUTES; if (HandleInformation) { HandleInformation->HandleAttributes = Attributes; HandleInformation->GrantedAccess = GrantedAccess; } *Object = &ObjectHeader->Body;//返回的是对象体的地址 return STATUS_SUCCESS; } Else //权限检查没通过 Status = STATUS_ACCESS_DENIED; } else Status = STATUS_OBJECT_TYPE_MISMATCH; } Else //有可能用户给定的句柄值是一个无效句柄,在句柄表中找不到 Status = STATUS_INVALID_HANDLE; *Object = NULL; return Status; }
两个特殊情况:
#define NtCurrentProcess() (HANDLE)-1
#define NtCurrentThread() (HANDLE)-2
这是两个伪句柄值,永远获得的是当前进程、线程的内核对象。
另外:若句柄值的最高位是1,则是一个内核句柄,各进程通用。内核型句柄是“System”进程的句柄表中的句柄。因此,要获得内核句柄对应的对象,系统会挂靠到“System”进程的地址空间中,去查询句柄表。
根据句柄值在句柄表中找到对应的表项是靠ExMamHandleToPointer这个函数实现的,这个函数又在内部调用ExpLookupHandleTableEntry来真正查找。句柄表组织为一个稀疏数组(目的用来节省内存),但可以
简单的看做一个一维数组,不影响理解,句柄值本身也可简单理解为一个索引。
调用方传递给驱动程序的句柄不会经过 I/O 管理器,因此 I/O 管理器不对这类句柄执行任何验证检查。决不要假设一个句柄有效;始终确保句柄拥有正确的对象类型、对于所需任务的合适的访问权、正确的访问模式,并且访问模式与请求的访问兼容。
驱动程序应该谨慎使用句柄,特别是那些从用户模式应用程序接收到的句柄。
第一,这种句柄特定于进程上下文,因此它们仅在打开句柄的进程中有效。当从不同的进程上下文或工作线程使用时,句柄可以引用不同的对象或者只是变得无效。
第二,在驱动程序使用句柄期间,攻击者可以关闭和重新打开句柄来改变其引用的内容。
第三,攻击者可以传入这样一个句柄来引诱驱动程序执行对于应用程序非法的操作,例如调用 ZwXxx 函数。对于这些函数的内核模式调用方,访问检查被跳过,因此攻击者可以使用这种机制绕过验证。
驱动程序还应该确保用户模式应用程序不能误用驱动程序创建的句柄。为一个句柄设置 OBJ_KERNEL_HANDLE 属性使其成为内核句柄,内核句柄可以在任何进程上下文中使用,但是只能从内核模式进行访问(对于传递给ZwXxx 例程的句柄,这特别重要)。用户模式的进程不能访问、关闭或替换内核句柄。