前情提要
昨天不小心把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 的分析贴呢~