windows内核态调用readfile

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了windows内核态调用readfile相关的知识,希望对你有一定的参考价值。

参考技术A 对于Windows操作系统的编程一般来说已经涉及到了较深的领域,针对该问题提出几家之言,均为转载:

一、

为了防止用户程序访问并篡改操作系统的关键部分,Windows使用了2种处理器存取模式(事实上Windows运行的处理器可以支持4种模式):用户模式和内核模式。用户程序运行在用户模式而操作系统代码(如系统服务和设备驱动程序)则运行在内核模式。在内核模式下程序可以访问所有的内存和硬件,并使用所有的处理器指令。操作系统程序比用户程序有更高的权限,使得系统设计者可以确保用户程序不会意外的破坏系统的稳定性。
虽然Windows进程有自己的运行空间,但是内核模式的操作系统代码和设备驱动程序代码则运行在同一个虚拟地址空间。虚拟内存中的每一页都标明了他可由处理器以哪

Windows内核学习笔记之浅析系统调用

在潘老师《Windows内核原理与实现》一书,解析了Windows应用程序发出的系统调用。图示如下。

技术分享

从图可看出,系统调用所提供的服务(函数)是运行在内核中的,也就是说,在"系统空间"中。

用户空间与系统空间所在的内存区间不一样,同样,对于这两种区间,CPU的运行状态也不一样。

在用户空间中,CPU处于"用户态";在系统空间中,CPU处于"系统态"。

CPU从系统态进入用户态是容易的,因为可以执行一些系统态特有的特权指令,从而进入用户态。

而相反,用户态进入系统态则不容易,因为用户态是无法执行特权指令的。

所以,一般有三种手段,使CPU进入系统态(即转入系统空间执行):中断、异常、自陷。

 

关于指令Int 2e。

技术分享

 

Intel X86处理器在IDT(中断描述服务表)查找2e项,IDTEntry包含了一个段选择符和中断历程的段内偏移,所以处理器还需要在GDT(全局描述表)中再查一次,得到段选择符指定的段的虚拟地址。段基地址加上中断例程偏移,最终得到中断例程的虚拟地址。

 

处理器在不同模式下使用不同的栈,用户模式代码使用用户栈,内核模式代码使用内核栈,因此当从用户模式切入到内核时,必定伴随着堆栈的变化。

Int 2e与Sysenter最主要的区别就在于堆栈切换方式的不同。但是不管是哪种方式本质上都是先获得内核栈然后保存用户模式下的ss,esp,eflags.cs.eip到内核栈,执行完系统调用后在通过保存的值进行用户空间的还原。

 

首先我们先分析下Int 2e/iret的栈切换。

技术分享

 

首先如何获得内核栈的ss和esp?

处理器的任务寄存器指向当前任务环境的TSS,其中RING0的esp位于TSS+4的位置。WINDOWS每次切换线程时,总会设置好TSS中RING0的esp。

完成中断处理后通过iret从内核栈中弹出eip,cs,eflags,esp,ss,然后将控制权交给eip所指向的用户模式代码。

显然,通过int 2e/iret进行模式切换过程中涉及很多流程,开销很大。所以在XP后引入了Sysenter/Sysexit,快速系统调用。

 

然后看下Sysenter/Sysexit栈切换。

 

为了避免过多的内存访问,Sysenter增加了三个MSR寄存器来指定跳转目标和栈位置。

 

技术分享

 

下面我们以XP下的Nt为例试着跟踪一下

 

kd> u Ntdll!NtWriteFile
ntdll!ZwWriteFile:
7c92df60 b812010000 mov eax,112h
7c92df65 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c92df6a ff12 call dword ptr [edx]
7c92df6c c22400 ret 24h
7c92df6f 90 nop

 

首先看到Ntdll中NtWriteFile把索引保存进eax中,然后调用了ShareUserData结构中的CallSystemStub函数指针。

 

我们继续跟踪下ShareUserData结构(_KUSER_SHARED_DATA)

kd> dt _KUSER_SHARED_DATA 0x7ffe0000
ntdll!_KUSER_SHARED_DATA

.

.

.

+0x300 SystemCall : 0x7c92e4f0
+0x304 SystemCallReturn : 0x7c92e4f4

.

.

这样我们可以清楚的看到0X300处的函数指针了。

kd> u 0x7c92e4f0
ntdll!KiFastSystemCall:
7c92e4f0 8bd4 mov edx,esp     ;注意此时的ESP代表的是参数块
7c92e4f2 0f34 sysenter

ntdll!KiFastSystemCallRet:
7c92e4f4 c3 ret

 

然后就sysenter指令进入内核的KiFastCallEntry() 函数了。

 

kd> u KiFastCallEntry l 50
nt!KiFastCallEntry:
.

.

.

8053e612 8bf2          mov esi,edx       ;用户空间的ESP

8053e614 8b5f0c         mov ebx,dword ptr [edi+0Ch]
8053e617 33c9                 xor ecx,ecx
8053e619 8a0c18              mov cl,byte ptr [eax+ebx]       
8053e61c 8b3f                  mov edi,dword ptr [edi]        
8053e61e 8b1c87              mov ebx,dword ptr [edi+eax*4]

8053e623 c1e902              shr ecx,2
8053e626 8bfc                  mov edi,esp
8053e628 3b35d4995580  cmp esi,dword ptr [nt!MmUserProbeAddress (805599d4)]
8053e62e 0f83a8010000  jae nt!KiSystemCallExit2+0x9f (8053e7dc)




以上是关顾从Ring3层发起的系统调用过程,有趣的是Ring0层如果调用Zw函数则会递归进入Kifastcallentry,然后调用Nt函数。

这就是内核Zw函数与Nt函数最大的区别!(Ntdll中的Zw函数与Nt函数没有区别)

所以在返回Kifastcallentry时会进行很繁琐,主要原因是通过先前模式判断发起系统调用的堆栈是用户控件或者系统空间,然后进行不同的处理。

 

 

   发起系统调用      入口内核例程    返回指令    返回内核例程

    int 2e       KiSystemService     iret                   KiSystemCallExit

      sysenter(Intel) KiFastCallEntry         SYSEXIT    KiSytemcallExit2

    syscall (AMD) KiFastCallEntry           SYSRETURN    KiSystemCallExit3

 

以上是关于windows内核态调用readfile的主要内容,如果未能解决你的问题,请参考以下文章

20135327郭皓--Linux内核分析第四周 扒开系统调用的三层皮(上)

linux系统中,进程进行系统调用进入内核态时,是该进程本身进入内核态还是操作系统新开了一个内核线程?

用户态内核态

用户态与内核态的理解

Windows内核开发-Windows内部概述-2-

Linux 内核Linux 内核体系架构 ( 硬件层面 | 内核空间 | 用户空间 | 内核态与用户态切换 | 系统调用 | 体系结构抽象层 )