13

Javaでクラスをロードし、クラスのパッケージ名/正規名を「偽造」することは可能ですか?当然の方法でこれを試してみましたが、「クラス名が一致しません」というメッセージが表示されますClassDefNotFoundException

これを行う理由は、リフレクションを使用せずに直接使用できるように、デフォルトパッケージで記述されたAPIをロードしようとしているためです。コードは、パッケージとパッケージ名のインポートを表すフォルダー構造のクラスに対してコンパイルされます。すなわち:

./com/DefaultPackageClass.class
// ...
import com.DefaultPackageClass;
import java.util.Vector;
// ...

私の現在のコードは次のとおりです。

public Class loadClass(String name) throws ClassNotFoundException {
    if(!CLASS_NAME.equals(name))
            return super.loadClass(name);

    try {
        URL myUrl = new URL(fileUrl);
        URLConnection connection = myUrl.openConnection();
        InputStream input = connection.getInputStream();
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        int data = input.read();

        while(data != -1){
            buffer.write(data);
            data = input.read();
        }

        input.close();

        byte[] classData = buffer.toByteArray();

        return defineClass(CLASS_NAME,
                classData, 0, classData.length);

    } catch (MalformedURLException e) {
        throw new UndeclaredThrowableException(e);
    } catch (IOException e) {
        throw new UndeclaredThrowableException(e); 
    }

}
4

3 に答える 3

17

Pete が述べたように、これは ASM バイトコード ライブラリを使用して実行できます。実際、そのライブラリには、これらのクラス名の再マッピングを処理するための特別なクラスが付属しています ( RemappingClassAdapter)。このクラスを使用したクラスローダーの例を次に示します。

public class MagicClassLoader extends ClassLoader {

    private final String defaultPackageName;

    public MagicClassLoader(String defaultPackageName) {
        super();
        this.defaultPackageName = defaultPackageName;
    }

    public MagicClassLoader(String defaultPackageName, ClassLoader parent) {
        super(parent);
        this.defaultPackageName = defaultPackageName;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        byte[] bytecode = ...; // I will leave this part up to you
        byte[] remappedBytecode;

        try {
            remappedBytecode = rewriteDefaultPackageClassNames(bytecode);
        } catch (IOException e) {
            throw new RuntimeException("Could not rewrite class " + name);
        }

        return defineClass(name, remappedBytecode, 0, remappedBytecode.length);
    }

    public byte[] rewriteDefaultPackageClassNames(byte[] bytecode) throws IOException {
        ClassReader classReader = new ClassReader(bytecode);
        ClassWriter classWriter = new ClassWriter(classReader, 0);

        Remapper remapper = new DefaultPackageClassNameRemapper();
        classReader.accept(
                new RemappingClassAdapter(classWriter, remapper),
                0
            );

        return classWriter.toByteArray();
    }

    class DefaultPackageClassNameRemapper extends Remapper {

        @Override
        public String map(String typeName) {
            boolean hasPackageName = typeName.indexOf('.') != -1;
            if (hasPackageName) {
                return typeName;
            } else {
                return defaultPackageName + "." + typeName;
            }
        }

    }

}

説明のために、デフォルト パッケージに属する 2 つのクラスを作成しました。

public class Customer {

}

public class Order {

    private Customer customer;

    public Order(Customer customer) {
        this.customer = customer;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

}

これは、再マッピングOrder のリストです。

> javap -private -c 注文
「Order.java」からコンパイル
public class Order extends java.lang.Object{
個人顧客の顧客。

公序(顧客);
  コード:
   0: aload_0
   1: 特別な #10 を呼び出します。//メソッド java/lang/Object."":()V
   4: aload_0
   5: aload_1
   6: putfield #13; //フィールドの顧客:LCustomer;
   9: 戻る

パブリック カスタマー getCustomer();
  コード:
   0: aload_0
   1: getfield #13; //フィールドの顧客:LCustomer;
   4: 戻る

public void setCustomer(顧客);
  コード:
   0: aload_0
   1: aload_1
   2: putfield #13; //フィールドの顧客:LCustomer;
   5: 戻る

}

これは再マッピングOrder のリストです(com.mycompanyデフォルトのパッケージとして使用):

> javap -private -c 注文
「Order.java」からコンパイル
public class com.mycompany.Order extends com.mycompany.java.lang.Object{
プライベート com.mycompany.Customer 顧客;

パブリック com.mycompany.Order(com.mycompany.Customer);
  コード:
   0: aload_0
   1: 特別な #30 を呼び出します。//メソッド "com.mycompany.java/lang/Object"."":()V
   4: aload_0
   5: aload_1
   6: putfield #32; //フィールドの顧客:Lcom.mycompany.Customer;
   9: 戻る

public com.mycompany.Customer getCustomer();
  コード:
   0: aload_0
   1: getfield #32; //フィールドの顧客:Lcom.mycompany.Customer;
   4: 戻る

public void setCustomer(com.mycompany.Customer);
  コード:
   0: aload_0
   1: aload_1
   2: putfield #32; //フィールドの顧客:Lcom.mycompany.Customer;
   5: 戻る

}

ご覧のとおり、再マッピングによって へのすべての参照と へのすべての参照が変更されていOrderます。com.mycompany.OrderCustomercom.mycompany.Customer

このクラスローダーは、次のいずれかのすべてのクラスをロードする必要があります。

  • デフォルトのパッケージに属している、または
  • デフォルト パッケージに属する他のクラスを使用します。
于 2010-05-25T00:00:17.630 に答える
1

ASMで何かをノックアップできるはずですが、ロード時ではなくビルド時にパッケージの名前を変更する方が簡単です。

于 2010-05-24T15:38:11.523 に答える
0

API をデフォルトのパッケージからより適切な場所に移動する方が簡単ではないでしょうか? ソースコードにアクセスできないようです。パッケージがクラス ファイルにエンコードされているかどうかわからないので、単に API クラスを移動するだけでも試してみる価値があるかもしれません。それ以外の場合は、通常、JAD などの Java 逆コンパイラが適切に機能するため、逆コンパイルされたソースのパッケージ名を変更して、再度コンパイルすることができます。

于 2010-05-24T14:45:56.107 に答える