Skip to content

Commit

Permalink
[重要] 支持方法调用 INVOKE 指令改反射调用混淆
Browse files Browse the repository at this point in the history
  • Loading branch information
4ra1n committed Jan 27, 2025
1 parent 0722e25 commit f2733ed
Show file tree
Hide file tree
Showing 9 changed files with 447 additions and 4 deletions.
24 changes: 20 additions & 4 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
# CHANGELOG

## 1.3.3

todo
## 1.4.0

支持将 `INVOKE*` 指令转为反射调用,结合其他配置可完成进阶混淆

```yaml
# 是否将 JVM INVOKE 指令改成反射调用
# 注意:该功能会明显影响执行效率
# 优点:经过该混淆后会更加难以分析
# 缺点:该功能未经过完善测试不稳定
enableReflect: false
# INVOKEVIRTUAL 转换
enableReflectVirtual: false
# INVOKESTATIC 转换
enableReflectStatic: false
# INVOKESPECIAL 转换
enableReflectSpecial: false
# INVOKEINTERFACE 转换
enableReflectInterface: false
```
更新日志:
- todo
- [重要] 支持方法调用 `INVOKE` 指令改反射调用混淆

感谢以下用户的贡献:

Expand Down
61 changes: 61 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@

![](img/006.png)

`1.4.0` 版本开始支持将 `INVOKE` 指令转为反射结合其他混淆方式隐藏特征

![](img/008.png)

## 背景

`jar-analyzer` 系列曾有一款工具 `jar-obfuscator` 实现 `jar` 包的混淆
Expand Down Expand Up @@ -232,6 +236,63 @@ enableHideField: false
# 可以防止大部分 IDEA 版本反编译
enableHideMethod: false

# 是否将 JVM INVOKE 指令改成反射调用
# 注意:该功能会明显影响执行效率
# 优点:经过该混淆后会更加难以分析
# 缺点:该功能未经过完善测试不稳定
enableReflect: false
# INVOKEVIRTUAL 转换
enableReflectVirtual: false
# INVOKESTATIC 转换
enableReflectStatic: false
# INVOKESPECIAL 转换
enableReflectSpecial: false
# INVOKEINTERFACE 转换
enableReflectInterface: false

```
## test
如何测试你混淆后的单个 `class` 可用?

- 结合具体场景和项目测试,取决于实际情况
- 覆盖到 `jar` 文件中测试,比较麻烦
- 放到对应目录中使用 `java` 命令测试,更麻烦
- 使用自定义 `ClassLoader` 测试,方便快速

```java
public class Test extends ClassLoader {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException(name);
}
return defineClass(name, classData, 0, classData.length);
}
private byte[] getClassData(String className) {
if ("test.ClassName".equals(className)) {
try {
// read bytes form obfuscated class
return Files.readAllBytes(Paths.get("Test_obf.class"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return null;
}
public static void main(String[] args) throws Exception {
TestRunner loader = new TestRunner();
Class<?> clazz = loader.loadClass("test.ClassName");
Object instance = clazz.getDeclaredConstructor().newInstance();
// usually main method
Method method = clazz.getMethod("main", String[].class);
method.invoke(instance, new Object[]{args});
}
}
```

## Thanks
Expand Down
Binary file added img/008.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
205 changes: 205 additions & 0 deletions src/main/java/me/n1ar4/clazz/obfuscator/asm/ReflectClassVisitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package me.n1ar4.clazz.obfuscator.asm;

import me.n1ar4.clazz.obfuscator.Const;
import me.n1ar4.clazz.obfuscator.core.ObfEnv;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;

public class ReflectClassVisitor extends ClassVisitor {
public ReflectClassVisitor(ClassVisitor classVisitor) {
super(Const.ASMVersion, classVisitor);
}

@Override
public void visitSource(String source, String debug) {
}

public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new MethodVisitor(Const.ASMVersion, mv) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
if (opcode == Opcodes.INVOKESPECIAL) {
if (!ObfEnv.config.isEnableReflectSpecial()) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
return;
}
}
if (opcode == Opcodes.INVOKEVIRTUAL) {
if (!ObfEnv.config.isEnableReflectVirtual()) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
return;
}
}
if (opcode == Opcodes.INVOKESTATIC) {
if (!ObfEnv.config.isEnableReflectStatic()) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
return;
}
}
if (opcode == Opcodes.INVOKEINTERFACE) {
if (!ObfEnv.config.isEnableReflectInterface()) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
return;
}
}

if (opcode == Opcodes.INVOKEVIRTUAL ||
opcode == Opcodes.INVOKESPECIAL ||
opcode == Opcodes.INVOKESTATIC ||
opcode == Opcodes.INVOKEINTERFACE) {
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
int numParams = argumentTypes.length;

mv.visitIntInsn(Opcodes.BIPUSH, numParams);
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");

int localVarIndex = (opcode == Opcodes.INVOKESTATIC) ? 0 : 1;
for (int i = 0; i < numParams; i++) {
mv.visitInsn(Opcodes.DUP);
mv.visitIntInsn(Opcodes.BIPUSH, i);

Type argType = argumentTypes[i];
switch (argType.getSort()) {
case Type.BOOLEAN:
mv.visitVarInsn(Opcodes.ILOAD, localVarIndex);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Boolean",
"valueOf", "(Z)Ljava/lang/Boolean;", false);
break;
case Type.CHAR:
mv.visitVarInsn(Opcodes.ILOAD, localVarIndex);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Character",
"valueOf", "(C)Ljava/lang/Character;", false);
break;
case Type.BYTE:
mv.visitVarInsn(Opcodes.ILOAD, localVarIndex);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Byte",
"valueOf", "(B)Ljava/lang/Byte;", false);
break;
case Type.SHORT:
mv.visitVarInsn(Opcodes.ILOAD, localVarIndex);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Short",
"valueOf", "(S)Ljava/lang/Short;", false);
break;
case Type.INT:
mv.visitVarInsn(Opcodes.ILOAD, localVarIndex);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Integer",
"valueOf", "(I)Ljava/lang/Integer;", false);
break;
case Type.FLOAT:
mv.visitVarInsn(Opcodes.FLOAD, localVarIndex);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Float",
"valueOf", "(F)Ljava/lang/Float;", false);
break;
case Type.LONG:
mv.visitVarInsn(Opcodes.LLOAD, localVarIndex);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Long",
"valueOf", "(J)Ljava/lang/Long;", false);
localVarIndex++;
break;
case Type.DOUBLE:
mv.visitVarInsn(Opcodes.DLOAD, localVarIndex);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Double",
"valueOf", "(D)Ljava/lang/Double;", false);
localVarIndex++;
break;
default:
mv.visitVarInsn(Opcodes.ALOAD, localVarIndex);
}

mv.visitInsn(Opcodes.AASTORE);
localVarIndex++;
}

String internalNameToClassName = owner.replace('/', '.');
mv.visitLdcInsn(internalNameToClassName);
mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/Class", "forName",
"(Ljava/lang/String;)Ljava/lang/Class;", false);
mv.visitLdcInsn(name);

mv.visitIntInsn(Opcodes.BIPUSH, argumentTypes.length);
mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Class");
for (int i = 0; i < argumentTypes.length; i++) {
mv.visitInsn(Opcodes.DUP);
mv.visitIntInsn(Opcodes.BIPUSH, i);
mv.visitLdcInsn(Type.getType(argumentTypes[i].getDescriptor()));
mv.visitInsn(Opcodes.AASTORE);
}

mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getMethod",
"(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;", false);

if (opcode == Opcodes.INVOKESTATIC) {
mv.visitInsn(Opcodes.ACONST_NULL);
} else {
mv.visitVarInsn(Opcodes.ALOAD, 0);
}

mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/reflect/Method", "invoke",
"(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false);

Type returnType = Type.getReturnType(descriptor);
if (returnType.getSort() != Type.VOID) {
handleReturnType(returnType);
} else {
mv.visitInsn(Opcodes.POP);
}

return;
}
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}

private void handleReturnType(Type returnType) {
String wrapperType;
String unwrapMethod;

switch (returnType.getSort()) {
case Type.BOOLEAN:
wrapperType = "java/lang/Boolean";
unwrapMethod = "booleanValue";
break;
case Type.CHAR:
wrapperType = "java/lang/Character";
unwrapMethod = "charValue";
break;
case Type.BYTE:
wrapperType = "java/lang/Byte";
unwrapMethod = "byteValue";
break;
case Type.SHORT:
wrapperType = "java/lang/Short";
unwrapMethod = "shortValue";
break;
case Type.INT:
wrapperType = "java/lang/Integer";
unwrapMethod = "intValue";
break;
case Type.FLOAT:
wrapperType = "java/lang/Float";
unwrapMethod = "floatValue";
break;
case Type.LONG:
wrapperType = "java/lang/Long";
unwrapMethod = "longValue";
break;
case Type.DOUBLE:
wrapperType = "java/lang/Double";
unwrapMethod = "doubleValue";
break;
default:
if (returnType.getSort() == Type.OBJECT || returnType.getSort() == Type.ARRAY) {
mv.visitTypeInsn(Opcodes.CHECKCAST, returnType.getInternalName());
}
return;
}

mv.visitTypeInsn(Opcodes.CHECKCAST, wrapperType);
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, wrapperType, unwrapMethod,
"()" + returnType.getDescriptor(), false);
}
};
}
}
Loading

0 comments on commit f2733ed

Please sign in to comment.