.net程序的破解 - hanmos

>>>  技術話題—商業文明的嶄新時代  >>> 簡體     傳統

【文章標題】: 靜態分析+代碼片斷+十六進制編輯 破解Spices.net v5.1 --- 試談.net程序的破解
【文章作者】: dreaman
【作者郵箱】: dreaman_163@163.com
【作者主頁】: http://dreaman.haolinju.net
【軟件名稱】: Spices.net v5.1
【軟件大小】: 6788KB
【下載地址】: http://www.9rays.net/download.aspx
【加殼方式】: 無
【保護方式】: 無
【編寫語言】: 未知
【使用工具】: reflector,research.net,dis#,SnippetCompiler,ildasm,ultraedit,hexworkshop等
【操作平臺】: winxp , .net 2.0
【軟件介紹】: .net 程序反編譯、.net PE結構觀察、assembly校驗及混淆的工具。
【作者聲明】: 基于不太多的破解經驗總結,錯誤之處在所難免,歡迎討論。


本文主要談以靜態分析為主的破解,并試圖探討以靜態分析為主的破解模式,我們主要針對純CLR程序,也
就是程序中不含native指令的.net程序,這樣的程序通常用C#或C++/CLI的pure clr編譯選項生成。
特別的,我們主要從沒有加殼的程序說起,如果是加殼的.net程序,則首先需要脫殼,近期DRT小組的rick
在.net MaxtoCode殼方面已經取得突破。從原理上說,.net程序因為要支持reflection與JIT編譯,其關鍵
部分“元數據與IL代碼”都是可以取得或重建的,也就是說脫殼幾乎總是可能的,應只在難度高低之分。當
然,如果將來有一種殼自己實現完整的JIT功能甚至CLR,上面的說法就無效了,不過這樣一來,是否仍是
.net平臺則需要懷疑了。

一、基本思路
1、現在大多數.net程序都是加了強名的,這樣一來,我們沒法直接修改程序了,所以我們的第一步便是解除強
名。本來也有工具可以直接去除文件的強名,不過現在許多.net程序將強名與它的加解密結合起來,直接去除
有時會倒致許多麻煩,spices.net 5019的keygen里提供了一個比較好的辦法,就是自己重新生成一個新的強
名,用新的強名的KEY與TOKEN替換原來文件中的強名的KEY與TOKEN,然后再用sn.exe強制為原文件簽名。有了強名
替換與重新簽名后,我們就可以根據需要修改原文件了,只需要在修改完重新簽上新的強名就OK了。

2、一般的.net程序通常會利用混淆軟件將名稱搞成不可見字符或是一長串不好記的字符,分析起來十分不便,
我們的第二步就是反混淆,這里使用我自己寫的一個小工具,因為現在只有dis#的反混淆比較好用,但它不能同時
對一組assembly反混淆,而且它的反混淆是在反編譯時進行的,反過后的代碼不容易對應到原程序的方法(比如我們
要用ildasm打開原程序,想找到dis#反混淆后的某個方法,會非常困難),我們自己的小工具是直接對原二進制文件
進行修改,這樣修改后的文件可以由多個工具使用,確保了多個工具間名稱的對應。(這個工具會生成新的section,將.net元數據搬到新的section中再補上反出來的名稱,這樣我們通常將反混淆后的文件放在一個新的地方進行分析
,實際打補丁則直接在未反混淆的文件上進行,分析的方法與原文件方法的對應可以通過方法表的token來對應或者通過
方法體中的特征字符串進行)。

3、文件的強名改變后,文件中與強名相關的加密解密的數據也需要作相應處理,比如原來有一段數據用舊的強名
加密的,則我們需要將原數據解密再用新的強名加密替換原來的數據,這是一項比較煩的工作。首先我們通過分析找到
這樣的地方,查找的主要方法是用reflector.exe的analyzer,通過查找對AssemblyName.GetPublicKey與AssemblyName.
GetPublicKeyToken的引用來定位使用強名稱的代碼。

4、經歷上面這些過程后,我們終于可以開始我們真正的破解了,相對來說,這部分倒似乎更簡單一些,呵呵,對
我們這次的目標Spices.net來說,主要就是去掉它的功能限制,因為是evaluation版本,沒有寫注冊機的必要了。我們
對反混淆的文件進行分析找出這些限制的地方,然后用ildasm反編譯原始程序,找到對應的IL代碼,然后用二進制查找
與替換功能將原始程序的字節碼改成我們修改后的代碼。

5、完成所有的修改后,用我們1中準備好的強名稱文件重新對所有修改過的文件簽名,之后便可以運行看看成果了
:)

二、工具集
破解.net程序對工具的依賴明顯比win32程序要嚴重,這里列出一些(后面有用到,汗!):
1、reflector.exe
這個反編譯工具是不可不備了,免費+強大,特別是analyzer功能,簡直是神了!!!

2、research.net
查看.net元數據信息的工具,許多時候得用它輔助對應反混淆后與反混淆前的程序。

3、dis#
反編譯并且能保存為文件,這一點比較好,破解.net程序差不多總是要從原程序里抽一些代碼片斷作計算了。

4、SnippetCompiler
C#代碼片斷編譯運行工具,從原程序里抽出代碼來臨時運行一下,總是用vs.net還要建工程就不爽了。

5、ildasm
低級但是強大的工具,到修改原程序之前,參照它的結果應該是必須的,至少總是要看一下IL字節碼吧。

6、ultraedit
文本編輯器,在一般文本與十六進制間來回倒騰,或者是要搜索文本內容時,這個還是比較方便一些的。

7、hexworkshop
別的不說了,就感覺最爽的功能是對原文件進行二進制替換時,Select Block=>Paste Special,就這兩步,恰到好處。
(winhex也有類似功能,不過感覺還是hexworkshop更順手一些,呵呵)

三、破解過程

/SC 0024000004800000940000000602000000240000525341310004000001000100f9525ef4fb72db4053790103572ec74a576bfe4aa024ed19b78356c93193b1e0dbbe9cb01e1f4fa5c5b181a83e83e25b8261717bf4dbcecc3ad453a4a71ac72f3e102403ed2d2ed01fcc71802f2ee4527825aedc31eb91c0abe100d5eacac664486d4f422f5e2d73fb30ed326c49ccb54dc2568900f1c32b27739822876658b8 002400000480000094000000060200000024000052534131000400000100010067790bbf0dee12b24b2bc6feb5e9e5dfbfaeea35efa57bb4f3b6c017329dba78a14e7d03b95a8b790f190f258f2423b577bb9a1d053529e7c93b252d75cb3f9be7710f44c6c48f441aa7348df218fb3390f55e5d50eb7a7cab35a2c9ad5bf8694d887f560e3289c560f3b290c239046332a3178b7886a19b117814cc24bbd6b9
/UC 0024000004800000940000000602000000240000525341310004000001000100f9525ef4fb72db4053790103572ec74a576bfe4aa024ed19b78356c93193b1e0dbbe9cb01e1f4fa5c5b181a83e83e25b8261717bf4dbcecc3ad453a4a71ac72f3e102403ed2d2ed01fcc71802f2ee4527825aedc31eb91c0abe100d5eacac664486d4f422f5e2d73fb30ed326c49ccb54dc2568900f1c32b27739822876658b8 002400000480000094000000060200000024000052534131000400000100010067790bbf0dee12b24b2bc6feb5e9e5dfbfaeea35efa57bb4f3b6c017329dba78a14e7d03b95a8b790f190f258f2423b577bb9a1d053529e7c93b252d75cb3f9be7710f44c6c48f441aa7348df218fb3390f55e5d50eb7a7cab35a2c9ad5bf8694d887f560e3289c560f3b290c239046332a3178b7886a19b117814cc24bbd6b9
/B 0024000004800000940000000602000000240000525341310004000001000100f9525ef4fb72db4053790103572ec74a576bfe4aa024ed19b78356c93193b1e0dbbe9cb01e1f4fa5c5b181a83e83e25b8261717bf4dbcecc3ad453a4a71ac72f3e102403ed2d2ed01fcc71802f2ee4527825aedc31eb91c0abe100d5eacac664486d4f422f5e2d73fb30ed326c49ccb54dc2568900f1c32b27739822876658b8 002400000480000094000000060200000024000052534131000400000100010067790bbf0dee12b24b2bc6feb5e9e5dfbfaeea35efa57bb4f3b6c017329dba78a14e7d03b95a8b790f190f258f2423b577bb9a1d053529e7c93b252d75cb3f9be7710f44c6c48f441aa7348df218fb3390f55e5d50eb7a7cab35a2c9ad5bf8694d887f560e3289c560f3b290c239046332a3178b7886a19b117814cc24bbd6b9
/SC 59d4bed864488801 19d207f78a3e6d0f
/UC 59d4bed864488801 19d207f78a3e6d0f
/B 59d4bed864488801 19d207f78a3e6d0f

這是原keygen里的一段腳本,用一個snr.exe的小程序執行,其實就是二進制替換,前三個是替換強名稱公鑰,后三個是替換強名稱公鑰的token,三個參數看起來像是字符串的三種格式ASCII、UTF8、UNICODE(不確定)。

2、反混淆
用我們的小工具DeObfuscator.exe,運行后添加進這幾個assembly:
  NineRays.AsmBrowser.DLL                                   
  NineRays.Build.Tasks.dll                                  
  NineRays.Controls.DLL                                   
  NineRays.Decompiler.dll                                    
  NineRays.Documenter.dll                                    
  NineRays.ILOMD.DLL                                         
  NineRays.ILOMD.Options.DLL                                 
  NineRays.Informer.dll                                    
  NineRays.Investigator.dll              
 NineRays.Localizer.dll                              
  NineRays.Modeler.dll                            
  NineRays.Obfuscator.DLL                             
  NineRays.Services.DLL                                  
  NineRays.UML.DLL                                    
  NineRays.Utils.DLL                                 
  NRObfuscator.exe                                   
  Spices.exe                                              
添加完后,選擇一下輸出目錄,然后點“反混淆”,這樣就會在輸出目錄下生成反混淆后的文件了。

 

3、破解與強名稱相關的字符串加密
用reflector.exe裝入我們前面反混淆過的文件,然后在mscorlib(如果沒有就裝入一下mscorlib.dll)下面找到System.Reflection.AssemblyName.GetPublicKeyToken,
右鍵Analyzer,此時會看到右邊列出所有引用這個方法的地方,依次查看與我們目標程序相關的那些地方,可以發現有三處是字符串加密的。

System.Reflection.AssemblyName.GetPublicKeyToken() : Byte[]
  Depends On
  Used By
    System.AppDomain.ApplyPolicy(String) : String
    System.Reflection.Assembly.IsSimplyNamed(AssemblyName) : Boolean
    System.Resources.ResourceManager.CompareNames(String, String, AssemblyName) : Boolean
    System.Resources.ResourceManager.InternalGetResourceSet(CultureInfo, Boolean, Boolean) : ResourceSet
    System.Xml.Serialization.TempAssembly.LoadGeneratedAssembly(Type, String, XmlSerializerImplementation&) : Assembly
    x15.x4.x33ED8() : Object
    x2.x4.x332B4() : Object
    x2.x4.x3381() : Object
    x9.x26.x9(AssemblyName, AssemblyName) : Boolean
    x9.x73.x9(Assembly) : x921
    x9.x8.x35(Object) : Void

現在用dis#.exe裝入剛剛有字符串加密的那個assembly,找到字符串加密的類,將它的代碼保存成c#文件,啟動SnippetCompiler,打開剛剛保存的C#文件,加上一些讀取
字符串并寫文件的代碼,運行一下,就得到了這個類里所有加密的字符串,注意那些包含evaluation的字符串對應的解密的方法,稍后會用到。
我們來看一下典型的幾個方法
internal sealed class x4
{
  internal static byte[] x33;
  internal static Encoding x33AB = Encoding.Unicode;

  static x4()
  {
         x4.x33 = x4.x33(new byte[] { ... });
  }
    
  internal static byte[] x33(byte[] obj1)
  {
      return ((TripleDESCryptoServiceProvider) x4.x332B4()).CreateDecryptor().TransformFinalBlock((byte[]) obj1, 0, obj1.Length);
  }

  internal static object x332B4()
  {
        TripleDESCryptoServiceProvider provider1 = new TripleDESCryptoServiceProvider();
        provider1.Key = new byte[] { 0x18, 0xac, 3, 0x7c, 0x7d, 0xb8, 0xaf, 13, 0xcb, 0x8d, 0xb2, 200, 0xc2, 0xe1, 0x5e, 170, 
              40, 0xa4, 0xea, 0x58, 0x98, 0x76, 0x3a, 0x77
         };
        provider1.IV = Assembly.GetExecutingAssembly().GetName().GetPublicKeyToken();
        return provider1;
  }
  
  internal static object x33()
  {
        return x4.x33AB.GetString((byte[]) x4.x33, 0x1e1c, 0x48);

 }

這個類在靜態構造里解密字符串數據塊,然后由它的各個靜態方法如x33()返回各個解密后的字符串,x332B4()構造了解密類,x33(byte[])執行解密。
x332B4()中使用了GetPublicKeyToken(),因為我們改變了文件的強名,這個解密可能會不正確了(本來似乎應該一直不正確,不過我發現好象有時候
IV不同也能正確解密,不知道是什么原因),我們要做的就是將靜態構造里的new byte[]后面的數據先解出來,然后再用我們新的公鑰token給它加密,
再用加密后的數據替換原來的數據(TripleDES是對稱算法,這里KEY已經給定,我們只是用公鑰token替換IV重新加密而已)。
這個也是用SnippetCompiler來作了,完成后生成兩個十六進字符串(就是可以直接在hexworkshop中搜索與替換用的十六進制串的格式),然后我們啟
動hexworkshop,先搜索原來的(沒必要指定全部,一般前7到8個字節就唯一了),Select Block,輸入數據塊的長度,然后,Paste Special,選中
“interpret as a hexadecimal string”,點OK,保存文件就可以了。
用同樣的方法處理另外幾個文件以及由GetPublicKey的引用發現的另外幾處涉及加密字符串的文件(這是個比較煩人的工作,總共有六處字符串需要處
理,也就有六個解密類,特別是ILMOD.dll,它的字符串數據有幾十K,在拷貝時會倒致程序暫時失去響應)。

4、破解功能限制
加密字符串處理完,工作量的一大部分就完了,不過,我們的破解好象還沒開始呢,汗。
1)破解反編譯只有50%方法的限制
運行一下spices.net,用C#語言反編譯任何類時總是只有約50%的方法編譯,另外的則注釋上了//evaluation version,仔細看一下,基本上是隔一個出現
一個注釋,因為注釋使用了字符串,我們就不看看字符串引用的情況,找到我們破解加密字符串時生成的文件,找到與evaluation version對應的那個方法
x15.x4.x338E4(),然后在reflector.exe里看一下對它的引用:

x15.x4.x338E4() : Object
  Depends On
  Used By
    NineRays.ILOMD.DecompilerManager..cctor()


然后我們看一下DecompilerManager的靜態構造:

static DecompilerManager()
{
      DecompilerManager.x15 = (string) x4.x33();
      DecompilerManager.x36 = (string) x4.x338DC();
      DecompilerManager.x11 = (string) x4.x338DD();
      DecompilerManager.x2 = (string) x4.x338DE();
      DecompilerManager.x24 = (string) x4.x338DF();
      DecompilerManager.x28 = (string) x4.x338E0();
      DecompilerManager.x26 = (string) x4.x338E1();
      DecompilerManager.x29 = (string) x4.x338E2();
      DecompilerManager.x19 = (string) x4.x338E3();
      DecompilerManager.x34 = (string) x4.x338E4();<--這個是目標字符串
      DecompilerManager.x73 = (string) x4.x338E5();
}

發現這個字符串被賦給x34這個成員,我們再查x34的引用:

NineRays.ILOMD.DecompilerManager.x34 : String
  Used By
    NineRays.ILOMD.DecompilerManager..cctor()
    x15.x439.x15(x349, NamingConventions) : x453


看一下x15這個方法:

private x453 x15(x349 x3, NamingConventions conventions1)
{
      if (x3.x654())
      {
            return null;
      }
      x453 x1 = new x453(null);
      if (this.x24 || ((x3.x21() % 2) < 2))
      {
            x565 x2 = x3.x15();
            try
            {
                  new x441(x1, x3, x2, this, this.x15, conventions1, this.x15, this.x36);
            }
            catch (Exception exception1)
            {
                  x1.x15(new x509(((string) x4.x339CA()) + exception1.Message + ((string) x4.x338F2())));
                  x1.x15(new x509(((string) x4.x339CB()) + exception1.StackTrace + ((string) x4.x338F2())));
            }
            return x1;
      }
      x1.x15(new x509(DecompilerManager.x34));
      return x1;
}

可以看到最后第二句使用了前面的字符串,函數有三個出口,最后一個出口應該就是顯示evaluation version的了,第二個是正常反編譯的,
看一下if的第二個條件,有一個 % 2 == 1 的操作,正好是隔一個編譯一個,與我們前面觀察到的特征符合,就是它了,改變的辦法很簡單
,我們將 % 2 == 1 改為 % 2 < 2 這個條件就會永遠為真了。接下來我們來看做這個修改的IL指令與字節碼,在reflector.exe中切換到IL
顯示方式,同時用ildasm反編譯原來的NineRays.Decompiler.dll,保存一個IL文本文件(要選上&ldquo;顯示字節&rdquo;與&ldquo;顯示標記值&rdquo;)。
用research.net找開反混淆過的DLL,因為x439這個類有許多方法叫x15,research.net在顯示方法表時沒有顯示完整的方法原型,所以我們
沒辦法直接找到方法的token,我們找這個類的第一個方法的token:06000399,用ultraedit找開我們在ildasm里反出來的文件,在其中查找
這個標記值,定位到了x439類的第一個方法:
.method /*06000399*/ public hidebysig specialname rtspecialname 
          instance void  .ctor(valuetype NineRays.ILOMD.DecompilerOptions/*020000A5*/ A_1,
                               class NineRays.ILOMD.NamingConventions/*02000015*/ A_2,
                               bool A_3) cil managed
  // SIG: 20 03 01 11 82 94 12 54 02
  {
    // 方法在 RVA 0x2106c 處開始
    // 代碼大小       148 (0x94)
    
然后我們以.method 為搜索條件,在ultraedit里向后瀏覽各個方法,注意方法參數的第二個參數類型為NineRays.ILOMD.NamingConventions
的方法:
.method /*060003B7*/ private hidebysig 
          instance class ''.'3'/*02000033*/ 
          ''(class [NineRays.ILOMD/*23000004*/]''.'9'/*01000046*/ A_1,
              class NineRays.ILOMD.NamingConventions/*02000015*/ A_2) cil managed
  // SIG: 20 02 12 80 CC 12 81 19 12 54
  {
    // 方法在 RVA 0x236d0 處開始
    // 代碼大小       172 (0xac)
 
對照reflector.exe里IL,我們找到 % 2 == 1 的地方:

    IL_001b:  /* 6F   | (0A)00018E       */ callvirt   instance int32 [NineRays.ILOMD/*23000004*/]''.'3'/*0100004D*/::''() /* 0A00018E */
    IL_0020:  /* 18   |                  */ ldc.i4.2
    IL_0021:  /* 5D   |                  */ rem
    IL_0022:  /* 16   |                  */ ldc.i4.0
    IL_0023:  /* FE01 |                  */ ceq
    IL_0025:  /* 2B   | 01               */ br.s       IL_0028

我們要將它改為 % 2 < 2 則需要將

    IL_0022:  /* 16   |                  */ ldc.i4.0
    IL_0023:  /* FE01 |                  */ ceq
    
改為
    ldc.i4.2
    clt
對應的十六進制IL碼為 18 FE 04 , (查找指令的指令碼比較方便的方法是用MSDN看.net框架庫參考里的System.Reflection.Emit.OpCodes結構的靜態只讀成員
的注釋,每個對應一條指令,說明也比較詳細。)

現在用hexworkshop打開未反混淆的原程序,搜索6F8E01000A185D16FE012B01,如果不唯一就再往后多看幾個(或者看一下是否在RVA 0x236d0附近[ildasm在方法開
始給出RVA]),呵呵(注意ildasm顯示的指令碼中操作數部分不是文件順序而32位值的顯示順序,如上面的(0A)00018E實際上在文件應反過來8E01000A),找到
后將其中的16 FE 01 改為 18 FE 04,保存即可。

2)破解不能反編譯spices.net處理過的文件的限制
Spices.net在反編譯它自己時會提示...protected from decompilation by ...然后就反編譯了,這個與上面一樣的方法破解,還是查一下這個相關字符串的解密
函數x15.x4.x339C6(),在reflector中查一下引用:

x15.x4.x339C6() : Object
  Depends On
  Used By
    x15.x439..cctor()

來到x15.x439的靜態構造:
static x439()
{
      x439.x15 = (string) x4.x339C5();
      x439.x36 = ((string) x4.x339C6()) + x439.x15 + ((string) x4.x339C7());<--這個包含目標字符串
      x439.x11 = (string) x4.x339C8();
}
再查x36成員的引用:
x15.x439.x36 : String
  Used By
    x15.x439..cctor()
    x15.x439.x15(CodeCompileUnit, String, x351[]) : Void
    x15.x439.x15(x351) : x493
    x15.x439.x15(x351, x349, Boolean) : CodeTypeMember
    x15.x439.x36(x16) : CodeCompileUnit

這個引用的次數比較多,我們打開跳到其中的一個看一下,x15.x439.x15(x351):
private x493 x15(x351 x4)
{
      if (!x4.x658())
      {
            x493 x3 = new x493(x4.x9());
            if (this.x15((x68) x4) || this.x15((x68) x4.x36()))
            {
                  x3.Comments.Add(new CodeCommentStatement(string.Format(x439.x36, x3.Name)));
                  return x3;
            }
...

可以看到在一個條件判斷后使用了目標字符串,然后函數返回,我們看一下這個條件判斷使用的函數x15:
private bool x15(x68 x3)
{
      foreach (x563 x1 in x3.x15())
      {
            x351 x2 = x1.x36();
            if (x2.x26().EndsWith(x439.x15))
            {
                  return true;
            }
      }
      return false;
}

可以看到是判斷以一個字符串結尾時返回true倒致不編譯的,x439.x15這個字符串在靜態構造里使用x4.x339c5()初始化,
查一下我們先前解密的字符串,是&ldquo;NineRays.Decompiler.NotDecompile&rdquo;,這是一個自定義屬性,可見Spices.net對有
這個屬性的方法或類型不反編譯,解決辦法很簡單,將return true改為return flase即可。如何將這些改動應用于原執行
文件與1)中相同,這里就不多說了。前面說過這個字符串的引用比較多,我們改了一處就可以了嗎?我們看一下我們改
過的x15方法的引用:
x15.x439.x15(x68) : Boolean
  Depends On
  Used By
    x15.x439.x15(CodeCompileUnit, String, x351[]) : Void
    x15.x439.x15(x351) : x493
    x15.x439.x15(x351, x349, Boolean) : CodeTypeMember
    x15.x439.x36(x16) : CodeCompileUnit

可以看到正是上面四處引用了這個方法,也就是改這一處就可以了,我們也可以一個一個方法的檢查一下,確實如此,呵呵。

3)破解PE/metadata功能里顯示evaluation的限制
spices.net的PE/metadata功能是與research.net相似的查看.net PE文件格式的功能,有些方面比research.net做得更出色,
不過在評估版里很多元數據項目寫成了evaluation,我們現在就是要破解這個限制,當然我們也可以用1)2)中的字符串查
找來尋找關鍵點,不過這個模塊的字符串沒有用強名信息加密,所以我們先前沒有將這些字符串解密出來,字符串解密畢竟
太煩了,我們嘗試看有沒有別的辦法來找到這個關鍵點。經過觀察發現evaluation似乎是隨機出現的,沒有什么規律。不過
,這本來就是一個規律了,隨機?在.net里取隨機數的類是System.Random,我們就來看看程序里有哪些地方使用了這個類:
System.Random
  Depends On
  Used By
    x11.x36..ctor(x87, x370, x26, Options, Hashtable)
    x11.x36.x11 : Random
    x11.x36.x11(x351, String[]&) : Void
    x15.x362.x15(String) : String
    x15.x362+x19..ctor(Random)
    x15.x362+x19.Compare(Object, Object) : Int32
    x15.x362+x19.x15 : Random
    x15.x605.x34() : String
    x15.x605.x73() : String
    x15.x605.x87() : String
    x15.x674..cctor()
    x15.x674.x15 : Random
    x15.x674.x15(x682, x92, String) : String
    x15.x674.x28() : String
    x15.x92+x370+x675
    x15.x92+x370+x675..ctor(Int32)
    x24.x842.x759() : Void
    x73.x12.x20(x365) : Void
    x73.x12.x73(x92, x652) : Void
    x9.x10.x12(String) : String

這一看還真不少,一個一個的看一下吧,首先我們把與混淆相關的排除(或者被它使用的),因為混淆需要使用隨機數,應該
不是我們要破解的目標,這樣子就只剩下了最后4個中的前三個(這個過程沒什么好說的,就是一點點看了):
    x24.x842.x759() : Void
    x73.x12.x20(x365) : Void
    x73.x12.x73(x92, x652) : Void
排除完了后發現這三個方法出現在informer.dll與investigator.dll中,看來是接近目標了,先看一下x24.x842.x759():

private void x759()
{
      Control control1 = base.Parent;
      if (control1 != null)
      {
            if (this.x24 == null)
            {
                  this.x24 = new x841(base.x24);
                  Column column1 = new Column((string) x4.x33C3());
                  column1.set_FitMode(3);
                  Column column2 = new Column((string) x4.x33133());
                  column2.set_FitMode(4);
                  this.x24.get_Columns().get_Items().AddRange(new Column[] { column1, column2 });
                  Columns columns1 = this.x24.get_Columns();
                  columns1.set_Options(columns1.get_Options() | 0x100);
                  this.x24.Dock = DockStyle.Bottom;
                  this.x24.add_NodeFocusChange(new NodeHandler(this, (IntPtr) this.x24));
                  this.x24.Height = 100;
                  Splitter splitter1 = new Splitter();
                  splitter1.Height = 8;
                  splitter1.BorderStyle = BorderStyle.None;
                  splitter1.BackColor = SystemColors.Control;
                  splitter1.Dock = DockStyle.Bottom;
                  x78 x1 = base.x24.GetService(typeof(x78)) as x78;
                  if (x1 != null)
                  {
                        x1.x264(this.x24);
                  }
                  control1.Controls.AddRange(new Control[] { splitter1, this.x24 });
            }
            this.x24.BeginInit();
            try
            {
                  this.x24.get_Rows().get_Items().Clear();
                  if (base.get_Selected() != null)
                  {
                        x843.x842 x2 = base.get_Selected().GetCellValue(base.get_Columns().get_Items().get_Count()) as x843.x842;
                        if ((x2 != null) && (x2.x24 != null))
                        {
                              Random random1 = new Random();
                              foreach (x68 x3 in x2.x24)
                              {
                                    if (x3 != null)
                                    {
                                          if (x3 is x350)
                                          {
                                                (x3 as x350).x36();
                                          }
                                          Node node1 = (random1.Next(2) != 1) ? new Node(new object[] { x4.x33134(), x4.x33134() }) : new Node(new object[] { x3.x15().x26(), x3 });
                                          this.x24.get_Rows().get_Items().Add(node1);
                                    }
                              }
                        }
                  }
            }
            finally
            {
                  this.x24.EndInit();
            }
      }
}

方法挺長的,不過只有一處用到了,就是
Node node1 = (random1.Next(2) != 1) ? new Node(new object[] { x4.x33134(), x4.x33134() }) : new Node(new object[] { x3.x15().x26(), x3 });
看一下x4.x33134(),原來也是一個字符串解密類,這樣就不用管它了,因為是寫一個固定的字符串,正是破解點無疑。破解方法,就是讓這個?:表達式的
條件永遠不成立,我是將 != 1 改為 == 3,呵呵,改IL字節碼的方法同1)中所述。

接下來看x73.x12.x20(x365):

try
{
      string text1 = (string) x4.x3313A();
      Random random1 = new Random();

...

case x92.x370.x676.String:
      if (random1.Next(2) != 1)
      {
            goto Label_03D2;
      }
      text2 = text1;
      goto Label_04AA;

case x92.x370.x676.Blob:
      if (random1.Next(2) != 1)
      {
            goto Label_0335;
      }
      text2 = text1;
      goto Label_04AA;

case x92.x370.x676.Guid:
      if (random1.Next(2) != 1)
      {
            goto Label_0296;
      }
      text2 = text1;
      goto Label_04AA;

default:
      goto Label_0455;

...
 

這個方法更長,我就只摘了與隨機數相關的這部分,可以看到三個分支處隨機數不為1時就將text1賦給text2了,text1是在函數開始時賦
值的x4.x3313A(),也是一個字符串解密函數,固定字符串,所以我們的破解是讓三個條件永遠成立,我的做法是將 != 1 改為 != 3 
,IL字節碼修改方法仍同前述:)

快接近勝利的尾聲了,我們來看最后一個x73.x12.x73(x92, x652):

try
{
      string text1 = (string) x4.x3313A();
      Random random1 = new Random();

...
if (random1.Next(2) == 1)
{
      textArray2[1] = text1;
}
else if (this.x73.x73().x679())
{
      textArray2[1] = guid1.ToString();
}
...

這個函數依然很長很長...總共有四處用到了隨機數,不過都是一樣的代碼模式,怎么感覺老外也喜歡拷貝代碼啊(或宏?)?text1是固定
字符串,所以這里的破解就是讓條件永遠不成立,我的做法是將 == 1 改為 == 3,呵呵,故伎重施而已。IL字節碼的修改依舊同前:)

終于改完了,還真是累啊!

5、簽名&運行測試
這個過程簡單了,寫一個批處理吧,不過sn.exe是.net SDK帶的工具,記得path環境變量一定設好,不好就找不到執行文件了。我是直接用
開始菜單vs2005里的那個vs2005 命令行打開的cmd,所以環境變量都設好了,命令如下:

sn -R NineRays.AsmBrowser.DLL activation.snk
sn -R NineRays.Build.Tasks.dll activation.snk
sn -R NineRays.Controls.DLL activation.snk
sn -R NineRays.Decompiler.dll activation.snk
sn -R NineRays.Documenter.dll activation.snk
sn -R NineRays.ILOMD.DLL activation.snk
sn -R NineRays.ILOMD.Options.DLL activation.snk
sn -R NineRays.Informer.dll activation.snk
sn -R NineRays.Investigator.dll activation.snk
sn -R NineRays.Localizer.dll activation.snk
sn -R NineRays.Modeler.dll activation.snk
sn -R NineRays.Obfuscator.DLL activation.snk
sn -R NineRays.Services.DLL activation.snk
sn -R NineRays.UML.DLL activation.snk
sn -R NineRays.Utils.DLL activation.snk
sn -R NRObfuscator.exe activation.snk
sn -R Spices.exe activation.snk

其中的activation.snk是我從原來spices.net 5019的keygen里直接拿過來的,呵呵,本文的附件里有這些東東。

簽完名后,運行一下spices.net,編譯一下它自己,看一下元數據,OK,沒發現異常,因為我不用混淆功能,所以我就沒有進一步的處理別的
東西了,有興趣的不妨繼續,呵呵。

四、關于調試的想法(只是想法)
現在好象只一個pebrowser可以在看見IL代碼的情況下調試.net EXE吧,而且IL只是用作參考,實際執行的是對應的匯編代碼,
我是不太會用這個工具,一般我只用它來找一下程序出錯的位置(查一下堆棧,借以知道是哪個方法出錯了,然后是從哪些方
法調過來的),單步跟蹤看執行結果我看得頭大,老是要將寄存器或內存的數據對應到IL中的變量上,實在是不直觀的緊。
對于.net程序的調試,我還是比較看好vs.net,事實上因為IL的設計是比較高級的,很容易對應到高級語言,我們完全可以將
反混淆后的文件用dis#反編譯后再用vs.net重新編譯調試,我沒有試過,不過感覺應該是可行的。

五、關于流程混淆的想法(只是想法)
.net規范要求IL代碼必須是一條指令接一條指令,中間不允許有無用數據,這便使得傳統win32的指令加花不可能用在IL一級,
現在的花指令看起來應該是在更高一級實現的,也就是在高級語言一級,加入很多跳轉與無用判斷或者循環,好象還有類似逆
指令流的寫法(好象在fox的執行程序里見過),一個方法的實際執行差不多是從最后一句一條一條往回走的。既然是在高級
語言一級加的花,那么我們最合適的破解方式可能也是在高級語言一級了,假設我們可以用vs.net裝入反編譯后的工程,利用
vs.net的語法語義分析應該可以直接根據標注/警告去掉大部分(有個vs.net的插件reshaper可以提供更多的信息),剩下的單
步走一遍,應該就差不多了,如果有人有興趣,還可以為vs.net寫一個去花插件。

六、回歸:.net在win32破解中的應用
這一部分是題外話,呵呵。
源于我們前一段采用注入方式取IL代碼的試驗,因為.net里可以很方便的使用C#作腳本,我做了一個向目標
進程注入C#腳本環境的程序,無事時找了個win32程序試了一下,居然也能注入,也就是說我們可以在一個
純win32進程空間執行C#代碼了,因為C#是可以允許不安全代碼與指針的,相當于提供了一個可以方便的讀
寫目標進程空間的腳本,這個腳本可以使用整個.net庫,其強大不言而喻,不過我是沒有試用過了,這里提
出來,是想看看會不會有人恰好有這種需求,呵呵。

 


hanmos 2015-05-19 00:20:13

[新一篇] 30多年程序員生涯經驗總結

[舊一篇] 360度全景攝影拍震撼超現實世界
回頂部
寫評論


評論集


暫無評論。

稱謂:

内容:

驗證:


返回列表