1). 在操作系统调用Windows API时,无论是 Kernel32.dll 还是 User32.dll 或者是 GDI.dll 最终调用的都是 Ntdll.dll 中的函数进入R0(不调用R0的函数除外)
2). 我们以 WriteProcessMemory 函数为例子,其在 Kernel32.dll 中,我们查看其的代码实现,如下:发现其最终调用的是位于 Ntdll.dll 中的 NtWriteVirtualMemory 函数, 我们进入 Ntdll.dll 查询
3). 发现其并没有做任何处理,而是 Call 0x7FFE0300 函数, eax 的值是系统服务号(SSDT表的索引)
在这里我们需要了解R0和R3的共享内存块,也就是一个共享的结构体 _KUSER_SHARED_DATA, 其结构如下(微软目前公布的结构体成员如下):
ntdll!_KUSER_SHARED_DATA
+0x000 TickCountLow : 0xb3c
+0x004 TickCountMultiplier : 0xfa00000
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : 0x14c
+0x02e ImageNumberHigh : 0x14c
+0x030 NtSystemRoot : [260] 0x43
+0x238 MaxStackTraceDepth : 0
+0x23c CryptoExponent : 0
+0x240 TimeZoneId : 0
+0x244 Reserved2 : [8] 0
+0x264 NtProductType : 1 ( NtProductWinNt )
+0x268 ProductTypeIsValid : 0x1 ''
+0x26c NtMajorVersion : 5
+0x270 NtMinorVersion : 1
+0x274 ProcessorFeatures : [64] ""
+0x2b4 Reserved1 : 0x7ffeffff
+0x2b8 Reserved3 : 0x80000000
+0x2bc TimeSlip : 0
+0x2c0 AlternativeArchitecture : 0 ( StandardDesign )
+0x2c8 SystemExpirationDate : _LARGE_INTEGER 0x0
+0x2d0 SuiteMask : 0x110
+0x2d4 KdDebuggerEnabled : 0x3 ''
+0x2d5 NXSupportPolicy : 0x2 ''
+0x2d8 ActiveConsoleId : 0
+0x2dc DismountCount : 0
+0x2e0 ComPlusPackage : 0xffffffff
+0x2e4 LastSystemRITEventTickCount : 0
+0x2e8 NumberOfPhysicalPages : 0x1ff6c
+0x2ec SafeBootMode : 0 ''
+0x2f0 TraceLogging : 0
+0x2f8 TestRetInstruction : 0xc3
+0x300 SystemCall : 0x7c92e4f0
+0x304 SystemCallReturn : 0x7c92e4f4
+0x308 SystemCallPad : [3] 0
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : 0
+0x330 Cookie : 0x59d47d1
所谓共享内存块指的是任何一个R3的进程中都有一个线性地址和R0的一个线性地址指向了同一块物理页
User层(3环)地址为:0x7FFE0000
Kernnel层(0环)地址为:0xFFDF0000
虽然指向同一块物理页,但是3环只是可读,而0环却是可读可写
在回到之前 Call 0x7FFE0300函数,我们通过上面可以知道是 _KUSER_SJARED_DATA 结构体成员的
+0x300 SystemCall : 0x7c92e4f0(如果不支持 sysenter 指令,则会替换为中断门函数 KiIntSystemCall 地址)
用 WinDbg 查看对应函数
kd> u 0x7c92e4f0
ntdll!KiFastSystemCall
用 IDA 查看函数流程, edx 指向了 R3 的ESP
发现只是调用了一个汇编指令 sysenter
在这里我们要明白,在08年前,操作系统进入0环,都是通过 INT 0x2E 进入对应的0环的分发函数, 之后用 sysenter 指令替代了调用门,因为 sysenter 指令更加的高效
通过 sysenter 进入0环,系统默认会去 MSR 寄存器中获取对应的 CS(MSRAddr: 174H)、EIP(MSRAddr: 176H)、ESP(MSRAddr: 175H)(参考INTEL白皮书)
我们用WinDbg查看对应的数据, EIP对应的0环跳转函数如下:
但是任何提权都需要改变 CS、EIP、ESP、SS 四个寄存器的值,为何这里没有 SS 寄存器?
其实在08年后,操作系统规定在 CS 代码段描述符后就是 SS 数据段描述符, 例如: 在 GDT表 0x8 段选子后 + 0x8字节的位置就是 SS 数据段描述符描述符 0x10 段选子
所以可以认为 CS段描述符后 + 0x8 == SS段描述符后:
4).所以(支持sysenter模式下)R3进入0环流程如上
但是如果不支持 sysenter 指令(操作系统通过 CPUID 指令来判断是否支持 sysenter 指令), 则会调用 KiIntSystemCall 函数, 其函数流程如下:
通过 INT 0x2E 进入 0环,我们可以查看对应的0环函数, edx 指向R3的函数参数:
[培训]内核驱动高级班,冲击BAT一流互联网大厂工
作,每周日13:00-18:00直播授课