混淆java程序的一些问题
本文地址:http://www.cnblogs.com/herbix/p/3545078.html
因为java字节码的形式很简单,不像机器码指令集那么丰富,所以可优化的余地较小。尽管如此,我还是尝试使用了java混淆工具proguard来优化我的一个小程序。大致达到了以下的目的:
- 除了main函数所在的类以外,所有的类和大部分的函数名都被换成了a,b,c,d这种。
- 被使用一次的函数都被内联了。
- 程序从300KB下降到了180KB,当然包括去除了一些没有被用到的类。
- 其他的一些优化,比如接口的处理,类继承的处理等等。
- 程序的运行结果没有任何变化。
看到这里,你可能会想,这不是挺好的吗,还有什么可说的。上面的目标不是天生就可以达到的,有些需要费一些工夫,对于某些程序,可能上述目标根本无法达成。
阻碍目标达成的罪魁祸首就是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代码的安全性不高,反编译相当容易。用了混淆工具的话,可以在功能不变的情况下使反编译变难,也可以略微提升运行效率。不过使用时需要注意以上问题,不然真是无法得到想要的结果。