Windows 中 FS 段寄存器 V2
[ 注意:本文是以前文章《 Windows中 FS段寄存器 》的修订版。 ]
代码运行在 RING0 (系统地址空间)和 RING3 (用户地址空间)时, FS 段寄存器分别指向 GDT( 全局描述符表 ) 中不同段:在 RING3 下, FS 段值是 0x3B (这是 WindowsXP 下值;在 Windows2000 下值为 0x38 。差别就是在 XP 下 RPL=3 );运行在 RING0 下时, FS 段寄存器值是 0x30 。下面以 XP 为例说说。
一. RING3 下的 FS
当代码运行在 Ring3 下时, FS 值为指向的段是 GDT 中的 0x38 段( RPL 为 3 )。该段的长度为 4K ,基地址为 当前线程 的线程环境块( TEB ),所以该段也被称为“ TEB 段”。
WINXPSP1 及以前的 Windows2000 等系统中,进程环境块( PEB )的地址固定为 0X7FFDF000 ,该进程的第一个线程的 TEB 地址为 0X7FFDE000 ,第二个 TEB 的地址为 0X7FFDD000….. 但是自从 WindowsXP SP2 开始 PEB 和 TEB 的地址都是随机映射的 ( 详见博文: MiCreatePebOrTeb函数注释 ) 。
下图是 WindowsXP SP3 下的 TEB 结构 ( 大小为 0XFB8) :
nt!_TEB +0x000 NtTib : _NT_TIB +0x000 ExceptionList : Ptr32 +0x004 StackBase : Ptr32 +0x008 StackLimit : Ptr32 +0x00c SubSystemTib : Ptr32 +0x010 FiberData : Ptr32 +0x010 Version : Uint4B +0x014 ArbitraryUserPointer : Ptr32 +0x018 Self : Ptr32 < —— +0x01c EnvironmentPointer : Ptr32 +0x020 ClientId : _CLIENT_ID +0x000 UniqueProcess : Ptr32 +0x004 UniqueThread : Ptr32 +0x028 ActiveRpcHandle : Ptr32 +0x02c ThreadLocalStoragePointer : Ptr32 +0x030 ProcessEnvironmentBlock : Ptr32 < —— +0x034 LastErrorValue : Uint4B +0x038 CountOfOwnedCriticalSections : Uint4B +0x03c CsrClientThread : Ptr32 +0x040 Win32ThreadInfo : Ptr32 ……
|
………… 8086dd6c 8b4b40 mov ecx,dword ptr [ebx+40h] 8086dd6f 894104 mov dword ptr [ecx+4],eax 8086dd72 8b6628 mov esp,dword ptr [esi+28h] 8086dd75 8b4620 mov eax,dword ptr [esi+20h] // 这两条指令将新线程的TEB 保存在KPRC 8086dd78 894318 mov dword ptr [ebx+18h],eax // 的0X18 中 8086dd7b fb sti 8086dd7c 8b4744 mov eax,dword ptr [edi+44h] 8086dd7f 3b4644 cmp eax,dword ptr [esi+44h] 8086dd82 c6475000 mov byte ptr [edi+50h],0 8086dd86 7440 je nt!SwapContext+0xe8 (8086ddc8) 8086dd88 8b7e44 mov edi,dword ptr [esi+44h] 8086dd8b 8b4b48 mov ecx,dword ptr [ebx+48h] 8086dd8e 314834 xor dword ptr [eax+34h],ecx 8086dd91 314f34 xor dword ptr [edi+34h],ecx 8086dd94 66f74720ffff test word ptr [edi+20h],0FFFFh 8086dd9a 7571 jne nt!SwapContext+0x12d (8086de0d) 8086dd9c 33c0 xor eax,eax 8086dd9e 0f00d0 lldt ax 8086dda1 8d8b40050000 lea ecx,[ebx+540h] 8086dda7 e850afffff call nt!KeReleaseQueuedSpinLockFromDpcLevel (80868cfc) 8086ddac 33c0 xor eax,eax 8086ddae 8ee8 mov gs,ax 8086ddb0 8b4718 mov eax,dword ptr [edi+18h] 8086ddb3 8b6b40 mov ebp,dword ptr [ebx+40h] 8086ddb6 8b4f30 mov ecx,dword ptr [edi+30h] 8086ddb9 89451c mov dword ptr [ebp+1Ch],eax 8086ddbc 0f22d8 mov cr3,eax 8086ddbf 66894d66 mov word ptr [ebp+66h],cx 8086ddc3 eb0e jmp nt!SwapContext+0xf3 (8086ddd3) 8086ddc5 8d4900 lea ecx,[ecx] 8086ddc8 8d8b40050000 lea ecx,[ebx+540h] 8086ddce e829afffff call nt!KeReleaseQueuedSpinLockFromDpcLevel (80868cfc) 8086ddd3 8b4318 mov eax,dword ptr [ebx+18h] // 这几句就是将新线程的TEB 的地址 8086ddd6 8b4b3c mov ecx,dword ptr [ebx+3Ch] // 更新到GDT 的0X38 描述符的基地址 8086ddd9 6689413a mov word ptr [ecx+3Ah],ax // 中去 。 8086dddd c1e810 shr eax,10h // 8086dde0 88413c mov byte ptr [ecx+3Ch],al // 8086dde3 88613f mov byte ptr [ecx+3Fh],ah // 8086dde6 ff464c inc dword ptr [esi+4Ch] ......... |
二. RING0 下的 FS
当线程运行在 Ring0 下时, FS 指向的段是 GDT 中的 0x30 段。该段的长度也为 4K ,基地址为 0xFFDFF000 (我的 P4 单核 XPSP3 下除了 0FFDFF000 外还会有其它值,不是是何原因?)。该地址指向系统的处理器控制区域( KPCR )。这个区域中保存这处理器相关的一些重要数据值,如 GDT 、 IDT 表的值等等(关于通过 KPCR 获得系统一些重要变量可看博文 Windows XP 内核变量 )。下面就是 WindowsXP sp3 中的 KPCR 数据结构:
nt!_KPCR +0x000 NtTib : _NT_TIB +0x000 ExceptionList : Ptr32 +0x004 StackBase : Ptr32 +0x008 StackLimit : Ptr32 +0x00c SubSystemTib : Ptr32 +0x010 FiberData : Ptr32 +0x010 Version : Uint4B +0x014 ArbitraryUserPointer : Ptr32 +0x018 Self : Ptr32 < ---- +0x01c SelfPcr : Ptr32 < ----- +0x020 Prcb : Ptr32 +0x024 Irql : UChar +0x028 IRR : Uint4B +0x02c IrrActive : Uint4B +0x030 IDR : Uint4B +0x034 KdVersionBlock : Ptr32 +0x038 IDT : Ptr32 +0x03c GDT : Ptr32 +0x040 TSS : Ptr32 +0x044 MajorVersion : Uint2B …………… |
看两个地址 0x18 和 0x1C 。在 TEB 中 0x18 指向自己,即 TEB 。而 KPCR 中指向自己的确是 0x1C ; 0x18 却是指向当前线程的 TEB ,所以 0x18 字段名叫做 Self-used 比较确切( WIN2K 源码如此定义)。 总之,不管是在 RING3 还是 RING0 , FS:[0x18] 总是指向当前线程的 TEB 。
三. RING0 与 RING3 之间的变换
RING0 和 RING3 之间的变换通常是发生在系统调用与返回时,关于系统调用,可参看博文 WINDOWS系统调用 和 SYSENTER系统服务调用过程 。
FS 在 RING0 和 RING3 中是不同的值,在 KiFastCallEntry / KiSystemService 中 FS 值由 0x3B 变成 0x30 ;在 KiSystemCallExit / KiSystemCallExitBranch / KiSystemCallExit2 中再将 RING3 的 FS 恢复。
下面来看看 KiSystemService 的开头部分代码 (KiFastCallEntry 也是一样 ) :
nt!KiSystemService: ………… |
再看看下面的 KiSystemCallExit 部分代码:
………… |