MyBatisCodeHelperPro Crack
准备工作
开始处理
1. 反编译
- 反编译 JAR 包
- 使用 IDEA 打开反编译后的代码
- 全局检索关键字符串
TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FDZzUyUjExV0h1MysvNUV2WnhkS0l2a3ovekpnS2VNUUhNLytMVkxSZS9zWUpFQlUxbUUrODc3MmJJckk4UThscldqSHc5cmVjQ1RWVVhXUnhWYXBndk1HYTZ3KzU4STZwYXdSaFhwZDBrRkhUY2xxeUZGWFpoS3ZiQUtoblphRGNuZkJtSkhObTQwR0JFTGpCTmx5MXpha2FIblFmUzF0QlhaSGQwOUV0c2VRSURBUUFC- 正式版的类名和方法名通常经过混淆,需要结合上下文定位,示例如下:
java
package com.ccnode.codegenerator.validate.utils;
import com.ccnode.codegenerator.validate.response.ValidateNewResultData;
import com.google.gson.Gson;
import java.io.UnsupportedEncodingException;
import java.security.interfaces.RSAPublicKey;
import java.util.Base64;
import kotlin.text.Charsets;
public class RsaUtils {
private static Gson gson = new Gson();
public static ValidateNewResultData fromEncrptData(String encrptString) {
Object var1 = null;
byte[] res;
try {
res = RSAEncrypt.decrypt(RSAEncrypt.loadPublicKeyByStr(new String(Base64.getDecoder().decode("TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FDZzUyUjExV0h1MysvNUV2WnhkS0l2a3ovekpnS2VNUUhNLytMVkxSZS9zWUpFQlUxbUUrODc3MmJJckk4UThscldqSHc5cmVjQ1RWVVhXUnhWYXBndk1HYTZ3KzU4STZwYXdSaFhwZDBrRkhUY2xxeUZGWFpoS3ZiQUtoblphRGNuZkJtSkhObTQwR0JFTGpCTmx5MXpha2FIblFmUzF0QlhaSGQwOUV0c2VRSURBUUFC"), Charsets.UTF_8)), Base64.getDecoder().decode(encrptString));
} catch (MyException var7) {
res = RSAEncrypt.decrypt((RSAPublicKey)(new MyPublicKey()), Base64.getDecoder().decode(encrptString));
}
ValidateNewResultData data = null;
String json;
try {
json = new String(res, "UTF-8");
} catch (UnsupportedEncodingException var6) {
throw new RuntimeException(var6);
}
try {
data = (ValidateNewResultData)gson.fromJson(json, ValidateNewResultData.class);
return data;
} catch (Exception var5) {
throw new RuntimeException("gson catch exception, the json string is" + json, var5);
}
}
}2. 添加依赖
xml
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.30.2-GA</version>
</dependency>注:反编译后需要先确认实际的全限定类名和方法名,再替换为对应值。
java
import javassist.*;
import javassist.bytecode.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Stream;
/**
* 基于特征字符串的字节码修改器
* 用于查找并修改 MyBatisCodeHelper-Pro 插件中的特定类,绕过其验证逻辑。
*/
public class MyBatisCodeHelperProCrack {
// 【特征码】:需要在字节码常量池中搜索的目标字符串(Base64编码的特定特征)
private static final String TARGET_FEATURE = "TUlHZk1BMEdDU3FHU0liM0RRRUJBUVVBQTRHTkFEQ0JpUUtCZ1FDZzUyUjExV0h1MysvNUV2WnhkS0l2a3ovekpnS2VNUUhNLytMVkxSZS9zWUpFQlUxbUUrODc3MmJJckk4UThscldqSHc5cmVjQ1RWVVhXUnhWYXBndk1HYTZ3KzU4STZwYXdSaFhwZDBrRkhUY2xxeUZGWFpoS3ZiQUtoblphRGNuZkJtSkhObTQwR0JFTGpCTmx5MXpha2FIblFmUzF0QlhaSGQwOUV0c2VRSURBUUFC";
// 【目标目录】:插件所在的本地目录名称
private static final String TARGET_DIR_NAME = "MyBatisCodeHelper-Pro";
public static void main(String[] args) {
try {
// 1. 寻找目标 JAR 文件
File jarFile = findMatchingJar();
File parentDir = jarFile.getParentFile();
System.out.println("🎯 [TARGET] 找到目标 JAR: " + jarFile.getName());
// 2. 清理上一次运行留下的临时文件
cleanupTempDirectory(parentDir);
System.out.println("🔍 [ACTION] 正在扫描包含特征字符串的类...\n");
// 3. 扫描 JAR 并修改符合条件的类
int modifiedCount = scanAndModifyTargets(jarFile, parentDir.getAbsolutePath());
System.out.println("\n----------------------------------------");
// 4. 如果有类被修改,则更新 JAR 包
if (modifiedCount > 0) {
updateJarFile(jarFile);
} else {
System.out.println("⚠️ [WARN] 未修改任何类,跳过 JAR 更新。");
}
// 5. 再次清理临时文件(保持环境整洁)
cleanupTempDirectory(parentDir);
System.out.println("🎉 [DONE] 流程执行完毕!");
} catch (Exception e) {
System.err.println("❌ [ERROR] 程序异常: " + e.getMessage());
}
}
/**
* 扫描并修改目标类
* 逻辑:遍历 JAR 中所有类 -> 查找常量池包含特征字符串的类 -> 查找包含 LDC 指令的方法 -> 替换方法体
*/
private static int scanAndModifyTargets(File jarFile, String outputDirPath) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.appendClassPath(jarFile.getAbsolutePath());
JarFile jar = new JarFile(jarFile);
Enumeration<JarEntry> entries = jar.entries();
int modifiedCount = 0;
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
// 只处理 .class 文件
if (!entry.getName().endsWith(".class")) continue;
String className = entry.getName().replace("/", ".").replace(".class", "");
try {
CtClass ctClass = pool.get(className);
ClassFile classFile = ctClass.getClassFile();
ConstPool constPool = classFile.getConstPool();
// 在常量池中查找特征字符串的索引
int targetCpIndex = findStringInConstPool(constPool, TARGET_FEATURE);
// 如果类的常量池中没有该特征,跳过
if (targetCpIndex == -1) {
ctClass.detach();
continue;
}
System.out.println("📦 [SCAN] 发现特征类: " + className);
boolean modified = false;
// 遍历类中的所有方法
for (MethodInfo methodInfo : classFile.getMethods()) {
CodeAttribute codeAttr = methodInfo.getCodeAttribute();
if (codeAttr == null) continue;
// 【核心匹配逻辑】:只有当方法字节码中真实出现了 LDC 指令加载该特征字符串时,才进行处理
// 这能精准定位到验证逻辑的核心方法
if (containsLdcInstruction(codeAttr, targetCpIndex)) {
String methodName = methodInfo.getName();
String descriptor = methodInfo.getDescriptor();
// --- 方法过滤逻辑 ---
// 1. 必须是静态方法 (static)
int accessFlags = methodInfo.getAccessFlags();
if ((accessFlags & AccessFlag.STATIC) == 0) {
System.out.println(" ⏩ [SKIP] 非static方法: " + methodName + descriptor);
continue;
}
// 2. 参数个数必须 <= 1 (通常用于处理验证Key或JSON输入)
CtClass[] paramTypes = Descriptor.getParameterTypes(descriptor, pool);
if (paramTypes.length > 1) {
System.out.println(" ⏩ [SKIP] 参数个数>1: " + methodName + descriptor);
continue;
}
// --- 逻辑替换准备 ---
// 通过方法名和描述符精准查找 CtMethod 对象(解决重载方法的歧义)
CtMethod targetCtMethod = null;
for (CtMethod ctMethod : ctClass.getDeclaredMethods()) {
if (ctMethod.getName().equals(methodName) &&
ctMethod.getMethodInfo2().getDescriptor().equals(descriptor)) {
targetCtMethod = ctMethod;
break;
}
}
if (targetCtMethod == null) {
System.out.println(" ⚠️ [WARN] 未找到匹配的 CtMethod: " + methodName + descriptor);
continue;
}
try {
// 生成绕过逻辑的代码并替换原方法体
String newBody = generateSmartBypassBody(targetCtMethod, descriptor);
targetCtMethod.setBody(newBody);
System.out.println(" ✅ [PATCH] 成功拦截并替换: " + methodName + descriptor);
System.out.println(" ↳ 注入逻辑: " + newBody);
modified = true;
} catch (NotFoundException e) {
System.out.println(" ⏭️ [SKIP] 缺少外部依赖: " + methodName + " | 缺失: " + e.getMessage());
} catch (Exception e) {
System.out.println(" ❌ [FAIL] 替换失败: " + methodName + descriptor + " | 原因: " + e.getMessage());
}
}
}
// 如果该类被修改,则写入文件系统(用于后续打包)
if (modified) {
ctClass.writeFile(outputDirPath);
modifiedCount++;
}
ctClass.detach();
} catch (Exception e) {
System.out.println(" ❌ [ERROR] 处理类失败: " + className + " | 原因: " + e.getMessage());
}
}
jar.close();
System.out.println("\n📊 [SUMMARY] 扫描完成,共精准修改了 " + modifiedCount + " 个类。");
return modifiedCount;
}
/**
* 在常量池中查找指定字符串
* @return 字符串在常量池中的索引,未找到返回 -1
*/
private static int findStringInConstPool(ConstPool cp, String target) {
for (int i = 1; i < cp.getSize(); i++) {
if (cp.getTag(i) == ConstPool.CONST_String) {
try {
if (target.equals(cp.getStringInfo(i))) return i;
} catch (Exception ignored) {}
}
}
return -1;
}
/**
* 检查方法字节码中是否包含加载指定常量池索引的 LDC 指令
* 这是精准定位“验证逻辑”的关键,避免误伤仅引用了该字符串但不用于验证的类
*/
private static boolean containsLdcInstruction(CodeAttribute codeAttr, int targetCpIndex) {
try {
CodeIterator iter = codeAttr.iterator();
while (iter.hasNext()) {
int pos = iter.next();
int op = iter.byteAt(pos);
// 检查 LDC, LDC_W, LDC2_W 指令
if (op == Opcode.LDC) {
if (iter.byteAt(pos + 1) == targetCpIndex) return true;
} else if (op == Opcode.LDC_W || op == Opcode.LDC2_W) {
if (iter.u16bitAt(pos + 1) == targetCpIndex) return true;
}
}
} catch (BadBytecode ignored) {}
return false;
}
/**
* 智能生成绕过逻辑的代码体
* 根据原方法的返回类型和参数类型,生成不同的返回逻辑
*/
private static String generateSmartBypassBody(CtMethod method, String descriptor) throws Exception {
// 解析方法描述符,获取返回类型描述
String returnDesc = descriptor.substring(descriptor.indexOf(')') + 1);
// 1. void 返回
if ("V".equals(returnDesc)) return "{ return; }";
// 2. boolean 返回 -> 返回 false (失败/无效)
if ("Z".equals(returnDesc)) return "return false;";
// 3. 数值类型返回 -> 返回 0
if ("I".equals(returnDesc) || "J".equals(returnDesc) || "F".equals(returnDesc) ||
"D".equals(returnDesc) || "B".equals(returnDesc) || "C".equals(returnDesc) || "S".equals(returnDesc)) {
return "return 0;";
}
// 4. 对象引用类型返回
if (returnDesc.startsWith("L") && returnDesc.endsWith(";")) {
String internalName = returnDesc.substring(1, returnDesc.length() - 1);
String fullyQualifiedName = internalName.replace('/', '.');
CtClass[] paramTypes = method.getParameterTypes();
// 【智能修复】:如果方法接收一个 String 参数(通常是 JSON 字符串)
// 则尝试使用 Gson 将其反序列化为期望的对象返回
// 这通常用于绕过需要返回复杂 LicenseResult 对象的验证
if (paramTypes.length == 1 && paramTypes[0].getName().equals("java.lang.String")) {
return "return (" + fullyQualifiedName + ") new com.google.gson.Gson().fromJson($1, " + fullyQualifiedName + ".class);";
}
// 其他对象类型直接返回 null
return "return null;";
}
return "return null;";
}
/**
* 寻找目标 JAR 文件
* 优先在 ~/Downloads/MyBatisCodeHelper-Pro/lib/ 下查找,未找到则在当前目录查找
*/
private static File findMatchingJar() throws Exception {
String userHome = System.getProperty("user.home");
Path basePath = Paths.get(userHome, "Downloads", TARGET_DIR_NAME, "lib");
if (!Files.exists(basePath) || !Files.isDirectory(basePath)) {
basePath = Paths.get(".");
}
try (Stream<Path> stream = Files.list(basePath)) {
return stream
.filter(path -> path.getFileName().toString().startsWith("instrumented-MyBatisCodeHelper-Pro"))
.findFirst()
.map(Path::toFile)
.orElseThrow(() -> new FileNotFoundException("未找到匹配的 JAR 文件"));
}
}
/**
* 更新 JAR 文件
* 调用系统的 jar 命令将修改后的 com 目录更新回原 JAR 包
*/
private static void updateJarFile(File jarFile) throws Exception {
File directory = jarFile.getParentFile();
Path comDir = directory.toPath().resolve("com");
if (!Files.exists(comDir)) {
System.out.println("⚠️ [WARN] 未找到修改后的 com 目录,跳过更新。");
return;
}
ProcessBuilder pb = new ProcessBuilder("jar", "uvf", jarFile.getName(), "com");
pb.redirectErrorStream(true);
pb.directory(directory);
System.out.println("🔧 [ACTION] 正在更新 JAR 文件...");
Process process = pb.start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(" | " + line);
}
}
int exitCode = process.waitFor();
if (exitCode == 0) {
System.out.println("✅ [SUCCESS] JAR 文件更新成功!");
} else {
System.err.println("❌ [ERROR] JAR 文件更新失败,退出码: " + exitCode);
}
}
/**
* 清理临时目录
*/
private static void cleanupTempDirectory(File directory) throws IOException {
Path tempDir = directory.toPath().resolve("com");
if (Files.isDirectory(tempDir)) {
deleteDirectoryRecursively(tempDir.toFile());
}
}
private static void deleteDirectoryRecursively(File directory) throws IOException {
try (Stream<Path> walkStream = Files.walk(directory.toPath())) {
walkStream.sorted(Comparator.reverseOrder()).forEach(path -> {
try {
Files.delete(path);
} catch (IOException ignored) {}
});
}
}
}3. 打包安装
- 将最终修改后的目录重新打包为 ZIP 插件包
- 将修改后的插件包安装到 IDEA 中
4. 离线激活
- Download ⚠️ 仅供个人学习使用
text
{
"paidKey": "xm.z",
"valid": true,
"userMac": "${your unique code}",
"validTo": 4859711999000
}