某低代码平台 逆向分析(二)【客户端注册/发布/插件】

查看 82|回复 9
作者:pjy612   
某低代码平台 逆向分析(二)【客户端注册/发布/插件】
前情提要
昨天不小心把VS给卸了。。。导致就算摸鱼想码文也不好贴图了 囧。。。
还好今儿看着 QA 没报啥 BUG 就继续码文吧~
书接上文
某低代码平台 逆向分析(一)【验证逻辑分析和实践】
(出处: 吾爱破解论坛)  
通过手动的方式把 服务端 授权先给弄了,离线注册也对上了。
那么现在就要来看看客户端相关功能是否正常了  
_(:3」∠)_ 这波弄完基本就真掏空了...  
嘉宾介绍
见上期 某低代码平台 逆向分析(一)【验证逻辑分析和实践】
准备工作
先进 客户端 目录看两眼
Forguncy 8\Website\designerBin
主程序是 Forguncy.exe 然后也有眼熟的 CommonUtilities.dll 先都备份一下,然后老样子扔 de4dot 里面去混淆。
加上 --dont-rename 免得有啥名称依赖。
处理完改个名 然后 跑跑看。


1.png (86.12 KB, 下载次数: 0)
下载附件
2023-4-21 18:23 上传

看来功能正常 开工。
客户端 注册码分析
先弄个假码试试


2.png (49.6 KB, 下载次数: 0)
下载附件
2023-4-21 18:23 上传

本能歪路子找触发
emmm 弹了个消息框,关键词 C# WinFrom,埋藏N年的血脉觉醒了。 无脑搜个 MessageBox


3.png (64.43 KB, 下载次数: 0)
下载附件
2023-4-21 18:23 上传

看来有好多 找个显眼的 比如 ShowMessageBox 然后 往里面看 看到
调用了 系统的 MessageBox.Show 在最靠内的 底层函数上 打个断点,然后 附加进程调试!


4.png (63.4 KB, 下载次数: 0)
下载附件
2023-4-21 18:23 上传

再点一下!


5.png (47.67 KB, 下载次数: 0)
下载附件
2023-4-21 18:23 上传

断下来了 是想要的内容,那就去看看堆栈。


6.png (100.56 KB, 下载次数: 0)
下载附件
2023-4-21 18:23 上传

先向上(工具是向下)找几个明显属于主程序的函数。
发现进的好像都是系统函数的,那就个挂断点,然后再重试一下假码。
好像 还是没有就只能往上多找找了。


7.png (92.4 KB, 下载次数: 0)
下载附件
2023-4-21 18:23 上传

这个 VerifyCodeValidatorWindow 看名称挺像 验证码 注册框的。
里面也有验证,不过 也许是 附加调试 权限咱们再直接拿 dnspy 调试看看。


8.png (56.81 KB, 下载次数: 0)
下载附件
2023-4-21 18:23 上传

果然 这样就能明显定位到上一层了。


9.png (14.59 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

  
正常找文案
因为多语言的 所以肯定有语言包。
Forguncy 8\Website\designerBin\zh-CN
下面的 资源 DLL 都拖 dnspy 里面搜一下。


10.png (20.47 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

然后在用关键词 VerifyCode_Failed 搜一下。


11.png (59.78 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

就找到位置了。
找到验证码触发后就可以看逻辑了。
public static bool a(string A_0)
{
        string text;
        string text2;
        if (!Forguncy.q.a.a(A_0, out text, out text2)) //验证码的有效性
        {
                return false;
        }
        string text3 = string.Format("\"{0}\"", A_0.Replace("\"", "\\\""));
        bool flag = false;
        bool flag2;
        try
        {
                FileUtilities.CreateHiddeUACProcess("VerifyCodeActive.exe", text3);//写激活码
                flag = true;
                if (ResourceHelper.IsChinese())
                {
                        Forguncy.q.a(text, text2); //上传记录
                        ae.Instance.y().ActiveDesignerRequest(text);//GA Google那个埋点还是收集什么的。
                }
                if (ResourceHelper.IsChinese() || ResourceHelper.IsEnglish())
                {
                        CollaborationManager.Instance.InitGlobalSignature();
                }
                flag2 = flag;
        }
        catch (Exception ex)
        {
                TraceHelper.Write("active cn captcha failed.", true);
                TraceHelper.TraceException(ex, null, "WriteVerifyInfo");
                flag2 = flag;
        }
        return flag2;
}
//Forguncy.q.a.a(A_0, out text, out text2)
public static bool a(string A_0, out string A_1, out string A_2)
{
        A_1 = string.Empty;
        A_2 = string.Empty;
        if (string.IsNullOrEmpty(A_0))//非空
        {
                TraceHelper.Write("VerifyCode failed, value is null.", true);
                return false;
        }
        string[] array = null;
        bool flag;
        try
        {
                array = JsonUtilities.FromJsonString(A_0); //注册码是个json数组
                goto IL_51;
        }
        catch (Exception ex)
        {
                TraceHelper.Write("VerifyCode failed, get json crashed.", true);
                TraceHelper.Write(A_0, true);
                TraceHelper.TraceException(ex, null, "VerifyCode");
                flag = false;
        }
        return flag;
        IL_51:
        if (array != null && array.Length == 2)
        {
                return Forguncy.q.a.a(array[0], array[1], out A_1, out A_2);//数组长度2
        }
        TraceHelper.Write("VerifyCode failed, json result invalid.", true);
        TraceHelper.Write(A_0, true);
        return false;
}
//Forguncy.q.a.a(array[0], array[1], out A_1, out A_2)
//A_0
//A_1
public static bool a(string A_0, string A_1, out string A_2, out string A_3)
{
        A_2 = string.Empty;
        A_3 = string.Empty;
        string[] array = A_0.Split(new char[] { '#' }, 2); //数组0部分 用 #  分隔
        if (array != null)
        {
                if (array.Length == 2)// # 分隔长度为2
                {
                        int num = 0;
                        if (!int.TryParse(array[1], out num) || !EmailValidator.EmailIsValid(array[0])) // 前部分为 email 之后为一个数字
                        {
                                return false;
                        }
                        if (!Forguncy.q.a.a())  // 这个 是判断 MD5 功能是否能用,不能用就直接返回了。
                        {
                                A_2 = array[0];
                                A_3 = array[1];
                                return true;
                        }
                        string text = null;
                        if (!Forguncy.q.a.a(A_0, ref text)) // 这里是 将 email#num 部分 Md5 成 Base64
                        {
                                return false;
                        }
                        bool flag = Forguncy.q.a.a(Forguncy.q.a.a, text, A_1); // RSA 验证
                        if (flag)
                        {
                                A_2 = array[0];
                                A_3 = array[1];
                        }
                        return flag;
                }
        }
        return false;
}
//Forguncy.q.a.a(A_0, ref text)
private static bool a(string A_0, ref string A_1)
{
        bool flag;
        try
        {
                HashAlgorithm hashAlgorithm = HashAlgorithm.Create("MD5");
                byte[] bytes = Encoding.UTF8.GetBytes(A_0);
                byte[] array = hashAlgorithm.ComputeHash(bytes);
                A_1 = Convert.ToBase64String(array);
                flag = true;
        }
        catch (Exception ex)
        {
                TraceHelper.Write("GetHash crashed", true);
                TraceHelper.Write("strSource:", true);
                TraceHelper.Write(A_0, true);
                TraceHelper.Write("strHashData:", true);
                TraceHelper.Write(A_1, true);
                TraceHelper.TraceException(ex, null, "GetHash");
                flag = false;
        }
        return flag;
}
//Forguncy.q.a.a(Forguncy.q.a.a, text, A_1)
private static bool a(string A_0, string A_1, string A_2)
{
        bool flag;
        try
        {
                byte[] array = Convert.FromBase64String(A_1);
                RSACryptoServiceProvider rsacryptoServiceProvider = new RSACryptoServiceProvider();
                rsacryptoServiceProvider.FromXmlString(A_0);  //这里也弄了一个公钥
                RSAPKCS1SignatureDeformatter rsapkcs1SignatureDeformatter = new RSAPKCS1SignatureDeformatter(rsacryptoServiceProvider);
                rsapkcs1SignatureDeformatter.SetHashAlgorithm("MD5");
                byte[] array2 = Convert.FromBase64String(A_2);
                if (rsapkcs1SignatureDeformatter.VerifySignature(array, array2)) //这里有RSA鉴权   为了和服务端统一 我们直接替换公钥
                {
                        flag = true;
                }
                else
                {
                        flag = false;
                }
        }
        catch (Exception ex)
        {
                TraceHelper.Write("SignatureDeformatter crashed", true);
                TraceHelper.Write("strKeyPublic:", true);
                TraceHelper.Write(A_0, true);
                TraceHelper.Write("strHashbyteDeformatter:", true);
                TraceHelper.Write(A_1, true);
                TraceHelper.Write("strDeformatterData:", true);
                TraceHelper.Write(A_2, true);
                TraceHelper.TraceException(ex, null, "SignatureDeformatter");
                flag = false;
        }
        return flag;
}
那么注册码的逻辑就知道了
["email#num",RSASign("email#num","MD5",PKCS1填充)]
开始码
无名小银,如果您要查看本帖隐藏内容请回复


12.png (17.25 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

激活成功! 可惜成功没啥提示。
发布功能 分析
简单用用之后,咱们来看看发布吧。
噔噔咚。。。发布失败。。。还触发了之前下的错误信息框断点。


13.png (108.46 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

刚好拿来定位问题。


14.png (21.45 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

//请求验证
private static void a(string A_0, string A_1, string A_2, bool A_3, bool A_4)
{
        if (!ResourceHelper.IsChinese())
        {
                return;
        }
        Dictionary dictionary = new Dictionary();
        dictionary["userName"] = A_1;
        dictionary["password"] = A_2;
        dictionary["isGetServerData"] = A_3;
        dictionary["publishUser"] = A_4;
        Forguncy.d.b(ServiceVisitor.CreateServiceVisitor(A_0).CallMethodToGetResultStream("Publish/GetLS", dictionary, null, true));
}


15.png (41.75 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

眼熟吧,这儿也得改。。。
改完再试试~
欧耶 发布成功!
然后 页面打开了!


20.png (14.29 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

这提示是什么鬼。。。
然后 在后端死找没找到相关内容,突发奇想会不会在前端。。。
然后就


21.png (72.69 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传



22.png (116.74 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传



23.png (99.79 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

   
数据是接口返回的 那么得去 发布端 查问题了   
通过新出现的 进程去查


24.png (21.29 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

  
然后查 代码


25.png (98.52 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

  
确认 发布后站点代码在 Forguncy.Server2
有兴趣同学的话可以追下接口(这里因为用了异步所以追码流程太长了就略过了)
这里直接说结果吧
因为 既然出现了鉴权失效 那结合之前的尿性 肯定就是哪儿 RSA 漏了,直接搜 F1=  


26.png (34.16 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

果然有。。。改呗。。。


27.png (81.12 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

成了
收费插件分析
插件市场里面有好多的插件,简单拖几个玩玩吧。


16.png (83.84 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

结果发布的时候出问题了


17.png (31.45 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

服务端控制台也有提示


18.png (34.57 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

咱们瞧瞧去
根据错误信息定位到
private static void c(string A_0, string A_1)
{
        FileUtilities.DeleteFolder(A_0);
        throw new Exception(A_1);
}
//然后 查引用查到
private static void a(UserServiceDBContext A_0, string A_1, Version A_2)
{
        string text = Forguncy.UserService2.Controllers.f.d(A_0).ServerLicenseInfoList[0].RealIssuingKey;
        w w = new w
        {
                ServerKey = text,
                AppPath = A_1,
                version = A_2.ToString()
        };
        byte[] bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(w));
        string text2;
        //看来 ForguncyServerConsole 这个应用的 AppCheck 逻辑里面有 验证啊,而且返回值必须不在 198-200 才能成功。
        switch (ProcessHelper.ExecuteProcessWithError("ForguncyServerConsole", "AppCheck" + " " + Convert.ToBase64String(bytes), out text2))
        {
        case 197:
                break;
        case 198:
                DeployApp.c(A_1, Resources.NoPermissionMsg);
                return;
        case 199:
                DeployApp.c(A_1, Resources.PlugInPaymentMsg + text2);
                return;
        case 200:
                DeployApp.c(A_1, Resources.PublishFailed);
                break;
        default:
                return;
        }
}


19.png (25.58 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

internal enum v
{
        // Token: 0x04000317 RID: 791
        a = 197,
        // Token: 0x04000318 RID: 792
        b,
        // Token: 0x04000319 RID: 793
        c,
        // Token: 0x0400031A RID: 794
        d
}
private static v a(string[] A_0, out string A_1)
{
        v v;
        try
        {
                A_1 = string.Empty;
                int num = A_0.Length;
                byte[] array = Convert.FromBase64String(A_0[num - 1]);
                w w = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(array));
                if (new Version(w.version)  list = new List();
        foreach (string text in Directory.GetFiles(A_0, "PluginConfig.json", SearchOption.AllDirectories))
        {
                u.b b = new u.b();
                aq aq = JsonConvert.DeserializeObject(File.ReadAllText(text));
                b.a = Path.GetFullPath(Path.Combine(text, ".."));
                try
                {
                        IEnumerable enumerable = aq.assembly.Where(new Func(b.b));//这里是找不在插件目录内的 dll
                        if (enumerable.Any())
                        {
                                foreach (string text2 in enumerable)
                                {
                                        if (!File.Exists(Path.Combine(b.a, Path.GetFileNameWithoutExtension(text2) + ".edll")))
                                        {
                                                throw new Exception("Could not find assembly " + text2 + "."); //如果 dll 不在 目录内 并且 没有匹配的 .edll 就直接弹错
                                        }
                                        m m = u.a(b.a, aq);//清单内容解密 但是也是 RSA 解密的是官方的授权。不好改 所以不动它
                                        if (string.IsNullOrEmpty((m != null) ? m.ServerLicenseKey : null) || ((m != null) ? m.ServerLicenseKey : null) != A_1)
                                        {
                                                //关键点在这儿,如果那个插件 授权 的 ServerLicenseKey 和我们注册的Key不一致就添加到list 里面。
                                                //那么我们实际上得爆破这里 不添加就行了。或者最后强制返回 空
                                                list.Add(u.a(aq));
                                        }
                                }
                        }
                }
                catch (Exception ex)
                {
                        TraceHelper.WriteLicenseException(ex.Message);
                        throw new Exception("Plugin " + u.a(aq) + " license validation failed.");
                }
        }
        return string.Join(",", list);
}
将最后改成
120        0160        ldstr        ","
121        0165        ldloc.3
122        0166        call        string [netstandard]System.String::Join(string, class [netstandard]System.Collections.Generic.IEnumerable`1)
123        016B        pop
124        016C        ldnull
125        016D        ret
在发布看看~


28.png (34.34 KB, 下载次数: 0)
下载附件
2023-4-21 18:24 上传

成功!
因为篇幅有限 其他没讲的就放到下次最终篇吧!
下期预告

  • 不看不知道 一看吓一跳,应用里竟然有检测白嫖的暗桩?!拿网上破解版来干坏事的小伙伴小心会遭重哟!

  • 虽然这个应用混淆不复杂也能改代码,但是校验的点有些多。。。
    不小心漏了就要继续分析,而且每次都手动改exe或dll是不是很麻烦?
    对这种能改的程序 有没有什么偷懒的方案,这种相同的逻辑能不能通杀?比如 Hook 一下?  

    感兴趣的话 给咱点免费评分呗... 咱好有动力抓紧码字啊...
    关于 Wyn
    看到不少留言说顺手弄一下 Wyn
    简单看过,这两个其实验证逻辑基本一样的。。。
    只是 鉴权类 有些差别。
    其他的就只有 各个 字段对应的值要怎么设了,有没有验证什么的
    而且 Wyn 只有一个端 只需要改一个dll
    真心推荐看过这两篇帖子的坛友自己上手试呢~
    相信这个如果弄会了,Wyn 一定 so easy !
    期待大伙儿对 Wyn 的分析贴呢~

    下载次数, 下载附件

  • pjy612
    OP
      

    沙发自己坐~
    lml0126   

    大佬牛,估计他家其他产品也类似,价格还贵
    3yu3   

    大佬的技术高超,文章写的更加精彩
    liduowu   

    弹窗应该也是那个COM来的,至于出现免费字样,在那JS文件里也可以找到这字样,清一下就好了
    koolaa   

    看看,学习一下,谢谢
    spray   

    来学习啦!
    yongyou   

    谢谢表哥分享的技术贴,学习下
    liangang   

    大佬的技术高超,文章写的更加精彩
    bin1421457921   

    111111111111111111111
    您需要登录后才可以回帖 登录 | 立即注册

    返回顶部