特别说明:本文仅做学习研究使用,有一定参考价值,是一些破解的心得和思路,完全是个人对程序的研究,因此也未说明破解的是哪个软件,不涉及商业行为,版权问题是另外一方面的事情了,本人对其他事务不负责


  公司最近买了某公司的一个.net OA软件进行二次开发,但由于其的许可证限制了同时可连接的用户数以及可使用的时间(截止时间到后变为演示版,演示版仅能2个用户),我们的客户一般比较多,每个人一个账号,在测试和二次开发方面不太方便,于是尝试自己破解一下。
先用Reflector查看了一下其DLL文件,发现使用了XenoCode进行混淆,但程度不是太深,基本上还不影响阅读。
根据观察,发现其所有页面都基于一个Page基类WebPage,其中有一个关键函数Loginon(),用于系统登录,其中判断能否登录,是根据一个login_result的结果来判断的
Login_result是一个enum
public enum LOGON_RESULT
{
    // Fields
    Fail = 0,
    Fail_Lic_Limit = -1,
    Success = 1,
    Success_Pwd_Expire = 3,
    Success_Pwd_Force_Change = 4,
    Success_Pwd_Length = 2
}
这是登录状态,那么肯定有返回这些状态的函数,通过查找,找到
类SecurityMgr中包含有一个函数
private SecurityMgr.LOGON_RESULT x5913b537a9ca4090(string x6b237db413aed7db, string xb45a741896260675, Page x9c79b5ad7b769b12)
用来返回登录状态
找到这个函数,反编译,查看代码,代码中的字符串都已经被XenoCode加密过,直接是看不出来的,好在我们可以利用反射来解密这些字符串的值(这些网上都有说明)
转换后,我们可以看到,其对用户数的判断是在对数据库中的用户表进行top筛选来限制的:select top "+license1.LicenseNumber+ "username from users
其中license1.LicenseNumber即是限制用户数的数目
这个从哪里来呢?往上看,可以看到
License license1 = new License(x9c79b5ad7b769b12.Application);
这样,我们就清楚了,所有的license信息是在此处读取的
找到这个类,
发现license1.LicenseNumber这个属性对应的是x957eec0002850847.x01b6751913efbd51
public int LicenseNumber
{
    get
    {
        return this.x957eec0002850847.x01b6751913efbd51;
    }
}
那么x957eec0002850847.x01b6751913efbd51元素在哪里有被使用过呢?
通过Reflector查看Analyzer功能,我们可以发现,
其在构造函数public License(HttpApplicationState app)和private void x1052178a2aa3affc(HttpApplicationState xbc9689dd53140596)
中被使用
对其进行分析,对字符串解码后,再去除花指令,对其混淆过的流程进行重新编排,我们可以看到
构造函数中对这个属性的操作仅仅是初始化,那么赋值的地方在哪里呢?
往下看我们可以看到
当app["License"] 为 null时,会去调用
this.x822eb47bc415e83b(app);
this.x1052178a2aa3affc(app);
用于取得license信息

很容易的,我们知道x957eec0002850847.x01b6751913efbd51这个属性一定在this.x1052178a2aa3affc(app)函数中被改写以取到相应的用户限制数,
来到该函数中逆向依次搜索x01b6751913efbd51》text7》text5》text4
得到这些关键语句
text1 = Encoding.Unicode.GetString(Convert.FromBase64String(this.x4c262adfb75e23fa.x3f7a1096840d5b39));
            text2 = text1.Substring(0, 0x40); 
              string text3 = text1.Substring(0x40); 
               text4 = this.xcc381ffa3ede662f(text3);
string[] textArray1 = text4.Split(new char[] { ';' });
text5 = textArray2[num1];
text6 = text5.Trim().Substring(0, 3); 
text7 = text5.Trim().Substring(3); 
知道,这个属性是从text4中来的,而text4是由
this.x4c262adfb75e23fa.x3f7a1096840d5b39经过一系列转换而来
而x4c262adfb75e23fa.x3f7a1096840d5b39的值我们通过分析再结合前文,可以很自然的想到是从x822eb47bc415e83b(app)函数运行后得到。
x822eb47bc415e83b(app)比较简单,很容易就知道其作用是ReadLicenseFromDB
从中我们找到对应关系
License下的一个包含4个元素的结构,用于记录从数据库中取到的信息,对比下程序,我们很容易找出其对应关系。
private struct x29e2d415f271a930
{
    public string xf67f2f73d67cc6b6;//Key
    public string x3f7a1096840d5b39;//License
    public string x00af6e04034127cd; //Data
    public DateTime xd00ef6aee32c8aff;//CreateTime
}

到此,我们就已经知道,用户数信息是根据数据库中的license字段信息经过一系列换算得到。
那么我们结合
private void x1052178a2aa3affc(HttpApplicationState xbc9689dd53140596)函数分析
private void x1052178a2aa3affc(HttpApplicationState xbc9689dd53140596)
{
    try
    {
        string text1;
        string text2;
        string text4;
        string text5;
        string text6;
        string text7;
        string text8;
        string[] textArray2;
        int num1;
        string text9;
        DateTime time1;
        if (this.x4c262adfb75e23fa.x3f7a1096840d5b39 != null)
        {
            goto Label_079D;
        }
        return;
    Label_0015:
        xbc9689dd53140596[string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("jccadejakdabjdhbpdobbefcadmcbbddddkdicbeocieoooe", 0x7db601dd))] = string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("elodfjhegcdcglcdldmc", 0x4270c1b9)) + this.x957eec0002850847.xf98c0407c36b694d.ToString(string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("lgocigfdfgmdcgdedbkeadbfncifkapfodggldng", 0x59062df2))) + string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("hjdgmobe", 0x5594d3d0));
        if (-2 != 0)
        {
            return;
        }
    Label_008A:
        if (time1.AddDays(2) > this.x957eec0002850847.xf98c0407c36b694d)
        {
            goto Label_0015;
        }
        return;
    Label_00B1:
        if (DateTime.Now > this.x957eec0002850847.xf98c0407c36b694d)
        {
            goto Label_01CA;
        }
        if (((uint) num1) < 0)
        {
            goto Label_0499;
        }
        time1 = DateTime.Now;
        if ((((uint) num1) | 3) != 0)
        {
            goto Label_008A;
        }
        goto Label_0015;
    Label_0106:
        this.x957eec0002850847.x01b6751913efbd51 = 2;
        xbc9689dd53140596[string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("dhpknigleinldiemjilmlicnkhjnlfaonhhochooihfpidmp", 0x1ac1af27))] = string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("fpddgnmdgoccphkb", 0x75ddb6fa)) + this.x957eec0002850847.xf98c0407c36b694d.ToString(string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("mflhjfcigfjidfajeahjbcojobfklplkpcdlmckl", 0x68787ae3))) + string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("kofhpdef", 0x2cede623));
        return;
    Label_0187:
        xbc9689dd53140596[string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("mfhdghodngfemgmechdfehkfdgbgeeigggpglfghbgnhacei", 0x2cb63710))] = string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("gjbjcgokdehljockbknkedmmihef", 0x5a852a6a)) + this.x957eec0002850847.x1bf37c739ee04ce8;
        goto Label_00B1;
    Label_01CA:
        this.x957eec0002850847.x63d2aa540697615b = string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("bdej", 0x45d793ed));
        goto Label_023D;
    Label_01ED:
        if (this.x957eec0002850847.x5d8a72c9b50ea3f1 != string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("jleogllonlcpaljpnkaaelhahkoaekfblkmbojdcljkcckbdfjidcjpdjjgeminejief", 0x1f82e489)))
        {
            goto Label_031B;
        }
        goto Label_0231;
    Label_0218:
        if (text8 != this.x957eec0002850847.x5d8a72c9b50ea3f1)
        {
            goto Label_0288;
        }
        goto Label_0187;
    Label_0231:
        if (4 == 0)
        {
            goto Label_0218;
        }
        goto Label_0187;
    Label_023D:
        if (((uint) num1) >= 0)
        {
            goto Label_0453;
        }
        goto Label_01ED;
        if ((((uint) num1) - ((uint) num1)) > uint.MaxValue)
        {
            goto Label_065A;
        }
        if (8 == 0)
        {
            goto Label_07B8;
        }
        goto Label_0328;
    Label_0288:
        this.x957eec0002850847.x63d2aa540697615b = string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("pmek", 0x6a1ca48b));
        this.x957eec0002850847.x01b6751913efbd51 = 2;
        LogService.Write(string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("nbfjhdmjocdknckkddblfdilecplgpfmfanmpbengblnfbcolbjonbapmahpemnpeoeaoolaapcbhcihmiehpjfhcpjjkpnk", 0x40c094d1)));
        xbc9689dd53140596[string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("gajbacachbhcgbocmbfdobmdnadeoojeabbffaiflapfkmfg", 0x374c18ba))] = string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("bilecgefagceaooejijegpaemjngjpcgblng", 0x41cdce86));
        if (((uint) num1) < 0)
        {
            goto Label_0757;
        }
        return;
    Label_031B:
        text8 = this.x1083e6509fcf5d08();
        goto Label_0218;
    Label_0328:
        if (0 == 0)
        {
            goto Label_0187;
        }
        goto Label_0453;
    Label_0333:
        if (1 == 0)
        {
            goto Label_04CC;
        }
        goto Label_01ED;
    Label_0344:
        if ((text9 = text6) != null)
        {
            goto Label_060C;
        }
    Label_034E:
        num1++;
    Label_0354:
        if (num1 < textArray2.Length)
        {
            goto Label_0701;
        }
        if ((((uint) num1) & 0) == 0)
        {
            goto Label_0333;
        }
    Label_0373:
        if (15 == 0)
        {
            goto Label_03D1;
        }
        goto Label_0394;
    Label_037C:
        if ((((uint) num1) - ((uint) num1)) < 0)
        {
            goto Label_0373;
        }
    Label_0394:
        this.x957eec0002850847.x5d8a72c9b50ea3f1 = text7;
        goto Label_034E;
    Label_03A3:
        this.x957eec0002850847.x1b3806a16cc4a0b2 = text7;
        goto Label_034E;
    Label_03B7:
        if (text9 == string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("dnmnjmdoflko", 0x256dc7e)))
        {
            goto Label_0404;
        }
        goto Label_03D8;
    Label_03D1:
        if (-2147483648 == 0)
        {
            goto Label_03B7;
        }
    Label_03D8:
        if (text9 != string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("jiegdjlgnhch", 0x6d846446)))
        {
            goto Label_05D6;
        }
        goto Label_037C;
    Label_0404:
        this.x957eec0002850847.x01b6751913efbd51 = int.Parse(text7);
        if ((((uint) num1) - ((uint) num1)) > uint.MaxValue)
        {
            goto Label_0520;
        }
        if (((uint) num1) > uint.MaxValue)
        {
            goto Label_0354;
        }
        if (0 != 0)
        {
            goto Label_0708;
        }
        goto Label_034E;
    Label_0453:
        if ((((uint) num1) | 0xff) != 0)
        {
            goto Label_0106;
        }
    Label_046B:
        this.x957eec0002850847.x63d2aa540697615b = text7;
        goto Label_034E;
    Label_047D:
        this.x957eec0002850847.xf98c0407c36b694d = DateTime.Parse(text7);
        goto Label_034E;
    Label_0499:
        if ((((uint) num1) | 2) == 0)
        {
            goto Label_0508;
        }
    Label_04B1:
        if (text9 != string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("koimmopmnmgn", 0x751bc896)))
        {
            goto Label_0635;
        }
    Label_04CC:
        if ((((uint) num1) | 0x7fffffff) == 0)
        {
            return;
        }
        goto Label_046B;
    Label_04EE:
        if (text9 == string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("gihkoiokkhfl", 0x5a6a743)))
        {
            goto Label_0538;
        }
        goto Label_0499;
    Label_0508:
        if ((((uint) num1) & 0) != 0)
        {
            goto Label_04EE;
        }
        goto Label_04B1;
    Label_0520:
        if ((((uint) num1) - ((uint) num1)) < 0)
        {
            goto Label_04EE;
        }
    Label_0538:
        this.x957eec0002850847.x1bf37c739ee04ce8 = text7;
        goto Label_034E;
    Label_0553:
        if (text9 == string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("hphnpoonlnfo", 0x76c2d7a4)))
        {
            this.x957eec0002850847.xb644c3d4b9723516 = text7;
            if ((((uint) num1) + ((uint) num1)) < 0)
            {
                goto Label_0499;
            }
            if (((uint) num1) > uint.MaxValue)
            {
                goto Label_03D1;
            }
            goto Label_034E;
        }
        if (0xff != 0)
        {
            goto Label_05FD;
        }
        if ((((uint) num1) + ((uint) num1)) <= uint.MaxValue)
        {
            goto Label_03D8;
        }
    Label_05D6:
        if (text9 == string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("gghclgocaffd", 0x7afc2719)))
        {
            goto Label_03A3;
        }
        goto Label_034E;
    Label_05FD:
        if (3 != 0)
        {
            goto Label_04EE;
        }
        goto Label_04B1;
    Label_060C:
        text9 = string.IsInterned(text9);
        if ((((uint) num1) + ((uint) num1)) >= 0)
        {
            goto Label_0553;
        }
        goto Label_04EE;
    Label_0635:
        if (text9 == string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("bmbcbnicdlpc", 0xaab217c)))
        {
            goto Label_047D;
        }
        goto Label_074D;
    Label_065A:
        text7 = text5.Trim().Substring(3);
        goto Label_0344;
    Label_0701:
        text5 = textArray2[num1];
    Label_0708:
        text6 = text5.Trim().Substring(0, 3);
        if (15 == 0)
        {
            goto Label_046B;
        }
        goto Label_065A;
    Label_072C:;
        string[] textArray1 = text4.Split(new char[] { ';' });
        textArray2 = textArray1;
        num1 = 0;
        goto Label_0354;
    Label_074D:
        if (0 == 0)
        {
            goto Label_0779;
        }
        return;
    Label_0757:
        if (!this.xb12591d958637077(text4, text2))
        {
            return;
        }
        if ((((uint) num1) | 3) != 0)
        {
            goto Label_072C;
        }
    Label_0779:
        if ((((uint) num1) + ((uint) num1)) <= uint.MaxValue)
        {
            goto Label_03B7;
        }
        goto Label_04B1;
    Label_079D:
        text1 = Encoding.Unicode.GetString(Convert.FromBase64String(this.x4c262adfb75e23fa.x3f7a1096840d5b39));
    Label_07B8:
        text2 = text1.Substring(0, 0x40);
        string text3 = text1.Substring(0x40);
        text4 = this.xcc381ffa3ede662f(text3);
        goto Label_0757;
    }
    catch (Exception exception1)
    {
        LogService.Write(string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("eebfofifffpfefggkfngmfehlelhnbciecjiceajndhjgeojidfkgdmkkbdledkllcbmkcimadpmcdgnbcnn", 0x6ca750f8)));
        LogService.Write(exception1.Message);
        return;
    }
}

可以得到这样的结果
License先经过Convert.FromBase64String函数转换为byte[]
然后再把得到的byte[]通过Encoding.Unicode.GetString函数转换为unicode字符串
然后取前64位存入字符串a,剩余的存入字符串b
对b使用对称算法密钥解密
ICryptoTransform transform1 = new DESCryptoServiceProvider().CreateDecryptor(buffer1, buffer2);
    byte[] buffer3 = Encoding.Unicode.GetBytes(b);
    byte[] buffer4 = transform1.TransformFinalBlock(buffer3, 0, buffer3.Length);
最后再转换Encoding.Unicode.GetString(buffer4);
得到最终所要的字符串信息
这时候,使用“;”将字符串分割成字符串组
对每组进行判断,即可得到最终所要的信息。
这些信息,分别对应
private struct x9cebba6cbd2b2289
{
    public string xb644c3d4b9723516;//序列号SN
    public string x1bf37c739ee04ce8;//公司名称CN
    public string x63d2aa540697615b;//版本类型TY
    public DateTime xf98c0407c36b694d;//过期时间EX
    public int x01b6751913efbd51;//用户数UN
    public string x5d8a72c9b50ea3f1;//CPU编码CP
    public string x1b3806a16cc4a0b2;//可用模块MU
}
那么前面得到的前64位字符串a用来干什么的呢?
分析代码可知,这个是用来和64位后的字符串b比较,以验证签名是否正确的,查看签名验证算法函数
private bool xb12591d958637077(string x4a3f0a05c02f235f, string x8ebde9a98df80374)
{
    byte[] buffer2;
    RSAParameters parameters1;
    RSACryptoServiceProvider provider1;
    byte[] buffer3;
    byte[] buffer1 = Encoding.Unicode.GetBytes(x4a3f0a05c02f235f);
    if (3 != 0)
    {
        SHA1 sha1;
    Label_007B:
        sha1 = new SHA1Managed();
        buffer2 = sha1.ComputeHash(buffer1);
        parameters1 = new RSAParameters();
        byte[] buffer4 = new byte[3];
        buffer4[0] = 1;
        buffer4[2] = 1;
        parameters1.Exponent = buffer4;
        if (0 != 0)
        {
            goto Label_001B;
        }
        parameters1.Modulus = new byte[] { 
            “一些字节信息用做公钥”
        };
        provider1 = new RSACryptoServiceProvider();
        if (3 != 0)
        {
            if (1 != 0)
            {
                goto Label_0013;
            }
        }
        else
        {
            goto Label_007B;
        }
        goto Label_00B8;
    }
Label_0013:
    provider1.ImportParameters(parameters1);
Label_001B:
    buffer3 = Encoding.Unicode.GetBytes(x8ebde9a98df80374);
    RSAPKCS1SignatureDeformatter deformatter1 = new RSAPKCS1SignatureDeformatter(provider1);
    deformatter1.SetHashAlgorithm(string.Intern(x1110bdd110cdcea4._d574bb1a8f3e9cbc("bheldglljfcmgejm", 0x2c0db41e)));
Label_00B8:
    return deformatter1.VerifySignature(buffer2, buffer3);
}
去除花指令,调整后流程顺序后得到
private bool xb12591d958637077(string x4a3f0a05c02f235f, string x8ebde9a98df80374)
{
    byte[] buffer2;
    RSAParameters parameters1;
    RSACryptoServiceProvider provider1;
    byte[] buffer3;
    byte[] buffer1 = Encoding.Unicode.GetBytes(x4a3f0a05c02f235f);
        SHA1 sha1;
        sha1 = new SHA1Managed();
        buffer2 = sha1.ComputeHash(buffer1);
        parameters1 = new RSAParameters();
        byte[] buffer4 = new byte[3];
        buffer4[0] = 1;
        buffer4[2] = 1;
        parameters1.Exponent = buffer4;
        parameters1.Modulus = new byte[] { 
            “一些字节信息,用做公钥”
        };
        provider1 = new RSACryptoServiceProvider();
           provider1.ImportParameters(parameters1);
        buffer3 = Encoding.Unicode.GetBytes(x8ebde9a98df80374);
    RSAPKCS1SignatureDeformatter deformatter1 = new RSAPKCS1SignatureDeformatter(provider1);
    deformatter1.SetHashAlgorithm("SHA1");
    return deformatter1.VerifySignature(buffer2, buffer3);
}

我们看到这里使用了RSAPKCS1SignatureDeformatter用于校验签名,而这种校验是使用公钥和私钥的,在我们没有私钥的情况下,是不能反向操作的,那么我们如果要破解这项操作,只能使用爆破手段,跳过这段代码校验,让这段代码,无论返回值是真还是假,都会继续执行剩下的代码,而不会让验证函数退出,而导致验证不通过。
分析
private struct x9cebba6cbd2b2289
{
    public string xb644c3d4b9723516;//序列号SN
    public string x1bf37c739ee04ce8;//公司名称CN
    public string x63d2aa540697615b;//版本类型TY
    public DateTime xf98c0407c36b694d;//过期时间EX
    public int x01b6751913efbd51;//用户数UN
    public string x5d8a72c9b50ea3f1;//CPU编码CP
    public string x1b3806a16cc4a0b2;//可用模块MU
}

我们发现,公司名称,过期时间,用户数都很好理解,只要修改这些信息就可以达到我们使用他们的程序而不守时间、用户数的限制,因为我们可以根据其算法得到最后的license,但其他的信息是否会影响到程序的使用呢?程序在其他地方是否还有验证呢?
带着这个问题,我继续查看代码,发现:
版本类型TY只有3种,要么R是正式版,要么T是试用版,要么D是演示版,而演示版是当过期日期到达时就会自动切换,这时候用户数仅剩下两个。
正式版和试用版有什么区别,暂时还未发现,但改了总好看吧?:)
可用模块MU是用于license类中的另外一个验证函数
public bool VerifyModule(Page p, string ModuleCode)的,这个函数用于验证这个产品是否拥有这个模块的使用权,这是模块化的设计方式,出售的时候按模块出售,在license中加入模块信息,以作判断是否客户购买了该模块的使用权
CPU编码CP是服务器的CPU ProcessorID,当其设为00:00:00:00:00:00时,不做任何限制,也就是其并不绑定CPU,如果这个值存在,程序就会验证license中的CPU编码是否和该服务器CPU编码相同,不同则跳出验证,使程序变为未验证,看来这里还是不动为好,直接就是00:00:00:00:00:00最好了
剩下一个序列号SN,发现其在任何地方都没有明显的校验,无法得知格式,也就无法写出注册机,那么该怎么办呢?
SN仅仅存在于this.x4c262adfb75e23fa.xf67f2f73d67cc6b6中
而this.x4c262adfb75e23fa.xf67f2f73d67cc6b6除了在以上函数中被调用外,还在一个private string x579b0ad13e0e2633()函数中被调用,分析x579b0ad13e0e2633()发现其返回的是我们系统的一些信息
private string x579b0ad13e0e2633()
{
    string text4;
    string text1 = this.x1083e6509fcf5d08();
    string text2 = this.x65a2df20baac0377();
    string text3 = Environment.MachineName;
    text4 = Environment.UserDomainName;
    while (true)
    {
        string text7;
        string text5 = Environment.OSVersion.ToString();
        string text6 = Environment.Version.ToString();
        text7 = "CPUSN=" + text1 + ";";
        text7 = text7 + "IP=" + text2 + ";";
        text7 = text7 + "ComputerName=" + text3 + ";";
        text7 = text7 + "DomainName=" + text4 + ";";
        text7 = text7 + "OSVer=" + text5 + ";";
        text7 = text7 + "DotNetVer=" + text6 + ";";
        text7 = text7 + "OrgName=" + this.OrgName + ";";
        object obj1 = text7;
        object[] objArray1 = new object[4];
        objArray1[0] = obj1;
        objArray1[1] = "LicNum=";
        objArray1[2] = this.LicenseNumber;
        objArray1[3] = ";";
        text7 = string.Concat(objArray1);
        text7 = text7 + "LicExpire=" + this.Expiration.ToShortDateString() + ";";
        return (text7 + "SN=" + this.x4c262adfb75e23fa.xf67f2f73d67cc6b6);
    }
}
包括了CPU编码、IP、机器名、域名、操作系统版本、.net版本、公司名称、用户数、过期时间以及SN
而x579b0ad13e0e2633()函数被xdea9b9654684a230函数调用,xdea9b9654684a230是用来网络升级程序使用的,说明这些信息会被提交到网络上的该公司的升级服务器
xdea9b9654684a230函数惟一被调用的地方就是在构造函数中,且是符合条件
License.x51ada703efb70b66.AddHours(24) < DateTime.Now
而License.x51ada703efb70b66是用来记录升级时间的
由此,我们可以得出程序会每隔24小时提交本机的一些必要信息给升级服务器
但升级归升级,信息提交上去,难免会被服务器端的程序进行校验,这时也就明白了SN的作用,甚至服务器上可以比较程序购买时提供的一些信息来比较是否为D版或者破解
我们肯定要避免这些,就算其验证不会影响本地的使用,让其知道有问题也是比较麻烦的一件事情
所以这边的每隔24小时网络验证,也需要爆破来屏蔽

这个时候,我们明白了该怎么破解这个OA软件了
首先,去掉DLL的强签名验证,避免修改后的程序因为签名验证不通过而无法使用
二、爆破掉license中的签名验证
三、爆破掉每隔24小时网络验证
四、写一个注册机,用于根据我们需要的公司名称、版本类型、过期时间、用户数来产生license
对于其他的一些信息,比如序列号SN、CPU编码、可用模块,基本上不用去动他,反正已经爆破屏蔽掉了,当然如果要让二次开发的产品认CPU,也可以在这个注册机中增加CPU编码信息,一般情况下只要用00:00:00:00:00:00即可,模块方面,只要知道这个产品中有什么模块,他的标识是什么,也可以添加到可用模块信息中去,我比较偷懒,就用他原来的
算法嘛,也很简单
首先写一个对称加密算法
       private string xcc381ffa3ede662f(string x4a3f0a05c02f235f)
       {
           byte[] buffer2;
           byte[] buffer1 = new byte[] { “一些字节信息用做key” };
           buffer2 = new byte[] { “一些字节信息用做key” };
           ICryptoTransform transform1 = new DESCryptoServiceProvider().CreateDecryptor(buffer1, buffer2);
           byte[] buffer3 = Encoding.Unicode.GetBytes(x4a3f0a05c02f235f);
           byte[] buffer4 = transform1.TransformFinalBlock(buffer3, 0, buffer3.Length);
           return Encoding.Unicode.GetString(buffer4);
       }
的反方法
       private string re(string text4)
       {
           byte[] buffer2;
           byte[] buffer1 = new byte[] { “一些字节信息用做key” };
           buffer2 = new byte[] { “一些字节信息用做key” };
           ICryptoTransform transform1 = new DESCryptoServiceProvider().CreateEncryptor(buffer1, buffer2);
           byte[] buffer3 = Encoding.Unicode.GetBytes(text4);
           byte[] buffer4 = transform1.TransformFinalBlock(buffer3, 0, buffer3.Length);
           return Encoding.Unicode.GetString(buffer4);
       }
再将上述信息,按照其格式写好一定的数据,连接成字符串,再进行一系列的操作,得到最终的license
string text1 = Encoding.Unicode.GetString(Convert.FromBase64String("原始证书"));//原始证书
           string text2 = text1.Substring(0, 0x40);//取十进制前64位
           string text3 = text1.Substring(0x40);//取64位开始的
           string text4 = this.xcc381ffa3ede662f(text3);
           string[] str = text4.Split(new char[] { ';' });
           str[1]="CN="+this.tx_CN.Text;
           str[2]="TY=";
           if (this.ra_R.Checked)
           {
               str[2]+="R";
           }
           else if (this.ra_T.Checked)
           {
               str[2]+="T";
           }
           else if (this.ra_D.Checked)
           {
               str[2]+="D";
           }
           str[3]="EX="+Convert.ToDateTime(this.tx_EX.Text).ToString("yyyy-MM-dd");
           str[4]="UN="+this.tx_UN.Text;
           text4=string.Join(";",str);
           string text5 = this.re(text4);
           string text6=Convert.ToBase64String(Encoding.Unicode.GetBytes(text2+text5));
           this.tx_license.Text=text6;

this.tx_license.Text的值就是按照你设定的公司名称、版本类型、过期时间、用户数产生的新license
(需要说明的是,我上面对校验text4的64位unicode string就是用的原始证书的信息,也可以该成其他的信息,只要能保证其在解码时能正确在64位处进行分割即可,因为这段校验在后面的操作中会被爆破,所以正确与否完全没有意义,同样,text4中的SN信息也是一样的,我这里都是用他原来的)
把新产生的license直接更新到数据库中的license字段即可
当然,现在DLL还未爆破,这样的license是不正确的,使用的话,会说系统未注册。
对DLL的爆破,就要用到ildasm了,当然不是官方的,官方的解不开有混淆过的信息,这个是破解过的,网络上也有地方得到,反编译后,转存为il文件。
打开这个il文件,在其中找到
.publickey = (“key信息,很长的一段”) // @...O"+.k.._.ol.
.hash algorithm 0x00008004
全部删除,这样就去除了这个DLL的强签名
然后查找method
.method private hidebysig instance void x1052178a2aa3affc([System.Web]System.Web.HttpApplicationState xbc9689dd53140596) cil managed
往下找到
    调用xb12591d958637077(string, string)的地方,在其下面看到
    L_075f: brfalse.s L_0750
   我们可以发现这里表明,只要返回false就会跳到L_0750去执行,而那边就是退出校验程序
要让其不校验这段,将L_0750该为下文中
    L_0761: ldloc num1
    L_0765: conv.u4 
    L_0766: ldc.i4 3
    L_076b: or 
    L_076c: ldc.i4.0 
    L_076d: ceq 
    L_076f: stloc flag1
    L_0773: ldloc flag1
    L_0777: brfalse.s L_072c
正常跳转的L_072c即可,
即改为L_075f: brfalse.s L_072c
这样就绕过校验,程序执行时仍旧会去校验完整性,但无论你校验结果是正确还是错误,统统跳过,直接执行下面的程序,这样就等于没有校验
再找到
.method public hidebysig specialname rtspecialname instance void .ctor([System.Web]System.Web.HttpApplicationState app) cil managed
在其中找调用xdea9b9654684a230()的地方
在他的下面有一个
    IL_0043: call     bool [mscorlib]System.DateTime::op_LessThan(valuetype [mscorlib]System.DateTime,
                                              valuetype [mscorlib]System.DateTime)
IL_0048: brtrue.s   IL_0023
IL_004a: br       IL_022c
这段代码是用来判断时间的,符合条件即跳转,IL_0023所在的地方即为调用xdea9b9654684a230()函数
我们需要的是其不要跳转,那么修改IL_0023为IL_022c即可,这样无论时间判断的结果如何,都会继续执行下面的程序
改动后的结果如下:
    IL_0048: brtrue.s   IL_004a

    IL_004a: br       IL_022c

然后是用ilasm工具
执行ilasm /res=test.res test.il /DLL编译成新的DLL即可

当然这时候你还可以对新产生的DLL加强签名,我是没有加就是了,懒-__-||

到此为止,破解完成,需要注意的是
把破解后的DLL引入工程时,需要删除原有的引用,重新添加此DLL为引用后重新编译才可正常使用
在更换数据库的license后,需要把进程中的w3p.exe中断再运行页面,才会识别出新的license信息
因为该OA软件使用的时application状态来存储license信息,在w3p不中断的情况下,并不会重新编译页面,也就不会重新取这些信息,而导致license信息不更新。