Windows系统调用学习笔记(三)—— 保存现场
要点回顾
API进入0环后调用的函数:
- 中断门 – KiSystemService
- 快速调用 – KiFastCallEntry
上一篇留了几个练习:
- 进0环后,原来的寄存器存在哪里?
- 如何根据系统服务号(eax中存储)找到要执行的内核函数?
- 调用时参数是存储到3环的堆栈,如何传递给内核函数?
- 2种调用方式是如何返回到3环的?
本篇将对第一个练习进行说明
基本概念
Trap Frame 结构
描述:
- 无论是通过中断门进入0环,还是通过快速调用进入0环,进入0环前(3环)的所有寄存器都会存到这个结构体中
- 这个结构体本身处于0环,由windows操作系统进行维护
- 当程序通过中断门从3环进入0环时,ESP指向TrapFrame+0x64的位置
- 当程序通过快速调用从3环进入0环时,ESP指向TrapFrame+0x78的位置
结构体:
在WinDbg中查看:
kd> dt _Ktrap_frame
注意:
5. 在保护模式下,最后四个成员(0x7C~0x88)并没有被使用,因此无需考虑;只有在虚拟8086模式下,才会用到
6. 当中断门执行时,3环的SS、ESP、EFLAGS、CS、EIP会被存储到结构体的0x68~0x78中,而执行快速调用时不会
线程相关的结构体
ETHREAD
结构体:
KTHREAD
结构体:
kd> dt _KTHREAD
CPU相关的结构体
KPCR
描述:
- 全称为CPU控制区(Processor Control Region)
- 每一个CPU都有一个CPU控制区,一核一个KPCR
结构体:
查看CPU数量:
kd>dd KeNumBerProcessors
查看KPCR:
kd>dd KiProcessorBlock L2
ffdff120 00000000
若第二个成员有值,说明当前CPU有两个核
_NT_TIB
描述:
_NT_TIB是KPCR结构体的成员之一
结构体:
KPRCB
描述:
KPRCB是KPCR结构体的成员之一
结构体:
kd> dt _KPRCB
实验一:分析 KiSystemService
注意:当进入KiSystemService时,3环的SS
、ESP
、EFLAGS
、CS
、EIP
就已经被存储到 Trap Frame 结构体中
IDA反汇编:
.text:004067D1 push 0 ; nt!_KTRAP_FRAME
; +0x064 ErrCode : Uint4B
; 中断门产生权限切换时一般往堆栈中压入5个值,但有些情况会压入6个值,第六个值为Error Code
; 具体细节可以参考Intel白皮书第三卷,章节名:Input and Exception Handling
; 其中有一小节叫做 Error Code
;
; 当通过 INT 2E 进入0环时,并没有压入Error Code
; 操作系统为了对齐,自己补了个0
.text:004067D3 push ebp ; nt!_KTRAP_FRAME
; +0x060 Ebp : Uint4B
.text:004067D4 push ebx ; +0x05c Ebx : Uint4B
.text:004067D5 push esi ; +0x058 Esi : Uint4B
.text:004067D6 push edi ; +0x054 Edi : Uint4B
.text:004067D7 push fs ; +0x050 SegFs : Uint4B
.text:004067D9 mov ebx, 30h ; 为FS寄存器赋值,指向KPCR结构体
.text:004067DE mov fs, ebx ; 加载下标为6的段描述符到fs
在WinDbg中查看GDT表中下标为6的段描述符:
段描述符:ff
c093df
`f000
0001
fs.Base:0xffdff000(KPCR)
.text:004067E0 push large dword ptr fs:0 ; 保存老的ExceptionList(异常列表)
; nt!_KPCR
; +0x000 NtTib : _NT_TIB
; nt!_NT_TIB
; +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
;
; nt!_KTRAP_FRAME
; +0x04c ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
.text:004067E7 mov large dword ptr fs:0, 0FFFFFFFFh ; 新的ExceptionList为空白
.text:004067F2 mov esi, large fs:124h ; 得到当前正在执行的线程信息
; nt!_KPCR
; +0x120 +0x4 CurrentThread : Ptr32 _KTHREAD
; 存储了当前正在跑的线程的信息,为 KTHREAD 结构体
在WinDbg中查看 KPCR + 0x120 + 0x4:
.text:004067F9 push dword ptr [esi+140h] ; 保存老的"先前模式"到堆栈
; nt!_KTHREAD
; +0x140 PreviousMode : Char
.text:004067FF sub esp, 48h ; 执行前,ESP位于_KTRAP_FRAME + 0x48 = PreviousPreviousMode
; 执行后,ESP 等于 _KTRAP_FRAME 结构指针
.text:00406802 mov ebx, [esp+68h+arg_0] ; arg_0 = 4
; 取出三环压入的参数CS(_KTRAP_FRAME + 0x6C)
.text:00406806 and ebx, 1 ; 权限检查:0环最低位为0,3环最低位为1
.text:00406809 mov [esi+140h], bl ; 新的"先前模式"
; 将权限检查的结果存储到 PreviousMode 中
; 目的是检测调用该代码前,程序处于几环
.text:0040680F mov ebp, esp ; ebp = esp = _KTRAP_FRAME 结构指针
.text:00406811 mov ebx, [esi+134h] ; _KTHREAD 的成员 TrapFrame
.text:00406817 mov [ebp+3Ch], ebx ; 将 _KTHREAD 中的 TrapFrame 暂时存放在这个位置
; 之后会将这个值重新取出来,赋值给 _KTHREAD 的 TrapFrame
.text:0040681A mov [esi+134h], ebp ; 将堆栈中形成的 _KTRAP_FRAME 结构指针赋值给 _KTHREAD 的 TrapFrame
.text:00406820 cld
.text:00406821 mov ebx, [ebp+60h] ; 将原来3环的ebp赋值给ebx
.text:00406824 mov edi, [ebp+68h] ; 将原来3环的eip赋值给edi
.text:00406827 mov [ebp+0Ch], edx ; edx存储的是3环参数的指针:
;
; _KiFasSystemCall函数:
; mov edx, esp
; sysenter
;
.text:0040682A mov dword ptr [ebp+8], 0BADB0D00h ; 给操作系统用的标志
.text:00406831 mov [ebp+0], ebx ; 原来3环的ebp存储到 _KTRAP_FRAME
; + 0x000 DbgEbp 的位置
.text:00406834 mov [ebp+4], edi ; 原来3环的eip存储到 _KTRAP_FRAME
; + 0x004 DbgEbp 的位置
.text:00406837 test byte ptr [esi+2Ch], 0FFh ; 判断 _KTHREAD + 0x02c DebugActive 是否为 -1
; 若为 -1, 即当前线程 未 处于调试状态
.text:0040683B jnz Dr_kss_a ; 若处于调试状态,则跳转
; 功能是对TrapFrame的Dr0~Dr7进行赋值
; 也就是TrapFrame+0x18 ~ TrapFrame+0x2c的位置进行赋值
; 若未处于调试状态,则继续向下执行(loc_406841)
在WinDbg中查看KTHREAD + 0x02c:
.text:00406841 sti ; 关闭中断
.text:00406842 jmp loc_406932 ; KiSystemService 与 KiFastCallEntry 函数的共同代码片段
; 下篇学习系统服务表时再进行分析
实验二:分析 KiFastCallEntry
IDA反汇编:
.text:0040689F mov ecx, 23h ; ECX = 0x23
.text:004068A4 push 30h ; 通过堆栈的入栈和出栈加载fs
.text:004068A6 pop fs ; 加载fs段寄存器:GDT表下标为6的段描述符
在WinDbg中查看GDT表下标为6的段描述符:
段描述符:ff
c093df
`f000
0001
fs.Base:0xffdff000(KPCR)
.text:004068A8 mov ds, ecx ; RPL=3;TI=0;INDEX=4
; 加载GDT表下标为4的段描述符给ds
.text:004068AA mov es, ecx ; 加载GDT表下标为4的段描述符给es
在WinDbg中查看GDT表下标为4的段描述符:
.text:004068AC mov ecx, large fs:40h ; ECX = TSS指针
; nt!_KPCR
; +0x040 TSS : Ptr32 _KTSS
.text:004068B3 mov esp, [ecx+4] ; nt!_KTSS
; +0x004 Esp0 : Uint4B
.text:004068B6 push 23h ; nt!_KTRAP_FRAME
; +0x078 HardwareSegSs : Uint4B
.text:004068B8 push edx ; edx保存的是3环参数的指针
; nt!_KTRAP_FRAME
; +0x074 HardwareEsp : Uint4B
.text:004068B9 pushf ; 旧的标志寄存器入栈
.text:004068BA
.text:004068BA loc_4068BA: ; CODE XREF: _KiFastCallEntry2+23↑j
.text:004068BA push 2 ; 新的标志寄存器的值入栈
.text:004068BC add edx, 8
.text:004068BF popf ; 新的标志寄存器的值出栈
; EFlags = 2,即第二位置1,其它位清零
.text:004068C0 or [esp+0Ch+var_B], 2
.text:004068C5 push 1Bh ; nt!_KTRAP_FRAME
; +0x06c SegCs : Uint4B
.text:004068C7 push dword ptr ds:0FFDF0304h ; +0x068 Eip : Uint4B
.text:004068CD push 0 ; +0x064 ErrCode : Uint4B
.text:004068CF push ebp ; +0x060 Ebp : Uint4B
.text:004068D0 push ebx ; +0x05c Ebx : Uint4B
.text:004068D1 push esi ; +0x058 Esi : Uint4B
.text:004068D2 push edi ; +0x054 Edi : Uint4B
.text:004068D3 mov ebx, large fs:1Ch ; nt!_KPCR
; +0x01c SelfPcr : Ptr32 _KPCR
; 指向当前 KPCR 结构体本身,目的是方便查找
.text:004068DA push 3Bh ; nt!_KTRAP_FRAME
; +0x050 SegFs : Uint4B
.text:004068DC mov esi, [ebx+124h] ; nt!_KPCR
; +0x120 +0x4 CurrentThread : Ptr32 _KTHREAD
; 存储了当前正在跑的线程的信息,为 KTHREAD 结构体
.text:004068E2 push dword ptr [ebx] ; ebx 保存的是KPCR结构体的地址
; KPCR 第一个成员为结构体 NT_TIB 的指针
; NT_TIB 的第一个值为 ExceptionList(异常列表)
; 这句话的目的是将异常列表保存到TrapFrame结构体中
; nt!_KPCR
; +0x000 NtTib : _NT_TIB
; nt!_NT_TIB
; +0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
;
; nt!_KTRAP_FRAME
; +0x04c ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
.text:004068E4 mov dword ptr [ebx], 0FFFFFFFFh ; 将 ExceptionList(异常列表)置为 -1
.text:004068EA mov ebp, [esi+18h] ; nt!_KTHREAD
; +0x018 InitialStack : Ptr32 Void
.text:004068ED push 1 ; nt!_KTRAP_FRAME
; +0x048 PreviousPreviousMode : Uint4B
; 往栈中压入老的先前模式,1表示先前模式为3环
.text:004068EF sub esp, 48h ; 执行前,ESP位于_KTRAP_FRAME + 0x48 = PreviousPreviousMode
; 执行后,ESP 等于 _KTRAP_FRAME 结构指针
.text:004068F2 sub ebp, 29Ch
.text:004068F8 mov byte ptr [esi+140h], 1 ; 新的先前模式
; nt!_KTHREAD
; +0x140 PreviousMode : Char
.text:004068FF cmp ebp, esp ; 检查 ebp 是否等于 esp,也就是 _KTRAP_FRAME 结构指针
.text:00406901 jnz loc_40686C ; 若不等于,则跳转,进行异常处理
.text:00406907 and dword ptr [ebp+2Ch], 0 ; 修改 TrapFrame 结构体中 Dr7 的值
; nt!_KTRAP_FRAME
; +0x02c Dr7 : Uint4B
.text:0040690B test byte ptr [esi+2Ch], 0FFh ; 检测当前进程是否处于调试状态
; nt!_KTHREAD
; +0x02c DebugActive : UChar
; 若不处于调试状态,则 DebugActive 的值为FF
.text:0040690F mov [esi+134h], ebp ; 将 TrapFrame 结构指针写入当前线程信息中
; nt!_KTHREAD
; +0x134 TrapFrame : Ptr32 _KTRAP_FRAME
.text:00406915 jnz Dr_FastCallDrSave ; 若当前进程处于被调试状态,则跳转,为 Dr0 ~ Dr7 进行赋值
; 若当前进程不处于被调试状态,则继续向下执行
.text:0040691B
.text:0040691B loc_40691B: ; CODE XREF: Dr_FastCallDrSave+10↑j
.text:0040691B ; Dr_FastCallDrSave+7C↑j
.text:0040691B mov ebx, [ebp+60h] ; 从 TrapFrame 结构体中取出 Ebp 到 ebx 寄存器
.text:0040691E mov edi, [ebp+68h] ; 从 TrapFrame 结构体中取出 Eip 到 edi 寄存器
.text:00406921 mov [ebp+0Ch], edx ; nt!_KTRAP_FRAME
; +0x000 DbgEbp : Uint4B
.text:00406924 mov dword ptr [ebp+8], 0BADB0D00h; +0x008 DbgArgMark : Uint4B
.text:0040692B mov [ebp+0], ebx ; +0x000 DbgEbp : Uint4B
.text:0040692E mov [ebp+4], edi ; +0x004 DbgEip : Uint4B
.text:00406931 sti ; 关闭中断
.text:00406932
.text:00406932 loc_406932: ; CODE XREF: _KiBBTUnexpectedRange+18↑j
.text:00406932 ; _KiSystemService+71↑j
; KiSystemService 与 KiFastCallEntry 函数的共同代码部分
; 下篇学习系统服务表时再进行分析
总结
- 当程序通过中断门从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环时,堆栈里的值不一样,走同一条函数会出问题