最近一段時間不忙,閒下來的空閒時間,重讀了一下CLR的原理,回味一下有關程序集的的知識,順便練了一下手,學習致用,破解了若干個.NET平臺的軟件。以此來反觀.NET程序開發中,須要注意的一些問題。css
.NET平臺的編譯格式是依靠MSIL中間語言,運行時即時編譯(JIT)成CPU指令,對Win 32 的PE格式進行了擴展。程序集是自描述的,自己蘊藏了豐富的元數據信息。MSDN中有一段代碼例子,請參考下面的程序html
using System; using System.Reflection; public class Example { public static void Main() { // Get method body information. MethodInfo mi = typeof(Example).GetMethod("MethodBodyExample"); MethodBody mb = mi.GetMethodBody(); Console.WriteLine("\r\nMethod: {0}", mi); // Display the general information included in the // MethodBody object. Console.WriteLine(" Local variables are initialized: {0}", mb.InitLocals); Console.WriteLine(" Maximum number of items on the operand stack: {0}", mb.MaxStackSize); // Display information about the local variables in the // method body. Console.WriteLine(); foreach (LocalVariableInfo lvi in mb.LocalVariables) { Console.WriteLine("Local variable: {0}", lvi); } // Display exception handling clauses. Console.WriteLine(); foreach (ExceptionHandlingClause ehc in mb.ExceptionHandlingClauses) { Console.WriteLine(ehc.Flags.ToString()); // The FilterOffset property is meaningful only for Filter // clauses. The CatchType property is not meaningful for // Filter or Finally clauses. switch (ehc.Flags) { case ExceptionHandlingClauseOptions.Filter: Console.WriteLine(" Filter Offset: {0}", ehc.FilterOffset); break; case ExceptionHandlingClauseOptions.Finally: break; default: Console.WriteLine(" Type of exception: {0}", ehc.CatchType); break; } Console.WriteLine(" Handler Length: {0}", ehc.HandlerLength); Console.WriteLine(" Handler Offset: {0}", ehc.HandlerOffset); Console.WriteLine(" Try Block Length: {0}", ehc.TryLength); Console.WriteLine(" Try Block Offset: {0}", ehc.TryOffset); } } // The Main method contains code to analyze this method, using // the properties and methods of the MethodBody class. public void MethodBodyExample(object arg) { // Define some local variables. In addition to these variables, // the local variable list includes the variables scoped to // the catch clauses. int var1 = 42; string var2 = "Forty-two"; try { // Depending on the input value, throw an ArgumentException or // an ArgumentNullException to test the Catch clauses. if (arg == null) { throw new ArgumentNullException("The argument cannot be null."); } if (arg.GetType() == typeof(string)) { throw new ArgumentException("The argument cannot be a string."); } } // There is no Filter clause in this code example. See the Visual // Basic code for an example of a Filter clause. // This catch clause handles the ArgumentException class, and // any other class derived from Exception. catch(Exception ex) { Console.WriteLine("Ordinary exception-handling clause caught: {0}", ex.GetType()); } finally { var1 = 3033; var2 = "Another string."; } } } // This code example produces output similar to the following: // //Method: Void MethodBodyExample(System.Object) // Local variables are initialized: True // Maximum number of items on the operand stack: 2 // //Local variable: System.Int32 (0) //Local variable: System.String (1) //Local variable: System.Exception (2) //Local variable: System.Boolean (3) // //Clause // Type of exception: System.Exception // Handler Length: 21 // Handler Offset: 70 // Try Block Length: 61 // Try Block Offset: 9 //Finally // Handler Length: 14 // Handler Offset: 94 // Try Block Length: 85 // Try Block Offset: 9
重頭戲在這一行: MethodBody mb = mi.GetMethodBody(); 它返回方法的元數據的字節流。說通俗一點,它返回的是方法的源代碼。把這個返回的字節流轉換成MSIL指令,不是一件難事。MSDN中有描述,CodeProject上面有一篇文章,講解如何寫一個解釋方法,把MethodBody傳化爲方法的MSIL代碼,幾乎就是一個反編譯器的模型。算法
再去理解那句話:.NET程序集是自描述的,是否是理解更深入了一些。安全
熟悉MSIL語言。對照文檔手冊,邊讀邊看邊學。個人辦法是,想知道高級語言編譯時如何翻譯成MSIL的,找一本高級語言(C#,VB.NET)的入門手冊書,把代碼敲進去,編譯成程序集,再用反編譯器.NET Reflector一行一行對比看,進步神速。之因此要找入門書,是由於它會講解到高級語言的各個特性,反編譯時看到的MSIL代碼會更全面。服務器
軟件A學習
軟件A採用的保護措施是根據用戶購買的許可數量,分紅我的版,專業版,企業版。版本越高,能擁有的功能更高級。網站
好比我的版只能一天只能下載50個文檔,專業版一天能夠下載1000份文檔,企業版則不受限制。this
方法:跟蹤軟件啓動時,加載的程序集和執行的動做。.NET程序通常有兩個地方放程序文件,一是GAC,另外一個是當前目錄,或是當前目錄的子目錄(須要在配置文件中指定)。找到GAC中的文件,先把它拷貝到普通文件夾。加密
再拿.NET Reflector打開看看,若是能打開,看是否有strong name,如打不開,則用IL DASM反編它,看生成的IL文件中,是否有strong name的值。再開一下軟件,看看哪些地方會顯示註冊/未註冊,試用期等信息。通常有幾個地方會暴露軟件的保護方法:spa
1 直接在主窗體的標題欄中顯示,「軟件已註冊」,」軟件未註冊,還有29天試用「
2 在關於對話框中顯示軟件是否註冊,剩餘許可天數或次數。
再從IL代碼中追查,看它在哪些地方,保存當前的使用天數的數據。以個人追查經驗,多半是保存在註冊表中。因而開一個註冊表寫入監控程序,一下就知道它寫到什麼去了,再來解碼就容易不少。
軟件B
個人軟件註冊方式也是這樣作的,因此我對這種方式很是熟悉。是運用Xml 簽名文件,生成一個只讀的許可文件,看起來是文本文件,可是你不能修改,一有任何的修改,從新計算Hash值會,會驗證失敗。這種方式,個人QQ羣中的朋友都知道,用下面的代碼,從新生成一套密鑰匙對,替換程序集中的公匙,再用私匙生成一個註冊文件讓它驗證。
[TestMethod] public void SolutionValidationTest() { string publickey = RSACryptionHelper.GeneratePublickKey(false); string privateKey = RSACryptionHelper.GeneratePublickKey(true); }
1 替換策略 當程序集中有寫死一些基本信息,好比strong name的public token,xml signature的public key,這時,只有替換程序集中的這些元數據,才能破解成功。由於這些信息是全球惟一的,就像GUID字符串的值同樣,全世界再沒有任何一臺電腦能生成和他同樣的數據(public token,public/private key),應用替換方法。
2 重簽名策略。strong name通常都會配合代碼進行檢查,讓它不會輕易被破解。遇到這種狀況,能夠考慮移除如今的簽名,用本機從新生成的key給它簽名。若是能保證程序集和它引用的程序都是同樣的簽名,則匹配成功。到目前爲止,尚未看到一套程序,會有幾個不一樣的strong name同時存在於不一樣的程序集中。
3 rouding-trip策略。根據程序,生成MSIL,修改MSIL,再生成程序集。這裏涉及到修改MSIL代碼指令,可讓程序直接繞過驗證,或是不驗證,這種方式威懾力最大。根本不用考慮驗證這一回事,直接跳過,把驗證方法方法體所有刪除,第一句IL代碼爲nop,或是ret,直接返回。
4 代碼與反編譯結合策略。有時候面對程序集中五花八門的字符,徹底不理解它的含義,無從下手。
遇到這種狀況,它是應用了字符串加密技術。以其人之道,還其人之身,下面的幾行簡單的方法,破解文中的不可理解的字符:
Assembly assembly=Assembly.GetExecutingAssembly(); Type type=assembly.GetType("Class64"); MethodInfo mi=type.GetMethod("smethod_0"); mi.Invoke(null,new object [] { " ᓏᓒᓕᓎᒹᓊᓝᓑ "} );
再把這個方法作成一個GUI程序,依此對照文中亂碼字符,所有解碼。
5 直接編輯策略。程序集文件也是一個文件,編譯器以生成目標格式的方式生成這個文件,而咱們用到的文本,則是手工敲入字符生成,你能夠用十六進制文件去編輯它的值,依照規律便可。.NET 反編譯時,常常遇到的一個錯誤是的
This assembly does not contain a CLI header,This assembly is not a PE.NET format。藉助於PE格式知識,把添加進去的錯誤的元數據刪除便可。這裏有一個典型的例子,Visual Studio自己是不能夠生成多Module的程序集,一個程序集只能有一個Module,可是加密程序一般會給它加上多餘的Module,對照PE.NET格式標準,刪除多餘的節便可。
6 利用Mono.Ceil.dll主動修改程序集策略。第四個策略中我提到字符串有加密,反其道行之,我把全部調用該方法的地方,再運算一次,從新生成一次,便可破解字符串,再把從新生成的代碼寫成一個程序集文件。
7 應用密碼學算法策略。若是應用對稱加密,試着給一些隨機的錯誤的序列號給它,看看它是如何驗證序列號的,再將此方向反向,導出如何生成它能夠驗證的字符中。舉例說明,有一個軟件,它的序列號是這樣的
1234567890 =》 12 34 56 78 90 => 18 52 86 120 144
序列號1234567890,它把這個序列號以兩個爲一組,先後相鄰的2個,轉化爲10進制數,再運算一次DES對稱算法解密,看是是符合要求的密碼。破解它的方法,就是學會如何生成一個字符串,讓它經過驗證,也就是理解這個流程。
也有應用MD5加密算法的軟件。這樣,整個軟件只有一個序列號可用。把給你的序列號,驗證時生成MD5哈希值,與它保存在當前程序中的密碼匹配,驗證錯誤則失敗,不然經過。這種方式的好處是,你徹底沒有辦法應用密碼學知識去破解它,要麼用前面提到的,把驗證方法改爲nop直接返回,別無它法。
8 跟蹤策略。.NET時代是開創綠色軟件時代,一個.NET Runtime,全部程序共用,再調用這個公共類庫。可是,我發現幾乎全部的加密方法中,都會涉及把註冊信息或是機密信息,寫到註冊表中去。寫到註冊表中去的鍵或值,確定不會是明文,至少也要用個ToBase64把它變成一堆亂碼。鍵值不可讀,通常要還原到驗證,你要知道本身在哪一個地方寫入了UserName,哪一個地方寫入LicenseKey,通常會用可逆的算法,就像第六條中所說的。也有軟件應用可不可逆的算法,好比直接用MD5加密,這時,生成鍵值UserName或LicenseKey的變量,確定是不變的,不然,沒法再次生成鍵值,去對比驗證。找一個合適的註冊表跟蹤軟件,如Reg Monitor爲你的破解之路添加一線但願。
與此相對應的,Process Explorer, Dependency Walker也都應當應用到實際中,以發現珠絲馬跡。
要作Web方面的破解,Findder,Http Watch能夠很好的幫忙你分析服務器與IE客戶端之間,有哪些數據交互來往。
有的追蹤是死路(dead end),好比你想知道在網上買東西,網銀付款時,在本身的打開的IE中,輸入的錢數,是如何提交到銀行,被銀行扣走的。可是大部分程序或是網站,沒有作到這麼高的安全級別,能夠考慮嘗試。