今天在這裏主要想了解一下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
待續,見下節……