帝国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,但为了保留原来代码的完整性,我在这次操作里没有改动)

  那我们可以这样:向游戏函数中每个方法中插入一条语句,这条语句很简单,就是为了调用我们的某个函数,并把参数传递过来,我们进行操作。

  就像这样:

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

  这个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. private JLabel lblFindType;
  36. /**
  37. * 调试寻找的数据
  38. */
  39. private JTextField txtFindValue;
  40. /**
  41. * 调试寻找数据提示
  42. */
  43. private JLabel lblFindValue;
  44. /**
  45. * 调试寻找的数据类型
  46. */
  47. private JComboBox comFindType;
  48.  
  49. /**
  50. * 调试按钮
  51. */
  52. private JButton btnDbg;
  53.  
  54. /**
  55. * 出现寻找数据的方法调用
  56. */
  57. private JTextArea txtMethodCalls;
  58. /**
  59. * 数据方法调用滚动支持
  60. */
  61. private JScrollPane spMethodCalls;
  62. /**
  63. * 整个调试流程里方法调用的顺序
  64. */
  65. private JTextArea txtMethodTrace;
  66. /**
  67. * 方法调用流程滚动支持
  68. */
  69. private JScrollPane spMethodTrace;
  70.  
  71. public FrmMain() {
  72. setTitle("帝国OL注入式调试工具");
  73. setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
  74. setSize(640,360);
  75. setResizable(false);
  76. setLayout(null);
  77.  
  78. // 设置窗体居中
  79. Toolkit kit = Toolkit.getDefaultToolkit();
  80. Dimension screenSize = kit.getScreenSize();
  81. int screenWidth = screenSize.width;
  82. int screenHeight = screenSize.height;
  83. int windowWidth = this.getWidth();
  84. int windowHeight = this.getHeight();
  85. setLocation(screenWidth / 2 - windowWidth / 2, screenHeight / 2
  86. - windowHeight / 2);
  87.  
  88. initComponents();
  89. }
  90.  
  91. private void initComponents() {
  92. lblFindType = new JLabel("数据类型:");
  93. lblFindType.setSize(80,24);
  94. lblFindType.setLocation(28, 18);
  95.  
  96. comFindType = new JComboBox(new String[]{"数字","字串"});
  97. comFindType.setSize(80, 24);
  98. comFindType.setLocation(100, 18);
  99.  
  100. lblFindValue = new JLabel("寻找数值:");
  101. lblFindValue.setSize(80, 24);
  102. lblFindValue.setLocation(200, 18);
  103.  
  104. txtFindValue = new JTextField();
  105. txtFindValue.setSize(145, 24);
  106. txtFindValue.setLocation(260, 18);
  107.  
  108. btnDbg = new JButton("启动调试");
  109. btnDbg.setSize(80,24);
  110. btnDbg.setLocation(28, 48);
  111. btnDbg.addActionListener(this);
  112.  
  113. txtMethodCalls = new JTextArea();
  114. txtMethodCalls.setSize(568, 100);
  115. txtMethodCalls.setLocation(28, 80);
  116. //txtMethodCalls.setEditable(false);
  117. //txtMethodCalls.setLineWrap(true);
  118. //txtMethodCalls.setWrapStyleWord(true);
  119.  
  120. spMethodCalls = new JScrollPane();
  121. spMethodCalls.setViewportView(txtMethodCalls);
  122. spMethodCalls.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
  123. spMethodCalls.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
  124. spMethodCalls.setSize(568, 100);
  125. spMethodCalls.setLocation(28, 80);
  126.  
  127. txtMethodTrace = new JTextArea();
  128. txtMethodTrace.setSize(568, 100);
  129. txtMethodTrace.setLocation(28, 200);
  130. //txtMethodTrace.setEditable(false);
  131. //txtMethodTrace.setLineWrap(true);
  132. //txtMethodTrace.setWrapStyleWord(true);
  133.  
  134. spMethodTrace = new JScrollPane();
  135. spMethodTrace.setViewportView(txtMethodTrace);
  136. spMethodTrace.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
  137. spMethodTrace.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
  138. spMethodTrace.setSize(568, 100);
  139. spMethodTrace.setLocation(28, 200);
  140.  
  141. add(lblFindType);
  142. add(comFindType);
  143. add(lblFindValue);
  144. add(txtFindValue);
  145. add(btnDbg);
  146. //add(txtMethodCalls);
  147. //add(txtMethodTrace);
  148. add(spMethodCalls);
  149. add(spMethodTrace);
  150. }
  151.  
  152. @Override
  153. public void actionPerformed(ActionEvent e) {
  154. debug = !debug;
  155. if(debug) {
  156. btnDbg.setText("停止调试");
  157. switch(comFindType.getSelectedIndex()){
  158. case 0:
  159. String val = txtFindValue.getText().trim();
  160. if(val.isEmpty()) return;
  161. try{
  162. int intval = Integer.parseInt(val);
  163. ViewMethodCall.enableDebug(ViewMethodCall.DEBUG_TYPE_INT, intval);
  164. }catch(Exception ex){
  165. JOptionPane.showMessageDialog(this, ex.getMessage());
  166. debug = !debug;
  167. btnDbg.setText("启动调试");
  168. }
  169. break;
  170. case 1:
  171. String val1 = txtFindValue.getText().trim();
  172. if(val1.isEmpty()) return;
  173. ViewMethodCall.enableDebug(ViewMethodCall.DEBUG_TYPE_STR, val1);
  174. }
  175. }else{
  176. btnDbg.setText("启动调试");
  177. ViewMethodCall.disableDebug();
  178. txtMethodCalls.setText("");
  179. txtMethodTrace.setText("");
  180. Hashtable<String,Integer> callcounter = ViewMethodCall.getDebugResult();
  181. for(String methodname : callcounter.keySet()){
  182. txtMethodCalls.append(methodname);
  183. txtMethodCalls.append("\t");
  184. txtMethodCalls.append(callcounter.get(methodname).toString());
  185. txtMethodCalls.append("\n");
  186. }
  187. List<String> calltrace = ViewMethodCall.getMethodCallStackTrace();
  188. for(String tracele : calltrace){
  189. txtMethodTrace.append(tracele);
  190. txtMethodTrace.append("\n");
  191. }
  192. }
  193. }
  194.  
  195. /*public static void main(String[] args) {
  196. java.awt.EventQueue.invokeLater(new Runnable() {
  197. public void run() {
  198. try {
  199. UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
  200. } catch (Exception e) {
  201.  
  202. }
  203. new FrmMain().setVisible(true);
  204. }
  205. });
  206. }*/
  207. }

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

  如果看不懂请不要深究,不然到时候查水表被多带走一个&=*

欢迎您移步我们的交流群,无聊的时候大家一起打发时间:

或者通过QQ与我联系:

(最后编辑时间2013-11-09 16:24:31)

我是怎样使用javassist将代码注入到帝国OL并进行调试的的更多相关文章

  1. 转:EasyHook远程代码注入

    EasyHook远程代码注入 最近一段时间由于使用MinHook的API挂钩不稳定,经常因为挂钩地址错误而导致宿主进程崩溃.听同事介绍了一款智能强大的挂钩引擎EasyHook.它比微软的detours ...

  2. 32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式

    32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式 一丶RadAsm的配置和使用 用了怎么长时间的命令行方式,我们发现了几个问题 1.没有代码提醒功能 2.编写代码很慢,记不住各 ...

  3. 阿里云提示Discuz uc.key泄露导致代码注入漏洞uc.php的解决方法

    适用所有用UC整合 阿里云提示漏洞: discuz中的/api/uc.php存在代码写入漏洞,导致黑客可写入恶意代码获取uckey,.......... 漏洞名称:Discuz uc.key泄露导致代 ...

  4. 【原】iOS动态性(三) Method Swizzling以及AOP编程:在运行时进行代码注入

    概述 今天我们主要讨论iOS runtime中的一种黑色技术,称为Method Swizzling.字面上理解Method Swizzling可能比较晦涩难懂,毕竟不是中文,不过你可以理解为“移花接木 ...

  5. Java链接MySQL练习题:格式化日期、性别;避免代码注入

    一.查询人员名单,按序号 姓名 性格(男或女) 民族(某族) 生日(年月日)输出 import java.sql.*; import java.text.SimpleDateFormat; publi ...

  6. Android 反编译 代码注入之HelloWorld

    为了向经典的"Hello, World"致敬,我们也从一个简单的程序开始HelloWorld.apk.当你把这个APK安装到手机上运行后,在屏幕上就显示一行文字"Hell ...

  7. apk反编译(4)Smali代码注入

    转自 : http://blog.sina.com.cn/s/blog_5674d18801019i89.html 应用场景 Smali代码注入只能应对函数级别的移植,对于类级别的移植是无能为力的.具 ...

  8. 注入攻击-SQL注入和代码注入

    注入攻击 OWASP将注入攻击和跨站脚本攻击(XSS)列入网络应用程序十大常见安全风险.实际上,它们会一起出现,因为 XSS 攻击依赖于注入攻击的成功.虽然这是最明显的组合关系,但是注入攻击带来的不仅 ...

  9. Method Swizzling以及AOP编程:在运行时进行代码注入-备用

    概述 今天我们主要讨论iOS runtime中的一种黑色技术,称为Method Swizzling.字面上理解Method Swizzling可能比较晦涩难懂,毕竟不是中文,不过你可以理解为“移花接木 ...

随机推荐

  1. Gson 与 fastJson 在使用上的差异(fastJson的优点)

    一.android 常用的json解析方式 Android 开发上常用的json解析方式有:Gson, fastJson,jackson. 因为jackjson jar包会比较大点(700+k),Gs ...

  2. 快速准备(复制替换)一套新测试环境,CentOS7 MySQL相关配置

    拿到一个新环境,需要找相关配置,我有一个办法,相对能比较快速地复制一套环境出来. 修改机器配置: virsh 相关几条命令,已完成,后续我再整理补充... 虚拟化相关,参考:https://www.c ...

  3. 使用Guava的ComparisonChain实现自定义的排序

    可以看到使用比较器前,先要写一个实体类,还要实现comparable接口,实现compareTo方法.这个方法一般会返回-1 0 1三个int类型数字,分别表示,对象和传入的对象比较,排序应该在传入的 ...

  4. 利用Xmanager Enterprise 5的passive显示远程linux主机图形化信息

    问题描述: 最初的需求是,安装oracle数据(第一次安装都是图形化linxu进去一步步操作,后续发现可以命令静默安装不调用图形化,学习就是步步入深,方得始终),最初实现window弹出linux主机 ...

  5. Vue中CSS模块化最佳实践

    Vue风格指南中介绍了单文件组件中的Style是必须要有作用域的,否则组件之间可能相互影响,造成难以调试. 在Vue Loader Scope CSS和Vue Loader CSS Modules两节 ...

  6. Spring Boot 2.0 整合Thymeleaf 模板引擎

    本节将和大家一起实战Spring Boot 2.0 和thymeleaf 模板引擎 1. 创建项目 2. 使用Spring Initlizr 快速创建Spring Boot 应用程序 3. 填写项目配 ...

  7. 物联网架构成长之路(16)-SpringCloud从入门到吹水

    1.前言 Spring Cloud 现在比较流行,版本更新也是蛮快的,网上资料也是很多.很多参考网上资料就可以学到了.这里给个 http://blog.csdn.net/forezp/article/ ...

  8. 设计模式---策略模式Strategy(对象行为型)

    1. 概述 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它的客户而独立变化. 策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不 ...

  9. 【iCore1S 双核心板_FPGA】例程十二:基于单口RAM的ARM+FPGA数据存取实验

    实验现象: 核心代码: module single_port_ram( input CLK_12M, input WR, input RD, input CS0, inout [:]DB, input ...

  10. 【Unity】讯飞语音识别SDK

    1.进入讯飞官网,注册帐号,进入控制台,创建新应用UnityXunfeiDemo,平台选Android.在当前应用这点下载SDK,添加AI能力(添加新服务),选择语音听写,即可下载安卓SDK(下称讯飞 ...