sysenter调用详解

sysenter调用

以CreateFile为例,因为它常见,且对应于nt中的函数NtCreateFile。

 对于程序中队CreateFile的调用,编译器会编译成如下汇编码:

先会将参数压栈,然后调用函数:

01031017 call        dword ptr[__imp__CreateFileW@28 (1032000h)] 

上面的1032000处的值为0x7664cc56,也就是跳到这个值处。这是应用其它模块函数的标准方式。(通过导入表跳转)

 

看下面的代码,在kernel32模块里面,

_CreateFileWImplementation@28:

7664CC56 mov         edi,edi 

。。。

7664CC98 push        dword ptr [ebp+8] 

7664CC9B call        _CreateFileW@28(7664CCA9h)

。。。

又调用函数了,看调用方式应该还在kernel32里面

_CreateFileW@28:

7664CCA9 jmp         dword ptr[__imp__CreateFileW@28 (766019E0h)] 

貌似和exe中的处理方式一样,跳转到其他模块的函数时会使用导入表中的对应函数地址。

 

果然,到KernelBase.dll中了,(发现VS2010每次加载dll都会从定位,这是为了安全着想,不过调试的时候很讨厌)

_CreateFileW@28:

75B9A850 mov         edi,edi

。。。

准备参数,入栈(44个字节的参数啊),

75B9AA1B lea         eax,[ebp-8] 

75B9AA1E push        eax 

75B9AA1F  call       esi 

75B9AA21 mov         ebx,eax 

75B9AA23 mov         edi,0C0000022h 

。。。

call了就到了ntdll里面的_NtCreateFile@44了(这个函数在msdn上有介绍,参数中有处理过的文件名)。这个函数应该是软件生成的,因为很多这样的函数,只是调用号和弹出字节数不一样,她有个名字叫stub function。

_NtCreateFile@44:

778455C8 mov         eax,42h 

778455CD mov         edx,7FFE0300h 

778455D2  call       dword ptr [edx]  

778455D4 ret         2Ch 

上面的42h是win7中nt!NtCreateFile的系统调用编号(和win2003的不一样了,微软真恶心)。7FFE0300h是一个固定的位置,该位置根据cpu是否支持sysenter设置成不同的函数。

 

下面就到了KiFastSystemCall了:

_KiFastSystemCall@0:

778470B0 mov         edx,esp 

778470B2 sysenter

调用上面这个又进无回的函数之前,eax是系统调用的调用号,那edx是什么了?看下面的栈结构:

KiFastSystemCall@0的返回后的eip地址

_NtCreateFile@44返回后的eip地址

参数1

参数2

参数3这个参数里面含有路径信息。验证方法看下面.

。。。

参数11

所以,edx-8为第一个参数的地址。参数3验证为:

找到参数3的内容(4字节),该地址指向POBJECT_ATTRIBUTES结构,该结构在msdn中有定义,第3个成员就是文件名。

 

好了,用户态就这么多了,剩下了就要进入内核了。

 

看一下sysenter干的事情,intel文档上说该指令是为了快速的系统调用(从ring3 到ring0),在执行sysenter之前了,必须要设置好ring0中的代码段、入口函数地址、ring0栈所在的段、栈指针,设置方式是写下列MSRs:

IA32_SYSENTER_CS:32bit,前16个字节为代码段选择符,其确定的索引+1为栈段的索引,所以在全局描述符表中这两个必须相连。

IA32_SYSENTER_EIP:32bit,入口指令地址。

IA32_SYSENTER_ESP:32bit,内核栈地址。

 

通过上面的介绍,进入进入内核的步骤应该就清楚了。执行sysenter时会将对应的段设置好,并且从IA32_SYSENTER_EIP给出的地址开始执行。下面看看这个地址的内容(rdmsr 176):

nt!KiFastCallEntry:

804dee0f b923000000      mov    ecx,23h

804dee14 6a30            push    30h

804dee16 0fa1            pop     fs

明显,进入内核开始执行的为上面的函数。

 

nt!KiFastCallEntry:

804dee0f b923000000      mov    ecx,23h

804dee14 6a30            push    30h

这个函数主要流程如下:

1.建立陷阱帧,准备eax为调用号,edx为用户态栈的esp(注意这里加过8了,即将用户栈的两个返回值排除了,直接是参数列表),esi为ETHERAD地址

2.调用_KiSystemServiceRepeat.

 

KiSystemServiceRepeat会根据ETHERAD中的系统服务描述表盒调用号决定该是使用nt里面的服务还是win32k里面的服务。确定了需要使用的系统服务描述符表,根据参数个数和edx的地址从用户栈拷贝参数到内核栈上。然后将服务地址放到ebx中,如下调用服务函数:

       call    ebx                     ; call system service

ok,调用完了就要返回用户空间了。

先根据当前的陷阱帧恢复旧的陷阱帧。然后会处理APC,再然后就是执行退出代码了,这个代码在wrk中是在EXIT_ALL宏中完成的。这个宏最后一点代码是:

_KiSystemCallExit2:

 

       test    dword ptr [esp+8],EFLAGS_TF

       jne     short _KiSystemCallExit

 

       pop     edx                 ; pop EIP

       add     esp, 4              ; Remove CS

       and     dword ptr [esp], NOTEFLAGS_INTERRUPT_MASK ; Disable interrupts in the flags

       popfd

       pop     ecx                 ; pop ESP

 

       sti                         ;sysexit does not reload flags

 

       iSYSEXIT


执行sysexit时(iSYSEXIT宏),它做的事情如下:

IA32_SYSENTER_CS:根据这个量设置ring3的cs和ss。

EDX:设置为用户态的EIP。

ECX:设置用户态的ESP。(刚进入KiSystemCallEntry是保存在了栈上push    dword ptrds:[USER_SHARED_DATA+UsSystemCallReturn] ;)

ok切换完成,系统调用结束。

那这个返回地址是在哪了?很奇怪,这个和0x2e进入时时不一样的。

原来,在内核空间的一个特殊的地方(0xffdf0000),存放了一个nt!_KUSER_SHARED_DATA结构的变量,该变量里面存放了进入内核之前要执行的代码和刚出来时会执行的代码。那用户空间为啥能访问了?这是应为系统同时将这块内存(貌似是64kB大小)映射到了用户空间(0x7ffe0000),该部分对于用户来说是可访问但不可修改(想想要是能修改这就算是直接写内核空间了)

 

那返回时要执行什么代码了?根据进入时用户栈的内容可以知道,如下的语句就满足要求了:

ret。

还记得吗?栈顶是一个返回值(函数_KiFastSystemCall@0),这样代码就连贯了。

 

ok,这就差不多了吧。

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值