-
windows7内核分析之x86&x64第二章系统调用
2.1内核与系统调用
上节讲到进入内核五种方式 其中一种就是 系统调用 syscall/sysenter或者int 2e(在 64 位环境里统一使用 syscall/sysret 指令,在 32 位环境里统一使用 sysenter/sysexit 在 compatibility 模式下必须切换到 64 位模式,然后使用 syscall/sysret 指令 注释:32位cpu是x86模式 也叫legacy模式 再说清楚点 就是包含了实模式:可以执行以前的16位程序 也包含了保护模式:可以执行32位的程序 64位cpu是long模式:分为两种 64位模式:只执行64位的程序和compatibility模式:可以执行x86模式的程序 老式的cpu不支持 不提供sysenter指令,只能由int 2e模拟中断方式进入内核,调用系统服务)这两者什么区别呢?
1,Int 2e速度慢 首先从TSS中加载内核堆栈的ss esp->保存5个寄存器的现场(ss esp eip eflags cs)->然后还要去IDT中查找isr,这个过程消耗的时间太多
2,sysenter 提供了三个MSR寄存器 分别是SYSENTER_CS_MSR SYSENTER_EIP_MSR SYSENTER_ESP_MSR 分别指示了内核对应处理例程的cs选择子(函数所在段的选择子 通过选择子找到GDT 从逻辑地址转换为线性地址 最后访问线性地址 会根据四级表转换为物理地址)和内核函数地址和内核堆栈地址 这样就省去了查找idt表 TSS段 获得内核函数地址和内核堆栈地址 节省了大量时间
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是X64的syscall 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
syscall 的内核入口点是 KiSystemCall64() ,系统在 KiInitializeBootStructures() 里对 syscall/sysret 执行环境进行了设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|
MSR_STAR 寄存器里的值被设为 23001000000000h,它意味着:
SYSCALL_EIP 为 0
SYSCALL_CS 为 0x10
SYSRET_CS 为 0x23
在 SYSRET_CS 中,SYSRET_CS.RPL = 3 返回的权限级别是 3 级(用户代码)。
MSR_CSTAR 寄存器被设为 nt!KiSystemCall32 (fffff800`03cc4c00) 地址值,这是为了 compaitibility 模式代码调用而设置的。
MSR_LSTAR 寄存器被设为 nt!KiSystemCall64 (fffff800`03cc4ec0) 地址值,是为 64-bit 模式而准备的。CSTAR 寄存器为 compatibility 模式下的代码提供 rip 值,当 processor 在 comatibility 模式下运行时,执行了 syscall 指令,此时 rip 值将从 MSR_STAR 寄存器中加载。请记住:只能在 AMD 的 processor 使用 compaitibility 模式下的调用。照顾通用性,为了在 Intel 和 AMD 的 processor 上都能够使用 fast call 功能,操作系统的设计者应该要避免在 comaptibility 模式下使用 syscall 指令。前面提到过,建议在 compatibility 模式下先切换到 64-bit 模式后,再执行 syscall 指令
1 2 3 4 5 6 |
|
MSR_SFMASK 寄存器设为 4700h,意味着:
NT DF IF TF
这些 rflags 寄存器中的标志位在进入 KiSystemCall64() 后会被清 0
在 long mode(win7 x64 ) 下,当执行 syscall 指令时,当前的 rflags 寄存器值被保存在 r11 寄存器,processor 在执行 syscall 时,准备的目标执行环境中,rflags 将会根据 SFMASK 寄存器的值进行设置:如果 SFMASK 寄存器的某一位置为 1,那么 rflags 寄存器中相应的位将会被清 0,置为 0 时,rflags 寄存器中相应位不变
它的逻辑 C 描述为:
rflags = rflags & (~sfmask);
你应该在系统服务例程先保存 r11 值(原来的 rflags 寄存器值),以便 sysret 执行返回时可以恢复原来的 rflags 值
那么,对于要进入系统调用的代码来说:
1 2 3 4 5 |
|
执行 syscall 后,rcx 会保存返回值,因此应该要保存 rcx 原来的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是X86的sysenter 讲解 参考总结++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
|
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++以下是int 2e 讲解 参考总结+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 2 3 4 5 6 7 8 |
|
2.2系统调用的内核入口KiSystemService()
上面讲到老的cpu是通过int 2e 模拟进入内核 而不是新cpu 直接支持快速系统调用
那么KiSystemService()就是int 2e的处理函数 进入这个函数之前 cpu会自动读取TR寄存器 找到TSS段 读取里面的ss esp 就是内核堆栈地址了 完后往这个地址保存用户空间的堆栈 eflags cs eip 具体开头已经说了 这里就不废话了
nt!KiSystemService:
83e8880e 6a00 push 0 //①是一种类似TrapFrame 进入这个函数之前 cpu会自动压入各种ss esp eip等等 你可以理解为一种上下文 这里push 0 是因为函数最后要把他的值存入eax当做状态码返回 另外就是操作系统 把这个Frame 定义了一个结构 然而异常发生后 cpu会自动压入一个错误码 中断和自陷都没有 所以为了通用 这个结构里面不管是异常还是中断进入内核 结构第一个元素都是0 占个位置的意思
83e88810 55 push ebp//保存栈帧
83e88811 53 push ebx//函数下面要用到ebx 所以这里先保存一下
83e88812 56 push esi//函数下面 要用到esi保存kthread 所以这先保存
83e88813 57 push edi//函数下面 要用edi引用调用号 所以这也要保存
83e88814 0fa0 push fs//内核fs指向kpcr 用户层指向TEB 所以这里提前保存 因为进了内核 fs要改变了
83e88816 bb30000000 mov ebx,30h//r0的fs指向kpcr fs和cs一样都是段选择子 所以这六30h是GDT的kpcr的索引
表②:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
|
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
为什么内核中不能直接调用NtReadFile()呢?
因为一方面NtReadFile不是导出函数 另外就是 调用这些函数 需要系统堆栈上有框架 但是内核中直接调用的话 肯定没有为本次调用的框架存在 可能是其他框架 比如自陷框架 中断异常框架 所以导致直接调用NtReadFile出错 可以通过调用ZwReadFile()调用
以下是x86的
1 2 3 4 5 6 7 |
|
以下是x64的
1 2 3 4 5 6 7 8 9 10 11 12 |
|
最近身体出问题了 所以进度很慢 希望大家多多支持
- jpg 改 rar
原文地址:https://www.cnblogs.com/kuangke/p/9524228.html