1 引言
為了保護自己的軟體的技術核心不被他人輕易盜用,軟體開發人員使用了各種加密技術來保障軟體的著作權不被侵犯,殼便是我們常用的一種軟體保護手段。對於Win32 中軟體加殼技術已經有非常成熟的商業產品,然而,對於.NET環境下軟體,由於.NET程式的編譯結果不是機器語言代碼,而是一種MSIL中間代碼,因此不能使用傳統的加殼技術。 目前 ,專門對.NET軟體實施加殼的商業軟體主要有MaxtoCode,另外,SafeNet公司也推出了其軟體保護產品聖天狗最新的外殼工具,滿足了軟體開發商的一大願望。聖天狗外殼加密工具可以自動完成對可執行檔的加密過程,從而讓開發商快速方便地完成軟體加密和授權管理的工作。
本文介紹了.NET環境下一種簡單的軟體加殼技術,該技術使用了數位簽章、MSIL代碼混淆、加密等技術,可達到高強度的軟體保護。與MaxtoCode相比,這種技術的優點是:可以防止軟體非法拷貝,針對 計算 機硬體“指紋”授權(指紋:即硬體資訊中終身不變的識別號,如CPU和硬碟的序號等,下同),對於 網路 版,可以防止非本伺服器的用戶端訪問服務。2 認識“殼”
殼是對加密軟體的一個形象的比喻,顧名思義,殼是軟體外部的一件“外衣”,是軟體的保護屏障。有了它,惡意攻擊者就無法在對軟體反組譯碼後,直接找到軟體的核心代碼。殼是一段程式,它先於程式運行,殼在運行後就獲得了該軟體控制權,利用其保護功能對軟體進行安全保護。
殼的工作原理大致是:先運行殼程式,殼將加密的主程式(主程式即原來的待加殼的程式,下同)代碼解密到記憶體中,運行其中相應功能,並將程式的控制權交還給主程式。
本文中 研究 的加殼技術正是基於這種原理,只是殼也是用.NET編寫的,加密與解密密鑰與使用者的機器硬體指紋有關,而不是固定的密鑰,所以可以防止軟體非法拷貝,達到更高的保護強度。3 本加殼技術的原理
先將原來的主程式編譯成DLL,再編寫一個殼程式,編譯成EXE,並將程式的運行入口(即main函數)移到殼中來,由殼開始執行整個程式。首先,可以用自編的加密工具,在軟體發布前將所有DLL檔案分別進行加密(輸入特定的密鑰),在殼載入DLL時臨時將其解密到記憶體中,並載入運行。由於解密DLL的結果只存在於記憶體,所以攻擊者無法獲得解密後的DLL,除非他能找到DLL的解密密鑰。同時,為了避免攻擊者 分析 外殼程式的邏輯,從中尋找解密密鑰,還可以將殼編譯成EXE檔案後,再用第三方軟體進行混淆(如:XenoCode)或加密(如:MaxtoCode),這樣攻擊者將無法瞭解DLL加密的演算法及處理邏輯。從而,更有效地保護主程式DLL。加殼與脫殼的原理1所示。圖 1 NET軟體加密型加殼的原理4 軟體發布與使用者註冊流程
為了推廣軟體,開發商一般會將軟體的試用版放在網上,讓使用者自由下載試用,使用者基本滿意後再註冊正式版。
軟體發布與使用者註冊的流程2所示。
5 軟體發布與使用者註冊的實現5.1 註冊申請
為了達到軟體防拷貝的功能,開發商必須為不同的使用者製作不同的安裝檔案,一套程式只能在一台機器上運行。為此,開發商在製作安裝檔案時,必須取得軟體將要啟動並執行目標機器的指紋和使用者的單位名稱,對於有 應用 程式伺服器的網路版軟體,只需要取得應用伺服器的指紋。指紋資料可以由使用者使用特定的程式取得,並通過簡訊或郵件的方式告知開發商。
指紋的提取有兩種 方法 :其一,通過軟體的試用版。試用版中設計申請註冊的模組,使用者通過此模組提取原生指紋資料。軟體的試用版檔案的製作不是針對目標機器的指紋製作的,可以在任何機器上運行。為了防止Cracker找到DLL的解密密鑰後,將試用版破解。通常可以對試用版軟體佈建功能限制(如:去掉部分關鍵代碼),這樣即使試用版被破解,也無法投入正式的應用。
方法二:使用專用於註冊申請的程式。對於網路版的應用服務程式,如果沒有使用者介面,或無須試用的使用者,只能使用由開發商提供的專用註冊程式來提取機器指紋。5.2 計算註冊碼
這種加殼技術本來可以省去輸入註冊碼手工註冊這個過程,因為每一個發布的版本只能在指定的機器上運行,但為了定製使用者單位資訊以及對使用者數進行限制,還是要有註冊過程的。註冊碼是由使用者單位、機器指紋及使用者數限制等資訊經過加密處理後得到的,處理可以用自製的註冊碼計算工具來實現。5.3 加密DLL檔案
本加殼技術的核心之一是加密DLL檔案,加密過程可以用自製的加密工具完成。密碼編譯演算法可以選擇.NET架構中提供任何密碼編譯演算法或者自行設計密碼編譯演算法。演算法可以不用公開的演算法,因為加密解密都是在自己的程式中進行。因此,此方案的安全性完全可以由開發商自己保證,而不依賴於第三方。5.4 製作安裝盤
製作之前,只需用專為此使用者加密的DLL檔案及授權檔案等替換安裝工程中相應的檔案,再產生安裝盤。安裝盤中可以單獨存放一份加密DLL檔案和授權檔案,以備客戶升級正式版時使用者直接拷貝。
一般而言,安裝檔案中不能直接包含註冊碼或授權檔案的,但在這種技術下,可以將授權檔案打包到安裝盤中,因為,即使安裝檔案被複製,也無法在非授權的機器上運行。5.5 正式版安裝
對於沒有安裝過試用版的機器,可以直接使用安裝盤安裝正式版。對安裝過試用版的機器,可以用安裝盤中的正式版檔案替換相應檔案即可變成正式版。5.6 使用者註冊
調用正式版中“協助”à“關於”à“註冊”功能,輸入註冊碼或選擇授權檔案進行註冊。由於安裝檔案中包含授權檔案,也可以在正式版首次運行時,通過授權檔案自動註冊,免去了手工註冊的過程。6 程式運行時脫殼的實現
脫殼實際上是將加密的程式碼解密並載入到記憶體程式區,脫殼需要特定的解密密鑰或特定的解密演算法。對於較簡單的程式,如果只有一兩個DLL,可以由殼程式進行一次性脫殼,全部放在記憶體中,這沒有什麼技術上的難度,只是記憶體消耗較多。對於有多個DLL的程式,DLL不一定都要使用,有時可能只用其中部分,所以沒有必要一次脫殼,全部佔據在記憶體中,可以根據需要來脫殼。脫殼涉及到DLL解密、DLL調用請求的捕獲等技術。6.1 DLL的解密
一般的加殼技術使用與使用者無關的密鑰,密鑰是固定中殼代碼中的,所以脫殼可以在任何一台機器上實現,無法實現軟體防拷貝。而在本方案中,DLL加密金鑰與使用者 計算 機硬體指紋相關,當然解密密鑰也不是殼代碼中固定的,而需要臨時從目標電腦上提取指紋產生密鑰,才能解密,所以脫殼只能在授權的電腦上進行,從而可以很好地防止軟體的非法拷貝。
解密密鑰的是由硬體指紋產生的,指紋的提取與申請註冊時提取指紋的演算法相同,並且,指紋通過相同的保密演算法轉換後產生加密解密密鑰。因此,指紋的提取演算法存在於殼和專用的註冊申請程式以軟體的試用版中,為了防止這些演算法的破解,需要使用第三方工具(如:XenoCode,MaxtoCode)對這些程式的MSIL代碼進行混淆或加密。對於 網路 版軟體,解密密鑰最好是由殼臨時從 應用 程式伺服器擷取;但為了簡單起見,也可以將解密密鑰存入授權檔案中,授權檔案經加密後隨用戶端軟體一起安裝,脫殼時從授權檔案中提取解密密鑰。
DLL的解密只能在記憶體中進行,不能產生臨時檔案,避免Cracker截獲解密後的DLL。這就要用到檔案流與加密流的技術,脫殼的部分代碼如下:public static Assembly asmload(string asmName){ Assembly asmsvr = null;FileStream fsr = null;byte[] byVec=new byte[16], byKey=new byte[32];//asmName待載入的程式集名,由參數帶入string toLoad=AppDomain.CurrentDomain.BaseDirectory+asmName +"e.dll";if ( ! File.Exists( toLoad ) ) return null;fsr = new FileStream( toLoad, FileMode.Open, FileAccess.Read );byte[] rawAssembly = new byte[ fsr.Length ];//提取機器指紋並產生DES加密金鑰與初向量createDesKeyVec( ref byVec,ref byKey );SymmetricAlgorithm des=SymmetricAlgorithm.Create();CryptoStream encStream=new CryptoStream(fsr, des.Create Decryptor ( byKey, byVec), CryptoStream Mode.Read );//讀取並解密到到緩衝區encStream.Read( rawAssembly, 0, (int)fsr.Length );encStream.Close();fsr.Close();asmsvr = AppDomain.CurrentDomain.Load(rawAssembly );}6.2 DLL調用請求的捕獲
根據需要來脫殼也就是當程式集被調用時,臨時脫殼並載入,程式集一旦載入,以後需要調用其中的功能時就可以直接從記憶體中運行,這就既避免了記憶體的浪費又不會 影響 程式運行速度。關鍵是程式集的調用不一定從殼中調用,可以從任何一個已經啟動並執行程式集中調用,怎麼才能截獲程式集的調用請求呢?
首先要瞭解應用程式定義域,它由AppDomain對象來表示,為執行Managed 程式碼提供隔離、卸載和安全邊界。多個應用程式定義域可以在一個進程中運行;但是,在應用程式定義域和線程之間沒有一對一的關聯。多個線程可以屬於一個應用程式定義域,儘管給定的線程並不局限於一個應用程式定義域,但在任何給定時間,線程都在一個應用程式定義域中執行。每當程式運行時,便會自動建立應用程式定義域。AppDomain執行個體用於載入和執行程式集(Assembly),AppDomain 類實現一組事件,這些事件使應用程式可以在載入程式集、卸載應用程式定義域或引發未處理的異常時進行響應。本方案中就是通過事件AssemblyResolve來捕獲程式集調用請求的。
實現 方法 是:首先,在殼的main()函數中註冊事件AssemblyResolve的響應代碼,形如:AppDomain.CurrentDomain.AssemblyResolve += new
ResolveEventHandler( CurrentDomain_AssemblyResolve );
然後,再編寫一段事件響應代碼,來實現程式集脫殼與載入。這樣,在調用任何程式集時,就可以直接調用了,因為程式集的脫殼會自動進行。下面是事件響應的部分代碼:/// <returns>返回找到的或臨時載入的程式集</returns>private static Assembly CurrentDomain_AssemblyResolve (object sender, ResolveEventArgs args){ Assembly ret = null; try{ AppDomain dm=(AppDomain)sender; string dllName= args.Name.Split(',')[0]; //用xx開頭的檔案表示加密過和DLL,區別於其它的DLL if (dllName.StartsWith("xx")&&!dllName.EndsWith ("resources") ) ret = asmload( dllName ); } catch (Exception ae ) { MessageBox.Show("載入資料集" + args.Name + "時出錯" ); } return ret;}7 總結
以上軟體保護方案的安全效能由DES演算法(若採用)和機器指紋的安全性決定。DES演算法的安全主要決定於對密鑰保護。另外,由於密鑰來自於機器指紋,而指紋資料來自於機器硬體資訊,雖然,取指紋的演算法要隨軟體外殼程式一起發布,但從指紋到密鑰要經過自己設計的保密演算法來轉換,且保密演算法不公開,所以演算法本身是安全的。因此,密鑰的安全還取決於指紋提取演算法的安全性和指紋轉密鑰的演算法安全性,而它們的安全性又取決於第三方加密或混淆的強度了,這是本方案所無法控制的,這也許就是此方案安全性最薄弱的環節。
對於用商業加殼軟體的攻擊較多,所以一旦商業加殼技術被破解,用它加密過的軟體便沒有安全了。但對於自編的加殼技術,加密技術可以自行設計(保密),並且可以隨時調整,而且,使用者數不多,因此,攻擊者比較少,用這種技術加密的軟體安全性也就相對更好。使用本文中的加殼技術,可以更有效地抵抗拷貝、反編譯、分發序號、註冊機各種常用的軟體破解方法。然而,這種自編加殼技術雖然安全,但軟體分發起來卻十分複雜,這也是此方案的美中不足之處。