ちょ、ワタシ、技術者っぽくね!?

いやぁ、なやんだわぁw
というわけで、私的技術メモ。

※内容の正誤も含め、これらを利用したことによる被害について、管理人は一切の責任を負いません。
まぁ、つまり間違っている可能性も多々ありますので、その点はご留意ください…。

★リフレクションのアセンブリロード関数の制限について。

アセンブリを読み込む関数に、生バイトを流し込むオーバーロード関数があります。

System.Reflection.Assembly.Load
http://msdn2.microsoft.com/ja-jp/library/system.reflection.assembly.load(VS.80).aspx

や、

System.AppDomain.Load
http://msdn2.microsoft.com/ja-jp/library/system.appdomain.load(VS.80).aspx

の、第一引数にバイト配列を受け取る関数が該当します。

これらは、アセンブリのファイルバイナリデータをそのまま渡して、ロードするというもの。
ぉ、ということは!

ファイルアーカイバデコーダーを作る

アーカイブからアセンブリDLLをバイナリデータをして取得

バイナリデータをそのままアセンブリとしてロード

…つまり、アーカイブファイル内のDLLをメモリ内で展開し、直接読み込めるので、
複数のDLLがひとまとめにでき、スッキリ( ゚Д゚)ウマウマー

と、思ったので、やってみるテスト。
まずは、ファイルストリームから直読み込みで。

System::IO::FileInfo^ infFileIO = gcnew System::IO::FileInfo(“infinity.FileIO.dll”);
System::IO::FileStream^ streamFileIO = gcnew System::IO::FileStream(infFileIO->FullName, System::IO::FileMode::Open, System::IO::FileAccess::Read, System::IO::FileShare::Read);

// バイトを読み込む
array^ bytesFileIO = gcnew array(streamFileIO->Length);
streamFileIO->Seek(0, System::IO::SeekOrigin::Begin);
streamFileIO->Read(bytesFileIO, 0, bytesFileIO->Length);

// ストリームを閉じる
streamFileIO->Close();

// 読み込むよ
assemblyFileIO = System::AppDomain::CurrentDomain->Load(bytesFileIO);

…実行時にこんなエラーが。

System.IO.FileLoadException が発生しました。
Message=”確認不可能なコードによるポリシー チェックが失敗しました。 (HRESULT からの例外: 0x80131402)”
Source=”mscorlib”
StackTrace:
場所 System.Reflection.Assembly.nLoadImage(Byte[] rawAssembly, Byte[] rawSymbolStore, Evidence evidence, StackCrawlMark& stackMark, Boolean fIntrospection)
場所 System.AppDomain.Load(Byte[] rawAssembly)
場所 infinity.Core.infinityCore.PluginManager..ctor(infinityCore objCore)

Σ(´Д`lll)ナンデストー
というわけで、テキトーに証拠を自前で指定してみりゅ。

System::IO::FileInfo^ infFileIO = gcnew System::IO::FileInfo(“infinity.FileIO.dll”);
System::IO::FileStream^ streamFileIO = gcnew System::IO::FileStream(infFileIO->FullName, System::IO::FileMode::Open, System::IO::FileAccess::Read, System::IO::FileShare::Read);

// バイトを読み込む
array^ bytesFileIO = gcnew array(streamFileIO->Length);
streamFileIO->Seek(0, System::IO::SeekOrigin::Begin);
streamFileIO->Read(bytesFileIO, 0, bytesFileIO->Length);

// SHA1を計算
System::Security::Cryptography::SHA1Managed^ shaFileIO = gcnew System::Security::Cryptography::SHA1Managed();
streamFileIO->Seek(0, System::IO::SeekOrigin::Begin);
shaFileIO->ComputeHash(streamFileIO);

// 証拠
System::Security::Policy::Evidence^ evidenceFileIO = gcnew System::Security::Policy::Evidence();

evidenceFileIO->AddHost(gcnew System::Security::Policy::Zone(System::Security::SecurityZone::MyComputer));
evidenceFileIO->AddHost(gcnew System::Security::Policy::Url(“file://” + infFileIO->FullName));
evidenceFileIO->AddAssembly(System::Security::Policy::Hash::CreateSHA1(shaFileIO->Hash));

// ストリームを閉じる
streamFileIO->Close();

// 読み込むよ
// ちなみに、第2引数はデバッグシンボルのバイナリを流し込むことができる(*.pdbのバイナリ)
assemblyFileIO = System::AppDomain::CurrentDomain->Load(bytesFileIO, nullptr, evidenceFileIO);

実行してみよう。

System.IO.FileLoadException が発生しました。
Message=”Attempt to load an unverifiable executable with fixup を含む確認できない実行可能ファイルを読み込もうとしています (3 セクション以上、または TLS セクションを含む IAT です)。 (HRESULT からの例外: 0x80131019)”
Source=”mscorlib”
StackTrace:
場所 System.Reflection.Assembly.nLoadImage(Byte[] rawAssembly, Byte[] rawSymbolStore, Evidence evidence, StackCrawlMark& stackMark, Boolean fIntrospection)
場所 System.AppDomain.Load(Byte[] rawAssembly, Byte[] rawSymbolStore, Evidence securityEvidence)
場所 infinity.Core.infinityCore.PluginManager..ctor(infinityCore objCore)

(´・ω・`)ショボーン
検証できないコードがあるとのご指摘のようです。

検証可能なタイプ セーフ コードの作成
http://msdn2.microsoft.com/ja-jp/library/01k04eaf(VS.80).aspx

確かに、コンパイラはC++/CLIであるし、コンパイラオプションは /clr、
つまり、アンマネージコードと混在しているため、PEVerifyでも警告が出る。

しかしながら、混在アセンブリがロードできないのでは、実用性として微妙なので、
どうにかならないものかと調べてみるが、衝撃の事実が…

以下のフォーラムを参照されたし。
http://www.dotnet247.com/247reference/msgs/26/134245.aspx

一番最後のレスポンスがトドメデアリマシタ。

意訳してみると、こんな感じでしょうか。

バイトストリームから、アンマネージコードを含むマネージアセンブリを読み込むことはできません。
CLRローダーのその部分の実装ではWindowsのLoadLibraryを使用していないため、
一切のアンマネージコードを扱うことができません。

ションボリダ…

■今日のまとめ

リフレクションでバイト配列からアセンブリをロードする場合、
対象のアセンブリは純粋なマネージコード(=検証可能なタイプセーフコード)で無ければなりません。

VB.NETやC#では、基本的には検証可能なタイプセーフコードになります。
C++/CLIの場合は、/clr:safe でコンパイルされたアセンブリが読み込めます。

■最後に

すごい長くなりましたが、これらのエラーで悩んでいた方の手助けになれば幸いです…orz

コメントなし

コメントを残す

真面目にトリビア?

たまには真面目な話題を書いてみようかと思います。

WindowsのAPIにGetCurrentProcessという関数があります。
日本語のMSDNはこちら。
http://msdn.microsoft.com/library/ja/default.asp?url=/library/ja/jpdllpro/html/_win32_getcurrentprocess.asp

さて、英語版の方を覗いてみると、一歩踏み込んで面白いことが書いてあります。
http://msdn2.microsoft.com/en-us/library/ms683179.aspx

解説(Remarks)冒頭部分を意訳するとこんな感じでしょうか。

解説:
擬似ハンドルは現在のプロセスを示す特別な定数値です。
現在では-1が常に返されますが、将来のWindowsの為に(仕様変更する可能性もあるため)
-1をそのままコードに書き込むべきではなく、この GetCurrentProcess 関数を呼び出すのが最善の方法です。

あれ? -1の値のハンドルって…

#define INVALID_HANDLE_VALUE (HANDLE)-1

…不正なハンドルを示す値と同じなのですね…
なので、念には念をと思っても、こんなことはしちゃいけない訳ですねw

HANDLE hndCurProcess = ::GetCurrentProcess();
if (hndCurProcess == INVALID_HANDLE_VALUE) {
// エラー処理;
}

あと、ハンドルを閉じようとしても駄目ですw

HANDLE hndCurProcess = ::GetCurrentProcess();
BOOL result = ::CloseHandle(hndCurProcess);

普通にエラーが返ってきちゃいますw

大概日本語版のMSDNは古いことが多いので、本家の英語版も確認しましょうというお話なのでした。
トリビアでも何でもねぇな、コレw

コメントなし

コメントを残す