混淆java程序的一些问题

本文地址:http://www.cnblogs.com/herbix/p/3545078.html 

因为java字节码的形式很简单,不像机器码指令集那么丰富,所以可优化的余地较小。尽管如此,我还是尝试使用了java混淆工具proguard来优化我的一个小程序。大致达到了以下的目的:

  1. 除了main函数所在的类以外,所有的类和大部分的函数名都被换成了a,b,c,d这种。
  2. 被使用一次的函数都被内联了。
  3. 程序从300KB下降到了180KB,当然包括去除了一些没有被用到的类。
  4. 其他的一些优化,比如接口的处理,类继承的处理等等。
  5. 程序的运行结果没有任何变化。

看到这里,你可能会想,这不是挺好的吗,还有什么可说的。上面的目标不是天生就可以达到的,有些需要费一些工夫,对于某些程序,可能上述目标根本无法达成。

阻碍目标达成的罪魁祸首就是java的反射机制,任何一个用了反射的java程序在混淆的过程中都要费些工夫来配置。因为proguard是根据函数和类之间的依赖来进行优化的,而反射的这种依赖不一定体现在代码里,比如:

1 Map<String, Class<? extends Packet>> map = ...;
2 
3 Class<? extends Packet> clazz = map.get("qksb");
4 Packet p = clazz.newInstance();

这段代码隐含了对Packet的所有子类的无参数构造函数的依赖,如果某个Packet的子类在无参数构造函数之外还有其他的构造函数,并且显式地被其他类依赖(比如new PacketA(0)),那么proguard就会把无参数构造函数去掉,调用newInstance的时候就会出现异常。想要避免这种事情,只能配置proguard中不对Packet的所有子类的无参数构造函数进行操作。

除了反射,其他任何依赖静态结构的程序段都可能在混淆中出现问题。比如使用ASM库动态构造类,并且继承某个接口,对接口名和函数名这种静态数据的依赖就会导致混淆出现问题。接口名可以用以下方式解决,而函数名就只能配置proguard来取消混淆了。

1 ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
2 
3 // 静态数据
4 cw.visit(V1_6, ACC_PUBLIC | ACC_FINAL, clazzName,
5     null, "java/lang/Object", new String[] { "test/NativeFilter" });
6 
7 // 动态数据
8 cw.visit(V1_6, ACC_PUBLIC | ACC_FINAL, clazzName,
9     null, "java/lang/Object", new String[] { NativeFilter.class.getName().replace('.', '/') });

总结一下,java代码的安全性不高,反编译相当容易。用了混淆工具的话,可以在功能不变的情况下使反编译变难,也可以略微提升运行效率。不过使用时需要注意以上问题,不然真是无法得到想要的结果。

原文地址:https://www.cnblogs.com/herbix/p/3545078.html