淺談JAVA程序破解淺談JAVA程序破解作者:舵手 申明:如轉(zhuǎn)載請保證文章的完整性以及出處 最近對JAVA程序的破解比較感興趣,拿幾個行業(yè)軟件練了一下手,略有心得,拿出來與菜鳥分享!注意只是一點(diǎn)心得, 本文并不涉及具體軟件的破解。初學(xué)破解,失誤之處在所難免,敬請高手賜教! 直接進(jìn)入正題,對JAVA的破可從下面幾方面入手: 一、反編譯 工具很多,建意用GUI工具,命令行下的JAD很容易因?yàn)椴荒芊淳幾g某一個方法或某一行代碼而終止整個文件的反 編譯,但GUI的工具卻能搞定,雖然反編譯后部分代碼較難看懂,但總比看jvm指命要好得多。而且,GUI的工具多數(shù)有 批量反編譯功能,且能讓反編譯的文件直接以.java為后綴保存,也是方便之處。 二、方法調(diào)用 安全意識強(qiáng)的開發(fā)者會把他的程序進(jìn)行高質(zhì)量的混淆,下面就是一個例子 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和數(shù)字零組程,要看懂這樣的程序基本上是不可能的,可能有 人會想到用有意義的變量進(jìn)行替換,當(dāng)然這也是一個方法,但如果應(yīng)用所包括的class文件數(shù)以千記,那這個工作量 是相當(dāng)大的。B/S結(jié)構(gòu)的授權(quán)方式一般都是文件的形式,當(dāng)然,肯定是經(jīng)過加密的。像下面的license就是經(jīng)過了RSA 非對稱加密算法,要分析license的構(gòu)成,有明文的license就更方便了,而公鑰是直接被寫在class文件中的 24D568B6A27AEFD683BC7A1CC93D11D074FB6B982A4B6E269712773BE536B40A67F1D345654F659C66D4265F5CE8FE0494B3A F33A8299A4F6B0E7500275A27EFF3B6D2E4983F14A9EA38A1AE3394B28A9C6D6924C15027F9B689FD9A3A689A301C4D4EB878 D75C207F68BAA352F550D8F19876FFA255864FDE8A7E5939202E9F 那么我們可以用eclipse建一個JAVA項(xiàng)目,把應(yīng)用的jar加入該項(xiàng)目的庫搜索路徑,寫一個自己的類調(diào)用解密方法,得到 明文license再分析。當(dāng)然,也可以調(diào)用其它一些方法,從調(diào)用參數(shù)和最后的返回值我們也可大概猜對該方法的作用, 對付象上面經(jīng)過高質(zhì)量混淆的代碼也比較管用。當(dāng)然,我這里只是簡單的舉兩個例子,其實(shí)“方法調(diào)用”的妙用還很多, 自己慢慢琢磨吧! 三、為class添加代碼 反編譯多數(shù)情況下也只能讓我們看看作者的思路,如果想把反編譯出來的代碼經(jīng)過修改后再編譯成class,通常 是行不通了。而且有時候必須讓程序運(yùn)行在它本身的環(huán)境才能行,否則一些類無法得到正確的初始化,“方法調(diào)用” 也就起不了什么作用。搞過java的人一定知道javassist,這個庫提供了足夠多的方法讓你直接修改class文件,而不 需要你了解字節(jié)碼的相關(guān)知識,我們可以利用這個庫解決上述的問題。下面是我寫的一個修改字節(jié)碼的類,目前還不 完善,真正要用時可能需要根據(jù)情況做一些修改。 import java.lang.reflect.*; import javassist.*; import java.io.*; /** * <p>Title: JAVA 字節(jié)碼修改類</p> * <p>Description: 得到類的相關(guān)信息或修改該類</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; /** * 修改字節(jié)碼中的方法 * @param clas[0] 待修改類的方法名 * @param clas[1] 修改位置定義 * @param clas[2] 使用insertAt方法插放代碼時行號參數(shù) * @param clas[3] 修改內(nèi)容 * @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] 方法內(nèi)容 * @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("可供調(diào)用的方法有:"); System.out.println("1-modifyMethod,2-addMethod,3-getMethods,4-getFields,5-removeMethod"); System.out.println("***********************************************************"); System.out.print("請選擇調(diào)用方法:"); 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("調(diào)用 modifyMethod 方法參數(shù):"); System.out.println("方法名稱,插入位置,行號,內(nèi)容"); 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("調(diào)用 addMethod 方法參數(shù):"); System.out.println("源方法,目標(biāo)方法,建立方式,內(nèi)容"); 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("調(diào)用 removeMethod 方法參數(shù):"); 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方法用來在類的指定方法中插入一行或多行代碼,參數(shù)為a時表示插在方法現(xiàn)有代碼的最后面,為b時 表示插在方法現(xiàn)有代碼的最前面,為i時表時插在代碼的指定行的前面,這個行和原代碼中的行沒有關(guān)系,插入位置 要插入一次才能確定,為i時返回的值代表實(shí)際插入位置,由這個實(shí)際插入位置你可以計算i的值。在實(shí)際破解中發(fā)現(xiàn), 用該方法插入一些代碼后,會使原來反編譯的不可讀的代碼變的容易讀懂,當(dāng)然,也有可能使本來可讀性很強(qiáng)的代碼, 因?yàn)槟悴迦肓艘恍┱Z句而變得不可讀。我常常在關(guān)鍵方法的代碼中插入一些 System.out.println();這樣的代碼來跟蹤 程序,還有一點(diǎn)限制,你不能直接用打印輸出的方法來輸出方法體內(nèi)的局部變量,但你可以對全局變量進(jìn)行引用操作。 如果要操作局部變量,目前我所知的方法只能在該類里重建該方法,如果那位有其它的好辦法,也請指點(diǎn)我一下。 addMethod方法在是類中增加一個新的方法,增加的方式有兩種,這里就不做具體介紹。 其它方法也就不一一解釋了,有興趣的朋友可以研究一下javassist,相信你會寫出功能更強(qiáng)大的修改class文件的類庫。 四、class的修改 在破解過程中經(jīng)常會看到rsa非對稱加密算法,公鑰往往以十六進(jìn)制存放在class文件中,(當(dāng)然,也有對公鑰加密后存 放在配置文件中的程序)以便解密已經(jīng)加密過的信息。前不久破解的一個J2EE的開發(fā)平臺就是這樣的,license用RSA加密, 在搞懂了它的算法后,自己構(gòu)件license明文,自己再生成一對rsa的公私密鑰,用自己的私鑰對license文明進(jìn)行RSA加密, 再用十六進(jìn)制編輯器替換程序中所有的公鑰,(當(dāng)然是用你的公鑰替換他的公鑰,不然也沒法解密)一切就搞定。當(dāng)然, 我所說的只是一個方面,有時暴破時可能還得用到一些JVM的指命,比如你想讓一個 return false 的方法 return ture 那你就得把相應(yīng)位置的03 AC改為04 AC,位置怎么確定就不用我說了吧! 五、讀JVM指令 沒有什么可以多說的,如果要從jvm指令看懂成程,必須像熟匯編一樣熟悉jvm指令集,還得有一個工具把class翻譯成 jvm指令代碼,總不能用十六進(jìn)制編輯器讀代碼嗎?如果真是,那你就太牛了。我這里介紹 bcel 這個工具,它可以把class 解釋為jvm指令集并存為html文件,結(jié)果就像下面: 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指令集的話就已經(jīng)能讀懂它在做什么了 發(fā)現(xiàn)有關(guān)JAVA程序破解的文章不是很多,所以本人粗淺的談?wù)摿艘幌翵AVA程序破解時所用到的一些方法,當(dāng)然,還有很多 憑經(jīng)驗(yàn)才能找到的靈感,無法一一列舉,本文質(zhì)在拋磚引玉,望高手能寫一些技術(shù)含量更高的文章供我們這些菜鳥學(xué)習(xí) |
|