一步一步:java文件的反编译与修补

处于种种原因,很多提供的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等程序的补丁一样,难度在于分析代码流程,添加补丁(汇编的话可能需要用汇编语言来添加),寻找插入点,堆栈平衡等地方。

2条评论

回复 admin 取消回复

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据