前面几篇文章已经介绍了Windows的架构、运行模式、核心架构等,本篇接着介绍windows的两种运行模式切换等方面的原理和技术。从上一章节可以可以知道,在内核层最上面的是执行体API,这一层负责接收来自应用层的各种程序的访问,而且是绝大部分是通过Ntdll.dll桥来调用的。因为应用程序是运行在用户模式下的,为了保证这种调用和访问的健壮性,抵御来自用户模式的错误调用或者恶意攻击,必须保证执行体API参数的有效性。这就是说要保证这些参数值的合规性,若是指针参数,还要保证指针所指的内存的有效。因此,需要在执行体服务函数的开始处,对参数和指针内存进行检查和验证。
PreviousMode=KeGetPreviousMode(); //获取以前的所处的模式
if (PreviousMode != KernelMode){ //非内核模式 即用户模式才执行
try
{
ProbeForWrite(InputInformation,InputInformationLength,sizeof(ULONG)); //进行检测
if (ARGUMENT_PRESENT(ReturnLength))
{
ProbeForWriteUlong(ReturnLength); //检测ReturnLength
}
}except(EXCEPTION_EXECUTE_HANDLER) { //抛出例外
return GetExceptionCode(); //获得例外码
}
}
以上代码是通过ProbeForWrite来探查InputInformation这个输入参数是否可写,通过ProbeForWriteUlong来探查ReturnLength这个输入参数是否可写。探查函数发生访问违例,说明这两个参数有问题,通过except抛出违例异常代码。
在32位系统下,用户模式代码只能访问2GB以下的虚拟地址空间(2GB以下的部分称为进程地址空间,以上的部分称为系统地址空间),而内核模式代码可以访问整个进程的4GB虚拟地址空间范围。
从图中可以看到位于0XFFFF 0000至 0XFFFF FFFF中间这个区域为64K禁入分区,两种模式下都不能访问,前面的代码就是检测目标内存是否越过了此特殊区域,如果越过则访问违例。在代码中可以看到,后面试图将目标地址处的赋值回该地址,触发一次该地址的读和写的操作,如该内存地址处当前线程不可写,则引发异常,引起except截获控制。windows系统就是通过这种方式来捕获用户模式代码传递一个系统地址空间或者传递一个无效内存地址的情形,从而进行执行体函数参数的检测和检验的。如果到用执行体的API函数是内核模式的程序,就不需要进行参数检验和有效性验证,因为执行体不会用坏的参数来调用自己的服务。只有当执行体API函数接收到一个或多个来自用户模式参数时,才使用Probe函数族检查参数的有效性。每个线程都维护着一个状态值,用以说明线程的以前的处理模式,当用户模式切换到内核模式时,该值将被设置为UserMode,这样就满足前面那段代码的if判断条件了。