淺談JAVA程序破解(轉)

淺談JAVA程序破解
http://www.blogjava.net/galaxyp/archive/2006/04/19/41833.html


作者:舵手
申明:如轉載請保證文章的完整性以及出處

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

發佈了27 篇原創文章 · 獲贊 0 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章