浅谈JAVA程序破解
作者:舵手
申明:如转载请保证文章的完整性以及出处
最近对JAVA程序的破解比较感兴趣,拿几个行业软件练了一下手,略有心得,拿出来与菜鸟分享!注意只是一点心得,
本文并不涉及具体软件的破解。初学破解,失误之处在所难免,敬请高手赐教!
直接进入正题,对JAVA的破可从下面几方面入手:
一、反编译
工具很多,建意用GUI工具,命令行下的JAD很容易因为不能反编译某一个方法或某一行代码而终止整个文件的反
编译,但GUI的工具却能搞定,虽然反编译后部分代码较难看懂,但总比看jvm指命要好得多。而且,GUI的工具多数有
批量反编译功能,且能让反编译的文件直接以.java为后缀保存,也是方便之处。
二、方法调用
安全意识强的开发者会把他的程序进行高质量的混淆,下面就是一个例子
public static Object getRemoteEJBHome(String OOOoOo00oO0O0O0ooOoOO, Class OO0oOO0O0o0oO0o00oOoO)
throws NamingException
{
try
{
if(OoO0o0o0O0oo0oO00oOO0 == null)
OoO0o0o0O0oo0oO00oOO0 = OoOOoOOO0Oo0OO0OooO0o();
Object OOOOOo00000OoOoO0O000 = PortableRemoteObject.narrow(OoO0o0o0O0oo0oO00oOO0.lookup(OOOoOo00oO0O0O0ooOoOO), OO0oOO0O0o0oO0o00oOoO);
Object obj = OOOOOo00000OoOoO0O000;
return obj;
}
catch(NamingException OO0Ooo0oOO0OO0OOOoOo0)
{
System.out.println(OO0Ooo0oOO0OO0OOOoOo0.getMessage());
throw OO0Ooo0oOO0OO0OOOoOo0;
}
}
这是我见过的最好的混淆效果,变量都是由大小写的O和数字零组程,要看懂这样的程序基本上是不可能的,可能有
人会想到用有意义的变量进行替换,当然这也是一个方法,但如果应用所包括的class文件数以千记,那这个工作量
是相当大的。B/S结构的授权方式一般都是文件的形式,当然,肯定是经过加密的。像下面的license就是经过了RSA
非对称加密算法,要分析license的构成,有明文的license就更方便了,而公钥是直接被写在class文件中的
24D568B6A27AEFD683BC7A1CC93D11D074FB6B982A4B6E269712773BE536B40A67F1D345654F659C66D4265F5CE8FE0494B3A
F33A8299A4F6B0E7500275A27EFF3B6D2E4983F14A9EA38A1AE3394B28A9C6D6924C15027F9B689FD9A3A689A301C4D4EB878
D75C207F68BAA352F550D8F19876FFA255864FDE8A7E5939202E9F
那么我们可以用eclipse建一个JAVA项目,把应用的jar加入该项目的库搜索路径,写一个自己的类调用解密方法,得到
明文license再分析。当然,也可以调用其它一些方法,从调用参数和最后的返回值我们也可大概猜对该方法的作用,
对付象上面经过高质量混淆的代码也比较管用。当然,我这里只是简单的举两个例子,其实“方法调用”的妙用还很多,
自己慢慢琢磨吧!
三、为class添加代码
反编译多数情况下也只能让我们看看作者的思路,如果想把反编译出来的代码经过修改后再编译成class,通常
是行不通了。而且有时候必须让程序运行在它本身的环境才能行,否则一些类无法得到正确的初始化,“方法调用”
也就起不了什么作用。搞过java的人一定知道javassist,这个库提供了足够多的方法让你直接修改class文件,而不
需要你了解字节码的相关知识,我们可以利用这个库解决上述的问题。下面是我写的一个修改字节码的类,目前还不
完善,真正要用时可能需要根据情况做一些修改。
import java.lang.reflect.*;
import javassist.*;
import java.io.*;
/**
* <p>Title: JAVA 字节码修改类</p>
* <p>Description: 得到类的相关信息或修改该类</p>
* <p>Copyright: Copyright () 2005</p>
* @author 舵手
* @version 1.0
*/
public class ModifyClass {
private static int call_method;
private static String _class;
private static ClassPool pool;
private static CtClass cc;
private static String[] clas;
/**
* 修改字节码中的方法
* @param clas[0] 待修改类的方法名
* @param clas[1] 修改位置定义
* @param clas[2] 使用insertAt方法插放代码时行号参数
* @param clas[3] 修改内容
* @return
*/
private static void modifyMethod()
{
String _method;
_method = clas[0];
try
{
CtClass[] param = new CtClass[4] ;
//param[0] = pool.get("");
//param[1] = pool.get("");
//param[2] = pool.get("java.lang.String");
//param[3] = pool.get("java.lang.String");
CtMethod cm = cc.getDeclaredMethod(_method);
if (clas[1].toLowerCase().equals("a"))
{
//方法的尾部加入代码
cm.insertAfter(clas[3]);
}
if (clas[1].toLowerCase().equals("b"))
{
//方法的首部加入代码
cm.insertBefore(clas[3]);
}
if (clas[1].toLowerCase().equals("i"))
{
System.out.println(cm.insertAt((Integer.valueOf(clas[2]).intValue()),clas[3]));
}
cc.writeFile();
}
catch(Exception e)
{
e.printStackTrace();
}
}
/**
* 在类中增加方法
* @param clas[0] 源方法名称
* @param clas[1] 新方法名称
* @param clas[2] 增加类型
* @param clas[3] 方法内容
* @return
*/
private static void addMethod()
{
String _oldmethod;
String _newmethod;
_oldmethod = clas[0];
_newmethod = clas[1];
try
{
StringBuffer newMethodBody = new StringBuffer();
if (clas[2].toLowerCase().equals("c"))
{
//add new Method (copy)
CtMethod oldMethod = cc.getDeclaredMethod(_oldmethod);
CtMethod newMethod = CtNewMethod.copy(oldMethod, _newmethod, cc, null);
newMethodBody.append(clas[3]);
newMethod.setBody(newMethodBody.toString());
cc.addMethod(newMethod);
}
if (clas[2].toLowerCase().equals("r"))
{
//add new Method (create)
CtMethod newMethod = CtNewMethod.make(clas[3], cc);
cc.addMethod(newMethod);
}
cc.writeFile();
}
catch(Exception e)
{
e.printStackTrace();
}
}
private static void getMethods(){
CtMethod[] cms = cc.getDeclaredMethods();
System.out.println();
System.out.println(cc.getName()+" 类的所有方法:");
for (int i=0 ; i<cms.length ; i++ )
{
System.out.println(cms[i].getName());
}
}
private static void getFields(){
CtField[] cfs = cc.getDeclaredFields();
System.out.println();
System.out.println(cc.getName()+" 类的所有属性:");
for (int i=0 ; i<cfs.length ; i++ )
{
System.out.println(cfs[i].getName());
}
}
private static void delMethod(){
try{
CtMethod cm = cc.getDeclaredMethod(clas[0]);
cc.removeMethod(cm);
}catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
StringBuffer buf = new StringBuffer(500);
int c;
System.out.print("请输入操作类名:");
try{
while ((c = System.in.read()) != 13) {
buf.append((char)c);
}
_class = buf.toString();
pool = ClassPool.getDefault();
cc = pool.get(_class);
buf.delete(0,buf.length());
System.out.println("***********************************************************");
System.out.println("可供调用的方法有:");
System.out.println("1-modifyMethod,2-addMethod,3-getMethods,4-getFields,5-removeMethod");
System.out.println("***********************************************************");
System.out.print("请选择调用方法:");
while ((c = System.in.read()) != 13) {
if (c == 10)
continue;
buf.append((char)c);
}
call_method = Integer.parseInt(buf.toString());
if (call_method == 1)
{
System.out.println("***********************************************************");
System.out.println("调用 modifyMethod 方法参数:");
System.out.println("方法名称,插入位置,行号,内容");
System.out.println("***********************************************************");
buf.delete(0,buf.length());
while ((c = System.in.read()) != 13) {
if (c == 10)
continue;
buf.append((char)c);
}
clas = (buf.toString()).split(",");
modifyMethod();
}
buf.delete(0,buf.length());
if (call_method == 2)
{
System.out.println("***********************************************************");
System.out.println("调用 addMethod 方法参数:");
System.out.println("源方法,目标方法,建立方式,内容");
System.out.println("***********************************************************");
buf.delete(0,buf.length());
while ((c = System.in.read()) != 13) {
if (c == 10)
continue;
buf.append((char)c);
}
clas = (buf.toString()).split(",");
addMethod();
}
if (call_method == 3)
{
getMethods();
}
if (call_method == 4)
{
getFields();
}
if (call_method == 5)
{
System.out.println("***********************************************************");
System.out.println("调用 removeMethod 方法参数:");
System.out.println("方法名称");
System.out.println("***********************************************************");
buf.delete(0,buf.length());
while ((c = System.in.read()) != 13) {
if (c == 10)
continue;
buf.append((char)c);
}
clas = (buf.toString()).split(",");
delMethod();
}
}catch(IOException ioe)
{
System.out.println();
ioe.printStackTrace();
System.exit(0);
}
catch(NotFoundException nfe)
{
System.out.println();
nfe.printStackTrace();
System.exit(0);
}
catch(NumberFormatException nfe)
{
System.out.println();
nfe.printStackTrace();
System.exit(0);
}
}
}
modifyMethod方法用来在类的指定方法中插入一行或多行代码,参数为a时表示插在方法现有代码的最后面,为b时
表示插在方法现有代码的最前面,为i时表时插在代码的指定行的前面,这个行和原代码中的行没有关系,插入位置
要插入一次才能确定,为i时返回的值代表实际插入位置,由这个实际插入位置你可以计算i的值。在实际破解中发现,
用该方法插入一些代码后,会使原来反编译的不可读的代码变的容易读懂,当然,也有可能使本来可读性很强的代码,
因为你插入了一些语句而变得不可读。我常常在关键方法的代码中插入一些 System.out.println();这样的代码来跟踪
程序,还有一点限制,你不能直接用打印输出的方法来输出方法体内的局部变量,但你可以对全局变量进行引用操作。
如果要操作局部变量,目前我所知的方法只能在该类里重建该方法,如果那位有其它的好办法,也请指点我一下。
addMethod方法在是类中增加一个新的方法,增加的方式有两种,这里就不做具体介绍。
其它方法也就不一一解释了,有兴趣的朋友可以研究一下javassist,相信你会写出功能更强大的修改class文件的类库。
四、class的修改
在破解过程中经常会看到rsa非对称加密算法,公钥往往以十六进制存放在class文件中,(当然,也有对公钥加密后存
放在配置文件中的程序)以便解密已经加密过的信息。前不久破解的一个J2EE的开发平台就是这样的,license用RSA加密,
在搞懂了它的算法后,自己构件license明文,自己再生成一对rsa的公私密钥,用自己的私钥对license文明进行RSA加密,
再用十六进制编辑器替换程序中所有的公钥,(当然是用你的公钥替换他的公钥,不然也没法解密)一切就搞定。当然,
我所说的只是一个方面,有时暴破时可能还得用到一些JVM的指命,比如你想让一个 return false 的方法 return ture
那你就得把相应位置的03 AC改为04 AC,位置怎么确定就不用我说了吧!
五、读JVM指令
没有什么可以多说的,如果要从jvm指令看懂成程,必须像熟汇编一样熟悉jvm指令集,还得有一个工具把class翻译成
jvm指令代码,总不能用十六进制编辑器读代码吗?如果真是,那你就太牛了。我这里介绍 bcel 这个工具,它可以把class
解释为jvm指令集并存为html文件,结果就像下面:
0 getstatic System.out Ljava/io/PrintStream;
3 ldc "is one"
5 invokevirtual java.io.PrintStream.println (Ljava/lang/String;)V(String):void
8 getstatic System.out Ljava/io/PrintStream;
11 ldc "is two"
13 invokevirtual java.io.PrintStream.println (Ljava/lang/String;)V(String):void
16 getstatic System.out Ljava/io/PrintStream;
19 ldc "is three"
21 invokevirtual java.io.PrintStream.println (Ljava/lang/String;)V(String):void
24 getstatic System.out Ljava/io/PrintStream;
27 ldc "is four"
29 invokevirtual java.io.PrintStream.println (Ljava/lang/String;)V(String):void
32 return
这是一个方法的全部指令,熟悉jvm指令集的话就已经能读懂它在做什么了
发现有关JAVA程序破解的文章不是很多,所以本人粗浅的谈论了一下JAVA程序破解时所用到的一些方法,当然,还有很多
凭经验才能找到的灵感,无法一一列举,本文质在抛砖引玉,望高手能写一些技术含量更高的文章供我们这些菜鸟学习。