• 浅谈系统服务分发


        欢迎转载,转载请注明出处:http://www.cnblogs.com/uAreKongqi/p/6597701.html

    0x00.说在前面

      就我们所知,Windows操作系统内核的陷阱处理器会分发中断、异常和系统服务调用,这里我们就其中的系统服务分发简单解析一下。

     

    0x01.粗看不同处理器进入系统调用

      (1).在PentiumII 之前的x86处理器上,Windows使用int2e指令产生一个陷阱,导致执行线程转到内核模式,进入系统服务分发器,eax保存系统服务号,edx指向参数列表。最后通过iret指令返回用户模式;

           我们查看IDT的2e成员,得知该成员保存的地址是系统调用分发器的地址,紧接着u一下KiSystemService,会发现在保存完寄存器等状态之后,他走到了KiFastCallEntry里面!(在Win7 x86下测试)

          

      (2).在x86Pentium II 处理器上,Windows使用了sysenter指令,内核的系统服务分发器例程的地址保存在与该指令相关联的一个MSR中,eax,edx保存与int2e相同的内容。最后通过sysexit指令返回用户模式;

         这里读取MSR的0x176处,其中包含了系统服务分发器地址,发现实际上调用的就是KiFastCallEntry(入口)!(在Win7 x86下测试)

          

      (3).在x64体系架构上,Windows使用syscall指令,将系统调用号保存在eax中,前四个参数放在寄存器(rcx/rdx/r8/r9)中,剩下的参数在栈中。

        64位平台读取MSR的0xC0000082处,里面保存的是64位的syscall,当我们u一下这个地址,发现这个就是x64系统调用分发的入口KiSystemCall64(在Win7 x64下测试)

          

        Ps:通过KiSystemCall64的地址可以通过硬编码得到SSDT、SSSDT地址

     ......

       我们发现,32位下,系统调用分发操作都会走到KiFastCallEntry里面,而64位下,系统调用分发操作会走到KiSystemCall64,然后去完成相应的系统服务调用。那么我们有个疑问,系统是怎么进入到这些系统调用的呢?

     

    0x02.举例查看系统调用如何发生

      这里以Win7 x86 平台下的 NtOpenProcess为例,切换到一个进程内(如explorer.exe),u一下NtOpenProcess,这里显示的是ntdll里的NtOpenProcess的反汇编:

      

      我们有两个收获,一个是看到了系统服务号放在了eax里了,另一个是它呼叫了一个地址,call指令将执行由内核建立起来的系统服务分发代码,该地址保存在ntdll!_KUSER_SHARED_DATA+0x300处,我们接着进这个地址看看:

      

      我们似乎有了点儿眉目了,系统调用在ntdll里面发生了,也就是说,在ntdll里面完成了从ring3到ring0的切换,其中eax保存服务号,edx保存参数列表首地址,通过服务号可以在SSDT中定位目标服务例程

      32位下的KeServiceDescriptorTable每个成员就是目标系统服务的绝对地址,64位下的目标系统服务真实地址是KeServiceDescriptorTable每个成员保存的偏移量(右移4位后的)+KeServiceDescriptorTable基地址;

      一开始线程的系统服务表地址指向Ntoskrnl.exe中的SSDT表,但当调用了一个USER或GDI服务时,服务表地址被修改成指向win32k.sys中的系统服务表。 

    0x03.从用户层到内核层完整的调用过程

      (1). 当一个Windows应用程序调用Kernel32.dll中的OpenProcess时,其导入并调用了API-MS-Win-Core-File-L1-1-0.dll(一个MinWin重定向Dll)中的NtOpenProcess函数;

      (2). 接着上述的NtOpenProcess函数又会调用KernelBase.dll中的OpenProcess函数,这里是函数的真正实现,它会对子系统相关的参数做了检查;

      (3). 然后KernelBase!OpenProcess就会调用ntdll.dll中的NtOpenProcess函数,在这儿就会触发系统调用(ntdll!KiFastSystemCall),传递NtOpenProcess的系统服务号和参数列表;

      (4). 系统服务分发器(Ntoskrnl.exe中的KiSystemService函数)就会调用真正的NtOpenProcess函数来处理该I/O请求。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    0x04.内核模式下的系统分发

      在系统调用中,如果原先模式为用户模式,在给系统服务传递的参数指向了用户空间缓冲区时,内核模式代码在操作该缓冲区前会检查是否可以访问该缓冲区,而原先模式就是内核模式的时候,默认参数有效,则不会对参数进行检查。而既然已经在内核模式了,那就不需要int2e中断或者sysenter之类的操作了,但如果直接像调用API一般直接调用NtOpenProcess之类的系统服务函数时,内核保存的原先模式值仍然是用户模式(进内核之前当然是用户模式咯~),但又检测到传递来的地址是一个内核模式地址(因为在当前内核模式下调用),于是会导致调用失败(STATUS_ACCESS_VIOLATION)。

      这里要介绍内核的Zw系列函数了,他们不仅是Nt版本函数的别名或包装,而是对应Nt系列系统调用的翻版,使用了同样的系统调用分发机制。他们会建立一个假的中断栈(CPU在中断后生成的栈),并直接调用KiSystemService例程,这个过程就在模拟CPU中断,仿佛调用来自用户模式一般,而在检测到该调用的实际特权级后将原先模式修改为内核模式,这样也省去了参数校验,成功调用到NtOpenProcess!

      

     0x05.简单小结

       Ring3 ---> Ring0 的系统调用: Kernel32.dll(API)--->ntdll.dll(Nt/Zw)--->用户模式转内核模式--->Ntoskrnl.exe(Nt)--->完成I/O请求(原路返回)

       Ring0 ---> Ring0 的系统调用:Ntoskrnl.exe(Zw)--->Ntoskrnl.exe(Nt)

       以上理解参考自《深入解析Windows操作系统6》第三章,如果有不正确的地方还请指出,我将虚心请教!

    详情见

    jpg 转 zip 

  • 相关阅读:
    Linux 下的类似Windows下Everything的搜索工具
    windows和linux环境下制作U盘启动盘
    程序调试手段之gdb, vxworks shell
    LeetCode 1021. Remove Outermost Parentheses (删除最外层的括号)
    LeetCode 1047. Remove All Adjacent Duplicates In String (删除字符串中的所有相邻重复项)
    LeetCode 844. Backspace String Compare (比较含退格的字符串)
    LeetCode 860. Lemonade Change (柠檬水找零)
    LeetCode 1221. Split a String in Balanced Strings (分割平衡字符串)
    LeetCode 1046. Last Stone Weight (最后一块石头的重量 )
    LeetCode 746. Min Cost Climbing Stairs (使用最小花费爬楼梯)
  • 原文地址:https://www.cnblogs.com/kuangke/p/9397468.html
Copyright © 2020-2023  润新知