https://www.52pojie.cn/thread-1967307-1-1.html
感谢大佬@ljkgpxs 提供的修改反序列化的思路:
通过修改反序列化,添加 userMac、valid、validTo、paidKey 的属性值,激活时输入任意字符,即可实现在线激活或者离线激活
1、找到修改反序列化的类和方法
安装插件后,咱们分别试下在线激活和离线激活,输入任意字符点激活,
在线激活:报错不好定位错误在哪,离线激活:报错直接显示错误在哪
2024-10-15_155125.png (86.33 KB, 下载次数: 0)
下载附件
2024-10-15 17:13 上传
咱们就从离线激活入手,报错信息如下:
[Java] 纯文本查看 复制代码激活码不正常java.lang.RuntimeException: com.ccnode.codegenerator.af.f.b: 密文有问题
at com.ccnode.codegenerator.af.f.d.b(SourceFile:281)
at com.ccnode.codegenerator.af.f.e.a(SourceFile:19)
at com.ccnode.codegenerator.af.b.a.a(SourceFile:412)
at com.ccnode.codegenerator.af.c.a(SourceFile:65)
at com.ccnode.codegenerator.b.a.doOKAction(SourceFile:219)
打开反编译工具 jadx-gui-1.5.0.exe,将 instrumented-MyBatisCodeHelper-Pro241-3.3.6+2321.jar 拖进去。
找到 com.ccnode.codegenerator.af.f.d.b(SourceFile:281):
这是激活时验证公钥的地方,这不是我们要找的。
2024-10-15_162321.png (141.6 KB, 下载次数: 0)
下载附件
2024-10-15 17:13 上传
找到 com.ccnode.codegenerator.af.f.e.a(SourceFile:19):
看到 Base64.getDecoder().decode 和 f1767a.fromJson(str2, f.class),这是对字符串(str)进行解码,然后反序列化转成对象(f)的过程,这就是我们要找的。
2024-10-15_162841.png (110.57 KB, 下载次数: 0)
下载附件
2024-10-15 17:13 上传
我们看一下方法返回的对象(f)的结构:
2024-10-15_163739.png (96.64 KB, 下载次数: 0)
下载附件
2024-10-15 17:13 上传
到这我们就找到了反序列化的方法,我们修改一下这个类的方法,添加 userMac、valid、validTo、paidKey 的属性值,
修改这个方法需要添加 gson 和 jettison 两个Jar包,可以通过Maven添加依赖后下载(pom.xml添加以下依赖)。
[XML] 纯文本查看 复制代码
com.google.code.gson
gson
2.11.0
org.codehaus.jettison
jettison
1.5.4
[Java] 纯文本查看 复制代码File oldJar = new File("D:\\test\\instrumented-MyBatisCodeHelper-Pro241-3.3.6+2321.jar");
String decodeClassFullName = "com.ccnode.codegenerator.af.f.e";
String jarDir = oldJar.getParent();
// 加载类
ClassPool classPool = ClassPool.getDefault();
// 添加需要修改和所依赖的Jar包
classPool.appendClassPath(oldJar.getAbsolutePath());
classPool.appendClassPath("D:\\test\\gson-2.11.0.jar");
classPool.appendClassPath("D:\\test\\jettison-1.5.4.jar");
// 获取需要修改的解码类
CtClass decodeCtClass = classPool.get(decodeClassFullName);
// 解码类的解码方法并修改
CtMethod decodeCtMethod = Arrays.stream(decodeCtClass.getDeclaredMethods())
.filter(method -> {
try {
return Modifier.isPublic(method.getModifiers())
&& method.getParameterTypes().length == 1
&& String.class.getSimpleName().equals(method.getParameterTypes()[0].getSimpleName());
} catch (NotFoundException e) {
throw new RuntimeException(e);
}
}).findFirst().orElseThrow();
String returnType = decodeCtMethod.getReturnType().getName();
decodeCtMethod.setBody("{"
// 添加 userMac、valid、validTo、paidKey 属性值
+ " org.codehaus.jettison.json.JSONObject jsonObject;"
+ " try {"
+ " jsonObject = new org.codehaus.jettison.json.JSONObject($1);"
+ " } catch (Exception e) {"
+ " jsonObject = new org.codehaus.jettison.json.JSONObject();"
+ " }"
+ " if (!jsonObject.has(\"userMac\") || jsonObject.get(\"userMac\") == null) {"
+ " String macAddress;"
+ " try {"
+ " java.net.InetAddress ip = java.net.InetAddress.getLocalHost();"
+ " java.net.NetworkInterface network = java.net.NetworkInterface.getByInetAddress(ip);"
+ " byte[] mac = network.getHardwareAddress();"
+ " StringBuilder stringBuilder = new StringBuilder();"
+ " for (int i = 0; i > 4) & 0xF, 16));"
+ " stringBuilder.append(Character.forDigit(b & 0xF, 16));"
+ " stringBuilder.append(\"-\");"
+ " }"
+ " macAddress = stringBuilder.substring(0, stringBuilder.length() - 1).toUpperCase();"
+ " } catch (Exception e) {"
+ " macAddress = \"00-00-00-00-00-00\";"
+ " }"
+ " jsonObject.put(\"userMac\", macAddress);"
+ " }"
+ " jsonObject.put(\"valid\", true);"
+ " jsonObject.put(\"validTo\", 4102415999000L);"
+ " jsonObject.put(\"paidKey\", java.util.UUID.randomUUID().toString());"
+ " $1 = jsonObject.toString();"
// 将Json字符串转换成指定对象
+ "com.google.gson.Gson gson = new com.google.gson.Gson();"
+ returnType + " result = (" + returnType + ")gson.fromJson($1," + returnType + ".class);"
+ "return result;"
+ "}");
// 将修改后的解码类文件写到本地
decodeCtClass.writeFile(jarDir);
// 修改强制退出类
String exitClassFullName = "com.ccnode.codegenerator.myconfigurable.Profile";
CtClass exitCtClass = classPool.get(exitClassFullName);
exitCtClass.getDeclaredMethod("getIfUseNewMapping").setBody("{return 1;}");
// 将修改后的解码类文件写到本地
exitCtClass.writeFile(jarDir);
我们将修改好的 com.ccnode.codegenerator.af.f.e.class 文件替换原来Jar包里的文件,试着在线和离线激活,随意输入字符都能激活成功
2024-10-15_165603.png (67.81 KB, 下载次数: 0)
下载附件
2024-10-15 17:13 上传
PS:后面新版本如何快速找到这个类?在 jadx-gui-1.5.0.exe 的工具栏点文本搜索,输入“private static Gson”,一下子就找到了
2、哈哈,显示激活成功了,是不是很高兴,别急还有坑。
在使用插件生成代码时,会出现弹窗,然后强制idea退出
2024-10-15_165753.png (23.23 KB, 下载次数: 0)
下载附件
2024-10-15 17:13 上传
在 jadx-gui-1.5.0.exe 的工具栏点文本搜索,输入“你正在使用”,点击进去就能看到插件做了判断:
getIfUseNewMapping() > 100 就会出现弹窗,然后强制退出
2024-10-15_172745.png (37.37 KB, 下载次数: 0)
下载附件
2024-10-15 17:32 上传
我们修改下 com.ccnode.codegenerator.myconfigurable.Profile 的 getIfUseNewMapping(),直接返回1即可。[Java] 纯文本查看 复制代码// 修改强制退出类
String exitClassFullName = "com.ccnode.codegenerator.myconfigurable.Profile";
CtClass exitCtClass = classPool.get(exitClassFullName);
exitCtClass.getDeclaredMethod("getIfUseNewMapping").setBody("{return 1;}");
// 将修改后的解码类文件写到本地
exitCtClass.writeFile(jarDir);
将修改好的 com.ccnode.codegenerator.myconfigurable.Profile.class 文件替换原来Jar包里的文件,就不会出现弹窗了。
3、将修改好的 class 文件替换原来Jar包里的文件,手动替换容易出错,咱们用Java来替换,生成新的Jar包[Java] 纯文本查看 复制代码// 生成新Jar包,并将修改好的类文件添加到新的Jar包中
File newJar = new File(oldJar.getAbsolutePath().replace(".jar", "-new" + ".jar"));
// 修改好的类文件
List editClasses = Stream.of(decodeClassFullName, exitClassFullName).map(item -> item.replace(".", "/") + ".class").toList();
try (JarFile jarFile = new JarFile(oldJar); JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(newJar))) {
Enumeration entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
// 将修改后的类文件和原Jar包的类文件,都添加到新的JAR中
try (InputStream inputStream = editClasses.contains(entry.getName()) ? new FileInputStream(new File(jarDir, entry.getName())) : jarFile.getInputStream(entry)) {
jarOutputStream.putNextEntry(new JarEntry(entry.getName()));
byte[] bytes = inputStream.readAllBytes();
jarOutputStream.write(bytes);
jarOutputStream.closeEntry();
}
}
}