首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

如何封锁您的(或打开别人的) Java 代码(2)

如何封锁您的(或打开别人的) Java 代码(2)

清单 2. javap 的输出结果

[size=0.875]1


[size=0.875]2


[size=0.875]3


[size=0.875]4


[size=0.875]5


[size=0.875]6


[size=0.875]7


[size=0.875][size=0.875]Local variables for method void priv(int)
[size=0.875]      Foo this  pc=0, length=35, slot=0
[size=0.875]      int argument  pc=0, length=35, slot=1
[size=0.875]Method void main(java.lang.String[])
[size=0.875]      0 new #4
[size=0.875]      3 invokespecial #10
[size=0.875]      6 return




[size=1.0625]请注意,清单 2 所示的并不是源代码。该清单的第一部分列出了方法的局部变量;第二部分是汇编代码,它也是人们可读的目标代码。
一个类文件中的元素[size=1.0625]javap 被用来反汇编或解包一个类文件。这里简要列出了可以通过使用 javap 进行反汇编的 Java 类文件所包含的信息:
  • 成员变量。每个类文件中包含了对应于该类每个数据成员的所有名称信息和类型信息。
  • 经过反汇编后的方法。类的每一个方法都是由一串虚拟机指令来表示的,并附带它的类型签名。
  • 行号。每个方法中的每个节被映射到源代码行,在可能的情况下,源代码行来生成节。这使得实时系统和调试器能够为在运行状态的程序提供堆栈跟踪。
  • 局部变量名一旦方法被编译了,这个方法的局部变量就不太需要名称了,但是能通过对 javac 编译器使用 -g 选项来包含它们。这也使得实时系统和调试器能帮助您。
[size=1.0625]既然对 Java 类文件的内部情况已有所了解,让我们看一下如何能转换这些信息来达到我们的目的。
使用反编译器[size=1.0625]从概念上讲,反编译器使用起来非常简单。他就是把编译器逆过来用:你给它 .class 文件,它还给你一个源代码文件。
[size=1.0625]一些比较新的反编译器有精致的图形界面。但在一开始所举的例子中,我们将使用的是 Mocha,它是第一个公开的可利用的反编译器。在本文的最后,我会讨论一下在 GPL 下一个较新的反编译器。(请参阅参考资料,下载 Mocha 并获取 Java 反编译器的清单。)
[size=1.0625]让我们假设在目录中有一个名为 Foo.class 的类文件。用 Mocha 对它进行反编译非常简单,只要键入以下命令:

[size=0.875]1


[size=0.875][size=0.875]$ java mocha.Decompiler Foo.class




[size=1.0625]这会生成一个新的名为 Foo.mocha 的文件(Mocha 使用 Foo.mocha 这个名字以避免覆盖原文件的源代码)。这个新文件就是 Java 的源文件,并且假设一切顺利的话,您现在就能正常地编译它。只需把它重命名为 Foo.java 就可以开始了。
[size=1.0625]但是这儿有个问题:如果在一些您已经有所改动的代码上运行 Mocha,您会注意到它生成的代码和源代码不是完全一样的。我举个例子,这样您能明白我的意思。清单 3 所示的原始源代码是来自一个名为 Foo.java 的测试程序。
清单 3. Foo.java 的一小部分原始源代码

[size=0.875]1


[size=0.875]2


[size=0.875]3


[size=0.875]4


[size=0.875]5


[size=0.875]6


[size=0.875][size=0.875]private int member = 10;
[size=0.875]public Foo() {
[size=0.875]  int local = returnInteger();
[size=0.875]  System.out.println( "foo constructor" );
[size=0.875]  priv( local );
[size=0.875]}




[size=1.0625]以下是 Mocha 生成的代码
清单 4. Mocha 生成的 Foo.java 的源代码

[size=0.875]1


[size=0.875]2


[size=0.875]3


[size=0.875]4


[size=0.875]5


[size=0.875]6


[size=0.875]7


[size=0.875]8


[size=0.875][size=0.875]private int member;
[size=0.875]public Foo()
[size=0.875]{
[size=0.875]    member = 10;
[size=0.875]    int local = returnInteger();
[size=0.875]    System.out.println("foo constructor");
[size=0.875]    priv(local);
[size=0.875]}




[size=1.0625]这两个代码片段的成员变量 member 被初始化为 10 的位置不同。在原始源代码中,它在与声明的同一行中被表示为一个初始值,而在被反编译后的源代码中,它在一个构造符中被表示为一条赋值语句。反编译后的代码告诉我们一些有关源代码被编译的方法;即它的初始值是作为在构造符中的赋值来被编译的。通过观察其反编译后的输出结果,您能了解到不少 Java 编译器的工作方法。
反编译是困难的:不断重复[size=1.0625]虽然 Mocha 的确可以反汇编您的目标代码,但它不会总是成功的。由于困难重重,没有一个反编译器能够准确无误地翻译出源代码,而且每个反编译器处理它们在翻译过程中的漏洞的方式也不同。举例来说,Mocha 有时在输出准确的循环构造的结构方面有一些问题。如果真的这样,它会在最终输出中使用伪 goto 语句,如清单 5 所示。
清单 5. Mocha 不能准确地反编译

[size=0.875]1


[size=0.875]2


[size=0.875]3


[size=0.875]4


[size=0.875]5


[size=0.875]6


[size=0.875]7


[size=0.875]8


[size=0.875]9


[size=0.875]10


[size=0.875][size=0.875]if (i1 == i3) goto 214 else 138;
[size=0.875]j3 = getSegment(i3).getZOrder();
[size=0.875]if (j1 != 1) goto 177 else 154;
[size=0.875]if (j3 > k2 && (!k1 || j3 < j2)) goto 203 else 173;
[size=0.875]expression 0
[size=0.875]if (j3 < k2 && (!k1 || j3 > j2)) goto 203 else 196;
[size=0.875]expression 0
[size=0.875]if == goto 201
[size=0.875]continue;
[size=0.875]i2 = i3;




[size=1.0625]撇开 Mocha 的问题不谈,反编译器在通常情况下还是能比较准确地翻译出源代码。一旦知道了某一反编译器的弱点,您可以手工分析和转换反编译后的代码,以使它们能较准确地符合原始源代码。随着反编译器正变得越来越出色,我们又碰到了另外一个问题:如果您不想让任何人能反编译您的代码,那该怎么办呢?
山不在高,有仙则名;水不在深,有龙则灵。
返回列表