处于种种原因,很多提供的jar包中的class都是混效过的,有些时候,可能处于参考,或者其他某些可告人,不可告人的目的,需要查看某些class的运行逻辑或者制作补丁等等什么的,就需要将class进行反编译了,在某些情况下,一些内容可能是动态的,或者想看修改之后出的效果,由于一个jar中往往有很多个class,但通过反编译,修改的可能仅仅只有一到两个文件,这样就需要将修改后的文件重新编译,添加到原始的jar中,来运行了。
我前两天刚好碰到了这个问题,在网上找java的反编译器,看到了一本关于java反编译与修复的书籍(竟然出书了),具体名字没记住,后来简单的思考一下,也不是什么太大的问题,于是制订了下计划,试试看自己的思路是否是正确的:
1、解压缩jar,获得class文件
2、反编译感兴趣的class文件
3、修改反编译后的class文件的源代码,使之没有语法问题
4、按需要修改上一步骤的源代码
5、编译单个java文件到class
7、将编译后的class重新放到jar中
jar使用的是zip的压缩算法,所以,不管是解压缩还是压缩都没有问题。
java有编译器jad,这一步也没有问题。
修改java代码,任何一款编辑器都没有问题。
修改后的代码能否进行编译?考虑到java代码都是单个单个的编译成class,这一步也没有问题。
编译后的class能否和原来的class配合很好的运行?从C/C++编译的角度来说,函数,变量等地址的整合在link的部分进行,而java没有这一部分,仅仅将class压缩成一个压缩包,从这个角度上来说,没有问题。如果class代码中地址已经固定死了?从理论上来说,这是可能的,但我觉得,这种可能性比较小,这次的测试也没有体现到这一点。改天再测试。
先看原始的代码:
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.GameCanvas;
import java.io.IOException;
import java.io.InputStream;
import javax.microedition.io.*;
import java.util.Vector;
import javax.microedition.media.*;
import javax.microedition.media.control.*;
import javax.microedition.lcdui.game.*;
/*!
* @class MainCanvas
* @brief 客户端主类实现
*
* 该类实现客户端的一切事件。
* @version 0.0.1
* @date 2007年08月15日
* @copyright GPL
*
*/
class MainCanvas extends GameCanvas implements Runnable
{
public contral parent = null; //!< 入口类实例
public Thread thread = null; //!< 游戏主线程
public final static int FPS = 20; //!< 游戏速度
public final static int KEY_PRESS = 1; //!< 按键按下事件
public final static int KEY_RELEASE = 2; //!< 按键弹起事件
public final static int GAME_INIT = 0; //!< 游戏初始化状态
public final static int GAME_TITLE = GAME_INIT + 1;
public final static int KEY_0 = 1; //!< 按键0
public final static int KEY_1 = 1 << 1; //!< 按键1
public final static int KEY_2 = 1 << 2; //!< 按键2
public final static int KEY_3 = 1 << 3; //!< 按键3
public final static int KEY_4 = 1 << 4; //!< 按键4
public final static int KEY_5 = 1 << 5; //!< 按键5
public final static int KEY_6 = 1 << 6; //!< 按键6
public final static int KEY_7 = 1 << 7; //!< 按键7
public final static int KEY_8 = 1 << 8; //!< 按键8
public final static int KEY_9 = 1 << 9; //!< 按键9
public final static int KEY_pOUND = 1 << 10; //!< 按键#
public final static int KEY_sTAR = 1 << 11; //!< 按键*
public final static int KEY_uP = 1 << 12; /**< 按键 上方向键 */
public final static int KEY_dOWN = 1 << 13; /**< 按键 下方向键 */
public final static int KEY_lEFT = 1 << 14; /**< 按键 左方向键 */
public final static int KEY_rIGHT = 1 << 15; /**< 按键 右方向键 */
public final static int KEY_sELECT = 1 << 16; /**< 按键 选择键 */
public final static int KEY_DELAY = 100; /**< 输入文字时的按键延迟 */
public Image iback = null; /**< 缓冲图 */
public Graphics gc = null; /**< 从缓冲图中生成的绘图设备 */
public Image LOGO = null; /**< 图象 logo */
int State = 0; //!< 游戏状态
int keyPress = 0; //!< 按键按下的值
int keyRepeate = 0; //!< 重复按键的值
int keypush = 0; //!< 游戏中用来判断按键按下的值
int keydata = 0; //!< 游戏中用来判断重复按下的值
Font NormalFont = null; /**< 普通字体 */
Vector obj_list = null;
/*!
* @brief 处理按键事件
*
* @param keycode 按键值
* @param event 按键事件类型
* @return 无
*/
public void postKeyEvent( int keycode, int event )
{
if( event == KEY_PRESS )
{
int temp = keyRepeate;
keyRepeate |= keycode;
if( temp != keyRepeate )
keyPress = keycode;
}else if( event == KEY_RELEASE )
{
keyRepeate &= ~keycode;
}
}
/*!
* @brief 构造函数
*
* @param aparent 客户端入口类
*/
public MainCanvas( contral aparent )
{
super(false);
parent = aparent;
//packet = new Packet();
}
/*!
* @brief 初以始化函数
*
*/
void init()
throws Exception
{
State = GAME_INIT;
NormalFont = Font.getFont( Font.FACE_SYSTEM | Font.SIZE_MEDIUM | Font.STYLE_PLAIN );
iback = Image.createImage( getWidth(), getHeight() );
gc = iback.getGraphics();
System.out.println( "start init" );
}
/*!
* @brief 处理游戏开始事件
*
*/
public void start()
{
try{
System.gc();
if( thread != null )
thread = null;
init();
thread = new Thread( this );
thread.start();
}catch(Exception other) {
System.out.println("exception="+other.toString());
}
}
/*!
* @brief 处理游戏退出事件
*
*/
public void quit()
{
//try{
if( thread != null )
{ thread = null; };
{ iback = null; };
{ gc = null; };
{ NormalFont = null; };
/*
}catch( IOException e )
{
}
*/
}
/*!
* @brief 处理游戏中断事件
*
*/
public void suspend()
{
if( thread != null )
{ thread = null; };
System.gc();
}
/*!
* @brief 处理游戏中断返回事件
*
*/
public void resume()
{
System.gc();
if( thread != null )
{ thread = null; };
thread = new Thread( this );
thread.start();
}
/*!
* @brief 处理重复按键事件
*
*/
protected void keyRepeated( int keyCode )
{
}
/*!
* @brief 处理按键按下事件
*
*/
protected void keyPressed( int keyCode )
{
int akeycode = -1;
switch( keyCode )
{
case KEY_NUM0:
akeycode = KEY_0;
break;
case KEY_NUM1:
akeycode = KEY_1;
break;
case KEY_NUM2:
akeycode = KEY_2;
break;
case KEY_NUM3:
akeycode = KEY_3;
break;
case KEY_NUM4:
akeycode = KEY_4;
break;
case KEY_NUM5:
akeycode = KEY_5;
break;
case KEY_NUM6:
akeycode = KEY_6;
break;
case KEY_NUM7:
akeycode = KEY_7;
break;
case KEY_NUM8:
akeycode = KEY_8;
break;
case KEY_NUM9:
akeycode = KEY_9;
break;
case KEY_POUND:
akeycode = KEY_pOUND;
break;
case KEY_STAR:
akeycode = KEY_sTAR;
break;
case -1:
case UP:
akeycode = KEY_uP;
break;
case -2:
case DOWN:
akeycode = KEY_dOWN;
break;
case -3:
case LEFT:
akeycode = KEY_lEFT;
break;
case -4:
case RIGHT:
akeycode = KEY_rIGHT;
break;
case -5:
akeycode = KEY_sELECT;
break;
}
if( akeycode != -1 )
postKeyEvent( akeycode, KEY_PRESS );
}
/*!
* @brief 处理按键释放事件
*
*/
protected void keyReleased( int keyCode )
{
int akeycode = -1;
switch( keyCode )
{
case KEY_NUM0:
akeycode = KEY_0;
break;
case KEY_NUM1:
akeycode = KEY_1;
break;
case KEY_NUM2:
akeycode = KEY_2;
break;
case KEY_NUM3:
akeycode = KEY_3;
break;
case KEY_NUM4:
akeycode = KEY_4;
break;
case KEY_NUM5:
akeycode = KEY_5;
break;
case KEY_NUM6:
akeycode = KEY_6;
break;
case KEY_NUM7:
akeycode = KEY_7;
break;
case KEY_NUM8:
akeycode = KEY_8;
break;
case KEY_NUM9:
akeycode = KEY_9;
break;
case KEY_POUND:
akeycode = KEY_pOUND;
break;
case KEY_STAR:
akeycode = KEY_sTAR;
break;
case -1:
case UP:
akeycode = KEY_uP;
break;
case -2:
case DOWN:
akeycode = KEY_dOWN;
break;
case -3:
case LEFT:
akeycode = KEY_lEFT;
break;
case -4:
case RIGHT:
akeycode = KEY_rIGHT;
break;
case -5:
akeycode = KEY_sELECT;
break;
}
if( akeycode != -1 )
postKeyEvent( akeycode, KEY_RELEASE );
}
/*!
* @brief 游戏的主循环处理
*
* 游戏的主循环处理,如果有未处理的数据包,并且超过一段时间,将强制发送数据包
*/
void update()
{
keypush = keyPress;
keydata = keyRepeate;
keyPress = 0;
switch( State )
{
case GAME_INIT:
// State = GAME_LOGIN;
break;
}
}
static long prevtime = 0;
/*!
* @brief 得到可以绘制的区域的宽度
*
*/
int getcanvasWidth()
{
if( iback != null )
return iback.getWidth();
return 0;
}
/*!
* @brief 得到可以绘制的区域的高度
*
*/
int getcanvasHeight()
{
if( iback != null )
return iback.getHeight();
return 0;
}
static final long SECS_TO_MICROSECS = 1000000L;
/*!
* @brief 显示游戏画面
*
*/
void display()
throws Exception
{
switch (State) {
case GAME_INIT:
gc.setFont( NormalFont );
gc.setColor(0xFFFFFF);
gc.fillRect(0, 0, getcanvasWidth(), getcanvasHeight());
State = GAME_TITLE;
break;
}
}
/*!
* @brief 得到屏幕宽度
*
*/
int getscreenWidth()
{
return getWidth();
}
/*!
* @brief 得到屏幕高度
*
*/
int getscreenHeight()
{
return getHeight();
}
/*!
* @brief 刷新屏幕
*
*/
public void paint( Graphics g )
{
g.setColor( 0 );
g.fillRect( 0, 0, getscreenWidth(), getscreenHeight() );
if( iback != null )
g.drawImage( iback, 0, 0, Graphics.LEFT | Graphics.TOP );
}
/*!
* @brief 线程主循环
*
*/
public void run()
{
Thread pthis = Thread.currentThread();
while( pthis == thread )
{
try{
long curtime = System.currentTimeMillis();
update();
display();
repaint();
long usetime = System.currentTimeMillis() - curtime;
usetime = 1000 / FPS - usetime;
if( usetime > 0 )
{
thread.sleep( usetime );
}else
{
thread.yield();
}
}catch( Exception e )
{
// System.out.println("exception"+e.getMessage());
e.printStackTrace();
quit();
}
}
}
}
上面的代码为原始的代码,经过编译为class,然后使用progruard混淆,然后使用jad反编译之后,变成这个样子:
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
import java.io.PrintStream;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.GameCanvas;
final class a extends GameCanvas
implements Runnable
{
private void a(int i, int j)
{
if(j == 1)
{
j = f;
f |= i;
if(j != f)
e = i;
return;
}
if(j == 2)
f &= ~i;
}
public a(contral contral)
{
super(false);
a = null;
b = null;
c = null;
d = 0;
e = 0;
f = 0;
g = null;
}
public final void a()
{
try
{
System.gc();
if(a != null)
a = null;
a a1;
(a1 = this).d = 0;
a1.g = Font.getFont(0);
a1.b = Image.createImage(a1.getWidth(), a1.getHeight());
a1.c = a1.b.getGraphics();
System.out.println("start init");
a = new Thread(this);
a.start();
return;
}
catch(Exception exception)
{
System.out.println("exception=" + exception.toString());
}
}
public final void b()
{
if(a != null)
a = null;
b = null;
c = null;
g = null;
}
public final void c()
{
if(a != null)
a = null;
System.gc();
}
protected final void keyRepeated(int i)
{
}
protected final void keyPressed(int i)
{
int j = -1;
switch(i)
{
case 48: // '0'
j = 1;
break;
case 49: // '1'
j = 2;
break;
case 50: // '2'
j = 4;
break;
case 51: // '3'
j = 8;
break;
case 52: // '4'
j = 16;
break;
case 53: // '5'
j = 32;
break;
case 54: // '6'
j = 64;
break;
case 55: // '7'
j = 128;
break;
case 56: // '8'
j = 256;
break;
case 57: // '9'
j = 512;
break;
case 35: // '#'
j = 1024;
break;
case 42: // '*'
j = 2048;
break;
case -1:
case 1: // '\001'
j = 4096;
break;
case -2:
case 6: // '\006'
j = 8192;
break;
case -3:
case 2: // '\002'
j = 16384;
break;
case -4:
case 5: // '\005'
j = 32768;
break;
case -5:
j = 0x10000;
break;
}
if(j != -1)
a(j, 1);
}
protected final void keyReleased(int i)
{
int j = -1;
switch(i)
{
case 48: // '0'
j = 1;
break;
case 49: // '1'
j = 2;
break;
case 50: // '2'
j = 4;
break;
case 51: // '3'
j = 8;
break;
case 52: // '4'
j = 16;
break;
case 53: // '5'
j = 32;
break;
case 54: // '6'
j = 64;
break;
case 55: // '7'
j = 128;
break;
case 56: // '8'
j = 256;
break;
case 57: // '9'
j = 512;
break;
case 35: // '#'
j = 1024;
break;
case 42: // '*'
j = 2048;
break;
case -1:
case 1: // '\001'
j = 4096;
break;
case -2:
case 6: // '\006'
j = 8192;
break;
case -3:
case 2: // '\002'
j = 16384;
break;
case -4:
case 5: // '\005'
j = 32768;
break;
case -5:
j = 0x10000;
break;
}
if(j != -1)
a(j, 2);
}
public final void paint(Graphics g1)
{
g1.setColor(0);
a a1;
g1.fillRect(0, 0, (a1 = this).getWidth(), (a1 = this).getHeight());
if(b != null)
g1.drawImage(b, 0, 0, 20);
}
public final void run()
{
Thread thread = Thread.currentThread();
_L2:
if(thread != a)
break; /* Loop/switch isn't completed */
long l = System.currentTimeMillis();
a a1;
(a1 = this).e = 0;
switch((a1 = this).d)
{
case 0: // '\0'
a1.c.setFont(a1.g);
a1.c.setColor(0xffffff);
a a2;
a1.c.fillRect(0, 0, (a2 = a1).b == null ? 0 : a2.b.getWidth(), (a2 = a1).b == null ? 0 : a2.b.getHeight());
a1.d = 1;
break;
}
repaint();
long l1 = System.currentTimeMillis() - l;
if((l1 = 50L - l1) > 0L)
Thread.sleep(l1);
else
Thread.yield();
continue; /* Loop/switch isn't completed */
JVM INSTR dup ;
Exception exception;
exception;
printStackTrace();
b();
if(true) goto _L2; else goto _L1
_L1:
}
private Thread a;
private Image b;
private Graphics c;
private int d;
private int e;
private int f;
private Font g;
}
修改run部分的语法,使之通过:
public final void run()
{
Thread thread = Thread.currentThread();
while(thread == a) {
try {
long l = System.currentTimeMillis();
a a1;
(a1 = this).e = 0;
switch((a1 = this).d)
{
case 0: // '\0'
a1.c.setFont(a1.g);
a1.c.setColor(0xffffff);
a a2;
a1.c.fillRect(0, 0, (a2 = a1).b == null ? 0 : a2.b.getWidth(), (a2 = a1).b == null ? 0 : a2.b.getHeight());
a1.d = 1;
break;
}
repaint();
long l1 = System.currentTimeMillis() - l;
if((l1 = 50L - l1) > 0L)
Thread.sleep(l1);
else
Thread.yield();
} catch(Exception e) {
}
}
}
原始的程序画了一个空空的白屏,我想在此基础上画一个红色方块,并在控制台打印出信息,所以修改了paint函数为:
public final void paint(Graphics g1) { g1.setColor(0); a a1; g1.fillRect(0, 0, (a1 = this).getWidth(), (a1 = this).getHeight()); if(b != null) g1.drawImage(b, 0, 0, 20); g1.setColor(0xFF0000); g1.fillRect(0, 0, 10, 10); System.out.println("patch!!!!"); }
然后编译修改后的程序,添加到jar包中。运行,你会发现没有任何问题。
PS:
这篇没有任何的新技术含量,拿出来轻松一下。但有些内容,说起来很简单,做起来却很难,就像java的反编译什么的,都没有任何可以提起的内容,但反编译之后的代码的复原,并非每个代码都是如此的简单,就像exe等程序的补丁一样,难度在于分析代码流程,添加补丁(汇编的话可能需要用汇编语言来添加),寻找插入点,堆栈平衡等地方。
:-),来看看~加油
写的时候没有多想,里面有些错误。改天验证完了再修改