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 的程序,可以获得指定进程地址空间中的内容。
欢迎交流,欢迎交朋友, 欢迎访问 http://jiurl.yeah.net http://jiurl.cosoft.org.cn/forum
下载 JiurlProcessMemSee 可执行文件及源程序
|