我是怎样使用javassist将代码注入到帝国OL并进行调试的

  帝国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)

 

posted @ 2013-11-09 16:32  云中双月  阅读(1861)  评论(0编辑  收藏  举报