今回はインポートアドレステーブルに格納されているインポートシンボルのアドレスを書き換えることでフックを行います。単にインポートシンボルのフックと書かないのは他にも方法があるからですが、それに関しては別の機会に。
前回実装した PEFile クラスでは HMODULE を引数にとるコンストラクタと、インポートシンボルをフックする HookImportSymbol() メソッドが追加されています。
PEFile.h
//! PE ファイルから情報を取得するためのクラス。 class PEFile { public: //! コンストラクタ explicit PEFile(const wchar_t* i_file_name);
//! モジュールハンドルからのコンストラクタ。 explicit PEFile(HMODULE i_module_handle);
//! デストラクタ virtual ~PEFile(); //! ロードアドレスを取得する。 BYTE* GetLoadBase(); //! DOS ヘッダへのポインタを取得。 IMAGE_DOS_HEADER* GetDosHeaderP(); //! NT ヘッダへのポインタを取得。 IMAGE_NT_HEADERS32* GetNtHeadersP(); //! ファイルヘッダへのポインタを取得。 IMAGE_FILE_HEADER* GetFileHeaderP(); //! オプショナルヘッダへのポインタを取得。 IMAGE_OPTIONAL_HEADER32* GetOptionalHeaderP(); //! i_index 番目のセクションヘッダへのポインタを取得。 IMAGE_SECTION_HEADER* GetSectionHeader(int i_index); //! i_index 番目のデータディレクトリエントリが指すデータへのポインタを取得する。 BYTE* GetDirEntryDataP(int i_index); //! i_index 番目のデータディレクトリエントリが指すデータへのサイズを取得する。 DWORD GetDirEntryDataSize(int i_index); //! インポートディレクトリへのポインタを取得する。 IMAGE_IMPORT_DESCRIPTOR* GetImportDirP(); //! インポートしている DLL 名を取得する。 std::vector<const char*> GetImportDllNames(); //! 指定した DLL からインポートしているシンボルを取得する。 std::vector<ImportSymbol> GetImportSymbols(const char* i_dll_name);
//! インポートシンボルをフックする。成功した場合 IAT エントリへのポインタを返す。 void** HookImportSymbol(void* i_old_p, void* i_new_p);
protected: //! イメージファイルをメモリにマッピングする。 static BYTE* MapImage(const wchar_t* i_file_name); protected: bool m_need_to_free; //!< m_load_base を解放する必要があるか? BYTE* m_load_base; //!< イメージのロードされたアドレス IMAGE_DOS_HEADER* m_dos_header_p; //!< DOS ヘッダへのポインタ IMAGE_NT_HEADERS* m_nt_headers_p; //!< NT ヘッダへのポインタ IMAGE_SECTION_HEADER* m_section_table_p; //!< セクションテーブルへのポインタ };
PEFile.cpp
//! モジュールハンドルからのコンストラクタ。 PEFile::PEFile(HMODULE i_module_handle) : m_need_to_free(false), // VirtualFree() の必要なし m_load_base(NULL), m_dos_header_p(NULL), m_nt_headers_p(NULL), m_section_table_p(NULL) { do { // DOS ヘッダの取得と確認。 m_dos_header_p = reinterpret_cast<IMAGE_DOS_HEADER*>(i_module_handle); if( IsBadReadPtr(m_dos_header_p, sizeof(IMAGE_DOS_HEADER)) || m_dos_header_p->e_magic != *reinterpret_cast<WORD*>("MZ") ) { _RPTF0(_CRT_WARN, "有効なモジュールハンドルではありません。"); break; } // NT ヘッダの取得と確認 m_nt_headers_p = reinterpret_cast<IMAGE_NT_HEADERS*>( reinterpret_cast<BYTE*>(i_module_handle) + m_dos_header_p->e_lfanew ); if( m_nt_headers_p->Signature != *reinterpret_cast<DWORD*>("PE\0\0") || m_nt_headers_p->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC ) { _RPTF0(_CRT_WARN, "PE ファイルではありません。\n"); break; } // セクションテーブルへのポインタを取得。 m_section_table_p = reinterpret_cast<IMAGE_SECTION_HEADER*>(m_nt_headers_p + 1); // m_load_base に i_module_handle を入れておく。 m_load_base = reinterpret_cast<BYTE*>(i_module_handle); } while( 0 ); }おもむろに引数で渡された i_module_handle を IMAGE_DOS_HEADER* にキャストしています。実は LoadLibrary() API や GetModuleHandle() が返す HMODULE, WinMain() に渡される HINSTANCE の値はイメージファイルがロードされたアドレスなのです。ですので、HMODULE から各ヘッダを取得して値をチェックすれば良いということになります。
PEFile.cpp
//! インポートシンボルをフックする。成功した場合 IAT エントリへのポインタを返す。 void** PEFile::HookImportSymbol(void* i_old_p, void* i_new_p) { void** retval = NULL; IMAGE_IMPORT_DESCRIPTOR* imp_descs_p = GetImportDirP(); if( imp_descs_p ) { for(int i = 0; imp_descs_p[i].FirstThunk != 0; i++) { // インポートアドレステーブルへのポインタを取得。 void** iat_p = GetDataP<void**>(imp_descs_p[i].FirstThunk); for(int j = 0; iat_p[j] != 0; j++) { if( iat_p[j] == reinterpret_cast<FARPROC>(i_old_p) ) { DWORD old_protect = 0; VirtualProtect(&iat_p[j], sizeof(FARPROC), PAGE_READWRITE, &old_protect); retval = &iat_p[j]; iat_p[j] = reinterpret_cast<FARPROC>(i_new_p); VirtualProtect(&iat_p[j], sizeof(FARPROC), old_protect, &old_protect); // break; // break しない。全ての IAT を走査する。 } } break; } } return retval; }全ての IAT を走査して、フックしたいインポートシンボルの場合だったら新しいもので置き換える、ということをやっています。エクスポートの転送を使ったりすることで IAT には同じインポートシンボルが複数存在する可能性があるので、書き換えたとしてもループは抜けません。
main.cpp
#include <windows.h> #include <stdio.h> #include <string> #include <vector> #include "PEFile.h" typedef int (WINAPI* MessageBoxAP)(HWND, const char*, const char*, int); //! フック前のメッセージボックス static MessageBoxAP s_prev_MessageBoxA = NULL; //! メッセージボックスを置き換える関数 static int WINAPI MyMessageBoxA( HWND i_window_handle, const char* i_message, const char* i_title, int i_type) { return s_prev_MessageBoxA( i_window_handle, "All your base are belong to us.", i_title, i_type ); } //! メインルーチン int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) //int wmain() { MessageBox(NULL, "Hello, world!", "フック前", MB_OK); s_prev_MessageBoxA = reinterpret_cast<MessageBoxAP>( GetProcAddress( GetModuleHandle("user32.dll"), "MessageBoxA" ) ); PEFile pe_file(GetModuleHandle(NULL)); pe_file.HookImportSymbol( s_prev_MessageBoxA, &MyMessageBoxA ); MessageBox(NULL, "Hello, world!", "フック後", MB_OK); return 0; }
実行結果
通常は置き換え後の関数とフック処理を DLL 内において、他プロセスにその DLL を注入するという形をとります。フック処理は DllMain() の DLL_PROCESS_ATTACH 時に行えば良いでしょう。
では、DLL を注入するにはどうすれば良いか?という話になります。DLL を他プロセスに注入するには SetWindowsHookEx() API を使う方法、CreateRemoteThread() API を使う方法、LoadLibrary() を呼び出すルーチンをフック対象プロセスに割り当てて既存スレッドをそちらにジャンプさせる方法の 3 種類があります。
SetWindowsHookEx() API を使うと良くも悪くも全てのプロセスに DLL が注入されるので、フック対象となるプロセスを絞らなければいけません。また、グローバルフックしたイベントがフック対象プロセスで発生するまで DLL がロードされません。例えば WH_GETMESSAGE とともに SetWindowsHookEx() を呼び出した場合は、フック対象プロセスのスレッドで GetMessage() や PeekMessage() が呼び出されないとフック用の DLL がロードされないのです。そのため、ウィンドウを持たないようなアプリケーションでは DLL がロードされず、フックに失敗してしまいます。さらにフック対象プロセスが生存している間、SetWindowsHookEx() を呼び出したプロセスも生存していなければいけません(GetProcAddress() でフック用 DLL 内の関数を返すため、フック対象プロセスが生存している間はフック用DLL がアンロードされてはいけない)。
CreateRemoteThread() API を使う方法ではプロセスを指定して DLL を注入することになりますが、CreateRemoteThread() API は Windows 2000 から追加された API で 98/Me 以前の OS では使用できないデメリットがあります。また、マルウェアやウィルスが自身を隠蔽するために CreateRemoteThread() を使うことが多く、アンチウィルスにひっかかってしまう場合があります。でも SetWindowsHookEx() を使うよりは簡単に実装できます。
3番目の LoadLibrary() を呼び出すルーチンをフック対象プログラムに割り当てて、既存スレッドをそちらにジャンプさせる方法に関しては、色々と厄介なので今回は扱いません。
まず、DLL の MyMessageBoxA(), MyMessageBoxW(), MyLoadLibraryA(), MyLoadLibraryW(), MyLoadLibraryExA(), MyLoadLibraryExW(), MyGetProcAddress() から。これらは SetWindowsHookEx() を使う場合も CreateRemoteThread() を使う場合も同じ処理で、MyXXX の XXX にとって代わる関数です。
LoadLibrary() 系では新たにロードされた DLL をフックしてから返します。LoadLibraryEx() 系では LOAD_LIBRARY_AS_DATAFILE フラグがセットされている場合にだけフック処理を行っています。これは LOAD_LIBRARY_AS_DATAFILE が指定されているとイメージファイルのマッピング処理が行われない(ただのファイルとして読み込まれる)上に、ロードアドレスに + 1 された値が返されるからです。まぁ、こんなことをしなくても DOS ヘッダのマジックナンバー確認時に蹴られるんですが。あとは Windows 2000 以降では DLL をロードするのに NTDLL.DLL に含まれる LdrLoadDll() を使うこともできますがそこまでしなくて良いでしょう。
MyGetProcAddress() はフックの対象となるインポートシンボルが GetProcAddress() されたときにフックで置き換える関数を返します。
DllMain.cpp
//! LoadLibraryA() に代わる関数。 static HMODULE WINAPI MyLoadLibraryA(const char* i_dll_name) { HMODULE module_handle = LoadLibraryA(i_dll_name); if( module_handle ) HookImports(PEFile(module_handle), true); return module_handle; } //============================================================================= //! LoadLibraryW() に代わる関数。 static HMODULE WINAPI MyLoadLibraryW(const wchar_t* i_dll_name) { HMODULE module_handle = LoadLibraryW(i_dll_name); if( module_handle ) HookImports(PEFile(module_handle), true); return module_handle; } //============================================================================= //! LoadLibraryExA() に代わる関数。 static HINSTANCE WINAPI MyLoadLibraryExA( const char* i_dll_name, HANDLE i_reserved, DWORD i_flags ) { HMODULE module_handle = LoadLibraryExA(i_dll_name, i_reserved, i_flags); // LOAD_LIBRARY_AS_DATAFILE フラグが指定されていない場合だけフック処理を行う if( module_handle && (i_flags & LOAD_LIBRARY_AS_DATAFILE) == 0 ) HookImports(PEFile(module_handle), true); return module_handle; } //============================================================================= //! LoadLibraryExW() に代わる関数。 static HINSTANCE WINAPI MyLoadLibraryExW( const wchar_t* i_dll_name, HANDLE i_reserved, DWORD i_flags ) { HMODULE module_handle = LoadLibraryExW(i_dll_name, i_reserved, i_flags); // LOAD_LIBRARY_AS_DATAFILE フラグが指定されていない場合だけフック処理を行う if( module_handle && (i_flags & LOAD_LIBRARY_AS_DATAFILE) == 0 ) HookImports(PEFile(module_handle), true); return module_handle; } //============================================================================= //! GetProcAddress() に代わる関数。 static FARPROC WINAPI MyGetProcAddress( HMODULE i_module_handle, const char* i_func_name ) { FARPROC retval = GetProcAddress(i_module_handle, i_func_name); for(size_t i = 0; i < numberof(s_hook_info); i++) { if( s_hook_info[i].old_func_p == retval ) { retval = reinterpret_cast<FARPROC>(s_hook_info[i].new_func_p); break; } } return retval; }
DllMain.cpp
//! DLL で共有されるセクション #pragma data_seg(".shared") static HHOOK s_hook_handle = NULL; static DWORD s_caller_process_id = 0; static DWORD s_target_process_id = 0; #pragma data_seg() #pragma comment(linker, "/SECTION:.shared,rws") //! 自 DLL のハンドル。DllMain() でセットする。 static HMODULE s_my_dll_handle = NULL; //! フックするインポートシンボルの情報 static struct { const char* dll_name; // インポートシンボルを含む DLL の名前 const char* func_name; // インポートシンボルの名前。 void* old_func_p; // インポートシンボルのポインタ。DllMain() で初期化される。 void* new_func_p; // 置き換え後のポインタ。 } s_hook_info[] = { { "kernel32.dll", "LoadLibraryA", NULL, &MyLoadLibraryA }, { "kernel32.dll", "LoadLibraryW", NULL, &MyLoadLibraryW }, { "kernel32.dll", "LoadLibraryExA", NULL, &MyLoadLibraryExA }, { "kernel32.dll", "LoadLibraryExW", NULL, &MyLoadLibraryExW }, { "kernel32.dll", "GetProcAddress", NULL, &MyGetProcAddress }, { "user32.dll", "MessageBoxA", NULL, &MyMessageBoxA }, { "user32.dll", "MessageBoxW", NULL, &MyMessageBoxW }, };s_hook_handle は SetWindowsHookEx() の戻り値を格納しておく先です。
DllMain.cpp
//! フックを開始する。 __declspec(dllexport) bool ImpHookStart(DWORD i_target_process_id) { bool retval = false; // 既に SetWindowsHookEx() が呼ばれている場合は無視する。 if( s_hook_handle == NULL ) { s_hook_handle = SetWindowsHookEx( WH_GETMESSAGE, GetMsgProc, s_my_dll_handle, 0 // target thread id ); s_caller_process_id = GetCurrentProcessId(); s_target_process_id = i_target_process_id; retval = s_hook_handle != NULL; } return retval; } //============================================================================= //! フックを終了する。 __declspec(dllexport) bool ImpHookStop() { bool retval = false; if( s_hook_handle ) { UnhookWindowsHookEx(s_hook_handle); s_hook_handle = NULL; retval = true; } return retval; }
DllMain.cpp
//! DLL のエントリポイント。フック処理を行う。 BOOL WINAPI DllMain( HINSTANCE i_module_handle, DWORD i_reason_for_call, void* ) { switch( i_reason_for_call ) { case DLL_PROCESS_ATTACH: s_my_dll_handle = reinterpret_cast<HMODULE>(i_module_handle); if( s_hook_handle && s_caller_process_id != GetCurrentProcessId() && (s_target_process_id == 0 || s_target_process_id == GetCurrentProcessId()) ) { for(size_t i = 0; i < numberof(s_hook_info); i++) { s_hook_info[i].old_func_p = GetProcAddress( GetModuleHandleA(s_hook_info[i].dll_name), s_hook_info[i].func_name ); } DebugOut(L"DLL_PROCESS_ATTACH ProcessId:%08X\n", GetCurrentProcessId()); HookImports(true); } break; case DLL_PROCESS_DETACH: if( s_hook_handle && s_caller_process_id != GetCurrentProcessId() && (s_target_process_id == 0 || s_target_process_id == GetCurrentProcessId()) ) { DebugOut(L"DLL_PROCESS_DETACH ProcessId:%08X\n", GetCurrentProcessId()); HookImports(false); } break; } return TRUE; }
DllMain.cpp
//! 全てのモジュールのインポートシンボルのフック/アンフックを行う。 static bool HookImports(bool i_hook) { bool retval = false; HANDLE snapshot_handle = INVALID_HANDLE_VALUE; MODULEENTRY32 module_entry = { sizeof(MODULEENTRY32), 0 }; do { // モジュールを列挙 snapshot_handle = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, GetCurrentProcessId() ); for(BOOL is_next = Module32First(snapshot_handle, &module_entry); is_next; is_next = Module32Next(snapshot_handle, &module_entry)) { // 自 DLL ではなかった場合のみ IAT を書き換える。 if( module_entry.hModule != s_my_dll_handle ) { HookImports(PEFile(module_entry.hModule), i_hook); } } retval = true; } while( 0 ); if( snapshot_handle != INVALID_HANDLE_VALUE ) { CloseHandle(snapshot_handle); snapshot_handle = INVALID_HANDLE_VALUE; } return retval; }
DllMain.cpp
//! 指定したモジュールのインポートシンボルのフック/アンフックを行う。 static bool HookImports(PEFile& io_pe_file, bool i_hook) { for(size_t i = 0; i < numberof(s_hook_info); i++) { if( i_hook ) { void** iat_entry_p = io_pe_file.HookImportSymbol( s_hook_info[i].old_func_p, s_hook_info[i].new_func_p ); if( iat_entry_p ) { DebugOut(L"フックしました。:%S@%S, %p\n", s_hook_info[i].func_name, s_hook_info[i].dll_name, iat_entry_p ); } } else { io_pe_file.HookImportSymbol( s_hook_info[i].new_func_p, s_hook_info[i].old_func_p ); } } return true; }
main.cpp
#define UNICODE #define _UNICODE #include <windows.h> //! フックを開始する。 __declspec(dllimport) bool ImpHookStart(DWORD i_target_process_id); //! フックを終了する。 __declspec(dllimport) bool ImpHookStop(); #pragma comment(lib, "..\\hook.lib") //============================================================================= //! エラー表示 static void Error(const wchar_t* i_format, ...) { va_list val; wchar_t message[1024 + 1] = { 0 }; va_start(val, i_format); wvsprintf(message, i_format, val); va_end(val); MessageBox(NULL, message, L"エラー", MB_ICONEXCLAMATION); } //============================================================================= //! プライマリスレッドをサスペンド状態にしてターゲットプログラムを起動する。 static bool ExecuteTarget(PROCESS_INFORMATION& o_process_information) { STARTUPINFO startup_info = { 0 }; return CreateProcess( L"HelloWorld.exe", NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &startup_info, &o_process_information ) != FALSE; } //============================================================================= //! メインルーチン int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) //int main() { PROCESS_INFORMATION process_information = { 0 }; do { // ターゲットプログラムをサスペンド状態で起動する。 if( ExecuteTarget(process_information) == false ) { Error(L"ターゲットプログラムの起動に失敗しました。\n"); break; } // フックを開始する。 if( ImpHookStart(process_information.dwProcessId) == false ) { Error(L"フックに失敗しました。"); break; } // ターゲットプログラムのスレッドを再開する。 ResumeThread(process_information.hThread); // ターゲットが終了するまで待機する。 WaitForSingleObject(process_information.hProcess, INFINITE); } while( 0 ); if( process_information.hThread != NULL ) { CloseHandle(process_information.hThread); process_information.hThread = NULL; } if( process_information.hProcess != NULL ) { CloseHandle(process_information.hProcess); process_information.hProcess = NULL; } // フックを終了する。 ImpHookStop(); return 0; }
HelloWorld.cpp
#include <windows.h> //! メインルーチン int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { while( MessageBox(NULL, "world!", "Hello", MB_YESNO) == IDYES ) { } return 0; }
CreateRemoteThread() を使う方法では SetWindowsHookEx() 関連の処理やグローバル変数がなくなっています。静的グローバル変数は s_hook_info のみとなり、DllMain() でも対象プロセスの判別がなくなっています。その他は SetWindowsHookEx() を使う方法と変わっていません。が、SetWindowsHookEx() を使う場合と比べて随分すっきりしました。
DllMain.cpp
//! DLL のエントリポイント。フック処理を行う。 BOOL WINAPI DllMain( HINSTANCE i_module_handle, DWORD i_reason_for_call, void* ) { switch( i_reason_for_call ) { case DLL_PROCESS_ATTACH: DebugOut(L"DLL_PROCESS_ATTACH ProcessId:%08X\n", GetCurrentProcessId()); for(size_t i = 0; i < numberof(s_hook_info); i++) { s_hook_info[i].old_func_p = GetProcAddress( GetModuleHandleA(s_hook_info[i].dll_name), s_hook_info[i].func_name ); } HookImports(i_module_handle, true); break; case DLL_PROCESS_DETACH: DebugOut(L"DLL_PROCESS_DETACH ProcessId:%08X\n", GetCurrentProcessId()); HookImports(i_module_handle, false); break; } return TRUE; }
main.cpp
//! メインルーチン int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) //int main() { PROCESS_INFORMATION process_information = { 0 }; do { // ターゲットプログラムをサスペンド状態で起動する。 if( ExecuteTarget(process_information) == false ) { Error(L"ターゲットプログラムの起動に失敗しました。\n"); break; } // hook.dll をロードさせる。 InjectHookDll(process_information.hProcess); // スレッドを開始する。 ResumeThread(process_information.hThread); } while( 0 ); if( process_information.hThread != NULL ) { CloseHandle(process_information.hThread); process_information.hThread = NULL; } if( process_information.hProcess != NULL ) { CloseHandle(process_information.hProcess); process_information.hProcess = NULL; } return 0; }
main.cpp
//! hook.dll をターゲットプロセスにロードさせる。 static bool InjectHookDll(HANDLE i_process_handle) { const wchar_t* DLL_NAME = L"hook.dll"; const size_t DLL_NAME_SIZE = (lstrlen(DLL_NAME) + 1) * sizeof(wchar_t); bool retval = false; void* dll_name_vp = NULL; DWORD written_size = 0; void* LoadLibraryW_p = NULL; DWORD thread_id = 0; HANDLE thread_handle = NULL; DWORD exit_code = 0; do { // ターゲットプロセス内に DLL 名を格納するためのメモリを確保。 dll_name_vp = VirtualAllocEx( i_process_handle, NULL, DLL_NAME_SIZE, MEM_COMMIT, PAGE_READWRITE ); if( dll_name_vp == NULL ) { Error(L"ターゲットプロセスでのメモリの割り当てに失敗しました。\n"); break; } // DLL 名を書き込み if( WriteProcessMemory( i_process_handle, dll_name_vp, DLL_NAME, DLL_NAME_SIZE, &written_size ) == FALSE ) { Error(L"DLL 名の書き込みに失敗しました。\n"); break; } // LoadLibraryW のアドレスを取得 LoadLibraryW_p = GetProcAddress( GetModuleHandle(L"kernel32.dll"), "LoadLibraryW" ); // DLL をロードさせる。 thread_handle = CreateRemoteThread( i_process_handle, NULL, 0, reinterpret_cast<LPTHREAD_START_ROUTINE>(LoadLibraryW_p), dll_name_vp, 0, &thread_id ); if( thread_handle == NULL ) { Error(L"CreateRemoteThread() に失敗しました。\n"); break; } // 終了するまで待機。 WaitForSingleObject(thread_handle, INFINITE); GetExitCodeThread(thread_handle, &exit_code); retval = exit_code != 0; } while( 0 ); // スレッドハンドルを閉じる。 if( thread_handle ) { CloseHandle(thread_handle); thread_handle = NULL; } // DLL 名用に割り当てたメモリを解放する。 if( dll_name_vp ) { VirtualFreeEx(i_process_handle, dll_name_vp, 0, MEM_RELEASE); dll_name_vp = NULL; } return retval; }
HANDLE CreateRemoteThread( HANDLE hProcess, // 新しいスレッドを稼働させるプロセスを識別するハンドル LPSECURITY_ATTRIBUTES lpThreadAttributes, // スレッドのセキュリティ属性へのポインタ DWORD dwStackSize, // 初期のスタックサイズ (バイト数) LPTHREAD_START_ROUTINE lpStartAddress, // スレッド関数へのポインタ LPVOID lpParameter, // 新しいスレッドの引数へのポインタ DWORD dwCreationFlags, // 作成フラグ LPDWORD lpThreadId // 取得したスレッド識別子へのポインタ );
DWORD WINAPI ThreadProc(
LPVOID lpParameter // スレッドのデータ
);
この ThreadProc() はポインタ型のパラメータを一つ受け取り、32bit の整数値を返す WINAPI(__stdcall) 規約の関数ということが分かります。
HMODULE WINAPI LoadLibrary(
LPCTSTR lpFileName // モジュールのファイル名
);