Handle

全局句柄表

如果我们对windows比较熟悉的话可以发现,只有线程和进程是有这种id的

Untitled

既然是叫全局句柄表那么一定是存在一个全局变量中的 → PspCidTable

Untitled

  • +0x000 TableCode : 0x9a644001 其中的后面的两位代表了层数 四种可能 00 01 10 11 其中windows只用了当中的三种
  • +0x004 QuotaProcess 如果是私有句柄表的话 这里指向了私有句柄表的进程对象
  • +0x008 UniqueProcessId 进程ID 这个句柄表是属于哪个进程的
  • +0x00c HandleLock : _EX_PUSH_LOCK 进程锁
  • +0x010 HandleTableList : _LIST_ENTRY [ 0x8ac01118 - 0x8ac01118 ] 链表
  • +0x018 HandleContentionEvent : _EX_PUSH_LOCK 修改句柄表事件对象
  • +0x01c DebugInfo : (null) debug的追踪
  • +0x028 FirstFreeHandle : 0xefc 指向我们当前释放的句柄
  • +0x02c LastFreeHandleEntry : 0x9a645a00 _HANDLE_TABLE_ENTRY 最后一次释放的是哪个
  • +0x030 HandleCount : 0x24c 句柄表中的有多少个句柄,这个地方不太精确

其中最重要的是TableCode 对于全局句柄表来说,64位和32位是不一样的,其中64位的句柄值是经过加密的(win7 64没有加密的,win10 64是加密的)

全局句柄表的结构

单层的结构:因为一个页是4096个字节,一个占有8字节,8字节的结构是_HANDLE_TABLE_ENTRY,其中包含了对象和属性

kd> dt _HANDLE_TABLE_ENTRY
ntdll!_HANDLE_TABLE_ENTRY
   +0x000 Object           : Ptr32 Void
   +0x000 ObAttributes     : Uint4B
   +0x000 InfoTable        : Ptr32 _HANDLE_TABLE_ENTRY_INFO
   +0x000 Value            : Uint4B
   +0x004 GrantedAccess    : Uint4B
   +0x004 GrantedAccessIndex : Uint2B
   +0x006 CreatorBackTraceIndex : Uint2B
   +0x004 NextFreeTableEntry : Uint4B

Untitled

多层结构:进程和线程过多的时候,正常我们的机器中的句柄数一定是超过了512,所以进行了分层,多层也就是1024 * 512

Untitled

1024 * 1024 * 512

Untitled

查询进程对象

怎么通过进程的PID找到相应的进程结构的:

开启了一个winobj.exe pid = 3932

Untitled

我们找到全局句柄表,可以看到地址为0x9a644001,是有两张表,当前的PID为3932,索引的话需要除以4,因为PID是4的倍数 → 0x3D7,因为一张表为512个,所以他在第二张表的0x1D7项中

Untitled

因为全局句柄表中没有权限划分,因此高4字节必然为0,所以得到的 00000000`870e5441 & FFFFFFF8

Untitled

可以看到找到了相应的进程

Untitled

如何确定对象为进程还是线程

因为全局句柄表中有进程还有线程那么我们怎么找到这个对象对应的是进程还是线程呢,因为在32位这个OBJECT_HEADER是固定的0x18字节,每个内核对象地址前面都有一个对象头结构,其中的TypeIndex来表示这个内核对象的类型(这个成员是内核对象类型的索引值)

Untitled

,内核对象的类型表存储在ObTypeIndexTable,结构体为_OBJECT_TYPE结构体,因为上述是7我们查看一下

Untitled

遍历全局句柄表来判断对象类型(代码)思路

通过搜索 PsLookupThreadByThreadId 搜索出 PspCidTable变量

Untitled

通过ObGetObjectType函数搜索 ObTypeIndexTable 对象数组来知道是不是进程或者线程

Untitled

也可以使用 PsProcessType,PsThreadType(注意导出的是2级指针)通过知道进程和线程是什么TypeInfo来知道当前的对象是进程还是线程的了

Untitled

Win10 x64情况

pid = 5924 因为是64位的情况下,如果是一级表的话,那么相应的就在5924 / 4 = 1481 1481 / 256 = 5 ….. 201

Untitled

可以看到找出来的值并不对,可能被加密了

Untitled

可以看到算法是右移动0x10

Untitled

所以DB87DC9E8080FB09 » 0x10 = FFFFDB87DC9EB080

Untitled

私有句柄表

补充一点东西在HANDLE_TABLE中,有一个链表,在游戏保护的时候很多人都会去查这个链表,所以可以断链,不过这里会PG

Untitled

全局句柄表中仅存储所有进程和线程,其他的什么对象是不在全局句柄表中的,他们这些东西的句柄都存储在调用者进程自身的私有句柄表中

用winobj.exe 举例子,查看EPROCESS结构可以发现私有句柄表在偏移0xFC的位置上,

Untitled

句柄表结构:和全局句柄表的概念一样 → 0就是一层,1就是两层

Untitled

查看TableCode中的区别,可以发现和全局句柄表中的句柄表有区别

Untitled

第一个句柄-2代表了当前的线程,3环调用的GetCurrentProcess表示获取当前进程返回的-1,线程的就是-2,前面的32位表示了权限一类的,在我们3环写代码的时候OpenProcess是有权限的,高32位的就代表有什么权限

Untitled

,低32位的末位3位有权限的划分(低3个二进制位):

  • 最后一位:锁位。为0代表加锁了。如进程句柄将该位清0,当尝试结束进程,系统会等待锁被释放,导致无法结束进程,即使恢复为1也无法结束
  • 倒数第二位:继承位。为1代表句柄可继承,为0代表不可继承。对应OpenProcess一类API的第二个参数
  • 倒数第三位(现在已经被转移到从最高位开始第6位,从0开始):可否被关闭位。为1,句柄无法被关闭(CloseHandle)

全局句柄表中存的是对象体,私有句柄表中存放的是对象头,所以实际的结构体要+0x18

kd> dt _Object_header
nt!_OBJECT_HEADER
   +0x000 PointerCount     : Int4B
   +0x004 HandleCount      : Int4B
   +0x004 NextToFree       : Ptr32 Void
   +0x008 Lock             : _EX_PUSH_LOCK
   +0x00c TypeIndex        : UChar
   +0x00d TraceFlags       : UChar
   +0x00e InfoMask         : UChar
   +0x00f Flags            : UChar
   +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : Ptr32 Void
   +0x014 SecurityDescriptor : Ptr32 Void
   +0x018 Body             : _QUAD

继承位的细说

对于继承位,1代表句柄继承,0代表不可继承,具体来说是什么意思呢?

就是我们在进程当然可以创建一个其他的进程createprocess就可以啦,那么当前进程就是父进程,父进程的句柄表和子进程的句柄表的关系

Untitled

如果句柄中的倒数第二位是0,那么就不会被继承了

进程降权

kd> dt _HANDLE_TABLE_ENTRY
ntdll!_HANDLE_TABLE_ENTRY
   +0x000 Object           : Ptr32 Void
   +0x000 ObAttributes     : Uint4B
   +0x000 InfoTable        : Ptr32 _HANDLE_TABLE_ENTRY_INFO
   +0x000 Value            : Uint4B
   +0x004 GrantedAccess    : Uint4B
   +0x004 GrantedAccessIndex : Uint2B
   +0x006 CreatorBackTraceIndex : Uint2B
   +0x004 NextFreeTableEntry : Uint4B

发现某个进程的句柄表中有自己的进程,那么就可以清空私有句柄表的自身句柄的权限位,导致句柄就不具有读和写的权限,从而保护进程

写代码实现的话,我们就要首先要了解一个函数EnumHandleTable,这个函数会枚举句柄表中所有有效的句柄

Untitled

还是以winobj.exe为例子,现在发现可以用CE可以读写内存

Untitled

#include <ntifs.h>

#define PROCESS_TERMINATE                  (0x0001)  
#define PROCESS_CREATE_THREAD              (0x0002)  
#define PROCESS_SET_SESSIONID              (0x0004)  
#define PROCESS_VM_OPERATION               (0x0008)  
#define PROCESS_VM_READ                    (0x0010)  
#define PROCESS_VM_WRITE                   (0x0020)  
#define PROCESS_DUP_HANDLE                 (0x0040)  
#define PROCESS_CREATE_PROCESS             (0x0080)  
#define PROCESS_SET_QUOTA                  (0x0100)  
#define PROCESS_SET_INFORMATION            (0x0200)  
#define PROCESS_QUERY_INFORMATION          (0x0400)  
#define PROCESS_SUSPEND_RESUME             (0x0800)  
#define PROCESS_QUERY_LIMITED_INFORMATION  (0x1000)  
#define PROCESS_SET_LIMITED_INFORMATION    (0x2000)

typedef struct _HANDLE_TABLE_ENTRY
{
    union
    {
        VOID* Object;                                                       //0x0
        ULONG ObAttributes;                                                 //0x0
        struct _HANDLE_TABLE_ENTRY_INFO* InfoTable;                         //0x0
        ULONG Value;                                                        //0x0
    };
    union
    {
        ULONG GrantedAccess;                                                //0x4
        struct
        {
            USHORT GrantedAccessIndex;                                      //0x4
            USHORT CreatorBackTraceIndex;                                   //0x6
        };
        ULONG NextFreeTableEntry;                                           //0x4
    };
}HANDLE_TABLE_ENTRY,*PHANDLE_TABLE_ENTRY;

typedef struct _HANDLE_TABLE
{
    ULONG TableCode;                                                        //0x0
    struct _EPROCESS* QuotaProcess;                                         //0x4
    VOID* UniqueProcessId;                                                  //0x8
    EX_PUSH_LOCK HandleLock;                                        //0xc
    LIST_ENTRY HandleTableList;                                     //0x10
    EX_PUSH_LOCK HandleContentionEvent;                             //0x18
    PVOID DebugInfo;                             //0x1c
    LONG ExtraInfoPages;                                                    //0x20
    union
    {
        ULONG Flags;                                                        //0x24
        UCHAR StrictFIFO : 1;                                                 //0x24
    };
    ULONG FirstFreeHandle;                                                  //0x28
    HANDLE_TABLE_ENTRY* LastFreeHandleEntry;                        //0x2c
    ULONG HandleCount;                                                      //0x30
    ULONG NextHandleNeedingPool;                                            //0x34
    ULONG HandleCountHighWatermark;                                         //0x38
}HANDLE_TABLE, * PHANDLE_TABLE;

typedef BOOLEAN(*EX_ENUMERATE_HANDLE_ROUTINE)(
    IN PHANDLE_TABLE_ENTRY HandleTableEntry,
    IN HANDLE Handle,
    IN PVOID EnumParameter
    );

NTKERNELAPI
BOOLEAN
ExEnumHandleTable(
    __in PHANDLE_TABLE HandleTable,
    __in EX_ENUMERATE_HANDLE_ROUTINE EnumHandleProcedure,
    __in PVOID EnumParameter,
    __out_opt PHANDLE Handle
);

BOOLEAN NTAPI ParserTable(
    IN PHANDLE_TABLE_ENTRY HandleTableEntry,
    IN HANDLE Handle,
    IN PVOID EnumParameter
)
{
    //后三位的二进制权限
    ULONG object = ((ULONG)HandleTableEntry->Object & ~0x7);
    if (MmIsAddressValid(object))
    {
        //_OBJECT_HEADER
        UCHAR index = *(PUCHAR)(object + 0xc);
        //_OBJECT_TYPE
        UCHAR ProcessIndex = *(((PUCHAR)(*PsProcessType)) + 0x14);
        if (index == ProcessIndex)
        {
            DbgBreakPoint();
            HandleTableEntry->GrantedAccess &= ~(PROCESS_VM_READ | PROCESS_VM_WRITE);
        }
    }
    return FALSE;
}

VOID
DriverUnload(
	_In_ struct _DRIVER_OBJECT* DriverObject
)
{

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pSeg)
{
    PEPROCESS eProcess = NULL;
    PsLookupProcessByProcessId(2600, &eProcess);

    HANDLE hProcess = NULL;
    //Get private handle table
    PHANDLE_TABLE handleTable = *(PULONG)((PUCHAR)eProcess + 0xf4);
    ExEnumHandleTable(handleTable, ParserTable, NULL, &hProcess);

    ObDereferenceObject(eProcess);
	pDriver->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

Untitled

由于我们的进程打开目标进程后,私有句柄表内的句柄权限被循环清空,导致无法读写目标进程的内存或只能查看很短的时间,所以可以进行句柄替换

构建EPROCESS结构,然后把目标进程的EPROCESS结构复制到构建的内存中

把新构建的EPROCESS中的PID,进程名那些进行修改清空

把一级页表复制,然后CR3改完新的内存物理地址进行CR3替换

修改自身进程私有句柄表中目标进程的句柄结构的object为新构建的EPROCESS结构