帝国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文件操作的代码请联系我获取:

 package form;

 import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Hashtable;
import java.util.List; import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.UIManager; import test.ViewMethodCall; /**
*
* @author RyanShaw
*/
public class FrmMain extends JDialog implements ActionListener { private static final long serialVersionUID = -8049035809432056277L; private boolean debug = false; /**
* 调试寻找数据类型提示
*/
private JLabel lblFindType;
/**
* 调试寻找的数据
*/
private JTextField txtFindValue;
/**
* 调试寻找数据提示
*/
private JLabel lblFindValue;
/**
* 调试寻找的数据类型
*/
private JComboBox comFindType; /**
* 调试按钮
*/
private JButton btnDbg; /**
* 出现寻找数据的方法调用
*/
private JTextArea txtMethodCalls;
/**
* 数据方法调用滚动支持
*/
private JScrollPane spMethodCalls;
/**
* 整个调试流程里方法调用的顺序
*/
private JTextArea txtMethodTrace;
/**
* 方法调用流程滚动支持
*/
private JScrollPane spMethodTrace; public FrmMain() {
setTitle("帝国OL注入式调试工具");
setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
setSize(640,360);
setResizable(false);
setLayout(null); // 设置窗体居中
Toolkit kit = Toolkit.getDefaultToolkit();
Dimension screenSize = kit.getScreenSize();
int screenWidth = screenSize.width;
int screenHeight = screenSize.height;
int windowWidth = this.getWidth();
int windowHeight = this.getHeight();
setLocation(screenWidth / 2 - windowWidth / 2, screenHeight / 2
- windowHeight / 2); initComponents();
} private void initComponents() {
lblFindType = new JLabel("数据类型:");
lblFindType.setSize(80,24);
lblFindType.setLocation(28, 18); comFindType = new JComboBox(new String[]{"数字","字串"});
comFindType.setSize(80, 24);
comFindType.setLocation(100, 18); lblFindValue = new JLabel("寻找数值:");
lblFindValue.setSize(80, 24);
lblFindValue.setLocation(200, 18); txtFindValue = new JTextField();
txtFindValue.setSize(145, 24);
txtFindValue.setLocation(260, 18); btnDbg = new JButton("启动调试");
btnDbg.setSize(80,24);
btnDbg.setLocation(28, 48);
btnDbg.addActionListener(this); txtMethodCalls = new JTextArea();
txtMethodCalls.setSize(568, 100);
txtMethodCalls.setLocation(28, 80);
//txtMethodCalls.setEditable(false);
//txtMethodCalls.setLineWrap(true);
//txtMethodCalls.setWrapStyleWord(true); spMethodCalls = new JScrollPane();
spMethodCalls.setViewportView(txtMethodCalls);
spMethodCalls.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
spMethodCalls.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
spMethodCalls.setSize(568, 100);
spMethodCalls.setLocation(28, 80); txtMethodTrace = new JTextArea();
txtMethodTrace.setSize(568, 100);
txtMethodTrace.setLocation(28, 200);
//txtMethodTrace.setEditable(false);
//txtMethodTrace.setLineWrap(true);
//txtMethodTrace.setWrapStyleWord(true); spMethodTrace = new JScrollPane();
spMethodTrace.setViewportView(txtMethodTrace);
spMethodTrace.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
spMethodTrace.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
spMethodTrace.setSize(568, 100);
spMethodTrace.setLocation(28, 200); add(lblFindType);
add(comFindType);
add(lblFindValue);
add(txtFindValue);
add(btnDbg);
//add(txtMethodCalls);
//add(txtMethodTrace);
add(spMethodCalls);
add(spMethodTrace);
} @Override
public void actionPerformed(ActionEvent e) {
debug = !debug;
if(debug) {
btnDbg.setText("停止调试");
switch(comFindType.getSelectedIndex()){
case 0:
String val = txtFindValue.getText().trim();
if(val.isEmpty()) return;
try{
int intval = Integer.parseInt(val);
ViewMethodCall.enableDebug(ViewMethodCall.DEBUG_TYPE_INT, intval);
}catch(Exception ex){
JOptionPane.showMessageDialog(this, ex.getMessage());
debug = !debug;
btnDbg.setText("启动调试");
}
break;
case 1:
String val1 = txtFindValue.getText().trim();
if(val1.isEmpty()) return;
ViewMethodCall.enableDebug(ViewMethodCall.DEBUG_TYPE_STR, val1);
}
}else{
btnDbg.setText("启动调试");
ViewMethodCall.disableDebug();
txtMethodCalls.setText("");
txtMethodTrace.setText("");
Hashtable<String,Integer> callcounter = ViewMethodCall.getDebugResult();
for(String methodname : callcounter.keySet()){
txtMethodCalls.append(methodname);
txtMethodCalls.append("\t");
txtMethodCalls.append(callcounter.get(methodname).toString());
txtMethodCalls.append("\n");
}
List<String> calltrace = ViewMethodCall.getMethodCallStackTrace();
for(String tracele : calltrace){
txtMethodTrace.append(tracele);
txtMethodTrace.append("\n");
}
}
} /*public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception e) { }
new FrmMain().setVisible(true);
}
});
}*/
}

FrmMain

 package test;

 import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List; import javax.swing.UIManager; import form.FrmMain; public class ViewMethodCall {
public static final int DEBUG_TYPE_INT = 1;
public static final int DEBUG_TYPE_STR = 0; private Hashtable<String, Integer> callcounter = new Hashtable<String, Integer>();
private List<String> callStackTrace = new ArrayList<String>();
private boolean debugenable = false;
private int debugType = DEBUG_TYPE_INT;
private String debugStr = null;
private int debugInt = -1;
private static ViewMethodCall me = new ViewMethodCall();
private ViewMethodCall(){
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
} catch (Exception e) { }
new FrmMain().setVisible(true);
}
});
}
static {} public static void beCall(String methodName, Object[] params) {
if(!me.debugenable) return;
me.callStackTrace.add(methodName);
switch(me.debugType){
case DEBUG_TYPE_INT:
for(Object obj : params)
if(obj != null && obj instanceof Integer && (Integer)obj == me.debugInt)
if(me.callcounter.contains(methodName))
me.callcounter.put(methodName, me.callcounter.get(methodName) + 1);
else
me.callcounter.put(methodName, 1);
break;
case DEBUG_TYPE_STR:
for(Object obj : params)
if(obj != null && obj instanceof String && obj.equals(me.debugStr))
if(me.callcounter.contains(methodName))
me.callcounter.put(methodName, me.callcounter.get(methodName) + 1);
else
me.callcounter.put(methodName, 1);
break;
}
} public static void enableDebug(int type, Object value){
me.debugenable = true;
me.callcounter.clear();
me.callStackTrace.clear();
me.debugType = type;
switch(type){
case DEBUG_TYPE_INT:
me.debugInt = (Integer) value;
break;
case DEBUG_TYPE_STR:
me.debugStr = (String) value;
break;
}
} public static void disableDebug(){
me.debugenable = false;
} public static Hashtable<String, Integer> getDebugResult(){
return me.callcounter;
} public static List<String> getMethodCallStackTrace(){
return me.callStackTrace;
}
}

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. 混沌的艺术--- YChaos通过数学公式生成混沌图像

    艺术真得很难吗?也许如同编程一样容易.我写了一套软件,其功能是通过输入数学方程式,生成艺术图像.一提到数学有人可能会发怵,这里请不要担心,生成混沌的数学公式大都很是简单,基本上只用加.减.乘.除.余. ...

  2. IDEA下使用Maven的test命令乱码

    IDEA下使用Maven的test命令乱码的时候,加上 -Dfile.encoding=GBK 就可以解决啦   如下图所示:   或者在Maven的pom.xml文件中增加: <propert ...

  3. ionic build - 修改gradle路径提升速度和成功率

    ionic build 不成功很多时候是因为下载gradle太慢, 我们可以下载好gradle然后修改配置文件, 从而省去这个烦人的下载过程 1. 下载报错时的gradle zip文件, 或者直接查看 ...

  4. fork failed because of Out Of Memory

    Maybe virtual memory over commit is prevented in your system. If it is prevented, then the virtual m ...

  5. JavaScript绑定this

    问题描述 var a = { one: 1, haha() { console.log(this.one) } } setTimeout(a.haha, 1000) 在上例中,函数haha引用了thi ...

  6. Sort_Buffer_Size 设置对服务器性能的影响

    基础知识: 1. Sort_Buffer_Size 是一个connection级参数,在每个connection第一次需要使用这个buffer的时候,一次性分配设置的内存.2. Sort_Buffer ...

  7. 【Sqlserver】SqlServer中EXEC 与 SP_EXECUTESQL的 区别

    MSSQL为我们提供了两种动态执行SQL语句的命令,分别是 EXEC 和 SP_EXECUTESQL ,我们先来看一下两种方式的用法. 先建立一个表,并添加一些数据来进行演示: CREATE TABL ...

  8. C#访问MySQL数据库的方法

    C#访问MySQL数据库的方法 (1)首先需要下载C#访问MySQL数据库的ADO.NET驱动程序 下载地址为: http://dev.mysql.com/downloads/connector/ne ...

  9. 收藏清单: python测试数据生成及代码扫描最全工具列表

    Test Data manipulation 测试数据的操作和处理 faker - 生成假数据的python库 fake2db - 创建假数据库 ForgeryPy - 使用起来很简单的假数据生成库. ...

  10. 9.翻译系列:EF 6以及EF Core中的数据注解特性(EF 6 Code-First系列)

    原文地址:http://www.entityframeworktutorial.net/code-first/dataannotation-in-code-first.aspx EF 6 Code-F ...