帝国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. C++并发编程 条件变量 condition_variable,线程安全队列示例

    1. 背景 c++11中提供了对线程与条件变量的更好支持,对于写多线程程序方便了很多. 再看c++并发编程,记一下学习笔记. 2. c++11 提供的相关api 3.1 wait wait用于无条件等 ...

  2. iOS开发-UIView扩展CGRect

    关于UIView的位置都会遇到,一般需要改变UIView的位置,需要先获取原有的frame位置,然后在frame上面修改,有的时候如果只是改变了一下垂直方向的位置,宽度和高度的一种,这种写法很麻烦.下 ...

  3. [Canvas]Bombman v1.00

    爆破小人Canvas版,请点此下载,并用浏览器打开试玩. 图例: 源码: <!DOCTYPE html> <html lang="utf-8"> <m ...

  4. 从入门到精通Puppet的实践之路

    本文有感于<精通Puppet配置管理工具>在豆瓣上的某些差评而顺手写的书评. 半路出家   故事要从12年初说起.  某天,部门老大让我所在team的老大调研一下当下业界的配置管理工具.于 ...

  5. JAVA 自定义注解在自动化测试中的使用

    在UI自动化测试中,相信很多人都喜欢用所谓的PO模式,其中的P,也就是page的意思,于是乎,在脚本里,或者在其它的page里,会要new很多的page对象,这样很麻烦,前面我们也讲到了注解的使用,很 ...

  6. ceph 对象存储跨机房容灾

    场景分析 每个机房的Ceph都是独立的cluster,彼此之间没有任何关系. 多个机房都独立的提供对象存储功能,每个Ceph Radosgw都有自己独立的命名空间和存储空间. 这样带来两个问题: 针对 ...

  7. php write_ini_file

    php读ini文件有很方便的pares_ini_file,但是写回去却没有,这里写一个: function write_ini_file($assoc_arr, $path, $has_section ...

  8. PHP——自定义比较算法

    很多时候,程序都是直接写好逻辑运算,提供给用户的是一个值,然后后台去比较:但是有时会提供一种类似计算器一样的交互方式的时候,PHP只能读懂用户的输入来进行比较了- 最近的一个项目涉及到一个由用户自定义 ...

  9. CSS中的继承

    继承:子元素继承父元素的样式,但是并不是所有属性都是默认继承的. 通过文档中的 inherited:yes 来判断属性是否可以继承,关于继承可以参见css的继承关键字: 一.无继承性的属性 1.dis ...

  10. TypeScript学习笔记(九):装饰器(Decorators)

    装饰器简介 装饰器(Decorators)为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式. 需要注意的是:装饰器是一项实验性特性,在未来的版本中可能会发生改变. 若要启用实验性的装饰器特 ...