• 用户态程序调用系统态程序快速系统调用


    在调试程序中,经常发现程序最后会调用到系统态的程序。这个过程是怎样的?用户空间的程序怎样进行系统调用,在此过程中是怎样进入和退出内核的。

    根据运行状态和执行代码所在的内存空间的不同,CPU既可以运行于非特权的“用户态”,也可以运行于特权的“系统态”。CPU从系统空间装入用户空间是容易的,CPU可以通过一些特权指令改变其运行状态,反过来要从用户空间转到系统空间就不容易了,因为运行于用户态的CPU不能执行特权指令。

    一般而言有三种方式可以使运行于用户态的CPU转入到系统态,下面只说一下Windows是如何利用IA-32处理器的SYSENTER、SYSEXIT指令实现快速系统调用的。

    Windows 2000或之前的Windows不支持快速系统调用,它们只能使用INT 2E的方式进行系统调研。在Windows XP和Windows Server 2003或以后版本在启动时会通过CPUID指令检测是否支持快速系统调用。如果支持,那么Windows会使用新的方式进行系统调用。

    使用快速系统调用需要如下工作:

    1. 在GDT中建立4个段描述符,分别用来提供SYSENTER指令进入内核模式时使用的代码段(CS)和栈段(SS),以及SYSEXIT指令从内核模式返回用户模式时使用的代码段(CS)和栈段(SS)。
    2. 设置专门用于系统调用的MSR寄存器,SYSENTER_EIP_MSR用于指SYSENTER指令要跳转的目标例程地址。Windows会将其设置为KiFastCallEntry的地址。SYSENTER_CS_MSR寄存器用来指定新的代码段,也就是KiFastCallEntry所在的代码段。SYSENTER_ESP_MSR用于指定新的栈指针(ESP)。
    3. 将一段名为SystemCallStub的代码复制到SharedUserData内存区,该区在被映射到每个Win32进程的进程空间中,这样当应用程序每次进行系统调用时,NtDll中的Stub函数便调用这段SystemCallStub代码。

    以下以Notepad为例,看看Notepad在调用NtWriteFile时的CPU从用户态到系统态的转换过程。

    使用Windbg挂着VWare用运行的Windows XP系统,在Notepad的主线程上设置ntdll!NtWriteFile的断点。

     

    kd> !process 0 3 notepad.exe
    PROCESS 81bd4658 SessionId: 0 Cid: 07a8 Peb: 7ffdf000 ParentCid: 0590
    DirBase: 1a8a3000 ObjectTable: e1ae2678 HandleCount: 38.
    Image: notepad.exe
    VadRoot 81e1ef30 Vads 62 Clone 0 Private 169. Modified 13. Locked 0.
    DeviceMap e17b2e98
    Token e1b8ece8
    ElapsedTime 00:01:50.327
    UserTime 00:00:00.062
    KernelTime 00:00:01.968
    QuotaPoolUsage[PagedPool] 32228
    QuotaPoolUsage[NonPagedPool] 2480
    Working Set Sizes (now,min,max) (1267, 50, 345) (5068KB, 200KB, 1380KB)
    PeakWorkingSetSize 1267
    VirtualSize 33 Mb
    PeakVirtualSize 34 Mb
    PageFaultCount 1330
    MemoryPriority BACKGROUND
    BasePriority 8
    CommitCharge 479
    THREAD 81e068e8 Cid 07a8.0340 Teb: 7ffde000 Win32Thread: e176ad58 RUNNING on processor 0

    kd> bp /t 81e068e8 ntdll!ntwritefile

     

    如上设置断点后,在Windows XP中使用Notepad保存文件就会断在ntdll!ZwWriteFile。

    kd> kb
    ChildEBP RetAddr Args to Child
    0006fa04 77e673b1 0000057c 00000000 00000000 ntdll!ZwWriteFile
    0006fa64 010048e8 0000057c 00092c40 000000b8 kernel32!WriteFile+0xf7
    0006fa9c 01004cb4 0000057c 000004e4 00000400 notepad!___PchSym_+0x6e8
    0006fad8 01002a1f 000701c0 01009900 00099570 notepad!___PchSym_+0xab4
    0006fafc 00000000 00000000 00000000 00000000 notepad!ServiceStarter+0x6b

    反汇编ntdll!ZwWriteFile可以看到调用了SystemCallStub

    ntdll!ZwWriteFile:
    001b:77f3f010 b812010000 mov eax,112h
    001b:77f3f015 ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
    001b:77f3f01a ffd2 call edx
    001b:77f3f01c c22400 ret 24h
    001b:77f3f01f 90 nop 

    反汇编SharedUserData!SystemCallStub查看它的代码,它代码作用是:把栈指针(ESP)放到EDX中,可以传递参数,也作为从内核模式返回时的栈地址。再执行SYSENTER指令。

    SharedUserData!SystemCallStub:
    7ffe0300 8bd4 mov edx,esp
    7ffe0302 0f34 sysenter
    7ffe0304 c3 ret 

    当执行SYSENTER指令时,CPU进入系统态,并且:

    • 把寄存器SYSENTER_CS_MSR的内容复制到段寄存器CS中。
    • 把寄存器SYSENTER_EIP_MSR的内容复制到寄存器EIP中。
    • 把寄存器SYSENTER_CS_MSR的内容+8写入堆栈段寄存器SS中。
    • 把寄存器SYSENTER_ESP_MSR的内容复制到堆栈指针ESP中。

    这样,只要预先设置好三个MSR寄存器的内容中,CPU在执行SYSENTER后就可以进入系统空间并从预定的地址执行程序,同时开始使用系统空间的堆栈。通过WinDbg中rdmsr命令可以查看到MSR寄存器的内容,可以看到SYSENTER_EIP_MSR指向nt!KiFastCallEntry,这就是系统空间中快速系统调用的总入口。nt!KiFastCallEntry中再进入nt!KiSystemService。

    kd> rdmsr 174
    msr[174] = 00000000`00000008
    kd> rdmsr 175
    msr[175] = 00000000`00000000
    kd> rdmsr 176
    msr[176] = 00000000`80599770
    kd> u 80599770
    nt!KiFastCallEntry: 

    从nt!KeServiceDescriptorTable中可以看到第112h是对应着nt!NtWriteFile。

    kd> dds poi(nt!KeServiceDescriptorTable) + 112*4
    8050f0f4 805e81cc nt!NtWriteFile
    8050f0f8 805e8a94 nt!NtWriteFileGather
    8050f0fc 80635c50 nt!NtWriteRequestData
    8050f100 8065ff93 nt!NtWriteVirtualMemory 

    在nt!NtWriteFile设置断点就可以断下,在Windbg中使用如下命令就可以看到结果。

    kd> bp /t 81e068e8 nt!NtWriteFile
    kd> k
    ChildEBP RetAddr
    f59347ac 8059994c nt!NtWriteFile
    f59347ac 7ffe0304 nt!KiSystemService+0x13b
    0006fa00 77f3f01c SharedUserData!SystemCallStub+0x4
    0006fa04 77e673b1 ntdll!ZwWriteFile+0xc
    0006fa64 010048e8 kernel32!WriteFile+0xf7
    0006fa9c 01004cb4 notepad!___PchSym_+0x6e8
    0006fad8 01002a1f notepad!___PchSym_+0xab4
    0006fafc 00000000 notepad!ServiceStarter+0x6b 

    上面列出了程序从用户空间转到系统空间的过程,那么怎么从系统空间中返回呢?堆栈指针保存在哪里?

    在nt!KiFastCallEntry中在进入nt!KiSystemServic前会保存0x7ffe0304到ECX,并压入堆栈。0x7ffe0304就是用户态的返回地址,对应着SharedUserData!SystemCallStub函数中的Ret指令。

    在调用返回时,会把堆栈POP到ECX中,再调用SYSEXIT。当CPU执行SYSEXIT指令时,CPU回到用户态,并且:

    • 把CS设置成(SYSENTER_CS_MSR的内容+16),这实际上是KGDT_R3_CODE。
    • 把寄存器EDX的内容复制到EIP。
    • 把SS设置成(SYSENTER_CS_MSR的内容+24),这实际上是KGDT_R3_DATA.
    • 把寄存器ECX的内容复制到ESP。

    kd> u nt!KiSystemCallExit2
    nt!KiSystemCallExit2:
    80599ae0 5a pop edx
    80599ae1 83c408 add esp,8
    80599ae4 59 pop ecx
    80599ae5 fb sti
    80599ae6 0f35 sysexit

  • 相关阅读:
    Anagram
    HDU 1205 吃糖果(鸽巢原理)
    Codeforces 1243D 0-1 MST(补图的连通图数量)
    Codeforces 1243C Tile Painting(素数)
    Codeforces 1243B2 Character Swap (Hard Version)
    Codeforces 1243B1 Character Swap (Easy Version)
    Codeforces 1243A Maximum Square
    Codeforces 1272E Nearest Opposite Parity(BFS)
    Codeforces 1272D Remove One Element
    Codeforces 1272C Yet Another Broken Keyboard
  • 原文地址:https://www.cnblogs.com/Quincy/p/1698604.html
Copyright © 2020-2023  润新知