当前位置:文档之家› 浅谈Java程序破解

浅谈Java程序破解

最近对 Java 程序的破解比较感兴趣,拿几个行业软件练了一下手,略有心得,拿出来与菜鸟分享!注意只是点心得,本文并不涉及具体软件破解。初学破解失误之处在所难免,敬请高手赐教!

直接进入正题,对 Java 的破解可从下面几方面入手:

一、反编译

工具很多,建议用 GUI 工具,命令行下的 JAD 很容易因为不能反编译某一个方法或某一行代码而终止整个文件的反编译。但 GUI 的工具却能搞定,虽然反编译后部分代码较难看懂,但总比看 jvm 指命要好得多。而且,GUI 的工具多数有批量反编译功能,且能让反编译的文件直接以 .java 为后缀保存,也是方便之处。这里推荐使用 DJ Java Decompiler。

二、方法调用

安全意识强的开发者会把他的程序进行高质量的混淆,下面就是一个例子:

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 和数字 0 组成,要看懂这样的程序基本上是天方夜谭。可能有人会想到用有意义的变量进行替换,当然这也是一个方法。但如果应用所包括的 class 文件数以千记,那这个工作量是相当大的。B/S 结构的授权方式一般都是文件的形式,当然肯定是经过加密的。像下面的 License 就是经过了 RSA 非对称加密算法,要分析 License 的构成,有明文的 License 就更方便了,而公钥是直接被写在 class 文件中的:

24D568B6A27AEFD683BC7A1CC93D11D074FB6B982A4B6E26
9712773BE536B40A67F1D345654F659C66D4265F5CE8FE0494B3A

F33A8299A4F6B0E7500275A27EFF3B6D2E4983F14A9EA38A1
AE3394B28A9C6D6924C15027F9B689FD9A3A689A301C4D4EB878

D75C207F68BAA352F550D8F19876FFA255864FDE8A7E5939202E9F

那么我们可以用 eclipse 建一个 Java 项目,把应用的 jar 加入该项目的库搜索路径,写一个自己的类调用解密方法,得到明文 License 再分析。当然,也可以调用其它一些方法,从调用参数和最后的返回值,我们也可大概猜对该方法的作用,对付像上面经过高质量混淆的代码也比较管用。当然,我这里只是简单的举两个例子,其实“方法调用”的妙用还很多,自己慢慢琢磨吧!

三、为 class 添加代码


反编译多数情况下也只能让我们看看作者的思路,如果想把反编译出来的代码经过修改后再编译成 class,通常是行不通了。而且有时候必须让程序运行在它本身的环境才能行,否则一些类无法得到正确的初始化,“方法调用”也就起不了什么作用。搞过 Java 的人一定知道 javassist,这个库提供了足够多的方法让你直接修改 class 文件,而不需要你了解字节码的相关知识,我们可以利用这个库解决上述的问题。下面是我写的一个修改字节码的类,目前还不完善,真正要用时可能需要根据情况做一些修改。

import https://www.doczj.com/doc/666803207.html,ng.reflect.*;
import javassist.*;
import java.io.*;
/**
*

Title: JAVA 字节码修改类


*

Description: 得到类的相关信息或修改该类


*

Copyright: Copyright () 2005


* @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("https://www.doczj.com/doc/666803207.html,ng.String");
//param[3] = pool.get("https://www.doczj.com/doc/666803207.html,ng.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(newMet

hodBody.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
{
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
{
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 明文,自己再生成一对 R

SA 的公私密钥,用自己的私钥对 License 文明进行 RSA 加密,再用十六进制编辑器替换程序中所有的公钥(当然是用你的公钥替换他的公钥,不然也没法解密),一切就搞定。当然,我所说的只是一个方面,有时暴破时可能还得用到一些 JVM 的指令,比如你想让一个 return false 的方法 return ture,那你就得把相应位置的 03 AC 改为 04 AC,位置怎么确定就不用我说了吧!

五、读 JVM 指令

没有什么可以多说的,如果要从 JVM 指令看懂成程,必须像熟汇编一样熟悉 JVM 指令集,还得有一个工具把 class 翻译成 JVM 指令代码,总不能用十六进制编辑器读代码吧。如果真是,那你就太牛了。我这里介绍 BCEL 这个工具(IDA似乎也有这种功能),它可以把 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程序破解时所用到的一些方法,当然,还有很多,凭经验才能找到的灵感无法一一列举。本文质在抛砖引玉,望高手能写一些技术含量更高的文章供我们这些菜鸟学习。

相关主题
文本预览
相关文档 最新文档