二进制入门学习笔记-21.句柄表

1.句柄表

1.1 内核对象与句柄表

前面三环的时候进程同步和进程互斥那里学过一些创建互斥体/获取互斥体等一类的内核对象操作,这些内核对象在不同的进程之间可以通过名称来跨进程访问。本质上来说也就是保存在高2G内核空间中的结构体。操作系统为了方便用户从三环访问到这些0环的内核对象,就在每一个进程(也只有进程才有)里设置了一张表,也就是传说中的句柄表。3环API访问时,将句柄(其实就是个数字)作为参数调用API。有效的避免了用户直接指定内核地址,防止出现蓝屏。简单来说就是如下的这样一张表:

句柄 内存地址
1 0xffff1111
2 0xffff2222
...... .......

注:反调试的时候也可以用这个判断,看看那个进程的句柄表里有没有我这个进程,如果有就看看这个进程是不是操作系统进程,不是的话可能就是调试器。

1.2 如何找句柄表

句柄表在EPROCESS结构体偏移0xC4的地方,使用windbg查看句柄表流程如下:

kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 819bd830  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 0032b000  ObjectTable: e1000cf8  HandleCount: 258.
    Image: System

.....省略

PROCESS 8184c020  SessionId: 0  Cid: 045c    Peb: 7ffd8000  ParentCid: 0594
    DirBase: 095802c0  ObjectTable: e1923680  HandleCount:  38.
    Image: conime.exe


kd> dt _EPROCESS 8184c020  
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x1d61224`5d6e081c
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x0000045c Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x8055b158 - 0x814994f8 ]
   +0x090 QuotaUsage       : [3] 0x9d8
   +0x09c QuotaPeak        : [3] 0xa00
   +0x0a8 CommitCharge     : 0xf0
   +0x0ac PeakVirtualSize  : 0x234f000
   +0x0b0 VirtualSize      : 0x1e2a000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0xf9ef6014 - 0x81499524 ]
   +0x0bc DebugPort        : (null) 
   +0x0c0 ExceptionPort    : 0xe15caac0 Void
   +0x0c4 ObjectTable      : 0xe1923680 _HANDLE_TABLE
    ......省略


kd> dt _HANDLE_TABLE 0xe1923680 
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0xe10fb000   //这里才是真正的句柄表的地址(最后两个二进制位是句柄表结构)!
   +0x004 QuotaProcess     : 0x8184c020 _EPROCESS
   +0x008 UniqueProcessId  : 0x0000045c Void
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList  : _LIST_ENTRY [ 0x8055c448 - 0xe252ebec ]
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : (null) 
   +0x02c ExtraInfoPages   : 0n0
   +0x030 FirstFree        : 0xa4
   +0x034 LastFree         : 0
   +0x038 NextHandleNeedingPool : 0x800
   +0x03c HandleCount      : 0n38
   +0x040 Flags            : 0
   +0x040 StrictFIFO       : 0y0


kd> dq 0xe10fb000
e10fb000  fffffffe`00000000 000f0003`e10000a1
e10fb010  00000003`e15473f9 000000a0`00000000
e10fb020  021f0003`8179bd49 000f000f`e1542641
e10fb030  021f0001`e1581f51 000f037f`81485b99
e10fb040  0002000f`e181f919 001f0001`8176dea9
e10fb050  000f01ff`818a7f61 000f037f`81485b99
e10fb060  00100003`814b8499 00100003`81665229
e10fb070  020f003f`e21be851 001f0003`81879ba1

当我们使用createprocess等函数返回的那个句柄值,除以4,就对应上述句柄表的编号(句柄表中的项以八字节为单位)。这里面低31-3位(最后三位0,1,2清0,例如,81485b99就改成81485b98)就对应这内核对象的地址(注意:任何一个内核对象其实都有一个如下的_OBJECT_HEADER,这里的地址指向的也就是这个头的开始位置,如果需要直接访问内核对象结构体直接向后偏移18个字节即可)。

kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
   +0x000 PointerCount     : Int4B
   +0x004 HandleCount      : Int4B
   +0x004 NextToFree       : Ptr32 Void
   +0x008 Type             : Ptr32 _OBJECT_TYPE
   +0x00c NameInfoOffset   : UChar
   +0x00d HandleInfoOffset : UChar
   +0x00e QuotaInfoOffset  : UChar
   +0x00f Flags            : UChar
   +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : Ptr32 Void
   +0x014 SecurityDescriptor : Ptr32 Void
   +0x018 Body             : _QUAD

1.3 句柄表结构

一个页4KB,一个句柄8字节,一个页最多能存512个句柄。当句柄数量不到512时,就是单页的,当句柄数量超过512时句柄表结构变为两级。自己访问判断是几级句柄表时,要看TableCode,TableCode的最后两个二进制位如果是00就是1级的,如果是01就是两级的,如果是10就是三级的。

2.全局句柄表

全局句柄表(PspCidTable)里存储了所有的进程和线程,这个句柄表是所有进程共用的,只要是存活的进程或者线程都可以在这里查到。找全局句柄表的操作如下:

kd> dd PspCidTable  //全局变量,编程的时候可以在内核文件导出表里找或者内存搜索特征码,这个地址存的是_HANDLE_TABLE。
8055b260  e1000898 00000002 00000000 00000000
8055b270  00000000 00000000 00000000 00000000
8055b280  00000000 00000000 00000000 00000000
8055b290  00000000 00000000 00000000 00000000
8055b2a0  00000000 00000000 00000000 00000000
8055b2b0  00000000 00000000 00000000 00000000
8055b2c0  00000000 00000000 00000000 00000000
8055b2d0  00000000 00000000 00000000 00000000

kd> dt _HANDLE_TABLE e1000898
nt!_HANDLE_TABLE
   +0x000 TableCode        : 0xe20c7001  //这里我的TableCode最后两个二进制位为01,根据上一节的句柄表结构,可以知道这是一个二级的表
   +0x004 QuotaProcess     : (null) 
   +0x008 UniqueProcessId  : (null) 
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK
   +0x01c HandleTableList  : _LIST_ENTRY [ 0xe10008b4 - 0xe10008b4 ]
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK
   +0x028 DebugInfo        : (null) 
   +0x02c ExtraInfoPages   : 0n0
   +0x030 FirstFree        : 0xf50
   +0x034 LastFree         : 0xef8
   +0x038 NextHandleNeedingPool : 0x1000
   +0x03c HandleCount      : 0n369
   +0x040 Flags            : 1
   +0x040 StrictFIFO       : 0y1

kd> dd 0xe20c7000
e20c7000  e1003000 e20c8000 00000000 00000000
e20c7010  00000000 00000000 00000000 00000000
e20c7020  00000000 00000000 00000000 00000000
e20c7030  00000000 00000000 00000000 00000000
e20c7040  00000000 00000000 00000000 00000000
e20c7050  00000000 00000000 00000000 00000000
e20c7060  00000000 00000000 00000000 00000000

#然后我们查看一下notepad进程在句柄表的位置,这里我的notepad的pid为2828。2828/4=707>512。707-512=195。所以它应该在索引号为1的句柄表的195(0xC3)位置。

kd> dq e20c8000+c3*8
e20c8618  00000000`81701361 00000a08`00000000
e20c8628  00000628`00000000 00000c10`00000000
e20c8638  00000418`00000000 00000738`00000000
e20c8648  00000b6c`00000000 00000d2c`00000000
e20c8658  00000fc4`00000000 000004d4`00000000
e20c8668  00000508`00000000 00000b50`00000000
e20c8678  00000864`00000000 00000d84`00000000
e20c8688  00000a4c`00000000 00000844`00000000

kd> dt _EPROCESS 81701360  //记得最后三个二进制位是属性要清0
nt!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x1d61390`2e76d973
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x00000b0c Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x818e4e28 - 0x817c20a8 ]
   +0x090 QuotaUsage       : [3] 0xa50
   +0x09c QuotaPeak        : [3] 0xb18
   +0x0a8 CommitCharge     : 0x185
   +0x0ac PeakVirtualSize  : 0x244d000
   +0x0b0 VirtualSize      : 0x1f87000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x818e4e54 - 0x817c20d4 ]
   +0x0bc DebugPort        : (null) 
   +0x0c0 ExceptionPort    : 0xe15caac0 Void
   +0x0c4 ObjectTable      : 0xe25a6650 _HANDLE_TABLE
   +0x0c8 Token            : _EX_FAST_REF
   +0x0cc WorkingSetLock   : _FAST_MUTEX
   +0x0ec WorkingSetPage   : 0x3461
   +0x0f0 AddressCreationLock : _FAST_MUTEX
   +0x110 HyperSpaceLock   : 0
   +0x114 ForkInProgress   : (null) 
   +0x118 HardwareTrigger  : 0
   +0x11c VadRoot          : 0x81684088 Void
   +0x120 VadHint          : 0x8161e280 Void
   +0x124 CloneRoot        : (null) 
   +0x128 NumberOfPrivatePages : 0xc1
   +0x12c NumberOfLockedPages : 0
   +0x130 Win32Process     : 0xe21393b0 Void
   +0x134 Job              : (null) 
   +0x138 SectionObject    : 0xe190cf10 Void
   +0x13c SectionBaseAddress : 0x01000000 Void
   +0x140 QuotaBlock       : 0x815ddcf0 _EPROCESS_QUOTA_BLOCK
   +0x144 WorkingSetWatch  : (null) 
   +0x148 Win32WindowStation : 0x0000003c Void
   +0x14c InheritedFromUniqueProcessId : 0x000006b4 Void
   +0x150 LdtInformation   : (null) 
   +0x154 VadFreeHint      : (null) 
   +0x158 VdmObjects       : (null) 
   +0x15c DeviceMap        : 0xe1e3dbe0 Void
   +0x160 PhysicalVadList  : _LIST_ENTRY [ 0x817014c0 - 0x817014c0 ]
   +0x168 PageDirectoryPte : _HARDWARE_PTE
   +0x168 Filler           : 0
   +0x170 Session          : 0xf9ef6000 Void
   +0x174 ImageFileName    : [16]  "notepad.exe"
   +0x184 JobLinks         : _LIST_ENTRY [ 0x0 - 0x0 ]
......省略

注意:全局句柄表中最后一级地址直接对应内核对象的首地址,没有指向前面的_OBJECT_HEADER,所以不需要偏移0x18!!!

如果要遍历进程的话,这里可以直接在向前偏移0x18指向_OBJECT_HEADER然后判断类型即可:

kd> dt _OBJECT_HEADER 81701360-0x18
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n15
   +0x004 HandleCount      : 0n2
   +0x004 NextToFree       : 0x00000002 Void
   +0x008 Type             : 0x819bde70 _OBJECT_TYPE
   +0x00c NameInfoOffset   : 0 ''
   +0x00d HandleInfoOffset : 0 ''
   +0x00e QuotaInfoOffset  : 0 ''
   +0x00f Flags            : 0x20 ' '
   +0x010 ObjectCreateInfo : 0x815ddcf0 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x815ddcf0 Void
   +0x014 SecurityDescriptor : 0xe190b2ea Void
   +0x018 Body             : _QUAD

kd> dt _OBJECT_TYPE 0x819bde70 
nt!_OBJECT_TYPE
   +0x000 Mutex            : _ERESOURCE
   +0x038 TypeList         : _LIST_ENTRY [ 0x819bdea8 - 0x819bdea8 ]
   +0x040 Name             : _UNICODE_STRING "Process"
   +0x048 DefaultObject    : (null) 
   +0x04c Index            : 5
   +0x050 TotalNumberOfObjects : 0x22
   +0x054 TotalNumberOfHandles : 0x7d
   +0x058 HighWaterNumberOfObjects : 0x24
   +0x05c HighWaterNumberOfHandles : 0x83
   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0ac Key              : 0x636f7250
   +0x0b0 ObjectLocks      : [4] _ERESOURCE

3.获取ntoskrnl.exe导出函数地址

获取ntoskrnl.exe中的导出函数的地址可以用MmGetSystemRoutineAddress。大致用法如下:

#include<ntifs.h>


VOID DriverUnLoad(PDRIVER_OBJECT pDriverObject)
{
    DbgPrint("驱动已卸载。");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegPath)
{

    NTSTATUS Status;
    ULONG RetAddress;

    pDriverObject->DriverUnload = DriverUnLoad;
    UNICODE_STRING functionName;
    RtlInitUnicodeString(&functionName, L"PsSynchronizeWithThreadInsertion");
        ULONG PsSynchronizeWithThreadInsertion =(ULONG) MmGetSystemRoutineAddress(&functionName);
        KdPrint(("PsSynchronizeWithThreadInsertion %x\n", PsSynchronizeWithThreadInsertion));

    return STATUS_SUCCESS;
}

4.练习

通过遍历全局句柄表遍历全部进程。