淺談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程序破解時所用到的一些方法,當然,還有很多
憑經驗才能找到的靈感,無法一一列舉,本文質在拋磚引玉,望高手能寫一些技術含量更高的文章供我們這些菜鳥學習。