首页
社区
课程
招聘
浅谈JAVA程序破解(原创)
发表于: 2005-12-20 09:55 31773

浅谈JAVA程序破解(原创)

2005-12-20 09:55
31773

浅谈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程序破解时所用到的一些方法,当然,还有很多
凭经验才能找到的灵感,无法一一列举,本文质在抛砖引玉,望高手能写一些技术含量更高的文章供我们这些菜鸟学习。


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 7
支持
分享
最新回复 (37)
雪    币: 332
活跃值: (479)
能力值: ( LV9,RANK:330 )
在线值:
发帖
回帖
粉丝
2
有空学习了
2005-12-20 09:59
0
雪    币: 442
活跃值: (1241)
能力值: ( LV12,RANK:1130 )
在线值:
发帖
回帖
粉丝
3
牛X,我以为将你的某行业软件放出来了
2005-12-20 10:19
0
雪    币: 257
活跃值: (105)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
4
嘿嘿……还是别给自己找麻烦才是明智的选择:)
2005-12-20 10:22
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
学习中,
2005-12-20 11:36
0
雪    币: 440
活跃值: (827)
能力值: ( LV9,RANK:690 )
在线值:
发帖
回帖
粉丝
6
学习
2005-12-20 11:43
0
雪    币: 107
活跃值: (55)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
7
牛人太多了,要好好学习一下。
2005-12-20 14:49
0
雪    币: 214
活跃值: (60)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
最初由 baby2008 发布
牛X,我以为将你的某行业软件放出来了
偶就是说怎么这么像MYBB
2005-12-20 15:28
0
雪    币: 538
活跃值: (32)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
高手无处不在啊pfpf
2005-12-20 16:45
0
雪    币: 61
活跃值: (160)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
10
good.....
2005-12-20 16:56
0
雪    币: 216
活跃值: (70)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
11
学习了
2005-12-22 16:49
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
初学破解,好多看不明白,呵呵。。。。
2005-12-22 17:14
0
雪    币: 146
活跃值: (72)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
好长时间不见,原来闭门修练功啊,一来就弄了两个精华,真牛~~顶一下啦。
2005-12-22 20:01
0
雪    币: 215
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
1.Jad 有命令行参数,也可以指定后缀。

2.“这是我见过的最好的混淆效果,变量都是由大小写的O和数字零组程,要看懂这样的程序基本上是不可能的,可能有
人会想到用有意义的变量进行替换,当然这也是一个方法,但如果应用所包括的class文件数以千记,那这个工作量
是相当大的。”

再用一个其它的混淆器,可以批量操作的,就可以把这些变量改为其它的了。
最早使用这样的变量混淆方法的java 工具好像是jade, Sun 中国技术中心开发出来的,好几年前的出的,做的不怎么好,重点是在java loader上做文章,也早已停止继续开发了。

用过比较好的是proguard. http://proguard.sourceforge.net/

是2年以前用这些的,现在的相关进展就不清楚了。
2005-12-23 01:19
0
雪    币: 257
活跃值: (105)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
15
最初由 playx 发布
好长时间不见,原来闭门修练功啊,一来就弄了两个精华,真牛~~顶一下啦。


嘿嘿……过夸了,按照约定你昨天中午没出现,靠,我还等你呢!

    多谢wwwddd的指点,sun的jade我现在还在用,只是必需要jdk 1.3 以下的版本中用。proguard 没有用过,这就去看一下。
    顺便再请教一个问题,JAVA包名好像不能为纯数字,但我确见到了这样的包,要导入这样的类就会报错,有什么好办法吗?
2005-12-23 08:31
0
雪    币: 300
活跃值: (412)
能力值: ( LV9,RANK:410 )
在线值:
发帖
回帖
粉丝
16
请问“方法名称,插入位置,行号,内容”
这里的插入位置,行号如何确定?请问是指源代码种的位置吗?

另外,简单爆破中的
“得把相应位置的03 AC改为04 AC,位置怎么确定就不用我说了吧!”

这个位置如何确定呢?
2005-12-23 15:40
0
雪    币: 11705
活跃值: (975)
能力值: ( LV12,RANK:779 )
在线值:
发帖
回帖
粉丝
17
受益匪浅。
ps,楼主笔误,
BCEL字节码引擎(The Byte Code Engineering Library)不是becl.
2005-12-23 20:37
0
雪    币: 257
活跃值: (105)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
18
to:kill
   这里的插入位置即为用那种方法插入,可能这里的描述不准确。a 插入到方法代码的最后面,b 插入到方法代码的最前面,i 插入到指定行的前面。为i时行号才有意义,(但为a或b时也需要,程序中没有处理)这时会把代码插入到“行号”所指定的位置前,如果所给的行号小于等于方法第一句所在行的行号,那么代码会插入到方法的最前面,如果所给的行号大于等于方第最后一句所在行的行号,那么代码会插入到方法的最后面。为i会返回一个int型的值,代表实际插入的位置,用这个返回值确定你要插入的位置就很方便了。
    位置的确定有多种方法,比如把一个类中的某一方法的return false改为return true,你找一下方类中一共有几个retrun语句,再数一下你要改的return在第几位,那么相应字节码中的也就是第几位。当然可能有例外,但目前我还没有遇到过。

to:reeayu
    感谢纠正文中的错误,马上改正!
2005-12-24 08:47
0
雪    币: 300
活跃值: (412)
能力值: ( LV9,RANK:410 )
在线值:
发帖
回帖
粉丝
19
谢谢舵手,我学习试试~
2005-12-24 21:14
0
雪    币: 215
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
最初由 舵手 发布


顺便再请教一个问题,JAVA包名好像不能为纯数字,但我确见到了这样的包,要导入这样的类就会报错,有什么好办法吗?
........


我没有见过这样的,java package name 是数字的话,应该编译时候就不能通过吧。

可能是用混淆器处理后的结果,如果不能导入,那就再用一个混淆器,定制一下,把package name 批量全部改为其它了。
2005-12-26 23:48
0
雪    币: 257
活跃值: (105)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
21
最初由 wwwddd 发布



我没有见过这样的,java package name 是数字的话,应该编译时候就不能通过吧。

........


有什么好的混淆工具能达到你所说的效果,望能介绍一下,多谢!
2005-12-27 08:42
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
学习中!!!
2008-12-18 14:15
0
雪    币: 208
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
学习新领域呀,已经申请加入舵主的群,好好学习下
2009-4-28 14:06
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
值得学习   在看 谢谢
2009-4-29 17:45
0
雪    币: 122
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
有见解,很好,支持了!
2009-5-17 03:42
0
游客
登录 | 注册 方可回帖
返回
//