句柄表篇——全局句柄表

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。


🔒 华丽的分割线 🔒


练习及参考

本次答案均为参考,可以与我的答案不一致,但必须成功通过。

1️⃣ 思考当我用函数打开一个内核对象时,如果用CloseHandle,这个内核对象一定会被销毁吗?假设我用OpenProcess打开了一个现有的进程,但打开后,这个进程被关闭了,这个内核对象还存在吗?

🔒 点击查看答案 🔒


  打开一个内核对象,调用CloseHandle,不一定会被销毁,这个函数只是将该内核对象的引用计数减一,直到为0的时候操作系统检测到它时再会被销毁。

  对于第二个问题,这个内核对象是存在的,代码测试见折叠,我们继续分析:

  我们仍使用记事本进程作为小白鼠,编译运行测试代码,输入它的PID,回车得到它的句柄为十进制的2024

  怎样通过句柄找到内核对象我就不赘述了,由于编号的一样的,顶多地址不太一样,句柄数目过多会有级数,参考上一篇的分析。

  然后我们关闭记事本,再查询那个地址是不是有东西,句柄表发现还存在,结构体依旧还有。

  然后再按任意键继续,立即查询那个结构体依旧存在,但过了3秒左右的时间,那块内存的数据已经乱码,进程名无法读取,被操作系统销毁。


🔒 点击查看代码 🔒
#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>

int main(int argc, char* argv[])
{
    int pid;
    printf("请输入程序的pid:");
    scanf("%d",&pid);
    HANDLE hprocess=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
    if (hprocess==INVALID_HANDLE_VALUE)
    {
        puts("进程句柄无效!!!");
    }
    else
    {
        printf("打开进程获得句柄成功!句柄为:%d\n",hprocess);
        puts("现在请关闭软件进行分析测试,按任意键关闭句柄进行测试。");
        system("pause");
        CloseHandle(hprocess);
    }
    system("pause");
    return 0;
}

2️⃣ 使用循环打开100次某个内核对象,分析句柄表的结构;然后打开1000次某个内核对象,继续分析上述操作。

🔒 点击查看答案 🔒
//主要是操作,废话不多说,直接用命令解释

kd> !process 0 0 
**** NT ACTIVE PROCESS DUMP ****
……
Failed to get VadRoot
PROCESS 89837020  SessionId: 0  Cid: 04c8    Peb: 7ffdf000  ParentCid: 03f0
    DirBase: 13900300  ObjectTable: e1688f38  HandleCount: 119.
    Image: HandleTest.exe

kd> dt _EPROCESS 89837020
ntdll!_EPROCESS
   +0x000 Pcb              : _KPROCESS
   +0x06c ProcessLock      : _EX_PUSH_LOCK
   +0x070 CreateTime       : _LARGE_INTEGER 0x01d80da9`0f25e3b8
   +0x078 ExitTime         : _LARGE_INTEGER 0x0
   +0x080 RundownProtect   : _EX_RUNDOWN_REF
   +0x084 UniqueProcessId  : 0x000004c8 Void
   +0x088 ActiveProcessLinks : _LIST_ENTRY [ 0x898570a8 - 0x89b5cc90 ]
   +0x090 QuotaUsage       : [3] 0x488
   +0x09c QuotaPeak        : [3] 0x678
   +0x0a8 CommitCharge     : 0x43
   +0x0ac PeakVirtualSize  : 0x825000
   +0x0b0 VirtualSize      : 0x7b3000
   +0x0b4 SessionProcessLinks : _LIST_ENTRY [ 0x898570d4 - 0x89b5ccbc ]
   +0x0bc DebugPort        : 0x89b450c0 Void
   +0x0c0 ExceptionPort    : 0xe1491f68 Void
   +0x0c4 ObjectTable      : 0xe1688f38 _HANDLE_TABLE
   ……

//当句柄为100个的时候

kd> dx -id 0,0,805539a0 -r1 ((ntdll!_HANDLE_TABLE *)0xe1688f38)
((ntdll!_HANDLE_TABLE *)0xe1688f38)                 : 0xe1688f38 [Type: _HANDLE_TABLE *]
    [+0x000] TableCode        : 0xe1ab1000 [Type: unsigned long]
    [+0x004] QuotaProcess     : 0x89837020 [Type: _EPROCESS *]
    [+0x008] UniqueProcessId  : 0x4c8 [Type: void *]
    [+0x00c] HandleTableLock  [Type: _EX_PUSH_LOCK [4]]
    [+0x01c] HandleTableList  [Type: _LIST_ENTRY]
    [+0x024] HandleContentionEvent [Type: _EX_PUSH_LOCK]
    [+0x028] DebugInfo        : 0x0 [Type: _HANDLE_TRACE_DEBUG_INFO *]
    [+0x02c] ExtraInfoPages   : 0 [Type: long]
    [+0x030] FirstFree        : 0x650 [Type: unsigned long]
    [+0x034] LastFree         : 0x0 [Type: unsigned long]
    [+0x038] NextHandleNeedingPool : 0x800 [Type: unsigned long]
    [+0x03c] HandleCount      : 119 [Type: long]
    [+0x040] Flags            : 0x0 [Type: unsigned long]
    [+0x040 ( 0: 0)] StrictFIFO       : 0x0 [Type: unsigned char]
kd> dq 0xe1ab1000 + 660/4*8
ReadVirtual: e1ab1cc0 not properly sign extended
e1ab1cc0  001f0fff`89837009 001f0fff`89837009
e1ab1cd0  001f0fff`89837009 001f0fff`89837009
e1ab1ce0  001f0fff`89837009 001f0fff`89837009
e1ab1cf0  001f0fff`89837009 001f0fff`89837009
e1ab1d00  001f0fff`89837009 001f0fff`89837009
e1ab1d10  001f0fff`89837009 001f0fff`89837009
e1ab1d20  001f0fff`89837009 001f0fff`89837009
e1ab1d30  001f0fff`89837009 001f0fff`89837009
kd> g

//当句柄增加至1000个的时候
kd> dx -id 0,0,805539a0 -r1 ((ntdll!_HANDLE_TABLE *)0xe1688f38)
((ntdll!_HANDLE_TABLE *)0xe1688f38)                 : 0xe1688f38 [Type: _HANDLE_TABLE *]
    [+0x000] TableCode        : 0xe1a95001 [Type: unsigned long]
    [+0x004] QuotaProcess     : 0x89837020 [Type: _EPROCESS *]
    [+0x008] UniqueProcessId  : 0x4c8 [Type: void *]
    [+0x00c] HandleTableLock  [Type: _EX_PUSH_LOCK [4]]
    [+0x01c] HandleTableList  [Type: _LIST_ENTRY]
    [+0x024] HandleContentionEvent [Type: _EX_PUSH_LOCK]
    [+0x028] DebugInfo        : 0x0 [Type: _HANDLE_TRACE_DEBUG_INFO *]
    [+0x02c] ExtraInfoPages   : 0 [Type: long]
    [+0x030] FirstFree        : 0xff0 [Type: unsigned long]
    [+0x034] LastFree         : 0x0 [Type: unsigned long]
    [+0x038] NextHandleNeedingPool : 0x1800 [Type: unsigned long]
    [+0x03c] HandleCount      : 1019 [Type: long]
    [+0x040] Flags            : 0x0 [Type: unsigned long]
    [+0x040 ( 0: 0)] StrictFIFO       : 0x0 [Type: unsigned char]
kd> dd 0xe1a95000
e1a95000  e1ab1000 e1aaa000 e1a9c000 00000000
e1a95010  00000000 00000000 00000000 00000000
e1a95020  00000000 00000000 00000000 00000000
e1a95030  00000000 00000000 00000000 00000000
e1a95040  00000000 00000000 00000000 00000000
e1a95050  00000000 00000000 00000000 00000000
e1a95060  00000000 00000000 00000000 00000000
e1a95070  00000000 00000000 00000000 00000000

//如何计算呢?我们以 fc0 这个句柄为例, fc0/4 = 3f0
//然后看看里面有几个 0x200(二级的一个句柄表一个页存512个)
//发现只有一个,那么它在第二个表,3f0 = 1f0 + 200

kd> dq e1aaa000 + 1f0*8
ReadVirtual: e1aaaf80 not properly sign extended
e1aaaf80  001f0fff`89837009 001f0fff`89837009
e1aaaf90  001f0fff`89837009 001f0fff`89837009
e1aaafa0  001f0fff`89837009 001f0fff`89837009
e1aaafb0  001f0fff`89837009 001f0fff`89837009
e1aaafc0  001f0fff`89837009 001f0fff`89837009
e1aaafd0  001f0fff`89837009 00000ff8`00000000
e1aaafe0  00000fec`00000000 001f0fff`89b685e9
e1aaaff0  00001004`00000000 001f03ff`8982f629
kd> g
🔒 点击查看代码 🔒
#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>

int main(int argc, char* argv[])
{
    int pid = GetCurrentProcessId();

    //提供一个缓存作为存储句柄的数组,用来调用 CloseHandle 函数减少引用计数,这是个好习惯
    //虽然该进程结束后,操作系统会帮我们清理掉
    HANDLE* buffer=(HANDLE*)VirtualAlloc(NULL,sizeof(HANDLE)*1000,MEM_COMMIT,PAGE_READWRITE);

    int i=0;
    for (; i<100;i++)
    {
        buffer[i]=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
        printf("进程句柄为:%x\n",buffer[i]);
    }

    puts("按任意键将句柄增加至1000个");
    system("pause");

    for (;i<1000;i++)
    {
        buffer[i]=OpenProcess(PROCESS_ALL_ACCESS,FALSE,pid);
        printf("进程句柄为:%x\n",buffer[i]);
    }   
    puts("按任意键关闭句柄,结束程序");
    system("pause");    
    for (i =0 ; i<1000;i++)
    {
        CloseHandle(buffer[i]);
    }

    VirtualFree(buffer,NULL,MEM_FREE);

    return 0;
}

全局句柄表

  在进程中可以创建、打开很多内核对象,这些内核对象的地址都存储在当前进程的句柄表中。我们在应用层得到的句柄实际上就是句柄表的索引。进程的句柄表是私有的,每个进程都有一个自己的句柄表。除此之外,系统还有一个全局句柄表:PspCidTable,全局句柄表因此又被称为CID句柄表
所有的进程和线程无论无论是否打开,都在这个表中。
  每个进程和线程都有一个唯一的编号:PIDTID,这两个值其实就是全局句柄表中的索引,统称CID。进程和线程的查询,主要是以下三个函数,按照给定的PIDTIDPspCidTable从查找相应的进线程对象:

PsLookupProcessThreadByCid(x, x, x);
PsLookupProcessByProcessId(HANDLE ProcessId, PEPROCESS *Process);
PsLookupThreadByThreadId(HANDLE ThreadId, PETHREAD *Thread);  

  全局句柄表的结构和进程句柄表的结构一模一样,就不再赘述了,不会的话请参考复习上一篇。

本节练习

本节的答案将会在下一节进行讲解,务必把本节练习做完后看下一个讲解内容。不要偷懒,实验是学习本教程的捷径。

  俗话说得好,光说不练假把式,如下是本节相关的练习。如果练习没做好,就不要看下一节教程了,越到后面,不做练习的话容易夹生了,开始还明白,后来就真的一点都不明白了。本节练习不多,请保质保量的完成。
1️⃣ 编写程序,通过全局句柄表PspCidTable,遍历所有进程(包括隐藏进程)

下一篇

  句柄表篇——总结与提升

posted @ 2022-01-20 11:53  寂静的羽夏  阅读(1231)  评论(0编辑  收藏  举报