Windows NT系統介紹

Windows NT系統介紹


前言
最近實驗室學長在報告中,介紹了許多Linux系統的運作方式。其中包括Linux80中斷取得系統服務。在這次討論之後,我開始想到NT系統中,透過2E中斷來達成許多系統服務的呼叫。
在記憶體管理方式,NTWindows 9X有一些不同點。舉例來說,在User Mode的記憶體大小,從Windows 9X3GB縮小為NT中的2GB。以往我們在Windows 9X中常見的Kernel32.dllUser32.dll..等放在2GB—3GB這塊共用記憶體空間的DLL,在NT中都放到2GB以下(每個Process私有的空間中)。如圖(),而在Inside Windows NT Second Edition曾提到Windows NT4.0 Service Pack3中提供的功能,可讓使用者應用程式使用3GB的虛擬記憶體空間,而剩下的1GB虛擬記憶體空間則保留給作業系統。

()
NT2E中斷
NT中常見到使用2E中斷的程式包括GDI32.DLL、 User32.DLLNTDLL.DLL(NTDLL.DLL中以Nt為首的函式名稱為我們主要討論的對象)…2E中斷使用的例子如下:
NTDLL.DLL中的NtAlertThread函式
B8 07 00 00 00 mov eax7
8D 54 24 04 lea edxdword ptr [esp+4]
CD 2E int 2Eh
C2 04 00 ret 4

其中Eax代表了所使用的函式編號,Edx則存入了呼叫NtAlertThread 時所使用引數的記憶體位址<參閱圖()>,以便2E中斷的處理者可以透過Eax得知所呼叫的系統服務為何,而這個系統服務所需用到的引數則透過Edx來取得。
()
首先,在下圖()中我將先以一個實際的例子來表達整個呼叫的流程
<Step 1>以如下的程式碼
void main()
{
Beep(3002000);
}
函式Beep()是由Kernel32.dll所提供的。
<Step 2>我們可以發現在Beep()函式執行後會進入到kernel32.dllBeep()函式所在的記憶體位址(可透過本程式的Import Table得知Beep()函式在記憶體中的位址)
<Step 3>Beep()函式的記憶體空間中,我們發現它呼叫了許多NTDLL.DLL所提供的函式。
<Step 4>列出了Beep()所呼叫NTDLL.DLL提供的函式。
<Step 5>NTDLL.DLL中的NtCreateFile()函式為例,由Eax得知它要求編號為17h的系統服務,由Ret 2Ch得知NtCreateFile()的引數大小為44 bytes
<Step 6>2E中斷觸發後,程式由User Mode轉到在Kernel Mode NTOSKRNL.EXE所提供的KiReleaseSpinLock函式,此函式的不同段落分別為其它中斷所對應的處理者,如
Int 2A  KiReleaseSpinLock+偏移位址0D86h
Int 2C  KiReleaseSpinLock+偏移位址0F80h
Int 2E  KiReleaseSpinLock+偏移位址0900h
在本文的稍後,也將以實例證明WinICE攔截了Windows NT部份的中斷,其中也包括了2E中斷。
(),可以看到NTDLL.DLL的函式NtCreateFile()呼叫2E中斷經由KiReleaseSpinLock(NTOSKRNL.EXE),分別呼叫了Kernel Mode
0008:80167000 NtCreateFile
0008:80161EC0 IoCreateFile
0008:8018A79C ObOpenObjectByName ……………..等函式
由此可以了解, KiReleaseSpinLock(NTOSKRNL.EXE)身為轉運者的角色,它把我們所要求的系統服務轉移到對應處理函式。


()

()
不過,並不是NTDLL.DLL中,每個名稱以Nt為首的函式都會透過2E中斷進入Kernel Mode,如

NtCurrentTeb

77F69284: 64 A1 18 00 00 00 mov eaxfs:[00000018]
77F6928A: C3 ret
就只簡單的把值傳回給呼叫者。
因此,是否使用2E中斷需依函式對系統服務需求而定。在圖()<Step 1>中,我把在NTDLL.DLL中名稱以Nt為首的函式所對應的Eax值整理出來(請參閱隨件附上”NTDLL2E中斷資料”),由Ret的值我們可以知道引數大小。在圖()<Step 2>則秀出部份在NTDLL.DLL中使用2E中斷的情形。圖()<Step 3><Step 4>User32.DLL使用2E中斷的情形,我分別以GetMessageA GetDC為例(Inside Windows NT Second Edition 101 Figure3-8有一張不錯的架構圖,或參閱我所畫的圖()簡圖)
有一件事情滿有趣的,在NT Kernel Mode的函式竟也有部份透過2E中斷來取得系統服務。既然都在Kernel Mode為何要使用該函式,不直接呼叫在Kernel Mode的函式位址,卻觸發2E中斷來取得所要的服務,這樣不是會多花一段時間嗎?(不過以現在CPU效能,也許感覺不出來….^_^)

()

()
在此我舉一個NTOSKRNL.EXE中使用到2E中斷的例子,由圖<><Step 1>可以看到在NTOSKRNL.EXE中的ZwAllocateVirtualMemory函式透過2E中斷來取得服務。
MOV EAX0A
LEA EDX[ESP+04]
INT 2E
ret 18
我想讀者們一定會好奇同樣是2E中斷那在NTDLL.DLL中使用Eax0Ah的函式名稱是什麼呢?
比對了一下在NTDLL.DLL Eax0Ah的函式是NtAllocateVirtualMemory
77F57624: B8 0A 00 00 00 mov eax0Ah
77F57629: 8D 54 24 04 lea edxdword ptr [esp+4]
77F5762D: CD 2E int 2Eh
77F5762F: C2 18 00 ret 18h
ZwAllocateVirtualMemory(NTOSKRNL)NtAllocateVirtualMemory(NTDLL)這兩個分別在Kernel ModeUser Mode使用2E中斷取得服務的函式,最後都會經由KiReleaseSpinLock(NTOSKRNL)到函式NtAllocateVirtualMemory(NTOSKRNL)的記憶體空間。
ZwAllocateVirtualMemory(NTOSKRNL)如果直接呼叫NtAllocateVirtualMemory(NTOSKRNL),由於兩者都在Kernel Mode,並不會造成違反特權等級的情況發生。但它卻不這樣做而又去觸發2E中斷,繞了一圈才到NtAllocateVirtualMemory(NTOSKRNL)的記憶體位址,其它有關NTDLL.DLLNTOSKRNL.EXE Int 2E部份的資料請參閱附上的檔案”NTDLL2E中斷資料
透過這些說明可以清楚的看到NT2E中斷的重要性,不論是上層的AP要透過2E中斷取得Kernel服務,連在Kernel的函式有些也會經由2E中斷來取得服務。

()
其實NTOSKRNL.EXE(Kernel Mode) Zw為首函式,如ZwFreeVirtualMemory,會與NTDLL.DLL(User Mode) Nt為首的函式,如NtFreeVirtualMemory都透過如下的程式碼
B83A000000 MOV EAX0000003A

8D542404 LEA EDX[ESP+04]

CD2E INT 2E
C21000 RETN 0010
經由KiReleaseSpinLock(NTOSKRNL.EXE),再執行NtFreeVirtualMemory(NTOSKRNL.EXE)來完成整個動作,如下圖()
相信讀者們看到這兒,心中的疑惑仍然存在吧!為何在Kernel ModeZwFreeVirtualMemory(NTOSKRNL.EXE)不直接去呼叫NtFreeVirtualMemory(NTOSKRNL.EXE)卻透過2E中斷來達成呢?Inside Windows NT Second Edition 第二章 中有一個NT函式名稱的表,或許可以對解釋這問題有一點幫助,如Zw為首的函式名稱說明為:”Mirror entry point for functions beginning with Nt that assume the previous caller was in kernel mode”,筆者用破破的英文程度跟各位說明一下,Zw為首的函式是Nt為首函式對應的一個進入點(即呼叫Zw….後會進入Nt….)且假設它的呼叫者在Kernel Mode,也就是如果在Kernel Mode 使用Zw為首的函式,在執行的過程中會交給在Kernel Mode裡 Nt為首的函式來執行,而在Inside Windows NT Second Edition中對Nt為首的函式說明為”Windows NT system services (most of which are exported as Win32 functions)”,這句話應該是說Nt為首的函式為NT提供的系統服務,大部份也提供為Win32的函式,透過這段話或許比較容易了解NtFreeVirtualMemory(NTDLL.DLL)User Mode執行後會進入Kernel ModeNtFreeVirtualMemory(NTOSKRNL.EXE)了。

()
PE格式的NT Driver
Windows NT  Windows 9XDriver在檔案格式上是不相同的,Windows NTDriverWin 32應用程式的格式同為PE格式。在圖(),我用UltraEditTCPIP.SYS的檔頭資料,明顯的可以得知NT Driver具有PE格式的檔頭。在稍後我也將針對NTDriver在記憶體的運作做一個簡單的說明。

()
在圖()<Step 1>中,我以NT DDK\SRC\GENERAL\SIMPLE所附的程式碼
status = IoCreateDevice(
DriverObject
0
&uniNtNameString
FILE_DEVICE_UNKNOWN
0
FALSE
&deviceObject
);
為例。<Step 2>為經過編譯後的結果。<Step 3>為程式碼放到記憶體後的結果,從0008:F99CD2EF CALL DWORD PTR [F99CD204]
這行程式碼,可以得知IoCreateDevice函式的記憶體位址紀錄在Import Table所屬的記憶體位址F99CD204<Step 4>中,我們取得了0008:F99CD204這個記憶體位址的資料為80161B08,這個位址正是在NTOSKRNLIoCreateDevice函式所在的記憶體位址。


()
其實在寫這篇文章的過程中,遇到了一些滿有趣的事情。在圖(十一)<Step 1>函式IoCompleteRequest( Irp IO_NO_INCREMENT ),在我編譯成Driver後竟然指到記憶體的另一個函式IofCompleteRequest去了。剛開始有點驚訝以為是自己弄錯了,後來看到在NTDDK.H中有<Step 2>這樣的資料
#define IoCompleteRequest(ab) \
IofCompleteRequest(ab)
當場恍然大悟,不過為何要大費週章的把IoCompleteRequest換成IofCompleteRequest卻不以IofCompleteRequest來寫程式。不過反正這是在編譯時所花的時間,並不會影響到執行的效果。


(十一)
除了在圖(十一)所提到的情形之外,我也想到曾和一個朋友討論過的函式ExAllocatePool。如圖(十二)<Step 1>的程式碼
ExAllocatePool(PagedPoolCacheAligned259)
在經過編譯後會被換成具有三個引數的
ExAllocatePoolWithTag(ab‘ kdD’);
編譯後的程式碼如下
6844646B20 PUSH 206B6444
6803010000 PUSH 00000103
6A05 PUSH +05
FF1510020100 CALL DWORD PTR [Import Table紀錄ExAllocatePoolWithTag的位址]
我們也可以在圖(十二)<Step 2>中,發現這些有趣的現象,原本以為的函式名稱,或是引數的個數在經過編譯的過程中,可能會因為DDK對某些函式的處理(加工?)上的不同而有一些滿好玩的結果。老實說以前剛遇到這樣的結果時還會以為是自己的程式有問題,後來看了一下*.h的檔案才比較知道原因。



(十二)
WinICE所攔截的中斷
在圖(十三)<Step 1>中,在有WinICE的情況下透過IDT BaseAddress取得目前中斷表所在記憶體位址,由該位址我們推算出2E中斷閘描述器所在記憶體位址為
80462471 (2E中斷閘描述器值為71 24 08 00 00 EE 46 80)
而有關閘描述器說明如下
0–15 bit => Offset 0–15
16-31 bit =>Selector
32-36 bit=>Dword count
37-39 bit=>0
40-43 bit=>Type
44 bit=> S = 0
45-46 bit=>DPL
47 bit=>P
48-63 bit=>Offset 16-31
因此在<Step 2>中,得知2E中斷位址為0008:80462471,而此記憶體空間竟是在NTICE.SYS之中,咿….為何跳到WinICE的空間去了?
<Step 3>中,以WinICEidt指令取得2E中斷所在位址為0008:8013CBC0 這正是KiReleaseSpinLock (NTOSKRNL)所在空間。因此,我猜測WinICE2E中斷給攔截到ICE的處理函式去了。當然囉,如果在NT中每個觸發的2E中斷都能透過我們的話,可以做的工作就多了,可以在那裡過濾許多系統服務,甚至可以攔截要透過2E中斷來使用系統服務的程式。



(十三)
不過,如果只以這樣的資料,我就說ICE2E中斷攔截了,似乎還有些立場不足。因此我改了一個NT DDK 所附的範例,透過這個Driver在沒有WinICE的情況下,把當時中斷表的資料存成一個檔案,來加以驗證。如圖(十四)(這個DriverSource Code,及說明檔隨稿件附上),想必讀者也發現畫面上的資料並不是文字檔,因為我不是很清楚如何存成文字檔,試了一些方式仍不成功,所以就以這方式呈現了。
我透過SIDT指令得到中斷表的記憶體位址,並把有WINICE與沒WINICE時的中斷表比較,得知
在沒有載入WinICE時以下中斷所在記憶體位址為
Int 2d 記憶體位址:0008:8013ddfc
Int 2e 記憶體位址:0008:8013cbc0
Int 31 記憶體位址: 0008:806b8044
Int 33 記憶體位址: 0008:80645dc4
Int 34 記憶體位址: 0008:806b9044
Int 37 記憶體位址: 0008:8013c336
載入WinICE後以下中斷所在記憶體位址為
Int 2d 記憶體位址: 0008:8053c462
Int 2e 記憶體位址: 0008:8053c471
Int 31 記憶體位址: 0008:8053c480
Int 33 記憶體位址: 0008:8053c48f
Int 34 記憶體位址: 0008:8053c49e
Int 37 記憶體位址: 0008:8053c4bc
其它資料我把它們整理好了,放在所附的檔案中。



(十四)
▓除了2E中斷
NTDLL.DLL中其實還包括了2B中斷、2C中斷
:
NtSetHighWaitLowThread
77F57F7C: CD 2B int 2Bh
77F57F7E: C3 ret
NtSetLowWaitHighThread
77F58020: CD 2C int 2Ch
77F58022: C3 ret
或是USER32.DLL
IsWindow
……(省略)
77E52244 8B442404 MOV EAX,[ESP+04]
77E52248 CD2B INT 2B
其它像2D中斷、31中斷等等,都是NT所包含的中斷。
▓結語
NT中可以討論的東西相當多,這一次我針對了NT中常見的2E中斷來加以說明。2E中斷在NT系統中可以說是相當重要的,許多使用Kernel服務的呼叫都會透過它。對User Mode程式來說,少了2E中斷,將無法取得許多Kernel的服務,正因為它的重要性所以對想要了解NT運作的人來說,也是不可缺少的一環。
或許有人會這樣想說,既然許多的API後來都會透過2E中斷來實現,在寫程式時直接使用2E中斷是否會比較有效率。我曾與朋友談過這件事,不過後來我覺得如果在開發軟體的過程中,使用太多未公開的東西。在作業系統架構改變的過程中,這些服務可能會有所變動或是擴充,而造成應用程式在新版系統的不相容,如果使用作業系統提供的標準API,比較有可能在日後系統更新時享受到相容性的便利,不曉得各位讀者有何見解呢歡迎寫E-mail與我討論