句柄表篇——全局句柄表


寫在前面

  此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的復雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章后面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看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,遍歷所有進程(包括隱藏進程)

下一篇

  句柄表篇——總結與提升


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM