首页
社区
课程
招聘
[原创]简析syscall,sysret和sysenter,sysexit的具体过程
2018-4-24 19:42 17708

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

2018-4-24 19:42
17708

这里我以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 <ntddk.h>

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

对应的地址:

 

sysenter

IF 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;

sysexit

IF 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 <ntddk.h>

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;

sysret

  • RIP=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 <ntddk.h>

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


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

最后于 2018-4-28 15:42 被chpeagle编辑 ,原因:
上传的附件:
收藏
点赞1
打赏
分享
最新回复 (7)
雪    币: 2914
活跃值: (2809)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
boursonjane 2018-4-24 20:54
2
0
..你不会把教材抄上来了吧..
雪    币: 6
活跃值: (164)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
lmwjt 2018-4-24 21:25
3
0
。。。。。。。。。。。。。。。。。。。。
雪    币: 512
活跃值: (3719)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
龙飞雪 2018-4-25 11:14
4
0
windows内核原理  这本书讲的很清楚
雪    币: 310
活跃值: (1912)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
niuzuoquan 2018-4-25 21:10
5
0
感谢分享
雪    币: 8861
活跃值: (2364)
能力值: ( LV12,RANK:760 )
在线值:
发帖
回帖
粉丝
cvcvxk 10 2018-4-26 11:01
6
0
好好的__readmsr不用非要自己写汇编

msdn了解一下:https://msdn.microsoft.com/en-us/library/y55zyfdx.aspx

最后于 2018-4-26 13:17 被cvcvxk编辑 ,原因:
雪    币: 1484
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
冰雄 2018-4-28 19:24
7
0
可以自己实现sysenter。不用系统的不是更好?
雪    币: 15
活跃值: (76)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
noinoinoi 2020-7-26 16:37
8
0
讲的不错,反正我是学到东西了
游客
登录 | 注册 方可回帖
返回