探索Windows内核系列

来源:岁月联盟 编辑:猪蛋儿 时间:2022-08-03

概述

在本文中,我们会探索句柄在windows内核中表现形式,帮助了解句柄在windows中的作用。通过调试与逆向内核代码的方式,介绍句柄是如何关联到内核对象,并在此基础上介绍一种利用句柄来保护我们的进程不被读写的技术方案。

句柄是什么?

本节主要介绍句柄在R3的作用,如果你了解,可以跳过,不影响后面的阅读。

如果你经常使用windows的OpenProcess、CreateFile等API来操作进程或文件,那么你应该非常熟悉句柄的使用,这些函数的返回值类型都是HANDLE,即句柄。在《windows内核原理与实现》3.4.1节对句柄有如下描述:

当一个进程利用名称来创建或打开一个对象时,将获得一个句柄,该句柄指向所创建或打开的对象。以后,该进程无须使用名称来引用该对象,使用此句柄即可访问。这样做可以显著地提高引用对象的效率。句柄是一个在软件设计中被广泛使用的概念。例如,在C运行库中,文件操作使用句柄来表示,每当应用程序创建或打开一个文件时,只要此创建或打开操作成功,则C运行库返回一个句柄。以后应用程序对文件的读写操作都使用此句柄来标识该文件。而且,如果两个应用程序以共享方式打开了同一个文件,那么,它们将分别得到各自的句柄,且都可以通过句柄操作该文件。尽管两个应用程序得到的句柄的值并不相同,但是这两个句柄所指的文件却是同一个。因此,句柄只是一个对象引用,同一个对象在不同的环境下可能有不同的引用(句柄)值。

上文中的"对象"指的是内核对象,我们在R3中所使用的文件、进程、线程在内核中都有对应内核对象。应用层每次创建或打开进程、文件都会对相应的内核对象创建一个句柄。当多个进程同时打开一个文件时,该文件在内核中只会存在一个文件内核对象,但每个进程都有一个各自的文件句柄,每个句柄会增加内核对象的引用计数,只有当内核对象的引用计数为0时,内核对象才会释放。

方便在R3查看句柄信息的小工具

我们可以使用微软官方的工具Process Explorer来查看文件或进程被哪个进程使用着,它的官方文档在这里。

比如我使用CE打开了notepad.exe,使用Process Explorer查找句柄功能,搜索notepad.exe,即可找到对应占用它的进程。这在我们想删除一个文件却提示"该文件已被占用"时非常有用,可以直接搜索到占用它的进程。
image-20220721235948764.png

句柄在内核中的存在形式

本节将通过实时内核调试结合逆向查看内核源代码的方式,来探索内核中的句柄结构。句柄结构经历各个windows版本到现在,变化非常大,这里使用的系统是Win10%201809。

Windows系统的句柄表主要有三种,第一种是系统全局PspCidTable句柄表,保存所有进程和线程的句柄,第二种是进程内部句柄表,保存该进程所打开的内核对象句柄,最后一种是系统进程system的全局句柄表,这几种句柄表对应的格式都是一样的。我们主要介绍进程中的句柄表。

_EPROCESS是进程在内核中内核对象结构,每个进程在内核中都有一个这样对应的内核结构来记录进程的信息。EPOCESS结构中的ObjectTable即该进程所有的句柄表,ObjectTable中的TableCode指向handle_table_entrys的首地址。

dt%20_EPROCESSnt!_EPROCESS%20%20%20+0x000%20Pcb%20%20%20%20%20%20%20%20%20%20%20%20%20%20:%20_KPROCESS%20%20%20+0x2d8%20ProcessLock%20%20%20%20%20%20:%20_EX_PUSH_LOCK%20%20%20+0x2e0%20UniqueProcessId%20%20:%20Ptr64%20Void%20%20%20+0x2e8%20ActiveProcessLinks%20:%20_LIST_ENTRY%20%20%20+0x2f8%20RundownProtect%20%20%20:%20_EX_RUNDOWN_REF%20%20%20+0x300%20Flags2%20%20%20%20%20%20%20%20%20%20%20:%20Uint4B%20%20%20......%20%20%20+0x418%20ObjectTable%20:%20Ptr64%20_HANDLE_TABLE%20//我们要找的句柄表%20%20%20......%20%20%20+0x848%20MmHotPatchContext%20:%20Ptr64%20Void0:%20kd>%20dt%20_HANDLE_TABLEnt!_HANDLE_TABLE%20%20%20+0x000%20NextHandleNeedingPool%20:%20Uint4B%20%20%20+0x004%20ExtraInfoPages%20%20%20:%20Int4B%20%20%20+0x008%20TableCode%20%20%20%20%20%20%20%20:%20Uint8B%20//指向存放handle_table_entry的地方%20%20%20+0x010%20QuotaProcess%20%20%20%20%20:%20Ptr64%20_EPROCESS%20%20%20+0x018%20HandleTableList%20%20:%20_LIST_ENTRY%20%20%20+0x028%20UniqueProcessId%20%20:%20Uint4B%20%20%20+0x02c%20Flags%20%20%20%20%20%20%20%20%20%20%20%20:%20Uint4B%20%20%20+0x02c%20StrictFIFO%20%20%20%20%20%20%20:%20Pos%200,%201%20Bit%20%20%20+0x02c%20EnableHandleExceptions%20:%20Pos%201,%201%20Bit%20%20%20+0x02c%20Rundown%20%20%20%20%20%20%20%20%20%20:%20Pos%202,%201%20Bit%20%20%20+0x02c%20Duplicated%20%20%20%20%20%20%20:%20Pos%203,%201%20Bit%20%20%20+0x02c%20RaiseUMExceptionOnInvalidHandleClose%20:%20Pos%204,%201%20Bit%20%20%20+0x030%20HandleContentionEvent%20:%20_EX_PUSH_LOCK%20%20%20+0x038%20HandleTableLock%20%20:%20_EX_PUSH_LOCK%20%20%20+0x040%20FreeLists%20%20%20%20%20%20%20%20:%20[1]%20_HANDLE_TABLE_FREE_LIST%20%20%20+0x040%20ActualEntry%20%20%20%20%20%20:%20[32]%20UChar%20%20%20+0x060%20DebugInfo%20%20%20%20%20%20%20%20:%20Ptr64%20_HANDLE_TRACE_DEBUG_INFO

以一个简单的图形先概述三者的结构如下:

<center>EPROCESS -> HANDLE_TABLE -> handle_table_entry </center>
TableCode事实上比上图描述的要复杂一点点,句柄表是一个多层结构,TableCode的低两位代表了句柄表的层数。若低两位为0,则TableCode直接指向一个存放handle_table_entrys的表,若低两位为1则TableCode指向一个中间的句柄表,该表里存放的也是"TableCode";同理若TableCode低两位为3,则往后还有两层TableCode表。

image-20220723170108513.png
<center>TableCode结构