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