>> i's SQUARE >> Javaのノウハウ >> Javaの逆コンパイル

Javaのリバースエンジニアリング(逆コンパイル)



■逆コンパイル【decompile】について

逆コンパイルとは、機械語で書かれたオブジェクトコードを、 コンパイラ型言語によるソースコードに変換することです!
そのためのソフトウェアは逆コンパイラと呼ばれています!

苦労して作ったアプリケーションのソースコードが、 配付した Javaバイトコード(.class, .jar)から簡単に再現できてしまうし、 コードをCrackしてアプリケーションなどのデモ版にパッチを当てて 製品と同様にしてしまう事がリバースエンジニアリングで簡単に実現できたりと、 著作権保護などの理由から、利用規定などで逆コンパイルを禁じているソフトウェアも少なくないです。

しかし、逆コンパイラは、バイトコードからソースコードへ変換するときに、 元のソースファイルよりもスリムになっている場合があり最適化にも利用される事があります!

*逆コンパイルの詳細については このページ がたいへん参考になります!


■Javaの逆コンパイラ(σ・∀・)σゲッツ!

Jad - the fast JAva Decompiler


■Javaの逆コンパイラの使い方

サンプルProgram(JdbcApplet.java

import java.sql.*;
import java.awt.*;
import java.io.*;
import java.util.*;
import java.applet.*;

public class JdbcApplet extends Applet{
  static final String driver_class = "oracle.jdbc.driver.OracleDriver";
  static final String connect_string = "jdbc:oracle:thin:@tmp01:1525:D2K";
  String query = "select distinct po_number from po_headers";
  String c1;

  Button execute_button, ok;
  TextArea output;
  Connection conn;
  TextField  tf1,tf2;

  public void init (){
    this.setLayout (new BorderLayout ());
    Panel p = new Panel ();
    Panel pp= new Panel ();
    //Panel ppp= new Panel ();
    p.setLayout (new FlowLayout (FlowLayout.LEFT));
    execute_button = new Button ("SQL");
    ok = new Button ("更新");
    tf1 = new TextField("",40);
    tf2 = new TextField("",40);
    p.add (tf1);
    p.add (execute_button);
    pp.add (tf2);
    pp.add (ok);
    this.add ("North", p);
    this.add ("South", pp);
    output = new TextArea (10, 60);
    this.add ("Center", output);
  }

  public boolean action (Event ev, Object arg){
    if (ev.target == execute_button){
      try{
        // Clear the output area
        output.setText (null);
        // See if we need to open the connection to the database
        if (conn == null)
        {
          // Load the JDBC driver
          output.appendText ("JDBCドライバをロードしています・・\n");
          Class.forName (driver_class);

          // Connect to the databse
          output.appendText ("データベースに接続中・・・ \n");
          conn = DriverManager.getConnection (connect_string,"test0102","test0102");
          output.appendText ("データベースに接続しました!\n\n");
        }

        // Create a statement
        Statement stmt = conn.createStatement ();

        query = tf1.getText();
        // Execute the query
        output.appendText ("SQL: " + query + "\n");
        ResultSet rset = stmt.executeQuery (query);

        // Dump the result
        while (rset.next ()){
          c1 = rset.getString(1);
          output.appendText ("  " +c1 +"\n");
        }
        // We're done
        output.appendText ("以上が結果です!\n");
      }
      catch (Exception e)
      {
        // Oops
        output.appendText (e.getMessage () + "\n" +e);
      }
      return true;
    }
    else
      return false;
  }
}

まずサンプルプログラムをコマンドプロンプト上からコンパイルしてソースファイルから実行モジュールに変換します!

>javac -nowarn JdbcApplet.java

コンパイルが成功すると JdbcApplet.class(オブジェクト) が作成されます。
このオブジェクトに対してJADを使い逆コンパイルします〜☆

>jad -8 JdbcApplet.class

逆コンパイルが成功すると JdbcApplet.jad(テキストファイル) が作成されます!
因みにプログラムの中で日本語を使っている場合はオプションで「-8」を指定します。
Javaの文字コードはUnicodeなので、逆コンパイル時に「-8」を指定しないと 日本語が文字化けを起こしてしまうからです!(正確には文字化けではなくてUnicodeのままで出力されます)

それでは、逆コンパイルされた「JdbcApplet.jad」を見てみます!

逆コンパイラで生成されたコード(JdbcApplet.jad

// Decompiled by Jad v1.5.7a. Copyright 1997-99 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/SiliconValley/Bridge/8617/jad.html
// Decompiler options: packimports(3) ansi
// Source File Name:   JdbcApplet.java

import java.applet.Applet;
import java.awt.*;
import java.sql.*;

public class JdbcApplet extends Applet
{

    public JdbcApplet()
    {
        query = "select distinct po_number from po_headers";
    }

    public void init()
    {
        setLayout(new BorderLayout());
        Panel panel = new Panel();
        Panel panel1 = new Panel();
        panel.setLayout(new FlowLayout(0));
        execute_button = new Button("SQL");
        ok = new Button("更新");
        tf1 = new TextField("", 40);
        tf2 = new TextField("", 40);
        panel.add(tf1);
        panel.add(execute_button);
        panel1.add(tf2);
        panel1.add(ok);
        add("North", panel);
        add("South", panel1);
        output = new TextArea(10, 60);
        add("Center", output);
    }

    public boolean action(Event event, Object obj)
    {
        if(event.target == execute_button)
        {
            try
            {
                output.setText(null);
                if(conn == null)
                {
                    output.appendText("JDBCドライバをロードしています・・\n");
                    Class.forName("oracle.jdbc.driver.OracleDriver");
                    output.appendText("データベースに接続中・・・ \n");
                    conn = DriverManager.getConnection("jdbc:oracle:thin:@kux02:1525:D2K", "itr0102", "itr0102");
                    output.appendText("データベースに接続しました!\n\n");
                }
                Statement statement = conn.createStatement();
                query = tf1.getText();
                output.appendText("SQL文: " + query + "\n");
                for(ResultSet resultset = statement.executeQuery(query); resultset.next(); output.appendText("\t" + c1 + "\n"))
                    c1 = resultset.getString(1);

                output.appendText("以上がSQL文の結果です!\n");
            }
            catch(Exception exception)
            {
                output.appendText(exception.getMessage() + "\n" + exception);
            }
            return true;
        } else
        {
            return false;
        }
    }

    static final String driver_class = "oracle.jdbc.driver.OracleDriver";
    static final String connect_string = "jdbc:oracle:thin:@kux02:1525:D2K";
    String query;
    String c1;
    Button execute_button;
    Button ok;
    TextArea output;
    Connection conn;
    TextField tf1;
    TextField tf2;
}

ほぼ完全に復元されることが確認できたと思います。そしてコードが最適化されています。
更にびっくりなのは変数名すら完全に復元している点です!
今後はソースを公開しないからといってローカル変数以外の変数についてはコードに変な変数名はつけられないですねw


運命は、それが甘美なときも、苦がいときも
それを私は好ましい糧として受け取ろう!
by ヘッセ