破解WebUI.NET Framework验证原理

【软件名称】:WebGrid.NET Version 4.0
【软件类别】:国外软件
【整理时间】:2005-08-17
【下载地址】:http://www.evget.com/view/viewProductInfo.asp?productId=89&tabIndex=0
【保护方式】:License
【保护软件】:XenoCode
【编译语言】:ASP.NET
【调试环境】:WinXP、.NET Framework 1.1、Reflector、ildasm、sn、ultraedit
【破解日期】:2005-08-17
【破解目的】:研究.NET程序的破解流程
【作者声明】:初学Crack,只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!

听说WebGrid.NET 4.0采用了另外的加密技术,于是下载下来想见识一下,结果非常失望。除了把验证部分转移到了Framework的通用框架内以外,没有发现有什么改进的,加密水平还后退回WebCombo.NET的水平去了。

先是利用流程反混淆技术,把一堆函数反混淆出来,过程没有什么新意,参看以前我写的《揭开.NET程序保护的秘密》和《破解WebCombo.NET Version 2.0》,用ildasm反编译成il后修改转跳流程,然后用ilasm重新编译回去即可,唯一遇到一点点麻烦的是ilasm居然不认那个混淆后的resource。这也没关系,我们只是要分析验证原理,暂时不把resource编进去就是了。虽然是用XenoCode混淆的,可是连变量名字都没有处理过,非常容易就找到这两个函数

<ISNet.WebUI.ISNetControl>
private void CheckLicense()
{
      string text2 = "";
      string text3 = "";
      RegistryKey key3 = this.GetIntersoftRegKey(); // HKLM\Software\Intersoft Solutions
      text2 = this.Product.ProductName; // 具体的软件产品会重载此field
      text3 = this.Product.VersionNumber; // 具体的软件产品会重载此field
      if (key3 != null)
      {
            RegistryKey key1 = key3.OpenSubKey(text2);//从注册表读键值
            if (key1 != null)
            {
                  RegistryKey key2 = key1.OpenSubKey(text3);
                  if (key2 != null)
                  {
                        string text1 = key2.GetValue("SID") as string//注册码
                        if ((text1 != "") && (text1 != null))
                        {
                              if (this.MatchSN(this.Product.dk, text1)) //关键,如果爆破只要nop掉这里即可
                              {
                                    this.LicenseType = LicType.Full;
                              }
                              else if (this.MatchSN(this.Product.dr, text1))
                              {
                                    this.LicenseType = LicType.RuntimeOnly;
                              }
                              else
                              {
                                    text1 = this.ReadWebConfigValue("RuntimeLicenseKey");
                                    if ((text1 != null) && this.MatchSN(this.Product.dr, text1))
                                    {
                                          this.LicenseType = LicType.RuntimeOnly;
                                    }
                              }
                        }
                  }
            }
      }
}


private bool MatchSN(byte[] ?, string ?)//比较序列号的核心call
{
      byte[] buffer1 = Encoding.ASCII.GetBytes(?);
      bool flag1 = false;
      bool flag2 = false;
      bool flag3 = false;
      int num1 = 0;
      byte[] buffer2 = ?;
      int num4 = 0;
Label_001B:
      if (num4 < buffer2.Length)  //实际是循环:for(num4=0;num4<buffer2.Length;num4++)
      {
            byte num2 = buffer2[num4];//读入num2和num3进行比较
            byte num3 = 0;
            try
            {
                  num3 = (byte) buffer1.GetValue(num1);
            }
            catch
            {
                  flag3 = true;
                  goto Label_014B;
            }
            if (num2 != 0xbc)//下面一大段是核心判断,后面解释
            {
                  if (num2 == 190)
                  {
                        if ((num3 >= 0x41) && (num3 <= 0x4b))
                        {
                              goto Label_0054;
                        }
                        flag3 = true;
                  }
                  else
                  {
                        if (num2 == 0xb8)
                        {
                              flag2 = true;
                              goto Label_005A;
                        }
                        if (num2 != 0x4a)
                        {
                              if (num2 != 0x7e)
                              {
                                    if (num2 != 90)
                                    {
                                          if (num2 == 0x42)
                                          {
                                                if ((num3 >= 0x30) && (num3 <= 0x35))
                                                {
                                                      goto Label_0054;
                                                }
                                                flag3 = true;
                                          }
                                          else
                                          {
                                                if (num2 != 0x80)
                                                {
                                                      if (!flag2)
                                                      {
                                                            flag3 = true;
                                                            goto Label_014B;
                                                      }
                                                      if (num3 != (num2 / 2))
                                                      {
                                                            flag3 = true;
                                                            goto Label_014B;
                                                      }
                                                      flag2 = false;
                                                      goto Label_0054;
                                                }
                                                if ((num3 >= 0x36) && (num3 <= 0x39))
                                                {
                                                      goto Label_0054;
                                                }
                                                flag3 = true;
                                          }
                                    }
                                    else
                                    {
                                          if (num3 == (num2 / 2))
                                          {
                                                goto Label_0054;
                                          }
                                          flag3 = true;
                                    }
                              }
                              else
                              {
                                    if ((num3 >= 0x41) && (num3 <= 90))
                                    {
                                          goto Label_0054;
                                    }
                                    flag3 = true;
                              }
                        }
                        else
                        {
                              if ((num3 >= 0x30) && (num3 <= 0x39))
                              {
                                    goto Label_0054;
                              }
                              flag3 = true;
                        }
                  }
            }
            else
            {
                  if ((num3 >= 0x4c) && (num3 <= 90))
                  {
                        goto Label_0054;
                  }
                  flag3 = true;
            }
      }
      goto Label_014B;
Label_0054:
      num1++;
Label_005A:  //实际是循环:for(num4=0;num4<buffer2.Length;num4++)
      num4++;
      goto Label_001B;
Label_014B:
      if (!flag3)
      {
            flag1 = true;
      }
      return flag1;
}


看起来有点眼花,实际是因为Reflector对switch语句的支持不太好造成的。按照里面的逻辑列一张表就清楚了:

num2    num3

190    [0x41,0x4b]
0xbc    [0x4c,90]
0xb8    flag2 = true,当前num3不变(num2在这里的作用是一个额外的转义字符)
0x42    [0x30,0x35]
90    45
0x7e    [0x41,90]
0x4a    [0x30,0x39]
0x80    [0x36,0x39]

default:  flag2 == true && num3 == num2 / 2, flag2 = false

再看看Product.dk是在哪里初始化的,用Callee graph查到WebGrid.NET当中
<ISNet.WebUI.WebGrid.WebGrid>
.method family hidebysig specialname virtual instance [ISNet.WebUI]ISNet.WebUI.ProductInfo get_Product() cil managed
{
      // Code Size: 292 byte(s)

      L_006b: ldc.i4.s 16
      L_006d: newarr unsigned int8
      L_0072: dup 
      L_0073: ldtoken ?/? ?::?
      L_0078: call void [mscorlib]System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray([mscorlib]System.Array, [mscorlib]System.RuntimeFieldHandle)
      L_007d: stfld unsigned int8[] [ISNet.WebUI]ISNet.WebUI.ProductInfo::dk
}
找那串乱码的定义
.field assembly static ?/? ? = ((BC 42 4A 4A 42 5A 42 BC BE 7E 5A 7E BC BE BE BC))
对照上面的表,就可以手动构造一个sn了。如0x4c, 0x30, 0x30, 0x30, 0x30, 45, 0x30, 0x4c, 0x41, 0x41, 45, 0x41, 0x4c, 0x41, 0x41, 0x4c,即N0000-0NAA-ANAAN。Keygen也不难写,甚至可以写一个WebUI Framework产品万能注册机,直接从exe里面读取这串东西,呵呵。这里就省略了。

想不明白WebUI Framework的注册码验证算法为什么不再用Remotesoft Protector了。莫非出现版权问题?不过这对偶等穷人是好事。