CrackJavaXcloud license analysis & crack
S0cke3t本文只做技术分析交流使用,请勿用于非法用途
前言
Wgcloud是一个Linux运维监控工具,支持系统硬件信息,内存,cpu,温度,磁盘空间及IO,硬盘smart,系统负载,网络流量等监控,服务接口,大屏展示,拓扑图,进程监控,端口监控,docker监控,文件防篡改,日志监控,数据可视化,web ssh,堡垒机,指令下发批量执行,Linux面板(探针),SNMP,故障告警,分为个人版和专业版,个人版免费但有监控数量限制,专业版按监控数量收费
授权分析
此系统采用springboot作为开发框架,只有一个jar包
将wgcloud-server-release.jar
拖入jd-gui进行反编译
在task里可以看到相关license验证的计划任务
在validateLicense中主要验证逻辑由LicenseUtil.validateLicense
处理
跟进LicenseUtil.validateLicense
在com.wgcloud.util.licenseLicenseUtil:validateLicense
中,可以看到具体license验证逻辑
validateLicense
首先读取目录下是否含有license.txt文件,如果没有检测到则重置为个人版
如果存在license文件则调用 RSAEncrypt.decrypt
并传入相应公钥对license文件进行解密
并将解密后的数据按-
进行分割,其中[0]
为授权到期时间,[1]
为授权数量,[2]
为授权名称
取值完成过后将具体值赋值给StaticKeys对象,StaticKeys是一个license对象,负责保存相关license信息
随后判断授权时间是否是2099开头,是的话显示为永久授权,最后打印相关license信息
跟进RSAEncrypt.decrypt
看一下解密中还有没有其他操作
可以看到在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 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); byte[] byteCode = clazz.toBytecode(); 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 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); byte[] byteCode = clazz.toBytecode(); clazz.detach(); System.out.println("Patch successfully"); return byteCode; } catch (Exception e) { e.printStackTrace(); } } return null;
|
大致流程为,检测到加载com.wgcloud.util.license.LicenseUtil
类时,直接修改validateLicense
函数代码体中的解密公钥
效果
按照验证流程生成license.txt文件放入根目录下
将agent编译成jar后使用-javaagent
参数启动wgcloud-server
1
| java -javaagent:Wg-agent-1.0-SNAPSHOT-jar-with-dependencies.jar -jar wgcloud-server-release.jar
|
成功解析授权文件,登录系统查看
Bingo
注
本文仅作为技术分享交流,不提供任何成品文件及工具
创业不易,有能力的请支持正版
购买Wgcloud