その昔、Wizard Bibleで、バイナリプロテクション
という記事を書いたのですが、.NET アプリケーションのロードがうまくいかないという問い合わせを受けたので、
調べてみました。
.NET アプリケーションをパックおよび実行すると「このアプリケーションを実行するランタイムのバージョン
が見つかりません。」というエラーが表示されて正しく起動されません。
上記のとおり、XP 以降の mscoree::_CorExeMain() はただのお飾りになってしまったようなので、
本来の動きを自分で実装してやらなければいけません。とは言っても、自インスタンスのヘッダを書き換えたり
する必要はなく、
「メモリに読み込んだアセンブリイメージがあるので、それを読み込んでください」と .NET ランタイムに
命令するので Smart EXE のローダや _CorExeMain() とは若干異なります。
今回は暗号化など一切せずに EXE そのままをメモリに読み込んで、それを実行する、という形をとります。
処理のほとんどは main.cpp の Main() 関数に書かれています。わざわざ main() や WinMain() ではなく
Main() にしているのは main()/WinMain() の切り替えを簡単にするためとデバッグのためです。
main.cpp
274 : //! 実際のメインルーチン。 275 : static int Main() 276 : { 277 : ICorRuntimeHost* host_cp = NULL; 278 : IUnknown* unknown_domain_cp = NULL; 279 : mscorlib::_AppDomain* app_domain_cp = NULL; 280 : mscorlib::_Assembly* assembly_cp = NULL; 281 : SAFEARRAY* assembly_data_hp = NULL; 282 : SAFEARRAY* parameters_hp = NULL; 283 : HRESULT hr = E_FAIL; 284 : 285 : do 286 : { 287 : // 共通言語ランタイムの初期化。 288 : hr = CorBindToRuntimeEx( 289 : NULL, 290 : NULL, 291 : 0, 292 : CLSID_CorRuntimeHost, 293 : IID_ICorRuntimeHost, 294 : (void**)&host_cp 295 : ); 296 : if( FAILED(hr) ) 297 : { 298 : _RPTF1(_CRT_WARN, "* ERROR * : unable to bind to runtime : 0x%08X.\n", hr); 299 : break; 300 : } 301 : 302 : // 共通言語ランタイムを開始。 303 : hr = host_cp->Start(); 304 : if( FAILED(hr) ) 305 : { 306 : _RPTF1(_CRT_WARN, "* ERROR * : unable to start : 0x%08X.\n", hr); 307 : break; 308 : } 309 : 310 : // プロセスのデフォルトドメインを取得。 311 : hr = host_cp->GetDefaultDomain(&unknown_domain_cp); 312 : if( FAILED(hr) ) 313 : { 314 : _RPTF1(_CRT_WARN, 315 : "* ERROR * : unable to get default domain : 0x%08X.\n", hr 316 : ); 317 : break; 318 : } 319 : 320 : // QueryInterface() で _AppDomain* を取得。 321 : hr = unknown_domain_cp->QueryInterface( 322 : __uuidof(mscorlib::_AppDomain), 323 : (void**)&app_domain_cp 324 : ); 325 : if( FAILED(hr) ) 326 : { 327 : _RPTF1(_CRT_WARN, "* ERROR * : unable to query interface : 0x%08X.\n", hr); 328 : break; 329 : } 330 : 331 : // アセンブリファイルの読込。 332 : wchar_t file_path[MAX_PATH] = { 0 }; 333 : GetAssemblyFilePath(file_path, numberof(file_path)); 334 : assembly_data_hp = LoadAssemblyFile(file_path); 335 : if( assembly_data_hp == NULL ) 336 : { 337 : _RPTF0(_CRT_WARN, "* ERROR * : unable to load assembly.\n"); 338 : break; 339 : } 340 : 341 : // _AppDomain にロードさせる。 342 : assembly_cp = app_domain_cp->Load_3(assembly_data_hp); 343 : if( assembly_cp == NULL ) 344 : { 345 : _RPTF0(_CRT_WARN, "* ERROR * : unable to load_3 : 0x%08X.\n"); 346 : break; 347 : } 348 : 349 : // cElements は引数の数を表す。引数(string[])をとる場合は 350 : // パラメータを渡さなければいけない。 351 : if( assembly_cp->EntryPoint->GetParameters()->rgsabound[0].cElements ) 352 : parameters_hp = CreateParameters(); 353 : 354 : // エントリポイントを呼び出す。 355 : try 356 : { 357 : assembly_cp->EntryPoint->Invoke_3(_variant_t(), parameters_hp); 358 : } 359 : catch(...) 360 : { 361 : _RPTF0(_CRT_WARN, "* ERROR * : exception occurred.\n"); 362 : } 363 : } 364 : while( 0 ); 365 : 366 : // パラメータとして生成した SAFEARRAY を解放。 367 : DestroyParameters(parameters_hp); 368 : // アセンブリファイル用に生成した SAFEARRAY を解放。 369 : UnloadAssemblyFile(assembly_data_hp); 370 : // 各インターフェイスの Release()。_Assembly は解放しなくて良いみたい。 371 : // RELEASE(assembly_cp); 372 : RELEASE(unknown_domain_cp); 373 : RELEASE(app_domain_cp); 374 : if( host_cp ) 375 : { 376 : host_cp->Stop(); 377 : RELEASE(host_cp); 378 : } 379 : 380 : return 0; 381 : }
main.cpp
53 : //! アセンブリファイルを読み込んで BYTE の SAFEARRAY* として返す。 54 : static SAFEARRAY* LoadAssemblyFile(const wchar_t* i_file_path) 55 : { 56 : SAFEARRAY* retval = NULL; 57 : SAFEARRAY* safe_array_hp = NULL; 58 : HANDLE file_handle = INVALID_HANDLE_VALUE; 59 : LARGE_INTEGER file_size = { 0 }; 60 : HRESULT hr = E_FAIL; 61 : 62 : do 63 : { 64 : // ファイルを開く。 65 : file_handle = CreateFile( 66 : i_file_path, 67 : GENERIC_READ, 68 : FILE_SHARE_READ, 69 : NULL, 70 : OPEN_EXISTING, 71 : FILE_ATTRIBUTE_NORMAL, 72 : NULL 73 : ); 74 : if( file_handle == INVALID_HANDLE_VALUE ) 75 : { 76 : _RPTF2(_CRT_WARN, 77 : "* ERROR * : unable to open file[%S] : 0x%08X.\n", 78 : i_file_path, GetLastError() 79 : ); 80 : break; 81 : } 82 : 83 : // ファイルサイズを取得する。 84 : if( GetFileSizeEx(file_handle, &file_size) == FALSE ) 85 : { 86 : _RPTF1(_CRT_WARN, 87 : "* ERROR * : unable to get file size : 0x%08X.\n", 88 : GetLastError() 89 : ); 90 : break; 91 : } 92 : 93 : // ファイルサイズをチェック。 94 : if( file_size.HighPart || file_size.LowPart > LONG_MAX 95 : || file_size.LowPart == 0 ) 96 : { 97 : _RPTF0(_CRT_WARN, "* ERROR * : invalid file size.\n"); 98 : break; 99 : } 100 : 101 : // メモリの確保 102 : BYTE* p = NULL; 103 : safe_array_hp = SafeArrayCreateVector(VT_UI1, 0, file_size.LowPart); 104 : 105 : // 配列へのアクセスを開始。 106 : hr = SafeArrayAccessData(safe_array_hp, (void**)&p); 107 : if( FAILED(hr) ) 108 : { 109 : _RPTF1(_CRT_WARN, "* ERROR * : unable to access data : 0x%08X.\n", hr); 110 : break; 111 : } 112 : 113 : // ファイルの読込 114 : DWORD read_bytes = 0; 115 : if( ReadFile(file_handle, p, file_size.LowPart, &read_bytes, NULL) ) 116 : { // 読込に成功した場合は retval に safe_array_hp をセット。 117 : retval = safe_array_hp; 118 : } 119 : 120 : // 配列へのアクセスを終了。 121 : SafeArrayUnaccessData(safe_array_hp); 122 : p = NULL; 123 : } 124 : while( 0 ); 125 : 126 : // 失敗した状態で safe_array_hp が生成されている場合、破棄する。 127 : if( retval == NULL && safe_array_hp ) 128 : { 129 : SafeArrayDestroy(safe_array_hp); 130 : safe_array_hp = NULL; 131 : } 132 : 133 : // ファイルを閉じる 134 : if( file_handle != INVALID_HANDLE_VALUE ) 135 : { 136 : CloseHandle(file_handle); 137 : file_handle = INVALID_HANDLE_VALUE; 138 : } 139 : 140 : return retval; 141 : }
main.cpp
349 : // cElements は引数の数を表す。引数(string[])をとる場合は 350 : // パラメータを渡さなければいけない。 351 : if( assembly_cp->EntryPoint->GetParameters()->rgsabound[0].cElements ) 352 : parameters_hp = CreateParameters(); 353 : 354 : // エントリポイントを呼び出す。 355 : try 356 : { 357 : assembly_cp->EntryPoint->Invoke_3(_variant_t(), parameters_hp);
面白くないのが、mscorlib::_Assembly::Load_3() に渡すアセンブリデータが EXE そのまんまという点です。 これだとここにブレークポイントを仕掛けてやれば簡単にアンパックできてしまいます。 「UnhandledExceptionFilter()を使ったアンチデバッギング手法」や「バイナリプロテクション3」と併用することで アンパックしにくくしておきましょう。というか、このローダ自体をパックすれば良いかな。