.NET體系中的源程序安全問題(2)
發(fā)布時(shí)間:2008-08-07 閱讀數(shù): 次 來源:網(wǎng)樂原科技
二、中間語(yǔ)言
為了了解在用VB.NET構(gòu)造工程的過程中發(fā)生了什么事情,我們需要?jiǎng)?chuàng)建一個(gè)生成代碼和程序集時(shí)使用的示例工程:打開VS.NET,新建一個(gè)Visual Basic工程,在窗體中加入一個(gè)文本標(biāo)簽(Label),然后把文本標(biāo)簽的Text屬性改成“Good Bye Visual Basic 6.0”(如圖1),把這個(gè)應(yīng)用命名為GoodByeVB6。
圖1
在深入.NET體系之前,我們需要了解一些有關(guān).NET的邊緣知識(shí)和術(shù)語(yǔ)。首先,IL(中間語(yǔ)言)并不是什么新概念,VB、C++編譯器生成和利用IL已經(jīng)有數(shù)年的歷史,只不過很少有人公開談?wù)撍驗(yàn)樗帉懳臋n。與過去編譯應(yīng)用的方法相比,.NET最大的改變之一就在于編譯器所生成的代碼不同。除了名字之外,新的MSIL與VB6編譯器的IL很少有類同之處。因此,如果你以前曾經(jīng)接觸過IL,現(xiàn)在還得從頭開始學(xué)習(xí)。請(qǐng)參見圖2,它是GoodByeVB6應(yīng)用的MSIL代碼片段:
圖2
這個(gè)代碼片段設(shè)置了一個(gè)8字節(jié)的棧,然后把this指針壓棧,調(diào)用get_Label1方法。接下來,代碼把要設(shè)置的標(biāo)簽文本壓入堆棧,然后調(diào)用setText方法。
傳統(tǒng)的CPU利用寄存器和棧完成所有工作。CLR所提供的執(zhí)行引擎只有一個(gè)棧,它的操作過程非常類似于一個(gè)逆波蘭表示法計(jì)算器。如果某個(gè)過程調(diào)用具有多個(gè)參數(shù),執(zhí)行引擎將在發(fā)出調(diào)用之前把參數(shù)壓棧。函數(shù)調(diào)用的返回值也通過棧傳遞。
MSIL中的局部變量很容易識(shí)別,它們用.locals關(guān)鍵詞聲明。如果符號(hào)存在的話,你將看到變量名字;否則,你看到的將是V_1、V_2之類的變量:
.locals init ([0] int32 x,
[1] int32 y,
[2] float64 z,
[3] class System.String Vb_t_string_0)
ldarg指令把參數(shù)裝入棧,ldc指令把數(shù)字常量裝入棧,stloc指令把值保存到合適的局部變量:
//000064: Dim x As Integer = 100
IL_0001: ldc.i4.s 100
IL_0003: stloc.0
在這個(gè)例子中,常量100被作為4字節(jié)整數(shù)壓入棧,隨后這個(gè)值被保存到第1個(gè)局部變量。關(guān)于MSIL指令的完整說明,請(qǐng)參見IL程序員參考的ILinstrset.doc文件。
本文的所有MSIL輸出都以GoodByeVB6應(yīng)用的調(diào)試版本為基礎(chǔ)。非調(diào)試版本雖然不帶代碼行和變量名字,但仍能夠提供大量有用的信息。在查看MSIL代碼的時(shí)候,調(diào)試符號(hào)雖然重要,但不是必不可少的。
當(dāng)我們運(yùn)行編譯器時(shí),它生成的不是我們今天熟悉的執(zhí)行文件,而是一個(gè)程序集(Assembly)。程序集是一個(gè)文件的集合,程序集中的文件可以作為單一整體進(jìn)行部署。在當(dāng)前的Windows體系中,我們可以把單個(gè)執(zhí)行文件看成一個(gè)程序集。但從更嚴(yán)格的意義上來說,程序集聚合了執(zhí)行文件和它的所有支持文件,包括DLL、圖形、資源以及幫助文件。
一般地,一個(gè)程序集至少由兩個(gè)文件構(gòu)成:執(zhí)行部分,manifest(英文單詞原意:載貨清單,乘客名單)。manifest是程序集內(nèi)所有文件的清單。程序集內(nèi)的可執(zhí)行部分又分開稱為模塊(Module)。從概念上說,模塊對(duì)應(yīng)著DLL或者EXE文件;除了父程序集所包含的元數(shù)據(jù)(Metadata)之外,每一個(gè)模塊都包含元數(shù)據(jù)。程序集是當(dāng)前可移植執(zhí)行文件格式(Portable Executable,PE)的一個(gè)增強(qiáng)版本。
如圖3所示,文件的開頭是標(biāo)準(zhǔn)的PE頭。文件內(nèi)部包含了CLR頭,CLR頭的后面是把代碼裝入進(jìn)程空間所必需的描述數(shù)據(jù)——即元數(shù)據(jù)。元數(shù)據(jù)為執(zhí)行引擎提供了大量信息,其中包括:如何裝載模塊,需要哪些支持文件,如何裝載支持文件,如何與COM以及.NET運(yùn)行時(shí)環(huán)境交互。另外,元數(shù)據(jù)還描述了模塊或者程序集所包含的方法、接口以及類。元數(shù)據(jù)所提供的信息使得JIT編譯器能夠編譯并運(yùn)行模塊。同時(shí),元數(shù)據(jù)暴露了有關(guān)應(yīng)用的大量?jī)?nèi)部信息,使得從反匯編IL獲取有價(jià)值的代碼更加方便。
圖3
使用.NET代碼的核心問題在于受管理代碼。受管理代碼是專門為在CLR控制之下運(yùn)行而編寫的代碼,它可以用VB.NET、C#以及C++等語(yǔ)言創(chuàng)建,但C++是唯一能夠創(chuàng)建.NET平臺(tái)非受管理代碼的語(yǔ)言。我們無法用VB6為.NET平臺(tái)創(chuàng)建非受管理代碼,這是因?yàn)樵赩B6中我們把代碼編譯成i386指令而不是IL代碼。正如使用VB.NET,如果你要使用受管理代碼,你只能把代碼編譯成IL。
現(xiàn)在我們來看看使用這種新的MSIL代碼有哪些優(yōu)點(diǎn)。如果代碼編譯成了MSIL,我們可以在任何支持CLR的平臺(tái)上安裝和運(yùn)行這些代碼。就目前來說,這一點(diǎn)可能不是很吸引人,因?yàn)楫?dāng)前支持.NET的平臺(tái)還很少:只有32位的Windows。但不久之后,64位平臺(tái)和.NET for Windows CE都將提供這方面的支持。把代碼編譯成MSIL格式使得我們能夠無縫地把應(yīng)用移植到所有這些平臺(tái)和未來的新平臺(tái)。
MSIL的另外一個(gè)優(yōu)點(diǎn)是:JIT編譯器在安裝應(yīng)用的目標(biāo)機(jī)器上把MSIL代碼編譯成機(jī)器指令,它能夠利用目標(biāo)機(jī)器的硬件特點(diǎn),根據(jù)平臺(tái)的具體情況對(duì)代碼進(jìn)行優(yōu)化。這一點(diǎn)很有用,例如,它能夠?yàn)槟繕?biāo)機(jī)器的特殊寄存器優(yōu)化代碼,或?yàn)槟繕?biāo)機(jī)器上帶有特殊處理器的硬件設(shè)備優(yōu)化操作代碼。請(qǐng)點(diǎn)擊VB6工程屬性窗口Compile選項(xiàng)卡中Advanced Optimizations按鈕了解更多信息。由于有了程序集中的元數(shù)據(jù),JIT編譯器知道代碼做些什么以及它支持哪些平臺(tái),從而能夠迅速地作出優(yōu)化決定、提高代碼的性能表現(xiàn)。
還有一個(gè)優(yōu)點(diǎn)涉及到.NET的兩個(gè)V:Validation(檢驗(yàn)),Verification(核查)。檢驗(yàn)是對(duì)模塊進(jìn)行的一系列檢查,確保元數(shù)據(jù)、MSIL代碼以及文件格式的一致性。不能通過這些檢查的代碼可能導(dǎo)致執(zhí)行引擎或者JIT編譯器崩潰。一旦模塊通過了檢驗(yàn),則代碼是正確的且可以開始運(yùn)行。
JIT編譯器把MSIL代碼轉(zhuǎn)換成機(jī)器代碼時(shí)對(duì)代碼進(jìn)行核查,它是對(duì)元數(shù)據(jù)進(jìn)行復(fù)查,保證程序不會(huì)訪問它不具有相應(yīng)許可的內(nèi)存或其他資源。經(jīng)過核查的代碼是類型安全的(Type-Safe)代碼。這種核查即使是在程序被直接編譯成機(jī)器代碼的時(shí)候也要進(jìn)行,但除非由JIT編譯器進(jìn)行核查,否則這種核查不是100%精確無誤,因?yàn)楹瞬榻Y(jié)果依賴于來自其他程序集的元數(shù)據(jù)。如果把源程序直接編譯成機(jī)器代碼,我們面臨著這樣一種危險(xiǎn):在目標(biāo)機(jī)器上的其他程序集發(fā)生了變化,從而導(dǎo)致程序不再類型安全。
使用JIT編譯器保證了檢驗(yàn)和核查是對(duì)所有相關(guān)程序集的當(dāng)前版本進(jìn)行。這些操作確保執(zhí)行程序總是類型安全,程序總是以合適的安全許可運(yùn)行。你可以用.NET SDK的PEVerify工具自己對(duì)代碼進(jìn)行檢驗(yàn)和核查。