diff --git a/CHANGELOG.MD b/CHANGELOG.MD index ceef2cd..57f3fc2 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,10 +1,11 @@ # CHANGELOG -## 1.8.1 +## 1.9.0 更新日志: -- [功能] 新增配置 `antiAI` 允许简单的对抗 `AI` 分析 +- [功能] 支持 `enableInvokeDynamic` 配置使 `IDEA` 报错 +- [功能] 支持 `antiAI` 允许简单的对抗 `AI` 分析 感谢以下用户的贡献: @@ -16,7 +17,7 @@ io.github.4ra1n class-obf - 1.8.1 + 1.9.0 ``` diff --git a/README.md b/README.md index a60259d..2d81f39 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,12 @@ ![](img/011.png) +从 `1.9.0` 版本后新增简单的 `AI` 对抗和 `IDEA` 报错对抗 + +![](img/012.png) + +![](img/013.png) + 本项目已深度集成到 `web-chains` 项目中 (https://github.com/vulhub/java-chains) ![](img/006.png) @@ -46,7 +52,7 @@ `class-obf` 相比商业化混淆器: -- 混淆强度不如商业化混淆器:弱于 `zkm` 混淆,接近 `allatori` 混淆 +- 混淆强度接近商业化混淆器:稍弱于 `zkm` 混淆,接近 `allatori` 混淆 - 保持更新,学习先进商业混淆器的思路,逐步完善 - 完全开源,有功能问题可以提 `PR` 贡献 - 配置简单,配置的参数 **远少于** 商业化混淆器,上手非常容易 @@ -211,6 +217,11 @@ badAnnoTextFile: bad-anno.txt # 插入对抗 PROMPT 使得 AI 分析混淆代码可能失效 # 测试功能 实际发现很多大模型无法被打断 antiAI: false + +# 是否启用 InvokeDynamic 混淆 +# 将普通的 invoke 指令转换为 invokedynamic 指令 +# 注意:只支持 STATIC 方法 且未经过完善的测试 可能不够稳定 +enableInvokeDynamic: false ``` ## 如何测试 diff --git a/img/013.png b/img/013.png new file mode 100644 index 0000000..e318ef8 Binary files /dev/null and b/img/013.png differ diff --git a/src/main/java/me/n1ar4/clazz/obfuscator/asm/InvokeDynamicClassVisitor.java b/src/main/java/me/n1ar4/clazz/obfuscator/asm/InvokeDynamicClassVisitor.java new file mode 100644 index 0000000..db93a57 --- /dev/null +++ b/src/main/java/me/n1ar4/clazz/obfuscator/asm/InvokeDynamicClassVisitor.java @@ -0,0 +1,110 @@ +package me.n1ar4.clazz.obfuscator.asm; + +import org.objectweb.asm.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; + +public class InvokeDynamicClassVisitor extends ClassVisitor { + private static final String BOOTSTRAP_METHOD_NAME = "bootstrap"; + private static final String BOOTSTRAP_METHOD_DESC = "(Ljava/lang/invoke/MethodHandles$Lookup;" + + "Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/invoke/CallSite;"; + private final Random random = new Random(); + private String className; + private boolean hasBootstrapMethod = false; + public InvokeDynamicClassVisitor(ClassVisitor classVisitor) { + super(Opcodes.ASM9, classVisitor); + } + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + this.className = name; + super.visit(version, access, name, signature, superName, interfaces); + } + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions); + return new InvokeDynamicMethodAdapter(mv); + } + @Override + public void visitEnd() { + if (hasBootstrapMethod) { + addBootstrapMethod(); + } + super.visitEnd(); + } + + private void addBootstrapMethod() { + MethodVisitor mv = super.visitMethod( + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, + BOOTSTRAP_METHOD_NAME, + BOOTSTRAP_METHOD_DESC, + null, + new String[]{"java/lang/Exception"} + ); + mv.visitCode(); + mv.visitTypeInsn(Opcodes.NEW, "java/lang/invoke/ConstantCallSite"); + mv.visitInsn(Opcodes.DUP); + mv.visitVarInsn(Opcodes.ALOAD, 3); + mv.visitMethodInsn(Opcodes.INVOKESTATIC, + "java/lang/Class", + "forName", + "(Ljava/lang/String;)Ljava/lang/Class;", + false); + mv.visitVarInsn(Opcodes.ALOAD, 0); + mv.visitInsn(Opcodes.SWAP); + mv.visitVarInsn(Opcodes.ALOAD, 4); + mv.visitVarInsn(Opcodes.ALOAD, 2); + mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, + "java/lang/invoke/MethodHandles$Lookup", + "findStatic", + "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/MethodHandle;", + false); + mv.visitMethodInsn(Opcodes.INVOKESPECIAL, + "java/lang/invoke/ConstantCallSite", + "", + "(Ljava/lang/invoke/MethodHandle;)V", + false); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(6, 5); + mv.visitEnd(); + } + + private class InvokeDynamicMethodAdapter extends MethodVisitor { + public InvokeDynamicMethodAdapter(MethodVisitor methodVisitor) { + super(Opcodes.ASM9, methodVisitor); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + // 只处理静态方法调用 + if (opcode == Opcodes.INVOKESTATIC && shouldObfuscate(owner, name)) { + hasBootstrapMethod = true; + Handle bootstrapHandle = new Handle( + Opcodes.H_INVOKESTATIC, + className, + BOOTSTRAP_METHOD_NAME, + BOOTSTRAP_METHOD_DESC, + false + ); + super.visitInvokeDynamicInsn( + name, + descriptor, + bootstrapHandle, + owner.replace('/', '.'), + name + ); + } else { + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + } + + private boolean shouldObfuscate(String owner, String name) { + if (owner.startsWith("java/") || owner.startsWith("javax/") || + owner.startsWith("sun/") || name.equals("") || name.equals("")) { + return false; + } + return !name.equals("main"); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/n1ar4/clazz/obfuscator/config/BaseConfig.java b/src/main/java/me/n1ar4/clazz/obfuscator/config/BaseConfig.java index ae69ec0..b3b9966 100644 --- a/src/main/java/me/n1ar4/clazz/obfuscator/config/BaseConfig.java +++ b/src/main/java/me/n1ar4/clazz/obfuscator/config/BaseConfig.java @@ -49,6 +49,8 @@ public class BaseConfig { private boolean antiAI; + private boolean enableInvokeDynamic; + /** * 如果配置没问题可以启动就返回 true * @@ -124,6 +126,8 @@ public class BaseConfig { config.setEnableEvilString(false); // 默认关闭 anti AI config.setAntiAI(false); + // 默认关闭 invoke dynamic + config.setEnableInvokeDynamic(false); return config; } @@ -429,4 +433,12 @@ public class BaseConfig { public void setAntiAI(boolean antiAI) { this.antiAI = antiAI; } + + public boolean isEnableInvokeDynamic() { + return enableInvokeDynamic; + } + + public void setEnableInvokeDynamic(boolean enableInvokeDynamic) { + this.enableInvokeDynamic = enableInvokeDynamic; + } } diff --git a/src/main/java/me/n1ar4/clazz/obfuscator/core/Runner.java b/src/main/java/me/n1ar4/clazz/obfuscator/core/Runner.java index 2ed66c2..285b182 100644 --- a/src/main/java/me/n1ar4/clazz/obfuscator/core/Runner.java +++ b/src/main/java/me/n1ar4/clazz/obfuscator/core/Runner.java @@ -271,6 +271,11 @@ public class Runner { logger.info("run anti ai transformer finish"); } + if (config.isEnableInvokeDynamic()) { + InvokeDynamicTransformer.transform(loader); + logger.info("run invoke dynamic transformer finish"); + } + if (config.isEnableDeleteCompileInfo()) { DeleteInfoTransformer.transform(loader); logger.info("run delete info transformer finish"); diff --git a/src/main/java/me/n1ar4/clazz/obfuscator/transform/AntiPromptTransformer.java b/src/main/java/me/n1ar4/clazz/obfuscator/transform/AntiPromptTransformer.java new file mode 100644 index 0000000..02ef1c4 --- /dev/null +++ b/src/main/java/me/n1ar4/clazz/obfuscator/transform/AntiPromptTransformer.java @@ -0,0 +1,37 @@ +package me.n1ar4.clazz.obfuscator.transform; + +import me.n1ar4.clazz.obfuscator.Const; +import me.n1ar4.clazz.obfuscator.asm.AntiPromptClassVisitor; +import me.n1ar4.clazz.obfuscator.core.ObfEnv; +import me.n1ar4.clazz.obfuscator.loader.CustomClassLoader; +import me.n1ar4.clazz.obfuscator.loader.CustomClassWriter; +import me.n1ar4.log.LogManager; +import me.n1ar4.log.Logger; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; + +import java.nio.file.Files; +import java.nio.file.Path; + +public class AntiPromptTransformer { + private static final Logger logger = LogManager.getLogger(); + + public static void transform(CustomClassLoader loader) { + Path classPath = Const.TEMP_PATH; + if (!Files.exists(classPath)) { + logger.error("class not exist: {}", classPath.toString()); + return; + } + try { + ClassReader classReader = new ClassReader(Files.readAllBytes(classPath)); + ClassWriter classWriter = new CustomClassWriter(classReader, + ObfEnv.config.isAsmAutoCompute() ? Const.WriterASMOptions : 0, loader); + AntiPromptClassVisitor changer = new AntiPromptClassVisitor(classWriter); + classReader.accept(changer, Const.ReaderASMOptions); + Files.delete(classPath); + Files.write(classPath, classWriter.toByteArray()); + } catch (Exception ex) { + logger.error("transform error: {}", ex.toString()); + } + } +} diff --git a/src/main/java/me/n1ar4/clazz/obfuscator/transform/InvokeDynamicTransformer.java b/src/main/java/me/n1ar4/clazz/obfuscator/transform/InvokeDynamicTransformer.java new file mode 100644 index 0000000..5869cf1 --- /dev/null +++ b/src/main/java/me/n1ar4/clazz/obfuscator/transform/InvokeDynamicTransformer.java @@ -0,0 +1,47 @@ +package me.n1ar4.clazz.obfuscator.transform; + +import me.n1ar4.clazz.obfuscator.Const; +import me.n1ar4.clazz.obfuscator.asm.InvokeDynamicClassVisitor; +import me.n1ar4.clazz.obfuscator.core.ObfEnv; +import me.n1ar4.clazz.obfuscator.loader.CustomClassLoader; +import me.n1ar4.clazz.obfuscator.loader.CustomClassWriter; +import me.n1ar4.log.LogManager; +import me.n1ar4.log.Logger; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; + +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * InvokeDynamic混淆转换器 + * 将普通的invoke指令转换为invokedynamic指令,增加静态分析难度 + */ +public class InvokeDynamicTransformer { + private static final Logger logger = LogManager.getLogger(); + + public static void transform(CustomClassLoader loader) { + Path classPath = Const.TEMP_PATH; + if (!Files.exists(classPath)) { + logger.error("class not exist: {}", classPath.toString()); + return; + } + + try { + logger.info("applying invoke dynamic obfuscation"); + + ClassReader classReader = new ClassReader(Files.readAllBytes(classPath)); + ClassWriter classWriter = new CustomClassWriter(classReader, + ObfEnv.config.isAsmAutoCompute() ? Const.WriterASMOptions : 0, loader); + InvokeDynamicClassVisitor changer = new InvokeDynamicClassVisitor(classWriter); + classReader.accept(changer, Const.ReaderASMOptions); + + Files.delete(classPath); + Files.write(classPath, classWriter.toByteArray()); + + logger.info("invoke dynamic obfuscation completed"); + } catch (Exception ex) { + logger.error("invoke dynamic transform error: {}", ex.toString()); + } + } +} \ No newline at end of file diff --git a/src/main/resources/config.yaml b/src/main/resources/config.yaml index 6e5b01e..514fe3e 100644 --- a/src/main/resources/config.yaml +++ b/src/main/resources/config.yaml @@ -95,4 +95,9 @@ badAnnoTextFile: bad-anno.txt # 可能的 AI 反编译对抗 # 插入对抗 PROMPT 使得 AI 分析混淆代码可能失效 # 测试功能 实际发现很多大模型无法被打断 -antiAI: false \ No newline at end of file +antiAI: false + +# 是否启用 InvokeDynamic 混淆 +# 将普通的 invoke 指令转换为 invokedynamic 指令 +# 注意:只支持 STATIC 方法 且未经过完善的测试 可能不够稳定 +enableInvokeDynamic: false \ No newline at end of file diff --git a/src/main/test/me/n1ar4/test/Test.java b/src/main/test/me/n1ar4/test/Test.java index 5586308..1a53356 100644 --- a/src/main/test/me/n1ar4/test/Test.java +++ b/src/main/test/me/n1ar4/test/Test.java @@ -13,6 +13,10 @@ public class Test { System.out.println("Counter: " + counter); } + public static void test(){ + System.out.println("static"); + } + public void testConditionalBranches() { if (flag) { System.out.println("Flag is true"); @@ -119,6 +123,7 @@ public class Test { } public static void main(String[] args) { + test(); System.out.println("=== 开始测试控制流混淆和花指令 ==="); Test test = new Test();