標籤:

淺談.NET程序集安全簽名

TestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=769a8f10a7f072b4n

如果你能看懂上面的代碼行是什麼意思,那麼你可能是一個.NET開發人員。同時你也可能知道結尾處的十六進位字元串表示的是一個公共密鑰令牌,這是一個表示程序集具有強名稱簽名的標識。 但你知道如何計算這個令牌嗎? 或者你知道強名稱簽名的結構嗎? 在這篇文章中,我將詳細介紹強名稱的工作原理及其缺點。 我們還將看看基於證書的簽名,最後,我們將檢查程序集驗證簽名的過程。

強名稱和公共密鑰令牌

一個有效的強名稱簽名可以確保收件人收到的程序集沒有被篡改。同時,它還可以作為給定的程序集的唯一地標識。雖然,它沒有說明關於簽名者身份的任何信息。有兩部分程序集二進位代碼在強名稱簽名驗證過程中發揮著作用。

第一部分是公共密鑰,它是程序集元數據中的#Blob流的一部分(下面的屏幕截圖中顯示了dnSpy窗口的一部分):

下圖中列出了構建公鑰塊的元素:

用於唯一地引用程序集的公共密鑰令牌是以公共密鑰的SHA-1哈希的低8位元組的十六進位並以反向位元組順序表示的。對於我本文中測試使用的程序集來說,公共密鑰令牌等於:769a8f10a7f072b4(SHA-1(00 24 00 00 0c 80 … c8 8a c1 b1)= 9aa4de0a96ada8d83d6d7678b472f0a7108f9a76)。

第二部分是程序集內容哈希後的RSA簽名。 在計算這個哈希值之前,我們需要用零填充文件的以下位元組:認證簽名條目(我們稍後會得到它),強名稱塊和PE頭校驗和。 之後,簽名會存儲在PE文件的text節中,其文件地址為保存在COR20頭中的偏移量:

為了計算RSA簽名,我們需要擁有與上一張圖片中列出的公共密鑰相對應的私鑰。 如果你想看看實現驗證的C#代碼,你可以查看dnLib庫中的StrongNameSigner.cs文件。

在研究了強名稱簽名結構之後,現在讓我們關注我們可以用來創建強名稱簽名結構的工具。 我們從生成.snk文件開始,該文件將存儲RSA密鑰的詳細信息(如果你對.snk文件格式的詳細信息感興趣,請查看我的010編輯器模板):

sn.exe -k 2048 TestLib.snkn

我們應該保存好生成的.snk文件的密文。 接下來,根據我們的方案,我們可以使用C#編譯器(csc.exe)或程序集鏈接器(al.exe)。 這兩者都接受 /keyfile參數,我們為這個參數提供了我們剛才生成的.snk文件的路徑,例如。

csc.exe /keyfile:TestLib.snk /t:library TestLib.csn

此命令將基於文件內容的SHA-1的哈希值生成簽名。 現在,SHA-1被認為不足以進行安全哈希操作,強烈建議使用SHA-2摘要演算法。 要使用SHA-2哈希來對我們的程序集簽名,我們首先需要提取.snk文件的公鑰部分:

sn.exe -p TestLib.snk TestLibPubKey.snk sha256n

然後延遲私鑰簽名過程(強名稱簽名塊將被填充為零,強名稱簽名標誌不會被置位)。 csc.exe和al.exe都接受 /delaysign+參數:

csc.exe /keyfile:TestLibPubKey.snk /delaysign+ /t:library TestLib.csn

最後,我們需要使用私鑰重新簽名程序集:

sn -Ra TestLib.dll TestLib.snkn

如果你有一個強名稱的程序集,並希望遷移簽名,請看看這篇文章。

驗證碼簽名

驗證碼簽名(Autheticode signature,),顧名思義,是用於驗證程序集的所有者。它還用於保護程序集的完整性。簽名的大小和位置存儲在PE可選頭中:

Force Integrity標誌(我在圖中已經標記了),用於強制載入器始終檢查給定程序集的簽名(針對載入受保護的進程的驅動程序和模塊,Windows會跳過簽名驗證)。 對於本地代碼,有一個特殊的鏈接器選項來啟用此特性。 我沒有在csc.exe或al.exe中找到這樣的參數選項以及使用dnSpy來設置這個標誌位(我需要首先延遲簽名程序集的過程,設置此標誌,並重新進行簽名)。

要創建驗證碼,我們需要擁有包含私鑰的證書(需要.pfx格式)。 為了測試目的,自簽名證書也是有效的(除非你設置了強制完整性的標誌),但對於發布部署的情況,你最好應該從受信任的提供者那裡獲取一個證書。 使用證書文件對程序集簽名的命令示例如下所示:

signtool sign /v /ph /fd sha256 `n/f .fileSignature.pfx `n/p {certificate-password} `n/t http://timestamp.verisign.com/scripts/timstamp.dll .TestLib.dlln

記住在創建驗證碼之前創建強名稱簽名。

簽名驗證

.NET從3.5版本開始,在程序集被載入到一個完全信任的應用程序域時不會執行強名稱簽名驗證。這基本上意味著在完全信任的應用程序域中,我們可以用一個只包含公鑰的程序集來替換一個具有強名稱的程序集,並且沒有人會注意到程序集被替換。我們可以通過在配置文件中啟用運行時的bypassTrustedAppStrongNames屬性,或者通過將HKLMSOFTWAREMicrosoft.NETFramework鍵中的AllowStrongNameBypass值設置為零(在64位系統上可以使用Wow6432作為32位應用程序)來更改此行為。

即使已經有以上這些設置選項保證程序集不被替換,但仍然有一種方法可以將部分簽名的程序集載入到我們的應用程序域中。在延遲程序集簽名過程時,可以使用此繞過機制。在開發期間,我們通常不想在每個構建的程序中對程序集進行完全簽名(私鑰放在在一個地方才能保持安全性),但同時,我們又希望程序集具有強名稱的行為。這可以通過在HKLMSoftwareMicrosoftStrongNameVerification下的註冊表中添加我們的程序集名稱和公共密鑰令牌來實現,例如:HKLMSoftwareMicrosoftStrongNameVerificationTestLib,8FCE6031CC56162D,或使用sn命令的-Vr選項。註:只有系統管理員可以修改驗證列表。

當涉及Authenticode驗證時,.NET僅在PE頭中的強制完整性標誌被設置時才會執行驗證。

幸運的是,我們不需要依賴自動驗證;我們可以自己執行驗證。我們用來對程序集簽名的相同工具,為我們提供了驗證它們的方法。要檢查強名稱簽名,我們可以使用sn -vf(-f選項強制 sn檢查簽名,即使它在註冊表中已被禁用)。將密鑰文件作為運行參數是可選的,但是建議在我們不使用Authenticode時使用此參數。用法示例:

sn -vf TestLib.dll TestLib.snkn

對於Authenticode驗證,我們可以使用signtool或sigcheck。 示例調用可能如下所示:

signtool verify TestLib.dlln(add /pa if you are using a self-signed root certificate)nsigcheck -i TestLib.dlln(-i will show the certificate chain used to sign the assembly)n

Sigcheck還可以遞歸地掃描目錄並驗證所有找到的二進位文件。我建議你看看它的幫助文檔,可以找到更多有趣的運行選項(例如把二進位的哈希值提交到VirusTotal)。

簡單的總結

由於很容易跳過簽名驗證,你可能會懷疑二進位文件簽名的整個概念。考慮一下,載入時的驗證不是那麼重要。如果一個惡意的人獲得了對二進位文件的寫許可權,即使對該文件進行簽名也不能保護你不受TA的活動的影響。但是…簽名程序集是用於證明程序集中的代碼是合法的的唯一的方法,並且沒有被篡改過(我鼓勵你使用兩個方法:強名稱簽名和Authenticode)。我們應該在客戶端接收到二進位文件(這通常由安裝程序完成)後執行第一次驗證 ——以確保在傳輸過程中沒有被篡改。接下來,在二進位文件所在的文件夾中設置有效的訪問許可權是非常重要的 —— 只有授權的人才能修改文件。最後,每當我們的應用程序出問題時,我們應該要求客戶端在填寫錯誤報告之前進行驗證簽名 ——只有這樣我們才能確定該錯誤的確是由我們造成的。

本文參考來源於lowleveldesign,如若轉載,請註明來源於嘶吼: 淺談.NET程序集安全簽名 更多內容請關注「嘶吼專業版」——Pro4hou

推薦閱讀:

造輪子進度(2017.07.26)
debian(kali Linux) 安裝net Core
.NET 開源兩年了

TAG:NET | 信息安全 |