# 前言
上一篇我们了解了 3 环进 0 环的两种方式。本篇将分析进入 0 环后,操作系统如何保存 3 环的寄存器现场。
API 进入 0 环后调用的函数:
- 中断门:
KiSystemService
- 快速调用:
KiFastCallEntry
# Trap Frame 结构
- 无论是通过中断门还是快速调用进入 0 环,进入 0 环前(3 环)的所有寄存器都会存到这个结构体中。
- 这个结构体本身处于 0 环,由 Windows 操作系统进行维护。
- 当程序通过中断门从 3 环进入 0 环时,ESP 指向
TrapFrame + 0x64 的位置。
- 当程序通过快速调用从 3 环进入 0 环时,ESP 指向
TrapFrame + 0x78 的位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| kd> dt _KTRAP_FRAME ntdll!_KTRAP_FRAME
+0x000 DbgEbp : Uint4B +0x004 DbgEip : Uint4B +0x008 DbgArgMark : Uint4B +0x00c DbgArgPointer : Uint4B +0x010 TempSegCs : Uint4B +0x014 TempEsp : Uint4B +0x018 Dr0 : Uint4B +0x01c Dr1 : Uint4B +0x020 Dr2 : Uint4B +0x024 Dr3 : Uint4B +0x028 Dr6 : Uint4B +0x02c Dr7 : Uint4B +0x030 SegGs : Uint4B +0x034 SegEs : Uint4B +0x038 SegDs : Uint4B +0x03c Edx : Uint4B +0x040 Ecx : Uint4B +0x044 Eax : Uint4B
+0x048 PreviousPreviousMode : Uint4B +0x04c ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x050 SegFs : Uint4B +0x054 Edi : Uint4B +0x058 Esi : Uint4B +0x05c Ebx : Uint4B +0x060 Ebp : Uint4B +0x064 ErrCode : Uint4B
+0x068 Eip : Uint4B +0x06c SegCs : Uint4B +0x070 EFlags : Uint4B
+0x074 HardwareEsp : Uint4B +0x078 HardwareSegSs : Uint4B
+0x07c V86Es : Uint4B +0x080 V86Ds : Uint4B +0x084 V86Fs : Uint4B +0x088 V86Gs : Uint4B
|
注意:
- 在保护模式下,最后四个成员(
0x7C ~ 0x88 )并没有被使用,只有在虚拟 8086 模式下才会用到。
- 当中断门执行时,3 环的 SS、ESP、EFLAGS、CS、EIP 会被硬件自动存储到结构体的
0x68 ~ 0x78 中。执行快速调用时,这些值需要由内核代码手动填充。
# 线程相关的结构体
# ETHREAD(Executive Thread,执行线程)
ETHREAD 块包含线程的状态、调度信息、堆栈和上下文等关键信息。它的第一个成员是 _KTHREAD 结构体。
1 2 3 4 5 6 7 8 9 10 11
| kd> dt _ETHREAD ntdll!_ETHREAD +0x000 Tcb : _KTHREAD +0x1c0 CreateTime : _LARGE_INTEGER +0x1c8 ExitTime : _LARGE_INTEGER +0x1d0 ExitStatus : Int4B +0x1ec Cid : _CLIENT_ID +0x220 ThreadsProcess : Ptr32 _EPROCESS +0x224 StartAddress : Ptr32 Void +0x228 Win32StartAddress : Ptr32 Void
|
# KTHREAD(Kernel Thread,内核线程)
KTHREAD 块包含与内核模式线程相关的信息,如调度、同步和内核态资源的管理。以下列出关键成员:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| kd> dt _KTHREAD ntdll!_KTHREAD +0x000 Header : _DISPATCHER_HEADER +0x018 InitialStack : Ptr32 Void +0x01c StackLimit : Ptr32 Void +0x020 Teb : Ptr32 Void +0x028 KernelStack : Ptr32 Void +0x02c DebugActive : UChar +0x034 ApcState : _KAPC_STATE +0x0e0 ServiceTable : Ptr32 Void +0x134 TrapFrame : Ptr32 _KTRAP_FRAME +0x140 PreviousMode : Char +0x168 StackBase : Ptr32 Void
|
# CPU 相关的结构体
# KPCR(Kernel Processor Control Region,CPU 控制区)
- KPCR 用于存储与当前处理器相关的内核信息,如中断向量表、当前处理器的上下文以及其他调度和处理器状态信息。
- 每一个 CPU 都有一个 KPCR,一核一个。
- FS 段寄存器在 3 环时指向 TEB(Thread Environment Block,线程环境块),在 0 环时指向 KPCR。
1 2 3 4 5 6 7 8 9 10 11
| kd> dt _KPCR nt!_KPCR +0x000 NtTib : _NT_TIB +0x01c SelfPcr : Ptr32 _KPCR +0x020 Prcb : Ptr32 _KPRCB +0x024 Irql : UChar +0x038 IDT : Ptr32 _KIDTENTRY +0x03c GDT : Ptr32 _KGDTENTRY +0x040 TSS : Ptr32 _KTSS +0x051 Number : UChar +0x120 PrcbData : _KPRCB
|
# 查看 CPU 数量
1
| kd> dd KeNumberProcessors L1
|
# 查看 KPCR
1
| kd> dd KiProcessorBlock L2
|
若第二个成员有值,说明当前 CPU 有两个核。
_NT_TIB 是 KPCR 结构体的第一个成员,用于保存线程的栈信息和异常处理信息。
1 2 3 4 5 6
| kd> dt _NT_TIB ntdll!_NT_TIB +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD +0x004 StackBase : Ptr32 Void +0x008 StackLimit : Ptr32 Void +0x018 Self : Ptr32 _NT_TIB
|
# KPRCB(Kernel Processor Control Block,处理器控制块)
KPRCB 是 KPCR 结构体的成员之一,包含当前处理器的状态、CPU 调度器的信息、当前运行线程的信息等。以下列出关键成员:
1 2 3 4 5 6 7 8 9
| kd> dt _KPRCB ntdll!_KPRCB +0x000 MinorVersion : Uint2B +0x002 MajorVersion : Uint2B +0x004 CurrentThread : Ptr32 _KTHREAD +0x008 NextThread : Ptr32 _KTHREAD +0x00c IdleThread : Ptr32 _KTHREAD +0x518 KeSystemCalls : Uint4B
|
# 实验 1:分析 KiSystemService
注意:当进入 KiSystemService 时,3 环的 SS、ESP、EFLAGS、CS、EIP 已经被硬件存储到 Trap Frame 结构体的 0x68 ~ 0x78 位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| .text:004067C1 _KiSystemService proc near
; 中断门产生权限切换时一般向堆栈压入 5 个值 ; 但有些情况会压入 6 个值(第六个为 Error Code) ; 通过 INT 2E 进入 0 环时并没有压入 Error Code,操作系统为了对齐自己补了个 0
; _KTRAP_FRAME + 0x064 ErrCode .text:004067C1 push 0
; _KTRAP_FRAME + 0x060 Ebp .text:004067C3 push ebp
; _KTRAP_FRAME + 0x05c Ebx .text:004067C4 push ebx
; _KTRAP_FRAME + 0x058 Esi .text:004067C5 push esi
; _KTRAP_FRAME + 0x054 Edi .text:004067C6 push edi
; _KTRAP_FRAME + 0x050 SegFs .text:004067C7 push fs
; 为 FS 寄存器赋值,指向 KPCR 结构体 ; 0x30 = 0b00110000 → Index = 6 ; 加载 GDT 表中下标为 6 的段描述符到 FS .text:004067C9 mov ebx, 30h .text:004067CE mov fs, ebx
|
此时可以在 WinDbg 中查看 GDT 表中下标为 6 的段描述符,fs.Base = 0xFFDFF000 (即 KPCR):
继续分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| ; 保存老的 ExceptionList(异常列表) ; KPCR.NtTib.ExceptionList → _KTRAP_FRAME.ExceptionList .text:004067D0 push large dword ptr fs:0
; 将异常列表置为空(0xFFFFFFFF) .text:004067D7 mov large dword ptr fs:0, 0FFFFFFFFh
; 获取当前线程:KPCR + 0x120(PrcbData)+ 0x4(CurrentThread) ; CurrentThread 为 KTHREAD 结构体 .text:004067E2 mov esi, large fs:124h
; 保存老的先前模式(PreviousMode)到堆栈 ; KTHREAD + 0x140 → _KTRAP_FRAME + 0x048 .text:004067E9 push dword ptr [esi+140h]
; 提升堆栈空间:ESP 下移 0x48 字节 ; 执行后 ESP 等于 _KTRAP_FRAME 结构体的头部 .text:004067EF sub esp, 48h
; 取出 3 环压入的 CS(_KTRAP_FRAME + 0x6C) .text:004067F2 mov ebx, [esp+68h+4]
; 权限检查:0 环最低位为 0,3 环最低位为 1 .text:004067F6 and ebx, 1
; 将权限检查的结果存储到 KTHREAD.PreviousMode 中 ; 目的是记录调用该代码前程序处于几环 .text:004067F9 mov [esi+140h], bl
; ebp = esp = _KTRAP_FRAME 结构指针 .text:004067FF mov ebp, esp
; 保存旧的 TrapFrame 指针(KTHREAD + 0x134)到临时位置 .text:00406801 mov ebx, [esi+134h] .text:00406807 mov [ebp+3Ch], ebx
; 将新的 _KTRAP_FRAME 指针赋值给 KTHREAD.TrapFrame .text:0040680A mov [esi+134h], ebp
.text:00406810 cld
; 将 3 环的 ebp 和 eip 存储到 TrapFrame 的 Dbg 字段 .text:00406811 mov ebx, [ebp+60h] ; 3 环 Ebp .text:00406814 mov edi, [ebp+68h] ; 3 环 Eip .text:00406817 mov [ebp+0Ch], edx ; DbgArgPointer(3 环参数指针) .text:0040681A mov dword ptr [ebp+8], 0BADB0D00h ; DbgArgMark .text:00406821 mov [ebp+0], ebx ; DbgEbp .text:00406824 mov [ebp+4], edi ; DbgEip
; 检测当前线程是否处于调试状态 ; KTHREAD + 0x02C DebugActive .text:00406827 test byte ptr [esi+2Ch], 0FFh
; 若处于调试状态,则跳转到 Dr_kss_a 为 Dr0~Dr7 赋值 ; 若不处于调试状态,则继续向下执行 .text:0040682B jnz Dr_kss_a
.text:00406831 loc_406831: ; 开启中断 .text:00406831 sti
; 跳转到 KiSystemService 与 KiFastCallEntry 的共同代码 ; 学习系统服务表时再进行分析 .text:00406832 jmp loc_406922 .text:00406832 _KiSystemService endp
|
# 实验 2:分析 KiFastCallEntry
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
| .text:0040688F _KiFastCallEntry proc near
; 加载段寄存器 .text:0040688F mov ecx, 23h .text:00406894 push 30h .text:00406896 pop fs ; FS → KPCR .text:00406898 mov ds, ecx ; DS → GDT[4] .text:0040689A mov es, ecx ; ES → GDT[4]
; 从 TSS 获取 0 环栈顶 ; KPCR + 0x40 = TSS → TSS + 0x4 = Esp0 .text:0040689C mov ecx, large fs:40h .text:004068A3 mov esp, [ecx+4]
; 手动构造 TrapFrame 中硬件应压入的值 ; +0x078 HardwareSegSs = 0x23(3 环 SS) .text:004068A6 push 23h
; +0x074 HardwareEsp = edx(3 环参数指针 / 栈顶) .text:004068A8 push edx
; 保存旧的 EFLAGS .text:004068A9 pushf
; 清除 EFLAGS 中的敏感标志位 .text:004068AA push 2 ; edx 原本指向 3 环 ESP,+8 跳过两个返回地址,使其指向实际参数 .text:004068AC add edx, 8 .text:004068AF popf
; 设置 IF 位(允许中断) .text:004068B0 or [esp+0Ch-0Bh], 2
; +0x06C SegCs = 0x1B(3 环 CS) .text:004068B5 push 1Bh
; +0x068 Eip = SystemCallReturn(从 KUSER_SHARED_DATA 取得) .text:004068B7 push dword ptr ds:0FFDF0304h
; +0x064 ErrCode = 0 .text:004068BD push 0
; +0x060 Ebp .text:004068BF push ebp
; +0x05C Ebx .text:004068C0 push ebx
; +0x058 Esi .text:004068C1 push esi
; +0x054 Edi .text:004068C2 push edi
; KPCR.SelfPcr → ebx .text:004068C3 mov ebx, large fs:1Ch
; +0x050 SegFs = 0x3B(3 环 FS) .text:004068CA push 3Bh
; 获取当前线程 ; KPCR + 0x120 + 0x4 = CurrentThread(KTHREAD 结构体) .text:004068CC mov esi, [ebx+124h]
; 保存老的异常列表 ; KPCR.NtTib.ExceptionList → _KTRAP_FRAME.ExceptionList .text:004068D2 push dword ptr [ebx]
; 将异常列表置为空 .text:004068D4 mov dword ptr [ebx], 0FFFFFFFFh
; 获取 KTHREAD.InitialStack .text:004068DA mov ebp, [esi+18h]
; 压入老的先前模式(直接设为 1,表示来自 3 环) ; +0x048 PreviousPreviousMode = 1 .text:004068DD push 1
; 提升堆栈空间 .text:004068DF sub esp, 48h
; 计算期望的 TrapFrame 地址 .text:004068E2 sub ebp, 29Ch
; 设置新的 PreviousMode = 1(来自 3 环) .text:004068E8 mov byte ptr [esi+140h], 1
; 检查 ebp 是否等于 esp(验证 TrapFrame 指针一致性) .text:004068EF cmp ebp, esp .text:004068F1 jnz loc_40685C ; 不相等则跳转异常处理
; 清除 TrapFrame 中的 Dr7 .text:004068F7 and dword ptr [ebp+2Ch], 0
; 检测当前线程是否处于调试状态 .text:004068FB test byte ptr [esi+2Ch], 0FFh
; 将 TrapFrame 指针写入 KTHREAD.TrapFrame .text:004068FF mov [esi+134h], ebp
; 若处于调试状态,则跳转为 Dr0~Dr7 赋值 .text:00406905 jnz Dr_FastCallDrSave
.text:0040690B loc_40690B: ; 取出 3 环的 ebp 和 eip 存入 Dbg 字段 .text:0040690B mov ebx, [ebp+60h] .text:0040690E mov edi, [ebp+68h] .text:00406911 mov [ebp+0Ch], edx .text:00406914 mov dword ptr [ebp+8], 0BADB0D00h .text:0040691B mov [ebp+0], ebx ; DbgEbp .text:0040691E mov [ebp+4], edi ; DbgEip
; 开启中断 .text:00406921 sti
; 跳转到共同代码部分(分析系统服务表) .text:00406922 loc_406922: ; ... 共同代码(下篇分析)
|
# 总结
- 当程序通过中断门从 3 环进入 0 环时,ESP 指向 TrapFrame + 0x64 的位置。
- 当程序通过快速调用从 3 环进入 0 环时,ESP 指向 TrapFrame + 0x78 的位置。
- 若通过中断门进入 0 环,在
KiSystemService 函数开始执行时,3 环的 SS、ESP、EFLAGS、CS、EIP 就已经被硬件存储到 TrapFrame 中了。
- TrapFrame 结构体的其它成员由
KiSystemService 和 KiFastCallEntry 通过软件方式赋值。
- 不管是
KiSystemService 还是 KiFastCallEntry ,最终都要执行一部分相同的代码。分为两个函数是因为进入 0 环时堆栈里的值不一样,统一走同一个函数会出问题。
上一篇:系统调用(二)| 3 环进 0 环
下一篇:系统调用(四)| 系统服务表