8.1 TEB與PEB概述

在開始使用TEB/PEB獲取進程或線程ID之前,我想有必要解釋一下這兩個名詞,PEB指的是進程環境塊(Process Environment Block),用於存儲進程狀態信息和進程所需的各種數據。每個進程都有一個對應的PEB結構體。TEB指的是線程環境塊(Thread Environment Block),用於存儲線程狀態信息和線程所需的各種數據。每個線程同樣都有一個對應的TEB結構體。

PEB中包含了進程的代碼、數據段指針、進程的環境變量、進程啓動參數信息以及加載的dll信息等。PEB結構體中的FS段寄存器通常被設置爲0x30,指向當前進程的PEB結構體。其他進程可以通過訪問自己的PEB結構體來獲取自己的狀態和信息。

TEB中包含了線程的堆棧指針、TLS(線程本地存儲)指針、異常處理鏈表指針、用戶模式分頁表指針等信息。TEB中的FS段寄存器通常被設置爲fs:[0],指向當前線程的TEB結構體。其他線程可以通過訪問自己的TEB結構體來獲取自己的狀態和信息。

在創建進程時,操作系統會爲每個線程分配一個TEB(線程環境塊),並且該環境塊中FS段寄存器總是被設置爲fs:[0]的位置上,也就是默認指向當前線程的TEB數據。因此,在進行代碼分析時,可以通過通配符來找到TEB結構體的具體名稱。

接着我們可通過dt -rv ntdll!_TEB命令,查詢ntdll!_TEB結構,如下圖所示,我們可以看到偏移爲+0x018的位置就是TEB結構頭指針,在該地址基礎上向下偏移0x30就可以得到PEB(進程環境塊)的基地址。

其中+0x000的位置指向了NT_TIB結構,+0x018指向NT_TIB結構TEB自身,+0x020+0x000指向的是當前進程的PID,+0x020+0x004則指向父進程的PPID,+0x030指向了PEB結構體,其他字段讀者可自行查閱官方文檔;

接着再來驗證一下,首先偏移地址0x18是TEB結構基地址,也就是指向自身偏移fs:[0x18]的位置,而!teb地址加0x30正是PEB的位置,在teb的基礎上加上0x30就可以得到PEB的基地址,拿到PEB基地址就可以幹很多事了。

在線程環境塊內,fs:[0x18]定位到TEB(線程環境塊),加上0x20得到ClientId,此處存儲的就是進程與線程ID的結構位置,通過+0x000可得到UniqueProcess也就是進程PID,通過+0x004可得到UniqueThread也就是線程TID,讀者可輸入如下所示的命令自行驗證;

0:000> dd fs:[0x18]                                  # 找到TEB基地址
0053:00000018  0081e000 00000000 0000139c 0000194c
0053:00000028  00000000 0081e02c 0081b000 00000000

0:000> dt _teb 0081e000 
ntdll!_TEB
   +0x000 NtTib            : _NT_TIB
   +0x01c EnvironmentPointer : (null) 
   +0x020 ClientId         : _CLIENT_ID              # 將TEB+0x20定位到這裏(進程與線程信息)
   +0x028 ActiveRpcHandle  : (null) 

0:004> dt _CLIENT_ID 0081e000                        # 查看進程詳細結構
ntdll!_CLIENT_ID
   +0x000 UniqueProcess    : 0x00b3f774 Void         # 獲取進程PID
   +0x004 UniqueThread     : 0x00b40000 Void         # 獲取線程TID

有了上述分析結果那麼讀者可以很容易的獲取到當前自身進程的PID以及TID信息,其完整代碼片段如下所示,當讀者向GetSelfID()傳入1則表示讀取PID,否則則讀取TID信息;

#include <stdio.h>
#include <Windows.h>

// 傳入1獲取PID傳入0獲取TID
DWORD GetSelfID(DWORD isPid)
{
    DWORD ref = 0;

    if (isPid == 1)
    {
        __asm
        {
            mov eax, fs:[0x18]    // 獲取到PEB基地址
            add eax, 0x20         // 加上20得到 _CLIENT_ID
            add eax, 0x0          // 加上偏移0得到 UniqueProcess
            mov eax, [eax]        // 取出內存地址中的值
            mov ref, eax
        }
    }
    else
    {
        __asm
        {
            mov eax, fs:[0x18]   // 獲取到PEB基地址
            add eax, 0x20        // 加上20得到 _CLIENT_ID
            add eax, 0x04        // 加上偏移04得到 UniqueThread
            mov eax, [eax]       // 取出內存地址中的值
            mov ref, eax
        }
    }
    return ref;
}

int main(int argc, char* argv[])
{
    printf("進程 Pid = %d \n", GetSelfID(1));
    printf("線程 Tid = %d \n", GetSelfID(0));

    system("pause");
    return 0;
}

讀者可運行上述代碼,並自行打開任務管理器驗證是否可以正確獲取到,此處執行效果圖如下所示;

本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/f60a0f6f.html
版權聲明: 本博客所有文章除特別聲明外,均採用 BY-NC-SA 許可協議。轉載請註明出處!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章