Windows系统调用学习笔记(三)—— 保存现场

要点回顾

API进入0环后调用的函数:

  1. 中断门 – KiSystemService
  2. 快速调用 – KiFastCallEntry

上一篇留了几个练习:

  1. 进0环后,原来的寄存器存在哪里?
  2. 如何根据系统服务号(eax中存储)找到要执行的内核函数?
  3. 调用时参数是存储到3环的堆栈,如何传递给内核函数?
  4. 2种调用方式是如何返回到3环的?

本篇将对第一个练习进行说明

基本概念

Trap Frame 结构

描述

  1. 无论是通过中断门进入0环,还是通过快速调用进入0环,进入0环前(3环)的所有寄存器都会存到这个结构体中
  2. 这个结构体本身处于0环,由windows操作系统进行维护
  3. 当程序通过中断门从3环进入0环时,ESP指向TrapFrame+0x64的位置
  4. 当程序通过快速调用从3环进入0环时,ESP指向TrapFrame+0x78的位置

结构体
Trap_Frame
在WinDbg中查看:

kd> dt _Ktrap_frame

注意
5. 在保护模式下,最后四个成员(0x7C~0x88)并没有被使用,因此无需考虑;只有在虚拟8086模式下,才会用到
6. 当中断门执行时,3环的SS、ESP、EFLAGS、CS、EIP会被存储到结构体的0x68~0x78中,而执行快速调用时不会

线程相关的结构体

ETHREAD

结构体
ETHREAD

KTHREAD

结构体

kd> dt _KTHREAD

CPU相关的结构体

KPCR

描述

  1. 全称为CPU控制区(Processor Control Region)
  2. 每一个CPU都有一个CPU控制区,一核一个KPCR

结构体
KPCR
查看CPU数量:

kd>dd KeNumBerProcessors

查看KPCR:

kd>dd KiProcessorBlock L2
ffdff120 00000000
若第二个成员有值,说明当前CPU有两个核

_NT_TIB

描述
_NT_TIB是KPCR结构体的成员之一

结构体
_NT_TIB

KPRCB

描述
KPRCB是KPCR结构体的成员之一

结构体

kd> dt _KPRCB

实验一:分析 KiSystemService

注意:当进入KiSystemService时,3环的SSESPEFLAGSCSEIP就已经被存储到 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的段描述符:
fs
段描述符ffc093df`f0000001
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
KPCR
KPRCB

.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
KTHREAD

.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的段描述符:
fs
段描述符ffc093df`f0000001
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的段描述符:
ds

.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 函数的共同代码部分
																	; 下篇学习系统服务表时再进行分析

总结

  1. 当程序通过中断门从3环进入0环时,ESP指向TrapFrame+0x64的位置
  2. 当程序通过快速调用从3环进入0环时,ESP指向TrapFrame+0x78的位置
  3. 若通过中断门进入0环,在KiSystemService函数开始执行时,3环的SSESPEFLAGSCSEIP就已经被存储到 TrapFrame 结构体中了
  4. TrapFrame 结构体的其它成员通过 KiSystemServiceKiFastCallEntry 进行赋值
  5. 不管是 KiSystemService 还是 KiFastCallEntry,最终都要执行一部分相同的代码,分为两个函数是因为进入0环时,堆栈里的值不一样,走同一条函数会出问题
  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Servlet 是 JavaEE 规范中的一部分,是处理 Web 请求的组件。Servlet 运行在服务器端,能够接收客户端发来的请求,并给客户端响应结果。下面我们来看看 Servlet 的基本概念和使用方法。 ## 一、Servlet 的基本概念 ### 1.1 Servlet 的生命周期 Servlet 的生命周期包含以下三个阶段: - 初始化阶段(init):当 Servlet 实例化后,Web 容器会调用其 init() 方法进行初始化操作。在此阶段,Servlet 可以执行一些初始化操作,例如读取配置信息、建立数据库连接等。 - 请求处理阶段(service):当客户端发来请求时,Web 容器会创建一个线程调用 Servlet 的 service() 方法处理请求。在此阶段,Servlet 可以获取请求参数、处理请求并生成响应数据。 - 销毁阶段(destroy):当 Web 应用停止或 Servlet 被卸载时,Web 容器会调用 Servlet 的 destroy() 方法进行清理工作。在此阶段,Servlet 可以释放资源、关闭数据库连接等。 ### 1.2 Servlet 的配置 在使用 Servlet 时,需要在 web.xml 文件中进行配置。以下是一个 Servlet 的基本配置: ```xml <servlet> <servlet-name>MyServlet</servlet-name> <servlet-class>com.example.MyServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>MyServlet</servlet-name> <url-pattern>/myservlet</url-pattern> </servlet-mapping> ``` 其中,servlet-name 表示 Servlet 的名称,servlet-class 表示 Servlet 的类名,url-pattern 表示请求的 URL 匹配规则。 ## 二、Servlet 的使用方法 ### 2.1 编写 Servlet 编写 Servlet 有两种方法:一种是继承 HttpServlet 类,另一种是实现 Servlet 接口。这里以继承 HttpServlet 类为例: ```java public class MyServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 处理 GET 请求 PrintWriter out = resp.getWriter(); out.println("Hello, world!"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 处理 POST 请求 PrintWriter out = resp.getWriter(); out.println("Hello, world!"); } } ``` 在 Servlet 中,doGet() 方法用于处理 GET 请求,doPost() 方法用于处理 POST 请求。通过调用 HttpServletResponse 对象的 getWriter() 方法可以向客户端返回响应数据。 ### 2.2 部署 Servlet 将编写好的 Servlet 部署到 Web 容器中,有两种方法:一种是将 Servlet 类打成 war 包放到 Web 容器的 webapps 目录下,另一种是通过 Eclipse 等开发工具将 Servlet 部署到 Web 容器中。部署完成后,可以通过访问 Servlet 的 URL 来测试 Servlet 是否正常工作。 ## 三、总结 本文介绍了 Servlet 的基本概念和使用方法。Servlet 是 Java Web 开发中非常重要的组件,掌握 Servlet 的使用方法对于 Java Web 开发人员来说是必不可少的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值