アプリ版:「スタンプのみでお礼する」機能のリリースについて

この前、ネットでゲームをダウンロードしたのですが、遊ぶ目的ではなくソースファイルが見たかっただけだったのですがコンパイルされていたせいか削除されたせいか見れませんでした。
そこで、デコンパイル?というのを知りました。

コンパイルされたファイルをコンパイル前の状態に戻せるというのですが、本当でしょうか。

また、そのゲームファイルがどのように作られたのか知りませんので恐くてできません。
C(もしくはC++)言語で生成されたファイルなのか、Javaで生成されたファイルなのか分かりませんが そのどちらも通用するデコンパイルって知りませんか?

そのゲームのフォルダではありませんが、別のゲームのファイルにはmakeという名前のファイルがありました。

ご存知の方、教えてください。
よろしくお願いいたします。

A 回答 (9件)

> コンパイルされたファイルをコンパイル前の状態に戻せるというのですが、本当でしょうか。



無理です。
機械語からソースコードレベルの情報は引き出せません。

> makeという名前のファイルがありました。

統合開発環境を使ってしまうとお目にかかることのない Makefile 。これにはコンパイラ(やリンカ)に対して、プログラムを構成するために必要なファイルやコンパイルの順序、くっつけるライブラリの指定などさまざまなことが書いてあります。かなり大きな手がかりにはなりますが、Makefile からソースコードの中身がわかるわけではありません。
    • good
    • 0

そうそう、Javaはコンパイルすると中間コードになっているだけなので、比較的ソースに戻しやすいそうですね。

まったく無理ということでもないようです。ただ、コメント文などは一切無いので、読めるかどうかは疑問です。
    • good
    • 0
この回答へのお礼

ありがとうございます。
デコンパイルについての解説など、詳しい情報ありがとうございました。

お礼日時:2007/05/30 23:31

一般にムリ。

コンパイルとはすなわち非可逆変換ですから。
    • good
    • 0

* 一般にそういう行為は「リバースエンジニアリング」と呼ばれます。


* GPL等のようにライセンスで許可されているものもありますが、
* 大抵のソフト(特に製品)ではライセンス条項で禁止されていることが多いです。
* 当然、多くのソフトではソースなど公開していません。
* (ソース公開しているゲームを選んでダウンロードしたのでしょうか?)

> コンパイルされたファイルをコンパイル前の状態に戻せるというのですが、本当でしょうか。

嘘です。仮にデコンパイルしても完全な状態にはできません。
# C/C++よりは、Javaの方が多少マシな状態になりますが…
いずれにせよ、デコンパイルして解析するのは、
ソースを自分で書く以上に高度な知識や技術が必要だと思いますので、
素人/初心者にはまず活用できないような難易度になると思います。


JavaはVMとか要求されるので、ちょっと調べればJavaか否かは分かります。
(GCJとかはちょっと特殊ですが…まぁレアケースだと思うので)
せめてこのくらいは判断できないと、仮にデコンパイルできても有益な情報が得られないのでは…?

また、C/C++(ネイティブ)とJavaに共通のデコンパイルなどというものはまずないと思います。
両者の根本的な設計が違うので、同じ方法で対応しようというのがまず間違いなんじゃないかと。

> そのゲームのフォルダではありませんが、別のゲームのファイルにはmakeという名前のファイルがありました。

"Makefile"があるならまだ分かりますが、ゲームのフォルダに"make"ですか?
プログラミングに使うmakeとは同名の別物だったりしませんか?
# 「負け」ファイルだとか、キャラ作成(make)ファイルだとか…
    • good
    • 0
この回答へのお礼

そうなんですか・・・。
元のソースファイルは所有者からもらうということでしか 中身を見るのは不可能ということですね。

逆コンパイルして、ソースコードなど分裂できたらと思っていました。
いろいろと情報、ありがとうございました。

お礼日時:2007/06/04 23:47

面白そうな話題ですね.:)


自称・リバースエンジニアリング技術のホビー研究家 (笑) としては,
逆コンパイラについて書きたいことがたくさんありますが,キリがないので少しだけ….

世界中どこでも,「逆コンパイルは可能か?」という質問をする人がいて,
それに対しては「不可能」というのがお決まりの反応のようです.
そしてその理由もだいたい判で押したように
「コメントやローカル変数名は復元できないから.」
(まあ,誰でもすぐ思いつく理由だからでしょうが.)

「100%元通りのソース」なんてないものねだりをしても「不可能」の一言で
片付けられるのがオチなので,ここではどこまで元の構造を復元できる
(可能性がある) かについて考えてみます.
といっても,復元不可能とわかっている情報 (コメントやローカル変数名など)
についてはスッパリあきらめて,復元可能な情報をいかに読みやすい形で表示
するか (元通りでなくても読みやすければよい) という目標にすり替えます.

以下,その前提でC逆コンパイルについて,かなり私の推測・主観入りの難易度判定です.
(今思いついたものもいくつかあるので,正確さは全く保証しません.)

・完全に復元不可能な情報の例
 ・コメント
 ・外部リンケージを持たない変数名・関数名
  (auto 変数名,static 変数名/関数名など)
 ・構造体や共用体のメンバ名
 ・構造体や共用体の,全く使用されていないメンバのデータ型.
 ・マクロ定義

・復元超容易
 ・外部リンケージを持つ変数名・関数名.
  (extern "C" 宣言されていない C++ の識別子であれば,型名も復元可能.)

・復元わりと容易
 ・基本データ型の変数.
 ・式,関数呼び出し.

・復元やや難
 ・構造体定義
  一ヶ所で一つの構造体のすべてのメンバにアクセスしているコードがあれば
  復元は楽だが,一般にはプログラムのあちこちで異なるメンバにアクセスする
  ことになるので,プログラム全体でそれを調べて情報を総合する必要がある.

・復元難
 ・制御構造.下手をすると goto 文だらけのソースになる.逆にうまくいけば,
  元のプログラムの制御構造にもよるが,スパゲッティ化していたものが
  整理される場合もありうる.制御フローグラフ解析アルゴリズムの出来次第 (たぶん).

・復元超困難
 ・共用体 (またはキャストしたポインタでアクセスされる変数)
  コードのどの範囲でどのデータ型としてアクセスしているのか判別が必要.
  データフロー解析,制御フロー解析両面からのアプローチが必要 (たぶん).

・復元不可能? (決定不可能問題?)
 ・ポインタ演算を使うポインタによるアクセス
  ポインタが参照するメモリ領域の1次元的構造を復元する必要があるが,
  動的に変化する可能性もあるので一般には決定不可能?
 ・関数ポインタ「だけ」を使って呼び出される static 関数
  関数ポインタの値を解析時に決定できなければ,static 関数のエントリ
  ポイントが見つけられない (したがってその関数は逆コンパイル対象にさえ
  ならない) … かも.

・復元可能であっても,ソースの表現方法が一義的に定まらないもの.
 ・例:あるメモリ領域が複数の異なるデータ型としてアクセスされている場合,
  それを共用体として表現するか,それともポインタのキャストで表現するか.


Cに比べて Java ははるかに逆コンパイルが容易で,1996 年に (たぶん) 初の
Java 逆コンパイラ Mocha が公開されて以来,多数の Java 逆コンパイラが公開
されています.

Java Decompilers
http://www.program-transformation.org/Transform/ …

Javaコードを守る方法 (あるいは他人のJavaコードを参照する方法)
 Javaコードの逆コンパイルおよび曖昧化の完全ガイド
http://www-06.ibm.com/jp/developerworks/java/011 …


Java 逆コンパイラはかなり元のソースを復元できるので,これを脅威に感じた
Java 業界はさまざまな逆コンパイラ対策ツールを有償・無償で提供しています.

Java が逆コンパイルされやすい原因として,CPU 独立なバイトコードを採用して
いるからだとか,オブジェクト指向だから (失笑) だとか説明しているサイトを
見かけたことがあります.しかしこれらはまったく的外れで,私は次のような
Java の言語仕様に原因があると思っています.
(と偉そうに書くほど Java を熟知しているわけではないので,違っていたら指摘してください.)

・共用体が存在しない.
・ポインタ演算が禁止されている.
・ポインタのキャストがかなり制限されている.(アップキャストとダウンキャストだけ?)

(以上3つを上に書いたCの場合と比較してください.)

それと,

・クラスファイルの中に,クラス (構造体) 定義情報が含まれている.

じゃ,クラスファイルからそれを削除すればいいじゃん,と思うかもしれませんが,
(多分) そうはいきません.(それができるなら上記の対策ツールがやっているはず.)

その理由は,

・構造体 (インスタンス) のどの位置にどういう型のポインタ (クラス型変数) が
 あるかわからないと,ガーベージコレクタが困る … つまり,オブジェクト間の
 トラバースができなくなる (はず).

・クラス定義情報がないと,Java のリフレクション機能が使えなくなる (はず).


以上書いたように,Cの逆コンパイラは Java に比べて遙かに実現困難で,
実用レベルのものは存在しないはずですが,研究レベルとしては多分 dcc が有名 … かな?
(Mocha の作者も dcc の論文を参考にしたらしい.)

Dcc Decompiler
http://www.program-transformation.org/Transform/ …

それから,マルウェア解析のために dcc を改造して Win32 PE 実行形式を
解析可能にした ndcc というのもあるようです.

History Of Decompilation 3
 ndcc decompiler, 2002.
http://www.program-transformation.org/Transform/ …



Machine Code Decompilers
http://www.program-transformation.org/Transform/ …

The Decompilation Wiki
http://www.program-transformation.org/Transform/ …


あぁ,少しだけのつもりだったのに….orz
    • good
    • 3

C++で外部リンケージがあったとしても、


コンパイラ毎にマングリングも違うわけで、
まずコンパイラを特定しないと話になりませんから、
JavaとC/C++を同じ方法、なんてツールは普通作られないわけですが。

構造体情報は実行時、基本的に唯のアドレスのオフセットにしか展開されないわけで、
この時点で構造体だったのか、唯のオフセット参照(+キャスト)だったのかは
(予測して/統計から倒していくことはできるとしても)
コンパイラのコードの癖等に頼らないと区別できないわけですが。
# と、またここでもコンパイラを特定することが前提に。

関数ポインタ経由であろうが、static関数であろうが、
(最適化で削除されずに)コード領域に残っているなら、
コード領域を舐めれば対象にできますし。(読み取るのは解析者の仕事では)
# 実行時に動的にコードを生成してて、(生成処理ではなく)
# 「生成された処理」を解析しろってのなら無理でしょうけど。

# どこまでツールでやってくれるのかにもよるわけですが、
# templateの特殊化とかつらそうだ。
    • good
    • 0
この回答へのお礼

私は、まだC++もCもJAVAも一切 手をつけていないのです。
付ける前に、逆コンパイルができるかできないか知りたかったのですが、この様子じゃできそうになりせんね・・・。

ありがとうございました。

お礼日時:2007/06/04 23:50

#5 です.長くなるので,複数回に分けて書きます.



#6 さん

> C++で外部リンケージがあったとしても、
> コンパイラ毎にマングリングも違うわけで、
> まずコンパイラを特定しないと話になりませんから、

はい,mungling については,原理的な復元の難易度について述べたもので,
実際には前提としてコンパイラの特定が必要になるのはおっしゃるとおりです.
(既知の複数のコンパイラ用の demungler を用意しておいて,
 片っ端から試すことによりコンパイラを特定するという方法も考えられますが.)

とは言うものの,C++ の逆コンパイラは,C の逆コンパイラと比べても
はるかに絶望的だと思います.最大の理由 (だと思うの) は,#5 でも書いた
「ソースの表現方法が一義的に定まらない」情報が C に比べて圧倒的に多いため.
例えば,祖先クラスを持つクラスが使用しているメンバ変数やメンバ関数を,
どのクラスに配置すべきか,いやそもそもどうクラスを分割すべきか … など.

それと,

> # templateの特殊化とかつらそうだ。

これも超難問ですね.テンプレートを特殊化した複数のコードがあったとして,
それらが同じテンプレートに由来するということを認識するには,類似したコード
パターンを検出する必要がありますが,「同じ」ではなく「類似」というのがコン
ピュータの最も苦手とするところ.しかもそのパターンは事前にはわからない.
仮に力ずくでやるとしても膨大な時間がかかる.
一般にはテンプレートはマクロと同様,復元不可能だと思います.

したがって,仮に自称 C++ 逆コンパイラがあって,処理内容を復元できたとしても,
C に毛の生えたようなソースを生成するのが関の山だろうと思います.
    • good
    • 0

> 構造体情報は実行時、基本的に唯のアドレスのオフセットにしか展開されないわけで、


> (中略)
> # と、またここでもコンパイラを特定することが前提に。

色々な実行ファイル形式やコンパイラの出力を調べたわけではないので
かなり推測混じりですが…

構造体がポインタを使わずにアクセスされている場合には,複数の基本型変数が
構造体メンバなのか,それともバラ売りのスカラ型なのかの判別は困難だと思います.
ただ,処理内容が変わるわけではないので,これは #5 に書いたソースの表現方法の
曖昧さの問題ですね.だから実用上はデフォルトでバラ売りを選んでおいて,ユーザ
の指示があれば構造体にまとめる,という方法がいいのではないかと思います.

他方,構造体がポインタでアクセスされている場合には復元しやすいと思います.
それは必ずしも (ベースアドレス+オフセット) のようなアドレシングモード
のメモリアクセス命令でなくてもかまいません.
メモリアクセス命令があれば,その参照アドレスのデータフローを逆にたどり,
(ベースアドレス+オフセット) という演算がなされていれば構造体メンバ
アクセスだと見なしてメモリマップに書き込みます.これをプログラム全体
について行えば構造体が復元できると思います.
(その過程で共用体やポインタのキャストが発見される場合もありますが.
あ,それと,構造体配列の可能性を考えるともっと厄介になりそう….orz)


> 関数ポインタ経由であろうが、static関数であろうが、
> (最適化で削除されずに)コード領域に残っているなら、
> コード領域を舐めれば対象にできますし。(読み取るのは解析者の仕事では)

はい,既存の逆アセンブラではそうやっているものもありますよね.
それだと,その関数単独 (およびその関数が呼び出している関数群) の
解析はできるので,#5 で書いたような見逃しはなくなります.
それでも次のような問題が残ります.(今,気がつきました.)

実行ファイルのエントリポイント (≒main() … 本当はスタートアップルーチン
だけど) を出発点とする制御フロー/データフロー解析を行うに当たり,
そのような関数 (とりあえず,ここでは孤児と呼ぶことにします) については
関数間の呼び出し関係をたどるグローバルなフロー解析ができません.
したがって構造体や共用体の復元に支障が出る場合があります.一例を挙げると,
孤児とその呼び出し元の間で同じ構造体や共用体にアクセスしていながら,
一方のみが使用しているメンバがある場合です.例えば,


元のソース -----------------------------------------------------------------

struct A {
  double a1; // size=alignment=8,offset=0 とする.
  char a2; // size=alignment=1,offset=8 とする.
}; // sizeof(struct A)=16

static void bar(struct A *pA);

// フロー解析の対象になっている関数
// (逆アセンブラは,これが main() から直接間接に呼び出されること,
// そしてその呼び出し方 (引数の型など) を知っている.)
void foo(…)
{
  struct A a;
  void (*f)(struct A*);

  // ここで色々演算して,最終的な f の値は bar になるが,
  // 逆コンパイラにはそれがわからないものとする.
  // また,この関数内では a.a2 には一切アクセスしないものとする.

  (*f)(&a); // 逆コンパイラには,これが bar を呼んでいることがわからない.
  :
  :
}

// フロー解析の対象外の関数 (孤児)
// (逆コンパイラは,これがどこからどのように呼ばれているのかわからない.)
static void bar(struct A *pA)
{
  // この関数 (およびそれが呼び出す関数) では,
  // pA->a1 には一切アクセスしないものとする.
  :
  :
  pA->a2 = 'A';
  :
  :
}

逆コンパイルされたソース (最大限の可能性を想定した場合) --------------------

// main() を始点とするグローバルフロー解析により復元された struct A.
struct Struct1 {
  double Struct1_Member1; // (struct A)::a1
  char Struct1_Unused1[8]; // 8バイトの未使用領域と誤認された.
};

// bar() を始点とするグローバルフロー解析により復元された struct A.
// 逆コンパイラには Struct1 と同一であることがわからない.
struct Struct2 {
  char Struct2_Unused1[8]; // 8バイトの未使用領域と誤認された.
  char Struct2_Member1; // (struct A)::a2
  char Struct2_Unused2[7]; // struct A 末尾のパディング (7バイト)
};

void foo(…)
{
  struct Struct1 var1; // a
  void (*var2)(struct Struct1 *); // f

  :
  :
  (*var2)(&var1);
  :
  :
}

// bar
static void StaticOrphanFunc1(struct Struct2 *arg1)
{
  :
  :
  arg1->Struct2_Member1 = 'A';
  :
  :
}

----------------------------------------------------------------------------

このように,(*var2)(&var1) の呼び出し先がわからなければ,元の struct A に
対して Struct1 と Struct2 の2つの構造体定義ができてしまいます.

もし逆コンパイラのユーザが (*var2)(&var1) が StaticOrphanFunc1 を呼び出して
いることを教えてやれば,(*var2)(&var1) と StaticOrphanFunc1 の引数を照合
することによって Struct1 と Struct2 が同一であることがわかるため,両者を
マージして

struct Struct3 {
  double Struct3_Member1; // (struct A)::a1
  char Struct3_Member2; // (struct A)::a2
  char Struct3_Unused1[7]; // struct A 末尾のパディング (7バイト)
};

のように復元できるはずです.ただしマージ中に衝突 (同じアドレスが異なる
データ型としてアクセスされている) が発見された場合,共用体またはポインタ
のキャストということになるので,#5 で書いたように厄介なことになります.
    • good
    • 0

> # 実行時に動的にコードを生成してて、(生成処理ではなく)


> # 「生成された処理」を解析しろってのなら無理でしょうけど。

逆コンパイラをマルウェア解析に利用する場合,
リバースエンジニアリング対策を講じているものもあるそうですから,
そういうケースに出くわすことも多いかもしれませんね.

# もしかしたら逆コンパイラの最大ユーザは,マルウェア対策ソフトの開発元かも….(単なる憶測)
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!