今天在这里主要想了解一下Windows如何从ring3下的Win32 API转到ring0下的Kernel Routine。
以NtReadFile为例:
kd> u ntdll!NtReadFile (Win 2003 SP1)
ntdll!ZwReadFile:
7c821b78 b8bf000000 mov eax,0BFh;(系统调用号)
7c821b7d ba0003fe7f mov edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c821b82 ff12 call dword ptr [edx]
可以看到NtReadFile被WinDBG自动转成了ZwReadFile,其实这两个函数都在ntdll中导出,实际指向的是同一个地址(7c821b78)。在kernel模式下对应的Routine也叫NtReadFile
kd> u nt!NtReadFile
nt!NtReadFile [e:/windowsresearchkernel-wrk/wrk-v1.2/base/ntos/io/iomgr/read.c @ 90]:
808e8552 6a58 push 58h
808e8554 68b8318080 push offset nt!GUID_DOCK_INTERFACE+0x34c (808031b8)
808e8559 e852c9f8ff call nt!__SEH_prolog (80874eb0)
808e855e 33f6 xor esi,esi
808e8560 8975e0 mov dword ptr [ebp-20h],esi
808e8563 8975d0 mov dword ptr [ebp-30h],esi
…………
但是kernel中也有一个叫ZwReadFile的函数
kd> u nt!ZwReadFile
nt!ZwReadFile [E:/WindowsResearchKernel-WRK/WRK-v1.2/base/ntos/ke/i386/sysstubs.asm @ 1644]:
8082cbd4 b8bf000000 mov eax,0BFh;(系统调用号)
8082cbd9 8d542404 lea edx,[esp+4]
8082cbdd 9c pushfd
8082cbde 6a08 push 8
8082cbe0 e8a55b0500 call nt!_KiSystemService (8088278a)
8082cbe5 c22400 ret 24h
这个函数其实是个stub,不做真正的事情,最终仍然要调用NtReadFile,所以内核中的NtReadFile才是真正干活的有用的函数!!
回到ntdll下的NtReadFile(ZwReadFile),0BFh是win 2003下的系统调用号(系统调用号其实就是一个索引,后面会讲到)。SharedUserData 是操作系统为每个进程提供的个共享数据结构,里面存放有很多重要的系统信息,如TickCount、系统时间、SystemRoot等……
其在DDK定义为:
#define KI_USER_SHARED_DATA 0xffdf0000
#define SharedUserData ((KUSER_SHARED_DATA * const) KI_USER_SHARED_DATA)
他在内核中的地址是0xffdf0000,操作系统通过共享映射把这个结构以只读方式映射到每个进程的0x7ffe0000(2G边界以下128K)的地方。
每个进程用户空间的0x7ffe0000都以只读方式映射到相同的物理页面上,而这个物理页面上就是KUSER_SHARED_DATA结构
的数据,因此操作系统上的每个进程都有一个这个结构,而操作系统只需要维护这一个结构就行了。
kd> dt KUSER_SHARED_DATA
nt!KUSER_SHARED_DATA
+0x000 TickCountLowDeprecated : Uint4B
+0x004 TickCountMultiplier : Uint4B
+0x008 InterruptTime : _KSYSTEM_TIME
+0x014 SystemTime : _KSYSTEM_TIME
+0x020 TimeZoneBias : _KSYSTEM_TIME
+0x02c ImageNumberLow : Uint2B
+0x02e ImageNumberHigh : Uint2B
+0x030 NtSystemRoot : [260] Uint2B
+0x238 MaxStackTraceDepth : Uint4B
+0x23c CryptoExponent : Uint4B
+0x240 TimeZoneId : Uint4B
+0x244 LargePageMinimum : Uint4B
+0x248 Reserved2 : [7] Uint4B
+0x264 NtProductType : _NT_PRODUCT_TYPE
+0x268 ProductTypeIsValid : UChar
+0x26c NtMajorVersion : Uint4B
+0x270 NtMinorVersion : Uint4B
+0x274 ProcessorFeatures : [64] UChar
+0x2b4 Reserved1 : Uint4B
+0x2b8 Reserved3 : Uint4B
+0x2bc TimeSlip : Uint4B
+0x2c0 AlternativeArchitecture : _ALTERNATIVE_ARCHITECTURE_TYPE
+0x2c8 SystemExpirationDate : _LARGE_INTEGER
+0x2d0 SuiteMask : Uint4B
+0x2d4 KdDebuggerEnabled : UChar
+0x2d5 NXSupportPolicy : UChar
+0x2d8 ActiveConsoleId : Uint4B
+0x2dc DismountCount : Uint4B
+0x2e0 ComPlusPackage : Uint4B
+0x2e4 LastSystemRITEventTickCount : Uint4B
+0x2e8 NumberOfPhysicalPages : Uint4B
+0x2ec SafeBootMode : UChar
+0x2f0 TraceLogging : Uint4B
+0x2f8 TestRetInstruction : Uint8B
+0x300 SystemCall : Uint4B
+0x304 SystemCallReturn : Uint4B
+0x308 SystemCallPad : [3] Uint8B
+0x320 TickCount : _KSYSTEM_TIME
+0x320 TickCountQuad : Uint8B
+0x330 Cookie : Uint4B
+0x334 Wow64SharedInformation : [16] Uint4B
所以ntdll!NtReadFile中的7ffe0300,就是上面红色部分,这个值是系统启动时设置的,它就是函数KiFastSystemCall
kd> u KiFastSystemCall
ntdll!KiFastSystemCall:
7c82ed50 8bd4 mov edx,esp;edx->进入内核前ring3栈
7c82ed52 0f34 sysenter;进入内核
ntdll!KiFastSystemCallRet:
7c82ed54 c3 ret
ret(KiFastSystemCall) <--edx,esp
ret(NtReadFile)
. (参数)
.
.
.
.
进入内核前ring3栈
sysenter后就进入内核了,那么到达内核的哪里呢?查看Intel文档
也可以用winDBG查看:
kd> rdmsr 174
msr[174] = 00000000`00000008 (IA32_SYSENTER_CS)
kd> rdmsr 175
msr[175] = 00000000`fa027000 (IA32_SYSENTER_ESP)
kd> rdmsr 176
msr[176] = 00000000`80882850 (IA32_SYSENTER_EIP) ;KiFastCallEntry
所以进入内核后的函数是KiFastCallEntry,CS代码段选择子为08h,ESP指向的内核栈叫DPC栈,这些值是全局不变的!
先回顾以下保护模式下的知识:
Index (Bits 3 through 15) — Selects one of 8192 descriptors in the GDT or LDT. The processor multiplies the index value by 8 (the number of bytes in a segment descriptor) and adds the result to the base address of the GDT or LDT (from the GDTR or LDTR register, respectively).
在windows ring3下:
cs=0000001b (00011011) ;GDT,18
ds=00000023 (00100011) ;GDT,20
ss=00000023 (00100011) ;GDT,20
fs=0000003b (00111011) ;GDT,38
在windows ring0下:
cs=00000008 (00001000) ;GDT,08
ds=00000023 (00100011) ;GDT,20
ss=00000010 (00010000) ;GDT,10
fs=00000030 (00110000) ;GDT,30
可见用的都是GDT,事实上windows几乎不用LDT。
-------------------------------------------------------------------------------
Sel. Base Limit DPL P G Description
-------------------------------------------------------------------------------
0008 00000000 FFFFFFFF 0 P 4Kb Execute/Read ;CS r0
0010 00000000 FFFFFFFF 0 P 4Kb Read/Write ;SS r0
0018 00000000 FFFFFFFF 3 P 4Kb Execute/Read ;CS r3
0020 00000000 FFFFFFFF 3 P 4Kb Read/Write ;DS r3,r0 ; SS r3
0028 80042000 000020AB 0 P 1b 32-Bit TSS (Busy)
0030 FFDFF000 00001FFF 0 P 4Kb Read/Write ;FS r0
0038 7FFDE000 00000FFF 3 P 1b Read/Write, accessed ;FS r3
0040 00000400 0000FFFF 3 P 1b Read/Write
0048 00000000 00000000 0 NP 1b Reserved
0050 808945B0 00000068 0 P 1b 32-Bit TSS (Available)
0058 80894618 00000068 0 P 1b 32-Bit TSS (Available)
0060 000230C0 0000FFFF 0 P 1b Read/Write
0068 000B8000 00003FFF 0 P 1b Read/Write
0070 FFFF7000 000003FF 0 P 1b Read/Write
0078 80400000 0000FFFF 0 P 1b Execute/Read
0080 80400000 0000FFFF 0 P 1b Read/Write
0088 00000000 00000000 0 P 1b Read/Write
0090 00000000 00000000 0 NP 1b Reserved
0098 00000000 00000000 0 NP 1b Reserved
00A0 00000000 00000000 0 NP 1b Reserved
00A8 00000000 00000000 0 NP 1b Reserved
00B0 00000000 00000000 0 NP 1b Reserved
00B8 00000000 00000000 0 NP 1b Reserved
00C0 00000000 00000000 0 NP 1b Reserved
00C8 00000000 00000000 0 NP 1b Reserved
00D0 00000000 00000000 0 NP 1b Reserved
00D8 00000000 00000000 0 NP 1b Reserved
00E0 00008003 0000F100 0 NP 1b Reserved
00E8 00000000 0000FFFF 0 P 1b Read/Write
00F0 80827E8C 000003B7 0 P 1b Execute-Only
00F8 00000000 0000FFFF 0 P 1b Read/Write
GDT Table
待续,见下节……