syscall指令_[原创]简析syscall,sysret和sysenter,sysexit的具体过程

本文详细介绍了Windows系统中,从用户态进入内核态的两种系统快速调用方式:sysenter/sysexit和syscall/sysret。通过实例展示了如何读取和使用MSR寄存器,以及这些指令在不同模式下的工作原理和寄存器交互细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里我以windows系统为例子,在其他系统实现方式是一样的.

我们都知道xp系统是通过int 2E中断从用户态进入内核态的.,但xp系统之后windows都是通过系统快速调用从用户态进入内核态的.

系统快速调用有两种:

sysenter/sysexit

syscall/sysret

前置知识MSR

MSR(Model-Specific Register) 是一类寄存器,这类寄存器数量庞大,并且和处理器的model相关.提供对硬件和软件相关功能的一些控制.能够对一些硬件和软件的运行环境进行设置.

使用MSR

每个MSR是64位宽的,每个MSR都有它的的地址值(编号).对MSR操作使用两个指令进行读写,由ecx寄存器提供需要访问的MSR地址值,EDX:EAX提供64位值(EAX表示低32位,EDX表示高32位)

rdmsr 读取MSR寄存器 其中高32位存放在EDX 低32位存放在EAX(64位和32位是一样,只是64位时rdx和rcx的高32位会被清零)

wrmsr 写入MSR寄存器 和读取一样写入时是用EDX表示高32位,EAX表示低32位

提示:如果MSR地址是保留未实现的,则执行rdmsr和wrmsr指令会产生#GP异常

示例:读取IA32_SYSENTER_EIP寄存器

demo:ShowMSR

#include

struct INT64

{

unsigned int low;

unsigned int hight;

};

VOID UnLoad(PDRIVER_OBJECT pDro)

{

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDro, PUNICODE_STRING pStr)

{

struct INT64 EIP = {0};//或者RIP

pDro->DriverUnload = UnLoad;

__asm {

xor edx, edx

xor eax, eax

mov ecx, 0x176 //IA32_SYSENTER_EIP寄存器

rdmsr

mov EIP.low,eax

mov EIP.hight, edx

}

KdPrint(("IA32_SYSENTER_EIP:%08x%08x",EIP.hight, EIP.low));

return STATUS_SUCCESS;

}

试验系统Win7 32位

sysenter/sysexit

在32位的windows系统中,是通过sysenter指令从用户态进入内核态的,从内核态返回用户态通过sysexit指令.

支持sysenter和sysexit的3个寄存器IA32_SYSENTER_CS

IA32_SYSENTER_ESP

IA32_SYSENTER_EIP

对应的地址:

sysenterIF in IA-32e mode

THEN

RSP ← IA32_SYSENTER_ESP;

RIP ← IA32_SYSENTER_EIP;

ELSE

ESP ← IA32_SYSENTER_ESP[31:0];

EIP ← IA32_SYSENTER_EIP[31:0];

FI;

CS.Selector ← IA32_SYSENTER_CS[15:0] AND FFFCH;

(* Operating system provides CS; RPL forced to 0 *)

SS.Selector ← CS.Selector + 8;

sysexitIF operand size is 64-bit

THEN (* Return to 64-bit mode *)

RSP ← RCX;

RIP ← RDX;

ELSE (* Return to protected mode or compatibility mode *)

RSP ← ECX;

RIP ← EDX;

FI;

IF operand size is 64-bit (* Operating system provides CS; RPL forced to 3 *)

THEN CS.Selector ← IA32_SYSENTER_CS[15:0] + 32;

ELSE CS.Selector ← IA32_SYSENTER_CS[15:0] + 16;//这里加了16

FI;

CS.Selector ← CS.Selector OR 3; (* RPL forced to 3 *)

SS.Selector ← CS.Selector + 8;//上面有加16

我们再做一个小例子:

demo:ShowMSR1

#include

struct INT64

{

unsigned int low;

unsigned int hight;

};

VOID UnLoad(PDRIVER_OBJECT pDro)

{

}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDro, PUNICODE_STRING pStr)

{

struct INT64 EIP = {0};//或者RIP

pDro->DriverUnload = UnLoad;

__asm {

xor edx, edx

xor eax, eax

mov ecx, 0x176 //IA32_SYSENTER_EIP寄存器

rdmsr

mov EIP.low,eax

mov EIP.hight, edx

}

KdPrint(("IA32_SYSENTER_EIP:%08x%08x",EIP.hight, EIP.low));

__asm {

xor edx, edx

xor eax, eax

mov ecx, 0x175 //IA32_SYSENTER_ESP寄存器

rdmsr

mov EIP.low, eax

mov EIP.hight, edx

}

KdPrint(("IA32_SYSENTER_ESP:%08x%08x", EIP.hight, EIP.low));

__asm {

xor edx, edx

xor eax, eax

mov ecx, 0x174 //IA32_SYSENTER_CS寄存器

rdmsr

mov EIP.low, eax

mov EIP.hight, edx

}

KdPrint(("IA32_SYSENTER_CS:%08x%08x", EIP.hight, EIP.low));

return STATUS_SUCCESS;

}

运行后(Win7 32):

可以看到:CS_Selector=8

返回时:CS=CS_Selector+16=24(18H) SS=CS_Selector+24=32(20h)

我们用x64Dbg随便查看一个程序看是否是这样的:

CS=1BH SS=23H 并不是18H和20H,知道有特权级的同学可能已经知道在用户模式下特权级是3,内核态是0,选择子的最后2位表示特权级,所以回到用户态特权级变为3,所以还要加上3.

18H+3H=1BH

20H+3H=23H

正好符合我们所描述的.

继续我们的主题

在32位模式下IA32_SYSENTER_EIP和IA32_SYSENTER_ESP的低32位存放进入(sysenter)内核态时的目标代码入口点和栈指针.当返回(sysexit)时需要EDX和ECX分别放入返回点和栈指针.

这里以Windows为例不介绍Long Mode模式,因为在该模式下Windows使用的是syscall和sysret

我们看一下CreateFile是怎么进入内核的:

demo:CreateFile(虚拟机没有按照VS的库,所以使用汇编来编写)

include kernel32.inc

includelib kernel32.lib

.data

szPath db '1.txt',0

.code

star:

push NULL

push FILE_ATTRIBUTE_NORMAL

push OPEN_EXISTING

push NULL

push FILE_SHARE_READ

push GENERIC_READ

push offset szPath

call CreateFileA

push eax

Call CloseHandle

ret

end star本来想用Windbg在系统快速调用的地址下断点,可是那里会一直断下来,也就放弃了

我们用x64Dbg调试:

syscall/sysret

支持syscall/sysret的MSR

syscall

syscall通过从IA32_LSTAR MSR加载RIP(syscall的下一条指令地址会保存到RCX).RFLAGS保存到R11寄存器,然后用IA32_FMASK MSR(MSR 地址:C0000084H)屏蔽RFLAGS的值,更具体的说是清除在IA32_FMASK MSR中设置的每一位.

syscall会从IA32_STAR[47:32]中加载CS和SS.

syscall不会保存堆栈指针(RSP).

官方描述(这里我隐藏了描述符检查和控制寄存器检查下同):

RCX ← RIP;保存syscall下一条指令地址到RCX

RIP ← IA32_LSTAR;RIP=IA32_LSTAR

R11 ← RFLAGS;R11=RFLAGS

RFLAGS ← RFLAGS AND NOT(IA32_FMASK);//根据IA32_FMASK屏蔽RFLAGS的相关位

CS.Selector ← IA32_STAR[47:32] AND FFFCH;确保CS的RPL为0

SS.Selector ← IA32_STAR[47:32] + 8;

sysretRIP=RCX

RFLAGS=R11

返回32位代码:CS=CS_Selector|3 SS=(CS_Selector+8)|3

返回64位代码:CS=(CS_Selector+16)|3 SS=(CS_Selector+8)|3

返回时不会修改(ESP or RSP)

IF (operand size is 64-bit)//如果是64位

THEN (* Return to 64-Bit Mode *)

RIP ← RCX;

ELSE (* Return to Compatibility Mode *)

RIP ← ECX;

FI;

RFLAGS ← (R11 & 3C7FD7H) | 2; (* Clear RF, VM, reserved bits; set bit 2 *)

IF (operand size is 64-bit)

THEN CS.Selector ← IA32_STAR[63:48]+16;

ELSE CS.Selector ← IA32_STAR[63:48];

FI;

CS.Selector ← CS.Selector OR 3; (* RPL forced to 3 *)

SS.Selector ← (IA32_STAR[63:48]+8) OR 3;

我们看一个demo

driver.c

#include

struct INT64

{

unsigned int low;

unsigned int hight;

};

VOID ReadMsr(unsigned long long,void *);

VOID UnLoad(PDRIVER_OBJECT pDo)

{

}

//RCX RDX R8 R9

NTSTATUS DriverEntry(PDRIVER_OBJECT pDo, PUNICODE_STRING pStr)

{

struct INT64 MSR = {0};

KdBreakPoint();

pDo->DriverUnload = UnLoad;

ReadMsr(0x0C0000081,&MSR);

KdPrint(("RetCS:%08X CallCS:%08X EIP:%08X",MSR.hight>>16,MSR.hight&0x0000FFFF,MSR.low));

ReadMsr(0x0C0000082, &MSR);

KdPrint(("RIP:%08X%08X", MSR.hight,MSR.low));

return STATUS_SUCCESS;

}64.asm

.code

ReadMsr proc

; rcx msr地址 rdx 接收的地址

push rdi

push rax

mov rdi,rdx

xor rax,rax

xor rdx,rdx

rdmsr

mov [rdi],eax

mov [rdi+4],edx

pop rax

pop rdi

ret

ReadMsr endp

end

由于在64位我的DbgView看不到信息,所以我在Windbg调试

可以知道:

SYSRET CS_Selector=0x23

读者可以推断在32位和64位时CS和SS的值分别是什么:

32位CS=CS_Selector|3=0x23

SS=(cs_Selector+8)|3=0x2B

64位CS=(CS_Selector+16)|3=0x33

SS=(CS_Selector+8)|3=0x2B

我们验证一下:

用x64dbg32位附加一个32位的程序:

用x64Dbg64位附加一个64位的程序

好了正好验证了我们的想法。

其实Windows要从32位进入内核时首先要把CS改为0X33,然后再进入64位,如果我们写了32位的代码,然后去指向64位的代码,其实也是把CS改为0x33.

后面的示例读者可以自己去验证,我已经说到很明白了。

示例程序是两个驱动:都是Win7的。

---15PB

最后于 2018-4-28 15:42

被chpeagle编辑

,原因:

上传的附件:

MSR.zip

(4.82kb,49次下载)

### configMAX_SYSCALL_INTERRUPT_PRIORITY BASEPRI 的含义 `configMAX_SYSCALL_INTERRUPT_PRIORITY` 是 FreeRTOS 配置参数之一,用于指定允许调用 FreeRTOS API 函数的最大中断优先级[^1]。这意味着任何具有等于或低于此值的中断都可以安全地中调用特定的 FreeRTOS API 函数(即那些带有 `FromISR` 后缀的函数)。对于 ARM Cortex-M 系列处理器而言,较低的数值代表更高的优先级。 #### BASEPRI 寄存器的作用 `BASEPRI` 是 ARM Cortex-M 处理器的一个特殊寄存器,用来屏蔽掉优先级低于其设定值的所有可屏蔽中断请求。当 `BASE PRIORITIES` 被设置为某个值时,所有优先级小于该值的中断都将被阻止进入处理状态;而高于或等于这个级别的中断仍然能够触发并被执行[^3]。 ### 使用场景及配置方法 为了确保系统的稳定性安全性,在使用 FreeRTOS 时应当合理规划各个外设以及内核自身的中断优先级: - **定义最大系统调用中断优先级** ```c #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 #define configMAX_SYSCALL_INTERRUPT_PRIORITY \ (configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS)) ``` 这段代码片段展示了如何通过位移操作来计算实际使用的优先级值,其中 `configPRIO_BITS` 表示硬件支持的有效优先级位数。 - **调整 BASEPRI 来保护临界区** 在执行关键任务期间,可以通过修改 `BASEPRI` 来临时提高当前线程的安全等级,防止低优先级中断打断正在进行的操作。例如,在进入一段不允许被打断的代码之前,可以将 `BASEPRI` 设置成大于或等于 `configMAX_SYSCALL_INTERRUPT_PRIORITY` 的值,从而只让更紧急的任务得以响应。 ```c void enter_critical_section(void){ __set_BASEPRI(configMAX_SYSCALL_INTERRUPT_PRIORITY); } void exit_critical_section(void){ __set_BASEPRI(0); /* 清除 BASEPRI */ } ``` 需要注意的是,不同型号的微控制器可能会有不同的实现细节,因此建议查阅具体的设备手册以获取最准确的信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值