我是怎樣使用javassist將程式碼注入到帝國OL並進行除錯的

Mr.Johness發表於2013-11-09

  帝國OL是拉闊一款手機網路遊戲(騰訊也有代理),我在中學時代玩兒過。

  帝國OL還維護著KJava版本遊戲客戶端,這意味著我們可以在PC端使用模擬器玩兒遊戲。

  不過這篇文章我主要是關注如何通過程式碼注入攔截其客戶端程式碼呼叫並測試其方法內容的。

 

  宣告:本人並沒有任何對於帝國OL遊戲程式碼的逆向工程、改編、分發或從中獲利的行為,本篇文章的執行資料和工作流程及所有言論和工作都是為了學習之用,如果文章中的內容侵犯了任何個人或集體的利益,請聯絡我關閉本篇文章。在您觀看此篇文章時則視為您已經預設了此文章為學習性質,否則請勿繼續向下觀看。

 

  帝國OL遊戲客戶端程式碼是混淆加密過的,在最新版客戶端中(截至時間2013-11-09,客戶端適用手機型號N5800)共有一個啟動類和149個方法提供類,和一般混淆結果一樣,我們如果閱讀class檔案或通過反編譯工具檢視會發現它類的類名、方法名和全域性變數名都是諸如a/aa/ba/bc等無意義、無規律的名稱,而且加密過後的程式碼是無法從反編譯結果直接再次編譯的。

  那我們還能對遊戲執行流程進行除錯嗎?比如監控遊戲方法呼叫?

  答案是肯定的。我們可以使用Java的一個第三方類庫,專門用於對class檔案進行操作。  

  

  我先將所需的工具和jar包發上來,大家可以下載或搜尋下載。

  Javassist:對Java位元組碼檔案進行操作的類庫,看起來和Java自己的reflection API很像,不過在對class檔案進行操作時功能更加強大。

  Kemulator:這個東西大家肯定不陌生,最常用的就是在電腦上玩兒手機遊戲。我推薦0.9.4版本,比較穩定。

  jd-gui:Java位元組碼檔案反編譯工具,使用很方便。不過對於雙重迴圈有時翻譯不出來……我們主要用於檢視一點資訊

 

  好,我們現在理一下思路:Javassist有一個功能,就是在某個方法執行之前或之後插入程式碼,而且還能拿到此次方法呼叫的所有引數型別和值。

  (注:此方法必須有方法體,且不是private的,當然,本身private方法我們也可以變成public,但為了保留原來程式碼的完整性,我在這次操作裡沒有改動)

 

  那我們可以這樣:向遊戲函式中每個方法中插入一條語句,這條語句很簡單,就是為了呼叫我們的某個函式,並把引數傳遞過來,我們進行操作。

  就像這樣:

public static void beCall(String methodName, Object[] params) {
}

  這個beCall函式就是要插入到遊戲原來程式碼方法中的內容。beCall函式接收兩個引數,第一個我定義為方法的簽名,包括方法名和引數型別,第二個引數是方法被呼叫時傳遞的引數。

  下面是我注入後的程式碼:

  

  從上圖的情況來看,我們對class的操作是成功的,我們只需要把ViewMethodCall拷入帝國OL客戶端jar包即可。

  (ViewMethodCall即我上文的類,beCall是公開的靜態函式)

 

  由於此篇文章涉及到某些政策問題,我就不將詳細步驟貼出來了。

  下面是我最後實現的功能:

  

  我可以用左側的窗體進行除錯,除錯主要在beCall函式接收到的引數值中尋找,比如下圖我就是在尋找當遊戲角色座標改變時遊戲內部的方法呼叫過程:

  

  然後我立即移動至24,當移動過程完成立即點選“停止除錯”,因為在除錯過程中的計算是十分佔用計算機運算效率和記憶體的:

  

  由於政策因素(-_- 如果我被查水錶大家為我默哀),我只貼出兩個關鍵類的程式碼,我對於class檔案操作的程式碼請聯絡我獲取:

  1 package form;
  2 
  3 import java.awt.Dimension;
  4 import java.awt.Toolkit;
  5 import java.awt.event.ActionEvent;
  6 import java.awt.event.ActionListener;
  7 import java.util.Hashtable;
  8 import java.util.List;
  9 
 10 import javax.swing.JButton;
 11 import javax.swing.JComboBox;
 12 import javax.swing.JDialog;
 13 import javax.swing.JLabel;
 14 import javax.swing.JOptionPane;
 15 import javax.swing.JScrollPane;
 16 import javax.swing.JTextArea;
 17 import javax.swing.JTextField;
 18 import javax.swing.UIManager;
 19 
 20 import test.ViewMethodCall;
 21 
 22 /**
 23  *
 24  * @author RyanShaw
 25  */
 26 public class FrmMain extends JDialog implements ActionListener {
 27 
 28     private static final long serialVersionUID = -8049035809432056277L;
 29 
 30     private boolean debug = false;
 31     
 32     
 33     /**
 34      * 除錯尋找資料型別提示
 35      */
 36     private JLabel lblFindType;
 37     /**
 38      * 除錯尋找的資料
 39      */
 40     private JTextField txtFindValue;
 41     /**
 42      * 除錯尋找資料提示
 43      */
 44     private JLabel lblFindValue;
 45     /**
 46      * 除錯尋找的資料型別
 47      */
 48     private JComboBox comFindType;
 49     
 50     /**
 51      * 除錯按鈕
 52      */
 53     private JButton btnDbg;
 54     
 55     /**
 56      * 出現尋找資料的方法呼叫
 57      */
 58     private JTextArea txtMethodCalls;
 59     /**
 60      * 資料方法呼叫滾動支援
 61      */
 62     private JScrollPane spMethodCalls;
 63     /**
 64      * 整個除錯流程裡方法呼叫的順序
 65      */
 66     private JTextArea txtMethodTrace;
 67     /**
 68      * 方法呼叫流程滾動支援
 69      */
 70     private JScrollPane spMethodTrace;
 71     
 72     public FrmMain() {
 73         setTitle("帝國OL注入式除錯工具");
 74         setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
 75         setSize(640,360);
 76         setResizable(false);
 77         setLayout(null);
 78         
 79         // 設定窗體居中
 80         Toolkit kit = Toolkit.getDefaultToolkit();
 81         Dimension screenSize = kit.getScreenSize();
 82         int screenWidth = screenSize.width;
 83         int screenHeight = screenSize.height;
 84         int windowWidth = this.getWidth();
 85         int windowHeight = this.getHeight();
 86         setLocation(screenWidth / 2 - windowWidth / 2, screenHeight / 2
 87                 - windowHeight / 2);
 88         
 89         initComponents();
 90     }
 91     
 92     private void initComponents() {
 93         lblFindType = new JLabel("資料型別:");
 94         lblFindType.setSize(80,24);
 95         lblFindType.setLocation(28, 18);
 96         
 97         comFindType = new JComboBox(new String[]{"數字","字串"});
 98         comFindType.setSize(80, 24);
 99         comFindType.setLocation(100, 18);
100         
101         lblFindValue = new JLabel("尋找數值:");
102         lblFindValue.setSize(80, 24);
103         lblFindValue.setLocation(200, 18);
104         
105         txtFindValue = new JTextField();
106         txtFindValue.setSize(145, 24);
107         txtFindValue.setLocation(260, 18);
108         
109         btnDbg = new JButton("啟動除錯");
110         btnDbg.setSize(80,24);
111         btnDbg.setLocation(28, 48);
112         btnDbg.addActionListener(this);
113         
114         txtMethodCalls = new JTextArea();
115         txtMethodCalls.setSize(568, 100);
116         txtMethodCalls.setLocation(28, 80);
117         //txtMethodCalls.setEditable(false);
118         //txtMethodCalls.setLineWrap(true);
119         //txtMethodCalls.setWrapStyleWord(true);
120         
121         spMethodCalls = new JScrollPane();
122         spMethodCalls.setViewportView(txtMethodCalls);
123         spMethodCalls.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
124         spMethodCalls.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
125         spMethodCalls.setSize(568, 100);
126         spMethodCalls.setLocation(28, 80);
127         
128         txtMethodTrace = new JTextArea();
129         txtMethodTrace.setSize(568, 100);
130         txtMethodTrace.setLocation(28, 200);
131         //txtMethodTrace.setEditable(false);
132         //txtMethodTrace.setLineWrap(true);
133         //txtMethodTrace.setWrapStyleWord(true);
134         
135         spMethodTrace = new JScrollPane();
136         spMethodTrace.setViewportView(txtMethodTrace);
137         spMethodTrace.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
138         spMethodTrace.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
139         spMethodTrace.setSize(568, 100);
140         spMethodTrace.setLocation(28, 200);
141                 
142         add(lblFindType);
143         add(comFindType);
144         add(lblFindValue);
145         add(txtFindValue);
146         add(btnDbg);
147         //add(txtMethodCalls);
148         //add(txtMethodTrace);
149         add(spMethodCalls);
150         add(spMethodTrace);
151     }
152 
153     @Override
154     public void actionPerformed(ActionEvent e) {
155         debug = !debug;
156         if(debug) {
157             btnDbg.setText("停止除錯");
158             switch(comFindType.getSelectedIndex()){
159             case 0:
160                 String val = txtFindValue.getText().trim();
161                 if(val.isEmpty()) return;
162                 try{
163                     int intval = Integer.parseInt(val);
164                     ViewMethodCall.enableDebug(ViewMethodCall.DEBUG_TYPE_INT, intval);
165                 }catch(Exception ex){
166                     JOptionPane.showMessageDialog(this, ex.getMessage());
167                     debug = !debug;
168                     btnDbg.setText("啟動除錯");
169                 }
170                 break;
171             case 1:
172                 String val1 = txtFindValue.getText().trim();
173                 if(val1.isEmpty()) return;
174                 ViewMethodCall.enableDebug(ViewMethodCall.DEBUG_TYPE_STR, val1);
175             }
176         }else{
177             btnDbg.setText("啟動除錯");
178             ViewMethodCall.disableDebug();
179             txtMethodCalls.setText("");
180             txtMethodTrace.setText("");
181             Hashtable<String,Integer> callcounter = ViewMethodCall.getDebugResult();
182             for(String methodname : callcounter.keySet()){
183                 txtMethodCalls.append(methodname);
184                 txtMethodCalls.append("\t");
185                 txtMethodCalls.append(callcounter.get(methodname).toString());
186                 txtMethodCalls.append("\n");
187             }
188             List<String> calltrace = ViewMethodCall.getMethodCallStackTrace();
189             for(String tracele : calltrace){
190                 txtMethodTrace.append(tracele);
191                 txtMethodTrace.append("\n");
192             }
193         }
194     }
195     
196     /*public static void main(String[] args) {
197         java.awt.EventQueue.invokeLater(new Runnable() {
198             public void run() {
199                 try {
200                     UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
201                 } catch (Exception e) {
202                     
203                 }
204                 new FrmMain().setVisible(true);
205             }
206         });
207     }*/
208 }
FrmMain
 1 package test;
 2 
 3 import java.util.ArrayList;
 4 import java.util.Hashtable;
 5 import java.util.List;
 6 
 7 import javax.swing.UIManager;
 8 
 9 import form.FrmMain;
10 
11 public class ViewMethodCall {
12     public static final int DEBUG_TYPE_INT = 1;
13     public static final int DEBUG_TYPE_STR = 0;
14     
15     private Hashtable<String, Integer> callcounter = new Hashtable<String, Integer>();
16     private List<String> callStackTrace = new ArrayList<String>();
17     private boolean debugenable = false;
18     private int debugType = DEBUG_TYPE_INT;
19     private String debugStr = null;
20     private int debugInt = -1;
21     private static ViewMethodCall me = new ViewMethodCall();
22     private ViewMethodCall(){
23         java.awt.EventQueue.invokeLater(new Runnable() {
24             public void run() {
25                 try {
26                     UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
27                 } catch (Exception e) {
28                     
29                 }
30                 new FrmMain().setVisible(true);
31             }
32         });
33     }
34     static {}
35 
36     public static void beCall(String methodName, Object[] params) {
37         if(!me.debugenable) return;
38         me.callStackTrace.add(methodName);
39         switch(me.debugType){
40         case DEBUG_TYPE_INT:
41             for(Object obj : params)
42                 if(obj != null && obj instanceof Integer && (Integer)obj == me.debugInt)
43                     if(me.callcounter.contains(methodName))
44                         me.callcounter.put(methodName, me.callcounter.get(methodName) + 1);
45                     else
46                         me.callcounter.put(methodName, 1);
47             break;
48         case DEBUG_TYPE_STR:
49             for(Object obj : params)
50                 if(obj != null && obj instanceof String && obj.equals(me.debugStr))
51                     if(me.callcounter.contains(methodName))
52                         me.callcounter.put(methodName, me.callcounter.get(methodName) + 1);
53                     else
54                         me.callcounter.put(methodName, 1);
55             break;
56         }
57     }
58     
59     public static void enableDebug(int type, Object value){
60         me.debugenable = true;
61         me.callcounter.clear();
62         me.callStackTrace.clear();
63         me.debugType = type;
64         switch(type){
65         case DEBUG_TYPE_INT:
66             me.debugInt = (Integer) value;
67             break;
68         case DEBUG_TYPE_STR:
69             me.debugStr = (String) value;
70             break;
71         }
72     }
73     
74     public static void disableDebug(){
75         me.debugenable = false;
76     }
77     
78     public static Hashtable<String, Integer> getDebugResult(){
79         return me.callcounter;
80     }
81     
82     public static List<String> getMethodCallStackTrace(){
83         return me.callStackTrace;
84     }
85 }
ViewMethodCall

  如果看不懂請不要深究,不然到時候查水錶被多帶走一個&=*

 

 歡迎您移步我們的交流群,無聊的時候大家一起打發時間:Programmer Union

 或者通過QQ與我聯絡:點選這裡給我發訊息

 (最後編輯時間2013-11-09 16:24:31)

 

相關文章