windows_指定したexeに対してDLLインジェクションでコピペを検出する
前回は自分自身のプロセスに対してDLLインジェクションを実行しメッセージボックスの表示内容を変更しました。今回は指定したEXEに対してコピー&ペーストを検出できるようにしたいと思います。
それではDLL側のプロジェクトをまず作成していきます。
DLLプロジェクトの作成
DLLが読み込まれたときに実行する処理は以下の様になります。文字セットはマルチバイト文字セットを指定しています。
dllmain.cpp
// dllmain.cpp : DLL アプリケーションのエントリ ポイントを定義します。 #include "stdafx.h" #include "dllmain.h" #include <string> #include <process.h> #include <Windows.h> #include <psapi.h> void logWrite(std::string content); std::string getLogHeader(); std::string getCurrentName(); BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { HANDLE hThread; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: hThread = CreateThread(NULL, 0, apiHook, (LPVOID)NULL, 0, NULL); if (hThread == NULL) { MessageBox(NULL, _TEXT("CreateThread"), _TEXT("Error"), MB_OK); return FALSE; } CloseHandle(hThread); break; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; } DWORD WINAPI apiHook(LPVOID pData) { std::string logContent; HMODULE baseAddr = GetModuleHandle(NULL); DWORD dwIdataSize; PIMAGE_IMPORT_DESCRIPTOR pImgDesc = (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToData(baseAddr, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &dwIdataSize); while (pImgDesc->Name) { char* lpModule = (char*)(baseAddr)+pImgDesc->Name; if (!_stricmp(lpModule, MODULE_DLL)) { break; } pImgDesc++; } if (!pImgDesc->Name) { return -1; } PIMAGE_THUNK_DATA pIAT, pINT; pIAT = (PIMAGE_THUNK_DATA)((char*)baseAddr + pImgDesc->FirstThunk); pINT = (PIMAGE_THUNK_DATA)((char*)baseAddr + pImgDesc->OriginalFirstThunk); logContent += getLogHeader() + "use32.dll\n"; while (pIAT->u1.Function) { if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal))continue; PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)((char*)baseAddr + (DWORD)pINT->u1.AddressOfData); logContent += " "; logContent += ((const char*)pImportName->Name); logContent += "\n"; DWORD dwOldProtect; if (!_stricmp((const char*)pImportName->Name, "MessageBoxA")) { VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect); pIAT->u1.Function = (ULONGLONG)Hook_MessageBoxA; VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), dwOldProtect, &dwOldProtect); } else if (!_stricmp((const char*)pImportName->Name, "MessageBoxW")) { VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect); pIAT->u1.Function = (ULONGLONG)Hook_MessageBoxW; VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), dwOldProtect, &dwOldProtect); } else if (!_stricmp((const char*)pImportName->Name, "SetClipboardData")) { VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect); pIAT->u1.Function = (ULONGLONG)Hook_SetClipboardData; VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), dwOldProtect, &dwOldProtect); } else if (!_stricmp((const char*)pImportName->Name, "GetClipboardData")) { VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect); pIAT->u1.Function = (ULONGLONG)Hook_GetClipboardData; VirtualProtect(&pIAT->u1.Function, sizeof(DWORD), dwOldProtect, &dwOldProtect); } pIAT++; pINT++; } logWrite(logContent); return 0; } int WINAPI Hook_MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { MESSAGEBOXA_FUNC MsgBoxAProc; if ((MsgBoxAProc = (MESSAGEBOXA_FUNC)GetProcAddress(GetModuleHandle(_T("user32")), "MessageBoxA")) == NULL) { //GetProcAddress Error } return MsgBoxAProc(hWnd, "hook", "hook", uType); } int WINAPI Hook_MessageBoxW( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) { MESSAGEBOXW_FUNC MsgBoxWProc; if ((MsgBoxWProc = (MESSAGEBOXW_FUNC)GetProcAddress(GetModuleHandle(_T("user32")), "MessageBoxW")) == NULL) { //GetProcAddress Error } return MsgBoxWProc(hWnd,L"hook" , L"hook", uType); } HANDLE WINAPI Hook_SetClipboardData(UINT uFormat, HANDLE hMem) { SETCLIPBOARD_FUNC SetClipBoardFunc; if ((SetClipBoardFunc = (SETCLIPBOARD_FUNC)GetProcAddress(GetModuleHandle(_T("user32")), "SetClipboardData")) == NULL) { //GetProcAddress Error } if (uFormat == CF_TEXT || uFormat == CF_OEMTEXT) { std::string logContent = getLogHeader() + " setClipBoardData : "; LPCSTR lpStr = (LPSTR)GlobalLock(hMem); logContent.append(lpStr); logWrite(logContent); } HANDLE nResult = SetClipBoardFunc(uFormat, hMem); return nResult; } HANDLE WINAPI Hook_GetClipboardData(UINT uFormat) { GETCLIPBOARD_FUNC GetClipBoardFunc; if ((GetClipBoardFunc = (GETCLIPBOARD_FUNC)GetProcAddress(GetModuleHandle(_T("user32")), "GetClipboardData")) == NULL) { //GetProcAddress Error } std::string logContent = getLogHeader() + " getClipBoardData : "; HANDLE resultForLog = GetClipBoardFunc(CF_TEXT); LPCSTR lpStr = (LPSTR)GlobalLock(resultForLog); logContent.append(lpStr); logWrite(logContent); HANDLE nResult = GetClipBoardFunc(uFormat); return nResult; } void logWrite(std::string content) { FILE* file; fopen_s(&file, "C:\\Users\\**********\\Desktop\\hook_report.txt", "a"); content += "\n"; fprintf(file, content.c_str()); fclose(file); } std::string getLogHeader() { return "プロセスID : " + std::to_string(_getpid()) + " ウィンドウ名: " + getCurrentName(); } std::string getCurrentName() { std::string ret = ""; // プロセスハンドルをオープン HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, _getpid()); if (NULL != hProcess) { // プロセス名の受け取り場所 TCHAR waBaseName[MAX_PATH]; // プロセスハンドルからプロセス名を取得します。 ::GetModuleBaseName(hProcess, NULL, waBaseName, _countof(waBaseName)); // プロセス名の表示 ret = std::string(waBaseName); // プロセスハンドルのクローズ ::CloseHandle(hProcess); } return ret; }
それから、これのヘッダファイルは以下になります。
dllmain.h
#pragma once #include <windows.h> #include <tchar.h> #include <dbghelp.h> #pragma comment(lib, "dbghelp.lib") #define MODULE_DLL "user32.dll" typedef int (WINAPI *MESSAGEBOXA_FUNC)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); typedef int (WINAPI *MESSAGEBOXW_FUNC)(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType); typedef HANDLE (WINAPI *SETCLIPBOARD_FUNC)(UINT uFormat, HANDLE hMem); typedef HANDLE (WINAPI *GETCLIPBOARD_FUNC)(UINT uFormat); DWORD WINAPI apiHook(LPVOID pData); int WINAPI Hook_MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); int WINAPI Hook_MessageBoxW( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType); HANDLE WINAPI Hook_SetClipboardData(UINT uFormat, HANDLE hMem); HANDLE WINAPI Hook_GetClipboardData(UINT uFormat);
各処理について、前回はメッセージボックス表示用の関数をフックさせていましたが、今回はコピー&ペーストをフックさせるということでsetClipboardDataとgetClipboardDataに対してIATフックしています。setClipboardDataとgetClipboardData関数のインターフェースは以下の公式のドキュメントを参照し、関数ポインタの型に反映させます。
https://msdn.microsoft.com/ja-jp/library/cc430086.aspx
https://msdn.microsoft.com/ja-jp/library/cc429794.aspx
今回は動作確認のためにuser32.dllのインポートアドレステーブル上にある関数名とコピペを実行したときの内容を出力するようにしています。 setClipboardDataをフックしたときに呼び出す処理としてHook_SetClipboardData関数を実装しています、ここではコピーしたデータのタイプがCF_TEXT, CF_OEMTEXTの時のみ内容をファイルに書き込むようにしています。 それからgetClipboardDataをフックしたときに呼び出す処理としてHook_GetClipboardData関数を実装していますが、ここではその時点のクリップボードのテキストデータをログファイルに書き込んでいるのですが実際は画像の貼り付けもあり得るので正しくはデータのフォーマットも気にする必要があります。
DLLのプロジェクトはこの2つのファイルを作成したらあとはビルドして大丈夫です。
次にEXE指定でDLLを読み込ませるプロジェクトを作成したいと思います。文字セットはマルチバイト文字セットを指定しています。
DLLを読み込ませるプロジェクトの作成
DLLを読み込ませる処理の実装は以下になります。
DllInject2.cpp
#include "stdafx.h" #include "DllInject2.h" #include <tchar.h> #include <tlhelp32.h> #include <string> TCHAR dllPath[256] = "C:\\Users\\****\\source\\repos\\dll_inject\\Release\\dll_inject.dll"; int dllPathLen = (lstrlen(dllPath) + 1) * sizeof(TCHAR); bool injectByProcessId(HWND hWnd, int targetProcId); bool DllInject2HookStart(HWND hWnd, LPCSTR targetExe) { int processIdCounter = 0; //search target process HANDLE hSnapShot; if ((hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)) == INVALID_HANDLE_VALUE) { //CreateToolhelp32Snapshot Error MessageBox(NULL, _T("CreateToolhelp32Snapshot"), _T("Error"), MB_OK); return false; } PROCESSENTRY32 pEntry; pEntry.dwSize = sizeof(pEntry); BOOL result = Process32First(hSnapShot, &pEntry); while (result) { if (lstrcmp(_T(targetExe), pEntry.szExeFile) == 0) { injectByProcessId(hWnd, pEntry.th32ProcessID); processIdCounter++; } result = Process32Next(hSnapShot, &pEntry); } CloseHandle(hSnapShot); if (processIdCounter == 0) { MessageBox(NULL, _T("Process Not Found."), _T("Error"), MB_OK); return false; } } bool injectByProcessId(HWND hWnd, int targetProcId) { //open target process HANDLE hTargetProc; if ((hTargetProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, targetProcId)) == NULL) { //OpenProcess Error MessageBox(NULL, _T("OpenProcess"), _T("Error"), MB_OK); return false; } //VirtualAlloc PWSTR memAddr; if ((memAddr = (PWSTR)VirtualAllocEx(hTargetProc, NULL, dllPathLen, MEM_COMMIT, PAGE_READWRITE)) == NULL) { //VirtualAllocEX Error MessageBox(NULL, _T("VirtualAllocEx"), _T("Error"), MB_OK); return false; } //WriteProcessMemory if (WriteProcessMemory(hTargetProc, memAddr, (PVOID)dllPath, dllPathLen, NULL) == 0) { //WriteProcessMemory Error MessageBox(NULL, _T("WriteProcessMemory"), _T("Error"), MB_OK); return false; } FARPROC LoadLibFunc; if ((LoadLibFunc = GetProcAddress(GetModuleHandle(_T("Kernel32")), "LoadLibraryA")) == NULL) { //GetProcAddress Error MessageBox(NULL, _T("GetProcAddress"), _T("Error"), MB_OK); return false; } //CreateRemoteThread HANDLE hThread; if ((hThread = CreateRemoteThread(hTargetProc, NULL, 0, (PTHREAD_START_ROUTINE)LoadLibFunc, memAddr, 0, NULL)) == NULL) { //CreateRemoteThread Error MessageBox(NULL, _T("CreateRemoteThread"), _T("Error"), MB_OK); return false; } std::string message = "success process_id:"; message.append(std::to_string(targetProcId)); MessageBox(hWnd, message.c_str(), "dll atach", MB_OK); return true; }
それから、これのヘッダファイルは以下になります。
DllInject2.h
#pragma once #include<Windows.h> bool DllInject2HookStart(HWND hWnd, LPCSTR text);
DLLインジェクションはDllInject2HookStart関数で開始されており、自分自身のハンドルとEXE名を受け取っていまして、自分自身のハンドルはエラーメッセージ表示のために使い、EXE名はフックする対象のものを受け取っています。
DllInject2HookStart関数ではすべてのプロセスでループさせてEXE名が指定したものであればinjectByProcessId関数を実行するようにしています。injectByProcessId関数はプロセスID指定でDLLを読み込ませています。大まかな処理としては以下のようになっています。
- OpenProcess対象のプロセスのアクセス権を取得
- VirtualAllocExで対象のプロセスのメモリに対して書き込み領域を確保
- WriteProcessMemoryでDLLプロジェクトが生成したDLLを書き込んでいます。
- GetProcAddressでLoadLibraryを実行したときに最初に実行されるDllMain関数のアドレスを取得しています(WriteProcessMemoryを実行した場合は、DllMainが自動で開始されません)
- CreateRemoteThreadでLoadLibraryを実行しています、これがうまく行けばDLLインジェクションは成功です。
それから、このソフトのフォームですがEXE名を入力するテキストエリアとDLLインジェクションを開始させるボタンがあれば良いので以下のようにしています。
Form1.h
#pragma once #include "DllInject2.h" #include <msclr\marshal.h> #include <random> #include <string> #include <process.h> #include<iostream> #include<fstream> #pragma comment(lib, "User32.lib") namespace CppCLR_WinformsProjekt { using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; using namespace msclr::interop; /// <summary> /// Zusammenfassung f・ Form1 /// </summary> public ref class Form1 : public System::Windows::Forms::Form { public: Form1(void) { InitializeComponent(); // //TODO: Konstruktorcode hier hinzuf・en. // } protected: /// <summary> /// Verwendete Ressourcen bereinigen. /// </summary> ~Form1() { if (components) { delete components; } } private: System::Windows::Forms::Button^ button1; private: System::Windows::Forms::Button^ button2; private: System::Windows::Forms::Label^ label1; private: System::Windows::Forms::Button^ button3; private: System::Windows::Forms::TextBox^ textBox1; protected: private: /// <summary> /// Erforderliche Designervariable. /// </summary> System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code /// <summary> /// Erforderliche Methode f・ die Designerunterst・zung. /// Der Inhalt der Methode darf nicht mit dem Code-Editor ge舅dert werden. /// </summary> void InitializeComponent(void) { this->button1 = (gcnew System::Windows::Forms::Button()); this->button2 = (gcnew System::Windows::Forms::Button()); this->label1 = (gcnew System::Windows::Forms::Label()); this->button3 = (gcnew System::Windows::Forms::Button()); this->textBox1 = (gcnew System::Windows::Forms::TextBox()); this->SuspendLayout(); // // button1 // this->button1->Location = System::Drawing::Point(33, 53); this->button1->Name = L"button1"; this->button1->Size = System::Drawing::Size(101, 40); this->button1->TabIndex = 0; this->button1->Text = L"start"; this->button1->UseVisualStyleBackColor = true; this->button1->Click += gcnew System::EventHandler(this, &Form1::button1_Click); // // button2 // this->button2->Location = System::Drawing::Point(152, 53); this->button2->Name = L"button2"; this->button2->Size = System::Drawing::Size(101, 40); this->button2->TabIndex = 1; this->button2->Text = L"stop"; this->button2->UseVisualStyleBackColor = true; this->button2->Click += gcnew System::EventHandler(this, &Form1::button2_Click); // // label1 // this->label1->AutoSize = true; this->label1->Font = (gcnew System::Drawing::Font(L"MS UI Gothic", 22)); this->label1->Location = System::Drawing::Point(37, 9); this->label1->Name = L"label1"; this->label1->Size = System::Drawing::Size(156, 30); this->label1->TabIndex = 2; this->label1->Text = L"HOOK OFF"; this->label1->Click += gcnew System::EventHandler(this, &Form1::label1_Click); // // button3 // this->button3->Location = System::Drawing::Point(33, 100); this->button3->Name = L"button3"; this->button3->Size = System::Drawing::Size(220, 48); this->button3->TabIndex = 3; this->button3->Text = L"click"; this->button3->UseVisualStyleBackColor = true; this->button3->Click += gcnew System::EventHandler(this, &Form1::button3_Click_1); // // textBox1 // this->textBox1->Location = System::Drawing::Point(39, 161); this->textBox1->Name = L"textBox1"; this->textBox1->Size = System::Drawing::Size(213, 19); this->textBox1->TabIndex = 4; this->textBox1->TextChanged += gcnew System::EventHandler(this, &Form1::textBox1_TextChanged); // // Form1 // this->AutoScaleDimensions = System::Drawing::SizeF(6, 12); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; this->ClientSize = System::Drawing::Size(283, 189); this->Controls->Add(this->textBox1); this->Controls->Add(this->button3); this->Controls->Add(this->label1); this->Controls->Add(this->button2); this->Controls->Add(this->button1); this->Name = L"Form1"; this->Text = L"Form1"; this->ResumeLayout(false); this->PerformLayout(); } #pragma endregion private: System::Void button1_Click(System::Object^ sender, System::EventArgs^ e) { System::String^ text = static_cast<System::String^>(this->textBox1->Text); marshal_context context; LPCTSTR lpcstr = context.marshal_as<const TCHAR*>(text); HWND hWnd = static_cast<HWND>(this->Handle.ToPointer()); if( DllInject2HookStart(hWnd, lpcstr)) { this->label1->Text = L"HOOK ON"; } } private: System::Void button2_Click(System::Object^ sender, System::EventArgs^ e) { HANDLE resultForLog = GetClipboardData(CF_TEXT); //HWND hWnd = static_cast<HWND>(this->Handle.ToPointer()); //if (DllInjectHookEnd(hWnd)) { // this->label1->Text = L"HOOK OFF"; //} } private: System::Void richTextBox1_TextChanged(System::Object^ sender, System::EventArgs^ e) { } private: System::Void label1_Click(System::Object^ sender, System::EventArgs^ e) { } private: System::Void button3_Click_1(System::Object^ sender, System::EventArgs^ e) { MessageBoxW(NULL, L"not hook", L"not hook", MB_OK); //MessageBox::Show("not hook", // "not hook", MessageBoxButtons::OKCancel, // MessageBoxIcon::Asterisk); } private: System::Void textBox1_TextChanged(System::Object^ sender, System::EventArgs^ e) { } }; }
これで動かすと以下のように、フックしたソフトのiatテーブルに存在するuser32.dll内の関数とコピペの内容を確認することができます。
また、今回のDLLインジェクションをメモ帳など64bitのソフトウエアに実行してみると失敗してしまいますが、64bitようにビルドし直せばDLLインジェクションには成功します。
- DLL生成プロジェクトにてプロジェクトのプロパティ -> 構成マネージャ の画面でアクティブソリューションプラットフォームにx64を指定して、ソリューションをビルドし直す
- DLLを読み込ませるプロジェクトでは読み込ませるDLLとして↑で生成したものを指定する
- DLLを読み込ませるプロジェクトでも同様にアクティブソリューションプラットフォームにx64を指定して、ソリューションをビルドし直す
これで64bitのソフトでもDLLインジェクションができるようになります。試しにメモ帳で実行したらDLLインジェクションは成功するのですがコピペの検出には失敗しています。ログを確認したところどうやらインポートアドレステーブル上にuser32.dllのsetClipBoardData, getClipboardDataの関数が存在しないためiatフックは行えていないことが確認できます。このような場合はシステムのプロセス自体にDLLインジェクションを実行すれば良いのでしょうか。
プロセスID : 18280 ウィンドウ名: notepad.exeuse32.dll SetDlgItemTextW GetDlgItemTextW EndDialog SendDlgItemMessageW GetDlgCtrlID WinHelpW GetCursorPos ScreenToClient ChildWindowFromPoint GetParent GetWindowPlacement CharUpperW GetSystemMenu LoadAcceleratorsW SetWindowLongW RegisterWindowMessageW LoadCursorW CreateWindowExW SetWindowPlacement LoadImageW RegisterClassExW SetScrollPos InvalidateRect UpdateWindow GetWindowTextLengthW GetWindowLongW CloseClipboard GetWindowTextW EnableWindow CreateDialogParamW DrawTextExW GetSystemMetrics SetWindowPos GetAncestor FindWindowW SetForegroundWindow GetMenuState SetWindowTextW UnhookWinEvent DispatchMessageW TranslateMessage TranslateAcceleratorW IsDialogMessageW GetMessageW SetWinEventHook CharNextW GetKeyboardLayout GetForegroundWindow MessageBeep DestroyWindow PostQuitMessage IsIconic DefWindowProcW IsClipboardFormatAvailable PeekMessageW OpenClipboard LoadStringW SetActiveWindow SetCursor ReleaseDC GetDC ShowWindow GetClientRect MessageBoxW GetFocus LoadIconW DialogBoxParamW SetFocus GetSubMenu EnableMenuItem GetMenu PostMessageW MoveWindow SendMessageW CheckMenuItem