您的位置:首页 > 编程语言 > Java开发

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

2005-04-21 00:16 627 查看
如何封锁您的(或打开别人的)Java代码
Greg Travis (mito@panix.com) 日期:2002-12-25 10:07:21


Java代码反编译和模糊处理的完全指南
GregTravis(mito@panix.com)
自由程序员,纽约
2001年5月
无论是修改许多网上开放源代码库中的代码,还是调用常见的操作系统例行程序,您免不了要花一些时间去琢磨您没有编写过的代码,而且您还可能没有这些代码的源文件。在开始调试代码时,您需要有一个好的Java反编译器,并了解正确使用它的技术。同时,您还要知道如何保护您自己的代码不被窥视。为此,您还需了解有关代码模糊处理的问题。在这篇有关打开和封锁Java代码的初学者指南中,GregTravis使用Mocha、HoseMocha、jmangle和JODE等流行工具中的示例,来循序渐进地教你有关反汇编、反编译和Java代码模糊处理的基础知识。
没有比发现一个错误,却没有源代码就不能修改更令人沮丧的了。正是这个原因导致了Java反编译器的出现,它可以把编译后的字节码完全转回成源代码。尽管代码反编译器不只是针对Java语言,但它从来没有象在Java开发人员中那样被公开地或广泛地使用。
与反编译针锋相对的是模糊处理。假设反编译人员能很容易从编译后的代码中设法得到源代码,那么要保护您的代码和有价值的技术秘密就不是那么简单了。随着Java反编译器的普遍使用,Java模糊处理器也同样被普及,它的作用就好像放一块烟幕在您的代码前面。反编译和模糊处理在商业开发领域中引起了一场争论--争论中的大部分都集中在了Java语言上。
在本文中,我将让您了解代码反编译和模糊处理的具体过程,讨论在这两种技术之后的理论问题,同时简要地谈到它们在商业编程领域中所引起的争论。我还将介绍一些比较有名的反编译器和模糊处理器(有商业的,也有开放源代码的),并随着文章的深入使用它们来创建一些实例。
什么是反编译?
反编译是一个将目标代码转换成源代码的过程。这应该很清楚了,因为编译是一个将源代码转换成目标代码的过程。但什么是目标代码呢?大体上的定义是:目标代码是一种用语言表示的代码,这种语言能通过实机或虚拟机直接执行。对于象C这样的语言,目标代码通常运行在硬件CPU上,而Java目标代码通常运行在虚拟机上。
反编译是困难的
正如以上所描述的,反编译听上去比较简单,但它实际上是非常困难的--从本质上说,它所包含的是根据小规模、低层次的行为来推断大规模、高层次的行为。为了对此有个直观的理解,我们把一个计算机程序看作是一个复杂的公司组织结构。高层管理人员向他们的下属下达类似“最大程度地提高技术生产能力”的命令,下属们再把这些命令转变成更具体的行动,例如安装新的XML数据库。
作为该公司的新雇员,您可能会问下属他或她在做些什么,并得到回答,“我在安装新的XML数据库。”从这句话中,您不可能推断出其最终目的是最大程度地提高技术生产能力。毕竟,最终目标不尽相同,例如可能是分离供应链或累积消费者的数据。
然而,如果属于好奇心特强的那类人,您可能会再多问几个问题,并让公司中不同级别的下属回答您的问题。最后,当把所有的答案汇总后,您可能会猜到企业更大的目标是最大程度地提高技术生产能力。
如果您把计算机程序的工作方式看作类似一个公司的组织结构,那么对于为什么反编译代码不是无关紧要的,以上的这个比方就会给你一个直接的感受。从比较理论化的角度来看,这儿要引用在该领域的杰出研究员CristinaCifuentes对反编译过程的描述:

任何一个二进制改造工程都需要对存储在二进制文件中的代码进行反汇编。从理论上说,分离vonNeumann上的数据和代码就好象停机问题,因此完全的静态翻译是不可能的。然而,实际上可以使用不同技术来提高可被静态翻译的代码的所占比例,或者采取可在运行中被使用的动态翻译技术。
--"BinaryReengineeringofDistributedObjectTechnology"(请参阅参考资料)
把目标代码转换成源代码并不是反编译时碰到的唯一问题。一个Java类文件潜在包含了一些不同类型的信息。知道类文件中可能包含了哪类信息对于了解您如何利用该信息以及对于信息作何种处理都是很重要的。这其实就是Java反汇编器所要做的。
反汇编一个类文件
Java类文件的真正二进制格式不是很重要。重要的是知道在那些字节中包含了哪些不同种类的信息。到了这一步,我们将利用多数JDK都带有的一个工具--javap。javap是一个Java代码反汇编器,它和反编译器是不同的。反汇编器把机器可读格式的目标代码(如清单1所示)转换***们可读的代码(如清单2所示)。
清单1.一个类文件的原始内容
0000000fecabeba03002d0042000008081f3400
00000200008073f2c0000070735360000070737
0000040380000070a3904001500000a00070a15
000006008001600000a00080a17080018000009
...

清单2.javap的输出结果
Localvariablesformethodvoidpriv(int)
Foothispc=0,length=35,slot=0
intargumentpc=0,length=35,slot=1
Methodvoidmain(java.lang.String[])
0new#4
3invokespecial#10
6return

请注意,清单2所示的并不是源代码。该清单的第一部分列出了方法的局部变量;第二部分是汇编代码,它也是人们可读的目标代码。
一个类文件中的元素
javap被用来反汇编或解包一个类文件。这里简要列出了可以通过使用javap进行反汇编的Java类文件所包含的信息:
成员变量。每个类文件中包含了对应于该类每个数据成员的所有名称信息和类型信息。

经过反汇编后的方法。类的每一个方法都是由一串虚拟机指令来表示的,并附带它的类型签名。

行号。每个方法中的每个节被映射到源代码行,在可能的情况下,源代码行来生成节。这使得实时系统和调试器能够为在运行状态的程序提供堆栈跟踪。

局部变量名一旦方法被编译了,这个方法的局部变量就不太需要名称了,但是能通过对javac编译器使用-g选项来包含它们。这也使得实时系统和调试器能帮助您。
既然对Java类文件的内部情况已有所了解,让我们看一下如何能转换这些信息来达到我们的目的。
使用反编译器
从概念上讲,反编译器使用起来非常简单。他就是把编译器逆过来用:你给它.class文件,它还给你一个源代码文件。
一些比较新的反编译器有精致的图形界面。但在一开始所举的例子中,我们将使用的是Mocha,它是第一个公开的可利用的反编译器。在本文的最后,我会讨论一下在GPL下一个较新的反编译器。(请参阅参考资料,下载Mocha并获取Java反编译器的清单。)
让我们假设在目录中有一个名为Foo.class的类文件。用Mocha对它进行反编译非常简单,只要键入以下命令:

$javamocha.DecompilerFoo.class

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

以下是Mocha生成的代码
清单4.Mocha生成的Foo.java的源代码
privateintmember;
publicFoo()
{
member=10;
intlocal=returnInteger();
System.out.println("fooconstructor");
priv(local);
}

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

撇开Mocha的问题不谈,反编译器在通常情况下还是能比较准确地翻译出源代码。一旦知道了某一反编译器的弱点,您可以手工分析和转换反编译后的代码,以使它们能较准确地符合原始源代码。随着反编译器正变得越来越出色,我们又碰到了另外一个问题:如果您不想让任何人能反编译您的代码,那该怎么办呢?
反编译和对安全的威胁
虽然,大部分的代码反编译是完全正大光明的,但事实是一个优秀的反汇编器是软件侵权的必需工具之一。正因如此,尤其对于在商业和不开放源代码领域中的开发人员来说,便宜的(或免费的)Java代码反汇编工具的存在是一个严重的问题。
就语言本身而言,由于其相对简单的Java虚拟机(与真实的微处理器相比)和其写得很规范的字节码格式,Java代码非常容易反汇编。而这随着Java语言在Web开发平台上的日益普及,已经在商业开发领域引起了很多争议。自从Mocha于1996年首次发布以来,一些在保护它们的源代码方面有过投资的公司和个人一直在为Java反编译器大吵大闹。
实际上,当Mocha第一次发布时,它的作者HanpetervanVliet曾被一些公司的诉讼威胁过(请参阅参考资料)。起初,他把反编译器从他的网站上移去,但是他后来以Crema的形式提供了一个更好的解决方案。Crema是一个Java模糊处理器,它完全对立于Mocha。
自Crema发布以来,许多Java模糊处理器开始出现,其中一些是商业的,也有一些是开放源代码的。正如您看到的那样,一个好的Java模糊处理器可以在很大程度上保护您的Java代码。
针锋相对的代码模糊处理
代码模糊处理字面上的意思就是模糊处理您代码的行为。Java模糊处理器用不易察觉的方法改变程序,以致于它的运行对JVM来说是一模一样的,但它使得试图理解程序的人更加迷惑了。
让我们看一下当反汇编器遇到经过模糊处理后的代码会发生什么情况。清单6显示了Mocha在尝试反汇编被一种名为jmangle的工具模糊处理的Java代码后的结果。请注意以下的一小段程序和我们在前面清单中使用的是相同的,尽管乍一看,您肯定不会这么认为。
清单6.经过jmangle模糊处理的代码
publicFoo()
{
jm2=10;
inti=jm0();
System.out.println("fooconstructor");
jm1(i);
}

象jmangle这样的模糊处理器把许多变量名和方法名(有时甚至是类名和包的名称)转换成没有意义的字符串。这样就使得人们难以阅读程序,但对于JVM来说,其在本质上和原来的程序是一样的。
变得卑鄙
所有的模糊处理器都要使标记变得没有意义,但他们所做的不仅仅是这些。Crema之所以臭名昭著是因为它用了许多卑鄙的手段来阻止反汇编,并且有许多在已经出现的模糊处理器中,纷纷仿效它。
一种常用的模糊处理代码的方法是用一个非法的字符串来替代类文件中的标记,这比使用没有意义的字符串更进了一步。替代的有可能是一个关键字,例如private,或者甚至是象***这样没有意义的标记。一些虚拟机--尤其在浏览器中--对这些古怪的用法不会作出合法的反应。从技术上说,一个象=这样的变量与Java的规范是相反的;一些虚拟机可以忽略它,而另一些不可以这样。
Crema放置***
按字面意思,Crema使用的另一个计策就是***。Crema具有完全关闭Mocha的能力。它在编译后的代码中添加一个小“***”,导致Mocha在试图反编译代码时崩溃。
可惜,Crema已经没有了,但有一种名为HoseMocha的工具是专门为关闭Mocha而设计的。为了了解HoseMocha是如何工作的,我们将使用javap,这个值得信赖的反汇编器。清单7所示的是HoseMocha放置***前的代码。
清单7.放置***前的代码
Methodvoidmain(java.lang.String[])
0new#4
3invokespecial#10
6return

以下是HoseMocha处理后的代码。
清单8.放置***后的代码
Methodvoidmain(java.lang.String[])
0new#4
3invokespecial#10
6return
7pop

您看到那颗***吗?请注意现在这个程序在返回后面有一条pop语句。等一下--一个函数在返回之后还能做什么吗?很显然,它不能,而这就是关键所在。在返回语句后放一条指令确保了它不会被执行。您这儿所见的是根本不可能被反汇编的。因为它没有对应任何可能的Java源代码,所以也就没有任何意义。
但为什么这一个小小的障碍就能导致Mocha崩溃呢?Mocha可以只是简单地忽略它,或发一条警告信息并继续下去。尽管Mocha对于此类***的脆弱性可以被认为是一个程序错误,但更有可能的是vanVliet为了回应对Mocha的攻击而故意设置的。
到此为止,我们已经了解了较老的反汇编工具和模糊处理工具--虽然有点过时,但还是比较出色的。但是,类似工具在这几年已经变得更加成熟,尤其在图形界面方面更是如此。在本文的最后,我们看一下一个较新的反汇编器,仅仅让您有个大致的概念。
这一领域的新成员
在过去的五年中,不仅仅是反汇编和模糊处理的技术越来越复杂,而且这些工具的界面也更加华丽。在最近出现的反汇编器中,有几个能让您浏览.class文件的目录并且只要单击一下,就能对它们进行反汇编。
JODE(Java优化和反编译环境)就是这样一个程序。在命令行中键入.jar文件的名称,JODE就会允许您图形化地浏览它的类,并自动反汇编每个类以让您查看。这特别有助于通过JavaSDK提供的库来查找源代码。简单地键入以下命令:

$javajode.swingui.Main--classpath[pathtoyourJavaSDK]/jre/lib/rt.jar

您就会得到如图1所示的对文件的完整翻译。
图1.JODE:一种反汇编器

结论
无论选择使用象Mocha或HoseMocha这样的经典工具,还是乐于亲自研究一下更新的工具,您都应把这篇文章作为您学习Java反汇编和模糊处理的起点。在此,请浏览一下在参考资料中所提供的许多链接,试着使用其中的一些工具,并准备以后不断磨练自己的技术。尽管有许多争议,反汇编和模糊处理的技术如今依然存在,并且在今后的几年中只会变得更加成熟和完善。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: