JIURL玩玩Win2k进程线程篇 TEB
JIURL玩玩Win2k进程线程篇 TEB
作者: JIURL
日期: 2003-7-30
TEB,Thread Environment Block,线程环境块。位于用户地址空间。在比 PEB 所在地址低的地方,比如 0x7FFDF000,0x7FFDE000。每个线程都有自己的一个 TEB。由于 TEB 在用户地址空间,所以本进程中运行在用户模式下的代码就可以访问 TEB 结构。Win2k Build 2195 中一个线程的 ETHREAD 结构偏移 +020 处的 *Teb 指向这个线程的 TEB 结构。在 undocumented.ntinternals.net (需要注意的是这是个非官方的站点)我们可以找到 TEB 及其相关结构的定义。从 kd 中也可以找到一些相关结构的定义。我们首先列出结构的定义,然后对一些内容进行说明。
// 来自 undocumented.ntinternals.net
typedef struct _TEB {
NT_TIB Tib;
PVOID EnvironmentPointer;
CLIENT_ID Cid;
PVOID ActiveRpcInfo;
PVOID ThreadLocalStoragePointer;
PPEB Peb;
ULONG LastErrorValue;
ULONG CountOfOwnedCriticalSections;
PVOID CsrClientThread;
PVOID Win32ThreadInfo;
ULONG Win32ClientInfo[0x1F];
PVOID WOW32Reserved;
ULONG CurrentLocale;
ULONG FpSoftwareStatusRegister;
PVOID SystemReserved1[0x36];
PVOID Spare1;
ULONG ExceptionCode;
ULONG SpareBytes1[0x28];
PVOID SystemReserved2[0xA];
ULONG GdiRgn;
ULONG GdiPen;
ULONG GdiBrush;
CLIENT_ID RealClientId;
PVOID GdiCachedProcessHandle;
ULONG GdiClientPID;
ULONG GdiClientTID;
PVOID GdiThreadLocaleInfo;
PVOID UserReserved[5];
PVOID GlDispatchTable[0x118];
ULONG GlReserved1[0x1A];
PVOID GlReserved2;
PVOID GlSectionInfo;
PVOID GlSection;
PVOID GlTable;
PVOID GlCurrentRC;
PVOID GlContext;
NTSTATUS LastStatusValue;
UNICODE_STRING StaticUnicodeString;
WCHAR StaticUnicodeBuffer[0x105];
PVOID DeallocationStack;
PVOID TlsSlots[0x40];
LIST_ENTRY TlsLinks;
PVOID Vdm;
PVOID ReservedForNtRpc;
PVOID DbgSsReserved[0x2];
ULONG HardErrorDisabled;
PVOID Instrumentation[0x10];
PVOID WinSockData;
ULONG GdiBatchCount;
ULONG Spare2;
ULONG Spare3;
ULONG Spare4;
PVOID ReservedForOle;
ULONG WaitingOnLoaderLock;
PVOID StackCommit;
PVOID StackCommitMax;
PVOID StackReserved;
} TEB, *PTEB;
// 来自 kd
struct _NT_TIB (sizeof=28)
+00 struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList
+04 void *StackBase
+08 void *StackLimit
+0c void *SubSystemTib
+10 void *FiberData
+10 uint32 Version
+14 void *ArbitraryUserPointer
+18 struct _NT_TIB *Self
struct _CLIENT_ID (sizeof=8)
+0 void *UniqueProcess
+4 void *UniqueThread
struct _EXCEPTION_REGISTRATION_RECORD (sizeof=8)
+0 struct _EXCEPTION_REGISTRATION_RECORD *Next
+4 function *Handler
struct _UNICODE_STRING (sizeof=8)
+0 uint16 Length
+2 uint16 MaximumLength
+4 uint16 *Buffer
异常处理链
struct _TEB
struct _NT_TIB (sizeof=28)
+00 struct _EXCEPTION_REGISTRATION_RECORD *ExceptionList
指向结构化异常处理(SEH)链的指针。
线程用户模式下的堆栈
struct _TEB
struct _NT_TIB (sizeof=28)
+04 void *StackBase
+08 void *StackLimit
一个线程,有两个自己的堆栈(Stack)。一个是内核模式下的堆栈,一个是用户模式下的堆栈。当线程在内核模式,也就是 ring0 下,执行代码的时候,使用的是内核模式堆栈。当线程在用户模式下,也就是 ring3 下,执行代码的时候,使用的是用户模式堆栈。某些只在内核模式运行的线程没有用户模式堆栈,比如 System 进程(PID为8的进程)的一些线程。
一个线程的用户模式堆栈,位于用户地址空间。线程 TEB 偏移 +04 处的 StackBase 是该线程用户模式堆栈的最高地址,也就是开始地址,堆栈是向下增长的。线程 TEB 偏移 +08 处的 StackLimit 是该线程用户模式堆栈的最低地址(有效部分)。
线程的内核模式堆栈的信息在线程 ETHREAD 结构中。
FS 段
在系统的许多函数的汇编代码中我们进程可以看到使用 fs 段。对于 x86 来说,分段机制是默认,并且必须使用的。Win2k 使用了平坦(Flat)模型,把段设为整个4G地址空间,隐藏了分段机制。不过 fs 段是一个例外。对于运行在用户模式下,也就是运行在ring3下的程序,fs 段是当前线程的 TEB 所在地址空间。对于运行在内核模式下,也就是运行在ring0下的程序,fs 段是从地址 FFDFF000 开始,大小为 0x2000 的那部分地址空间。
下面我们使用 SoftICE 分别观察在 ring3 执行代码的 FS段 和在 ring0 执行代码的 FS段。
ring3
在ring3执行某一时刻的段寄存器和全局描述符表
:r -d
CS:EIP=001B:00401919 SS:ESP=0023:0012FE20
EAX=00000001 EBX=7FFDF000 ECX=0012FFB0 EDX=00040000
ESI=0012FE20 EDI=0012FF80 EBP=0012FF80 EFL=00000246
DS=0023 ES=0023 FS=0038 GS=0000
注意 CS 为 1B ,说明 CPL 为 ring3。注意 FS 段选择符。
:gdt
Sel. Type Base Limit DPL Attributes
GDTbase=80036000 Limit=03FF
0008 Code32 00000000 FFFFFFFF 0 P RE
0010 Data32 00000000 FFFFFFFF 0 P RW
001B Code32 00000000 FFFFFFFF 3 P RE
0023 Data32 00000000 FFFFFFFF 3 P RW
0028 TSS32 801F4000 000020AB 0 P B
0030 Data32 FFDFF000 00001FFF 0 P RW
003B Data32 7FFDE000 00000FFF 3 P RW
// FS 对应的段描述符,Base=7FFDE000 DPL=3
// 当前的线程的 TEB 就在 7FFDE000 开始处的 4KB 地址空间中。
0043 Data16 00000400 0000FFFF 3 P RW
0048 Reserved 00000000 00000000 0 NP
0050 TSS32 80470040 00000068 0 P
...
ring0
刚才的ring3程序进行系统调用,产生了 int 2e 中断。当转到中断2e的中断处理程序时,CPU 以及转换了堆栈段,代码段。中断2e的中断处理程序会把原来的fs段选择符压栈,将fs段选择符赋值为30。
这时的段寄存器和全局描述符表
// 注意 CS 和 SS ,CPL 已经是 ring0 了。注意 FS 值为30。
:r -d
CS:EIP=0008:804615DD SS:ESP=0010:EF0B5DB4
EAX=00000038 EBX=00000030 ECX=80002000 EDX=0012FD9C
ESI=00000000 EDI=0012FF80 EBP=0012FDF8 EFL=00000002
DS=0023 ES=0023 FS=0030 GS=0000
:gdt
Sel. Type Base Limit DPL Attributes
GDTbase=80036000 Limit=03FF
0008 Code32 00000000 FFFFFFFF 0 P RE
0010 Data32 00000000 FFFFFFFF 0 P RW
001B Code32 00000000 FFFFFFFF 3 P RE
0023 Data32 00000000 FFFFFFFF 3 P RW
0028 TSS32 801F4000 000020AB 0 P B
0030 Data32 FFDFF000 00001FFF 0 P RW
// FS 对应的段描述符,Base=FFDFF000 DPL=0
003B Data32 7FFDE000 00000FFF 3 P RW
...
用户模式下的 FS 段是当前线程的 TEB。
内核模式下的 FS 段,是 FFDFF000 开始的8KB(通常只有4KB映射了物理内存)地址空间。它的内容是和当前线程有关的一些信息。其中
偏移+00 处的4个字节是内核模式下 EXCEPTION_REGISTRATION_RECORD *ExceptionList 。
偏移+04 处的4个字节,偏移+08 处的4个字节,是和线程内核堆栈有关的信息。
偏移+124 处的4个字节,是指向当前线程的 ETHREAD 结构的指针。
注意 FFDFF000 开始的这段地址空间中是当前线程的有关信息,对于不同的线程,这段地址空间中的内容也是不一样。
为了方便观察某个进程地址空间中内容,我写了一个叫 JiurlProcessMemSee 的程序,可以获得指定进程地址空间中的内容。
欢迎交流,欢迎交朋友,