控制程式碼表篇——全域性控制程式碼表

寂靜的羽夏發表於2022-01-20

寫在前面

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

你如果是從中間插過來看的,請仔細閱讀 羽夏看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;
}

全域性控制程式碼表

  在程式中可以建立、開啟很多核心物件,這些核心物件的地址都儲存在當前程式的控制程式碼表中。我們在應用層得到的控制程式碼實際上就是控制程式碼表的索引。程式的控制程式碼表是私有的,每個程式都有一個自己的控制程式碼表。除此之外,系統還有一個全域性控制程式碼表:PsdCidTable,全域性控制程式碼表因此又被稱為CID控制程式碼表
所有的程式和執行緒無論無論是否開啟,都在這個表中。
  每個程式和執行緒都有一個唯一的編號:PIDTID,這兩個值其實就是全域性控制程式碼表中的索引,統稱CID。程式和執行緒的查詢,主要是以下三個函式,按照給定的PIDTIDPspCidTable從查詢相應的進執行緒物件:

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

  全域性控制程式碼表的結構和程式控制程式碼表的結構一模一樣,就不再贅述了,不會的話請參考複習上一篇。

本節練習

本節的答案將會在下一節進行講解,務必把本節練習做完後看下一個講解內容。不要偷懶,實驗是學習本教程的捷徑。

  俗話說得好,光說不練假把式,如下是本節相關的練習。如果練習沒做好,就不要看下一節教程了,越到後面,不做練習的話容易夾生了,開始還明白,後來就真的一點都不明白了。本節練習不多,請保質保量的完成。
1️⃣ 編寫程式,通過全域性控制程式碼表PsdCidTable,遍歷所有程式(包括隱藏程式)

下一篇

  控制程式碼表篇——總結與提升

相關文章