Windows的特權保護
Windows的特權保護和處理器硬體的支援是分不開的。優先順序的劃分、指令的許可權檢查和超出許可權訪問的異常處理等是構成特權保護的基礎。
什麼是中斷?
中斷指當程式執行過程中有更重要的事情需要即時處理時(如串口中有資料到達,不及時處理資料會丟失,串列控制器就提交一個中斷訊號給處理器要求處理),硬體通過中斷控制器通知處理器。處理器暫時掛起當前啟動並執行程式,轉移到中斷處理常式中;當中斷處理常式處理完畢後,通過iret指令回到原先被打斷的程式中繼續執行。
什麼是異常?
異常指指令執行中發生不可忽略的錯誤時(如遇到無效的指令編碼,除法指令除零等),處理器用和中斷處理相同的操作方法掛起當前啟動並執行程式轉移到例外處理常式中。例外處理常式決定在修正錯誤後是否回到原來的地方繼續執行。
中斷和異常處理的方式是相同的。
實模式下的中斷或異常處理:實模式下的中斷和異常服務程式地址存放在中斷向量表中。中斷向量表位於實體記憶體中,每個中斷向量是一個xxxx:yyyy格式的地址,佔用4位元組。當發生n號異常或n號中斷,或者執行到int n指令的時候,CPU首先到記憶體n×4的地方取出服務程式的地址aaaa:bbbb;然後將標誌寄存器、中斷時的CS和IP壓入堆棧,接著轉移到aaaa:bbbb處執行(步驟②);在服務程式最後遇到iret的時候,CPU從堆棧中恢複標誌寄存器,然後取出CS和IP並返回。
保護模式下,中斷或異常處理往往從使用者代碼切換到作業系統代碼中執行。由於保護模式下的代碼有優先順序之分,因此出現了從優先順序低的應用程式轉移到優先順序高的系統代碼中的問題,如果優先順序低的代碼能夠任意調用優先順序高的代碼,就相當於擁有了高優先順序代碼的許可權。為了使高優先順序的代碼能夠安全地被低優先順序的代碼調用,保護模式下增加了“門”的概念。“門”指向某個優先順序高的程式所規定的進入點,所有優先順序低的程式調用優先順序高的程式只能通過門重新導向,進入門所規定的進入點。這樣可以避免低層級的程式碼從任意位置進入優先順序高的程式的問題。保護模式下的中斷和異常等服務程式也要從“門”進入,80386的門分為中斷門、自陷門和任務門幾種。
在保護模式下要表示一個中斷或異常服務程式的資訊需要用8個位元組,包括門的種類以及xxxx:yyyyyyyy格式的入口地址等。這組資訊叫做“中斷描述符”。這樣,中斷向量表就無法採用和實模式下同樣的4位元組一組的格式。保護模式下把所有的中斷描述符放在一起組成“中斷描述符表”IDT(Interrupt Descriptor Table)。IDT不再放在固定的地址00000h處,而是採用可程式化設定的方式,支援的中斷數量也可以設定。為此80386處理器引入了一個新的48位寄存器IDTR。IDTR的高32位指定了IDT在記憶體中的基址(線性地址),低16位指定了IDT的長度,相當於指定了可以支援的中斷數量。
1.8所示,保護模式下發生異常或中斷時,處理器先根據IDTR寄存器得到中斷描述符的地址,然後取出n號中斷/異常的門描述符,再從描述符中得到中斷服務程式的地址xxxx:yyyyyyyy,經過段地址轉換後得到服務程式的32位線性地址並轉移後執行。
由於保護模式下用中斷門可以從低優先順序的代碼調用高優先順序的代碼,所以不能讓使用者程式寫中斷描述符表,否則會引發安全問題(又想到了CIH病毒)。這樣就如關了窗子擋住蒼蠅,也擋住了微風,使用者的系統擴充程式也就不能像在DOS中一樣再用中斷服務程式的方式提供服務了。因為使用者程式根本沒有許可權將中斷地址指到自己的代碼中來。
在Windows中,作業系統使用動態連結程式庫來代替中斷服務程式提供系統功能,所以 Win32彙編中int指令也就失去了存在的意義。這就是在Win32彙編原始碼中看不到int指令的原因。其實那些調用API的指令原本是用int指令實現的。
上面的這段寫的真的很精彩。大學時學習數字邏輯,組合語言的時候很清楚的記得有中斷服務調用的說法,感覺從中斷到後來的Windows動態連結程式庫之間缺了什麼。也就糊裡糊塗的學下來了。這下算是明白了動態連結程式庫的由來。
80386的保護機制
80286之前的處理器只支援單任務,作業系統並沒有什麼安全性可言,電腦的全部資源套件括作業系統的內部資源都可以任憑程式員調用。但對於多任務的作業系統,某個搗亂的程式為所欲為令使所有程式都無法運行。所以80286及以上的處理器引入了優先順序的概念。80386處理器共設定4個優先順序(0~3)。0級是最進階(特權級);3級是最低級(使用者級);1級和2級介於它們之間。特權級代碼一般是作業系統的代碼,可以訪問全部系統資源;其他層級的代碼一般是使用者程式,可以訪問的資源受到限制。
80386採用保護機制主要為了檢查和防止低層級代碼的越權操作,如訪問不該訪問的資料、連接埠以及調用高優先順序的代碼等。保護機制主要由下列幾方面組成:
- 段的類型檢查——段的類型是由段描述符指定的,主要屬性有是否可執行,是否可讀和是否可寫等。而CS,DS和SS等段選取器是否能裝入某種類型的段描述符是有限制的。如不可執行檔段不能裝入CS;不可讀的段不能裝入DS與ES等資料區段寄存器;不可寫的段不能裝入SS等。如果段類型檢查通不過,則處理器會產生一般性保護異常或堆棧異常。
- 頁的類型檢查——除了可以在段層級上指定整個段是否可讀寫外,在頁表中也可以為每個頁指定是否可寫。對於特權級下的執行代碼,所有的頁都是可寫的。但對於1,2和3級的代碼,還要根據頁表中的R/W項決定是否可寫,企圖對唯讀頁進行寫操作會產生頁異常。
- 訪問資料時的層級檢查——優先順序低的代碼不能訪問優先順序高的資料區段。80386的段描述符中有一個DPL域(描述符優先順序),表示這個段可以被訪問的最低優先順序。而段選取器中含有RPL域(請求優先順序),表示當前執行代碼的優先順序。只有DPL在數值上大於或等於RPL值的時候,該段才是可以訪問的,否則會產生一般性保護異常。
- 控制轉移的檢查——在處理器中,有很多指令可以實現控制轉移,如jmp,call,ret,int和iret等指令。但優先順序低的代碼不能隨意轉移到優先順序高的代碼中,所以遇到這些指令的時候,處理器要檢查轉移的目的位置是否合法。
- 指令集的檢查——有兩類指令可以影響保護機制。第一類是改變GDT,LDT,IDT以及控制寄存器等關鍵寄存器的指令,稱為特權指令;第二類是操作I/O連接埠的指令以及cli和sti等改變中斷允許的指令,稱為敏感指令。試想一下,如果使用者級程式可以用sti禁止一切中斷(包括時鐘中斷),那麼整個系統就無法正常運行,所以這些指令的運行要受到限制。特權指令只能在優先順序0上才能運行,而敏感指令取決於eflags寄存器中的IOPL位。只有IOPL位表示的優先順序高於等於當前程式碼片段的優先順序時,指令才能執行。
- I/O操作的保護——I/O地址也是受保護的對象。因為通過I/O操作可以繞過系統對很多硬體進行控制。80386可以單獨為I/O空間提供保護,每個任務有個TSS(任務狀態段)來記錄任務切換的資訊。TSS中有個I/O允許位元影像,用來表示對應的I/O連接埠是否可以操作。某個I/O地址在位元影像中的對應資料位元為0則表示可以操作;如果為1則還要看eflags中的IPOL位,這時只有IOPL位表示的優先順序高於等於當前程式碼片段的優先順序,才允許訪問該I/O連接埠。
Windows的保護機制
在Windows下,作業系統運行於0級,應用程式運行於3級。因為Alpha電腦只支援兩個優先順序,為了便於將應用程式移植到Alpha電腦上,Windows作業系統不使用1和2級這兩個優先順序。
Windows作業系統充分利用80386的保護機制,所有和作業系統密切相關的東西都是受保護的。運行於優先順序3上的使用者程式有很多限制,只有在寫VxD等驅動程式的時候才可以使用全部資源。
在Win32彙編編程中要注意避免以下的越權操作(當然寫驅動程式不在此列):
- 顯而易見,所有的特權指令都是不可執行檔,如lgdt,lldt,lidt指令和對CRx與TRx等寄存器賦值。但是,讀取重要寄存器的指令是可以執行的,如sgdt,sldt和sidt等。
- Windows在頁表中把程式碼片段和資料區段中的記憶體頁賦予不同的屬性。程式碼片段是不可寫的,資料區段中也只有變數部分的頁面是可寫的。所以雖然可以定址所有的4 GB空間,但訪問超出許可權規定以外的東西還是會引發保護異常。
- 在Windows 98中,系統硬體用的I/O連接埠是受保護的,但其餘的則可以操作。如果使用者在機器中插了一塊自己的卡,用的是300h等系統未定義的連接埠,那麼在應用程式中就可以直接操作,但要操作3f8h(串口)和1f0h(硬碟連接埠)等系統已定義的連接埠就不行了。在Windows NT中,任何的連接埠操作都是不允許的。
如果違反了Windows規定的“保護條例”,那麼會引發保護異常,處理器會毫不猶豫地把控制權轉移到對應的例外處理常式中去。Windows會在處理常式中用一個很酷的“非法操作”對話方塊把使用者的程式判死刑,沒有一點迴旋的餘地!在Windows 9x中,系統有時會用一個藍螢幕來通知使用者程式試圖訪問不存在的記憶體頁。
如果程式調用的DLL中有錯,那麼錯誤還是會算在應用程式頭上,因為DLL的地址空間是被映射到應用程式的空間中去的。Windows 9x本身是32位和16位混合的作業系統,為了相容DOS和Win16程式,很多的保護措施做起來力不從心。所以系統內部反而常常出現越權操作,以至於藍螢幕不斷,這些就不是使用者應用程式自己的問題了。
摘自《Win32彙編教程》作者羅雲彬