S0cke3t
文章43
标签29
分类12
Xcloud license analysis & crack

Xcloud license analysis & crack

本文只做技术分析交流使用,请勿用于非法用途

前言

Wgcloud是一个Linux运维监控工具,支持系统硬件信息,内存,cpu,温度,磁盘空间及IO,硬盘smart,系统负载,网络流量等监控,服务接口,大屏展示,拓扑图,进程监控,端口监控,docker监控,文件防篡改,日志监控,数据可视化,web ssh,堡垒机,指令下发批量执行,Linux面板(探针),SNMP,故障告警,分为个人版和专业版,个人版免费但有监控数量限制,专业版按监控数量收费

授权分析

此系统采用springboot作为开发框架,只有一个jar包

image-20230319170355599

wgcloud-server-release.jar拖入jd-gui进行反编译

image-20230319170541413

在task里可以看到相关license验证的计划任务

image-20230319170718757

在validateLicense中主要验证逻辑由LicenseUtil.validateLicense处理

跟进LicenseUtil.validateLicense

image-20230319170854194

com.wgcloud.util.licenseLicenseUtil:validateLicense中,可以看到具体license验证逻辑

image-20230319171127558

validateLicense首先读取目录下是否含有license.txt文件,如果没有检测到则重置为个人版

image-20230319171206214

如果存在license文件则调用 RSAEncrypt.decrypt并传入相应公钥对license文件进行解密

并将解密后的数据按-进行分割,其中[0]为授权到期时间,[1]为授权数量,[2]为授权名称

取值完成过后将具体值赋值给StaticKeys对象,StaticKeys是一个license对象,负责保存相关license信息

image-20230319171557229

随后判断授权时间是否是2099开头,是的话显示为永久授权,最后打印相关license信息

跟进RSAEncrypt.decrypt看一下解密中还有没有其他操作

image-20230319171702695

可以看到在RSAEncrypt.decrypt就是常规的RSA解密,并无其他操作

到这里我们大致了解了license解密验证相关流程,可以选择破解的方案有很多

  • 直接修改validateLicense整体代码逻辑,直接赋值StaticKeys对象值
  • 修改RSAEncrypt.decrypt返回值
  • 替换解密公钥
  • …..

笔者这里选择替换解密公钥

实现

具体实现我们可以反编译一份代码,直接在源代码基础上修改相关代码,但此方式工作量比较大且比较繁琐,无法适配新版本

这里笔者采用javassist技术进行无侵入式修改字节码技术,也就是javaagent技术

具体如何编写一个javaagent,感兴趣的可以自己去google搜索,这里给出一个大致demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
if(className.startsWith("com/wgcloud/util/license/LicenseUtil")) {
System.out.println("Finded license class -------- " + className + "\r\n");
}
if ("com/wgcloud/util/license/LicenseUtil".equals(className)) {
System.out.println("*****Loaded license class****");
try {
ClassPool classPool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(this.getClass());
//String path = System.getProperty("user.dir");
String jarpath = getPath() + "/wgcloud-server-release.jar";
System.out.println("Current path: " + jarpath);
classPool.appendClassPath(jarpath);
classPool.insertClassPath(classPath);
CtClass clazz = classPool.get("com.wgcloud.util.license.LicenseUtil");
CtMethod convertToAbbr = clazz.getDeclaredMethod("validateLicense");
String methodBody = "{try {\n" +
" String path = System.getProperty(\"user.dir\");\n" +
" String licenseStr = readLicFile(path + \"/license.txt\");\n" +
" if (org.apache.commons.lang3.StringUtils.isEmpty(licenseStr)) {\n" +
" logger.info(\"没有检测到授权文件/server/license.txt,当前版本重置为个人版\");\n" +
" return \"0\";\n" +
" } \n" +
" byte[] res = com.wgcloud.util.license.RSAEncrypt.decrypt(com.wgcloud.util.license.RSAEncrypt.loadPublicKeyByStr(\"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCHIQUYtBNK6seYa3+q8qtbtuobp1WZ/5dphM1EzpvIC3FWcak6vfM5ZHsT+29b6uuqW6CgeLusU1Dc9bcokytfKWv/mFWf8BRJE+tMvUnmqSUE0KjNdtWljU001LjNLw7sydv1FoDIOoA1EqHBgKguVkUCZYr9SdUcMhCMBSHzswIDAQAB\"), cn.hutool.core.codec.Base64.decode(licenseStr));\n" +
" String restr = new String(res);\n" +
" if (org.apache.commons.lang3.StringUtils.isEmpty(restr)) {\n" +
" logger.info(\"license解密公钥为空\");\n" +
" return \"0\";\n" +
" } \n" +
" String[] restrs = restr.split(\"-\");\n" +
" String date = restrs[0];\n" +
" String num = restrs[1];\n" +
" String name = restrs[2];\n" +
" com.wgcloud.util.staticvar.StaticKeys.LICENSE_DATE = date;\n" +
" com.wgcloud.util.staticvar.StaticKeys.LICENSE_NUM = Integer.valueOf(num).intValue();\n" +
" com.wgcloud.util.staticvar.StaticKeys.LICENSE_NAME = name;\n" +
" logger.info(\"license解析成功:到期时间\" + (com.wgcloud.util.staticvar.StaticKeys.LICENSE_DATE.startsWith(\"2099\") ? \"永久授权\" : com.wgcloud.util.staticvar.StaticKeys.LICENSE_DATE) + \",授权节点数量\" + com.wgcloud.util.staticvar.StaticKeys.LICENSE_NUM + \",客户名称\" + com.wgcloud.util.staticvar.StaticKeys.LICENSE_NAME);\n" +
" long LICENSE_DATE = Long.valueOf(date).longValue();\n" +
" if ($1 > com.wgcloud.util.staticvar.StaticKeys.LICENSE_NUM || $3 > com.wgcloud.util.staticvar.StaticKeys.LICENSE_NUM) {\n" +
" logInfoService.save(\"授权解析错误\", \"监控节点超过授权节点数量:\" + num + \",当前监控节点数量:\" + $1, \"2\");\n" +
" logger.info(\"监控节点超过授权节点数量,当前监控节点数量\" + $1 + \",数通PING数量\" + $3);\n" +
" return \"3\";\n" +
" } \n" +
" Long nowDate = Long.valueOf(com.wgcloud.util.DateUtil.getCurrentDate().replace(\"-\", \"\"));\n" +
" if (nowDate.longValue() > LICENSE_DATE) {\n" +
" logInfoService.save(\"授权解析错误\", \"授权已经到期:\" + date, \"2\");\n" +
" logger.info(\"授权已经到期\");\n" +
" return \"2\";\n" +
" } \n" +
" com.wgcloud.util.staticvar.StaticKeys.LICENSE_PAGE = com.wgcloud.util.staticvar.StaticKeys.LICENSE_NUM / $2 + 1;\n" +
" } catch (Exception e) {\n" +
" logger.error(\"解析授权文件错误:\", e);\n" +
" return \"0\";\n" +
" } \n" +
" return \"1\";}";
convertToAbbr.setBody(methodBody);
// 返回字节码,并且detachCtClass对象
byte[] byteCode = clazz.toBytecode();
//detach的意思是将内存中曾经被javassist加载过的对象移除,如果下次有需要在内存中找不到会重新走javassist加载
clazz.detach();
System.out.println("Patch successfully");
return byteCode;
} catch (Exception e) {
e.printStackTrace();
}
}
return if(className.startsWith("com/wgcloud/util/license/LicenseUtil")) {
System.out.println("Finded license class -------- " + className + "\r\n");
}
if ("com/wgcloud/util/license/LicenseUtil".equals(className)) {
System.out.println("*****Loaded license class****");
try {
ClassPool classPool = ClassPool.getDefault();
ClassClassPath classPath = new ClassClassPath(this.getClass());
//String path = System.getProperty("user.dir");
String jarpath = getPath() + "/wgcloud-server-release.jar";
System.out.println("Current path: " + jarpath);
classPool.appendClassPath(jarpath);
classPool.insertClassPath(classPath);
CtClass clazz = classPool.get("com.wgcloud.util.license.LicenseUtil");
CtMethod convertToAbbr = clazz.getDeclaredMethod("validateLicense");
String methodBody = "{try {\n" +
" String path = System.getProperty(\"user.dir\");\n" +
" String licenseStr = readLicFile(path + \"/license.txt\");\n" +
" if (org.apache.commons.lang3.StringUtils.isEmpty(licenseStr)) {\n" +
" logger.info(\"没有检测到授权文件/server/license.txt,当前版本重置为个人版\");\n" +
" return \"0\";\n" +
" } \n" +
" byte[] res = com.wgcloud.util.license.RSAEncrypt.decrypt(com.wgcloud.util.license.RSAEncrypt.loadPublicKeyByStr(\"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCHIQUYtBNK6seYa3+q8qtbtuobp1WZ/5dphM1EzpvIC3FWcak6vfM5ZHsT+29b6uuqW6CgeLusU1Dc9bcokytfKWv/mFWf8BRJE+tMvUnmqSUE0KjNdtWljU001LjNLw7sydv1FoDIOoA1EqHBgKguVkUCZYr9SdUcMhCMBSHzswIDAQAB\"), cn.hutool.core.codec.Base64.decode(licenseStr));\n" +
" String restr = new String(res);\n" +
" if (org.apache.commons.lang3.StringUtils.isEmpty(restr)) {\n" +
" logger.info(\"license解密公钥为空\");\n" +
" return \"0\";\n" +
" } \n" +
" String[] restrs = restr.split(\"-\");\n" +
" String date = restrs[0];\n" +
" String num = restrs[1];\n" +
" String name = restrs[2];\n" +
" com.wgcloud.util.staticvar.StaticKeys.LICENSE_DATE = date;\n" +
" com.wgcloud.util.staticvar.StaticKeys.LICENSE_NUM = Integer.valueOf(num).intValue();\n" +
" com.wgcloud.util.staticvar.StaticKeys.LICENSE_NAME = name;\n" +
" logger.info(\"license解析成功:到期时间\" + (com.wgcloud.util.staticvar.StaticKeys.LICENSE_DATE.startsWith(\"2099\") ? \"永久授权\" : com.wgcloud.util.staticvar.StaticKeys.LICENSE_DATE) + \",授权节点数量\" + com.wgcloud.util.staticvar.StaticKeys.LICENSE_NUM + \",客户名称\" + com.wgcloud.util.staticvar.StaticKeys.LICENSE_NAME);\n" +
" long LICENSE_DATE = Long.valueOf(date).longValue();\n" +
" if ($1 > com.wgcloud.util.staticvar.StaticKeys.LICENSE_NUM || $3 > com.wgcloud.util.staticvar.StaticKeys.LICENSE_NUM) {\n" +
" logInfoService.save(\"授权解析错误\", \"监控节点超过授权节点数量:\" + num + \",当前监控节点数量:\" + $1, \"2\");\n" +
" logger.info(\"监控节点超过授权节点数量,当前监控节点数量\" + $1 + \",数通PING数量\" + $3);\n" +
" return \"3\";\n" +
" } \n" +
" Long nowDate = Long.valueOf(com.wgcloud.util.DateUtil.getCurrentDate().replace(\"-\", \"\"));\n" +
" if (nowDate.longValue() > LICENSE_DATE) {\n" +
" logInfoService.save(\"授权解析错误\", \"授权已经到期:\" + date, \"2\");\n" +
" logger.info(\"授权已经到期\");\n" +
" return \"2\";\n" +
" } \n" +
" com.wgcloud.util.staticvar.StaticKeys.LICENSE_PAGE = com.wgcloud.util.staticvar.StaticKeys.LICENSE_NUM / $2 + 1;\n" +
" } catch (Exception e) {\n" +
" logger.error(\"解析授权文件错误:\", e);\n" +
" return \"0\";\n" +
" } \n" +
" return \"1\";}";
convertToAbbr.setBody(methodBody);
// 返回字节码,并且detachCtClass对象
byte[] byteCode = clazz.toBytecode();
//detach的意思是将内存中曾经被javassist加载过的对象移除,如果下次有需要在内存中找不到会重新走javassist加载
clazz.detach();
System.out.println("Patch successfully");
return byteCode;
} catch (Exception e) {
e.printStackTrace();
}
}
return null;

大致流程为,检测到加载com.wgcloud.util.license.LicenseUtil类时,直接修改validateLicense函数代码体中的解密公钥

效果

按照验证流程生成license.txt文件放入根目录下

image-20230319182847269

将agent编译成jar后使用-javaagent参数启动wgcloud-server

1
java -javaagent:Wg-agent-1.0-SNAPSHOT-jar-with-dependencies.jar -jar wgcloud-server-release.jar

image-20230318155825914

成功解析授权文件,登录系统查看

image-20230318160022557

Bingo

本文仅作为技术分享交流,不提供任何成品文件及工具

创业不易,有能力的请支持正版

购买Wgcloud

本文作者:S0cke3t
本文链接:https://www.saferoad.cc/Crack/Xcloud-license-analysis-crack.html
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可
×