Octopus 注册算法分析

逆向教程 3个月前 mimosa
1,031 0 0
这是 3Ds Max 的一个插件,据说这个插件很强……但是我怎么在百度搜不到这个插件???
B站倒是有介绍的教程:
3ds max插件《octopus》 第一话:插件骚功能演示
https://www.bilibili.com/video/av48411342
在插件官网看了一下售价,25 欧元,换成人民币接近 200,因此我不会提供对应的注册机成品,白嫖党请自觉退散。
Octopus 注册算法分析
从官网下载了一份插件,安装后运行 Max,找到了注册的位置。
Octopus 注册算法分析
然后傻眼了,我不知道作为 Max 的插件,我应该逆向哪个文件……
看了一下 Max 的目录结构,盲猜他在这里,毕竟这个文件夹叫插件。
Octopus 注册算法分析
进去以后,看到是有个和 Octopus 有关的文件的,查壳是 .NET 的。载入 dnspy 分析了一下,只是个单纯的入口文件,没有实际的功能在里面。
Octopus 注册算法分析
怎么办呢?笨办法,把 Octopus 插件的所有文件都拿出来分析下吧。
那我得看看,安装包都包含了哪些文件,接着我瞎了。
Octopus 注册算法分析
这个插件的安装包是作者自己写的,而不是用的打包工具……
既然是 .NET 的,继续丢到 dnspy 里分析,然后可以找到我们本次逆向的目标。
Octopus 注册算法分析
老规矩,把原文件备份一份,然后查壳。
Octopus 注册算法分析
看到 Confuser(1.X)[-] 差点儿扭头就走,查了一下保护发现啥都没有,觉得此中定有蹊跷。
Octopus 注册算法分析
丢到 dnspy 里看了下,确实没有混淆。那应该是 DIE 的判断规则有问题了,看了一下 DIE 的判断依据:
Octopus 注册算法分析
if(PE.isNETStringPresent("ConfusedByAttribute"))
{
    var sConfuserVersion=getConfuserVersion();
    if(sConfuserVersion!="")
    {
        sVersion=sConfuserVersion;
    }
    else
    {
        sVersion="1.X";
    }
    bDetected=1;
}

只是单纯的判断了下是否有 "ConfusedByAttribute",回到 dnspy 中看了一眼,确实是有这个方法。

Octopus 注册算法分析
这就是查壳结果错误的原因,下次我写程序也加一个这个方法骗骗人吧。
既然没壳,就直接 dnpsy 开整就行,根据插件注册界面上的文字,进行搜索。
搜 Activate 并没有一步到位,搜 Activat 倒是找到了有用的。
Octopus 注册算法分析
Octopus 注册算法分析
注意上面的箭头,为了防止你问我:为什么搜 Activate 没有匹配到 ActivateLicense 。我直接解释了吧,注意当前的搜索选项卡,检索的是 数字/字符串,而 ActivateLicense 是一个方法名,因此不在搜索结果中。
而搜索 Activate 匹配到的,则是箭头指出的 activation 这个字符串。这就是原因,闭嘴吧狗子,别再问了。
来分析下这个注册逻辑吧,ActivateLicense 方法如下:
internal static void ActivateLicense(string email, string key, string productName, string proxyString)
{
    if(!RapidToolsLicensing.LocalCheck(productName))
    {
        WebRequest webRequest = WebRequest.Create(RapidToolsLicensing.licServer);
        webRequest.Method = "POST";
        string s = string.Format("wc-api=software-api&request=activation&email={0}&license_key={1}&product_id={2}", email, key, productName);
        byte[] bytes = Encoding.UTF8.GetBytes(s);
        webRequest.ContentType = "application/x-www-form-urlencoded";
        webRequest.ContentLength = (long) bytes.Length;
        if(!string.IsNullOrEmpty(proxyString))
        {
            webRequest.Proxy = new WebProxy(proxyString);
        }
        Stream stream = null;
        try
        {
            stream = webRequest.GetRequestStream();
            stream.Write(bytes, 0, bytes.Length);
            stream.Close();
        }
        catch
        {
            if(MessageBox.Show("这个框太长,我屏蔽了", "Error reaching the license server", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
            {
                Process.Start("敏感网址,我屏蔽了");
            }
        }
        if(stream != null)
        {
            WebResponse response = webRequest.GetResponse();
            stream = response.GetResponseStream();
            StreamReader streamReader = new StreamReader(stream);
            string input = streamReader.ReadToEnd();
            LicenseData licenseData = new JavaScriptSerializer().Deserialize < LicenseData > (input);
            licenseData.key = key;
            licenseData.product = productName;
            string text = new JavaScriptSerializer().Serialize(licenseData);
            MessageBox.Show(text);
            if(licenseData.code == null && licenseData.error == null && licenseData.activated)
            {
                string user = GetInfo.GetUSER();
                string s2 = StringCipher.Encrypt(text, user);
                if(!Directory.Exists(Dir.LicenseDir))
                {
                    Directory.CreateDirectory(Dir.LicenseDir);
                }
                FileStream fileStream = File.Create(Dir.LicenseDir + productName + ".lic");
                byte[] bytes2 = Encoding.ASCII.GetBytes(s2);
                fileStream.Write(bytes2, 0, bytes2.Length);
                fileStream.Close();
            }
            streamReader.Close();
            stream.Close();
            response.Close();
        }
    }
}
一开始使用了 LocalCheck 方法不知道判断了什么鬼,整个方法都在这个 if 语句中了。根据这个方法名,猜测是检查本地的许可证或者什么内容的。先不去管它,假设 LocalCheck 方法返回的值为 false,则进入了 if 语句的函数体。
首先它 POST 了一个网址,提交了三个变量,email, key, productName,回忆一下软件的注册界面,是有这三个项的,分别对应了 Order E-Mail,License Key,Product Name。
那注册逻辑就是,通过网络请求,来验证这三个参数,是否是花钱买的正版呗。
Octopus 注册算法分析
如果提交的 POST 成功并返回了验证结果,到 87 行,判断结果不为 null,接下来将要对结果进行处理。
92 行,读取了这个结果。
93 行,将返回结果反序列化。
94/95 行,将 key 和 productName 的值赋给 licenseData 类的成员。
96 行,序列化数据。
97 行是我调试的时候加的,请无视,谢谢合作。
98 行判断,licenseData 的成员 code 和 error 为 null,且 activated 为 true 时,执行 100 - 109 行的注册逻辑。
综上所述,整个注册流程为:
填写激活信息后,按下激活按钮,插件会通过 POST 提交你的注册信息到服务器,服务器验证你的激活码后会返回结果,插件根据结果判断,如果 98 行的判断成立,则执行注册,如果不成立,哪来的回哪里去。
为了验证这个想法,抓一下插件提交的数据看看:
Octopus 注册算法分析
Octopus 注册算法分析
抓到了,说明上述逻辑是合理的,确实是提交了数据,并且如果激活码错误的话,返回的结果为:
{
    "error":"Invalid Request",
    "code":"100",
    "activated":false,
    "timestamp":1585576146,
    "sig":"58a0cd5d909332214feac40d3f5551e7"
}
那接下来看,如果 98 行的判断成立,会有什么情况。
if(licenseData.code == null && licenseData.error == null && licenseData.activated)
{
    string user = GetInfo.GetUSER();
    string s2 = StringCipher.Encrypt(text, user);
    if(!Directory.Exists(Dir.LicenseDir))
    {
        Directory.CreateDirectory(Dir.LicenseDir);
    }
    FileStream fileStream = File.Create(Dir.LicenseDir + productName + ".lic");
    byte[] bytes2 = Encoding.ASCII.GetBytes(s2);
    fileStream.Write(bytes2, 0, bytes2.Length);
    fileStream.Close();
}
通过 GetUSER 方法获得了 user,然后通过 Encrypt 方法对数据加密并赋值给 s2 ,跟进 Encrypt 方法看了一眼,喜闻乐见的 AES 加密。
Octopus 注册算法分析
然后……他把加密后的数据直接作为授权文件了。
多么令人窒息的操作。
如果要写注册机的话,跳过那复杂的判断逻辑,直接伪造注册信息,然后调用这段生成授权文件的方法,即可,就像我这样。
Octopus 注册算法分析
Octopus 注册算法分析
粗暴的让人心疼。
但在使用的时候会遇到一个问题,分配快捷键后,按下快捷键会验证许可。
Octopus 注册算法分析
搜索一下找到他。
Octopus 注册算法分析
这个判断方法超级长……
Octopus 注册算法分析
有一个全局的 LicValid 参数,如果这个参数为 True 了,就说明许可是正确的。
然后有点儿懵,您这是啥玩意儿???
反复读了两遍,我就只看懂了前几行。
107 行,如果检查到授权文件目录下存在一个名叫 octopus 的许可,并且这个许可是正确的,则 LicValid 的值就为 True。
那这也简单,就生成一个名叫 octopus 的许可就行了。
Octopus 注册算法分析
这样的话就能搞定了。
后来返回去看了一下,问题出在激活按钮按下后,除了 POST 提交数据验证后生成许可以外,还有一个验证本地许可来赋值的方法。
Octopus 注册算法分析
因此如果要制作,使用任意文件名的许可的话,需要跟进这个方法里去看看,以便创造符合这个方法的判断规则的许可证。
大概看了下好像是我写注册机时对 productName 的大小写处理有问题,不过不重要,统一换成 octopus 就好了。
另外因为 isLicValid 这个参数的赋值是由 LocalCheck 方法控制的,因此可以直接改返回值来爆破。
除此之外,因为其将 POST 后的返回值判断后,直接写入了授权,所以直接抓包,然后改包成满足注册条件的结果,也是可以实现注册。
按照难易程度的话,爆破最容易,改包其次,注册机最难。另外,别找我要成品,不提供,谢谢合作。
版权声明:mimosa 发表于 2022年2月20日 下午6:35。
转载请注明:Octopus 注册算法分析 | 软件爱好者

相关文章

暂无评论

暂无评论...