私はバイトコード分析を使用して、クラスファイルのすべてのインポートされたクラスを取得しています(BCELを使用)。ここで、定数プールを読み取ると、インポートされたすべてのクラスがCONSTANT_Class(仕様を参照)として表示されるのではなく、CONSTANT_Utf8としてのみ表示されます。今の私の質問:インポートされたファイルを読み取るために、定数プールのCONSTANT_Class-entriesだけに依存することはできませんか?クラス名かどうか、本当にすべてのエントリを見て推測する必要がありますか?これもすべての状況で正しいとは限りません。または、バイトコード全体を読み取る必要がありますか?よろしく
2 に答える
いいえ、CONSTANT_Class_infoエントリのみを使用して、他のクラス/インターフェイスへの依存関係を検出することは正しくありません。信頼できる入力ファイルを解析している場合、または誤った情報を許容できる場合は、1つのコーナーケースを除いて、定数プールの解析のみを回避できます。任意の入力に関する正確な情報を取得するには、クラスファイル全体を解析する必要があります。(「依存関係」とは、 JVMの第5章で説明されているように、クラスをロードまたはリンクしないと例外が発生する可能性があるクラスまたはインターフェイスを意味すると思います。これには、Class.forName
または他の反射手段を介して取得されたクラスは含まれません。)
次のクラスを考えてみましょう。
public class Main {
public static void main(String[] args) {
identity(null);
}
public static Object identity(Foo x) {
return x;
}
}
javap -p -v Main.class
プリント:
Classfile /C:/Users/jbosboom/Documents/stackoverflow/build/classes/Main.class
Last modified Jul 2, 2014; size 346 bytes
MD5 checksum 2237cda2a15a58382b0fb98d6afacc7e
Compiled from "Main.java"
public class Main
SourceFile: "Main.java"
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#17 // java/lang/Object."<init>":()V
#2 = Class #18 // Main
#3 = Class #19 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 LMain;
#11 = Utf8 identity
#12 = Utf8 (LFoo;)Ljava/lang/Object;
#13 = Utf8 x
#14 = Utf8 LAAA;
#15 = Utf8 SourceFile
#16 = Utf8 Main.java
#17 = NameAndType #4:#5 // "<init>":()V
#18 = Utf8 Main
#19 = Utf8 java/lang/Object
#20 = Utf8 java/lang/Thread
#21 = Class #20 // java/lang/Thread
#21 = Utf8 (LBar;)LFakename;
{
public Main();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LMain;
public static java.lang.Object identity(Foo);
descriptor: (LFoo;)Ljava/lang/Object;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: areturn
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 x LAAA;
}
Foo
メソッドのパラメータとして参照されるクラスidentity
は、定数プールにCONSTANT_Class_infoエントリとして表示されません。identity
(エントリ#12)のメソッド記述子に表示されます。フィールド記述子は、CONSTANT_Class_infoエントリとして表示されないクラスを参照する場合もあります。したがって、定数プールのみからすべての依存関係を見つけるには、すべてのUTF8エントリを調べる必要があります。
コーナーケース:CONSTANT_String_infoエントリによって参照されるUTF8エントリが存在する場合があります。重複するUTF8エントリがマージされるため、1つのUTF8エントリは、メソッド記述子、文字列リテラル、またはその両方である可能性があります。定数プールのみを解析している場合は、このあいまいさを抱えて生きる必要があります(おそらく、それを過度に近似し、依存関係として扱うことによって)。
入力が適切に動作するJavaコンパイラによって生成されたものであると信頼できる場合は、文字列のコーナーケースに注意して、すべてのUTF8エントリを解析し、ここで読むのをやめることができます。攻撃者がツールの手作りクラスファイルをフィードするのを防ぐ必要がある場合(たとえば、逆コンパイラーを作成していて、攻撃者が逆コンパイルを防止したい場合)、クラスファイル全体を解析する必要があります。潜在的な問題の例をいくつか示します。
- エントリ#20は、によって使用されないクラスを指定し
Main
ます。JVMは、この参照を解決しようとする場合としない場合があります(JVMS 5.4では、遅延読み込みと熱心な読み込みの両方が許可されます)。どちらの方法でもクラスが存在するため、エラーは発生しません。したがって、この余分なエントリは無害ですが、一定のプールを調べているツールをだまして、スレッドが依存関係であると考えさせます。 - エントリ#21は、2つの架空のクラスを参照する未使用のメソッド記述子です。この記述子は使用されないため、エラーは発生しませんが、定数プールを信頼するツールがそれを解析します。
- エントリ#14は、架空のクラスを参照するフィールド記述子です。このエントリは実際にはLineNumberTable属性によって使用されますが、このデバッグ情報はJVMによってチェックされないため、参照は無害ですが、ツールをだます可能性があります。
- この例はありませんが、InnerClasses属性はCONSTANT_Class_infoエントリを参照しており、他のクラスファイルとの整合性がチェックされていません(JVMS 4.7.6によると、非規範的な注記ですが)。これらの参照は、読み込みやリンクを妨げることはありませんが、定数プールを調べるツールを混乱させる可能性があります。
それは私が頭のてっぺんから思いついたものです。細かい櫛でJVMを通過する巧妙な攻撃者は、使用されているように見えるが使用されていないエントリを定数プールに追加する場所をさらに見つける可能性があります。攻撃者に直面しても正確な情報が必要な場合は、クラスファイル全体を解析し、JVMがそれをどのように使用するかを理解する必要があります。
JVMS 4.2、FQクラスおよびインターフェース名の内部形式を参照してください。
簡単に言うと、クラス構造はUTF8エントリを指します。
(または、参照されているすべてのクラスがクラスと名前のエントリで表されているわけではないと言っているのですか?)
FWIW、クラスは動的にロードでき、まったく表示されない可能性があるため、依存関係を判断するためにこの情報のみに依存することに注意してください。