为什么proguard不会混淆方法体?

时间:2015-07-20 02:57:56

标签: java variables jvm proguard obfuscation

我正在使用ProGuard来混淆我的.jar程序。一切正常,但ProGuard不会在方法体中混淆局部变量。这是一个例子:

原材料:

enter image description here

混淆:

enter image description here

以黄色突出显示的变量名称应该被混淆,但它们不是。我怎样才能对它们进行模糊处理(将它们重命名为 a,b,c等。?)

这是我的ProGuard配置:http://pastebin.com/sb3DMRcC(上述方法不是来自其中一个排除的类)。

1 个答案:

答案 0 :(得分:12)

  

为什么proguard不会混淆方法体?

因为它不能 编译时根本不存储方法参数和局部变量的名称 您正在查看的名称由您的反编译器生成。

对于已编译的代码,有两种方法可以在本地存储数据(即在方法中):

  • 在操作数堆栈上
  • 在局部变量中

操作数堆栈实际上只是一个堆栈 有关堆栈运算符,请参阅Java VM规范中的Table 7.2 您可以弹出值(pop),复制最高值(dup),交换前两个值(swap)以及略微改变的行为(pop2dup_x1dup_x2dup2dup2_x1dup2_x2)。
大多数情况下,如果不是所有产生返回值的指令都会将所述值丢弃到堆栈中。

这个问题的重要之处在于如何引用堆栈上的内容,这与任何其他堆栈类似:
相对于顶部位置,并根据使用的指令 没有指定的号码或名称,它只是目前的任何内容。

现在,对于所谓的"局部变量":

将它们视为ArrayList而不是Java中的变量 因为这正是您访问它们的方式:按索引 对于变量0到3,有特殊指令(即单字节),因为它们经常使用,所有其他变量只能通过双字节指令访问,其中第二个字节是索引。
再次见Table 7.2,"加载"和"商店"。
两个表中的前五个条目是每种数据类型的宽(双字节)存储/加载指令(请注意,对于单个值,booleancharbyte和{ {1}}全部转换为short,仅将intintfloat作为单一广告位值,Objectlong作为双时隙1),接下来的20条指令是直接访问寄存器0到3的指令,最后8条指令用于访问数组索引(注意内部数组doublebooleanbytechar 转换为short,不浪费空间,这就是为什么还有三个指令(不是四个,因为{{1} }和int具有相同的大小))。

最大堆栈大小和局部变量数都是有限的,必须在每个方法的byte属性的标题中给出,如Section 4.7.3中所定义(charCode)。

关于局部变量的有趣之处在于它们是方法参数的两倍,这意味着局部变量的数量永远不会低于方法参数的数量。
请注意,在计算Java VM的值时,max_stackmax_locals类型的变量将被视为两个值,并且需要两个"插槽"因此,
另请注意,对于非静态方法,参数0将为long,这需要另一个" slot"为了自己。

话虽如此,让我们看看一些代码!

示例:

double

此处我们有三个局部变量thisclass Test { public static void main(String[] myArgs) throws NumberFormatException { String myString = "42"; int myInt = Integer.parseInt(myString); double myDouble = (double)myInt * 42.0d; System.out.println(myDouble); } } myString,以及一个参数myInt
另外,我们有两个常量myDoublemyArgs,还有很多外部引用:

  • "42" - 班级
  • 42.0d - 班级
  • java.lang.String[] - 班级
  • java.lang.NumberFormatException - 方法
  • java.lang.String - 字段
  • java.lang.Integer.parseInt - 方法

还有一些导出:java.lang.System.outjava.io.PrintStream.println,加上编译器为我们生成的默认构造函数。

所有常量,引用和导出都将导出到Constant Pool - 局部变量和参数名称不会。

编译和反汇编类(使用Test)会产生:

main

除了默认构造函数之外,我们还可以逐步查看javap -c Test方法 请注意Compiled from "Test.java" class Test { Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]) throws java.lang.NumberFormatException; Code: 0: ldc #2 // String 42 2: astore_1 3: aload_1 4: invokestatic #3 // Method java/lang/Integer.parseInt:(Ljava/lang/String;)I 7: istore_2 8: iload_2 9: i2d 10: ldc2_w #4 // double 42.0d 13: dmul 14: dstore_3 15: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream; 18: dload_3 19: invokevirtual #7 // Method java/io/PrintStream.println:(D)V 22: return } mainmyStringastore_1aload_1以及myInt istore_2访问iload_2的方式}和myDouble
dstore_3无法在任何地方访问,因此也没有字节码处理它,但在方法的开头,对String数组的引用将在本地变量1中,很快就会到来被dload_3的引用覆盖。

如果你传递myArgs标志,

"42"也会显示常量池,但它并没有真正为输出添加任何值,因为来自常量池的所有相关信息无论如何都会显示在评论中。

但现在,让我们看一下反编译器会产生什么!

JD-GUI 0.3.5(JD-Core 0.6.2):

javap

Procyon 0.5.28:

-v

请注意导出到常量池的所有内容是如何保留的,而JD-GUI只是为局部变量选择一些名称,而Procyon则完全优化它们。
参数的名称 - import java.io.PrintStream; class Test { public static void main(String[] paramArrayOfString) throws NumberFormatException { String str = "42"; int i = Integer.parseInt(str); double d = i * 42.0D; System.out.println(d); } } vs class Test { public static void main(final String[] array) throws NumberFormatException { System.out.println(Integer.parseInt("42") * 42.0); } } (与原paramArrayOfString相对) - 是一个完美的例子,表明没有&#34;正确&#34;再一次命名,反编译器只需要依赖一些选择名称的模式。

我不知道&#34; true&#34;您的反编译代码中的名称来自,但我相当确定它们不包含在jar文件中。
IDE的功能可能吗?