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

作者:root 时间:24-06-28 阅读数:155人阅读

 

https://www.52pojie.cn/thread-1776905-1-1.html

前情提要

昨天不小心把VS给卸了。。。导致就算摸鱼想码文也不好贴图了 囧。。。
还好今儿看着 QA 没报啥 BUG 就继续码文吧~
书接上文
某低代码平台 逆向分析(一)【验证逻辑分析和实践】
(出处: 吾爱破解论坛)  

通过手动的方式把 服务端 授权先给弄了,离线注册也对上了。
那么现在就要来看看客户端相关功能是否正常了  

_(:3」∠)_ 这波弄完基本就真掏空了...  

嘉宾介绍

见上期 某低代码平台 逆向分析(一)【验证逻辑分析和实践】

准备工作

先进 客户端 目录看两眼
Forguncy 8\Website\designerBin
主程序是 Forguncy.exe 然后也有眼熟的 CommonUtilities.dll 先都备份一下,然后老样子扔 de4dot 里面去混淆。
加上 --dont-rename 免得有啥名称依赖。

处理完改个名 然后 跑跑看。


看来功能正常 开工。

 

客户端 注册码分析

先弄个假码试试

 

本能歪路子找触发

emmm 弹了个消息框,关键词 C# WinFrom,埋藏N年的血脉觉醒了。 无脑搜个 MessageBox


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

再点一下!

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

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

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

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

 

正常找文案

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


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

就找到位置了。

 

找到验证码触发后就可以看逻辑了。

 复制代码 隐藏代码
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<string[]>(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填充)]

开始码


 复制代码 隐藏代码
static string GetClientCode(string email="52@pojie.com#666"){     string tmp = email;     byte[] bytes = Encoding.UTF8.GetBytes(tmp);     byte[] signData = rsa.SignData(bytes, HashAlgorithmName.MD5, RSASignaturePadding.Pkcs1);     return JsonConvert.SerializeObject(new[] {tmp, Convert.ToBase64String(signData)}); }



激活成功! 可惜成功没啥提示。

 

发布功能 分析

简单用用之后,咱们来看看发布吧。
噔噔咚。。。发布失败。。。还触发了之前下的错误信息框断点。


刚好拿来定位问题。

 

 复制代码 隐藏代码
//请求验证private static void a(string A_0, string A_1, string A_2, bool A_3, bool A_4){         if (!ResourceHelper.IsChinese())         {                 return;         }         Dictionary<string, object> dictionary = new Dictionary<string, object>();         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)); }


眼熟吧,这儿也得改。。。
改完再试试~

 

欧耶 发布成功!

然后 页面打开了!


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


    

 

数据是接口返回的 那么得去 发布端 查问题了   

通过新出现的 进程去查

  

 

然后查 代码

  

 

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

 

果然有。。。改呗。。。

 

成了

收费插件分析

插件市场里面有好多的插件,简单拖几个玩玩吧。


结果发布的时候出问题了

服务端控制台也有提示

咱们瞧瞧去

 

根据错误信息定位到

 复制代码 隐藏代码
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;         } }

 

 复制代码 隐藏代码
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<w>(Encoding.UTF8.GetString(array));                 if (new Version(w.version) < new Version("7.0.0.0"))                 {                         v = v.a;                 }                 else                {                         string text = u.b(Path.Combine(w.AppPath, "Metadata"), w.ServerKey);                         if (!string.IsNullOrWhiteSpace(text))                         {                                 if (SystemConfigDef.IsCloudServer)                                 {                                         TraceHelper.Write("IPProtectionCheckState CheckLicense CheckTemplateString checkResult=" + text, true);                                 }                                 A_1 = text;                                 v = v.b;                         }                         else                        {                                 string text2 = u.a(Path.Combine(w.AppPath, "Plugins"), w.ServerKey);//也就是 text2 必须没有值                                if (!string.IsNullOrWhiteSpace(text2))                                 {                                         if (SystemConfigDef.IsCloudServer)                                         {                                                 TraceHelper.Write("IPProtectionCheckState CheckLicense CheckPluginProtection pluginCheckResult=" + text2, true);                                         }                                         A_1 = text2;                                         v = v.c;                                 }                                 else                                {                                         v = v.a; //所以要解排除错误得走这儿                                }                         }                 }         }         catch (Exception ex)         {                 TraceHelper.Write(ex.Message, true);                 A_1 = ex.Message;                 v = v.d;         }         return v; }//u.a(Path.Combine(w.AppPath, "Plugins"), w.ServerKey)//A_1 ServerKeyinternal static string a(string A_0, string A_1) {         if (!Directory.Exists(A_0))         {                 return null;         }         List<string> list = new List<string>();         foreach (string text in Directory.GetFiles(A_0, "PluginConfig.json", SearchOption.AllDirectories))         {                 u.b b = new u.b();                 aq aq = JsonConvert.DeserializeObject<aq>(File.ReadAllText(text));                 b.a = Path.GetFullPath(Path.Combine(text, ".."));                 try                {                         IEnumerable<string> enumerable = aq.assembly.Where(new Func<string, bool>(b.b));//这里是找不在插件目录内的 dll                        if (enumerable.Any<string>())                         {                                 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.3122        0166        call        string [netstandard]System.String::Join(string, class [netstandard]System.Collections.Generic.IEnumerable`1<string>)123        016B        pop124        016C        ldnull125        016D        ret

在发布看看~


成功!

 

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

本文链接:http://0595banjia.com/?id=56 

分享到: