深刻理解.NET程序的原理 談一談破解.NET軟件的工具和方法

最近一段時間不忙,閒下來的空閒時間,重讀了一下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  代碼與反編譯結合策略。有時候面對程序集中五花八門的字符,徹底不理解它的含義,無從下手。

image

遇到這種狀況,它是應用了字符串加密技術。以其人之道,還其人之身,下面的幾行簡單的方法,破解文中的不可理解的字符:

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中,輸入的錢數,是如何提交到銀行,被銀行扣走的。可是大部分程序或是網站,沒有作到這麼高的安全級別,能夠考慮嘗試。

相關文章
相關標籤/搜索