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 @@

+从 `1.9.0` 版本后新增简单的 `AI` 对抗和 `IDEA` 报错对抗
+
+
+
+
+
本项目已深度集成到 `web-chains` 项目中 (https://github.com/vulhub/java-chains)

@@ -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();