.net程序的破解

【文章标题】: 静态分析+代码片断+十六进制编辑 破解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文本文件(要选上“显示字节”与“显示标记值”)。
用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()初始化,
查一下我们先前解密的字符串,是“NineRays.Decompiler.NotDecompile”,这是一个自定义属性,可见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库,其强大不言而喻,不过我是没有试用过了,这里提
出来,是想看看会不会有人恰好有这种需求,呵呵。

posted @ 2011-04-11 10:06  hanmos  阅读(1508)  评论(0编辑  收藏  举报