1. 程式人生 > >【CLR】詳解CLR中的程序集

【CLR】詳解CLR中的程序集

技術分享 名稱 創建 分享 目錄名 str cut assembly dem

目錄結構:

contents structure [+]
  1. 程序集的簡介
  2. 為程序集分配強名稱
    1. 如何指定程序集的版本資源信息
    2. 如何對程序集簽名
  3. 全局程序集緩存
  4. 如何查看程序集的信息
  5. 強命名程序集防串改

1. 程序集的簡介

CLR支持兩種程序集:弱命名程序集和強命名程序集。弱命名和強命名程序集的結構完全相同。兩者的區別在於:強命名程序集使用發布者的公鑰/私鑰進行了簽名。這對秘鑰允許對程序集進行唯一性的標識、保護和版本控制,並允許程序部署到用戶機器的任何地方。

程序集可采用兩種方式部署:私有或全局。私有部署的程序集是指部署到應用程序基目錄或者某個子目錄下面。全局部署是指部署到一些公認位置的程序集。弱命名程序集只能私有部署,強命名程序集既可以私有部署也可以全局部署。

如下:

全局部署 私有部署
強命名程序集
弱命名程序集

2. 為程序集分配強名稱

要由多個應用程序訪問的程序集必須放到公認的目錄。另外,檢測到對程序集的引用時,CLR必須能自動檢查該目錄。

CLR支持對程序集進行唯一性標識的機制,這就是“強命名程序集”。強命名程序集共有4個重要特征,它們共同對程序集進行唯一性的標識:文件名(不計擴展名)、版本號、語言文化和公鑰。由於公鑰數字很大,所以經常使用從公鑰派生的小哈希值,稱為公鑰標記(public key token)。例如下面4個程序集標識字符串顯示了4個完全不同的程序集文件:

"MyTypes,Version=1.0.8123.0,Culture=neutral,PublicKeyToken=b77a5c561934e089"
"MyTypes,Version=1.0.8123.0,Culture="en-Us",PublicKeyToken=b77a5c561934e089"
"MyTypes,Version=2.0.8123.0,Culture=neutral,PublicKeyToken=b77a5c561934e089"
"MyTypes,Version=1.0.8123.0,Culture=neutral,PublicKeyToken=a0371q001382p23a"

2.1 如何指定程序集的版本資源信息

在PE文件中能嵌入標準的Win32版本資源。在生成程序集時,應該使用特性來設置各種版本資源字段,每種語言生成程序集版本資源的特性代碼的都不一樣,下面介紹使用VB語言生成PE文件時如何指定版本資源信息。

首先在Microsoft Visual Studio上新建一個VB的類庫項目。在項目的本地文件夾下,可以找到AssemblyInfo.vb的文件
技術分享圖片
然後打開這個文件,可以看到其中的內容如下(筆者經過了修改,並且只貼出了部分代碼):

<Assembly: AssemblyTitle("名稱")>
<Assembly: AssemblyDescription("描述")>
<Assembly: AssemblyCompany("公司")>
<Assembly: AssemblyProduct("產品")>
<Assembly: AssemblyCopyright("Copyright © Microsoft 2018")>
<Assembly: AssemblyTrademark("商標")>
<Assembly: ComVisible(False)>
<Assembly: AssemblyVersion("1.0.0.0")>
<Assembly: AssemblyFileVersion("1.0.0.0")>

然後在Visual Studio中重新生成文件,找到生成的PE文件,右鍵“屬性”->"詳細信息",就可以看到如下圖所示:
技術分享圖片

通過上面的方式指定程序集有點繁瑣,好在Visual Studio為我們提供了便利,只需要在Visual Studio中右擊項目,選擇“屬性”->"應用程序"->"程序集信息",就可以進行修改了。
技術分享圖片

2.2 如何對程序集簽名

在上面我們已經知道了,弱命名程序集經過簽名(使用公鑰和私鑰簽名)就會成為強命名程序集,下面介紹如何簽名程序集。

首先使用SN.exe來獲得秘鑰,SN.exe程序一般在C:\Program Files (x86)\Microsoft SDKs\Windows的子目錄下面,下面使用SN.exe來創建一個包含公鑰、私鑰的文件。

sn -k MyCompany.snk

創建的MyCompany.snk是包含了一對公鑰和秘鑰的文件。公鑰的數字很大,如果願意的話,可以再次使用sn.exe來查看完整的公鑰值和公鑰標記值。只需要執行兩次sn.exe。
第一次使用-p創建只含公鑰的文件

sn -p MyCompany.snk MyCompany.PublicKey sha256

第二次使用-tp顯示公鑰標記和公鑰本身。

sn -tp MyCompany.PublicKey 

然後會得到類似如下的輸出:

Microsoft(R) .NET Framework 強名稱實用工具 版本 4.0.30319.17929
版權所有(C) Microsoft Corporation。保留所有權利。

公鑰(哈希算法: sha256):
002400000c800000940000000602000000240000525341310004000001000100f70211026bf0c5
04ec93bd52e3c7c14373e18f65d385e7151fc2de3559b50668cc8f4d5eae739745ead0d0e16036
d2aa033b8ec9366e92cf2d90a5a0d02ae00ee7a915df4e1eeb01d74a473063b741c0473c345254
211060134f626c30e3bb1057e43fd56ee04810713ba05101a32d591278d6a0497d14db70e488a3
1a731cbe

公鑰標記為 47ea4c468f699f0b

在創建好公鑰/私鑰標記對後,就可以利用具體語言的具體命令來創建強命名程序集了。

3. 全局程序集緩存(GAC)

我們已經知道了如何創建強命名程序集了,強命名程序集可以私有部署,也可以全局部署(部署到GAC中)。

這裏先介紹一下全局程序集緩存(Global Assembly Cache,GAC)的概念:由多個應用程序訪問的程序集必須放到公認的目錄,而且CLR在檢測到該程序集的引用時,必須知道檢查該目錄,這個公認的位置就是GAC。

GAC一般在如下的位置:

%SystemRoot%\Microsoft.NET\Assembly

GAC的目錄是結構化的:其中包含許多子目錄,子目錄名稱用算法生成。永遠不要將程序集手動復制到GAC目錄,相反應該用工具來完成。

開發和測試時在GAC中安裝命名程序集最常用的工具是GACUtil.exe,該工具一般在 C:\Program Files (x86)\Microsoft SDKs\Windows 的子目錄下面。

4. 如何查看程序集的信息

我們已經知道了一個程序集由四部分標識,分別為:文件名(不計擴展名)、版本號、語言文化和公鑰標記(公鑰很大,一般不使用)。弱命名程序集沒有公鑰(自然也沒有公鑰標記),若命名程序集經過公鑰/私鑰對簽名後,就稱為了強命名程序集。

當我們知道一個程序集後,如果獲得這些標識呢?在程序集中,沒有直接給我們相關的信息,這些信息都是分散的,需要我們去分別查找。

使用ILDASM反編譯一個程序集,點擊“視圖”->“元信息”->“顯示!”,然後查找“Assembly”的定義部分(Assembly一般位於文檔最底部),如圖:
技術分享圖片
其中Name代表文件名;Version代表版本號;Locale代表語言文化,如果空,則表明是中性語言(neutral);Public Key則表示是公鑰。

現在我們知道了程序集全部信息,圖片中的public Key是完整的公鑰而不是公鑰標記。可以按照下面的過程得到公鑰標記,

利用sn.exe程序的 -T參數
比如:

sn -T System.Data.dll

然後就可以看到如下的輸出:

Microsoft(R) .NET Framework 強名稱實用工具 版本 4.0.30319.17929
版權所有(C) Microsoft Corporation。保留所有權利。

公鑰標記為 b77a5c561934e089

除此之外,若強命名程序集安裝在GAC中的話,直接通過文件夾名稱的後綴就可以看出:
技術分享圖片

5. 強命名程序集防篡改

用私鑰對程序集進行簽名,並將公鑰和簽名嵌入程序集,CLR就可驗證程序集未被修改或損壞。程序集安裝到GAC時候,系統對包含清單的那個文件內容進行哈希處理,將哈希值與PE文件中嵌入的RSA數字簽名進行比較(在用公鑰解除了簽名之後)。如果兩個值完全一致,表明文件內容未被篡改。此外,系統還對程序集的其他文件的內容進行哈希處理,並將哈希值與清單文件的FileDef表中存儲的哈希值進行比較。任何一個哈希值不匹配,表明至少有一個文件被篡改,程序集將無法安裝到GAC。

應用程序需要綁定到程序集時,CLR根據被引用程序集的屬性(名稱、版本、語言文化和公鑰)在GAC中定位查找該程序集。如果被引用的程序集不在GAC中,CLR會查找引用程序的基目錄,然後查找應用程序配置文件中的任何私有路徑,如果還找不到就會拋出System.IO.FileNotFoundException成一行。

如果強命名程序集文件從GAC之外的位置加載(通過應用程序的基目錄,或者通過配置文件中的codeBase元素),CLR會在程序集加載後比較哈希值。也就是說,每次應用程序執行並加載程序集時,都會對文件進行哈希處理,以犧牲性能為代價,保證程序集文件中的內容沒有被篡改。

下面這張圖可以幫助理解該流程:
技術分享圖片
通過這張圖片可以清楚的看出,MyLibrary.dll文件進行強命名簽名時,會把MyLibrary.dll文件進行哈希值處理,並且將結果值用秘鑰進行處理,再將結果值嵌入到CLR頭部中。把公鑰嵌入到元數據中。

當MyLibrary.dll被加載時,程序會用清單中的公鑰對CLR頭中的數字簽名進行解密操作,和再次對MyLibrary.dll進行哈希處理,然後比較這兩個結果值,如果這兩個值不一致的話,說明有文件已經被篡改,將會阻止運行。

【CLR】詳解CLR中的程序集