我是怎样使用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文件操作的代码请联系我获取:
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并进行调试的的更多相关文章
- 转:EasyHook远程代码注入
EasyHook远程代码注入 最近一段时间由于使用MinHook的API挂钩不稳定,经常因为挂钩地址错误而导致宿主进程崩溃.听同事介绍了一款智能强大的挂钩引擎EasyHook.它比微软的detours ...
- 32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式
32位汇编第三讲,RadAsm,IDE的配置和使用,以及汇编代码注入方式 一丶RadAsm的配置和使用 用了怎么长时间的命令行方式,我们发现了几个问题 1.没有代码提醒功能 2.编写代码很慢,记不住各 ...
- 阿里云提示Discuz uc.key泄露导致代码注入漏洞uc.php的解决方法
适用所有用UC整合 阿里云提示漏洞: discuz中的/api/uc.php存在代码写入漏洞,导致黑客可写入恶意代码获取uckey,.......... 漏洞名称:Discuz uc.key泄露导致代 ...
- 【原】iOS动态性(三) Method Swizzling以及AOP编程:在运行时进行代码注入
概述 今天我们主要讨论iOS runtime中的一种黑色技术,称为Method Swizzling.字面上理解Method Swizzling可能比较晦涩难懂,毕竟不是中文,不过你可以理解为“移花接木 ...
- Java链接MySQL练习题:格式化日期、性别;避免代码注入
一.查询人员名单,按序号 姓名 性格(男或女) 民族(某族) 生日(年月日)输出 import java.sql.*; import java.text.SimpleDateFormat; publi ...
- Android 反编译 代码注入之HelloWorld
为了向经典的"Hello, World"致敬,我们也从一个简单的程序开始HelloWorld.apk.当你把这个APK安装到手机上运行后,在屏幕上就显示一行文字"Hell ...
- apk反编译(4)Smali代码注入
转自 : http://blog.sina.com.cn/s/blog_5674d18801019i89.html 应用场景 Smali代码注入只能应对函数级别的移植,对于类级别的移植是无能为力的.具 ...
- 注入攻击-SQL注入和代码注入
注入攻击 OWASP将注入攻击和跨站脚本攻击(XSS)列入网络应用程序十大常见安全风险.实际上,它们会一起出现,因为 XSS 攻击依赖于注入攻击的成功.虽然这是最明显的组合关系,但是注入攻击带来的不仅 ...
- Method Swizzling以及AOP编程:在运行时进行代码注入-备用
概述 今天我们主要讨论iOS runtime中的一种黑色技术,称为Method Swizzling.字面上理解Method Swizzling可能比较晦涩难懂,毕竟不是中文,不过你可以理解为“移花接木 ...
随机推荐
- sf2gis@163.com
1.下载boost1.52,http://www.boost.org/.解压文件到d:\boost\boost_1_52_0. 2.下载python2.7.3,http://www.python.or ...
- Android典型界面设计(4)——使用ActionBar+Fragment实现tab切换
一.问题描述 之前我们使用ViewPager+Fragment区域内头部导航,在Android 3.0之后Google增加了新的ActionBar,可方便的实现屏幕Head部区域的 设计如返回键.标题 ...
- oracle 11g 安装及netca,dbca乱码之解决
在中文Linux下安装Oracle 11g,运行runInstaller后默认会出现乱码,解决办法如下: 1.准备字体zysong.ttf,点击下载,解压下载到的fallback 2.使用归档管理器打 ...
- docker部署maven私有仓库 nexus3
docker pull sonatype/nexus3: docker run -d --name nexus3.x --network host -v /volume-data/nexus3/nex ...
- HDU 4666 Hyperspace (最远曼哈顿距离)
Hyperspace Time Limit: 20000/10000 MS (Java/Others) Memory Limit: 65535/65535 K (Java/Others)Tota ...
- 如何部署hadoop集群
假设我们有三台服务器,他们的角色我们做如下划分: 10.96.21.120 master 10.96.21.119 slave1 10.96.21.121 slave2 接下来我们按照这个配置来部署h ...
- Axure RP for Mac(网站交互式原型设计工具)破解版安装
1.软件简介 Axure RP 是 macOS 系统上一款最知名和最强大的原型设计工具,增加了大量新的特性,如应用多个动画,并同一时间运行一个小部件,如褪色,同时移动等,而且具有全新的图标和界面 ...
- Eureka微服务ID
Instance ID用于唯一标识注册到Eureka Server上的微服务实例.我们可在Eureka Server的首页直观地看到各个微服务的Instance ID.例如,图11-1中的itmuch ...
- java框架篇---Struts2 本地化/国际化(i18n)
国际化(i18n)是规划和实施的产品和服务,使他们能很容易地适应特定的本地语言和文化的过程中,这个过程被称为本地化.国际化的过程有时也被称为翻译或本地化启用.国际化是缩写i18n,因为我和两端用n字打 ...
- vuex的理解
首先需要了解vuex的基本概念和使用方式,vue的官网也有很详细的说明或者浏览:https://zhuanlan.zhihu.com/p/24357762. vue是单页应用所以当页面刷新时vuex的 ...