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\r\n",ObjectHeader);

                  PVOID ObjectBody = (PVOID)((UINT8*)ObjectHeader + _BODY_OFFSET_OBJECT_HEADER);

                  if (MmIsAddressValid(ObjectBody))   //这里应当判断对象是否合法

                  {

                      DbgPrint("Object:%p\r\n", 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\r\n",ObjectHeader);
PVOID ObjectBody = (PVOID)((UINT8*)ObjectHeader + _BODY_OFFSET_OBJECT_HEADER);
if (MmIsAddressValid(ObjectBody)) //这里应当判断对象是否合法
{
DbgPrint("Object:%p\r\n", 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()\r\n");
}

  

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);

扫描系统句柄表(WIN7 x86)(附录源码)的更多相关文章

  1. windows内存体系结构 内存查询,读,写(附录源码)

    “进程内存管理器”这个程序实现的最基本功能也就是对内存的读写,之前的两篇文章也就是做的一个铺垫,介绍了内核模式切换和IoDeviceControl函数进行的应用程序与驱动程序通信的问题.接下来就进入正 ...

  2. Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战

    Java生鲜电商平台-秒杀系统微服务架构设计与源码解析实战 Java生鲜电商平台-  什么是秒杀 通俗一点讲就是网络商家为促销等目的组织的网上限时抢购活动 比如说京东秒杀,就是一种定时定量秒杀,在规定 ...

  3. Ext.NET 4.1 系统框架的搭建(后台) 附源码

    Ext.NET 4.1 系统框架的搭建(后台) 附源码 代码运行环境:.net 4.5  VS2013 (代码可直接编译运行) 预览图: 分析图: 上面系统的构建包括三块区域:North.West和C ...

  4. 【.net+jquery】绘制自定义表单(含源码)

    前言 两年前在力控的时候就想做一个类似的功能,当时思路大家都讨论好了,诸多原因最终还是夭折了.没想到两年多后再这有重新提出要写一个绘制表单的功能.对此也是有点小激动呢?总共用时8.5天的时间基本功能也 ...

  5. 【Android 系统开发】CyanogenMod 13.0 源码下载 编译 ROM 制作 ( 手机平台 : 小米4 | 编译平台 : Ubuntu 14.04 LTS 虚拟机)

                 分类: Android 系统开发(5)                                              作者同类文章X 版权声明:本文为博主原创文章 ...

  6. Django开发密码管理表实例【附源码】

    文章及代码比较基础,适合初.中级人员,高手略过 阅读此篇文章你可以: 获取一个Django实现增删改查的案例源码 了解数据加密的使用场景和方法以及如何在Python3中使用 背景介绍 DBA需要维护一 ...

  7. Django—admin系统:admin的使用及源码剖析

    admin组件使用 Django 提供了基于 web 的管理工具. Django 自动管理工具是 django.contrib 的一部分.你可以在项目的 settings.py 中的 INSTALLE ...

  8. 16 BasicHashTable基本哈希表类(三)——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/oloroso ...

  9. 15 BasicHashTable基本哈希表类(二)——Live555源码阅读(一)基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 本文由乌合之众 lym瞎编,欢迎转载 http://www.cnblogs.com/oloroso ...

随机推荐

  1. springboot整合mybatis将sql打印到日志(转)

    在前台请求数据的时候,sql语句一直都是打印到控制台的,有一个想法就是想让它打印到日志里,该如何做呢? 见下面的mybatis配置文件: <?xml version="1.0" ...

  2. 项目中同一个dll的x86和x64同时引用

    <ItemGroup Condition=" '$(Platform)' == 'x86' "> <Reference Include="System. ...

  3. 【Coursera】History: Dawn of Electronic Computing学后小结

    今天学习了Coursera上University of Michigan开的互联网的历史.技术和安全课程的FirstWeek内容. 先是吐槽下这个Coursera,认证非常麻烦,PC端需要摄像头拍照. ...

  4. UVa 11729 突击战

    https://vjudge.net/problem/UVA-11729 题意:有n个部下,每个部下需要完成一项任务.第i个部下需要你话B分钟交代任务,然后立刻执行J分钟完成任务.安排交代任务顺序并计 ...

  5. 基于Java的三种对象持久化方式

    1:序列化技术: 序列化的过程就是将对象写入字节流和从字节流中读取对象.将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存到文件中,可以通过管道或线程读取,或通过网络连接将对象 ...

  6. Codeforces Beta Round #94 div2 D 优先队列

    B. String time limit per test 2 seconds memory limit per test 256 megabytes input standard input out ...

  7. Linux Shell学习笔记(一)

    Shell,见名知意,就是一个作为用户与Linux OS间接口的程序,允许用户向OS输入需要执行的命令.Shell众多,这里只介绍Bash. 0)实验的Shell版本 显示shell版本: /bin/ ...

  8. Java中的hashcode方法

    一.hashCode方法的作用 对于包含容器类型的程序设计语言来说,基本上都会涉及到hashCode.在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列 ...

  9. 关于angular5的惰性加载报错问题

    之前为了测试一个模块优化问题,于是用angular-cli快速搭建了个ng5的脚手架demo,在应用惰性加载功能的时候发现浏览器报错如下: ERROR Error: Uncaught (in prom ...

  10. python-day41--约束条件

    一 .介绍 约束条件与数据类型的宽度一样,都是可选参数 作用:用于保证数据的完整性和一致性主要分为: PRIMARY KEY (PK) 标识该字段为该表的主键,可以唯一的标识记录 FOREIGN KE ...