ETHREAD APC 《寒江獨釣》核心學習筆記(4)

Andrew.Hann發表於2013-12-03

繼續學習windows 中和執行緒有關係的資料結構: ETHREAD、KTHREAD、TEB

 

1. 相關閱讀材料

《windows 核心原理與實現》 --- 潘愛民

 

 

 

2. 資料結構分析

我們知道,windows核心中的執行體層負責各種與管理和策略相關的功能,而核心層(微核心)實現了作業系統的核心機制。程式和執行緒在這兩層上都有對應的資料結構。
我們先從執行體層的ETHREAD開始。

 

一.  ETHREAD

ETHREAD(執行體執行緒塊)是執行體層上的執行緒物件的資料結構。在windows核心中,每個程式的每一個執行緒都對應著一個ETHREAD資料結構。接下來,我們以windows XP下的notepad.exe為實驗材料進行學習,winDbg的雙機除錯和winDbg的命令使用請參閱(學習筆記(2))。

kd> dt _ethread 80553740  
ntdll!_ETHREAD
   +0x000 Tcb              : _KTHREAD
   +0x1c0 CreateTime       : _LARGE_INTEGER 0x0
   +0x1c0 NestedFaultCount : 0y00
   +0x1c0 ApcNeeded        : 0y0
   +0x1c8 ExitTime         : _LARGE_INTEGER 0x0
   +0x1c8 LpcReplyChain    : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x1c8 KeyedWaitChain   : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x1d0 ExitStatus       : 0
   +0x1d0 OfsChain         : (null) 
   +0x1d4 PostBlockList    : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x1dc TerminationPort  : (null) 
   +0x1dc ReaperLink       : (null) 
   +0x1dc KeyedWaitValue   : (null) 
   +0x1e0 ActiveTimerListLock : 0
   +0x1e4 ActiveTimerListHead : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x1ec Cid              : _CLIENT_ID
   +0x1f4 LpcReplySemaphore : _KSEMAPHORE
   +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
   +0x208 LpcReplyMessage  : (null) 
   +0x208 LpcWaitingOnPort : (null) 
   +0x20c ImpersonationInfo : (null) 
   +0x210 IrpList          : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x218 TopLevelIrp      : 0
   +0x21c DeviceToVerify   : (null) 
   +0x220 ThreadsProcess   : (null) 
   +0x224 StartAddress     : (null) 
   +0x228 Win32StartAddress : (null) 
   +0x228 LpcReceivedMessageId : 0
   +0x22c ThreadListEntry  : _LIST_ENTRY [ 0x0 - 0x0 ]
   +0x234 RundownProtect   : _EX_RUNDOWN_REF
   +0x238 ThreadLock       : _EX_PUSH_LOCK
   +0x23c LpcReplyMessageId : 0
   +0x240 ReadClusterSize  : 0
   +0x244 GrantedAccess    : 0x1f03ff
   +0x248 CrossThreadFlags : 0
   +0x248 Terminated       : 0y0
   +0x248 DeadThread       : 0y0
   +0x248 HideFromDebugger : 0y0
   +0x248 ActiveImpersonationInfo : 0y0
   +0x248 SystemThread     : 0y0
   +0x248 HardErrorsAreDisabled : 0y0
   +0x248 BreakOnTermination : 0y0
   +0x248 SkipCreationMsg  : 0y0
   +0x248 SkipTerminationMsg : 0y0
   +0x24c SameThreadPassiveFlags : 0
   +0x24c ActiveExWorker   : 0y0
   +0x24c ExWorkerCanWaitUser : 0y0
   +0x24c MemoryMaker      : 0y0
   +0x250 SameThreadApcFlags : 0
   +0x250 LpcReceivedMsgIdValid : 0y0
   +0x250 LpcExitThreadCalled : 0y0
   +0x250 AddressSpaceOwner : 0y0
   +0x254 ForwardClusterOnly : 0 ''
   +0x255 DisablePageFaultClustering : 0 ''

在windows的"開源"專案WRK中,我們也可以找到ETHREAD的結構體定義程式碼:

typedef struct _ETHREAD 
{
    KTHREAD Tcb;

    LARGE_INTEGER CreateTime;

    union 
    {
        LARGE_INTEGER ExitTime;
        LIST_ENTRY LpcReplyChain;
        LIST_ENTRY KeyedWaitChain;
    };
    union 
    {
        NTSTATUS ExitStatus;
        PVOID OfsChain;
    };
 
    LIST_ENTRY PostBlockList; 

    union 
    {  
        PTERMINATION_PORT TerminationPort; 

        struct _ETHREAD *ReaperLink; 

        PVOID KeyedWaitValue; 
    };

    KSPIN_LOCK ActiveTimerListLock;
    LIST_ENTRY ActiveTimerListHead;

    CLIENT_ID Cid;
 
    union 
    {
        KSEMAPHORE LpcReplySemaphore;
        KSEMAPHORE KeyedWaitSemaphore;
    };

    union 
    {
        PVOID LpcReplyMessage;          // -> Message that contains the reply
        PVOID LpcWaitingOnPort;
    }; 

    PPS_IMPERSONATION_INFORMATION ImpersonationInfo; 

    LIST_ENTRY IrpList; 

    ULONG_PTR TopLevelIrp;  // either NULL, an Irp or a flag defined in FsRtl.h
    struct _DEVICE_OBJECT *DeviceToVerify;

    PEPROCESS ThreadsProcess;
    PVOID StartAddress;
    union {
        PVOID Win32StartAddress;
        ULONG LpcReceivedMessageId;
    }; 

    LIST_ENTRY ThreadListEntry; 

    EX_RUNDOWN_REF RundownProtect;
 
    EX_PUSH_LOCK ThreadLock;

    ULONG LpcReplyMessageId;    // MessageId this thread is waiting for reply to

    ULONG ReadClusterSize; 

    ACCESS_MASK GrantedAccess;    

    union 
    { 
        ULONG CrossThreadFlags; 

        struct 
    {
            ULONG Terminated              : 1;
            ULONG DeadThread              : 1;
            ULONG HideFromDebugger        : 1;
            ULONG ActiveImpersonationInfo : 1;
            ULONG SystemThread            : 1;
            ULONG HardErrorsAreDisabled   : 1;
            ULONG BreakOnTermination      : 1;
            ULONG SkipCreationMsg         : 1;
            ULONG SkipTerminationMsg      : 1;
        };
    }; 

    union 
    {
        ULONG SameThreadPassiveFlags;

        struct 
    { 
            ULONG ActiveExWorker : 1;
            ULONG ExWorkerCanWaitUser : 1;
            ULONG MemoryMaker : 1; 
            ULONG KeyedEventInUse : 1;
        };
    };
  
    union
    {
        ULONG SameThreadApcFlags;
        struct 
    {  
            BOOLEAN LpcReceivedMsgIdValid : 1;
            BOOLEAN LpcExitThreadCalled   : 1;
            BOOLEAN AddressSpaceOwner     : 1;
            BOOLEAN OwnsProcessWorkingSetExclusive  : 1;
            BOOLEAN OwnsProcessWorkingSetShared     : 1;
            BOOLEAN OwnsSystemWorkingSetExclusive   : 1;
            BOOLEAN OwnsSystemWorkingSetShared      : 1;
            BOOLEAN OwnsSessionWorkingSetExclusive  : 1;
            BOOLEAN OwnsSessionWorkingSetShared     : 1; 

            BOOLEAN ApcNeeded                       : 1;
        };
    };

    BOOLEAN ForwardClusterOnly;
    BOOLEAN DisablePageFaultClustering;
    UCHAR ActiveFaultCount; 
} ETHREAD, *PETHREAD;

我們現在來一條一條地學習這個資料結構的域成員.

 

1. KTHREAD Tcb

如同EPROCESS結構包含了核心層的KPROCESS物件一樣,ETHREAD結構也"內嵌(注意是內嵌,不是指向)"了KTHREAD物件作為第一個資料成員,所以,一個指向ETHREAD物件的指標同時也是一個指向KTHREAD物件的指標。關於核心層的KTHREAD結構我們放到後面介紹,我們集中精力學習ETHREAD的結構。

+0x000 Tcb              : _KTHREAD

 

2. LARGE_INTEGER CreateTime

CreateTime域包含餓了執行緒的建立時間,它是線上程建立時被賦值的。我們要明白的是,我們在工作管理員中之所以能看那麼多的效能引數,"計劃任務"的執行,包括執行緒的"飢餓演算法"的排程,很大程度上是因為線上程和程式的資料結構中儲存了大量一些基礎引數。

 

 

3. LARGE_INTEGER ExitTime

ExitTime域包含了執行緒的退出時間。它是線上程退出函式中被賦值的:

VOID ExitThread(DWORD dwExitCode);

我們回想在KPROCESS中的KernelTimeUserTime域成員。我們現在知道了,只有線上程退出時,在賦值ExitTime的同時還會給當前執行緒所屬的程式的KernelTimeUserTime域進行"更新"。

 

4. LPC(跨程式通訊)

LIST_ENTRY LpcReplyChain;
LIST_ENTRY KeyedWaitChain;

 LpcReplyChain域用於跨程式通訊(LPC),KeyedWaitChain域用於帶鍵事件的等待連結串列,這裡對LPC的相關知識做一下擴充。

4.1)  本地過程呼叫(LPC Local Procedure Call),有時也稱為Lightweight Procdure Call。

(回想還有多少種其他的程式間通訊機制)

1) 互斥體(mutex)
2) 訊號量(samaphore)
3) 鎖(lock)
4) 臨界區(critical section)
5) 自旋鎖(spinlock)和忙等待
6) 訊息

//關於程式/執行緒間的"通訊"機制的詳細內容請參考《windows 核心原理與實現》5.1 章
(其實同步機制也算通訊機制的一種,只不過側重點不同,同步機制有其他更好的方法)

 

4.2) 那LPC有什麼特點呢?

LPC是一種直接由核心支援的程式間通訊機制,它非常高效,主要用於作業系統各個"元件"之間進行通訊,或者使用者模式程式與系統元件之間通訊。

LPC涉及兩方面通訊,其基本工作方式是訊息傳遞。一個程式建立一個"LPC埠物件(注意這些名詞,第一步一定是先建立一個埠物件,之後會解釋它們的關係)",然後等待其他程式連線過來。當其他程式成功地連線到一個LPC埠之後,兩者便可以開始通訊。LPC允許兩個程式進行雙向通訊。

它在windows中的主要應用場景如下:

1) windows應用程式與系統程式,包括windows環境子系統之間的通訊。這通常發生在一些windows API函式的內部
2) 使用者模式程式與核心模式元件之間的通訊。比如lsass(Local Security Authority SubSystem)程式與安全引用監視器(SRM Security Reference Monitor)之間的通訊就是通過LPC
來完成的
3) 當RPC(遠過程呼叫 Remote Procedure Call)的"兩端(LPC是一個C/S模型)"在同一個系統中時,RPC通訊會自動轉化為LPC

回想我們之前學習EPROCESS資料結構的時候曾經說過有兩個成員域:

PVOID DebugPort;
PVOID ExceptionPort; 

分別為除錯埠和異常埠,它們有機會接收和處理該程式中的異常,包括除錯事件。這裡的異常埠正是LPC埠,windows子系統通過此埠可以獲取程式中發生的異常。

 

4.3) LPC結構模型(通訊建立過程)

通過LPC進行通訊的兩個程式本質上是客戶-伺服器模型,其工作方式與基於連線的socket程式設計模型相仿(只不過socket是通過傳送TCP/UDP資料包的方式來進行通訊,而LPC是核心中建立埠進行通訊)。

1. 伺服器程式建立一個LPC"連線埠"物件,然後在該"連線埠"上監聽連線請求。

2. 客戶程式根據已知的埠名稱,請求連線到此埠物件上

3. 當伺服器收到連線請求時,它建立一個"通訊埠"物件,用以代表與"該客戶程式(一個LPC的服務端可以同時和多個客戶端互動)"之間的LPC連線

4. 而客戶程式也會建立一個LPC"通訊埠"物件,代表它與伺服器程式之間的LPC連線

5. 連線埠物件有名稱,通訊埠物件沒有名稱,它們是私有物件(這也很好理解,通訊埠是臨時的,而連線埠必須常在),所以,除了伺服器程式和客戶程式通過"通訊埠控制程式碼"來訪問它們
以外,其他程式是無法訪問的(思考原因: 因為控制程式碼是程式私有的,同一個控制程式碼值在不同程式空間是不同的,所以只能本程式來使用這個通訊埠控制程式碼)
6. LPC連線埠物件屬於伺服器程式,它們有公開的名稱,通常被加入到系統的"物件管理器目錄"中,所以,其他程式可以通過"名稱"訪問它們,從而建立起單獨的LPC連線。

LPC伺服器程式可以同時連線多個客戶,每個客戶通過名稱連線到伺服器的連線埠物件,而伺服器為每個接受的客戶程式建立一個通訊埠物件。所以說,LPC是一對多的通訊模型。LPC的連線埠物件是一箇中心的連線點,它只接受連線請求,不接受資料請求(思考FTP的21號埠是不是同樣的思想),而通訊埠可以進行任意的資料請求和服務(思考FTP的20號資料埠是不是同樣的思想)。這是連線埠和通訊埠職責的區別。

(更多內容請參閱《windows 核心原理與實現》 8.2章)

 

 

 

5. NTSTATUS ExitStatus

ExitStatus是執行緒的退出狀態。當執行緒主動退出或被動退出時這個域會由框架程式碼填充,回想EPROCESS的LastThreadExitStatus域,程式中的每個執行緒退出時,除了給自己的ETHREAD的ExitStatus賦值以外,還會給當前執行緒所屬的程式的EPROCESS的LastThreadExitStatus進行賦值。

 

6. LIST_ENTRY PostBlockList

PostBlockList域是一個雙連結串列頭節點,該連結串列中的各個節點型別為PCM_POST_BLOCK,它被用於一個執行緒向"配置管理器"登記登錄檔鍵的變化通知。

typedef struct _CM_POST_BLOCK 
{ 
#if DBG 
    BOOLEAN                     TraceIntoDebugger; 
#endif 
    LIST_ENTRY                  NotifyList; 
    LIST_ENTRY                  ThreadList; 
    LIST_ENTRY                  CancelPostList; // slave notifications that are attached to this notification 
    struct _CM_POST_KEY_BODY    *PostKeyBody; 
    ULONG                       NotifyType; 
    PCM_POST_BLOCK_UNION        u; 
} CM_POST_BLOCK, *PCM_POST_BLOCK; 

 

7. PTERMINATION_PORT TerminationPort

TerminationPort域是一個連結串列頭,當一個執行緒退出時,系統會通知所有已經登記過要接收其終止事件的那些"埠"

 

8. struct _ETHREAD *ReaperLink

ReaperLink域是一個單連結串列節點,它僅線上程退出時使用。當執行緒被終止時,該節點將被掛到PsReaperListHead連結串列上(用以告知核心當前執行緒將要退出了,請收到相關的執行緒資源),所以,線上程回收器(reaper)的工作專案(WorkItem)中該執行緒的核心棧得以收回。

 

9. 執行緒的定時器

KSPIN_LOCK ActiveTimerListLock;
LIST_ENTRY ActiveTimerListHead;
ActiveTimerListHead域是一個雙連結串列的頭,連結串列中包含了當前執行緒的所有定時器。
ActiveTimerListHead域操作這個連結串列(包含當前執行緒的所有定時器的雙連結串列)的自旋鎖。使用自旋鎖可以把原本可能發生的並行事件導致的問題通過強制序列化得到解決。比如對執行緒中的定時器這個互斥
資源就典型的需要序列化,否則將導致定時器的錯亂等很多問題

 

10. CLIENT_ID Cid

Cid域是一個常用的域,其中包含了執行緒的"唯一識別符號",其型別為CLIENT_ID

typedef struct _CLIENT_ID
{
     PVOID UniqueProcess;
     PVOID UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
CLIENT_ID包括兩部分: 
UniqueProcess: 等於所屬程式的UniqueProcessId
UniqueThread: 等於此執行緒物件在程式控制程式碼表中的控制程式碼
(每個核心物件都是以控制程式碼的形式在其他核心物件的控制程式碼表中存在的,關於核心物件及其控制程式碼的定義請參閱《windows 核心程式設計》 3章 核心物件。  關於控制程式碼表的相關知識請參閱學習筆記(2))

 

 

11. LPC通訊相關

union 
{
     KSEMAPHORE LpcReplySemaphore;
     KSEMAPHORE KeyedWaitSemaphore;
};

union 
{
     LPC_MESSAGE LpcReplyMessage;          // -> Message that contains the reply
     PVOID LpcWaitingOnPort;
}; 

這兩個union域域LPC有關。我們合併學習

1) LpcReplySemaphore域用於LPC應答通知
2) KeyedWaitSemaphore域用於處理帶鍵的事件
typedef struct _KSEMAPHORE
{
     DISPATCHER_HEADER Header;
     LONG Limit;
} KSEMAPHORE, *PKSEMAPHORE;
3) LpcReplyMessage域是一個指向LPCP_MESSAGE的指標,其中包含了LPC應答的訊息
//Structure LPC_MESSAGE it's a header for all LPC messages. Any LPC message must contains at least 0x18 bytes length for LPC_MESSAGE header. 
typedef struct _LPC_MESSAGE 
{
  USHORT                  DataLength;
  USHORT                  Length;
  USHORT                  MessageType;
  USHORT                  DataInfoOffset;
  CLIENT_ID               ClientId;
  ULONG                   MessageId;
  ULONG                   CallbackId;
} LPC_MESSAGE, *PLPC_MESSAGE;
4) LpcWaitingOnPort
LpcWaitingOnPort說明了當前執行緒在哪個"埠物件"上等待訊息

LpcReplyMessageLpcWaitingOnPort兩個域雖然公用同一個union空間,但是其最低位可用來區分應該用哪個,在WRK的 \base\ntos\lpc\lpcp.h中有兩個巨集專門用來進行此判斷
#define LpcpGetThreadMessage(T)                                                  \
    (                                                                            \
        (((ULONG_PTR)(T)->LpcReplyMessage) & LPCP_PORT_BIT) ? NULL :             \
            (PLPCP_MESSAGE)((ULONG_PTR)(T)->LpcReplyMessage & ~LPCP_THREAD_ATTRIBUTES)      \
    )

#define LpcpGetThreadPort(T)                                                     \
    (                                                                            \
        (((ULONG_PTR)(T)->LpcReplyMessage) & LPCP_PORT_BIT) ?                    \
            (PLPCP_PORT_OBJECT)(((ULONG_PTR)(T)->LpcWaitingOnPort) & ~LPCP_THREAD_ATTRIBUTES):     \
            NULL                                                                 \
    )

關於LPC的,在《windows 核心原理與實現》 8.2節 有更詳細的介紹

 

 

12. PPS_IMPERSONATION_INFORMATION ImpersonationInfo

ImpersonationInfo域指向執行緒的模仿資訊,windows允許一個執行緒在執行過程中模仿其他的使用者來執行一段功能,這樣可以實現更為靈活的訪問控制安全特性。關於這個欄位,我沒有找到很多相關的資料。

http://blogs.ejb.cc/archives/6955/windows-internal-quest-thread

文章中提到:

ImpersonationInfo  欄位: 儲存了身份模仿資訊的指標(訪問令牌和模仿級別,如果該執行緒正在模仿一個客戶的話)

我自己理解的是是不是有點類似在javascript程式設計中很常見的那種情況,模擬使用者點選,移動等事件的那種思路。具體的我也不是太清除,希望知道的朋友能給出連結,共同學習

 

13. LIST_ENTRY IrpList

IrpList域是一個雙連結串列頭,其中包含了當前執行緒所有正在處理但尚未完成的I/O請求(Irp物件)。關於IRP的相關知識請參閱學習筆記(1)

 

14. ULONG_PTR TopLevelIrp

TopLevelIrp域指向執行緒的頂級IRP,它有三種可能的取值:

1) 指向NULL
2) 指向一個IRP
3) 包含了fsrtl.h中定義的標記FSRTL_FAST_IO_TOP_LEVEL_IRP或FSRTL_FSP_TOP_LEVEL_IRP

僅當一個執行緒的I/O呼叫層次中最頂層的元件是檔案系統時(核心中裝置棧的概念),TopLevelIrp域才指向當前執行緒傳送的IRP

 

15. struct _DEVICE_OBJECT *DeviceToVerify

DeviceToVerify域指向的是一個"待檢驗"的裝置,當磁碟或CD-ROM裝置的驅動程式"發現"自從上一次該執行緒訪問該裝置以來,該裝置有了"變化",就會設定執行緒的DeviceToVerify域,從而使最高層的驅動程式(比如檔案系統),可以檢測到裝置變化

DeviceToVerify指向的是一個裝置物件:

typedef struct _DEVICE_OBJECT
{
     SHORT Type;
     WORD Size;
     LONG ReferenceCount;
     PDRIVER_OBJECT DriverObject;
     PDEVICE_OBJECT NextDevice;
     PDEVICE_OBJECT AttachedDevice;
     PIRP CurrentIrp;
     PIO_TIMER Timer;
     ULONG Flags;
     ULONG Characteristics;
     PVPB Vpb;
     PVOID DeviceExtension;
     ULONG DeviceType;
     CHAR StackSize;
     BYTE Queue[40];
     ULONG AlignmentRequirement;
     KDEVICE_QUEUE DeviceQueue;
     KDPC Dpc;
     ULONG ActiveThreadCount;
     PVOID SecurityDescriptor;
     KEVENT DeviceLock;
     WORD SectorSize;
     WORD Spare1;
     PDEVOBJ_EXTENSION DeviceObjectExtension;
     PVOID Reserved;
} DEVICE_OBJECT, *PDEVICE_OBJECT;

 

 

16. PEPROCESS ThreadsProcess

ThreadsProcess域指向當前執行緒所屬的程式的EPROCESS結構,這是線上程初始建立時賦值的,因為每個執行緒都是從一個程式中建立來的,利用這個欄位可以很方便的從一個執行緒定址到它所屬的程式。

base\ntos\inc\ps.h 中的巨集 THREAD_TO_PROCESS 就是專門用來實現此功能的。

#define THREAD_TO_PROCESS(Thread) ((Thread)->ThreadsProcess)

回想一下,在KPROCESS和EPROCESS中也都有有一個成員域: ThreadListHead用來指向程式對應的執行緒的。我們可以發現,windows中的程式和執行緒結構是互相繫結的。要互相定址非常方便。

1) EPROCESS->ThreadListHead: 指向當前程式所屬執行緒的ETHREAD連結串列

2) KPROCESS->ThreadListHead: 指向當前程式所屬執行緒的KTHREAD連結串列

3) ETHREAD->ThreadsProcess:  指向當前執行緒所屬的程式的EPROCESS連結串列

4) KTHREAD->Process: 指向當前執行緒所屬的程式的KPROCESS連結串列

 

 

17.  PVOID StartAddress

StartAddress域包含了執行緒的啟動地址,這是真正的執行緒啟動地址,即入口地址。也就是我們在建立執行緒的之後指定的入口函式的地址。

 

19. PVOID Win32StartAddress

Win32StartAddress域包含的是windows子系統接收到的執行緒啟動地址,即CreateThread API函式接收到的執行緒啟動地址。那這就有一個問題了,這個Win32StartAddress和我們之前學的StartAddress有什麼區別呢?

 StartAddress域包含的通常是系統DLL中的執行緒啟動地址,因而往往是相同的(例如kernel32.dll中的BaseProcessStart或BaseThreadStart函式)。而Win32StartAddress域中包含的才真正是windows子系統接收到的執行緒啟動地址,即CreateThread中指定的那個函式入口地址。

HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes, //執行緒安全屬性
    DWORD dwStackSize, // 堆疊大小
    LPTHREAD_START_ROUTINE lpStartAddress, // 執行緒函式,即入口地址
    LPVOID lpParameter, //執行緒引數
    DWORD dwCreationFlags, // 執行緒建立屬性
    LPDWORD lpThreadId // 執行緒ID
);

所以,Win32StartAddress不僅對於windows子系統有意義,對於診斷和分析不同執行緒的行為更有重要的意義

 

20. ULONG LpcReceivedMessageId

LpcReceivedMessageId域包含了接收到的LPC訊息的ID,此域僅當SameThreadPassiveFlags(後面會介紹)域中的LpcReceivedMesIdValid位被置上時候才有效。

//要注意的是:
由於LpcReceivedMessageId和Win32StartAddress域組成了一個union:
union
{
        PVOID Win32StartAddress;
        ULONG LpcReceivedMessageId;
}; 

所以,當一個windows子系統執行緒在接收到LPC訊息時,它的Win32StartAddress域也會被修改

 

 

21. LIST_ENTRY ThreadListEntry

ThreadListEntry是一個雙連結串列節點,每個執行緒都會加入到它所屬的EPROCESS結構的ThreadListHead雙連結串列中(仔細讀這句話,執行緒和程式的從屬關係一定要搞清楚)。我們可以看到,windows通過資料結構很好的維護了程式和執行緒之間的從屬關係

(可以想到可以利用這個域成員來進行程式中的執行緒列舉,我們會在之後的程式/執行緒列舉學習筆記中看到)

 

22. EX_RUNDOWN_REF RundownProtect

RundownProtect域是執行緒的停止保護鎖,對於跨執行緒引用TEB結構或者掛起執行緒的執行等操作,需要獲得此鎖才能執行,以避免在操作過程中執行緒被銷燬。

typedef struct _EX_RUNDOWN_REF
{
     union
     {
          ULONG Count;
          PVOID Ptr;
     };
} EX_RUNDOWN_REF, *PEX_RUNDOWN_REF;

 

23. EX_PUSH_LOCK ThreadLock

ThreadLock域是一把推鎖,使用者保護執行緒的資料屬性,例如PspLockThreadSecurityExclusive和PspLockThreadSecurityShared利用該域來保護執行緒的安全屬性

typedef struct _EX_PUSH_LOCK
{
     union
     {
          ULONG Locked: 1;
          ULONG Waiting: 1;
          ULONG Waking: 1;
          ULONG MultipleShared: 1;
          ULONG Shared: 28;
          ULONG Value;
          PVOID Ptr;
     };
} EX_PUSH_LOCK, *PEX_PUSH_LOCK;

 

24. ULONG LpcReplyMessageId

LpcReplyMessageId域指明瞭當前執行緒正在等待對一個LPC訊息的應答。

 

25. ULONG ReadClusterSize

ReadClusterSize域指明瞭在一次I/O操作中讀取多少個頁面,用於頁面交換檔案和記憶體對映檔案的讀操作。

關於這個頁面交換檔案或者記憶體對映檔案我的理解都是使用了"記憶體對映技術",就是把磁碟上的檔案直接對映到虛擬記憶體中,這樣CPU就可以直接對記憶體資料進行讀寫,"記憶體對映技術"可以大大提高效率,並且在windows中應該算很常見的了。

我在學習《寒江獨釣》8章 -- 檔案系統透明加密中,就會涉及到這個notepad.exe的記憶體對映以及快取的知識,希望看這篇文章的朋友也能動手自己去編碼實驗一下,對記憶體對映檔案一定會有
一個更好的體會。

 

26. ACCESS_MASK GrantedAccess

GrantedAccess域包含了執行緒的訪問許可權,這裡的訪問許可權是一個"位組合",各種許可權定義的巨集以 bit 位的形式 OR到一起. public\sdk\inc\ntpsapi.h 中的巨集 THREAD_XXX定義了執行緒的許可權

#define THREAD_TERMINATE               (0x0001)  // winnt
// end_ntddk end_wdm end_ntifs
#define THREAD_SUSPEND_RESUME          (0x0002)  // winnt
#define THREAD_ALERT                   (0x0004)
#define THREAD_GET_CONTEXT             (0x0008)  // winnt
#define THREAD_SET_CONTEXT             (0x0010)  // winnt
// begin_ntddk begin_wdm begin_ntifs
#define THREAD_SET_INFORMATION         (0x0020)  // winnt
// end_ntddk end_wdm end_ntifs
#define THREAD_QUERY_INFORMATION       (0x0040)  // winnt
// begin_winnt
#define THREAD_SET_THREAD_TOKEN        (0x0080)
#define THREAD_IMPERSONATE             (0x0100)
#define THREAD_DIRECT_IMPERSONATION    (0x0200)
// begin_ntddk begin_wdm begin_ntifs

#define THREAD_ALL_ACCESS         (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | \
                                   0x3FF)

這裡我們回想一下在EPROCESS中也有一個GrantedAccess域表名了程式的許可權:

#define PROCESS_TERMINATE         (0x0001)  // winnt
#define PROCESS_CREATE_THREAD     (0x0002)  // winnt
#define PROCESS_SET_SESSIONID     (0x0004)  // winnt
#define PROCESS_VM_OPERATION      (0x0008)  // winnt
#define PROCESS_VM_READ           (0x0010)  // winnt
#define PROCESS_VM_WRITE          (0x0020)  // winnt
// begin_ntddk begin_wdm begin_ntifs
#define PROCESS_DUP_HANDLE        (0x0040)  // winnt
// end_ntddk end_wdm end_ntifs
#define PROCESS_CREATE_PROCESS    (0x0080)  // winnt
#define PROCESS_SET_QUOTA         (0x0100)  // winnt
#define PROCESS_SET_INFORMATION   (0x0200)  // winnt
#define PROCESS_QUERY_INFORMATION (0x0400)  // winnt
#define PROCESS_SET_PORT          (0x0800)
#define PROCESS_SUSPEND_RESUME    (0x0800)  // winnt

// begin_winnt begin_ntddk begin_wdm begin_ntifs
#define PROCESS_ALL_ACCESS        (STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | \
                                   0xFFF)

我們可以發現程式和執行緒在某種程度上來說具有一定的"對稱性"

 

 

27. 執行緒的位標識

接下來合併學習三組unino:

union 
    { 
        ULONG CrossThreadFlags; 

        struct 
      {
            ULONG Terminated              : 1;
            ULONG DeadThread              : 1;
            ULONG HideFromDebugger        : 1;
            ULONG ActiveImpersonationInfo : 1;
            ULONG SystemThread            : 1;
            ULONG HardErrorsAreDisabled   : 1;
            ULONG BreakOnTermination      : 1;
            ULONG SkipCreationMsg         : 1;
            ULONG SkipTerminationMsg      : 1;
        };
    }; 

    union 
    {
        ULONG SameThreadPassiveFlags;

        struct 
      { 
            ULONG ActiveExWorker : 1;
            ULONG ExWorkerCanWaitUser : 1;
            ULONG MemoryMaker : 1; 
            ULONG KeyedEventInUse : 1;
        };
    };
  
    union
    {
        ULONG SameThreadApcFlags;
        struct 
      {  
            BOOLEAN LpcReceivedMsgIdValid : 1;
            BOOLEAN LpcExitThreadCalled   : 1;
            BOOLEAN AddressSpaceOwner     : 1;
            BOOLEAN OwnsProcessWorkingSetExclusive  : 1;
            BOOLEAN OwnsProcessWorkingSetShared     : 1;
            BOOLEAN OwnsSystemWorkingSetExclusive   : 1;
            BOOLEAN OwnsSystemWorkingSetShared      : 1;
            BOOLEAN OwnsSessionWorkingSetExclusive  : 1;
            BOOLEAN OwnsSessionWorkingSetShared     : 1; 

            BOOLEAN ApcNeeded                       : 1;
        };
    };

 

1) ULONG CrossThreadFlags

CrossThreadFlags域是一些針對跨執行緒訪問的標誌位,可以看到結構體中的 ":1"這種結構,這是C中的特有的寫法,代表這個欄位只佔 1bit位。

所以,CrossThreadFlags標誌位包含:

Terminated: 執行緒已終止操作
DeadThread: 執行緒建立失敗
HideFromDebugger: 該執行緒對於偵錯程式不可見
ActiveImpersonationInfo: 執行緒正在模仿
SystemThread: 是一個系統執行緒
HardErrorsAreDisabled: 對於該執行緒,硬體錯誤無效
BreakOnTermination: 偵錯程式線上程終止時停下該執行緒
SkipCreationMsg: 不向偵錯程式傳送建立訊息
SkipTerminationMsg: 不向偵錯程式傳送終止訊息

 

2) ULONG SameThreadPassiveFlags

SameThreadPassiveFlags域是一些只有在最低中斷級別(被動級別)上才可以訪問的標誌,並且只能被該執行緒自身訪問,所以對這些標誌位的訪問不需要互鎖操作

 

3) ULONG SameThreadApcFlags

SameThreadApcFlags域是一些在APC中斷級別(也是很低的級別)上被該執行緒自身訪問的標誌位,同樣地,對這些標誌位的訪問也不需要互鎖操作

關於APC的知識屬於windows中斷與異常相關方面的知識,這裡做一些擴充,更多詳細內容請參閱《windows 核心原理與實現》 5.2節

APC:

要說APC,首先要介紹一些windows中的中斷的知識。

1) CPU硬體中斷的優先順序順序

在Intel x86體系結構中,外部硬體中斷是通過處理器上的"中斷管腳"或者一個稱為"本地APIC(local APIC)"的內建模組來發生的。本地APIC可以接收的中斷源包括:

1) 處理器管腳(LINT0和LINT1)
2) 本地APIC定時器(timer)
3) 效能監視計數器中斷
4) 熱感測器中斷
5) APIC內部錯誤中斷

//這些中斷源都稱為本地中斷源,另外還有通過I/O APIC轉送過來的中斷源: 外部連線的I/O裝置的中斷訊息和IPI訊息
Ps: "中斷"屬於"異常"範疇中的一種,我們在學習的時候要注意歸類這些概念,對我們整體把握windows的機制有很大好處,建議參考《深入理解計算機系統(原書第2版)》 8.2章異常的相關知識
我們這裡介紹就只是中斷,即"異常"大範疇的一個子類,我通過查閱資料發現嚴格意義的"中斷"就是指的硬體中斷,只要是計算機中的的硬體產生的請求都叫"中斷",我自己是這樣總結的,
如有不對,歡迎指正,不吝感激。

而APIC這個硬體中斷裝置是有優先順序的,它使用了一個可程式設計陣列硬體來實現,並且在系統初始化的時候就完成了,Intel x86定義了256箇中斷向量號(Interrupt Vector Number),也稱為中斷向量,從0~255。這裡我們可以把知識聯立起來了,IDT是個中斷描述符表,IDT的表項數目就是256,也就是說,APIC定義的是IDT中的中斷例程的優先順序

 

2) 對於一個處理器,它一旦被中斷(可能來自內部,可能來自外部,可能是硬中斷,可能是軟中斷),則某個預設的"中斷服務例程"便被執行。而系統軟體(作業系統)要做的事情就是,提供這些例程,並將它們設定到處理器的硬體中斷向量表(即IDT)中。

結合我們之前學習的IDT方面的知識,我們可以這樣理解:
1. IDT本身可以放線上性地址空間的任何地方,但基地址應該8位元組對齊(在學習筆記(3)中對IDT有詳細介紹)。 2. 我們是可以把IDT放在任意地方,但是要告知系統它存在在哪裡,所以這個IDT的基址儲存在IDTR中 3. 可以通過LIDT和SIDT指令分別用於載入和儲存IDTR暫存器(LIDT只有在特權級0下才可以使用)

 

3) 中斷請求級別(IRQL)

儘管APIC中斷控制器已經提供了中斷優先順序支援,不過,windows還是自己定義了一套優先順序方案,稱為"中斷請求級別(IRPL Interrupt Request Level)"。在Intel x86系統中,windows使用了0~31來表示優先順序,數值越大,優先順序越高(聯想程式/執行緒的優先順序是不是也是這樣?)。

軟體中斷後非中斷程式碼的IRQL是在核心中管理的,而硬體中斷則在HAL中被對映到對應的IRQL。IRQL的巨集定義如下

#define PASSIVE_LEVEL 0        //Passive release level(被動級別)
#define LOW_LEVEL 0        //Lowest interrupt level
#define APC_LEVEL 1        //APC interrupt level
#define DISPATCH_LEVEL 2    //Dispatcher level
#define PROFILE_LEVEL 27    //Timer used for profiling
#define CLOCK1_LEVEL 28        //Interval clock 1 level
#define CLOCK2_LEVEL 28        //Interval clock 2 level
#define IPI_LEVEL 29        //Interprocessor interrupt level
#define POWER_LEVEL 30        //Power failure level
#define HIGH_LEVEL 31        //Highest interrupt level

對於這個IRQL,我們要這麼理解,它就相當於一種"重要程度"。PASSIVE_LEVEL(被動級別)代表了最低的IRQL,那既然你最低,執行在PASSIVE_LEVEL的執行緒可以被任何更高的IRQL(從LOW_LEVEL開始都可以)的事情打斷,所有的的使用者模式程式碼都執行在PASSIVE_LEVEL(被動模式)上。

Ps: 這裡插個題外話,我們使用的APC程式注入的原理也在於此,APC_LEVEL(APC級別)比PASSIVE_LEVEL高,這也正是在一個執行緒中插入一個APC可以打斷該執行緒(如果被插入的這個執行緒正在
PASSIVE_LEVEL上執行)的原因

 

4) APC(非同步過程呼叫)

我們對IRQL中的APC_LEVEL進行一下重點學習

它位於PASSIVE_LEVEL和DISPATCH_LEVEL之間,這是"專門"為另一種稱為APC(非同步過程呼叫 Asynchronous Procedure Call)的"軟體中斷"而保留的IRQL。每個APC都是在特定的執行緒環境中執行的,從而也一定在特定的程式環境中執行。APC是針對執行緒的,每個執行緒都有自己特有的APC連結串列。同一個執行緒的APC也是被排隊執行的(思考執行緒的IRP對清佇列,我們的IO請求要被排隊執行,APC作為IRP請求的一種也不例外)。

由於APC的IRQL高於PASSIVE_LEVEL,所以,它優先於普通的"執行緒程式碼"。當一個執行緒獲得控制時,它的APC程式會立刻被執行(即執行APC佇列中的IRP請求)。這一特性使得APC非常適合於實現各種非同步通知事件。例如,I/O完成通知可以用APC來實現。

(同樣,我們可以在程式的建立完成IO回撥中插入APC事件,達到APC程式注入的目的)
//詳細細節參考sudami的經典文章《N種核心注入DLL的思路及實現》
http://www.pediy.com/kssd/pediy10/75887.html

一下是APC的核心物件(稱為APC物件)的資料結構定義:

typedef struct _KAPC
{
     UCHAR Type;
     UCHAR SpareByte0;
     UCHAR Size;
     UCHAR SpareByte1;
     ULONG SpareLong0;
     PKTHREAD Thread;
     LIST_ENTRY ApcListEntry;
     PVOID KernelRoutine;
     PVOID RundownRoutine;
     PVOID NormalRoutine;
     PVOID NormalContext;
     PVOID SystemArgument1;
     PVOID SystemArgument2;
     CHAR ApcStateIndex;
     CHAR ApcMode;
     UCHAR Inserted;
} KAPC, *PKAPC;

更多細節請參閱《windows 核心原理與實現》 5.2.6 APC(非同步過程呼叫)

 

 

 

28. 執行緒的錯誤處理相關

BOOLEAN ForwardClusterOnly;
BOOLEAN DisablePageFaultClustering;
UCHAR ActiveFaultCount; 

最後三個域成員合併學習,它們與執行緒中發生的頁面錯誤處理有關。

ForwardClusterOnly: 指示是否僅僅前向聚集
DisablePageFaultClustering: 用於控制頁面交換的聚集與否
ActiveFaultCount: 包含了正在進行之中的頁面錯誤數量



至此,ETHREAD的資料結構就學習完成了。限於篇幅的原因,我們把KPROCESS和TEB放到下一篇中繼續學習

相關文章