内核模式和用户模式的切换

32位x86系统,每个进程的空间是4GB,即地址0x00000000到0xFFFFFFFF。
为了高效调用,Windows会把操作系统的内核数据和代码映射的系统中所有进程的进程空间中。因此4GB空间被划分为两个区域:用户空间和系统空间,默认大小为各2GB。
为了保护映射到进程空间的系统代码和数据,Windows提供了权限控制机制。也就是两种访问模式:用户模式和内核模式。
处理器在硬件一级保证高优先级别的数据和代码不会被低优先级破坏。
对x86处理器来说,没有任何寄存器表明处理器当前处于何种模式下,优先级知识代码或数据所在的内存段或页的一个属性。

模式切换的两种方式:软中断、快速系统调用指令。

1.INT 2E切换到内核模式
CPU在把执行权交给KiSystemService函数前,需要做一些准备工作:
(1)权限检查,即检查源和目标位置所在的代码段权限;
(2)准备内核态使用的栈。所有线程在内核态执行时必须使用内核栈;

KiSystemService的流程
(1)根据服务ID从SSDT中查找要调用的服务函数地址和参数;
(2)将参数从用户态栈复制到该线程的内核栈中;
(3)KiSystemService调用内核中正在的NtReadFile();
(4)KiSystemService将操作结果复制回线程用户态栈;
(5)通过IRET指令将执行权交回给NtDll.dll中的NtReadFile();

开销
(1)CPU必须从内存中加载门描述符和段描述符,以便得到KiSystemService()的地址;
(2)进行权限检查;
系统调用使用的很频繁,如果能减少这些开销,是很有意义的。
可以从两个方面来降低开销:
(1)把系统服务的例程KiSystemService()的地址放到寄存器中;
(2)避免权限检查,也就是使用特殊的指令让CPU省去那些对系统服务调用来说不需要的权限检查。奔腾II引入的SYSENTER/SYSEXIT就是为此设计的,AMD K7引入的是SYSCALL/SYSRETURN;

2.快速系统调用
从WindoXp和Windows Service 2003开始,系统在启动过程中会通过CPUID指令检测CPU是否支持快速系统调用指令。如果支持这些指令,Windows会决定用新的方式进行系统调用,并做好如下准备工作:
(1)在GDT中建立4个段描述符,分别用来描述供SYSENTER/SYSEXIT使用的CS和SS;
(2)设置MSR寄存器,填充SYSENTER/SYSEXIT要跳转的地址等;
(3)将一小段名为SystemCallStub的代码复制到ShareUserData内存区。该代码调用SYSENTER或SYSCALL进入内核态;

0:000> u ntdll!ntreadfile
ntdll!NtReadFile:
7c92d9ce b8b7000000      mov     eax,0B7h
7c92d9d3 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c92d9d8 ff12            call    dword ptr [edx] 
7c92d9da c22400          ret     24h
7c92d9dd 90              nop

Win-XP-SP1时是call edx,Win-XP-SP2变成了call    dword ptr [edx],所以不能用u SharedUserData!SystemCallStub
至于为什么要改变这个 call [EDX],我特地查了《Kernel-mode Payloads on Windows》(Uninformed, 2005)写在脚注里且红色标出。
Due to the fact that SharedUserData contained executable instructions, it was thus necessary that the SharedUserData mapping had to be marked as executable. When Microsoft began work on some of the security enhancements included with XP SP2 and 2003 SP1, such as Data Execution Prevention (DEP), they presumably realized that leaving SharedUserData executable was largely unnecessary and that doing so left open the possibility for abuse. To address this, the fields in KUSER_SHARED_DATA were changed from sets of instructions to function pointers that resided within ntdll.dll.
由于 SharedUserData 中包含了可执行指令,那么 SharedUserData 所在的页就会被映射成可执行代码段。当微软试图对 XP SP2 以及 2003 SP1 做一些安全性增强工作时(例如:数据执行保护 DEP),他们大概意识到让 SharedUserData (所在页面)变得可执行非常地多余,这留下了潜在的滥用可能性。为了解决这个问题,那个在 KUSER_SHARED_DATA 里的域由一组指令变成了指向 ntdll.dll 里指令的函数指针.
引用自:http://advdbg.org/forums/1982/ShowPost.aspx


0:000> dd SharedUserData!SystemCallStub
7ffe0300  7c92e510 7c92e514 00000000 00000000
7ffe0310  00000000 00000000 00000000 00000000
7ffe0320  00000000 00000000 00000000 00000000

0:000> u 7c92e510
ntdll!KiFastSystemCall:
7c92e510 8bd4            mov     edx,esp
7c92e512 0f34            sysenter
ntdll!KiFastSystemCallRet:
7c92e514 c3              ret

kd> u nt!kifastcallentry l20
nt!KiFastCallEntry:
8053e550 b923000000      mov     ecx,23h
8053e555 6a30            push    30h
8053e557 0fa1            pop     fs
8053e559 8ed9            mov     ds,cx
8053e55b 8ec1            mov     es,cx
8053e55d 8b0d40f0dfff    mov     ecx,dword ptr ds:[0FFDFF040h]
8053e563 8b6104          mov     esp,dword ptr [ecx+4]
8053e566 6a23            push    23h
8053e568 52              push    edx
8053e569 9c              pushfd
8053e56a 6a02            push    2
8053e56c 83c208          add     edx,8
8053e56f 9d              popfd
8053e570 804c240102      or      byte ptr [esp+1],2
8053e575 6a1b            push    1Bh
8053e577 ff350403dfff    push    dword ptr ds:[0FFDF0304h]   

我机器上u nt!kifastcallentry反汇编的结果跟《软件调试》书中给出的有点区别:
(1)将ShareUserData内存区里SystemCallStub例程中ret指令的地址压栈的语句push ecx,变成了如下命令
8053e55d 8b0d40f0dfff    mov     ecx,dword ptr ds:[0FFDFF040h]
8053e563 8b6104          mov     esp,dword ptr [ecx+4]
8053e566 6a23            push    23h
8053e568 52              push    edx
8053e569 9c              pushfd
[0FFDF0304h]的值是7c92e514,即ShareUserData内存区里SystemCallStub例程中ret指令的地址。在进入nt!SystemService之前,该地址连同其他参数一起被压栈。用来指定SYSEXIT返回用户模式时的目标地址。

而INT 2E进行系统调用时不需要这样做,因为INT n指令会自动将中断发生时的CS和EIP压栈,当中断例程执行IRETD返回时,会将栈中保存的CS和EIP返回到合适的位置。

(2)进入nt!KiSystemService的语句
《软件调试》书中给出的是在push ecx语句后接着就是nt!KiSystemService,但我机器上反汇编的结果去找不到。在网上查到一个比较详细的解释,如下:
//Get the KPCR->SelfPcr pointer.804defa2 
8b1d1cf0dfff    mov     ebx,dword ptr ds:[0FFDFF01Ch]
...
//Get the KPCR->PrcbData.CurrentThread pointer804defaa 
8bb324010000    mov     esi,dword ptr [ebx+124h]
...
//This is rather complicated, but effectively does:
//   EDI = &ETHREAD->ServiceTable[ServiceTableToUse]
//Since EAX is the call number, HIBYTE(EAX) will be the value that determines which service table is in use (since GUI calls use service indices in the form 1xxx, and non-GUI calls use the form 0xxx).  Then, it ANDs it with 0x30, which returns a multiple of 0x10 (on modern Windows, 0x00 or 0x10).  Incidentally, the service table structure is
0x10 bytes long, from which we can say that this call either uses the Windows API service table (nt!KiServiceTable) or the Windows GUI API service table (win32k!W32pServiceTable).
//Finally, this piece of code masks off the service table selector, leaving just the call number.
804df000 8bf8            mov     edi,eax
804df002 c1ef08          shr     edi,8
804df005 83e730          and     edi,30h
804df008 8bcf            mov     ecx,edi
804df00a 03bee0000000    add     edi,dword ptr [esi+0E0h]
804df012 25ff0f0000      and     eax,0FFFh
...
//This is where the actual system service is determined. It gets the array of function pointers (as above, KiServiceTable or W32pServiceTable), and then indexes into it based on the call number (which has been set above).
804df04f 8b3f            mov     edi,dword ptr [edi]
804df051 8b1c87          mov     ebx,dword ptr [edi+eax*4]
...
//And here's where the actual system service is called.
804df069 ffd3            call    ebx
摘自:http://forum.sysinternals.com/question-about-kifastcallentry-kisystemservice_topic22985.html

另外,该网页也提到
KiSystemServiceis called when calling kernel-mode ZwXxx functions or when user-mode application executes "int 0x2e" instruction。

windbg的u命令可参考文档:《kd>u ntdll!zwopenprocess失败的解决》

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值