帝国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. [Java] LinkedHashMap 源码简要分析

    特点 * 各个元素不仅仅按照HashMap的结构存储,而且每个元素包含了before/after指针,通过一个头元素header,形成一个双向循环链表.使用循环链表,保存了元素插入的顺序. * 可设置 ...

  2. 【Linux】Cent OS 虚拟机开机自启动配置

    一次断电,导致实体机关机了,虚拟机也连不上去,只好手动来起来. 我想增加一下自启动,让硬件开机的时候,自动启动虚拟机: 其实是有办法的,尝试了一下,也成功了,这里简单标记下. virsh autost ...

  3. 微软BI 之SSIS 系列 - 在 SSIS 中使用 Web Service 以及 XML 解析

    开篇介绍 Web Service 的用途非常广几乎无处不在,像各大门户网站上的天气预报使用到的第三方 Web Service API,像手机客户端和服务器端的交互等都可以通过事先设计好的 Web Se ...

  4. Swift 类型桥接

    前言 iOS 中的 API 基本都是在许多年前由 OC 写成的,现在通过桥接的方法在 Swift 中可以用,基本看不出区别,非常自然.但是一些特殊的类型,在两种语言进行桥接的时候需要特别注意. 1.N ...

  5. ASP.NET MVC多语言 仿微软网站效果(转)

    本文转自: https://blog.csdn.net/Cooldiok/article/details/7831351 2017年10月22日 21:31:22 Cooldiok 微软作为ASP.N ...

  6. 借着Python-3来聊聊utf-8字符集

    [关于文本文件] 文本文件也是以二进制序列的方式保存在磁盘中的,磁盘并不能保存文本:我们打开文本文件的时候之所以能看到文字,是因为 软件根据文件所用编码的字符集对文件进行解码的原因. [以utf-8字 ...

  7. Forward+ Shading架构

    Forward+ = Tile based Light Culling + Tile based Forward Rendering 整体渲染架构分为如下3大步骤: 1.PrePass将场景的min ...

  8. ThinkPad T420 Fn+F5

    关于F5,可做如下设置:     1)官网win7系统下载SIhotkey[8jvu39ww].exe:最新版本的我没测试,应该也可以用.     2)双击安装,并按程序安装,直到要你选择安装on s ...

  9. mac上Python多版本共存(python2.7.10和python3.5.0)

    本文的实现目标是在mac上安装一个python3.5.0的版本,跟当前系统自带的python2.7.10共存. 查看当前版本号 python -V 2.7.10 安装配置Python版本管理器pyen ...

  10. 线程安全的ConcurrentQueue<T>队列

    队列(Queue)代表了一个先进先出的对象集合.当您需要对各项进行先进先出的访问时,则使用队列.当您在列表中添加一项,称为入队,当您从列表中移除一项时,称为出队. ConcurrentQueue< ...