• 扫描系统句柄表(WIN7 x86)(附录源码)


                           

    PspCidTable存放着系统中所有的进程和线程对象,其索引也就是进程ID(PID)或线程ID(TID).先通过它来看看windbg里的HANDLE_TABLE结构:

     

    可以看到地址 0x83f41bc4中存放的内容是 0x 8da010a8,这是系统的_HANDLE_TABLE的结构。

     

    好了,现在windbg是得到HANDLE_TABLE结构了,还是要代码实现的。这里只简单用一下加偏移:

            //system进程的eprocess地址

    PEPROCESS EProcess = (PEPROCESS)0x86aee798;

    #define  _HANDLE_TABLE_OFFSET_EPROCESS  0x0f4

    HandleTable = (PHANDLE_TABLE)(*((ULONG*)((UINT8*)EProcess + _HANDLE_TABLE_OFFSET_EPROCESS)));

    HANDLE_TABLE这里有两个需要关注的内容,第一个是TableCode,另一个是NextHandleNeedingPool.

    TableCode成员,可以认为它是一个指向句柄表的地址,其中这个数值的低2位表示的是句柄表的层数,所以我们实际得到的句柄表的地址是要掩掉低2位的,也就是 TableCode&& 0xFFFFFFF8(TableCode&~0x3) ,其中低两位 为0时表示  1层索引,为1时表示2层索引,为2时表示3层索引,最后我们索引到的是一个_HANDLE_TABLE_ENTRY的结构,这个结构里面有我们要的_EPROCESS对象地址。

    借某位博主图一用(图片源自:http://www.cnblogs.com/ck1020/p/5897460.html

     

    这个图可以说是非常清晰了(有一处小错误,TableCode低两位为2是三级索引,不过瑕不掩瑜呐)

    对于每一个索引表大小都为1页 4KB,其中一级表存放的是8Byte的_HANDLE_TABLE_ENTRY的结构,所以每一个1级表就只能存放512个项;

    2级表存放的是1级表的地址(4Byte)那么每一个2级表能够存放4KB/4B = 1024个1级表的地址,如果存在3级表的话,相当庞大的数目了。

    回到上面可以看到TableCode为0x8f71001, 低2位为01 ,可以知道当前是2层索引结构,其中句柄表的地址为0x8f71000,通过命令 dd 0x0x8f71000看到对应的2级索引表:

     

    可以看到就只有两项,也就是说有两个1级表,那么当前的句柄表能够容纳512*2=1024个句柄.

    通过地址0x8da04000访问到第一个1级索引表来看看 :

     

    里面是很多8字节的_HANDLE_TABLE_ENTRY,查看WRK的源码看它的结构:

    typedef struct _HANDLE_TABLE_ENTRY {

    //
    // The pointer to the object overloaded with three ob attributes bits in
    // the lower order and the high bit to denote locked or unlocked entries
    //

    union {

    PVOID Object;

    ULONG ObAttributes;

    PHANDLE_TABLE_ENTRY_INFO InfoTable;

    ULONG_PTR Value;
    };

    //
    // This field either contains the granted access mask for the handle or an
    // ob variation that also stores the same information. Or in the case of
    // a free entry the field stores the index for the next free entry in the
    // free list. This is like a FAT chain, and is used instead of pointers
    // to make table duplication easier, because the entries can just be
    // copied without needing to modify pointers.
    //

    union {

    union {

    ACCESS_MASK GrantedAccess;

    struct {

    USHORT GrantedAccessIndex;
    USHORT CreatorBackTraceIndex;
    };
    };

    LONG NextFreeTableEntry;
    };
    } HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

    typedef struct _HANDLE_TABLE_ENTRY {

     

        //

        //  The pointer to the object overloaded with three ob attributes bits in

        //  the lower order and the high bit to denote locked or unlocked entries

        //

     

        union {

     

            PVOID Object;

     

            ULONG ObAttributes;

     

            PHANDLE_TABLE_ENTRY_INFO InfoTable;

     

            ULONG_PTR Value;

        };

     

        //

        //  This field either contains the granted access mask for the handle or an

        //  ob variation that also stores the same information.  Or in the case of

        //  a free entry the field stores the index for the next free entry in the

        //  free list.  This is like a FAT chain, and is used instead of pointers

        //  to make table duplication easier, because the entries can just be

        //  copied without needing to modify pointers.

        //

     

        union {

     

            union {

     

                ACCESS_MASK GrantedAccess;

     

                struct {

     

                    USHORT GrantedAccessIndex;

                    USHORT CreatorBackTraceIndex;

                };

            };

     

            LONG NextFreeTableEntry;

        };

    } HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

    通过这个结构可以看出HANDLE_TABLE_ENTRY 8Byte的前4Byte是一个Object对象,这也就是我们要找的_EPROCESS指针,但是需要注意的是,句柄表中的Object指针的低3位则是有另外意义的:

    ①第0位OBJ_PROTECT_CLOSE,表示调用者是否允许关闭该句柄;

    ②第1位OBJ_ INHERIT,指示该进程所创建的子进程是否可以继承该句柄,即是否将该句柄项拷贝到子进程的句柄表中;

    ③第2位OBJ_ AUDIT_OBJECT_ CLOSE。指示关闭该对象时是否产生一个审计事件。

    所以我们在使用该指针的时候要掩掉低3Bit

    也就是说对于Object=86ae88a9应该变为ObjectHeader=0x86aee799& 0xFFFFFFF8 (0x86aee799&~0x07)&~0x07 = 0x86aee798,这才是需要的_EPROCESS 的地址;

    说了这么多,我们的代码也来实现实现:

                 TableCode = HandleTable->TableCode;

        TableCode = (ULONG)TableCode & 0xFFFFFFFC;  //去掉低两位

    #define  _SPECIAL_PURPOSE                8

                 //越过特殊用途8字节到第一个HandleTableEntry

        HandleTableEntry = (PHANDLE_TABLE_ENTRY)((ULONG*)((UINT8*)TableCode + _SPECIAL_PURPOSE));

                 //去掉低3位掩码标志,转换为对象(体)指针

                 //EPROCESS地址

                 PVOID ObjectHeader = (PVOID)((ULONG)(HandleTableEntry->Object) & 0xFFFFFFF8);

    Windbg继续开进,验证一下0x86aee798,这个地址的对象类型:

    (在写这个程序代码的时候,就已经WINDBG查看了system进程的EPROCESS地址写死到了代码中,回头看看,正是这个0x86aee798!!!!)

     

    一个Process对象,

     

     

    Bingo!

                   代码无误,代接下来循环读取HandleTableEntry结构,打印出对象头和对象体:

                   

    NTSTATUS EnumTable0(PVOID TableCode)
    
    {
    
        PHANDLE_TABLE_ENTRY HandleTableEntry = NULL;
    
        ULONG i = 0;
    
        //越过特殊用途8字节到第一个HandleTableEntry
    
        HandleTableEntry = (PHANDLE_TABLE_ENTRY)((ULONG*)((UINT8*)TableCode + _SPECIAL_PURPOSE));
    
        for (i = 0; i<_MAX; i++)
    
        {
    
             if (MmIsAddressValid((PVOID)HandleTableEntry))   //判断该虚拟内存是否合法
    
             {
    
                 //去掉低3位掩码标志,转换为对象(体)指针
    
                 //EPROCESS地址
    
                 PVOID ObjectHeader = (PVOID)((ULONG)(HandleTableEntry->Object) & 0xFFFFFFF8);
    
                 if (MmIsAddressValid(ObjectHeader))
    
                 {
    
                      DbgPrint("ObjectHeader:%p
    ",ObjectHeader);
    
                      PVOID ObjectBody = (PVOID)((UINT8*)ObjectHeader + _BODY_OFFSET_OBJECT_HEADER);
    
                      if (MmIsAddressValid(ObjectBody))   //这里应当判断对象是否合法
    
                      {
    
                          DbgPrint("Object:%p
    ", ObjectBody);
    
                          __ObjectCount++;
    
                      }
    
                 }
    
             }
    
             HandleTableEntry++;     //结构体指针++ 一加一个结构体
    
        }
    
        return STATUS_SUCCESS;
    
    }
    
     
    
     

    接下来回头谈谈_HANDLE_TABLE中的一个叫做NextHandleNeedingPool的成员:

    这个成员描述了下次句柄表增长的时,起始的句柄值(别忘了句柄是以4为步长增长的),上面的分析我们知道系统有2个2级索引那么最多能描述512*2=1024个_HANDLE_ENTRY,也就是说最大能表示的句柄值为1024*4=4096=0x1000,因为是从0x00开始的,所以当前的索引表状态能够描述的最大句柄上限为0x1000,这个值也就是下次句柄表扩展的起始句柄值.

    最后来验证一下通过PspCidTable和进程的PID找到进程对应的_EPROCESS:

    以audiodg.exe作为检验对象。

    它的PID为1048(十进制),应当位于第1048/4=262 个表项,我们每一个1级索引表能容纳512个表项,PID为1048应该在第1个1级索引的第262=0x106个表项(每个表项8Byte)

     

    BINGO!

    最后的最后总结一下流程:

    (1)(WINDBG    )获取到PspCidTable的地址,根据Tablecode低2位判断句柄表的层数。

    (2)遍历句柄表:只有一级句柄表才是_HANDLE_TABLE_ENTRY(8字节),二级和三级都是指针(4字节),每一个表都是1页(4KB)大小,

    (3)获取到Object之后,可以通过ObjectHeader的TypeIndex看看是不是Process.

    (4)HANDLE_TABLE_ENTRY 8Byte的前4Byte是一个Object对象,去掉低三位才是才是需要的_EPROCESS 对象的地址,_EPROCESS 对象偏移0x18处是相对于对象头的对象体。

    源代码:

    ScanProcessHandleTable.c
    #include "ScanProcessHandleTable.h"
    
    #define  _HANDLE_TABLE_OFFSET_EPROCESS  0x0f4
    #define  _SPECIAL_PURPOSE                8
    #define  _MAX                           511
    #define  _BODY_OFFSET_OBJECT_HEADER     0x18
    
    ULONG64  __ObjectCount = 0;
    
    NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
    {
    	UNREFERENCED_PARAMETER(RegisterPath);
    	NTSTATUS Status = STATUS_SUCCESS;
    	//system进程的eprocess地址
    	PEPROCESS EProcess = (PEPROCESS)0x86aee798;
    	PDEVICE_OBJECT  DeviceObject = NULL;
    
    	DriverObject->DriverUnload = DriverUnload;
    
    	SeScanProcessHandleTable(EProcess);
    
    	return Status;
    }
    
    NTSTATUS SeScanProcessHandleTable(PEPROCESS EProcess)
    {
    	NTSTATUS Status = STATUS_UNSUCCESSFUL;
    	PHANDLE_TABLE  HandleTable = NULL;
    	PVOID  TableCode = NULL;
    	ULONG  Flag = 0;
    	if (EProcess==NULL)
    	{
    		return Status;
    	}
    
    	HandleTable = (PHANDLE_TABLE)(*((ULONG*)((UINT8*)EProcess + _HANDLE_TABLE_OFFSET_EPROCESS)));
    	
    	if (HandleTable==NULL)
    	{
    		return Status;
    	}
    
    	TableCode = HandleTable->TableCode;
    	TableCode = (ULONG)TableCode & 0xFFFFFFFC;   //去掉低两位
        Flag = (ULONG)(HandleTable->TableCode) & 0x03;    //00 01 10 11
    	switch (Flag)
    	{
    	case 0:
    	{
    		EnumTable0(TableCode);
    		break;
    	}
    	case 1:
    	{
    		EnumTable1(TableCode);
    		break;
    	}
    	case 2:
    	{
    		EnumTable2(TableCode);
    		break;
    	}
    	case 3:
    	{
    		EnumTable3(TableCode);
    		break;
    	}
    	}	 
    }
    
    NTSTATUS EnumTable0(PVOID TableCode)
    {
    	PHANDLE_TABLE_ENTRY HandleTableEntry = NULL;
    	ULONG i = 0;
    	//越过特殊用途8字节到第一个HandleTableEntry
    	HandleTableEntry = (PHANDLE_TABLE_ENTRY)((ULONG*)((UINT8*)TableCode + _SPECIAL_PURPOSE));
    	for (i = 0; i<_MAX; i++)
    	{
    		if (MmIsAddressValid((PVOID)HandleTableEntry))   //判断该虚拟内存是否合法
    		{
    			//去掉低3位掩码标志,转换为对象(体)指针
    			//EPROCESS地址
    			PVOID ObjectHeader = (PVOID)((ULONG)(HandleTableEntry->Object) & 0xFFFFFFF8);
    			if (MmIsAddressValid(ObjectHeader))
    			{
    				DbgPrint("ObjectHeader:%p
    ",ObjectHeader);
    				PVOID ObjectBody = (PVOID)((UINT8*)ObjectHeader + _BODY_OFFSET_OBJECT_HEADER);
    				if (MmIsAddressValid(ObjectBody))   //这里应当判断对象是否合法
    				{
    					DbgPrint("Object:%p
    ", ObjectBody);
    					__ObjectCount++;
    				}
    			}
    		}
    		HandleTableEntry++;     //结构体指针++ 一加一个结构体
    	}
    	return STATUS_SUCCESS;
    }
    
    NTSTATUS EnumTable1(PVOID TableCode)
    {	
    	do
    	{
    		EnumTable0(*(ULONG*)TableCode);
    		(UINT8*)TableCode += sizeof(ULONG);
    
    	} while (*(ULONG*)TableCode != 0 && MmIsAddressValid(*(ULONG*)TableCode));
    
    	return STATUS_SUCCESS;
    }
    NTSTATUS EnumTable2(PVOID TableCode)
    {
    	do
    	{
    		EnumTable1(*(ULONG*)TableCode);
    		(UINT8*)TableCode += sizeof(ULONG);
    
    	} while (*(ULONG*)TableCode != 0 && MmIsAddressValid(*(ULONG*)TableCode));
    
    	return STATUS_SUCCESS;
    }
    NTSTATUS EnumTable3(PVOID TableCode)
    {
    	do
    	{
    		EnumTable2(*(ULONG*)TableCode);
    		(UINT8*)TableCode += sizeof(ULONG);
    
    	} while (*(ULONG*)TableCode != 0 && MmIsAddressValid(*(ULONG*)TableCode));
    
    	return STATUS_SUCCESS;
    }
    
    VOID DriverUnload(PDRIVER_OBJECT DriverObject)
    {
    	UNREFERENCED_PARAMETER(DriverObject);
    	DbgPrint("DriverUnload()
    ");
    }
    

      

    ScanProcessHandleTable.h
    #pragma once
    #include <ntifs.h>
    
    
    typedef struct _HANDLE_TABLE 
    {
        PVOID TableCode;
        PEPROCESS QuotaProcess;
        HANDLE UniqueProcessId;
        ULONG HandleTableLock;
        LIST_ENTRY HandleTableList;
        ULONG HandleContentionEvent;
        PVOID DebugInfo;
        LONG  ExtraInfoPages;
        ULONG Flags;
        ULONG FirstFreeHandle;
        PVOID LastFreeHandleEntry;
        ULONG HandleCount;
        ULONG NextHandleNeedingPool;
        ULONG HandleCountHighWatermark;
    } HANDLE_TABLE, *PHANDLE_TABLE;
    
    typedef struct _HANDLE_TABLE_ENTRY
    {
        union {
            PVOID Object;
            ULONG ObAttributes;
            PVOID InfoTable;
            PVOID Value;
        };
        union {
            union {
                ULONG GrantedAccess;
                struct {
                    USHORT GrantedAccessIndex;
                    USHORT CreatorBackTraceIndex;
                };
            };
            ULONG NextFreeTableEntry;
        };
    } HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;
    NTSTATUS EnumTable0(PVOID TableCode);
    NTSTATUS EnumTable1(PVOID TableCode);
    NTSTATUS EnumTable2(PVOID TableCode);
    NTSTATUS EnumTable3(PVOID TableCode);
    NTSTATUS SeScanProcessHandleTable(PEPROCESS EProcess);
    VOID DriverUnload(PDRIVER_OBJECT DriverObject);
  • 相关阅读:
    C语言实现mq收发数据的函数
    4G通信模块在ARM平台下的应用
    4G 通信模块在ARM 平台下的应用
    修改web前端访问端口
    从零开始用 Flask 搭建一个网站(一)
    Python实现Windows定时关机
    前端和后端的数据交互(jquery ajax+python flask+mysql)
    python开源项目及示例代码
    Django读取Mysql数据并显示在前端
    C++关于string的一些用法
  • 原文地址:https://www.cnblogs.com/lsh123/p/7296423.html
Copyright © 2020-2023  润新知