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

繼續學習windows 中和線程有關係的數據結構: ETHREAD、KTHREAD、TEBjavascript

 

1. 相關閱讀材料html

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

 

 

 

2. 數據結構分析算法

咱們知道,windows內核中的執行體層負責各類與管理和策略相關的功能,而內核層(微內核)實現了操做系統的核心機制。進程和線程在這兩層上都有對應的數據結構。
咱們先從執行體層的ETHREAD開始。

 

一.  ETHREAD編程

ETHREAD(執行體線程塊)是執行體層上的線程對象的數據結構。在windows內核中,每一個進程的每個線程都對應着一個ETHREAD數據結構。接下來,咱們以windows XP下的notepad.exe爲實驗材料進行學習,winDbg的雙機調試和winDbg的命令使用請參閱(學習筆記(2))。windows

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的結構體定義代碼:api

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 Tcbsass

如同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放到下一篇中繼續學習

相關文章
相關標籤/搜索